lucaong
Hello Elixir and Nerves community,
I have been working for a while on an open-source embedded key-value database for Elixir, that I called CubDB. I use it for several IoT projects I run using Nerves, where I need to store large-ish amount of data locally to the device.
I am already using it in production, but before I release version 1.0.0 I would love some feedback from the Nerves (and Elixir) community.
You can find the CubDB repository here
And here the API documentation
A quick basic usage example:
{:ok, db} = CubDB.start_link("my/data/directory")
CubDB.put(db, :foo, "some value")
#=> :ok
CubDB.get(db, :foo)
#=> "some value"
CubDB.delete(db, :foo)
#=> :ok
CubDB.put(db, {:keys, "can", :be, 'anything'}, ["and", :values, 'too'])
#=> :ok
# Check out docs for advanced usage with select/3 and get_and_update_multi/4
I know that Elixir comes with ETS/DETS and Mnesia, but:
-
ETS is not persistent across reboots
-
DETS does not offer sorted collections, and is thus not ideal when one needs to select arbitrary ranges of keys, iterate in order, etc.
-
Mnesia is great, but on embedded projects I don’t need distribution
-
Sometimes I really just need a “persisted map”, sorted by key
-
It’s nice to be able to backup the whole DB by just copying one file
The use-cases I am primarily targeting is what described in this blog post by the Nerves team: https://embedded-elixir.com/post/2017-09-22-using-ecto-and-sqlite3-with-nerves/
CubDB is somehow similar to SQLite in which it stores the data locally in a single file, but it is written in Elixir, is key-value and schema-less, and both keys and values can be any Elixir (or Erlang) terms, so no serialization/de-serialization is needed.
The data structure it uses is an append-only immutable B-tree, inspired by CouchDB: that guarantees robustness to data corruption (no in-place mutation), and enables features like concurrent read operations that do not block writes, and atomic transactions.
It was already a lot of fun for me to develop it, but I would love to hear your constructive feedback.
What do you think about it? Do you have a use-case where this could be useful? Do you have feedback about the API?
Thanks in advance ![]()
Trending in Discussions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #ai
- #phoenix_html
- #iex
- #elixirconf-us
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
fuelen
I think using function/1 or mfa for querying data would be more flexible instead of own DSL that just redirects some functions to Stream
tristan
Did you compare with pure Erlang k/v stores?
I think the most recent is GitHub - martinsumner/leveled: A pure Erlang Key/Value store - based on a LSM-tree, optimised for HEAD requests · GitHub
But there is at least also GitHub - basho/bitcask: because you need another a key/value storage engine · GitHub and GitHub - refuge/cowdb: Pure Key/Value database library for Erlang Applications · GitHub – maybe others as well.
lucaong
Nice, I didn’t know those projects!
At a glance,
CubDBworks very similarly toCowDBandbitcaskin that they all use an append-only copy-on-write data structure. I will look into them and hopefully learn more about their strategy to deal with the tricky bits, like compaction.The main difference would be, I guess, that
(not implying that it’s a huge advantage, as it’s trivial to use Erlang libraries from Elixir).
CubDBis written in Elixirdimitarvp
Nice. I’ve been looking for sqlite alternative.
lucaong
Good point.
I initially wanted
select/3to just return a lazyEnumerablethat users could use their ownStreamfunctions on. The reason why I use that specific DSL inselect/3is that the lazyEnumerablereferences a specific point in the DB data file, and I need to know when no more reader references it, so a compaction operation can safely “garbage collect”. By “proxying” the stream operations, I know when they have completed, and can “check out” the reader.Maybe there’s a better way though. I could possibly just accept a single arbitrary
reducefunction (that can do all thatmap,filter,take, … can do), but I felt it’s easier to operate with separate pipeline functions.lucaong
Hi @dimitarvp , thanks for your kind words!
The
compact/1function is already available. Even more conveniently, you can opt-in to auto-compaction either at startup or later withset_auto_compact/2.Regarding the “strict” version, I didn’t plan it yet, but why not
It should be reasonably easy to implement it as a layer on top of
CubDB. I was kinda thinking of building an SQL layer as a separate library, mostly for fun, but have no idea when I will actually get to it.About the query API, at the moment
select/3is the query workhorse. It supports efficiently selecting ranges of keys, filtering results, mapping, iterating, reducing, etc. That said, as @fuelen commented, it might be possible to improve its API to make it even simpler to use.AndyL
@lucaong - very nice!
With
select/3you can specify a key-range (min-key, max-key). It looks like a key can be any Erlang term. (is that right?) How would min/max ranges work with different key types (numbers, strings, lists, tuples, …) ??Is there any way to use pattern-matching to select keys?
dimitarvp
Let me give you some food for thought. Don’t take it as a wish list, I am just sharing.
There is currently a market for sqlite-like storage engines. Sqlite3 is an amazing little DB but it comes with quite a hefty load of legacy decisions – like a lack of proper timestamp type, lack of boolean type, lack of enums, accepting arbitrarily typed data in integer columns etc.
A lot of people out there use Sqlite3. It’s deployed on trillions of devices, literally. Yet it lacks some very common sense features like strict typing.
IMO the BEAM VM (and thus Erlang, Elixir, LFE, Alpaca etc.) is uniquely positioned. We basically don’t need Redis and Memcached due to ETS, DETS, Mnesia, Erlang’s
persistent_termand several others. If we complement that with a self-sufficient single-file storage engine then the BEAM ecosystem becomes a de facto standard for a lot of development scenarios.Again, don’t take this as a list of demands. It’s just my opinion that the BEAM ecosystem seriously needs a good Sqlite3-like experience.
lucaong
Thanks @AndyL!
Yes, keys (and values) can be any Elixir or Erlang term. One neat thing about Erlang and Elixir is that order of arbitrary terms is well defined. Try for example
:a > {1, “something”}Even nicer, the ordering actually makes sense for tuples, because elements are compared in order. That can be used to good effects in
CubDB. Imagine you want to store different “tables” in the same database. You could structure your keys as{:table_name, id}and, if you want to select only entries in the:userstable, useselect/3withmin_key: {:users, 0}, max_key: {{:usert, 0}, :excluded}. Because:usertis the lexical successor of:users, that would select all entries in the user table, and no other entry.That, together with serialization of arbitrary terms, is a great thing that the Erlang VM offers and that
CubDBleveragesAndyL
OMG didn’t know that!
Any way to pattern match on keys? Or to fetch a [list] of keys?