把 PostgreSQL 搜索升级到 TIN:别只比延迟,还要验证相关性与成本

2026-09-23 11 预计阅读时间: 1 分钟
来源: planetscale.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

一个 PostgreSQL 搜索演示可以很快跑起来,但“能搜索”与“适合生产”之间仍隔着索引体积、尾延迟、结果相关性和基础设施成本。来源案例把现有演示升级到 TIN,并将结论概括为更快、更好、更便宜。真正值得借鉴的,不只是替换搜索组件,而是如何用同一批数据和查询证明这三个判断。

由于摘要没有给出 TIN 的安装方式和 SQL 接口,下面不会虚构扩展名称或建索引语法。示例先建立一个可运行的 PostgreSQL 全文搜索基线,再说明如何把相同工作负载接入 TIN 做对照实验。

“更快、更好、更便宜”是三个不同指标

搜索系统最容易犯的错误,是只展示一条查询的执行时间。一次命中缓存的 EXPLAIN ANALYZE 不能代表真实负载,更不能证明结果质量。

可以把评估拆成三组指标:

  • 更快:关注吞吐量、P50、P95、P99 延迟,以及冷缓存和热缓存的差异。
  • 更好:检查前 10 或前 20 条结果是否真正相关,可使用 Precision@K、Recall@K、MRR 或 nDCG。
  • 更便宜:同时计算索引磁盘空间、内存需求、CPU 时间、写入放大和运维复杂度。

成本不能只看数据库实例价格。一个搜索方案即使单次查询更快,如果索引构建时间过长、更新代价高,或者需要保留多份数据,总成本仍可能更高。

可以用一个简单模型统一比较:

月度总成本 = 数据库计算成本
           + 索引存储成本
           + 构建与更新索引的计算成本
           + 备份和副本成本
           + 运维时间成本

TIN 是否占优,应在相同数据量、相同查询集和相同结果质量目标下判断,而不是拿不同配置的演示数字直接比较。

建立一个可复现的 PostgreSQL 搜索基线

下面的例子使用 PostgreSQL 自带全文搜索和 GIN 索引。运行前需要安装 Docker;端口 5432 如果已被占用,可以改成其他端口。

docker run --name pg-search-demo \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=search_demo \
  -p 5432:5432 \
  -d postgres:16

export DATABASE_URL='postgresql://postgres:postgres@localhost:5432/search_demo'

until docker exec pg-search-demo pg_isready -U postgres >/dev/null 2>&1; do
  sleep 1
done

创建 20 万条测试文档和一个存储生成列。这里的数据是合成数据,只适合验证流程,不代表真实搜索质量。

cat > setup.sql <<'SQL'
CREATE TABLE documents (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    title text NOT NULL,
    body text NOT NULL,
    search_vector tsvector GENERATED ALWAYS AS (
        setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
        setweight(to_tsvector('english', coalesce(body, '')), 'B')
    ) STORED
);

INSERT INTO documents (title, body)
SELECT
    CASE i % 4
        WHEN 0 THEN 'Postgres search indexing guide'
        WHEN 1 THEN 'Running databases on Kubernetes'
        WHEN 2 THEN 'Improving API latency'
        ELSE 'Monitoring query performance'
    END || ' #' || i,
    CASE i % 4
        WHEN 0 THEN 'Postgres full text search index ranking query performance'
        WHEN 1 THEN 'Kubernetes operator storage backup database cluster'
        WHEN 2 THEN 'HTTP API cache latency throughput production service'
        ELSE 'SQL execution plan buffers indexes monitoring optimization'
    END || ' token-' || md5(i::text)
FROM generate_series(1, 200000) AS g(i);

CREATE INDEX documents_search_gin
    ON documents USING gin (search_vector);

ANALYZE documents;
SQL

psql "$DATABASE_URL" -f setup.sql

现在执行一条带排名的搜索,并记录执行计划和缓冲区使用量:

psql "$DATABASE_URL" <<'SQL'
EXPLAIN (ANALYZE, BUFFERS, WAL, SETTINGS)
SELECT
    id,
    title,
    ts_rank_cd(
        search_vector,
        websearch_to_tsquery('english', 'postgres search')
    ) AS rank
FROM documents
WHERE search_vector @@ websearch_to_tsquery('english', 'postgres search')
ORDER BY rank DESC
LIMIT 20;
SQL

重点观察:

  • 是否使用了目标索引;
  • Execution Time 是否包含昂贵排序;
  • 命中了多少 shared buffers;
  • 返回候选集后,排名阶段处理了多少行;
  • 同一查询连续执行时,冷缓存与热缓存相差多少。

用同一工作负载比较 TIN

升级到 TIN 时,不应一边修改索引,一边修改数据、查询条件和并发数。稳妥的做法是准备两个独立数据库:一个保留 PostgreSQL 基线,另一个按照 TIN 的正式文档安装并创建对应索引。

摘要没有提供 TIN 的准确 DDL,因此需要把 TIN 官方安装和建索引命令填入迁移步骤,而不是猜测接口。完成安装后,尽量保持以下条件一致:

  1. 导入相同快照中的数据;
  2. 使用相同字段和分词语言;
  3. 使用相同查询词、过滤条件和 LIMIT
  4. 固定连接数、线程数和测试时长;
  5. 分开记录索引构建、增量写入与查询阶段的成本。

可以使用 pgbench 对两套环境施加相同负载。先创建查询脚本:

cat > search.sql <<'SQL'
\set topic random(1, 4)
SELECT id, title
FROM documents
WHERE search_vector @@ plainto_tsquery(
    'english',
    CASE :topic
        WHEN 1 THEN 'postgres search'
        WHEN 2 THEN 'kubernetes database'
        WHEN 3 THEN 'api latency'
        ELSE 'query performance'
    END
)
LIMIT 20;
SQL

再为基线库和 TIN 库设置连接地址。这里假设迁移后仍可使用同一条 SQL;如果 TIN 提供不同的查询函数,应复制一份 search-tin.sql,但要保持查询语义和返回数量一致。

export BASELINE_URL='postgresql://postgres:postgres@localhost:5432/search_demo'
export TIN_URL='postgresql://user:password@tin-host:5432/search_demo'

mkdir -p benchmark-results

pgbench -n -c 16 -j 4 -T 60 \
  -f search.sql "$BASELINE_URL" \
  | tee benchmark-results/baseline.txt

pgbench -n -c 16 -j 4 -T 60 \
  -f search.sql "$TIN_URL" \
  | tee benchmark-results/tin.txt

pgbench 可以快速给出吞吐量和平均延迟,但生产决策还需要 P95/P99。可以在应用层记录每次请求耗时,或配合 PostgreSQL 日志、pg_stat_statements 和可观测性平台采集延迟分布。

“更好”必须靠标注查询验证

排名质量不能由执行计划回答。建议从真实搜索日志中抽取 50~200 个查询,为每个查询标注理想结果,形成一个小型评测集:

{"query":"postgres search","relevant_ids":[12,48,91]}
{"query":"reduce api latency","relevant_ids":[203,811]}
{"query":"database backup kubernetes","relevant_ids":[77,102,450]}

然后分别收集基线和 TIN 的前 K 条结果,计算:

  • Precision@10:前 10 条中有多少是相关结果;
  • Recall@20:已标注的相关文档有多少出现在前 20 条;
  • MRR:第一个相关结果出现得是否足够靠前;
  • nDCG@10:当相关性存在等级时,排序是否合理。

这里还要控制一个常见变量:分词、停用词和词干化配置。如果两套系统采用不同语言配置,即使底层索引完全相同,结果质量也可能出现明显差异。比较时应明确这是索引能力、排名算法还是分析器配置带来的变化。

上线前的检查清单

TIN 升级值得尝试,但不要因为演示中的漂亮数字直接替换生产索引。上线前至少确认以下事项:

  • TIN 支持当前 PostgreSQL 版本、部署平台和高可用方案;
  • 备份、恢复、复制和版本升级流程经过演练;
  • 索引构建不会长时间阻塞写入或耗尽临时磁盘;
  • 新增、修改和删除文档后的可见延迟符合业务要求;
  • 混合过滤、分页、排序和权限条件仍能高效执行;
  • 基准测试包含热缓存、冷缓存、高并发和持续写入场景;
  • 搜索质量至少不低于现有实现;
  • 回滚路径清晰,可以在新旧索引之间快速切换。

更稳妥的迁移方式是双写索引、镜像查询并离线比较结果,再让少量流量进入 TIN。只有当延迟、质量和总成本同时达到目标时,“更快、更好、更便宜”才从演示结论变成了可执行的生产决策。


相关推荐