题目重点:Tech screen 要 get/put、cache 失效重启从持久存储恢复;拆解提交顺序、旧版本清理、少 key 大 value 和崩溃窗口。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练版单进程、单机持久卷,key/value 是字节串;get 返回最近一次已确认 put 的 value 或不存在。没有 delete 与 TTL,不能把 stale data 擅自解释成过期时间。
- 已确认 put 要在进程崩溃并重启后可恢复;若还要求机器掉电耐久,必须给 fsync/rename 与硬件假设。以下先按 WAL 记录完成刷盘后才确认写入。
- 大 value 可用独立 blob 文件,内存 cache 设容量并允许不预加载 value;重启时只恢复索引元数据,不保证所有 value 都留内存。
解题路线
- 最小版 append-only WAL:每次 put 写 (长度,key,version,value,checksum) 完整记录并刷盘,成功后更新内存索引/缓存,再给调用方 ack。重启顺序扫描校验记录,只应用完整且 checksum 正确的版本;尾部残缺记录忽略或隔离。
- 如果 put 先改内存并提前 ack,随后文件写失败/崩溃就会丢已确认数据;若文件已提交但进程在更新内存前崩溃,重启重放仍可恢复。并发 put 同 key 用每 key 或全局顺序号串行化,否则 WAL 顺序和内存最新值可能背离。
- append-only 反复覆盖会留下 stale 旧版本;定期写紧凑快照,仅保留每 key 最新版本,再原子切换 manifest,旧日志在新快照可恢复后回收。需衡量写放大、恢复时间、空间与 compact 阻塞。
- K 少、V 巨大时把 value 放独立 blob/segment,内存只缓存小索引 (key→文件位置、长度、version、checksum) 与热 value;get miss 依据索引读文件。题目提一 key 一个文件和时间戳,但单纯‘读最后一行’不能处理二进制/多行 value,时间戳也不能保证并发写次序。
- 测试 put→重启→get、同 key 连写→重启、记录写一半崩溃、checksum 错、compact 中崩溃、大 value 不入内存、同时 put 同 key;恢复后与独立最近成功写模型对照。
手动走一遍
put(k,A) 的 WAL 版本 1 刷盘后 ack,再 put(k,B) 版本 2 刷盘后 ack,进程随即崩溃。重启扫描两条完整记录,索引最终指向 B。若第二条只写了 header 和半个 value,尚未 ack,恢复忽略残尾并得到 A。compact 完成后快照只保留 k→B,旧版本 A 才可安全回收。
规模与取舍
长度为 |k|+|v| 的 put 至少 O(|k|+|v|) 写 I/O,get 命中内存平均 O(1) 查找另加返回 value 的 O(|v|) 拷贝;磁盘 miss 还要随机/顺序读 value。WAL 恢复 O(日志字节数),快照恢复 O(当前键值总量或索引规模),compact O(存活数据+旧日志) I/O。
易错点与追问
容易失分的边界
- 把写内存完成当成 durable ack,却尚未把文件刷到要求的持久层。
- 只用 wall-clock timestamp 判最新版本,遇并发、时钟回拨或同毫秒写入会出错。
- 无限 append 而没有 checkpoint/compaction,重启越来越慢且旧值占满磁盘。
- 每 key 文件的最后一行当 value,无法表示多行或二进制大 value。
- 启动时把所有大 value 读进内存,与 cache 容量目标冲突。
继续深挖
- 故障模型扩展到整机断电或磁盘损坏时需要什么保证?
- 多个写进程共享存储时如何达成单 key 顺序?
- 如何以 copy-on-write manifest 保证 compact 中途崩溃仍可恢复?
- value 比内存大时,get 接口是否改为流式读取?
