MVCC Implementation
MVCC Implementation
This document provides a detailed explanation of Stoolap’s Multi-Version Concurrency Control (MVCC) implementation, which enables transaction isolation with minimal locking.
MVCC Overview
Multi-Version Concurrency Control (MVCC) is a concurrency control method used by Stoolap to provide transaction isolation. The key principles are:
- Create a new version of data items for each update
- Maintain multiple versions of data concurrently
- Each transaction has a consistent view based on visibility rules
- Reads never block writes, and writes never block reads
- Implement optimistic concurrency control for conflict detection
Core Components
Stoolap’s MVCC implementation consists of these key components:
Transaction Manager
- Manages transaction lifecycle (begin, commit, rollback)
- Assigns unique transaction IDs
- Tracks transaction state (active, committed, aborted)
- Implements isolation levels
- Resolves conflicts
Version Store
- Maintains multiple versions of each row
- Associates versions with transaction IDs
- Tracks creation and deletion transaction IDs
- Implements garbage collection of obsolete versions
- Optimizes storage of versions
Visibility Layer
- Implements rules to determine which versions are visible to each transaction
- Based on transaction ID, timestamp, and isolation level
- Ensures consistent views of the database
- Prevents anomalies like dirty reads, non-repeatable reads, and phantom reads
Version Tracking
Row Versioning
Each row in Stoolap can have multiple versions, with each version containing:
- CreatedTxnID - The transaction ID that created this version
- DeletedTxnID - The transaction ID that deleted this version (if any)
- Column Values - The actual data for this version
Version Chain
Versions of a row form a chain:
- New versions are created when rows are updated or deleted
- Each version points to its predecessor
- The chain represents the complete history of a row
- Obsolete versions are eventually garbage collected
Transaction IDs and Timestamps
Stoolap uses a sophisticated transaction ID and timestamp system:
- TxnID - Unique identifier for each transaction
- ReadTimestamp - Timestamp when the transaction started
- CommitTimestamp - Timestamp when the transaction committed
These identifiers are used to implement visibility rules and ensure transaction isolation.
Isolation Levels
Stoolap supports two isolation levels through its MVCC implementation:
Read Committed
- A transaction sees only committed data as of the start of each statement
- Different statements within the same transaction may see different data
- No dirty reads, but non-repeatable reads and phantom reads are possible
- Implemented through per-statement visibility checks
Snapshot Isolation
- A transaction sees a consistent snapshot of the database as it existed at the start of the transaction
- All statements within a transaction see the same data, regardless of concurrent commits
- No dirty reads, non-repeatable reads, or phantom reads
- Implemented by using the transaction’s read timestamp for all visibility checks
Visibility Rules
The core of the MVCC implementation is the set of visibility rules:
Basic Rules
A row version is visible to a transaction if:
- It was created by the transaction itself, OR
- It was created by a committed transaction before the current statement/transaction started, AND
- It was not deleted, OR it was deleted by a transaction that had not committed when the current statement/transaction started, OR it was deleted by the current transaction
Implementation
These rules are implemented in the IsVisible
and IsDirectlyVisible
functions:
// Simplified example of visibility rules
func IsVisible(txn *Transaction, createdTxnID, deletedTxnID TxnID) bool {
// Case 1: Row created by this transaction
if createdTxnID == txn.ID {
return true
}
// Case 2: Row created by another transaction
creatorTxn := registry.GetTransaction(createdTxnID)
if creatorTxn == nil || creatorTxn.Status != Committed ||
creatorTxn.CommitTimestamp > txn.ReadTimestamp {
return false
}
// Case 3: Row not deleted or deleted by uncommitted transaction
if deletedTxnID == InvalidTxnID {
return true
}
// Case 4: Row deleted by this transaction
if deletedTxnID == txn.ID {
return false
}
// Case 5: Row deleted by another transaction
deleterTxn := registry.GetTransaction(deletedTxnID)
return deleterTxn == nil || deleterTxn.Status != Committed ||
deleterTxn.CommitTimestamp > txn.ReadTimestamp
}
Concurrency Control
Stoolap uses optimistic concurrency control within its MVCC implementation:
- Transactions proceed without acquiring locks for reading
- Write operations create new versions without blocking readers
- At commit time, the system checks for conflicts
- If a conflict is detected, the transaction is aborted
First-Committer-Wins
Stoolap implements the “first-committer-wins” strategy:
- If two transactions modify the same row, the first to commit succeeds
- The second transaction will detect the conflict at commit time and abort
- This approach minimizes blocking while ensuring data consistency
Transaction Operations
Beginning a Transaction
When a transaction begins:
- A unique transaction ID is assigned
- A read timestamp is captured
- The transaction is registered in the transaction manager
- The isolation level is set (ReadCommitted or SnapshotIsolation)
Read Operations
During read operations:
- The storage engine retrieves all versions of matching rows
- The visibility layer filters versions based on the transaction’s view
- Only visible versions are returned
- For ReadCommitted, visibility is based on the current statement time
- For SnapshotIsolation, visibility is based on the transaction’s start time
Write Operations
During write operations:
- INSERT - A new row version is created with the current transaction ID
- UPDATE - A new row version is created, and the old version is marked as deleted
- DELETE - The row version is marked as deleted by the current transaction
- All changes are only visible to the current transaction until commit
Committing a Transaction
When a transaction commits:
- Conflict detection is performed
- If conflicts are found, the transaction is aborted
- If no conflicts, a commit timestamp is assigned
- The transaction state is changed to Committed
- Changes become visible to other transactions based on their isolation level
Rolling Back a Transaction
When a transaction is rolled back:
- All changes made by the transaction are discarded
- The transaction state is changed to Aborted
- Resources associated with the transaction are released
Garbage Collection
To prevent unbounded growth of version chains:
- Obsolete versions are identified (no active transaction can see them)
- These versions are removed during garbage collection
- Garbage collection runs periodically or when certain thresholds are reached
- The oldest active transaction determines which versions can be safely removed
Snapshot Management
For persistent storage, Stoolap manages consistent snapshots:
- Snapshots represent a point-in-time state of the database
- Each snapshot is consistent across all tables
- Snapshots include only committed transactions
- Recovery uses snapshots and WAL to rebuild the database state
Implementation Details
Stoolap’s MVCC is implemented in the following key components:
- mvcc.go - Core MVCC functionality and timestamp generation
- transaction.go - Transaction implementation
- version_store.go - Version storage and management
- registry.go - Transaction registry and visibility rules
- table.go - Table implementation with MVCC support
- scanner.go - Data scanning with visibility filtering
Performance Optimizations
Several optimizations improve MVCC performance:
Fast-Path Operations
- Single-transaction operations take a faster path
- Read-only transactions avoid certain overhead
- Common patterns are recognized and optimized
Efficient Version Storage
- Versions are stored in a way that optimizes memory usage
- Column-oriented storage works well with MVCC
- Segment-based organization improves concurrency
Visibility Filtering
- Batch filtering of versions improves performance
- Early pruning eliminates unnecessary version checks
- Index-based filtering accelerates visibility determination
Best Practices
To get the most out of Stoolap’s MVCC implementation:
- Keep transactions short - Long-running transactions increase version accumulation
- Choose appropriate isolation levels - Use the minimum required isolation
- Consider workload patterns - Read-heavy workloads benefit most from MVCC
- Batch related operations - Group related changes in a single transaction
- Handle conflicts appropriately - Implement retry logic for conflicting transactions