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

图片

本文字数:13269;估计阅读时间:34 分钟

作者:Jimmy Aguilar, Elmi Ahmadov, and Robert Schulze

本文在公众号【ClickHouseInc】首发

图片

简而言之(TL;DR)

我们在 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 的行,我们可能会执行如下查询:

SELECT [...] FROM [...] WHERE documents LIKE '% 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 中。

这种索

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值