Content Entry

ClickHouse 内部的全文搜索:快速、原生、列式架构

我们在 ClickHouse 中彻底重构了全文搜索功能,性能更高、更轻量,并且与列式数据库架构深度融合。

这篇文章由参与构建该功能的工程师撰写,将深入解析新版架构,从倒排索引到查询优化技巧——包括如何在查询中完全跳过读取文本列。如果你希望在数据库内部实现高性能搜索,或者对数据库底层机制充满兴趣,这篇文章值得一读。

从旧架构到极速体验:ClickHouse 全文搜索的演进之路

虽然全文搜索(Full-text search,简称 FTS)早已在 ClickHouse 中出现,但此前的版本存在性能和灵活性方面的限制。本次我们从零开始,全面重构全文搜索功能,使其运行更快、更节省空间,并且深度融入 ClickHouse 的列式存储架构。

点击这个链接(https://clickhouse.com/cloud/inverted-index-waitlist)即可申请新版全文搜索功能的私测资格。

这将是一场深入底层机制的探索,我们将介绍核心数据结构——倒排索引、有穷状态自动机(finite state transducers)、倒排列表(posting lists)——以及查询执行流程的重新设计方式,如何减少 I/O 操作并提升搜索速度。

如果你曾经遇到以下场景:

  • 想在 ClickHouse 内直接运行搜索类分析,
  • 希望省去维护独立搜索引擎的成本,
  • 想深入理解现代列式存储中的文本索引机制,

……这篇文章将为你带来完整的原理解析与实践方法,并结合真实示例展示可量化的性能收益。

我们从新版索引如何存储与组织文本说起,这是一切优化的基础。

全文搜索为何如此高效?

设想你在 ClickHouse 中存储产品评论、日志信息或用户留言。每行数据代表一段自然语言文本——即由人或机器撰写、符合自然语言语法的内容(而非随机 ID 或哈希值)。

由于 ClickHouse 是一款列式数据库,这些文档被依次存储在一个 String(https://clickhouse.com/docs/en/sql-reference/data-types/string/) 类型的列中,每行对应一个可供搜索的文档。

为了支持搜索,ClickHouse 会将每个文档拆分成一系列的 Token(词元),通常对应单词。例如,句子:

All cats like mice

将被分词为:

[All, cats, like, mice]

默认的分词器会按空格或标点将字符串切分成若干词元。更高级的分词器还能将日志消息拆解为多个字段,如时间戳、严重性等级和具体消息,甚至支持提取 n-gram,以实现模糊搜索。

全文搜索(Full-text search) 的基本原理是查找包含某个特定词元的文档。比如,为了查找所有提及 cat 的行,我们可能会执行如下查询:

(注意空格的重要性 —— 如果忽略空格,可能会误匹配如 “edu cat e” 或 “multipli cat ion” 这样的词。)

全文搜索主要有两种实现方式:

  • 全表扫描:遍历所有行(速度较慢)
  • 倒排索引:将每个词元映射到其所在的行(速度较快)

倒排索引可以让我们在毫秒级完成对海量数据的搜索,这正是 ClickHouse 现在原生支持的特性,具备良好的扩展性与性能表现。

在深入介绍其实现细节之前,我们先来看看“倒排索引(Inverted Index)”这个术语的含义。它是一种简单却可能让人感到直观上难以理解的结构:它颠覆了我们通常对文档和词项关系的认知——从“文档 → 词项”变成了“词项 → 文档”。

为什么称它为倒排索引?

当你按顺序阅读多个文档时,通常会记录每个文档中包含的唯一词项。这文档 → 词项的映射结构被称为正排索引(forward index)。

而 倒排索引(inverted index) 则反其道而行之:它保存的词项 → 文档的映射关系。也就是说,给定一个词项,你可以迅速查找到所有包含该词项的文档。

这类似于书籍的索引页:你无需翻阅整本书去寻找某个词,而是可以直接查找该词在哪些页码上出现。正是这种反向查找方式,使得搜索引擎可以实现快速检索,而 ClickHouse 已将这一机制原生集成。

既然我们已经了解了什么是倒排索引,那么接下来我们不妨看看 ClickHouse 的起点。虽然 ClickHouse 早期就提供了文本查找加速方式,但本质上仍属于概率型近似方案。为了说明为何需要真正的倒排索引,我们将回顾从最初基于 Bloom 过滤器的方案到现在新架构的演进过程。

从 Bloom 过滤器到真正的索引

在引入原生文本索引功能之前,ClickHouse 已支持使用 “bloom_filter”、”tokenbf_v1” 和 “ngrambf_v1” 索引实现全文搜索。这些机制基于 Bloom 过滤器——一种用于判断某个值是否存在于已索引文档中的概率型数据结构。

与倒排索引相比,Bloom 过滤器存在几个显著限制:

  • 难以调优: 使用 Bloom 过滤器时需要手动设置字节大小、哈希函数数量,并了解这些参数如何影响误报率。这通常需要较高的专业技术门槛。

  • 存在误报: Bloom 过滤器可能会返回“值可能存在”的结果,即使该值实际上并不在文档中。这导致系统仍需对更多行执行扫描操作,降低了整体搜索效率。

  • 功能受限: Bloom 过滤器索引仅支持 WHERE 子句中的部分表达式,而倒排索引从原理上具有更强的通用性与表达能力。

倒排索引有效解决了上述三大问题,因此成为 ClickHouse 当前全文搜索功能的核心技术基础。

新文本索引的工作原理

要理解新引擎如何实现高性能与精准查询,首先需要了解索引在磁盘中的组织方式,以及查询过程是如何与其内部结构协同工作的。

索引结构:字典 + 倒排列表

ClickHouse 的全文搜索基于一种原生倒排索引,也称为 文本索引(text index)。从结构上看,该索引由两个核心组成部分构成:

  1. 字典(dictionary):用于存储所有文档中出现的唯一 Token(词元)。
  2. 倒排列表(posting lists):记录每个词元所在文档的行号信息。

其运行机制如下:

  • 当你查询某个词元时,ClickHouse 会首先在字典中查找该词元。
  • 如果找到,字典会返回该词元对应的倒排列表位置。
  • 倒排列表本质上是一个行号数组,即包含该词元的所有文档所在的行。

举例来说:

  • 词元 wind 可能出现在文档 12、15、99、100 和 141 中。
  • 词元 winter 可能出现在文档 12、514、678 和 2583 中。

这种索引结构,即使在面对海量数据时,也能实现高效的词元查找。

下面是这一结构的简化图示:

在底层,ClickHouse 通过高级数据结构对字典和倒排列表进行压缩存储,下一节将深入解析其内部细节。

FST:高效节省空间的字典结构

为了快速查找词元,我们需要一种既高效又节省空间的字典结构。

最基础的实现方式是使用一个排好序的 (词元 → 倒排列表) 映射列表。这样我们可以通过快速的二分查找定位目标词元。考虑到文档通常为自然语言文本,这也带来了一个优化空间:前缀共享(prefix sharing)。

例如,许多词元具有相同的前缀:

  • ” win d”,” win ter”
  • ” click “,” click house”

普通的词元列表无法利用这些前缀相同的特征,但 有限状态转导器(Finite State Transducer,FST) 却可以很好地支持这种压缩方式。

什么是 FST?

FST(有限状态转导器)是一种紧凑的自动机结构,本质上是由字符构成的图,用于以高度压缩的方式编码词典。它最初设计用于实现字符串之间的转换,但也非常适合用于文本索引。像 Lucene(https://burntsushi.net/transducers/) 和 ClickHouse 这样的系统,就利用 FST 来表示排序后的字典。

与将每个 Token 直接存储为字符串不同,FST 具有以下优势:

  • 对共享的前缀和后缀仅存储一次;
  • 每个 Token 都被编码为图中的一条路径;
  • 当路径到达最终的“接受状态”结点时,FST 会输出一个值,例如该词元对应倒排列表的地址。

这种结构尤其适合多个 Token 拥有共同前缀或后缀的场景,使得整个字典更加紧凑。

Token 到倒排列表的映射

需要注意的是,FST 并不直接存储倒排列表内容,而是提供了定位它们的方法。具体来说,每个 Token 都对应一个倒排列表偏移量,即该列表在另一独立文件中的起始字节位置。

为了获取某个 Token 的偏移量,我们从 FST 的起始状态出发,逐步遍历路径直到接受状态。过程中,每次状态跳转可能会输出一个整数,最终通过累加这些数值得到对应的偏移量。虽然听起来略显复杂,但这正是标准 FST 构造算法的运作方式(参考文献:ref(https://link.springer.com/chapter/10.10073-540-44674-5_18))。

示例

假设我们有三个 Token:

ClickBench、ClickHouse、JSONBench

我们可以基于这组 Token 构建一个对应的 FST,并观察其图结构。

在 FST 中,这些 Token 被表示为字符之间的状态转换路径。例如,”Click” 作为公共前缀会被多条路径复用。当每个词元的路径到达终点时,FST 会输出相应的偏移值(如 10、20、30):

因此,FST 实现了两个关键功能:

  • 快速判断某个词元是否存在;
  • 精确计算该词元对应倒排列表的字节偏移量。

通过这种机制,ClickHouse 能够以高度压缩、快速可查的方式存储大规模词元字典。

Roaring Bitmaps:高效的集合操作

每个 倒排列表(posting list) 存储的是包含某个特定词元的所有文档行号。

为了在执行搜索时兼顾性能和存储效率,ClickHouse 需要一种同时具备以下特性的存储结构:

  • 紧凑:尽可能节省存储空间;
  • 高效:能够快速执行集合间的交、并、差等操作。

这种集合运算能力在处理包含多个词元并通过逻辑运算符组合的查询时尤为重要,例如执行多个词的“与”查询(AND)、“或”查询(OR)或排除查询(NOT)。

为什么压缩至关重要

传统的倒排列表压缩方案通常结合以下两种技术:

  • 增量编码(Delta encoding):通过记录相邻数值之间的差值来减少冗余;
  • Golomb-Rice 编码:对这些差值进行高效压缩。

这类方案在处理自然语言文本时通常效果不错,因为自然语言往往包含少量高频词和大量低频词。然而,它们以“位”为单位处理数据,不利于现代 CPU 的流水线机制和 SIMD 指令优化,因此在性能上存在瓶颈。

引入 Roaring Bitmaps

为了提升处理效率,ClickHouse 采用了 Roaring Bitmap(https://roaringbitmap.org/),这是一种专为大规模整数集合设计的高性能存储格式。

其核心设计理念如下:

  1. 将倒排列表视为一个 位图(bitmap),例如 [3, 5, 8] 可表示为 000101001;
  2. 将整个位图划分为多个 块(chunk),每块包含 65,536(2¹⁶)个可能值;
  3. 每个块根据其稀疏或密集程度,采用不同的容器进行存储:数组容器(Array):
  • 用于稀疏数据;
  • 位图容器(Bitmap):用于密集数据;
  • 游程编码容器(Run-length encoding):适用于长连续数据段。

这种结构不仅能有效压缩存储空间,还支持极快速的集合运算,如多个倒排列表之间的 AND、OR、NOT 操作。为了进一步提速,Roaring Bitmaps 还配有专门的 SIMD 加速实现,充分发挥现代处理器的性能优势。

可视化解析

在 Roaring Bitmap 中,每个 32 位的行号会被拆分为两个 16 位部分:

  • 高 16 位:用于确定所属容器;
  • 低 16 位:作为该容器内实际存储的值。

举个例子:

  • 行号:124586
  • 二进制表示为:00000000 00000001 11100110 11101010
  • 高 16 位:00000000 00000001 → 表示选择的容器
  • 低 16 位:11100110 11101010 → 存储于容器中的值

每个容器只负责某个特定的值区间,并根据其数据密度采用最合适的压缩格式。这种结构设计,使得系统能够高效地完成数十亿行号的扫描、合并和过滤操作。

到目前为止,我们已经介绍了 ClickHouse 文本索引的两个核心组成:

  • FST:构建紧凑、支持前缀共享的 Token 字典;
  • Roaring Bitmap:实现快速、压缩高效的倒排列表存储。

通过这两种结构,字典与倒排列表得以在存储层面实现高效组织。接下来我们来看整个索引在磁盘上的具体组织方式。

存储布局:段(segments)、粒度(granules)与文件结构

现在我们将所有组件组合起来,完整展现倒排索引的磁盘布局。

每个倒排索引在磁盘上由五个文件构成:

  1. ID 文件:记录段 ID 及其版本信息(关于“段”的定义见下文);
  2. 元数据文件(metadata):维护所有段的元信息;
  3. Bloom Filter 文件:存储布隆过滤器,用于在可能的情况下避免加载字典和倒排列表;
  4. 字典文件:以 FST 格式存储各个段的 Token 字典;
  5. 倒排列表文件:存储所有倒排列表,以压缩整数序列的形式表示。

可参考下图,了解这些文件在磁盘中的整体结构。

数据是如何组织的

每个数据 part 都会构建对应的索引,并在 合并操作(merges)(https://clickhouse.com/docs/merges) 中进行重建。在内部,索引被划分为若干个 段(segments),每个段包含以下内容:

  • 一个基于 FST 的词元字典;
  • 多个倒排列表(使用 Roaring Bitmap 表示),每个词元对应一个列表。

元数据文件用于记录以下信息:

  • 段 ID;
  • 起始行号(row ID);
  • 在 Bloom Filter 文件、字典文件、倒排列表文件中的偏移位置。

在上图中,箭头①、②、③展示了在查询过程中各个文件的加载顺序。

不同类型的文件,其大小也各不相同:

  • Bloom Filter 文件体积最小;
  • 字典文件体积适中;
  • 倒排列表文件体积最大。

段(Segment)与索引颗粒(Index Granule)

要理解“段”的概念,需先介绍 ClickHouse 中的 索引颗粒(granules),详见官方文档:链接(https://clickhouse.com/docs/guides/best-practices/sparse-primary-indexes#data-is-organized-into-granules-for-parallel-data-processing)

  • 索引颗粒 是 ClickHouse 的基本索引单位,通常包含 8192 行数据;
  • 段(segment) 是颗粒中的进一步划分单元,设计初衷是降低索引构建时的内存占用。

段的划分依据是字典的大小,而非行数。这种策略可以确保每个段的 FST 字典规模大致一致,从而在处理大规模数据集时,避免出现内存溢出的问题。

每个段都是独立的存储单元,包含:

  • 一段行号范围;
  • 一个 FST 格式的词元字典;
  • 多个对应的倒排列表。

虽然较小的段有助于减少内存压力,但也可能由于多个段中重复存储相同的词元而导致额外的存储开销。段划分功能默认未开启,但对于某些高级用例可选择启用。

索引查询的执行流程

当执行搜索查询时,ClickHouse 是通过以下步骤定位相关的文档行:

  1. 遍历所有段中的字典:

系统会在每个段的 FST 中查找目标词元。当遍历路径到达接受状态,说明该词元在对应段中存在。

  1. 累加 FST 输出值:

这些值代表该词元在倒排列表文件中的偏移位置,用于后续加载操作。

  1. 加载对应的倒排列表:

在“冷路径”(cold run)中,倒排列表需要从磁盘加载;

在“热路径”(hot run)中,倒排列表可能已被缓存在内存中。

注意:倒排列表中记录的行号是相对于段起始行号的“相对值”,在查询阶段会被转换为完整的颗粒级行号。(后文将在“直接索引使用优化”部分中进一步说明。)

在掌握了基本结构后,我们来看看新版中对文本索引的关键性优化,包括 API 设计改进、内存使用优化,以及更智能的颗粒处理等方面。

近期功能优化一览

过去三个月,ClickHouse 在全文搜索能力上取得了显著进展,涵盖从用户界面到底层存储的全面升级。以下是当前已支持以及即将发布的新功能:

  1. 更简洁的 API 设计

我们将索引名称从 “inverted” 改为更具语义的 “text”,同时重新设计了语法结构,使其更加直观易用。

现在支持在 INDEX 子句中以键值对形式配置参数,这不仅提升了可读性,也增强了配置的灵活性。

如下示例将创建一个文本索引,使用默认分词器(基于非字母数字字符进行分词)。你也可以选择其他分词器,如 ‘ngram’、’split’ 或 ‘no_op’,并进一步指定参数,例如设置 n-gram 的大小。

  1. 新增 split 分词器

我们引入了一种全新的分词器类型:split,专为日志、CSV 等半结构化文本场景设计。

与默认分词器根据通用语言规则自动识别单词不同,split 分词器仅在指定的分隔符处进行拆分(默认分隔符包括:,、;、\n 和 \)。

这种方式特别适合以下使用场景:

  • 类似 CSV 格式的结构化输入;
  • 使用固定分隔符的日志内容;
  • 任何需要精确控制词元边界的文本处理任务。

示例:CSV 格式文本

以下是默认分词器与 split 分词器在处理某个 CSV 样式字符串时的差异表现:

插入一个多行 CSV 风格的示例值

split 分词器的处理效果

默认分词器会将上述内容按常规方式拆分为多个词元,

它忽略了 “Click” 与 “House” 之间的换行符,因此不会将 “Click\nHouse” 视为一个完整的 Token。

而 split 分词器则严格按照定义的分隔符进行拆分。由于换行符 “\n” 并不匹配默认分隔符的精确边界位置,因此会保留 “Click\nHouse” 作为一个完整词元,不会被进一步拆分。

搜索行为说明

在搜索过程中,不同分词器对多行文本的处理存在差异:

默认分词器:无法匹配跨行的词元;

split 分词器:能够正确识别并命中完整的多行 Token。

核心结论:

当你需要基于精确定义的分隔符进行分词,且希望保留跨行或特殊格式的词元时,建议使用 split 分词器。

  1. 更小的索引体积

我们对文本索引在内存和磁盘层面的占用进行了显著优化:

  • 倒排列表采用 PFOR 压缩 鉴于交集与并集操作仅在内存中的倒排列表上执行,对于磁盘存储部分,我们可以优先考虑压缩率。因此,在原有 Delta 编码基础上,我们引入了 PFOR(Patched Frame of Reference)压缩算法。该改进使倒排列表的磁盘占用最多可减少约 30%。

  • FST 引入 Zstd 压缩 FST 在写入磁盘时现已默认启用 Zstd 压缩,平均体积进一步降低约 10%。

  1. 完善的云端兼容性

文本索引现已全面兼容 ClickHouse Cloud,包括对云环境中使用的 packed part 数据格式的支持。

5.更智能的索引粒度设置

我们将默认索引粒度从 1 提升至 64。

粒度指的是每个索引颗粒覆盖的行数范围,详见:索引粒度说明 →(https://clickhouse.com/docs/optimize/skipping-indexes)

  • 较小粒度:可以实现更精细的过滤,但由于词元在多个段中重复出现,会带来更高的内存消耗和磁盘 I/O。
  • 较大粒度:能显著降低资源开销,对大多数常见词元性能更优,但在处理极少数或聚集性强的词元时,过滤精度可能略有下降。

通过大量实验,我们发现将粒度设为 64 能在多数使用场景下带来优异表现,因此该设置已成为默认值。

在上述核心优化之外,我们还新增了若干功能,以进一步提升查询效率并减少资源消耗。其中之一就是引入布隆过滤器作为预过滤层,用于在加载倒排列表和字典前快速判断是否需要查询。

  1. 作为预过滤机制的 Bloom FilterBloom Filter

是一种轻量级的概率型数据结构,用于高效判断某个元素是否存在于集合中。它的判断结果具有以下特点:

  • 一定不在集合中时可明确排除;
  • 可能在集合中,但存在一定概率的误报(false positive),误报率可调。

由于 Bloom Filter 永远不会出现假阴性(false negative),它非常适合作为前置判断工具,在执行较重的计算或 I/O 操作前,先快速排除不可能匹配的项。

在 ClickHouse 中,我们将 Bloom Filter 引入为文本索引的预过滤层。这意味着,在扫描 FST 字典之前,可以先通过 Bloom Filter 判断词元是否*确定不存在*,从而有效降低磁盘读取和 CPU 开销。由于 Bloom Filter 占用空间小,通常可以常驻内存中,提升访问效率。

文本索引 vs Bloom Filter 索引:两者区别

在全文搜索中,Bloom Filter 的使用方式主要有两种:

  1. 作为独立的 Bloom Filter 文本索引:将词元直接插入 Bloom Filter 中;
  2. 作为附加层叠加在文本索引段上:由段内字典自动生成 Bloom Filter。

如前所述,基于 Bloom Filter 的独立索引存在一个主要缺点:需要用户手动设置参数(如大小、误报率等),调优复杂。

而第二种方式则是完全自动的,基于实际索引数据和可配置的误报率(默认值为 0.1%)生成 Bloom Filter,无需用户干预,使用更简单,适用性更强。

通常建议:除非对索引精度有特殊要求,否则推荐使用文本索引而非 Bloom Filter 索引。除了在结构层引入预过滤机制,我们还进一步优化了查询接口,引入了更现代、灵活的搜索函数。

  1. 新增搜索函数:searchAny 与 searchAll

现代问题,需要现代方法。

随着 ClickHouse 对分词器的支持不断扩展,并引入了多种自定义参数,原有的 “hasToken” 函数逐渐显得局限:它总是使用默认分词器来处理查询词,而不考虑索引实际使用的分词方式。这种行为常导致结果不符合预期,因此我们对此进行了改进。

我们新增了两个用于全文搜索的函数:searchAny 和 searchAll,以支持更加灵活、准确的查询。

简单示例

假设我们有如下建表语句及数据:

使用旧函数 hasToken 查询如下:

该查询不会返回结果。

原因如下:

hasToken 函数要求词元前后必须存在非字母数字字符作为分隔符。由于 “ClickHouse” 是一个完整单词,未被分隔,因此匹配失败。

如果改用 searchAny 函数:

则返回两行匹配结果:

在该示例中,使用 searchAll 同样会返回相同结果。

新函数优势

无需特殊分隔符

新引入的函数不再依赖词元周围的特殊字符,它们会直接使用索引中配置的分词器进行解析。

原生支持多个词元查询

例如,原本需要写作如下形式:

现在可以更简洁地写作:

语法更简洁,行为更一致

这些新函数始终依据索引中定义的分词器与参数配置执行,避免因分词器不一致而导致的查询偏差。

注意:searchAny 与 searchAll 仅适用于已创建全文索引的列。详细说明可参考官方文档:searchAny(https://clickhouse.com/docs/en/sql-reference/functions/string-search-functions#searchany) 和 searchAll(https://clickhouse.com/docs/en/sql-reference/functions/string-search-functions#searchall)

虽然这些函数带来了更高的可用性与灵活性,但真正实现性能飞跃的,是一项关键优化 —— 跳过文本列读取的能力。

实现无需读取文本列的高性能行级搜索

ClickHouse 中的倒排索引本质是一种跳过式索引(skip index),用于在查询的第一阶段快速排除不相关的数据颗粒(granules)。仅将可能包含目标词元的颗粒保留下来,并在第二阶段从磁盘加载这些颗粒并逐行进行匹配过滤。

这种机制对于低频词元效果非常显著,能够大幅降低 I/O 与处理开销。但若查询的是高频词元,极有可能导致多数甚至全部颗粒通过第一阶段筛选,进而在第二阶段形成接近全表扫描的实际执行成本。

全文搜索的新突破

我们彻底解决了全文搜索中的关键性能瓶颈。

倒排索引现在支持直接在行级别进行过滤,完全基于索引完成匹配,无需读取文本列。

该优化在现有颗粒级过滤逻辑基础上,引入了一个全新的行级匹配引擎,对用户完全透明,无需更改任何查询语句。

示例场景

考虑如下查询:

优化前:必须读取文本列才能识别匹配行

  • 查询引擎必须从已建立索引的 col3 文本列中读取内容,以逐行判断哪些行符合查询条件;
  • 即使前期已使用索引排除了部分不相关颗粒,仍需扫描大量行内容;
  • 由于文本列通常较大,且缺乏行级索引支持,这种方式处理效率偏低。

优化后:只依赖索引完成匹配 —— 查询提速高达 10 倍

  • 查询过程中仅读取倒排索引文件;
  • 引擎直接使用索引提取出匹配的行号(row ID),无需访问 col3;
  • 系统仅读取所需返回的列(如 col1 和 col2);
  • 对于高频词元,查询时间可缩短 90% 以上。

下图清晰对比了优化前后执行路径的差异:

优化前的执行流程:

在执行 hasToken(col3, 'hello') 时,ClickHouse 必须:

  • 从 col3 文本列中加载颗粒内容,查找匹配行;
  • 读取 col1 和 col2 等其它列用于返回查询结果。

尽管倒排索引已用于跳过部分无关颗粒,但系统仍需对“幸存颗粒”逐行读取并解析文本,以完成最终匹配判断。换句话说,索引只参与了第一阶段的粗筛,未能参与第二阶段的精确匹配。

这种模式在处理大型文本字段时尤为低效,原因包括:

  • 文本列通常占用磁盘较多,读取开销大;
  • 实际需要的信息已保存在体积更小、处理更快的倒排索引结构中。

优化后的执行流程:

新引擎实现完全基于索引的匹配逻辑:

  • 不再读取文本列;
  • 仅加载倒排索引文件;
  • 基于行号直接完成过滤;
  • 仅读取结果所需列。

这一优化彻底打通了全文搜索在 ClickHouse 中的性能瓶颈。

真实业务中的性能收益

我们基于 Hacker News 数据集(https://github.com/ClickHouse/ClickHouse/issues/29693)(约 2700 万行)对全文索引的优化效果进行了测试。

测试覆盖了从高频到低频词元的多种情况:

(测试运行环境:m6i.8xlarge 实例,32 核 vCPU,128 GiB 内存,Xeon 8375C @ 3.5GHz)

用户无需干预,效果自动生效

这项优化对用户完全透明,无需更改查询语句即可享受性能提升。目前我们也在继续优化冷路径表现,并计划很快将此功能正式开放。

综合这些改进,ClickHouse 的全文搜索已具备更高的执行效率,能够自然地集成进现有分析查询流程中。

至此,搜索体验全面升级

从分词逻辑到全索引路径访问,全新的原生文本索引让 ClickHouse 的搜索能力实现“快、准、深度融合”。无论是模糊匹配、多样化分词器支持,还是无需读取文本列的行级过滤逻辑,都让你可以像写分析 SQL 一样流畅地执行复杂搜索操作。

comments loading