How Turso Manages Concurrent Write Conflicts in Its MVCC Framework

Turso detects and aborts conflicting transactions during the commit phase by comparing timestamps against version chains, returning a WriteWriteConflict error when multiple transactions attempt to modify the same row concurrently.

Turso's Multi-Version Concurrency Control (MVCC) engine provides snapshot isolation for concurrent database operations. Unlike pessimistic locking systems that block writers, the tursodatabase/turso implementation allows multiple transactions to proceed concurrently and validates conflicts only at commit time. This approach ensures high throughput while preventing silent data overwrites through deterministic conflict detection implemented primarily in core/mvcc/database/mod.rs.

Understanding Turso's MVCC Version Chain Architecture

Row Version Storage with Timestamp Metadata

Each row in Turso exists as a Vec<RowVersion> structure that maintains a complete history of modifications. According to the source code in core/mvcc/database/mod.rs at line 1615, every version contains a begin timestamp indicating when it became visible, plus an optional end marker denoting when it was superseded or deleted. This chain-based storage enables snapshot isolation by allowing each transaction to see a consistent point-in-time view of the database without blocking concurrent writers.

Timestamp-Based Visibility Rules

Transactions read row versions based on their begin_ts (transaction start timestamp), ensuring they only see committed data that existed when they started. When a transaction modifies a row, Turso appends a new version to the chain rather than updating in place. The existing version remains accessible to concurrent readers until the writing transaction commits and closes the version chain by setting its end field to the commit timestamp.

Commit-Time Conflict Detection in Turso

The check_version_conflicts Routine

During the Preparing stage of commit, Turso iterates over every row modified by the transaction and invokes check_version_conflicts. This function, located at lines 1685-1704 in core/mvcc/database/mod.rs, traverses the version chain backwards (most recent first) to identify conflicts. Specifically, it searches for any version where end_ts > tx.begin_ts (indicating a commit occurred after this transaction began) or an in-flight tombstone created by another active transaction. When detected, Turso immediately returns LimboError::WriteWriteConflict.

Tombstone Handling as Write Locks

Deleting a row creates a tombstone version with begin set to None and end pointing to the creator's TxID. As implemented at lines 1707-1715, Turso treats these tombstones exactly like active write locks during conflict detection. Any transaction attempting to write a row with an existing tombstone from another active transaction triggers an immediate abort with a write-write conflict error, preventing lost updates on deleted rows.

Exclusive Transactions and Schema Conflicts

Exclusive Transaction Guards

For transactions requiring serialized access, Turso checks mvcc_store.is_exclusive_tx before allowing commits. Lines 2629-2638 in core/mvcc/database/mod.rs implement the logic that aborts concurrent commits with WriteWriteConflict when an exclusive transaction is active. This ensures that BEGIN EXCLUSIVE transactions have sole write access to the database, providing a pessimistic locking alternative to the optimistic MVCC default.

Schema Version Validation

Beyond row-level conflicts, Turso validates schema stability during commit. If the schema_updated flag is set—indicating a schema change occurred after the transaction began—Turso aborts the transaction with LimboError::SchemaConflict rather than risking execution against an incompatible schema structure. This check appears at lines 2624-2632 alongside the exclusive transaction validation.

Practical Example: Detecting Write-Write Conflicts

The following Rust example demonstrates how Turso's MVCC framework handles concurrent insertions on the same primary key:

use turso::{Connection, Result, LimboError};

fn demo_conflict() -> Result<()> {
    // Open two independent connections to the same database.
    let mut db1 = Connection::open("file:demo.db?mode=rw")?;
    let mut db2 = Connection::open("file:demo.db?mode=rw")?;

    // Enable MVCC on the database (once per file).
    db1.execute("PRAGMA journal_mode = 'mvcc'")?;

    // Transaction 1 – writes a row.
    db1.execute("BEGIN; INSERT INTO users(id, name) VALUES (1, 'Alice');")?;

    // Transaction 2 – concurrently writes the same primary key.
    db2.execute("BEGIN; INSERT INTO users(id, name) VALUES (1, 'Bob');")?;

    // Commit Tx1 – succeeds.
    db1.execute("COMMIT;")?;

    // Commit Tx2 – aborts with a write‑write conflict.
    match db2.execute("COMMIT;") {
        Ok(_) => println!("Tx2 committed (unexpected)"),
        Err(e) => {
            if let Some(err) = e.downcast_ref::<LimboError>() {
                match err {
                    LimboError::WriteWriteConflict => {
                        println!("Tx2 failed: write‑write conflict detected");
                    }
                    _ => println!("Tx2 failed with other MVCC error: {:?}", err),
                }
            }
        }
    }
    Ok(())
}

In this scenario, the second transaction fails during the commit phase because check_version_conflicts detects that the first transaction committed a version of row 1 after the second transaction began.

Summary

  • Turso stores each row as a version chain (Vec<RowVersion>) with timestamp metadata to enable snapshot isolation without read locks.
  • The check_version_conflicts function detects conflicts at commit time by scanning for versions committed after the transaction's begin_ts or active tombstones from concurrent writers.
  • Delete operations create tombstones that function as write locks, preventing subsequent modifications until the deleting transaction commits or rolls back.
  • Write-write conflicts result in a specific LimboError::WriteWriteConflict error, while schema changes trigger LimboError::SchemaConflict.
  • Exclusive transactions bypass optimistic concurrency by forcing serialization through is_exclusive_tx checks.

Frequently Asked Questions

When does Turso detect write conflicts in the transaction lifecycle?

Turso detects conflicts during the commit phase when a transaction enters the Preparing state, not at the moment of the INSERT, UPDATE, or DELETE operation. This optimistic approach allows transactions to proceed concurrently and only validates conflicts at commit time by examining version chains in core/mvcc/database/mod.rs and comparing timestamps against the transaction's begin_ts.

What error does Turso return when two transactions modify the same row?

Turso returns LimboError::WriteWriteConflict when check_version_conflicts identifies that another transaction has committed a version or holds a tombstone on the same row after the current transaction began. This error propagates to the client, requiring the application to retry the transaction with fresh data.

How does Turso handle deleted rows during conflict detection?

Deleted rows create tombstone versions with begin set to None and end pointing to the deleting transaction's ID. During conflict checks at lines 1707-1715 of core/mvcc/database/mod.rs, Turso treats these tombstones as active write locks. Any concurrent transaction attempting to modify the same row aborts with a write-write conflict, preventing resurrection of deleted data by concurrent writers.

What is the difference between a write-write conflict and a schema conflict in Turso?

A write-write conflict occurs when two transactions modify the same row concurrently and is detected via version chain timestamp analysis in check_version_conflicts. A schema conflict occurs when the database schema changes after a transaction begins, detected via the schema_updated flag at lines 2624-2632, and results in a LimboError::SchemaConflict rather than a write-write error, ensuring transactions execute against a consistent schema definition.

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 →