TiCI 的设计考量

主要介绍 TiCI 的一些实现。

TiCI 要解决什么问题?

TiFlash 中已经实现了 Vector Index 和 Inverted Index 了,在一些版本中也支持 FTS 了。但是在 TiFlash 中使用这些索引存在一些问题:

  • TiFlash 中的 Region 的分布随 TiKV 而定,这导致优化空间较小
    • Region 的迁入和迁出会导致重建 Inverted Index 字典和 HNSW 图,开销大。
  • TiFlash 的并发较低,但本质上,并发低的原因还是和索引有关
  • TiFlash 默认提供了强一致和 SI 的隔离级别,但是这些特性并不是全部必须的,而它们又带来很高的成本,如 MVCC、Learner Read 等

写入路径

涉及到的问题

  • 确定 Compaction 的策略
  • Compaction vs Split
  • S3 的 SlowDown

数据源

Push or Pull

TiCI 设计主要以 TiCDC 作为数据源。所以一开始是每个 Shard 会不停 Poll S3 上的某个 prefix,复杂度是 O(shard_count*log_count)。为了减小 LIST 的读放大,我们对 CDC 的日志进行了编号,这样只需要不停 GET 就行。

延迟

根据 TiCDC 的原理,基于 TiCDC 为数据源,很难将延迟降低到秒以内。

MetaService

Outline

Shard

在 shard 的大小默认设置为 4 GiB,这样对于 80 TiB 的集群规模,会有 20k+ 个 shard。一个 shard 中有多个 fragment,fragment 之间是可以 overlap 的,我们也按照传统方法引入了 leveled compaction。

过多的 shard/fragment 意味更小的 shard/fragment 的 size,这对于查询是不利的:

  • 过多的 fragment,会导致每个 fragment 中的词典会很小,压缩率不高
  • 此外,一个查询如果覆盖多个 fragment,就需要按照每个 fragment 的词典都过滤一遍,再 merge,这样并发上不去

Split & Compaction

Split 和 Compaction 存在冲突。

tantivy

保序

如果要支持逆向?

Tantivy 的倒排表文件是顺序压缩存储的,例如:

  • doc_id 列表经过 delta-encoded
    如果要支持逆向,则需要保存两个方向的差值,十分麻烦
  • 然后通过 bit-packed 或 block-wise 压缩
  • 压缩的 Block 按顺序写入文件
    如果要支持逆向,则需要能定位到前一个 Block 的 offset。
  • 查找时通过 BlockReader 顺序解码

这么做的目的是因为顺序访问能够提高吞吐量。而对于随机访问,tantivy 主要是通过如下方式进行快速的 seek:

  • skiplist

对于搜索引擎而言,存储空间通常比随机访问代价更便宜,尤其是 posting list 占比主要来自长尾词,常见高频词列表短,存两份并不会显著膨胀。

为什么常见高频词反而消耗 posting list 的物理空间更小?

  • 因为足够高频,所以 delta-encode + bitpack 的优化效果非常好
  • 高频词可能是 Stopword,不会为它生成倒排表
  • 基于布尔逻辑,或者范围过滤的短路逻辑,常常能够跳过这些高频词的 posting list 的读取