# txn-db v0.3.0 — Concurrency control & serializable isolation

**The hard part, on schedule.** v0.3.0 adds serializable isolation and rebuilds
the commit path to be sharded and lock-free, retiring the single global commit
lock the foundation release shipped with. Snapshot isolation stays the default
and is unchanged. The concurrent-commit path is verified with `loom`.

## What is txn-db?

A multi-version concurrency control (MVCC) transaction engine: the layer that
turns a key-value store into a transactional database. Every write produces a new
version tagged with a commit timestamp, so readers get a stable snapshot without
ever blocking writers, and writers detect conflicts at commit time instead of
holding locks for the lifetime of a transaction. The version store is a trait, so
the engine composes on top of any backend that can keep timestamped versions.

## What's new in 0.3.0

### Serializable isolation

Snapshot isolation permits *write skew*: two transactions that read the same rows
and write different ones can both commit, breaking an invariant that ties those
rows together. With the new `serializable` feature, `Db::begin_serializable`
starts a transaction that records every key it reads and validates that read set
at commit. If any key it read has changed since its snapshot, the commit is
rejected — closing write skew and the read-only anomaly.

```rust
# #[cfg(feature = "serializable")]
# {
use txn_db::Db;

let db = Db::new();
let mut seed = db.begin();
seed.put(b"on_call:alice".to_vec(), vec![1]);
seed.put(b"on_call:bob".to_vec(), vec![1]);
seed.commit()?;

// Both read the pair, then each takes one row off — classic write skew.
let mut t1 = db.begin_serializable();
let mut t2 = db.begin_serializable();
let _ = (t1.get(b"on_call:alice")?, t1.get(b"on_call:bob")?);
let _ = (t2.get(b"on_call:alice")?, t2.get(b"on_call:bob")?);
t1.put(b"on_call:alice".to_vec(), vec![0]);
t2.put(b"on_call:bob".to_vec(), vec![0]);

t1.commit()?;                     // first commits
assert!(t2.commit().is_err());    // second read a row t1 changed — rejected
# }
# Ok::<(), txn_db::TxnError>(())
```

It is optimistic read-set validation: the serialization order is the commit
order, and retry-on-conflict applies to serializable transactions the same way it
does to snapshot ones. A serializable transaction that writes nothing commits
trivially, since it observed a consistent snapshot. Snapshot isolation remains
the default and is entirely unaffected when the feature is off.

### Sharded, lock-free commit path

The foundation release serialized every commit behind one global lock. v0.3.0
shards the keyspace: `MemoryStore` partitions keys across independently locked
maps, a commit locks only the shards its keys touch (in a fixed order, so
concurrent commits cannot deadlock), and commits to unrelated keys run in
parallel. Commit timestamps are allocated with a single atomic increment, and the
read watermark — the timestamp a new transaction reads at — is published in its
own atomic, so `begin` and `snapshot` take no lock at all. `MemoryStore::with_shards`
tunes the shard count.

### `loom`-verified concurrency

The concurrent-commit path is checked with `loom`, which explores every legal
interleaving of timestamp allocation, shard locking, and watermark advancement:

- On a contended key, exactly one of two racing commits wins and its value is the
  one that ends up visible — never both, never neither.
- Two commits to disjoint keys both succeed, and once both finish a fresh
  snapshot sees both writes — proving out-of-order commit completion never makes
  a write timestamp visible before its data is applied.

The CI `loom` job now runs these as a required check.

## Testing

- **Serializable property tests** — write skew never lets both commit; disjoint
  serializable transactions both commit; uncontended and read-only serializable
  transactions always commit.
- **Snapshot-isolation property and concurrency tests** carried over from the
  foundation — reference-model equivalence, snapshot stability, lost-update
  prevention under contention, reader isolation.
- **`loom` model checks** for the concurrent-commit path.

## Breaking changes

One, on the **Tier-3 trait only**. `VersionStore` replaced its `latest_commit_ts`
and `apply` methods with a single:

```rust
fn try_commit(
    &self,
    read_ts: Timestamp,
    commit_ts: Timestamp,
    writes: Vec<WriteEntry>,
    reads: &[Arc<[u8]>],
) -> Result<()>;
```

This makes the store the atomic serialization point for a commit — validating the
read and write sets and applying the writes as one step — which is what enables
sharded, lock-free commits. **The Tier-1 surface (`Db`, `Transaction`,
`Snapshot`) is unchanged**; only code that implements a custom `VersionStore`
needs to migrate, and a delegating wrapper is a one-line forward to the inner
store's `try_commit`.

## Verification

Run on Windows x86_64 (Rust stable 1.95.x and MSRV 1.85) and on Linux (WSL2
Ubuntu); the configured CI matrix runs the same on Linux, macOS, and Windows:

```bash
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo test                       # default features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo +1.85 clippy --all-targets --all-features -- -D warnings
cargo +1.85 test --all-features
RUSTFLAGS="--cfg loom" cargo test --test loom_txn --release
cargo build --examples --all-features
cargo deny check
cargo audit
```

All green. Counts at this tag (`--all-features`): 31 unit + 10 integration
(4 serializable property + 4 snapshot-isolation property + 2 concurrency) + 30
doctests, plus 2 `loom` model checks.

## What's next

- **0.4.0 — Durable commit log.** A `wal-db`-backed commit log under the
  `durability` feature: log the commit record before acknowledging, replay
  committed transactions on open, and crash-recovery integration tests.

## Installation

```toml
[dependencies]
txn-db = "0.3"

# Opt into serializable isolation:
txn-db = { version = "0.3", features = ["serializable"] }
```

MSRV: Rust 1.85 (the 2024 edition).

## Documentation

- [README](https://github.com/jamesgober/txn-db/blob/main/README.md)
- [API Reference](https://github.com/jamesgober/txn-db/blob/main/docs/API.md)
- [CHANGELOG](https://github.com/jamesgober/txn-db/blob/main/CHANGELOG.md)

---

**Full diff:** [`v0.2.0...v0.3.0`](https://github.com/jamesgober/txn-db/compare/v0.2.0...v0.3.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/txn-db/blob/main/CHANGELOG.md#030---2026-06-07).
