AOF: File.open("ab") + log_to_aof kwarg
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 binary file. $aof_mu serialises writes across threads. log_to_aof: false during replay so we do not re-append.
$aof = File.open(AOF_PATH, 'ab')
$aof_mu = Mutex.new
def aof_append(args)
return if $aof.nil?
$aof_mu.synchronize { $aof.write(encode_array_strings(args)); $aof.flush }
end
# inside handle_args:
aof_append(args) if log_to_aof && %w[SET DEL].include?(cmd) && !reply.start_with?('-')$aof_mu protects encode + write. Without it two threads could interleave bytes mid-frame.
log_to_aof: false is a kwarg, defaulting to true. Calling handle_args(args, log_to_aof: false) inside the replay function reuses every dispatch case without re-appending to the file.
def aof_replay
return [0, 0] unless File.exist?(AOF_PATH)
buf = File.binread(AOF_PATH)
cur = 0
replayed = 0
while cur < buf.bytesize
v, nc = parse_resp(buf, cur)
return [replayed, buf.bytesize - cur] if v == :incomplete || v == :invalid
cur = nc
handle_args(args_from(v), log_to_aof: false)
replayed += 1
end
[replayed, 0]
endReplay reads the file, parses RESP frames, calls handle_args with log_to_aof: false.
$aof.flush pushes to the OS page cache. For full durability, add $aof.fsync after flush. For appendfsync everysec semantics, spawn a Thread that fsyncs once per second. The exercises walk through both.
Quiz: Quiz
Loading practice…