NornicDB Transaction Implementation¶
Transaction Semantics¶
What's Implemented¶
✅ Atomicity - All operations commit together or none do
✅ Operation Buffering - Changes are invisible until commit
✅ Read-Your-Writes - Transaction can see its own uncommitted changes
✅ Rollback - Discard all buffered operations
✅ Closed Transaction Detection - Error if operating on closed tx
Isolation Semantics¶
NornicDB storage transactions now provide snapshot isolation via MVCC:
- explicit transactions capture a read snapshot when
BeginTransaction()starts - all committed reads inside the transaction are filtered to that snapshot
- uncommitted changes are not visible to other transactions
- read-your-writes still applies for changes buffered by the current transaction
- concurrent write-write races fail at commit with a conflict instead of silently overwriting newer state
In practice, all reads within a transaction see a consistent storage-layer view of the graph as of the transaction start, plus the transaction's own pending changes.
Usage Example¶
engine := storage.NewMemoryEngine()
tx := engine.BeginTransaction()
// Operations are buffered, not yet visible in engine
tx.CreateNode(&Node{ID: "user-1", Labels: []string{"User"}})
tx.CreateNode(&Node{ID: "user-2", Labels: []string{"User"}})
tx.CreateEdge(&Edge{ID: "e1", StartNode: "user-1", EndNode: "user-2", Type: "FOLLOWS"})
// Read-your-writes works
node, _ := tx.GetNode("user-1") // Returns the buffered node
// Either commit all...
err := tx.Commit()
// ...or rollback all
// tx.Rollback()
Performance Benchmarks¶
BenchmarkTransaction_CommitNodes-16 129,942 10,618 ns/op 10,737 B/op 96 allocs/op
BenchmarkTransaction_RollbackNodes-16 224,329 5,403 ns/op 6,776 B/op 66 allocs/op
Interpretation:
- Committing 10 nodes takes ~10.6μs (~1μs per node)
- Rolling back 10 nodes takes ~5.4μs (faster - no storage writes)
- Memory overhead: ~1KB per node in transaction buffer
Test Coverage¶
| Package | Coverage |
|---|---|
pkg/storage | 85.2% |
pkg/filter | 96.0% |
pkg/search | 89.8% |
pkg/decay | 87.4% |
pkg/inference | 85.5% |
Test Results¶
=== RUN TestTransaction_CreateNode_Basic
--- PASS: TestTransaction_CreateNode_Basic (0.00s)
=== RUN TestTransaction_Rollback
--- PASS: TestTransaction_Rollback (0.00s)
=== RUN TestTransaction_Atomicity
--- PASS: TestTransaction_Atomicity (0.00s)
=== RUN TestTransaction_DeleteNode
--- PASS: TestTransaction_DeleteNode (0.00s)
=== RUN TestTransaction_UpdateNode
--- PASS: TestTransaction_UpdateNode (0.00s)
=== RUN TestTransaction_CreateEdge
--- PASS: TestTransaction_CreateEdge (0.00s)
=== RUN TestTransaction_CreateEdgeWithNewNodes
--- PASS: TestTransaction_CreateEdgeWithNewNodes (0.00s)
=== RUN TestTransaction_DeleteEdge
--- PASS: TestTransaction_DeleteEdge (0.00s)
=== RUN TestTransaction_ClosedTransaction
--- PASS: TestTransaction_ClosedTransaction (0.00s)
=== RUN TestTransaction_IsActive
--- PASS: TestTransaction_IsActive (0.00s)
=== RUN TestTransaction_Isolation
--- PASS: TestTransaction_Isolation (0.00s)
=== RUN TestTransaction_MultipleOperationTypes
--- PASS: TestTransaction_MultipleOperationTypes (0.00s)
PASS
ELI12 Explanation¶
Imagine you're rearranging furniture in your room:
- BEGIN = "I'm going to rearrange my room"
- Operations = Moving furniture around (but not committing yet)
- COMMIT = "Yes! I like this arrangement, keep it!"
- ROLLBACK = "Nope, put everything back where it was"
The transaction remembers where everything was before, so if you change your mind (ROLLBACK), everything goes back to the original spots!
Date¶
Implemented: November 26, 2025