lucaong

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 :slight_smile:

Showing Posts 1 to 10

fuelen

fuelen

Do you have feedback about the API?

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

lucaong

lucaong OP

Nice, I didn’t know those projects!

At a glance, CubDB works very similarly to CowDB and bitcask in 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 CubDB is written in Elixir :stuck_out_tongue: (not implying that it’s a huge advantage, as it’s trivial to use Erlang libraries from Elixir).

dimitarvp

dimitarvp

Nice. I’ve been looking for sqlite alternative.

  • Do you plan to introduce a query API?
  • Do you plan to make a strict data typed variant, more akin to Postgres?
  • Would you consider adding a command to it that compacts it (and thus destroy the history of changes but oh well)?
lucaong

lucaong OP

Good point.

I initially wanted select/3 to just return a lazy Enumerable that users could use their own Stream functions on. The reason why I use that specific DSL in select/3 is that the lazy Enumerable references 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 reduce function (that can do all that map, filter, take, … can do), but I felt it’s easier to operate with separate pipeline functions.

lucaong

lucaong OP

Hi @dimitarvp , thanks for your kind words!

The compact/1 function is already available. Even more conveniently, you can opt-in to auto-compaction either at startup or later with set_auto_compact/2.

Regarding the “strict” version, I didn’t plan it yet, but why not :slight_smile: 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/3 is 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

AndyL

@lucaong - very nice!

With select/3 you 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

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_term and 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

lucaong OP

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”} :slight_smile:

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 :users table, use select/3 with min_key: {:users, 0}, max_key: {{:usert, 0}, :excluded}. Because :usert is 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 CubDB leverages :slight_smile:

AndyL

AndyL

OMG didn’t know that!

Any way to pattern match on keys? Or to fetch a [list] of keys?

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 92995 915
New
AstonJ
The obligatory hello world thread! Who are you and where are you from? :stuck_out_tongue:
4616 55835 594
New
caslu
I want to open this thread for you all to discuss and help those who really like Ash but are still hesitant to use it in a real project. ...
New
arcanemachine
I was working on an Ecto migration and I needed a timestamp. So, for the nth time, I looked up the different data types for timestamps, a...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New
Herve37
We’re evaluating API mocking tools for OpenAPI-based projects and would love to hear what other teams are using. We’re particularly inte...
New
matt-savvy
Is there a word for the ~> symbol used in Version strings? Do you also just call it a Squiggle Arrow™ ?!
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews