andzdroid
I saw this bit of text on the cubdb repo’s readme (GitHub - lucaong/cubdb: Elixir embedded key/value database · GitHub):
Snapshots come at no cost: nothing is actually copied or written on disk or in memory, apart from some small internal bookkeeping.
In relation to this code:
{x, y} = CubDB.with_snapshot(db, fn snap ->
x = CubDB.Snapshot.get(snap, :x)
y = CubDB.Snapshot.get(snap, x)
{x, y}
end)
I looked at the with_snapshot function and all it does is make a genserver call. Can somebody explain how this doesn’t copy anything in memory?
def snapshot(db, timeout \\ 5000) do
GenServer.call(db, {:snapshot, timeout}, :infinity)
end
My understanding of elixir was that messages across processes are always copied. Is my understanding wrong? Am I missing something?
Trending in Questions
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
Hello,
I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
Hi everyone,
I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding.
I sta...
New
Documentation
While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
So my question is quite simple and i have found no conclusive answer on forum, google or AI.
Should we use :erlang.float for Integer to ...
New
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New
Other Trending Topics
Edit: 2026 May 15 - This post is archived.
Mob is alive!!
Main docs: mob v0.7.11 — Documentation
A bit of explanation for the slightly c...
New
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Hi there! We created Gust: A task orchestrator inspired by Airflow.
For those who have never heard about Aiflow, it’s a Python-based wor...
New
Hi everyone!
The first release candidate for the Expert language server project is now available!
We’ve published a press release detai...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #ai
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #elixirconf-eu
- #metaprogramming
- #hex










Showing Posts 14 to 5- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
lucaong
Good point. The reason why the subtree referenced by the root does not get copied to the GenServer caller is that those references are not standard memory references, but rather pointers to an offset in the database file.
CubDBknows how to follow such pointers on demand to access the file in the correct locations when reading and traversing the tree.joey_the_snake
Thank you for the explanations you’ve provided in this thread. What you’ve created is really interesting.
I think the part I was getting tripped up over is that it seems like the btree is returned from a genserver to the caller. And it looked to me like the btree contains the root, which in return has a list of references to children. I thought all of these would have to be deep copied to the caller from the genserver.
lucaong
Also, more clarifications regarding @joey_the_snake replies:
No: it is a reference to the original data, a simple pointer, but it is isolated from other updates. The trick is that the original data is immutable, it cannot be changed by other updates. Instead, updates create a new Btree root that points to all the data that did not change, plus the part that changed. It is the same principle underlying immutable data structures with structural sharing (like those in Elixir, Clojure, etc.)
Not the whole tree, just the root, which is a small data structure. And yes, the tree is walked when accessed, but it is not a copy: it is the original (immutable) tree, but accessed from the snapshot root.
It’s not the full tree. You can think about it with an analogy with Elixir immutable data structures:
Note the part that is in common between
sandmwas not copied: bothsandmpoint to the same values (for example the key:bazpoints to the same list inmands, not to two identical copies). Yet, because maps are immutable in Elixir,sdoes not see changes. Simply,mtracks the latest updates, whileskeeps pointing to a certain version.As soon as
sis not in scope anymore, the garbage collector can then clean up the old data that is not reachable anymore fromm.CubDBworks in much the same way, just on disk instead than in memory, and therefore with some different implementation details to account for the difference performance characteristics of disk vs. memory.I just found this nice explanation of how an append-only Btree works, which corresponds to how
CubDBworks internally. On top of that,CubDBuses a compaction process to “garbage collect” disk space and avoid growing indefinitely with each update.I hope this clarifies it further
andzdroid
Thank you for the detailed explanation!
lucaong
@LostKobrakai is essentially right.
CubDBuses an append-only Btree: changes are appended to the database file, like in a log file. Basically, every new change causes a new Btree root to be appended to the database file. The root points at the old nodes as well as to some new ones containing the data that changed. Existing Btree nodes are never updated in place. It works very similarly to an immutable data structure, like the ones used by Elixir. This means that if one has a reference to an old Btree root, they can traverse the tree and see the database exactly as it was when that root was the current one.Obtaining a snapshot then is as simple as holding a reference to a Btree root: new changes can be written to the database, but they won’t change what is pointed by that old root. Therefore, there is no database copying, as in CubDB does not have to make a copy of the database to give you an immutable snapshot. The Btree root is a small data structure, only holding a reference to other nodes down the tree: it doesn’t contain any data. Taking a snapshot is a very fast
O(1)operation. In fact, it is internally used on every read operation, to ensure consistency in presence of concurrent writes.The bookkeeping mentioned is simply because occasionally, in order to avoid growing the database file indefinitely as more changes accumulate, CubDB performs a compaction, which discards the data which is old and not referenced anymore. CubDB has to keep track of what’s referenced by the existing snapshots to avoid removing it, a bit like a garbage collector.
The details of compaction are a bit more involved, as compaction can proceed in parallel with reads and writes, but in essence when you take a snapshot CubDB does not copy any data: it merely remembers which Btree root your snapshot points to.
When you read from the snapshot, of course data will be copied from disk into memory. But that’s just the data you read, not the whole database. And, again, that is when you read from the snapshots, not when you take a snapshot.
I hope this clarifies it
LostKobrakai
I’d imagine that in the grand scheme of things the tree is probably tiny compared to what would need to be moved around if values would be affected – especially when the db reaches sizes where you actually start caring for it not being copied.
joey_the_snake
Yeah true. I do see the OP’s point though. I wouldn’t necessarily expect the full tree structure, even if it doesn’t have all the data, based on the description.
LostKobrakai
Yeah, but that tree structure does in turn refer to a data store and that data store (file by default) holds the actual data. So yes, some stuff is copied (the internal bookkeeping mentioned), but it’s not the database nor the values it stores.
joey_the_snake
Looking at the source, it seems like a tree structure is copied to the caller. And this tree structure is walked when trying to read particular keys.
LostKobrakai
That depends on the datastructure used and what information makes up the pointer. E.g. if the datastructure is an append only log you can create a “snapshot” just by having the id of the last item in the log, which is still part of the snapshot. CubDB afaik uses some kind of tree structure to store data.