feat(storage): 扫描覆盖 SSTable——修复下游投递静默漏投已落盘数据 - #292
Conversation
ScanRange 此前只遍历 active 与 dirty 两张内存表,已 flush 的数据对扫描完全不可见。 实测:写入 200 条并等 flush 落定后,逐个 Get 命中 200 条,而 ScanRange 只看到 0 条—— 两条读路径的可见范围不一致,问题因此很难察觉。 后果不止 SCAN 命令少返回结果。下游投递(delivery.KVSource)正是按游标反复调用 Scan 取数, 而 flush 阈值上万条、投递每秒百条,flush 必然快于投递——落盘的记录就再也不会被投递。 「摄入缓冲 + 可靠投递」是本项目的核心能力,这里却在静默丢数据。 实现为多路归并,复用 compaction 已有的归并迭代器:抽出 entryIterator 接口,让内存表快照 与 SSTable 文件迭代器混合参与;源序按新旧排列(SSTable 由旧到新、其后 dirty、最后 active),故同 key 只保留最新版本,最新版本是墓碑时整条跳过。 内存表在锁内按范围拷出快照、锁外做归并:归并要读磁盘,全程持锁会让写入停等 I/O,而无锁 遍历跳表又会与并发写相争(Get 曾因此出错)。拷贝量由内存表大小天然有界。 按游标扫描必须能跳过前缀,否则投递的总代价随数据量平方增长——首版实现顺序跳过小于 start 的条目,实测 2000 条投递 6 秒仅完成 810 条。改为借块索引二分定位到目标块起点, 无块索引(老格式或尾部残缺)时退回从头读,正确性由范围裁剪保证。 验证:新增存储层与 service 层回归用例(含覆盖写与墓碑跨 SSTable 的新旧判定),并已变异 验证——退回只扫内存表后用例立即失败。端到端实测:MaxMemTableSize=50 写入 2000 条产生 56 个 SSTable,投递收敛到 2000/2000、零错误。读吞吐用交替 A/B 核对无回归。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 10 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🐯 BanGD 数据库内核评审整体风险:🟡 中 变更总结:此 PR 修复了 LSM 存储引擎的「扫描覆盖缺口」:此前
架构问题(共 3 项)
普通问题(共 2 项)
💡 [建议 · 兼容]
本次评审消耗 token:共 183677 tokens(输入 164707,输出 5786,缓存命中 13184,缓存写入 0)|维度 [concurrency, memory, lock, storage, performance]|补充阅读周边文件 [storage/skiplist.go, storage/sstable.go, storage/sstable_meta.go]|对抗式复核 3 票/条,过滤疑似误报 1 条 |
问「功能还能不能优化」,查下来第一件事就不是缺功能,是核心功能在静默丢数据。
实测证据
ScanRange只遍历active与dirty两张内存表,已 flush 的数据对扫描完全不可见。两条读路径的可见范围不一致,所以很难察觉。后果不止 SCAN 少返回结果
下游投递正是按游标反复调用
Scan取数:而 flush 阈值上万条、投递每秒百条 —— flush 必然快于投递,落盘的记录于是再也不会被投递。「摄入缓冲 + 可靠投递」是本项目 README 的头号能力,这里却在静默漏投。
实现
复用 compaction 已有的归并迭代器:抽出
entryIterator接口,让内存表快照与 SSTable 文件迭代器混合参与多路归并。源序按新旧排列(SSTable 由旧到新 →dirty→active),故同 key 只保留最新版本;最新版本是墓碑时整条跳过。锁的处理:内存表在锁内按范围拷出快照,锁外做归并。归并要读磁盘——全程持锁会让写入停等 I/O,而无锁遍历跳表又会与并发写相争(
Get曾因此出错)。拷贝量由内存表大小天然有界。一个必须解决的性能问题
按游标扫描必须能跳过前缀,否则投递的总代价随数据量平方增长。首版实现顺序跳过小于
start的条目,端到端实测:2000 条投递 6 秒只完成 810 条。改为借块索引二分定位到目标块起点(
blockIndex本来就在,读路径点查已在用它)。无块索引时(老格式或尾部残缺)退回从头读,正确性由范围裁剪保证。验证
MaxMemTableSize=50写入 2000 条、产生 56 个 SSTable,投递收敛到 2000/2000、零错误go build ./...(含-tags pprof)、go vet、gofmt、go test -race ./...连跑 2 次全绿。顺带修正的文档
KVServer.Scan的注释仍写着「扫描 MemTable 热数据」,已更正为覆盖内存表与 SSTable。🤖 Generated with Claude Code