来自 #292 的架构级评审建议。不阻塞合入,仅供参考是否有更好的架构解法。
💡 [建议 · 存储] 扫描块索引二分依赖 lastKey 是否含墓碑 storage/iterator.go:39
问题根因:newSSTableIteratorFrom 的块索引二分逻辑:找到「first lastKey >= start」的块,从其 BlockOffset 起读。但若目标块恰为墓碑所在块且 lastKey 取自块内最后一条的 key,这在 start 恰好落在该块中间(在 lastKey 与已定位 key 之间且该 key 该块中靠前)时仍正确——因为它是「第一个 lastKey>=start」的块,start 必然落在此块。此处逻辑其实自洽。真正的隐患在别处:块索引的 LastKey 是否与迭代器实际产出的 key 语义完全一致(含墓碑 key 的入索引方式)未在 diff 中封闭验证,若某个块仅含墓碑、lastKey 被落下/未被索引,则二分定位可能漏掉该块中实际存在的 key。
为什么低级解法不够:在二分定位外再补一层范围裁剪的兜底能掩盖不一致,但没有确立「块索引 lastKey 与迭代器产出 key 的互斥契约」,同类错位会在配置变化后复发。
架构级方案:在扫描实现里建立并验证不变量:块索引二分定位后,仍交由 rangeIterator 做 [start,end) 裁剪兜底(diff 已这么做),同时补一条跨越「仅墓碑块」「start 落在块中间」「start 正好等于块 lastKey」的多用例守护,锁定「二分定位 + range 裁剪」组合对任意 start 的正确性——即无论块索引是否完整,正确性由 range 裁剪保证、性能才依赖索引。这个分级保证(correctness by range-clip, perf by index)应在注释和测试中明示。
代价/收益:代价:属加固性工作,需补边界多用例;收益:把「索引优化引入扫描也可能引入正确性回归」的风险锁定为被测试屏障覆盖的既定契约,长期可信。
💡 [建议 · 存储] 扫描块索引二分依赖 lastKey 是否含墓碑
storage/iterator.go:39问题根因:
newSSTableIteratorFrom的块索引二分逻辑:找到「first lastKey >= start」的块,从其 BlockOffset 起读。但若目标块恰为墓碑所在块且 lastKey 取自块内最后一条的 key,这在start恰好落在该块中间(在 lastKey 与已定位 key 之间且该 key 该块中靠前)时仍正确——因为它是「第一个 lastKey>=start」的块,start 必然落在此块。此处逻辑其实自洽。真正的隐患在别处:块索引的LastKey是否与迭代器实际产出的 key 语义完全一致(含墓碑 key 的入索引方式)未在 diff 中封闭验证,若某个块仅含墓碑、lastKey 被落下/未被索引,则二分定位可能漏掉该块中实际存在的 key。为什么低级解法不够:在二分定位外再补一层范围裁剪的兜底能掩盖不一致,但没有确立「块索引 lastKey 与迭代器产出 key 的互斥契约」,同类错位会在配置变化后复发。
架构级方案:在扫描实现里建立并验证不变量:块索引二分定位后,仍交由 rangeIterator 做 [start,end) 裁剪兜底(diff 已这么做),同时补一条跨越「仅墓碑块」「start 落在块中间」「start 正好等于块 lastKey」的多用例守护,锁定「二分定位 + range 裁剪」组合对任意 start 的正确性——即无论块索引是否完整,正确性由 range 裁剪保证、性能才依赖索引。这个分级保证(correctness by range-clip, perf by index)应在注释和测试中明示。
代价/收益:代价:属加固性工作,需补边界多用例;收益:把「索引优化引入扫描也可能引入正确性回归」的风险锁定为被测试屏障覆盖的既定契约,长期可信。