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

budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
axelson
Hi there! :wave: @frigidcode and I (but mostly him) have been running an Elixir Book club, we’re almost done with Designing Elixir Syste...
New
achempion
I’ve been using Emacs as my main code editor for more than a two years. It’s a custom build version although I’ve tried doom emacs and sp...
New
budgie
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with. But I don’t use it anymore. Serverless was the promis...
New
jtormey
Lately I’ve been thinking about how to organize components as a LiveView application grows. One of the pain points I’ve found (for myself...
New
Null-logic-0
What IDE or editor are you using for Elixir development? Personally, I use Zed, and I really like it, but sometimes I wish there were a ...
New

Other Trending Topics Top

KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
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
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
kip
In 2021 I started a new library called Tempo with the objective of modelling time as a set of intervals - not as instants. In 2022 I gave...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews