fastskip
Lock-free arena-backed skip list for LSM-tree memtables. Designed for high-throughput write workloads in storage engines like RocksDB, LevelDB, and CockroachDB.
Why fastskip?
| Feature | Description |
|---|---|
| Lock-free writes | Multiple threads insert/delete concurrently via single CAS at level 0 |
| Lock-free reads | Point lookups walk the skip list without any locks |
| O(1) allocation | Per-thread arena shards — zero-contention bump allocation |
| Snapshot isolation | Point-in-time snapshots remain consistent under concurrent writes |
| Range cursors | Cursor with lower-bound seek for prefix/range iteration |
| Seal/freeze lifecycle | seal() freezes memtable for flushing, returns fresh one |
| Bulk deallocation | Dropping memtable reclaims all arena memory at once |
| Auto-seal thresholds | Automatic sealing based on memory usage or entry count |
| Backpressure detection | Detect when nearing capacity limits |
| Batch operations | Efficient bulk insert and multi-get operations |
Quick Start
Basic Operations
use ConcurrentSkipList;
let sl = new;
sl.insert;
sl.insert;
let = sl.get.unwrap;
assert_eq!;
assert!;
With Auto-Seal Thresholds
use ConcurrentSkipList;
// Create with auto-seal at 1MB memory or 100k entries
let sl = with_capacity_and_shards;
// Check if nearing limits
if !sl.is_under_backpressure
// Get memory statistics
println!;
println!;
println!;
Delete / Tombstones
Delete marks a key with a tombstone. Deleted keys remain in the skip list until flushed to SSTable.
use ConcurrentSkipList;
let sl = new;
sl.insert;
// Delete returns true if key was found
assert!;
// get still returns the key, but tombstone = true
let = sl.get.unwrap;
assert!;
// get_live filters out tombstones
assert_eq!;
LSM Memtable Lifecycle
The intended lifecycle for an LSM-tree storage engine:
use ConcurrentSkipList;
// 1. Create active memtable with auto-seal thresholds
let memtable = with_capacity_and_shards;
// 2. Concurrent writers insert/delete
memtable.insert;
memtable.delete;
// 3. Check for backpressure before inserting batch
if !memtable.is_under_backpressure
// 4. When full (auto-sealed or manual), seal it — returns frozen (for flush) + fresh (for writes)
let = memtable.seal.unwrap;
// 5. Flush frozen to SSTable (iterate snapshot)
for entry in frozen.iter
// 6. Drop frozen (reclaims arena memory)
drop;
// 7. Fresh memtable ready for new writes
fresh.insert;
// Monitor memory usage
println!;
println!;
Snapshots — Point-in-Time Iteration
Readers are fully lock-free. For consistent reads under concurrent writes, use a snapshot:
use ConcurrentSkipList;
let sl = new;
sl.insert;
sl.insert;
// Snapshot captures sequence number — skips post-snapshot inserts
let snap = sl.snapshot;
// Insert more after snapshot (won't appear in snapshot)
sl.insert;
// Snapshot sees only "a" and "b"
assert_eq!;
// Live iterator sees all three
assert_eq!;
Range Scans
use ConcurrentSkipList;
let sl = new;
sl.insert;
sl.insert;
sl.insert;
sl.insert;
// Seek to first key >= "banana"
let mut cursor = sl.cursor_at.unwrap;
while let Some = cursor.next_entry
// Output: banana, cherry, date
Memory Stats
use ConcurrentSkipList;
let sl = new;
sl.insert;
println!;
println!;
println!;
println!;
println!;
println!;
println!;
println!;
Use Cases
- LSM-tree memtables — RocksDB, LevelDB, CockroachDB style storage engines
- Write-heavy key-value stores — high-throughput ingestion pipelines
- Time-series append — sequential inserts, periodic flush to disk
- Request-scoped caches — per-request memtable, bulk reclaim on completion
Performance
Benchmarks on AMD Ryzen 5 3600 (6-core, 3.6GHz). All numbers from cargo bench.
Concurrent writes (10K entries/thread, 64-byte keys)
| Threads | Throughput | Latency/insert |
|---|---|---|
| 4 | 9.4M ops/s | 106ns |
| 8 | 7.8M ops/s | 128ns |
Single-threaded latency
| Operation | fastskip | BTreeMap | HashMap |
|---|---|---|---|
| insert (seq, 10K) | 136µs | 1.37ms | 171µs |
| insert (rand, 10K) | 2.2ms | 1.5ms | 212µs |
| get hit | 80ns | 43ns | 20ns |
| get miss | 38ns | 91ns | 16ns |
| cursor seek (1K) | 36ns | 397ns | — |
| cursor seek (10K) | 60ns | 5µs | — |
fastskip trades single-threaded speed for lock-free concurrent writes — BTreeMap and HashMap require external locking for multi-threaded access, which destroys throughput.
v0.1.1 Optimizations
- 27% faster concurrent inserts (4 threads: 14.5ms → 10.6ms)
- 20% faster sequential inserts (10K: 1.7ms → 1.36ms)
- 21% faster random inserts (10K: 2.8ms → 2.2ms)
- 33% faster cursor seek (1K: 54ns → 36ns)
- 19% faster seal+iterate (1K: 155µs → 126µs)
- O(1)
memory_usage()— atomic running total instead of iterating all shards - Conditional allocation tracking — zero overhead when memory limits aren't configured
- Bulk u64 header writes in
init_node()(3 stores vs 8 individual stores) - Adaptive backoff on CAS failure reduces cache-line bouncing under contention
- Fat LTO + strip in release profile for maximum codegen quality
See USAGE.md for complete API reference.
Configuration
use ConcurrentSkipList;
// Default: CPU count shards, 64KB initial arena per shard
let sl = new;
// Custom shard count
let sl = with_shards;
// Custom capacity and shards
let sl = with_capacity_and_shards;
// With auto-seal thresholds
let sl = with_capacity_and_shards;
Thread Safety
ConcurrentSkipList is Send + Sync — can be shared across threads:
use ConcurrentSkipList;
use Arc;
use thread;
let sl = new;
let sl1 = sl.clone;
let t1 = spawn;
let sl2 = sl.clone;
let t2 = spawn;
t1.join.unwrap;
t2.join.unwrap;
assert_eq!;
Design
| Property | Detail |
|---|---|
| Algorithm | Lock-free skip list (RocksDB InlineSkipList / CockroachDB arenaskl) |
| Concurrency | Single CAS at level 0, best-effort CAS at upper levels |
| Memory | Per-thread arena shards, no per-node free |
| Ordering | Lexicographic byte ordering |
| Duplicates | Rejected (first value wins) |
| Delete | Tombstone (entry stays, marked deleted) |
| Re-insert after delete | Seal memtable, write to fresh one |
| Auto-seal | Size or count based thresholds |
| Backpressure | 90% threshold warning |
| Batch ops | insert_batch, get_many |
Testing
Documentation
See USAGE.md for complete API reference and examples.