How MVCC and Transactions Work in RocksDB - Artem Krylysov
How MVCC and Transactions Work in RocksDB
July 23, 2026
databases
key-value
lsm
rocksdb
RocksDB is built on an LSM-tree, which never modifies data in-place - every write creates a new version of the key. That's half of what you need to implement Multi-Version Concurrency Control (MVCC).
Real-world databases serve many clients reading and writing the same data at the same time. The simplest way to make concurrent access safe is locking (think of a hash map protected with a mutex). This works from the correctness perspective, but makes readers and writers block each other, which can quickly become a performance bottleneck. MVCC solves this problem, allowing readers and writers to proceed concurrently. The idea is that:
Writers never modify or delete keys in-place, instead they create new versions of the keys
Readers see a consistent view of the data as it existed when the read started - they access older versions of the keys, while writers keep creating new versions independently
MVCC is used in many DBs including PostgreSQL, CockroachDB, FoundationDB, MongoDB WiredTiger, and LMDB. Today we'll see how RocksDB implements the other half of MVCC - snapshots that give readers that consistent view, atomic updates and transactions built on top.
Versioning Keys #
All inserts, updates and deletes go through a write buffer (memtable) and then get flushed to disk into an immutable SST (Static Sorted Table) file - you naturally have multiple versions of the same key. Compaction is the garbage collection process that merges SSTs and removes older versions of keys.
Note
If you want to go deeper on LSM internals, I covered them in How RocksDB works.
Let's pretend our database is an insert-only list ordered by key. If we add these key-value pairs dog,a, chipmunk,a, cat,a, raccoon,a, we'll end up with a list that looks like this:
It's a toy database, so point lookup queries run by scanning the entire list. Now a query starts, trying to look up dog. While the query is running, a writer proceeds more quickly and overwrites dog with b before the reader reaches it. The reader is still at chipmunk, but the writer already finished inserting dog,b:
Should the reader see dog,b since it's the latest value inserted, or see dog,a because that was the value when the reader started the query? "Readers see a consistent view" clearly answers this - the reader should see dog,a.
Earlier I said the LSM-tree already does half of what's needed to implement MVCC, half because having immutable memtables and SSTs alone is not enough. You also need a way to provide readers a snapshot of the database, and the GC process needs some way of knowing which tables are being accessed by in-flight queries.
Internally, RocksDB assigns each inserted key a monotonically increasing number, which it calls a sequence number. When you insert a key into RocksDB, what actually gets into the memtable and SST files on disk is a triplet of (user_key, sequence_number, value_type), stored together with the value. The user key is the key you provided, the sequence number is a counter incremented on every write, and finally the value type is whether it's an insert or a delete (or other operations, which are irrelevant for this blog post).
With these, the updated representation of the list from the example before is:
RocksDB tracks two sequence numbers - the last allocated sequence (the last number assigned on write), and the published sequence (the number visible to readers, advanced after a write completes).
In our example, the last allocated sequence number is 5 - the number that was assigned to dog,b. The published sequence when the reader R started was 4, and it will be 5 after the write completes.
When a read query starts, RocksDB captures the published sequence number. This sequence is then used for querying the memtables and the SSTs on disk. The reader R, when it sees dog,b with the sequence number 5, will skip over it, as it is allowed to see only keys with a sequence number of 4 or below.
Note
RocksDB is highly configurable. I'm describing the default configurations and behaviors with some simplifications to manage the complexity of this blog post.
The memtable implementation in RocksDB is not that far from our toy example. As in the example we used, it's based on a linked list, specifically on a skip list. The data structure adds layers of links that allow fast search and insertion in sorted order:
Layer 0 contains every entry - like the linked list from the example before. Each next layer contains a subset of the previous layer's entries. The search starts at the sparsest layer, descending to the full linked list. This structure gives O(log n) average time complexity for search.
When comparing internal keys, RocksDB first orders by the user-provided key ascending and then by the sequence number descending, so that when two keys share the same user key, the one with the...