wal文件必须采用固定字段+变长payload的紧凑二进制格式,支持追加写、crc32校验、跨平台字节序与内存对齐处理,并通过o_dsync或file_flag_write_through确保落盘,崩溃恢复需顺序扫描校验有效record,轮转须原子切换。

WAL 文件格式必须支持追加写和校验,不能用普通文本
直接 fwrite 结构体二进制流是常见错误——字节序、对齐、编译器 padding 都会导致跨平台或升级后解析失败。WAL 不是给人读的,但必须能被自己精确还原。
推荐固定字段 + 变长 payload 的紧凑二进制格式:
struct WALRecord {
uint32_t magic = 0x57414C01; // "WAL\1"
uint32_t version = 1;
uint32_t record_type; // PUT=1, DEL=2
uint64_t seq_num;
uint32_t key_size;
uint32_t value_size;
// 后续紧跟 key_size 字节 key + value_size 字节 value
uint32_t crc32; // 校验从 magic 到 value 末尾
};
- 所有整数字段统一用
uint32_t/uint64_t,避免int平台差异 -
crc32必须覆盖整个 record(含 header),写入前计算,读取后验证,不匹配则丢弃该 record - 禁止在 record 中嵌指针或 STL 容器;
key和value均以 raw bytes 存储
fsync 和 O_DSYNC 是 WAL 生效的关键开关,不是可选项
只调用 write() 或 ofstream::write() 后不刷盘,进程崩溃时数据大概率丢失——内核页缓存还没落盘。WAL 的语义前提是“写入即持久”,否则恢复无依据。
- Linux 下打开文件必须带
O_WRONLY | O_CREAT | O_APPEND | O_DSYNC(O_DSYNC比fsync()轻量,只保 data+metadata,够用) - Windows 对应用
FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING,且 buffer 必须按磁盘扇区对齐(通常 512B) - 每次
write()后不要立即fsync()——太慢;而是累积小批量 record 后fsync()一次,但需保证seq_num严格递增且不跳号
崩溃恢复时必须从最后一个完整 record 开始重放,不能依赖文件长度
WAL 文件末尾常有半截 record(比如写完 header 就 crash),直接 lseek() 到末尾反向扫描会误判。正确做法是顺序扫描 + 校验,找到最后一个 crc32 正确的 record。
- 恢复函数要忽略所有
crc32错误、magic不匹配、record_type未知的 record - 记录已成功应用的最大
seq_num,后续 replay 只处理seq_num > last_applied的 record - 如果 WAL 文件本身损坏(如开头 magic 全 0),应停止恢复并报错
"corrupted WAL file",不能静默跳过
WAL 文件轮转(rotation)必须原子切换,避免恢复时读到不完整日志
单个 WAL 文件无限增长不可行,但 rename 旧文件 + 创建新文件的过程若被 crash 中断,可能留下两个半有效文件。必须确保“旧文件关闭”和“新文件创建+写 header”之间无间隙。
- 轮转前先
fsync()当前文件,再close(),然后open()新文件并立即写入一个 valid header record(type=0, size=0),再fsync() - 新文件名建议含时间戳或单调递增编号(如
wal_000001.bin),避免命名冲突 - 删除旧 WAL 文件只能在确认对应数据已刷入主存储(如 SSTable)之后,且要用
unlink()而非remove()(后者在 Windows 上可能失败)
WAL 的难点不在写,而在“写对”和“读对”——每一个字节的解释都必须和写入时完全一致,任何松动都会让故障恢复变成随机行为。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











