MVCC (Multi-Version Concurrency Control) is the technique jasonisnthappy uses to provide snapshot isolation: multiple transactions can read and write concurrently without blocking each other. Each transaction sees a consistent snapshot of the database from when it began.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/sohzm/jasonisnthappy/llms.txt
Use this file to discover all available pages before exploring further.
How MVCC works
Instead of locking documents, jasonisnthappy creates new versions on every update:- xmin: Transaction ID that created this version
- xmax: Transaction ID that deleted/updated this version (0 = still current)
- snapshot: Transaction’s view of the latest committed transaction when it began
A version is visible to a transaction if:
xmin ≤ snapshot_id(version existed at snapshot time)xmax == 0 OR xmax > snapshot_id(version wasn’t deleted yet)
Transaction manager
TheTransactionManager (src/core/mvcc.rs:21) tracks active transactions:
Transaction lifecycle
Begin transaction:- Allocate unique transaction ID (src/core/mvcc.rs:44)
- Capture current committed transaction ID as snapshot
- Register as active
- Remove from active transactions (src/core/mvcc.rs:73)
- Update global commit timestamp if newer
The
last_committed_tx_id is what new transactions use as their snapshot ID. This ensures they see all previously committed work.Document versioning
Each document stores MVCC metadata in its header:Visibility check
The visibility rule (src/core/mvcc.rs:139):| Version | Snapshot ID | Visible? | Reason |
|---|---|---|---|
| xmin=5, xmax=0 | tx=10 | ✓ | Created at 5, still current |
| xmin=5, xmax=0 | tx=3 | ✗ | Created at 5, after snapshot |
| xmin=5, xmax=8 | tx=10 | ✗ | Deleted at 8, before snapshot |
| xmin=5, xmax=8 | tx=6 | ✓ | Created at 5, deleted at 8, in between |
Version chains
When a document is updated, the old version is added to a version chain (src/core/mvcc.rs:152):- Transaction with snapshot=3 sees version 1
- Transaction with snapshot=7 sees version 2
- Transaction with snapshot=12 sees version 3
Update flow with MVCC
Here’s what happens when you update a document:-
Read current version (xmin=10, xmax=0)
- Track original xmin in
doc_original_xminfor conflict detection - Mark that document existed in snapshot (
doc_existed_in_snapshot)
- Track original xmin in
-
Create new version
- Allocate new page for document
- Write document with xmin=11 (this transaction’s ID), xmax=0
- Insert into transaction’s write buffer
-
On commit
- Check for conflicts: has the committed version’s xmin changed?
- Write new version to B-tree
- Add old version (xmin=10, xmax=11) to version chain
- Update B-tree root atomically
Why track doc_original_xmin?
Why track doc_original_xmin?
Conflict detection compares the xmin we first read against the current committed xmin:This implements first-committer-wins: if another transaction committed an update after our snapshot, we conflict.
Garbage collection
Old versions accumulate over time. Garbage collection removes versions that no active transaction can see.GC algorithm
The GC process (src/core/mvcc.rs:172) works as follows:- It was created AND deleted before the oldest active transaction
- No active transaction can possibly see it
Running garbage collection
Calldb.garbage_collect() to reclaim space:
MVCC and the B-tree
The B-tree only stores the current version of each document (the one with xmax=0). Old versions live in version chains. When a transaction reads a document:- Search B-tree for document ID → get current version
- Check visibility of current version
- If visible → return it
- If not visible → search version chain for visible version
This design optimizes for the common case: most reads access the current version, requiring only a B-tree lookup. Version chain traversal only happens for reading old snapshots.
Conflict detection revisited
Now we can understand conflict detection in detail (src/core/transaction.rs:357):xmin > snapshot_id?
If the new xmin is ≤ our snapshot, it means the update was committed before we started, so we should have seen it. No conflict. But if xmin > snapshot, it means someone committed after we began, which is a write-write conflict.
Performance implications
Read performance
- Best case: Document is current → single B-tree lookup
- Worst case: Reading old snapshot → B-tree + version chain scan
- Typical: ~0.009ms per query (README.md:74)
Write performance
- Each update creates a new page (copy-on-write)
- Version chains grow until GC
- Benefit: No lock contention for reads
- Update: ~7.90ms with full ACID + MVCC
- Includes WAL write, fsync, and conflict check
Memory usage
- Version chains stored in memory:
HashMap<Collection, HashMap<DocID, Vec<Version>>> - Old versions occupy disk pages until GC
- Mitigation: Regular garbage collection, short transactions
Snapshot isolation vs serializability
jasonisnthappy provides snapshot isolation, which prevents: ✓ Dirty reads - Never see uncommitted changes ✓ Non-repeatable reads - Snapshot is consistent throughout transaction ✓ Phantom reads - Snapshot includes consistent set of documents ✓ Lost updates - Conflict detection prevents concurrent overwrites But allows: ✗ Write skew - Two transactions read overlapping data, write disjoint data Example of write skew:Next steps
Transactions
Learn about commit, rollback, and conflict handling
Storage Engine
See how B-trees and copy-on-write enable MVCC