How Turso’s Skiplist Data Structure Powers Its Database Operations

Turso’s lock‑free skiplist data structure serves as the foundational concurrent ordered container for its MVCC engine, enabling multiple threads to perform inserts, deletes, and range scans on transaction tables, index maps, and version chains without global locks or mutex contention.

The tursodatabase/turso repository implements its core ordered data structures in core/skiplist using a vendored crossbeam‑skiplist foundation with a custom allocator hook. Rather than defaulting to traditional B‑Trees, the database engine relies on SkipMap and SkipSet containers that are Send + Sync and accept &self for all mutating operations. This architecture eliminates contention points in high‑concurrency transactional workloads while preserving the ordered semantics required for SQL range queries and secondary index maintenance.

Lock‑Free Skiplist Architecture

Turso’s skiplist implementation is defined in core/skiplist/mod.rs, which exports SkipMap from core/skiplist/map.rs and SkipSet from core/skiplist/set.rs. The module provides two primary generic containers that replace standard library ordered collections:

  • SkipMap<K, V> – An ordered map analogous to BTreeMap supporting concurrent insertions and deletions.
  • SkipSet<T> – An ordered set analogous to BTreeSet for simple ordered collections.

Both containers are Send + Sync and expose methods that take &self rather than &mut self, allowing many threads to mutate the structure simultaneously without external locking. Internally, the implementation relies on crossbeam‑epoch for epoch‑based memory reclamation as documented in core/skiplist/mod.rs, ensuring that nodes removed by one thread are only freed after all other threads have exited the critical section where they might hold references to those nodes.

Where the Skiplist Appears in Turso’s MVCC Engine

The skiplist data structure appears throughout Turso’s MVCC layer, providing a unified, lock‑free ordered container for critical metadata and data indices.

Transaction Table Management

Active transactions are stored in a SkipMap<TxID, Transaction> defined in core/mvcc/database/mod.rs. This map orders transactions by their ID, enabling the engine to perform fast range scans for conflict detection and commit‑dependency tracking. Because worker threads insert and remove transactions concurrently during commit processing, the lock‑free property prevents the contention that would plague a Mutex<BTreeMap>.

Finalized State History

Committed and aborted transaction states live in SkipMap<TxID, TransactionState>, which maintains a compact history for checkpointing operations. Reads dominate this workload (e.g., during recovery), while writes are rare; the skiplist provides O(log n) look‑ups with the same concurrent semantics as the live transaction table.

Row Version Chains

Each row’s version chain is accessible through SkipMap<RowID, RowVersions> in core/mvcc/database/mod.rs. While the version chain itself is protected by Arc<RwLock<Vec<RowVersion>>>, the mapping from RowID to that chain uses the skiplist. This allows the MVCC engine to locate version chains quickly for reads, writes, and garbage collection, and to walk rows in key order for range scans while supporting simultaneous inserts from multiple transactions.

Index‑Row Mapping

Secondary indexes rely on a type alias declared at line 128 of core/mvcc/database/mod.rs:

type IndexRowsMap<A> = SkipMap<Arc<SortableIndexKey>, RowVersions, BasicComparator, A>;

This maps index keys to their version chains. Index entries are created and deleted concurrently by many transactions, and the ordered map enables efficient range scans on secondary indexes without requiring a global lock.

Schema‑Level Metadata

Schema metadata such as table_id_to_rootpage uses SkipMap<MVTableId, Option<u64>> and similar small, highly contended maps. Lock‑free reads and writes keep the engine responsive even when multiple transactions access catalog information simultaneously.

Concurrency Benefits and Performance Trade‑offs

The skiplist data structure delivers specific advantages for Turso’s architecture:

  • Concurrent Updates – Because methods take &self, threads can insert and delete simultaneously without blocking. This is critical for the multi‑core commit path where each transaction may insert rows into several indexes concurrently.
  • Ordered Traversal – Maintaining natural key order allows range scans (e.g., SELECT … WHERE x BETWEEN a AND b) by walking forward from the first matching key, identical to B‑Tree semantics but without lock overhead.
  • Memory Safety – Epoch‑based GC guarantees that a node removed by one thread remains valid for any transaction that started before the removal, aligning perfectly with MVCC visibility guarantees.

The trade‑off is a few percent higher latency for pure read‑only workloads compared to a hand‑optimized RwLock<BTreeMap>. Turso accepts this cost in exchange for higher throughput under mixed transactional loads.

Implementation Details and Code Examples

The skiplist implementation resides in core/skiplist, with the MVCC integration located in core/mvcc/database/mod.rs and iteration helpers in core/mvcc/cursor.rs.

Creating and using a transaction table:

// 1️⃣ Creating a skip‑map that will hold transaction objects
use turso_core::skiplist::SkipMap;
let txs: SkipMap<u64, Transaction> = SkipMap::new();

// Insert a new transaction (concurrent threads may call this simultaneously)
txs.insert(tx_id, transaction);

// Look up a transaction by its ID
if let Some(tx) = txs.get(tx_id) {
    println!("found tx {}", tx_id);
}

Index mapping with custom comparator and allocator:

// 2️⃣ Index‑row map used by MVCC (see core/mvcc/database/mod.rs)
use std::sync::Arc;
use turso_core::skiplist::{SkipMap, comparator::BasicComparator};
type IndexRowsMap = SkipMap<Arc<SortableIndexKey>, RowVersions, BasicComparator, TursoAllocator>;

let index_map: IndexRowsMap = IndexRowsMap::new();
index_map.insert(key_arc, row_versions);

Performing ordered range scans:

// 3️⃣ Range scan – iterate over keys in order
for entry in &index_map {
    let key = entry.key();          // Arc<SortableIndexKey>
    let versions = entry.value();   // RowVersions
    // process the index entry ...
}

These patterns are defined in core/skiplist/map.rs and utilized throughout the MVCC engine to provide lock‑free ordered access.

Summary

  • Turso’s core/skiplist module provides lock‑free SkipMap and SkipSet containers built on crossbeam‑skiplist with epoch‑based memory reclamation.
  • The skiplist data structure enables concurrent, ordered storage for transaction tables, finalized states, row version chains, and secondary indexes without global locks.
  • All mutating operations use &self, making the containers Send + Sync and suitable for high‑contention MVCC workloads.
  • File locations include core/skiplist/map.rs for the container implementation and core/mvcc/database/mod.rs for the MVCC integration.
  • The architecture trades a small amount of read‑only latency for significantly higher throughput during concurrent transactional operations.

Frequently Asked Questions

Why does Turso use a skiplist instead of a B‑Tree?

Turso uses a skiplist data structure because it offers lock‑free concurrent operations while maintaining ordered traversal semantics. Traditional B‑Trees require either global locks or complex latch‑coupling for thread safety, which becomes a bottleneck when many threads insert into transaction tables and indexes simultaneously. The skiplist’s &self mutation model allows unlimited concurrent writers without contention.

How does Turso’s skiplist handle memory safety without locks?

The implementation uses epoch‑based memory reclamation provided by crossbeam‑epoch. When a node is removed from the skiplist, it is not immediately freed; instead, it is marked for deletion and reclaimed only after all threads have exited the epoch in which they might have observed the node. This prevents use‑after‑free bugs while maintaining the lock‑free property.

What specific database components rely on the skiplist?

According to the source code in core/mvcc/database/mod.rs, the skiplist backs the transaction table (SkipMap<TxID, Transaction>), finalized state history (SkipMap<TxID, TransactionState>), row version chain lookup (SkipMap<RowID, RowVersions>), index‑row mapping (IndexRowsMap), and schema metadata maps such as table_id_to_rootpage. These components require frequent concurrent mutations and ordered range scans.

Is the skiplist data structure slower than a B‑Tree for read‑only queries?

For pure read‑only workloads, the skiplist can be a few percent slower than a hand‑optimized RwLock<BTreeMap> due to the overhead of pointer chasing and cache misses. However, Turso prioritizes transactional throughput where concurrent writes dominate, making the skiplist’s lock‑free concurrency model the optimal choice for its MVCC engine.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →