来自 #292 的架构级评审建议。不阻塞合入,仅供参考是否有更好的架构解法。
💡 [建议 · 性能] 扫描的块索引切换未复用餐复路径 storage/engine.go:156
问题根因:newSSTableIteratorFrom 用 ss.getBlockIndex(path) 取块索引并 f.Seek(blockOffset) 从目标块开始读,走的是全新打开的文件句柄。而既有读路径已有 fdCache(常驻只读复用手柄)与 blockCache(数据块缓存,热数据点查不触盘)。ScanRange 每次打开新文件,逐块 ReadFull 一行行读,既不复用句柄也不命中块缓存——对一个以按游标反复全量扫描为核心场景(投递)的系统,这等于放弃了既有的缓存设施,让扫描每条都触盘。
为什么低级解法不够:在单条磁盘读上做小的缓冲优化只是把成本从这一行挪到下一行,没有解构扫描与点查共享同一套缓存的问题。
架构级方案:让扫描复用与点查一致的读路径设施:通过 fdCache 获取复用手柄(顺便复用之),并考虑让范围迭代走 blockCache 命中的整块读取——扫描的热数据块与点查的热数据块共享缓存预算,LRU 自然让重复扫描游标区域少触盘。更进一步的架构机会是:既然投递按游标推进、每批以新游标重扫,整个扫描其实可以下沉为对每个 SSTable 的「从上次水位继续」的增量迭代,而不是每次 open+parse 一遍——这属于将「投递游标」提升为一等公民的存储级抽象。
代价/收益:代价:fdCache/blockCache 复用时需注意句柄共享的并发语义(ReadAt 无偏移依赖,天然可并发共享,与点查一致),改动不大。收益:扫描这一热路径(投递场景是持续性后台负载)从每行触盘变为缓存命中,吞吐显著提升,且与点查共享缓存预算、整体内存占用更低。
💡 [建议 · 性能] 扫描的块索引切换未复用餐复路径
storage/engine.go:156问题根因:
newSSTableIteratorFrom用ss.getBlockIndex(path)取块索引并f.Seek(blockOffset)从目标块开始读,走的是全新打开的文件句柄。而既有读路径已有fdCache(常驻只读复用手柄)与blockCache(数据块缓存,热数据点查不触盘)。ScanRange 每次打开新文件,逐块 ReadFull 一行行读,既不复用句柄也不命中块缓存——对一个以按游标反复全量扫描为核心场景(投递)的系统,这等于放弃了既有的缓存设施,让扫描每条都触盘。为什么低级解法不够:在单条磁盘读上做小的缓冲优化只是把成本从这一行挪到下一行,没有解构扫描与点查共享同一套缓存的问题。
架构级方案:让扫描复用与点查一致的读路径设施:通过
fdCache获取复用手柄(顺便复用之),并考虑让范围迭代走blockCache命中的整块读取——扫描的热数据块与点查的热数据块共享缓存预算,LRU 自然让重复扫描游标区域少触盘。更进一步的架构机会是:既然投递按游标推进、每批以新游标重扫,整个扫描其实可以下沉为对每个 SSTable 的「从上次水位继续」的增量迭代,而不是每次 open+parse 一遍——这属于将「投递游标」提升为一等公民的存储级抽象。代价/收益:代价:fdCache/blockCache 复用时需注意句柄共享的并发语义(ReadAt 无偏移依赖,天然可并发共享,与点查一致),改动不大。收益:扫描这一热路径(投递场景是持续性后台负载)从每行触盘变为缓存命中,吞吐显著提升,且与点查共享缓存预算、整体内存占用更低。