Fact.WriteAheadLog (Fact v0.3.1)

View Source

A segmented, append-only write-ahead log for durability and crash recovery.

Fact.WriteAheadLog records every event write as a binary entry before it is committed to the ledger and indexes. On crash, the Fact.EventLedger replays uncommitted WAL entries to restore the database to a consistent state.

The WAL is organized as a series of numbered segment files. When the active segment exceeds :max_file_size, it is rotated and a new segment is opened. Old segments beyond :max_segments are deleted automatically.

Checkpoint entries can be written to mark a known-good recovery point. During recovery, replay begins from the most recent checkpoint rather than scanning the entire log.

A periodic sync timer flushes the write buffer to disk at the configured :sync_interval. When :enable_fsync is true, each sync calls fsync to guarantee durability.

This process is started and supervised by Fact.DatabaseSupervisor and is not intended to be started directly. Configuration options can be passed through Fact.open/2 via the :wal key.

Configuration

See wal_option/0 for available options and their defaults.

Summary

Types

Options accepted by start_link/1.

Write-ahead log configuration options.

Functions

Returns a specification to start this module under a supervisor.

Flushes the write buffer and closes the active segment file.

Writes a checkpoint entry to the write-ahead log.

Reads all entries from every segment in the write-ahead log.

Reads entries from segments at or after the given segment offset.

Repairs the most recent WAL segment by discarding corrupt trailing entries.

Starts a Fact.WriteAheadLog process linked to the calling process.

Flushes the write buffer to disk.

Appends a binary entry to the write-ahead log.

Types

option()

(since 0.3.0)
@type option() ::
  {:database_id, Fact.database_id()} | {:name, GenServer.name()} | wal_option()

Options accepted by start_link/1.

  • :database_id - (required) The database identifier used to scope the WAL directory and process registration.
  • :name - (required) The registered process name, typically constructed via Fact.Registry.via/2.
  • :enable_fsync - See wal_option/0.
  • :max_file_size - See wal_option/0.
  • :max_segments - See wal_option/0.
  • :sync_interval - See wal_option/0.

wal_option()

(since 0.3.0)
@type wal_option() ::
  {:enable_fsync, boolean()}
  | {:max_file_size, pos_integer()}
  | {:max_segments, pos_integer()}
  | {:sync_interval, pos_integer()}

Write-ahead log configuration options.

  • :enable_fsync - Whether to call fsync after writes. Defaults to true.
  • :max_file_size - Maximum size in bytes of a WAL segment file before rotation. Defaults to 16_777_216 (16 MB).
  • :max_segments - Maximum number of segment files to retain. Defaults to 4.
  • :sync_interval - Time in milliseconds between periodic sync operations. Defaults to 200. Values below 10 are clamped to 10 to prevent mailbox flooding.

Functions

child_spec(init_arg)

(since 0.3.0)

Returns a specification to start this module under a supervisor.

See Supervisor.

close(database_id)

(since 0.3.0)
@spec close(Fact.database_id()) :: :ok

Flushes the write buffer and closes the active segment file.

create_checkpoint(database_id, data)

(since 0.3.0)
@spec create_checkpoint(Fact.database_id(), binary()) :: :ok | {:error, term()}

Writes a checkpoint entry to the write-ahead log.

A checkpoint marks a known-good recovery point. During crash recovery, replay begins from the most recent checkpoint rather than the beginning of the log. The buffer is flushed to disk immediately after a checkpoint is written.

read_all(database_id, from_checkpoint? \\ false)

(since 0.3.0)

Reads all entries from every segment in the write-ahead log.

When from_checkpoint? is true, only entries written after the most recent checkpoint are returned. When false, all entries across all segments are returned in order.

read_all_from_offset(database_id, offset, from_checkpoint? \\ false)

(since 0.3.0)
@spec read_all_from_offset(Fact.database_id(), non_neg_integer(), boolean()) :: [
  Fact.WriteAheadLog.Entry.t()
]

Reads entries from segments at or after the given segment offset.

Behaves like read_all/2 but skips segments with an index lower than offset.

repair(database_id)

(since 0.3.0)
@spec repair(Fact.database_id()) :: {:ok, list()} | {:error, :no_segments}

Repairs the most recent WAL segment by discarding corrupt trailing entries.

Reads the newest segment file entry-by-entry. Valid entries (those that pass CRC verification) are kept; the first corrupt entry and everything after it are discarded. The repaired segment is written back to disk in place.

start_link(opts)

(since 0.3.0)
@spec start_link([option()]) :: GenServer.on_start()

Starts a Fact.WriteAheadLog process linked to the calling process.

Requires :database_id and :name in opts. Additional wal_option/0 keys are optional.

sync(database_id)

(since 0.3.0)
@spec sync(Fact.database_id()) :: :ok

Flushes the write buffer to disk.

write_entry(database_id, data)

(since 0.3.0)
@spec write_entry(Fact.database_id(), binary()) :: :ok | {:error, term()}

Appends a binary entry to the write-ahead log.

The entry is written to the active segment file. If the segment exceeds :max_file_size, a rotation occurs before the write.