{"id":51514701,"url":"https://github.com/jamesgober/txn-db","last_synced_at":"2026-07-08T10:01:41.176Z","repository":{"id":362973903,"uuid":"1253906648","full_name":"jamesgober/txn-db","owner":"jamesgober","description":"MVCC transaction primitive for Rust databases - snapshot isolation, SSI, deadlock detection.","archived":false,"fork":false,"pushed_at":"2026-06-06T19:01:11.000Z","size":65,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-06T21:04:10.661Z","etag":null,"topics":["database","mvcc","reps","rust","snapshot-isolation","transactions"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesgober.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-29T23:47:59.000Z","updated_at":"2026-06-06T19:01:15.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jamesgober/txn-db","commit_stats":null,"previous_names":["jamesgober/txn-db"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/jamesgober/txn-db","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ftxn-db","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ftxn-db/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ftxn-db/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ftxn-db/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesgober","download_url":"https://codeload.github.com/jamesgober/txn-db/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Ftxn-db/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35260671,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","mvcc","reps","rust","snapshot-isolation","transactions"],"created_at":"2026-07-08T10:01:40.402Z","updated_at":"2026-07-08T10:01:41.148Z","avatar_url":"https://github.com/jamesgober.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n    \u003cimg width=\"99\" alt=\"Rust logo\" src=\"https://raw.githubusercontent.com/jamesgober/rust-collection/72baabd71f00e14aa9184efcb16fa3deddda3a0a/assets/rust-logo.svg\"\u003e\n    \u003cbr\u003e\n    \u003cb\u003etxn-db\u003c/b\u003e\n    \u003cbr\u003e\n    \u003csub\u003e\u003csup\u003eMVCC TRANSACTION ENGINE\u003c/sup\u003e\u003c/sub\u003e\n\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n    \u003ca href=\"https://crates.io/crates/txn-db\"\u003e\u003cimg alt=\"Crates.io\" src=\"https://img.shields.io/crates/v/txn-db\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://crates.io/crates/txn-db\" alt=\"Download txn-db\"\u003e\u003cimg alt=\"Crates.io Downloads\" src=\"https://img.shields.io/crates/d/txn-db?color=%230099ff\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://docs.rs/txn-db\" title=\"txn-db Documentation\"\u003e\u003cimg alt=\"docs.rs\" src=\"https://img.shields.io/docsrs/txn-db\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/jamesgober/txn-db/actions\"\u003e\u003cimg alt=\"GitHub CI\" src=\"https://github.com/jamesgober/txn-db/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/rust-lang/rfcs/blob/master/text/2495-min-rust-version.md\" title=\"MSRV\"\u003e\u003cimg alt=\"MSRV\" src=\"https://img.shields.io/badge/MSRV-1.85%2B-blue\"\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cbr\u003e\n\n\u003cdiv align=\"left\"\u003e\n    \u003cp\u003e\n        \u003cstrong\u003etxn-db\u003c/strong\u003e is a \u003cb\u003emulti-version concurrency control\u003c/b\u003e transaction engine: the layer that turns a key-value store into a transactional database. Each 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 rather than holding locks for the duration of a transaction.\n    \u003c/p\u003e\n    \u003cp\u003e\n        It is deliberately a \u003cb\u003elayer, not a store\u003c/b\u003e: the version store is a trait, so \u003ccode\u003etxn-db\u003c/code\u003e composes on top of \u003ccode\u003elsm-db\u003c/code\u003e or any other backing store, and its durable commit log is \u003ccode\u003ewal-db\u003c/code\u003e. Snapshot isolation is the default; serializable isolation (SSI) is a feature flag.\n    \u003c/p\u003e\n    \u003cp\u003e\n        The common case is \u003ccode\u003ebegin\u003c/code\u003e / \u003ccode\u003eget\u003c/code\u003e / \u003ccode\u003eput\u003c/code\u003e / \u003ccode\u003ecommit\u003c/code\u003e, with automatic conflict detection and retry guidance on the error path.\n    \u003c/p\u003e\n    \u003cbr\u003e\n    \u003chr\u003e\n    \u003cp\u003e\n        \u003cstrong\u003eMSRV is 1.85+\u003c/strong\u003e (Rust 2024 edition). Snapshot isolation by default. Optional serializable (SSI). Durable commits via wal-db.\n    \u003c/p\u003e\n    \u003cblockquote\u003e\n        \u003cstrong\u003eStatus: stable (1.0).\u003c/strong\u003e The public API is frozen until \u003ccode\u003e2.0\u003c/code\u003e, and the durable commit-log format is frozen for the \u003ccode\u003e1.x\u003c/code\u003e series (see \u003ca href=\"./docs/COMMIT_LOG_FORMAT.md\"\u003e\u003ccode\u003edocs/COMMIT_LOG_FORMAT.md\u003c/code\u003e\u003c/a\u003e). See \u003ca href=\"./CHANGELOG.md\"\u003e\u003ccode\u003eCHANGELOG.md\u003c/code\u003e\u003c/a\u003e for detail.\n    \u003c/blockquote\u003e\n\u003c/div\u003e\n\n\u003chr\u003e\n\u003cbr\u003e\n\n\u003ch2\u003eWhat it does\u003c/h2\u003e\n\nAvailable now (`0.5`, feature-complete):\n\n- **MVCC** \u0026mdash; each write creates a new version; readers see a consistent snapshot without blocking writers\n- **Snapshot isolation** \u0026mdash; a transaction reads the database as of its start timestamp; its own writes are visible to itself before commit\n- **Serializable (SSI)** \u0026mdash; opt-in read-set validation under the `serializable` feature, rejecting write skew and the read-only anomaly\n- **Durable commit log** \u0026mdash; under the `durability` feature, `Db::open` logs each commit to a `wal-db` write-ahead log and syncs before acknowledging; the log is replayed on restart\n- **Garbage collection** \u0026mdash; `Db::collect_garbage` reclaims versions no live transaction or snapshot can observe; an oldest-reader watermark guarantees a held snapshot's versions are never reclaimed\n- **Write-write conflict detection** \u0026mdash; first-committer-wins at commit; the later writer is told to retry with a typed, retryable error\n- **Sharded commit path** \u0026mdash; lock-free timestamp allocation and per-shard conflict checks, so commits to unrelated keys do not contend (loom-checked)\n- **Pluggable backing store** \u0026mdash; the version store is the `VersionStore` trait; an in-memory store ships, and any backend (an LSM tree, a B-tree, a remote store) plugs in unchanged\n\n\n\u003cbr\u003e\n\n## Installation\n\n```toml\n[dependencies]\ntxn-db = \"1.0\"\n\n# Opt into serializable isolation and/or a durable commit log:\ntxn-db = { version = \"1.0\", features = [\"serializable\", \"durability\"] }\n```\n\n\u003cbr\u003e\n\n## Quick start\n\nFor a single read or write, skip the ceremony — `get`, `put`, and `delete` on\nthe database run in their own transaction (writes retry on conflict):\n\n```rust\nuse txn_db::Db;\n\nlet db = Db::new();\ndb.put(b\"user:1:name\".to_vec(), b\"ada\".to_vec())?;\nassert_eq!(db.get(b\"user:1:name\")?.as_deref(), Some(\u0026b\"ada\"[..]));\n# Ok::\u003c(), txn_db::TxnError\u003e(())\n```\n\nWhen several operations must be atomic, open a transaction: begin, read and\nwrite through it, commit.\n\n```rust\nuse txn_db::Db;\n\nlet db = Db::new();\n\n// Write two keys in one atomic transaction.\nlet mut tx = db.begin();\ntx.put(b\"user:1:name\".to_vec(), b\"ada\".to_vec());\ntx.put(b\"user:1:role\".to_vec(), b\"admin\".to_vec());\ntx.commit()?;\n\n// A later transaction reads a consistent snapshot.\nlet tx = db.begin();\nassert_eq!(tx.get(b\"user:1:name\")?.as_deref(), Some(\u0026b\"ada\"[..]));\n# Ok::\u003c(), txn_db::TxnError\u003e(())\n```\n\nWhen two transactions race to write the same key, the first to commit wins and\nthe second is told to retry — that is what prevents lost updates:\n\n```rust\nuse txn_db::Db;\n\nlet db = Db::new();\nlet mut a = db.begin();\nlet mut b = db.begin();\na.put(b\"counter\".to_vec(), b\"1\".to_vec());\nb.put(b\"counter\".to_vec(), b\"2\".to_vec());\n\na.commit()?;                          // first committer wins\nlet err = b.commit().unwrap_err();    // second is rejected\nassert!(err.is_retryable());          // retry against the fresh snapshot\n# Ok::\u003c(), txn_db::TxnError\u003e(())\n```\n\nThe retry loop is a few lines; see [`examples/concurrent_counter.rs`](./examples/concurrent_counter.rs)\nfor the contended read-modify-write pattern, [`examples/bank_transfer.rs`](./examples/bank_transfer.rs)\nfor an atomic multi-key transfer, and [`examples/custom_store.rs`](./examples/custom_store.rs)\nfor plugging in your own `VersionStore`.\n\n## Serializable isolation\n\nSnapshot isolation still allows *write skew*: two transactions that read the same\nrows and write different ones can both commit, breaking an invariant that ties\nthose rows together. With the `serializable` feature,\n[`begin_serializable`](https://docs.rs/txn-db) validates a transaction's read set\nat commit and rejects exactly those cases.\n\n```rust\n# #[cfg(feature = \"serializable\")]\n# {\nuse txn_db::Db;\n\nlet db = Db::new();\nlet mut seed = db.begin();\nseed.put(b\"on_call:alice\".to_vec(), vec![1]);\nseed.put(b\"on_call:bob\".to_vec(), vec![1]);\nseed.commit()?;\n\n// Both read the pair, then each takes one row off — classic write skew.\nlet mut t1 = db.begin_serializable();\nlet mut t2 = db.begin_serializable();\nlet _ = (t1.get(b\"on_call:alice\")?, t1.get(b\"on_call:bob\")?);\nlet _ = (t2.get(b\"on_call:alice\")?, t2.get(b\"on_call:bob\")?);\nt1.put(b\"on_call:alice\".to_vec(), vec![0]);\nt2.put(b\"on_call:bob\".to_vec(), vec![0]);\n\nt1.commit()?;                          // first commits\nassert!(t2.commit().is_err());         // second read a row t1 changed — rejected\n# }\n# Ok::\u003c(), txn_db::TxnError\u003e(())\n```\n\nSee [`examples/serializable_doctors.rs`](./examples/serializable_doctors.rs) for the\nfull on-call-doctors demonstration, side by side under both isolation levels.\n\n## Durability\n\nWith the `durability` feature, `Db::open` backs the database with a `wal-db`\nwrite-ahead log. Each commit's record is appended and synced before `commit`\nreturns, so an acknowledged commit survives a crash; on restart the log is\nreplayed and uncommitted work leaves no trace.\n\n```rust\n# #[cfg(feature = \"durability\")]\n# {\n# let dir = tempfile::tempdir().unwrap();\n# let path = dir.path().join(\"txn.wal\");\nuse txn_db::Db;\n\n// First run: commit, then the process exits.\n{\n    let db = Db::open(\u0026path)?;\n    let mut tx = db.begin();\n    tx.put(b\"k\".to_vec(), b\"v\".to_vec());\n    tx.commit()?;\n}\n\n// Restart: the log is replayed and the committed write is back.\nlet db = Db::open(\u0026path)?;\nassert_eq!(db.begin().get(b\"k\")?.as_deref(), Some(\u0026b\"v\"[..]));\n# }\n# Ok::\u003c(), txn_db::TxnError\u003e(())\n```\n\nSee [`examples/durable_store.rs`](./examples/durable_store.rs) for a commit /\ndrop / reopen walkthrough.\n\n## Garbage collection\n\nEvery write keeps the previous version so in-flight readers see a stable\nsnapshot, so versions accumulate. `Db::collect_garbage` reclaims the versions no\nlive transaction or snapshot can still observe and returns how many it removed.\nA held snapshot pins the versions it can see, so collection never reclaims data\na live reader depends on.\n\n```rust\nuse txn_db::Db;\n\nlet db = Db::new();\nfor v in 0..100u8 {\n    let mut tx = db.begin();\n    tx.put(b\"k\".to_vec(), vec![v]);\n    tx.commit()?;\n}\n\n// With no snapshot held, only the newest version need be kept.\nlet reclaimed = db.collect_garbage();\nassert!(reclaimed \u003e 0);\nassert_eq!(db.begin().get(b\"k\")?.as_deref(), Some(\u0026[99u8][..]));\n# Ok::\u003c(), txn_db::TxnError\u003e(())\n```\n\nSee [`examples/garbage_collection.rs`](./examples/garbage_collection.rs) for a\ndemonstration of a held snapshot pinning versions against collection.\n\n\u003cbr\u003e\n\n## Examples\n\n| Example | What it shows |\n|---------|---------------|\n| [`quick_start`](./examples/quick_start.rs) | Shortest end-to-end: open, write, read back. |\n| [`bank_transfer`](./examples/bank_transfer.rs) | Atomic multi-key update with conflict retries. |\n| [`concurrent_counter`](./examples/concurrent_counter.rs) | Many threads increment one key; no update is lost. |\n| [`snapshot_reads`](./examples/snapshot_reads.rs) | A snapshot stays stable as the database moves on. |\n| [`custom_store`](./examples/custom_store.rs) | Backing the engine with a custom `VersionStore`. |\n| [`serializable_doctors`](./examples/serializable_doctors.rs) | Write skew under SI vs serializable (needs `--features serializable`). |\n| [`durable_store`](./examples/durable_store.rs) | Commit, drop, reopen — recovery from the log (needs `--features durability`). |\n| [`garbage_collection`](./examples/garbage_collection.rs) | Reclaiming old versions; a held snapshot pins what it can see. |\n\n```bash\ncargo run --example quick_start\ncargo run --example garbage_collection\ncargo run --example serializable_doctors --features serializable\ncargo run --example durable_store --features durability\n```\n\n\u003cbr\u003e\n\n## Status\n\nThis is `1.0` — **stable**. The engine is feature-complete (snapshot and\nserializable isolation, sharded lock-free commits, a durable `wal-db` commit log,\nwatermark garbage collection), tuned, hardened against adversarial schedules, and\nbenchmarked honestly. The **public API is frozen until `2.0`** and the durable\ncommit-log format is frozen for the `1.x` series\n([`docs/COMMIT_LOG_FORMAT.md`](./docs/COMMIT_LOG_FORMAT.md)). See\n[`docs/API.md`](./docs/API.md) for the full surface and\n[`docs/PERFORMANCE.md`](./docs/PERFORMANCE.md) for hot-path and comparison\nnumbers.\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Where It Fits\n\n`txn-db` is the transaction layer. It builds on:\n\n- [`wal-db`](https://github.com/jamesgober/wal-db) \u0026mdash; durable transaction commit log\n- [`lsm-db`](https://github.com/jamesgober/lsm-db) \u0026mdash; a natural backing version store\n- Hive DB \u0026mdash; the transaction orchestration layer (DISTRO) builds on these semantics\n\nIt stays foreign-compatible: usable standalone over any version store that implements the trait.\n\n\u003cbr\u003e\n\n## Contributing\n\nBefore opening a PR, `cargo fmt --all`, `cargo clippy --all-targets --all-features -- -D warnings`, and `cargo test --all-features` must be clean. Hot-path changes require a `criterion` benchmark; correctness-critical paths require property and/or `loom` tests.\n\n\n\u003cbr\u003e\n\n\u003cdiv id=\"license\"\u003e\n    \u003ch2\u003eLicense\u003c/h2\u003e\n    \u003cp\u003eLicensed under either of\u003c/p\u003e\n    \u003cul\u003e\n        \u003cli\u003e\u003cb\u003eApache License, Version 2.0\u003c/b\u003e \u0026mdash; see \u003ca href=\"./LICENSE-APACHE\"\u003eLICENSE-APACHE\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003cb\u003eMIT License\u003c/b\u003e \u0026mdash; see \u003ca href=\"./LICENSE-MIT\"\u003eLICENSE-MIT\u003c/a\u003e\u003c/li\u003e\n    \u003c/ul\u003e\n    \u003cp\u003eat your option.\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ch2\u003e\u003c/h2\u003e\n  \u003csup\u003eCOPYRIGHT \u003csmall\u003e\u0026copy;\u003c/small\u003e 2026 \u003cstrong\u003eJAMES GOBER.\u003c/strong\u003e\u003c/sup\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Ftxn-db","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesgober%2Ftxn-db","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Ftxn-db/lists"}