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
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.
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…