Background Bitcask writer for deferred persistence of small values.
Each shard has one BitcaskWriter process. When the StateMachine applies a
write for a small value (< hot_cache_max_value_size), it inserts the value
into ETS immediately (for instant read availability) and sends a cast to
this process with the key, value, and the active file path at the time of
the write. The BitcaskWriter accumulates writes and flushes them to Bitcask
in batches, then updates each ETS entry's file_id and offset via
:ets.update_element/3.
This decouples the ~50us synchronous NIF write from the ra_server process,
allowing it to process the next Raft command immediately. The ETS entry is
tagged with file_id = :pending until the background write completes.
Batching strategy
Writes are flushed when either:
- The pending batch reaches 100 entries, OR
- 1ms has elapsed since the first pending entry was queued
File rotation handling
The active file path is passed with each write cast, so the writer automatically handles file rotations -- writes to different paths are grouped and flushed separately.
Invariants
- A key with
file_id = :pendingin ETS has its value in ETS (non-nil). MemoryGuard must NOT evict these entries. - After the Bitcask write completes, the writer updates ETS positions
5 (file_id), 6 (offset), and 7 (value_size) via
update_element. - Large values (>= hot_cache_max_value_size) are NOT routed here -- they use the synchronous path in StateMachine because their ETS value is nil and cold reads need a valid disk offset immediately.
Summary
Functions
Returns a specification to start this module under a supervisor.
Queues a deferred Bitcask tombstone (delete) for background processing.
Synchronously flushes all pending writes to Bitcask and returns.
Flushes all running BitcaskWriter processes.
Starts a BitcaskWriter for the given shard index.
Queues a deferred Bitcask write for background processing.
Queues a batch of deferred Bitcask writes in a single cast.
Returns the registered name for a shard's BitcaskWriter.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec delete(non_neg_integer(), binary(), binary()) :: :ok
Queues a deferred Bitcask tombstone (delete) for background processing.
Called from Router.async_delete/2 when raft is disabled. The tombstone
is written in the same ordered batch as value writes, so a tombstone
for key K will always appear after any preceding value write for K.
The ETS entry has already been deleted by the caller before this cast.
Synchronously flushes all pending writes to Bitcask and returns.
Used in tests and before shard shutdown to ensure all deferred writes are persisted.
@spec flush_all(non_neg_integer()) :: :ok
Flushes all running BitcaskWriter processes.
Iterates through shard indices 0..N-1 (default N=4) and flushes each writer that is alive. Silently ignores writers that are not running. Used in tests that need all background writes to be on disk before simulating eviction or verifying disk state.
@spec start_link(keyword()) :: GenServer.on_start()
Starts a BitcaskWriter for the given shard index.
@spec write( non_neg_integer(), binary(), non_neg_integer(), atom(), binary(), binary(), non_neg_integer() ) :: :ok
Queues a deferred Bitcask write for background processing.
Called from StateMachine.apply/3 for small values. The write is
non-blocking (cast) so the ra_server process is not delayed.
Parameters
shard_index-- zero-based shard indexactive_file_path-- the active log file path at the time of the writeactive_file_id-- the numeric file ID for the active log fileets_table-- the ETS table name (keydir) to update after writingkey-- the key being writtenvalue-- the value to persist (always a binary, always < 64KB)expire_at_ms-- expiry timestamp in milliseconds (0 = no expiry)
@spec write_batch(non_neg_integer(), [ {binary(), non_neg_integer(), atom(), binary(), binary(), non_neg_integer()} ]) :: :ok
Queues a batch of deferred Bitcask writes in a single cast.
Same semantics as calling write/7 for each entry, but sends one
GenServer cast instead of N. Each entry is a tuple:
{active_file_path, active_file_id, ets_table, key, value, expire_at_ms}.
@spec writer_name(non_neg_integer()) :: atom()
Returns the registered name for a shard's BitcaskWriter.