PlanetScale 发布 Postgres 全文检索扩展 TIN:索引直接存 ctid,官方基准 199 QPS 对 ParadeDB 的 7.9 QPS
PlanetScale 推出 PostgreSQL 全文检索扩展 TIN(Text INdex),已作为 GA 版本发布。它的做法是从根上取消一层间接:倒排记录不用连续文档编号,直接用 Postgres 底层的 48 位 ctid 物理元组标识符,段合并时位图可以直接转移所有权,也就不再有重新编号带来的写入放大。官方基准里,混合查询 Top-10 BM25 的吞吐达到 199 QPS,是 ParadeDB(7.9 QPS)的 25 倍;作者总结说各种场景下吞吐至少比同类方案高 8 倍。
作者:核流编辑 · 栏目:资源
在 PostgreSQL 里做全文检索,开发者长期在两个坏选项里二选一:内置的 GIN 索引做析取查询,也就是「任一关键词命中即可」那种,在官方基准测试里直接内存溢出,跑不完测试;引入外部扩展又得维护一层文档映射;报道里的 ParadeDB 这类方案接受写入时,读吞吐还会明显下滑。 云数据库厂商 PlanetScale 在 2026 年 9 月 16 日发布的全文检索扩展 TIN,就是对这个两难的回答。 新扩展名为 TIN(全称 Text INdex),专为 PostgreSQL 打造。它已作为 GA 版本正式发布,可立即用于所有 Postgres 与 Neki 数据库。名字起得也直白:Text INdex,字面意思。 不重新给文档编号,直接用 ctid 传统全文搜索引擎通常会给每个索引文档赋予一个从 1 开始递增的连续编号(Doc ID)。而 PostgreSQL 内部定位数据行,靠的是 48 位物理元组标识符 ctid,也就是 32 位页面号加 16 位页内偏移。 于是外部扩展多了一道工序:维护一张庞大的二次映射表,把文档 ID 逐条转换回 ctid。处理千万级结果集时,这笔转换开销不小。TIN 是从底层取消了这层间接。为什么要这么做,作者 Eric Ridge 与 Patrick Reynolds 在技术解析里给过理由。 他们写道:「TIN 直接使用 ctid,因为 Postgres 内部本身就以 ctid 运转。」 压缩是同一道题的另一半。作者进一步解释:「常规倒排列表压缩技术在面对不连续的 48 位数字时往往效率低下。」数据页的紧凑特性,让两级位图有了用武之地。 PostgreSQL 的标准数据页是 8KB。一个页面最多容纳 291 个物理元组;对含文本列的表,多数页面往往只有 32 行甚至更少。两级位图编码针对的正是这个结构:页面级记一次位置,偏移级再记一次。 页面级位图 256 位,刚好完整装入一个 AVX2 寄存器;偏移级位图则能装进单个 AVX-512 寄存器,或拆进两组 AVX2。压缩效果上,位图把不连续的 ctid 压得很小:高频词接近 1 bit/posting,中频词约 7 bit,低频词可接近 25 bit。 段合并时,这种设计的便宜更明显:旧段里的位图可以直接把所有权转移给新段,不需要重新编号,也就没有传统引擎二次重写带来的写入放大。返回结果时,TIN 天然按堆存储顺序给出数据,读行变成顺序 I/O——哪怕用的是 NVMe,顺序访问依然比随机访问快得多。 执行层面,TIN 把布尔与、布尔或检索直接映射为 CPU 原生的向量化位运算指令;COUNT(*) 这类统计则调用硬件级 POPCNT 指令统计置位位图,循环与分支跳转能省就省。 它还会与 PostgreSQL 的可见性位图求交集。如果每个堆页面都标记为全可见,计数查询可以不碰堆表;数据有更新时,只核对非全可见页上的 ctid,事务可见性依旧由 MVCC 保证。 基准:199 QPS 对 7.9 QPS 先把测试条件摊开:AWS i7i.8xlarge 实例,Postgres 18.6 装在限额 8 vCPU、32GB 内存的隔离容器里,本地 NVMe,语料是 85GB 的 Stack Exchange 数据。容器限额是故意卡小的,为的是让索引装不进 Postgres 缓冲区,逼出真实差距。 混合查询(与、或、短语)Top-10 BM25 检索,无并发写入:TIN 吞吐量达到 199 QPS,是 ParadeDB(7.9 QPS)的 25 倍,P99 延迟低 26 倍(256ms 对 6765ms);内置 GIN 索引则因内存溢出无法完成该测试。 边写边查:在每秒 1000 次 UPDATE 的写入压力下,TIN 仍维持 125 QPS 的检索吞吐,10 分钟内完成了 270,279 次数据更新;同期 ParadeDB 掉到 2.2 QPS,pg_textsearch 的读吞吐停在 3.5 QPS,十分钟只写完 735 次更新。 作者给出的总结是:在各种场景下,TIN 的吞吐至少比同类方案高 8 倍,从磁盘读取的数据更少,索引更新期间的性能下降也有限。这是官方自报的基准,不过测试实例、容器限额与调参都写在文章里,想复现的人可以照着来一遍。 装好就能用的 SQL 适用的场景不用编:电商平台要取回同时包含几个关键词的前十件商品;法律取证平台要返回含任一关键词的全部文档,且完全不关心排序;照片平台只想数出带某个标签的照片有多少张。 开发者在支持的数据库实例里安装扩展后,剩下的就是标准 SQL: -- 创建 TIN 全文索引 CREATE INDEX an_index_name ON table_name USING tin(text_column_name); -- 关键词检索(==> 为 TIN 的检索操作符) SELECT a, b, c FROM lyrics WHERE content ==> 'give you up'; -- 方括号表示析取:命中任一关键词即可 SELECT a, b, c FROM lyrics WHERE content ==> '[insider trading conspiracy]'; -- 双引号表示短语匹配;计数查询不需要访问堆表 SELECT COUNT(*) FROM lyrics WHERE content ==> '"san francisco"'; 想深入了解的话,功能说明与接入指南都在 PlanetScale 的文档里。对已经在用 PostgreSQL、又不想再单独运维一套搜索引擎的团队来说,这是少加一层基础设施的选择——建索引一句 USING tin(...),查询一句 ==>,就这么多。 参考资料 PlanetScale:Introducing TIN: full-text search for Postgres