为什么 Postgres 可能已经包含了你一半的基础设施
大多数团队使用 Postgres 处理关系型数据,然后使用 Redis 进行缓存,单独的服务进行向量搜索,cron 调度器,地理空间数据库和消息队列。如果其中四五个功能已经存在于你的 Postgres 安装中,只需通过一个 CREATE EXTENSION 命令就能启用,会怎样?
这并非炒作。Postgres 扩展生态系统已经成熟到专业扩展可以替代整个外部服务的程度——具有更低的运营开销、更简单的架构,并且通常具有相当的性能。本指南涵盖了值得部署的扩展、它们能替代什么以及它们的不足之处。
pgvector:淘汰你的独立向量数据库
如果你在 2026 年构建 AI 驱动的功能,你可能已经看过 Pinecone、Weaviate 或 Qdrant。在注册之前,先检查 pgvector 是否能满足你的用例——对大多数团队来说是可以的。
pgvector 为 Postgres 添加了 vector 数据类型和高效的相似性搜索(HNSW 和 IVFFlat 索引)。安装方法:
-- 在 Ubuntu/Debian 上
sudo apt install postgresql-16-pgvector
-- 然后在 psql 中
CREATE EXTENSION vector;
创建包含嵌入的表并通过余弦相似度进行查询:
CREATE TABLE documents (
id bigserial PRIMARY KEY,
content text,
embedding vector(1536) -- OpenAI ada-002 维度
);
-- 创建 HNSW 索引用于快速近似搜索
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 找出 5 个最相似的文档
SELECT id, content,
1 - (embedding <=> '[0.1,0.2,...]'::vector) AS similarity
FROM documents
ORDER BY embedding <=> '[0.1,0.2,...]'::vector
LIMIT 5;
杀手级功能是将向量搜索与结构化过滤器结合——外部向量数据库处理起来很麻烦:
-- 在特定用户的文档中进行语义搜索
SELECT d.id, d.content,
1 - (d.embedding <=> $1::vector) AS similarity
FROM documents d
WHERE d.user_id = $2
AND d.created_at > NOW() - INTERVAL '30 days'
ORDER BY d.embedding <=> $1::vector
LIMIT 10;
这个查询要么需要在独立向量数据库中进行预过滤(降低召回率),要么需要采用两步式的获取然后过滤的方法。在 Postgres 中,这是一个具有完整 ACID 保证的单查询。
当 pgvector 不够用时
当向量数量超过约 500 万且需要低于 10 毫秒的延迟时,pgvector 开始显示出压力。如果你达到了这个规模,像 Qdrant 或 Weaviate 这样的专用系统能提供更好的吞吐量。对于大多数应用程序——尤其是早期阶段——pgvector 是正确的起点。
pg_cron:替换你的外部作业调度器
涉及数据库的 Cron 作业通常由外部管理:Linux crontab 条目运行脚本、AWS EventBridge 规则、Kubernetes CronJob。每种方式都会增加运营开销和一个故障域。pg_cron 直接在 Postgres 内部运行计划作业。
-- 安装
CREATE EXTENSION pg_cron;
-- 每天凌晨 3 点 UTC 安排清理作业
SELECT cron.schedule(
'cleanup-old-sessions',
'0 3 * * *',
$$DELETE FROM user_sessions WHERE expires_at < NOW() - INTERVAL '7 days'$$
);
-- 每小时运行一次数据汇总
SELECT cron.schedule(
'hourly-stats-rollup',
'0 * * * *',
$$INSERT INTO hourly_stats SELECT date_trunc('hour', created_at), COUNT(*), SUM(amount)
FROM orders WHERE created_at >= date_trunc('hour', NOW()) - INTERVAL '1 hour'
AND created_at < date_trunc('hour', NOW())
GROUP BY 1$$
);
-- 查看计划作业
SELECT * FROM cron.job;
-- 查看最近的运行历史
SELECT * FROM cron.job_run_details ORDER BY start_time DESC LIMIT 20;
pg_cron 的配置需要将其添加到 postgresql.conf 中的 shared_preload_libraries:
shared_preload_libraries = 'pg_cron'
cron.database_name = 'myapp'
相比外部调度器的实际优势
- 无需网络跳转 — 作业在数据所在的位置运行
- 作业在事务中运行 — 失败的作业不会留下部分状态
- 作业历史记录在数据库中 — 可查询,可与其他数据连接
- 无需单独的凭据或连接管理
限制:在副本设置中,pg_cron 只在主节点上运行。如果主节点发生故障,计划作业在故障转移完成前不会运行。对于真正关键的计划工作,请保留外部备用方案。
PostGIS:无需单独的地理服务即可进行地理查询
PostGIS 将 Postgres 转变为功能齐全的空间数据库。如果您的应用程序存储位置、路线、服务区域或任何地理数据,PostGIS 消除了对大多数后端查询单独的地图/地理服务的需求。
CREATE EXTENSION postgis;
-- 存储商业位置
CREATE TABLE businesses (
id bigserial PRIMARY KEY,
name text,
location geometry(Point, 4326) -- WGS84 坐标
);
-- 添加空间索引
CREATE INDEX businesses_location_idx
ON businesses USING GIST (location);
-- 查找距离某点 500 米内的所有商业场所
SELECT name,
ST_Distance(
location::geography,
ST_SetSRID(ST_MakePoint(-73.9847, 40.7490), 4326)::geography
) AS distance_meters
FROM businesses
WHERE ST_DWithin(
location::geography,
ST_SetSRID(ST_MakePoint(-73.9847, 40.7490), 4326)::geography,
500
)
ORDER BY distance_meters;
更复杂的空间地理操作:
-- 检查一个点是否在配送区域多边形内
SELECT z.name AS zone_name
FROM delivery_zones z
WHERE ST_Within(
ST_SetSRID(ST_MakePoint($1, $2), 4326),
z.boundary
);
pg_partman: 自动表分区
时间序列表在没有分区的情况下会无限增长。pg_partman 可以按时间或序列范围自动创建和维护分区表——这替代了许多团队使用自定义脚本或单独的时间序列数据库来解决的问题。
CREATE EXTENSION pg_partman;
-- 创建一个分区化的事件表
CREATE TABLE events (
id bigserial,
event_type text,
payload jsonb,
created_at timestamptz NOT NULL DEFAULT NOW()
) PARTITION BY RANGE (created_at);
-- 将其交给 pg_partman:创建月度分区,保留 12 个月
SELECT partman.create_parent(
p_parent_table => 'public.events',
p_control => 'created_at',
p_type => 'native',
p_interval => 'monthly',
p_premake => 3
);
-- 配置保留策略:删除超过 12 个月的分区
UPDATE partman.part_config
SET retention = '12 months',
retention_keep_table = false
WHERE parent_table = 'public.events';
-- 运行维护(使用 pg_cron 安排此任务)
SELECT partman.run_maintenance();
pg_trgm: 全文搜索和模糊匹配
在部署 Elasticsearch 或 Typesense 进行搜索之前,可以尝试 pg_trgm。它基于三字符组(trigram)实现相似度搜索,并显著加快了文本列上 LIKE/ILIKE 查询的速度。
CREATE EXTENSION pg_trgm;
-- 为快速 LIKE 查询创建 GIN 索引
CREATE INDEX products_name_trgm_idx
ON products USING GIN (name gin_trgm_ops);
-- 快速 LIKE 搜索(使用索引)
SELECT * FROM products
WHERE name ILIKE '%wireless headphones%'
ORDER BY similarity(name, 'wireless headphones') DESC
LIMIT 20;
-- 模糊搜索:处理拼写错误
SELECT name, similarity(name, 'airpods pro') AS score
FROM products
WHERE similarity(name, 'airpods pro') > 0.3
ORDER BY score DESC
LIMIT 10;
结合 Postgres 全文搜索(tsvector/tsquery),这可以覆盖大多数搜索用例,无需使用 Elasticsearch:
-- 带排序的全文搜索
SELECT name, ts_rank(search_vector, query) AS rank
FROM products, to_tsquery('english', 'wireless & headphones') query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 20;
扩展组合:真正的威力
复合优势在于这些扩展可以协同工作。单个查询可以通过 pg_cron 调度,对由 pg_partman 管理的分区表执行带有地理空间过滤的向量相似性搜索 — 所有操作都具有完整的事务一致性。
-- 查找符合口味偏好(嵌入向量)的附近餐厅
SELECT r.id, r.name,
ST_Distance(r.location::geography, $1::geography) AS distance_m,
1 - (r.cuisine_embedding <=> $2::vector) AS preference_match
FROM restaurants r
WHERE ST_DWithin(r.location::geography, $1::geography, 2000)
AND r.is_active = true
ORDER BY
(1 - (r.cuisine_embedding <=> $2::vector)) * 0.6
+ (1 - LEAST(ST_Distance(r.location::geography, $1::geography) / 2000, 1)) * 0.4
DESC
LIMIT 10;
尝试在 Pinecone + PostGIS + 你的关系型数据库中作为一个原子查询完成同样的操作。
诚实的权衡评估
Postgres 扩展并不总是正确答案。以下是诚实的分析:
- pgvector vs. Pinecone/Qdrant:在 500 万个向量以下使用 pgvector。超过这个数量,专用向量数据库在吞吐量上更胜一筹。
- pg_cron vs. Temporal/Airflow:pg_cron 适合简单的周期性 SQL 任务。需要重试和分支的复杂多步骤工作流需要专门的编排器。
- PostGIS vs. 专用地理 API:PostGIS 能出色处理后端查询。但对于路由、地图渲染或地理编码,你仍需要外部服务。
- pg_trgm vs. Elasticsearch:pg_trgm 覆盖了大多数应用的搜索需求。Elasticsearch 在大规模、分面搜索和相关性调优方面更胜一筹。
入门:审核你当前的架构
值得做的练习:列出你架构中所有非 Postgres 服务,并询问 Postgres 扩展是否能覆盖 80% 的用例。对于大多数每项服务记录数不超过 1000 万的团队来说,答案往往比预期更频繁地是肯定的。
如果你正在进行任何 AI/嵌入工作,从 pgvector 开始。添加 pg_cron 来消除你的 cron 脚本。运营的简便性 — 只需监控、备份、扩展一个数据库 — 会随时间累积。你运行的每一个服务都是你不需要的故障点。
目标不是永远在 Postgres 中运行一切。而是在你仍在学习应用实际需求的过程中,避免过早的架构复杂性。
