LSM-tree 上的读路径,从出生就带着镣铐。因为 CoW 的使用,读一条记录实际上需要把这条记录所有的增量碎片都找到。因为横跨内存和磁盘两种介质和有层次化的存储,这些碎片可能藏在各种犄角旮旯里面。更惨的是,如果是读一个范围内的记录,俗称 range scan,因为 LSM-tree 的每一层的 key range 是交叠的,那么一个 range 内的数据就很有可能会落在所有的层次上,为了把他们都找到,我们就需要每层都去读,这个工作量也不小。
为了解决这个问题,目前的 LSM-tree 引擎把各种经典技术都用上了:各种索引、各种 cache。但是为了提高索引和 cache 的效率,让他们一直发挥比较好的作用,难度不小。以富贵论坛 cache 为例,X-Engine 中使用了两种经典的 cache,一种是 row cache,缓存记录级别的热数据,一种是 block cache,缓存数据块级别的热数据。Row cache 可以加速点查询,block cache 可以加速 range scan,一切看上去都是很完美的芭蕾舞。然而,当 compaction 被大王叫来巡山的时候,危险就发生了。因为 compaction 会重新组织数据块里面的内容,干掉一些老的 block,生成一些新的 block,传统的 cache 替换策略对老的 block 做的访问统计会失效,而新的 block 它不认识,没统计信息。此外,compaction 还会移动数据。这两点加起来,只要 compaction 巡了一次山,cache 里面缓存的记录就有很大可能出现大面积失效,导致原本可以命中 cache 的查询,不得不去访问磁盘,造成严重的延迟尖刺。