pub struct Db<S: VersionStore = MemoryStore> { /* private fields */ }Expand description
A transactional, multi-version key-value database.
Db is the front door. Db::new gives you an in-memory database;
Db::with_store builds one over any VersionStore. From there the whole
common case is begin / get /
put / commit,
with snapshot for read-only point-in-time views.
Transactions default to snapshot isolation. With the serializable
feature enabled, begin_serializable starts a transaction whose read set is
validated at commit, rejecting write skew and the other anomalies snapshot
isolation permits.
A Db is a clonable handle over shared state, like an Arc. Cloning it
is cheap and every clone refers to the same database, so the idiomatic way
to use it across threads is to clone a handle per thread.
§Examples
The four-call common case:
use txn_db::Db;
let db = Db::new();
let mut tx = db.begin();
tx.put(b"greeting".to_vec(), b"hei".to_vec());
tx.commit()?;
let tx = db.begin();
assert_eq!(tx.get(b"greeting")?.as_deref(), Some(&b"hei"[..]));Sharing one database across threads:
use std::thread;
use txn_db::Db;
let db = Db::new();
let handles: Vec<_> = (0..4u8)
.map(|i| {
let db = db.clone();
thread::spawn(move || {
let mut tx = db.begin();
tx.put(vec![i], vec![i]);
// Independent keys never conflict.
tx.commit().expect("commit");
})
})
.collect();
for h in handles {
h.join().expect("thread");
}Implementations§
Source§impl Db<MemoryStore>
impl Db<MemoryStore>
Sourcepub fn new() -> Self
pub fn new() -> Self
Create an empty in-memory database.
This is the default configuration: a MemoryStore backing store, ready
for begin.
§Examples
use txn_db::Db;
let db = Db::new();
assert_eq!(db.last_committed(), txn_db::Timestamp::ZERO);Sourcepub fn open(path: impl AsRef<Path>) -> Result<Db<MemoryStore>>
Available on crate feature durability only.
pub fn open(path: impl AsRef<Path>) -> Result<Db<MemoryStore>>
durability only.Open a durable database backed by a write-ahead log at path, replaying
any committed transactions already in the log.
Every transaction committed against the returned database appends its
record to the log and syncs it before commit
returns, so an acknowledged commit survives a crash. On open, the log is
replayed: each committed transaction is reinstated, and a transaction that
never reached the log — because it aborted, or because the process crashed
before its record was made durable — is simply absent. The recovered data
lives in memory; the log is the durable record from which it is rebuilt.
Available with the durability feature.
§Errors
Returns TxnError::Durability if the log
cannot be opened or a record read back from it does not decode.
§Examples
use txn_db::Db;
// Commit, then drop the database.
{
let db = Db::open(&path)?;
let mut tx = db.begin();
tx.put(b"k".to_vec(), b"v".to_vec());
tx.commit()?;
}
// Reopening replays the log: the committed write is still there.
let db = Db::open(&path)?;
assert_eq!(db.begin().get(b"k")?.as_deref(), Some(&b"v"[..]));Source§impl<S: VersionStore> Db<S>
impl<S: VersionStore> Db<S>
Sourcepub fn with_store(store: S) -> Self
pub fn with_store(store: S) -> Self
Create a database over a custom VersionStore.
This is the Tier-3 seam: supply any backing store and the transaction semantics — snapshot isolation, read-your-own-writes, conflict detection — compose on top of it unchanged.
§Examples
use txn_db::{Db, MemoryStore};
let db = Db::with_store(MemoryStore::new());
let mut tx = db.begin();
tx.put(b"k".to_vec(), b"v".to_vec());
tx.commit()?;Sourcepub fn begin(&self) -> Transaction<S>
pub fn begin(&self) -> Transaction<S>
Begin a snapshot-isolation transaction over the current state.
The transaction takes its snapshot at this moment: it reads as of the
most recent commit and is unaffected by commits that happen afterward.
Its writes are checked for write-write conflicts at commit, but its reads
are not validated — use begin_serializable (with the serializable
feature) when you need serializability.
§Examples
use txn_db::Db;
let db = Db::new();
let mut tx = db.begin();
tx.put(b"k".to_vec(), b"v".to_vec());
tx.commit()?;Sourcepub fn begin_serializable(&self) -> Transaction<S>
Available on crate feature serializable only.
pub fn begin_serializable(&self) -> Transaction<S>
serializable only.Begin a serializable transaction over the current state.
A serializable transaction tracks every key it reads and, at commit, validates that none of them changed since its snapshot — in addition to the write-write check every transaction gets. That read-set validation is what rejects write skew and the read-only anomaly that plain snapshot isolation permits, giving serializable behavior for the transactions that commit writes. A serializable transaction that writes nothing commits trivially, exactly like a read-only snapshot.
Available with the serializable feature. Snapshot isolation remains the
default and is unaffected.
§Examples
use txn_db::Db;
let db = Db::new();
// Seed two rows that an invariant ties together.
let mut tx = db.begin();
tx.put(b"on_call:alice".to_vec(), vec![1]);
tx.put(b"on_call:bob".to_vec(), vec![1]);
tx.commit()?;
// A serializable transaction validates the rows it read at commit.
let mut tx = db.begin_serializable();
let _alice = tx.get(b"on_call:alice")?;
let _bob = tx.get(b"on_call:bob")?;
tx.put(b"on_call:alice".to_vec(), vec![0]);
tx.commit()?;Sourcepub fn snapshot(&self) -> Snapshot<S>
pub fn snapshot(&self) -> Snapshot<S>
Take a read-only snapshot of the current state of the database.
The returned Snapshot reads as of this instant and never changes,
even as other transactions commit. Use it to read several keys at one
consistent point in time without the overhead of a transaction.
§Examples
use txn_db::Db;
let db = Db::new();
let snap = db.snapshot();
assert_eq!(snap.get(b"k")?, None);Sourcepub fn last_committed(&self) -> Timestamp
pub fn last_committed(&self) -> Timestamp
The timestamp of the most recent commit visible to a new transaction.
Returns Timestamp::ZERO for a database that has never been written.
This is the read watermark: the timestamp a transaction beginning now
would read at.
§Examples
use txn_db::Db;
let db = Db::new();
assert_eq!(db.last_committed(), txn_db::Timestamp::ZERO);
let mut tx = db.begin();
tx.put(b"k".to_vec(), b"v".to_vec());
let ts = tx.commit()?;
assert_eq!(db.last_committed(), ts);Sourcepub fn collect_garbage(&self) -> usize
pub fn collect_garbage(&self) -> usize
Reclaim versions that no live transaction or snapshot can observe, returning how many were removed.
txn-db keeps every version of a key so that an in-flight reader sees a
stable snapshot. Once no live reader can observe an old version — because
every active transaction and snapshot reads at a timestamp newer than a
later version of that key — the old one is unreachable and this reclaims
it. A key deleted before the oldest live reader’s snapshot is dropped
entirely.
Call it periodically, or after retiring long-running snapshots, to bound
memory. It is safe to call at any time and from any thread: a version a
live reader can still reach is never reclaimed. With the default
in-memory store this prunes the version chains; a custom
VersionStore that keeps no history can leave the
default no-op in place.
§Examples
use txn_db::Db;
let db = Db::new();
// Overwrite the same key several times.
for v in 0..5u8 {
let mut tx = db.begin();
tx.put(b"k".to_vec(), vec![v]);
tx.commit()?;
}
// No snapshot is held, so only the newest version need be kept.
let reclaimed = db.collect_garbage();
assert!(reclaimed > 0);
assert_eq!(db.begin().get(b"k")?.as_deref(), Some(&[4u8][..]));