AOF with the send-based replay flag

Before the code: what crash-safety actually means

Your store is fast because it lives in memory. The price of that speed is that a single power cut wipes everything. This module fixes that. The trick is simple: keep a flight recorder. Every write the server accepts is appended to a file on disk. After a crash, you replay the file from the top and the server is back. That is the entire append-only log idea, used inside Redis, every SQL database, Kafka, and even git.

Write to memory, log to disk

Every write goes two places at once: the in-memory map for fast reads, and the log file so the map can be rebuilt after a crash.

Quiz: Quiz

Loading practice…

Append-only file in the GenServer state. During replay we use send/2 to ourselves with {:replay_set, k, v} messages, which handle_info applies without appending.

05-aof-persistence/server.exs
elixir
def handle_call({:set, key, value}, _from, s) do
  s = put_in(s, [:store, key], value)
  append_aof(s.aof, ["SET", key, value])
  {:reply, :ok, s}
end

defp append_aof(file, args), do: IO.binwrite(file, Resp.encode_array_strings(args))

def handle_info({:replay_set, k, v}, s), do: {:noreply, put_in(s, [:store, k], v)}

append_aof writes the RESP frame inside the GenServer call. handle_info({:replay_set, ...}) updates state without the AOF append.

This is the Elixir translation of Rust's Option<&AofFile> or Ruby's log_to_aof: false. We use the type of message (call for serving, info for replay) as the discriminator. More idiomatic than a runtime flag.

IO.binwrite flushes to the OS page cache. For full durability call :file.datasync(file) after each write. For everysec semantics, schedule a :fsync message with :timer.send_interval. Exercise 1 walks through this.

Quiz: Quiz

Loading practice…