Skip to main content

WalStore

Trait WalStore 

Source
pub trait WalStore: Send + Sync {
    // Required methods
    fn write_at(&self, offset: u64, bytes: &[u8]) -> Result<()>;
    fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize>;
    fn truncate(&self, len: u64) -> Result<()>;
    fn sync(&self) -> Result<()>;
    fn len(&self) -> Result<u64>;

    // Provided methods
    fn is_empty(&self) -> Result<bool> { ... }
    fn head(&self) -> Result<u64> { ... }
    fn truncate_before(&self, _offset: u64) -> Result<u64> { ... }
}
Expand description

A byte-addressable, append-only store with an explicit durability barrier.

The log treats a store as a growing array of bytes. It writes framed records at reserved offsets — possibly from several threads concurrently and out of order — reads from arbitrary offsets during recovery, and occasionally truncates a torn tail. The one guarantee the log cannot provide itself — that written bytes have reached stable storage — is delegated to sync.

§Implementing a backend

The contract an implementation must honour:

  • write_at writes bytes at offset, growing the store if offset is past the current end and zero-filling any gap (so a later offset written before an earlier one leaves detectable zero bytes in between, exactly as a sparse file does). Concurrent calls to disjoint ranges must not corrupt each other.
  • read_at fills buf starting at offset, returning the number of bytes read. It returns fewer than buf.len() only when the store ends first — that short read is how recovery detects a torn tail.
  • sync returns only once every prior write is durable.
  • truncate discards everything at or after len.

Send + Sync is required so the log can be shared across threads.

§Examples

use wal_db::{MemStore, Wal};

let wal = Wal::with_store(MemStore::new())?;
wal.append(b"record")?;
wal.sync()?;

Required Methods§

Source

fn write_at(&self, offset: u64, bytes: &[u8]) -> Result<()>

Write bytes at byte offset, growing the store and zero-filling any gap if offset is beyond the current end.

Source

fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize>

Read into buf starting at byte offset, returning the number of bytes read.

A return value smaller than buf.len() means the store ended before buf could be filled.

Source

fn truncate(&self, len: u64) -> Result<()>

Discard every byte at or after len, shrinking the store to exactly len bytes.

Source

fn sync(&self) -> Result<()>

Flush every preceding write_at to stable storage.

Returns only once the data will survive a power loss. This is the durability barrier the whole log rests on.

Source

fn len(&self) -> Result<u64>

The current size of the store in bytes.

Provided Methods§

Source

fn is_empty(&self) -> Result<bool>

Whether the store holds no bytes.

The default defers to len; override it only if a backend can answer more cheaply.

Source

fn head(&self) -> Result<u64>

The lowest offset that still holds data.

Normally 0. A backend that can drop a prefix (see truncate_before) reports the offset of its first surviving byte here, so recovery knows where to begin scanning. The default backends that cannot drop a prefix leave this at 0.

Source

fn truncate_before(&self, _offset: u64) -> Result<u64>

Discard storage entirely below offset, if the backend can, returning the new head.

Offsets are preserved: dropping a prefix never renumbers what remains, so a record keeps its byte position (its LSN) for life. A backend that cannot remove a prefix — a single file, where the surviving bytes would have to move — leaves the store unchanged and returns its current head. One that can (a segmented store, by deleting whole leading segment files) removes what it can at its own granularity and returns the resulting head, which may be below offset.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§