2026 年 9 月 16 日,爱里克·里奇(Erik Rijkers)与帕特里克·雷诺兹(Patrick Reynolds)宣布推出名为 TIN 的 PostgreSQL 全文搜索扩展。该工具旨在提供快速、功能完整且可靠的文本索引能力,目前已作为 GA(正式可用)版本发布,适用于所有 Postgres 及 Neki 数据库实例。

TIN 核心特性与设计目标
TIN 的设计初衷是解决现有 Postgres 文本搜索方案在功能完整性与性能之间的权衡问题。其基本使用方式如下:
CREATE INDEX an_index_name ON table_name USING tin(text_column_name);
SELECT * FROM table_name
WHERE text_column_name ==> 'some words';
开发团队指出,一个理想的 Postgres 文本索引应同时具备以下能力:
- 支持布尔表达式、短语查询和跨度查询;
- 支持术语级别的模糊匹配、代码匹配及正则表达式匹配;
- 自动处理大小写折叠与重音符号折叠;
- 支持 COUNT(*) 查询及基于 BM25 评分的 Top-K 排序查询。
此外,TIN 强调其在复杂场景下的兼容性,包括多表连接、混合列类型的 WHERE 子句、并发更新、数据复制、备份机制以及正确的事务可见性支持。尽管 Postgres 生态中已存在至少三种文本搜索索引方案,但据称尚无一款能完全满足上述所有要求,而 TIN 在此方面进行了针对性优化。
应用场景示例
应用程序开发者可利用 TIN 构建多种搜索功能。例如,电商平台需检索包含特定关键词的前十个产品:
SELECT * FROM products
WHERE description ==> 'stretch denim jeans'
ORDER BY tin.score(ctid) DESC
LIMIT 10
法律发现平台可能需要返回包含特定词组的文件,且不关注排名结果:
SELECT * FROM emails
WHERE body ==> '[insider trading conspiracy]'
照片标签平台则可能仅需统计特定标签的照片数量:
SELECT COUNT(*) FROM photos
WHERE tags ==> '"san francisco"';
在实际生产环境中,应用通常需要在持续写入、更新或删除文档的同时进行索引查询。TIN 承诺,只要事务提交,搜索查询即可立即反映新增或修改的数据行。
基准测试环境与配置
为评估性能,团队针对多种工作负载运行了基准测试,涵盖合取(AND)、析取(OR)及短语查询,并测试了 BM25 评分排序及并发写入场景下的表现。
数据集选择
测试使用了多个大型文本语料库,包括整个维基百科、总计 2.3 TB 的 Reddit 评论集合,以及一个名为“pile”的混合工作负载(含 797 GB 开放获取论文、法律文件、公版书籍及 Enron 邮件)。主要基准测试结果基于 Stack Exchange 问答数据导出集:该语料库包含 1.5 亿份文档,总大小 85 GB。由于缺乏标准查询轨迹,团队通过采样 2 至 15 个词的子串生成了合成查询,分别解释为合取、析取和短语查询,共计生成 1,719 条查询。
硬件与软件环境
基准测试在 AWS i7i.8xlarge EC2 实例上进行。每个文本搜索扩展均在隔离容器中运行 Postgres 18.6。为确保公平性,测试顺序执行以避免资源争用,并使用相同实例类型以最小化网络延迟影响。
配置参数方面,除 ParadeDB 基准工具默认值外,团队调整了三项关键参数以匹配容器资源:max_parallel_workers 设为 8(原默认 40),shared_buffers 设为 24 GB(原默认 128 MB),maintenance_work_mem 设为 24 GB(原默认 64 MB)。测试对比对象包括 TIN v1.0.2、ParadeDB v0.25.2、pg_textsearch v1.4.0 以及 Postgres v18.6 内置 GIN 索引。结果显示,除 TIN 外,仅 ParadeDB 能完成全部基准测试。
性能测试结果
索引构建效率
在索引构建阶段,各引擎的表现差异显著。值得注意的是,除 TIN 外的其他三个引擎在初始配置的 32GB RAM 限制下均出现失败,因此仅在该环节增加了可用内存进行测试。最终查询阶段统一恢复至 32 GB RAM 配置。
| 引擎 | 总耗时 | 索引大小 | 所需 RAM |
|---|---|---|---|
| TIN | 8分10秒 | 50.7 GB | 32 GB |
| ParadeDB | 19分20秒 | 52.1 GB | 64 GB |
| pg_textsearch | 26分49秒 | 41.5 GB | 128 GB |
| Postgres GIN | 2小时09分04秒 | 28.0 GB | 64 GB |
混合查询与 Top-10 排序
在不涉及并发写入的情况下,对合取、析取和短语混合查询并按 BM25 分数返回前 10 名结果的测试显示,TIN 的每秒查询数(QPS)是 ParadeDB 的 25 倍,p99 延迟低 26 倍。GIN 因在执行析取查询时耗尽内存而无法完成测试,pg_textsearch 则因仅支持合取查询而未参与此项对比。
合取与短语查询
在仅包含合取和短语查询的 Top-10 场景中,TIN 的 QPS 达到 ParadeDB 的 10 倍、GIN 的 541 倍;p99 延迟分别低 6 倍和 1,356 倍。此测试中,TIN 与 ParadeDB 使用 BM25 算法,而 GIN 使用 ts_rank_cd 进行排名。
并发写入下的析取查询
当引入每秒 1,000 次 UPDATE 操作的并发客户端时,TIN 依然保持高性能。在十分钟的运行中,TIN 完成了 270,279 次更新,而 ParadeDB 完成 185,584 次,pg_textsearch 仅完成 735 次。TIN 的读取吞吐量是 pg_textsearch 的 36 倍、ParadeDB 的 57 倍,p99 延迟分别低 24 倍和 36 倍。相比之下,ParadeDB 的写入接受策略牺牲了读取性能,而 pg_textsearch 因锁竞争导致写入几乎停滞。
内存充足场景
当索引完全适配共享缓冲区时,针对维基百科语料库(8.0 GB 文档)的析取查询计数测试显示,TIN 的性能优势更为明显。pg_textsearch 因不支持非 Top-K 计数查询而未参与此项对比。
详细数据汇总
下表展示了不同场景下的具体指标,其中“MB/查询”反映了从磁盘或块缓存读取的数据量,较低的值有助于减轻 I/O 压力并保持服务器整体响应速度。
Conjunction, disjunction, and phrase queries; top-10
┌────────────────────────────────────────────────────────────────────┐
│ QPS p99 MB/query Updates │
├─────────────────────────┬───────┬──────────┬───────────┬───────────┤
│ TIN - read-only │ 199 │ 256ms │ 65 │ │
│ - with updates │ 172 │ 284ms │ 88 │ 271,398 │
├─────────────────────────┼───────┼──────────┼───────────┼───────────┤
│ ParadeDB - read-only │ 7.9 │ 6,765ms │ 582 │ │
│ - with updates │ 6.0 │ 7,990ms │ 591 │ 193,487 │
└─────────────────────────┴───────┴──────────┴───────────┴───────────┘
Conjunction and phrase queries; top-10 (read-only)
┌───────────────────────────────────────────────┐
│ QPS p99 MB/query │
├───────────────┬───────┬───────────┬───────────┤
│ TIN │ 242 │ 212ms │ 73 │
├───────────────┼───────┼───────────┼───────────┤
│ ParadeDB │ 24 │ 1,279ms │ 668 │
├───────────────┼───────┼───────────┼───────────┤
│ Postgres GIN │ 0.4 │ 288,066ms │ 595 │
└───────────────┴───────┴───────────┴───────────┘
Disjunction queries; top-10
┌────────────────────────────────────────────────────────────────────────┐
│ QPS p99 MB/query Updates │
├──────────────────────────────┬───────┬───────────┬─────────┬───────────┤
│ TIN - read-only │ 148 │ 324ms │ 48 │ │
│ - with updates │ 125 │ 354ms │ 77 │ 270,279 │
├──────────────────────────────┼───────┼───────────┼─────────┼───────────┤
│ ParadeDB - read-only │ 17 │ 2,385ms │ 303 │ │
│ - with updates │ 2.2 │ 12,634ms │ 394 │ 185,584 │
├──────────────────────────────┼───────┼───────────┼─────────┼───────────┤
│ pg_textsearch - read-only │ 3.5 │ 8,646ms │ 11,639 │ │
│ - with updates │ 3.5 │ 8,409ms │ 11,656 │ 735 │
└──────────────────────────────┴───────┴───────────┴─────────┴───────────┘
Conjunction, disjunction, and phrase queries; COUNT(*) (read-only)
┌─────────────────────────────────────────┐
│ QPS p99 MB/query │
├───────────┬───────┬──────────┬──────────┤
│ TIN │ 179 │ 438ms │ 97 │
├───────────┼───────┼──────────┼──────────┤
│ ParadeDB │ 10 │ 2,704ms │ 544 │
└───────────┴───────┴──────────┴──────────┘
Disjunction queries; COUNT(*); Wikipedia corpus (read-only)
┌───────────────────────────────────────────────────┐
│ QPS p99 MB/query │
├───────────────┬──────────┬─────────────┬──────────┤
│ TIN │ 10,260 │ 2ms │ 1.7 │
├───────────────┼──────────┼─────────────┼──────────┤
│ ParadeDB │ 291 │ 95ms │ 22 │
├───────────────┼──────────┼─────────────┼──────────┤
│ Postgres GIN │ 1.4 │ 30,292ms │ 2.5 │
└───────────────┴──────────┴─────────────┴──────────┘
综合来看,TIN 在各类场景中吞吐量至少比替代方案高 8 倍,磁盘读取数据量更少,且在高频更新下仍能保持稳定的性能表现。
技术架构解析:为何 TIN 速度快?
TIN 的高性能源于其独特的架构设计,核心在于直接使用 Postgres 的 ctid 作为文档标识符,并利用现代 CPU 的向量化指令进行高效交集与并集运算。
文档标识符的选择
传统文本索引系统通常将文档编号分配为连续整数(如段内 ID 1 到 n),以便利用 Delta 编码和位打包等技术高度压缩倒排列表。然而,这种独立分配的 ID 意味着不同段之间的 ID 无法直接比较,且最终查询时需将这些内部 ID 映射回 Postgres 可识别的物理位置。
TIN 摒弃了内部连续 ID 方案,直接采用 Postgres 的 ctid(Current Tuple Identifier)作为文档标识符。ctid 是一个 48 位数值,由页号(高 32 位)和页内偏移号(低 16 位)组成,唯一标识堆表中元组的物理位置。由于 Postgres 内部索引(B-tree, GIN, GiST, Hash)及缺失位图扫描均原生支持 ctid,TIN 无需维护额外的 ID 映射结构,从而避免了百万级匹配结果时的查找开销。
两级位图编码与向量化
虽然 48 位的 ctid 不连续使得传统压缩技术失效,但 TIN 利用了 Postgres 页面的特性实现了高效编码。单个 8KB 页面最多容纳约 291 个元组,实际场景中通常少于 32 个。这意味着页内偏移号分布密集且范围小,适合使用微型位图存储。
TIN 采用两级位图结构:
- 页级位图:标记哪些页面包含特定术语。每个位图长 256 位,恰好适配 x86 CPU 的 AVX2 向量寄存器。
- 偏移级位图:标记页面内的具体元组位置。每个位图可适配一个 AVX-512 寄存器或两个 AVX2 寄存器。
这种结构允许大量计算卸载至 CPU 向量指令:
- 合取查询(AND):通过对页级位图执行向量 AND 操作,快速排除不包含共同术语的页面,避免解码无关的偏移级位图。
- 析取查询(OR)与计数:对于
COUNT(*)类查询,若两个术语的页级位图无交集,TIN 可直接相加各自的精确帖子计数,跳过读取完整的倒排列表。CPU 原生的POPCNT指令可用于快速统计位图中置位的数量。 - 结果提取:查询返回的行数据可通过位位置直接重构出
ctid,无需磁盘查找。由于ctid本身按堆物理顺序排列,后续读取匹配元组时天然形成顺序访问模式,极大提升了 NVMe 等现代存储设备的 I/O 效率。





