-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adapter for FoundationDB #51
Comments
Hey @fire, well I haven't worked with FoundationDB yet, but overall, I'd suggest:
Anything I can help you with, just let me know. On the other hand, it is great to know you are interested to contribute with an adapter like this 👍 |
Any progress with the adapter? Do you have a GH repo to take a look at it? or is it a private project? |
I only have limited bandwidth to work on this, so the plan is to get a few hours in the weekend. |
Cool, so in the meantime, I will close this issue! |
I have today to play with this. https://github.com/ananthakumaran/fdb Is the elixir client. Question: Should I use the direct foundation database layers or through a mongodb translation?
Note: Everything in fdb is in a transaction. Batching? |
init# https://github.com/ananthakumaran/fdb#getting-started
:ok = FDB.start(610)
db = FDB.Database.create(cluster_file_path)
FDB.Database.transact(db, fn transaction ->
value = FDB.Transaction.get(transaction, key)
:ok = FDB.Transaction.set(transaction, key, value <> "hello")
end) |
Get and set reference code: # https://github.com/ananthakumaran/fdb#coder
alias FDB.{Transaction, Database, KeySelectorRange}
alias FDB.Coder.{Integer, Tuple, NestedTuple, ByteString, Subspace}
coder =
Transaction.Coder.new(
Subspace.new(
{"ts", ByteString.new()},
Tuple.new({
# date
NestedTuple.new({
# year, month, date
NestedTuple.new({Integer.new(), Integer.new(), Integer.new()}),
# hour, minute, second
NestedTuple.new({Integer.new(), Integer.new(), Integer.new()})
}),
# website
ByteString.new(),
# page
ByteString.new(),
# browser
ByteString.new()
})
),
Integer.new()
)
db = Database.create(%{coder: coder})
Database.transact(db, fn t ->
m = Transaction.get(t, {{{2018, 03, 01}, {1, 0, 0}}, "www.github.com", "/fdb", "mozilla"})
c = Transaction.get(t, {{{2018, 03, 01}, {1, 0, 0}}, "www.github.com", "/fdb", "chrome"})
end)
range = KeySelectorRange.starts_with({{{2018, 03, 01}}})
result =
Database.get_range_stream(db, range)
|> Enum.to_list() TODO: Convert to directory in FDB instead of subspaces. |
Created a git repo. Install FDB on Windows. https://www.foundationdb.org/download/ Install elixir from chocolatey. Ensure visual studio 2017 is installed.
Open native command prompt for Visual Studio.
|
A complete example using the regular foundation db api without the directory layer. :ok = FDB.start(600)
cluster_file_path = 'test'
db = FDB.Cluster.create(cluster_file_path) |> FDB.Database.create()
alias FDB.Coder.{Integer, Tuple, NestedTuple, ByteString, Subspace}
# set
FDB.Database.transact(db,
fn transaction ->
:ok = FDB.Transaction.set(transaction, "key", "hello")
end
)
# get
FDB.Database.transact(db,
fn transaction ->
FDB.Transaction.get(transaction, "key")
end
)
# delete
FDB.Database.transact(db,
fn transaction ->
FDB.Transaction.clear(transaction, "key")
end
)
# get
FDB.Database.transact(db,
fn transaction ->
FDB.Transaction.get(transaction, "key")
end
) |
Complete example with directories in FoundationDB. alias FDB.{Directory, Transaction, Database, KeySelectorRange}
alias FDB.Coder.{Integer, Tuple, NestedTuple, ByteString, Subspace}
:ok = FDB.start(600)
cluster_file_path = 'test'
db = FDB.Cluster.create(cluster_file_path) |> FDB.Database.create()
root = Directory.new()
dir = Database.transact(db, fn tr ->
Directory.create_or_open(root, tr, ["nebulex", "test"])
end)
test_dir = Subspace.new(dir)
coder = Transaction.Coder.new(test_dir)
test_db = FDB.Database.set_defaults(db, %{coder: coder})
# set
FDB.Database.transact(test_db ,
fn transaction ->
:ok = FDB.Transaction.set(transaction, "key", "hello")
end
)
# get
FDB.Database.transact(test_db ,
fn transaction ->
FDB.Transaction.get(transaction, "key")
end
)
# delete
FDB.Database.transact(test_db ,
fn transaction ->
FDB.Transaction.clear(transaction, "key")
end
)
# get
FDB.Database.transact(test_db ,
fn transaction ->
FDB.Transaction.get(transaction, "key")
end
)
# List directories
Database.transact(db, fn tr ->
Directory.list(root, tr, ["nebulex"])
end) |
Hey!
I don't understand the question, I haven't checked/used FDB before, so I didn't know there was a kind of "mongodb translation". But overall, I'd try to use it directly with fdb client without a translation.
Not necessary, you can just wrap up each callback impl with the FDB transaction (this is internal on each function for the adapter impl). For example: @impl Nebulex.Adapter
def get(cache, key, opts) do
# get the db instace (the init creates the cluster, perhaps you can create a module to hold the db)
FDB.Database.transact(db, fn transaction ->
value = FDB.Transaction.get(transaction, key)
build_object(key, value, opts) # maybe a function to create Nebulex.Object.t()
end)
end The most important part at the beginning is the Also, check out how the client works, if it runs in a separate app, or you can start the supervision tree from the Let me know once you have something in order to review it and help you more! Stay tuned! |
Since the database is loaded from config files I would probably use a gen server with initial settings loaded from a configuration file or somewhere else like a cluster key set dynamically. Although I think the database object isn't shareable between different computers. Advice? I have to look up gen servers api, I don't remember using it before. Can you clarify?
|
How would I add the concept of a database storage path to the nebulex api? |
Each cache should have its own DB object. So you can use either a
For example, within the @impl true
defmacro __before_compile__(env) do
## ... maybe other things
def __db__ do
:ets.lookup_element(:meta, :db, 2)
end
end For the @impl true
def init(_opts) do
:meta = :ets.new(:meta, [:named_table, :public, {:readd_concurrency, true}])
:ok = FDB.start(610)
db = FDB.Database.create(cluster_file_path)
true = :ets.insert(:meta, {:db, db})
{:ok, []}
end And from the adapter's functions: @impl Nebulex.Adapter
def get(cache, key, opts) do
FDB.Database.transact(cache.__db__, fn transaction ->
value = FDB.Transaction.get(transaction, key)
build_object(key, value, opts) # maybe a function to create Nebulex.Object.t()
end)
end Is it clear?
Please elaborate more on this, what do you mean with add the concept of a database storage path to the nebulex api (I think you don't need to add any concept to the Nebulex API) |
Should the ets table be namespaced with nebulex_fdb_meta? So in Foundation Database I have this concept of a directory path. So for a particular key-value database it would be in alpha/db. My best guess is to put it in the init options to give a path, but I wasn't sure if there's a place to put stuff like that there. |
It doesn't matter, just use the name of the adapter module (
Ok, yes, that is something you can put and then load from the config file. The important things you know you will need in the adapter, you can use @impl true
defmacro __before_compile__(env) do
cache = env.module
config = Module.get_attribute(cache, :config) # your config
path = Keyword.fetch!(config, :db_path)
quote do
def __db_path__, do: unquote(path)
def __db__ do
:ets.lookup_element(:meta, :db, 2)
end
end
end |
How do you write tests for adapters? |
This is the work I have. https://github.com/fire/nebulex_fdb_adapter/blob/master/lib/nebulex_fdb_adapter.ex |
Currently stuck on initializing the module in am iex console. Can't seem to call the init function. defmodule NebulexFdbAdapter.TestCache do
use Nebulex.Cache,
otp_app: :nebulex_fdb_adapter,
adapter: NebulexFdbAdapter,
config: "test",
cluster_file_path: "test",
db_path: ["nebulex", "test"]
end
NebulexFdbAdapter.TestCache.get("hi") |
You are calling |
I believe the FDB.start(600) is for initializing the shared library so before_compile is correct. After turning the fdb database to be save to ssd, the current code can now set, get and delete. |
What do I return for https://hexdocs.pm/nebulex/Nebulex.Adapter.html#c:get_many/3 inside of the adapter? |
The
Returns a map: |
The problem with FDB.start(600) in the init call is it exceptions on the second call :|. According to the docs it can only be used once per elixir startup. |
I understand. Perhaps you should consider initialize it from the app using the FDB adapter. I think there should be a better way to initialize this library (C NIF), I'd suggest reviewing other options. Anyways, for the moment if within the |
A basic set many and get many is in. I'll ask if it's possible from the library to return the standard :ok, {:error, exception} on the FDB.start(600) code. |
I'm having a hard time storing arbitrary data in the bytestring. I was thinking of storing the keys and the values in Erlang Term format or flatbuffers. Thoughts? |
I solved the problem with key "1" and value 1 not being accepted by converting all stored values to erlang binary terms. How do you recommend benchmarking? |
Yes, when you have issues due to the data types. sometimes is better just use
You can use |
First of all, threadpool? If you meant be able to use the multicore arch, it is not about Nebulex, you have to configure the Erlang VM (e.g.: |
I mis-spoke, I mean when I run the benchmark it only causes 100% one core in the system monitor. Not sure where in the system it is being limited. |
I was mistaken, I only started one FDB server process, I should start a number related to the number of cores on my computer. |
Oh, I see, but IMHO that's something the library should handle. anyways, I'm glad that it is working for you now 😄 !! |
Something is weird on benchee:
Any ideas? Guesses:
|
Indeed, that depends on the adapter implementation, for the local adapter since it is based on ETS tables, there is no way to perform a |
Why does your benchee get_many always from 1 to 10 keys? Doesn't this cause problems? |
I integrated poolboy. Benchee seems inaccurate. The database server queries stats is much higher than the operations per second in benchee. |
I will look for another benchmarking tool, benchee requires custom formatters to get accurate numbers and is hard to use. The fdb elixir library maintainer was able to explain why the fdbcli status numbers didn't match the benchee numbers. EDITED: Hard to use means that the library doesn't abstract the concept of an operation and there doesn't seem to be inputs for zipfian distribution. |
I'd suggest basho_bench, for load tests and benchmarks the tool is great, I've used it several times. This is an example (to run load tests for Nebulex) nebulex_bench. |
The adapter works, but it's not ready for production. The documentation isn't great and the project using the FDB adapter is on standby. However, the fdb adapter works! |
Awesome 😄 !!! |
Hi Cabol,
I was wondering what approach should I go to implementing an adapter for FoundationDB?
Thanks.
The text was updated successfully, but these errors were encountered: