Docs
  • 0.0.2latest

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(())
}
  • put writes a value; a later put of the same key replaces it.
  • delete removes 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. start must be below end.
  • 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.

DurabilityThe write returns whenSurvives
Sync (default)its log record is on stable storagepower loss
Bufferedits log record is handed to the operating systema process crash; may be lost with the machine until a later sync
NoWalit is in the in-memory table; nothing is loggednothing 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::Absolute refuses to open when any frame is damaged and leaves the files untouched for an operator.