Writes and durability
Atomic batches of puts, deletes and range deletes; the three durability levels a write can choose; syncing on demand; and what opening does with a damaged log.
Every write is a WriteBatch: an ordered list of puts, deletes and range
deletes, applied atomically. Either every entry is visible to later reads, or
none is — across a crash too.
Building a batch
use tessarikv::{Db, KeyspaceId, WriteBatch, WriteOptions};
async fn move_stock(db: &Db) -> Result<(), Box<dyn std::error::Error>> {
let mut batch = WriteBatch::new();
batch.put(KeyspaceId::DEFAULT, b"sku:1001", b"11 in stock")?;
batch.delete(KeyspaceId::DEFAULT, b"reservation:77")?;
// Removes every key from "cart:42:" up to, not including, "cart:42;".
batch.delete_range(KeyspaceId::DEFAULT, b"cart:42:", b"cart:42;")?;
db.write(&batch, &WriteOptions::default()).await?;
Ok(())
}
putwrites a value; a later put of the same key replaces it.deleteremoves one key.delete_range(start, end)removes every key in[start, end). Older versions become invisible, and a key written after the range delete is visible again.startmust be belowend.- A batch may name several keyspaces and stays atomic across them.
Limits: a key up to 8 MiB, a value up to 1 GiB, an encoded batch up to 2 GiB.
Past them the call is refused with a TooLarge error and the batch is left
unchanged. Batch methods return the table format's error type, which
tessarikv::Error does not convert from; return a boxed error where you build
batches. Transaction writes return tessarikv::Error.
Choosing durability
WriteOptions::durability says what a successful write promises to survive.
Durability | The write returns when | Survives |
|---|---|---|
Sync (default) | its log record is on stable storage | power loss |
Buffered | its log record is handed to the operating system | a process crash; may be lost with the machine until a later sync |
NoWal | it is in the in-memory table; nothing is logged | nothing until its table is flushed; for loads you can redo |
use tessarikv::{Durability, WriteOptions};
fn buffered() -> WriteOptions {
let mut options = WriteOptions::default();
options.durability = Durability::Buffered;
options
}
Concurrent Sync writes share log syncs: writers that arrive together are
covered by one sync rather than one each.
Syncing on demand
Db::sync_wal makes every batch written before the call durable, Buffered
ones included, with one log sync. A common pattern writes Buffered and calls
sync_wal at a checkpoint of your own, such as the end of a request batch.
Db::flush goes further: it writes every batch committed before the call into
table files and returns once they are there.
When writes slow down
Writes slow down, then stop, when flushes and compactions fall behind (see
Compaction and backpressure). By
default a write waits. With WriteOptions::no_slowdown set, it fails with
Error::WriteStalled instead, in the Unavailable category, so the caller can
shed load. Error::is_retryable is true for it.
When the background stops
If a log write, a log sync, a flush or a compaction fails, the database stops
accepting writes. Db::background_error returns the failure. Reads go on
working. Writes are refused until the database is reopened.
Recovery after a crash
Opening replays the log above the last flush, flushes what it replayed and
removes that log. A crash can leave the last log frame half written.
DbOptions::wal_recovery decides what opening does then:
WalRecovery::PointInTime(default) recovers every batch up to the first damaged frame, discards the rest and opens. Every write acknowledged at a durability that survives that crash is in the database.WalRecovery::Absoluterefuses to open when any frame is damaged and leaves the files untouched for an operator.