一个 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 官方安装和建索引命令填入迁移步骤,而不是猜测接口。完成安装后,尽量保持以下条件一致:
- 导入相同快照中的数据;
- 使用相同字段和分词语言;
- 使用相同查询词、过滤条件和
LIMIT; - 固定连接数、线程数和测试时长;
- 分开记录索引构建、增量写入与查询阶段的成本。
可以使用 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。只有当延迟、质量和总成本同时达到目标时,“更快、更好、更便宜”才从演示结论变成了可执行的生产决策。