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 get(&self, key: &[u8]) -> Result<Option<Arc<[u8]>>>
pub fn get(&self, key: &[u8]) -> Result<Option<Arc<[u8]>>>
Read one key without opening a transaction.
A convenience for the common single-read case: it takes a snapshot of the
current state and reads key from it, returning the newest committed
value or None if the key is absent. For reading several keys at one
consistent instant, take a snapshot and reuse it.
§Errors
Returns TxnError::Store if the backing store
fails the read. The default in-memory store never fails.
§Examples
use txn_db::Db;
let db = Db::new();
db.put(b"k".to_vec(), b"v".to_vec())?;
assert_eq!(db.get(b"k")?.as_deref(), Some(&b"v"[..]));
assert_eq!(db.get(b"absent")?, None);Sourcepub fn put(
&self,
key: impl Into<Arc<[u8]>>,
value: impl Into<Arc<[u8]>>,
) -> Result<Timestamp>
pub fn put( &self, key: impl Into<Arc<[u8]>>, value: impl Into<Arc<[u8]>>, ) -> Result<Timestamp>
Write one key in its own transaction, retrying on conflict, and return the commit timestamp.
A convenience for the common single-write case: it begins a transaction,
buffers the write, and commits. If a concurrent transaction wins the
commit race it retries against a fresher snapshot, so this is
last-writer-wins and never surfaces a conflict — the value is always
installed. When you need to read-then-write atomically, or to control the
conflict outcome yourself, use begin instead.
§Errors
Returns TxnError::Store if the backing store
fails to apply the write, or
TxnError::Durability for a durable
database whose commit cannot be made durable. Conflicts are retried, not
returned.
§Examples
use txn_db::Db;
let db = Db::new();
let ts = db.put(b"k".to_vec(), b"v".to_vec())?;
assert!(ts > txn_db::Timestamp::ZERO);Sourcepub fn delete(&self, key: impl Into<Arc<[u8]>>) -> Result<Timestamp>
pub fn delete(&self, key: impl Into<Arc<[u8]>>) -> Result<Timestamp>
Delete one key in its own transaction, retrying on conflict, and return the commit timestamp.
The delete counterpart of put: last-writer-wins, conflicts
retried. After it returns the key reads as absent until written again.
§Errors
Returns TxnError::Store if the backing store
fails, or TxnError::Durability for a
durable database whose commit cannot be made durable. Conflicts are
retried, not returned.
§Examples
use txn_db::Db;
let db = Db::new();
db.put(b"k".to_vec(), b"v".to_vec())?;
db.delete(b"k".to_vec())?;
assert_eq!(db.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][..]));