PostgreSQL 能否一统数据系统:从 Kafka、Elasticsearch 到 Redis 的边界分析

2026-08-20 38 预计阅读时间: 1 分钟
来源: oschina.net 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.

预计阅读时间:12 分钟

“PostgreSQL for Everything”每隔一段时间就会重新引发讨论。原因并不难理解:PostgreSQL 的核心能力很完整,扩展生态也越来越丰富,JSONB、全文搜索、逻辑复制、LISTEN/NOTIFY、分区表和丰富的索引类型,让它能够覆盖许多原本需要专用系统的场景。

但“能做”不等于“应该替代”。PostgreSQL 更像一把能力很宽的多用途工具,而 Kafka、Elasticsearch、Redis、MongoDB 和 ClickHouse 仍然在各自的性能模型、扩展方式和运维边界上占据优势。真正值得讨论的问题不是“PG 能不能替代它们”,而是:在什么规模和约束下,单一 PostgreSQL 能让系统更简单?什么时候专用系统的成本是合理的?

PG 为什么会覆盖这么多场景

PostgreSQL 的优势不只是 SQL。它把事务、约束、索引、JSON 文档、全文检索、窗口函数、物化视图、分区和复制放在了同一个数据平台里。开发团队可以用一套权限模型、一套备份策略和一套监控体系,处理关系数据、半结构化数据和一部分搜索需求。

几个能力尤其容易造成“一个数据库替代多个系统”的效果:

  • JSONB 支持文档式数据,同时保留事务、外键和 SQL 查询能力。
  • GINGiSTpg_trgm 和内置全文搜索能够覆盖许多站内搜索场景。
  • LISTEN/NOTIFY 适合轻量级进程通知,不需要额外部署消息组件。
  • 表分区、逻辑复制和并发控制可以支撑不少事件记录、任务队列和审计场景。
  • 扩展机制允许团队按需增加地理数据、向量搜索或其他领域能力。

这些能力降低了系统数量,也降低了跨系统同步、数据一致性和权限管理的复杂度。不过,所有能力都共享同一套 PostgreSQL 资源。写入高峰、复杂搜索和大规模分析如果同时发生,可能互相争抢 CPU、内存、磁盘 I/O 和连接数。

四类替代关系要分开看

Kafka:可以承接事件,但不是天然的日志平台

PostgreSQL 表可以存储业务事件,事务提交时还能保证“业务数据和事件记录一起成功”。通过 Outbox 模式,应用先把事件写入表,再由消费者批量读取并发布到下游,这在中小规模系统中很实用。

但 Kafka 的核心优势是高吞吐追加日志、分区扩展、消费者组和较长时间的事件保留。PostgreSQL 的表锁竞争、索引维护、WAL 压力和 vacuum 成本,会让它在大量独立消费者、持续高写入和长时间回放场景下变得不合适。

结论是:PG 可以替代简单消息队列或事件中转层,但不应默认替代高吞吐事件流平台。

Elasticsearch:适合产品级搜索,不等于搜索集群

PostgreSQL 内置全文搜索和 pg_trgm 对标题、正文、模糊匹配、过滤和排序已经够用。数据量可控、搜索字段稳定、要求事务内可见的业务系统,直接在 PG 中搜索通常更简单。

Elasticsearch 更适合复杂相关性排序、分词分析、多字段检索、聚合、搜索集群横向扩展,以及对搜索索引和事务库进行明确隔离的架构。如果搜索体验是产品核心,或者需要持续承受大量搜索请求,PG 的搜索能力不应被包装成 Elasticsearch 的完全替代品。

Redis:缓存和极低延迟仍然是不同问题

PG 可以通过物化视图、合适的索引和连接池减少很多缓存需求,也可以用表实现可靠任务队列。但 Redis 的内存数据结构、单线程事件循环、极低延迟和过期策略,是 PostgreSQL 不会自然提供的组合。

如果缓存命中率、毫秒以下延迟、排行榜、计数器、分布式锁或短生命周期状态是关键指标,Redis 仍然更合适。把所有临时数据写进 PG,可能让主库承受大量本可丢弃的读写流量。

MongoDB:JSONB 很强,但文档模型并未消失

JSONB 允许团队渐进式存储半结构化数据,并且可以通过表达式索引和 GIN 索引查询内部字段。对“多数字段稳定、少数字段灵活”的业务,PG 往往比单独引入文档数据库更省事。

如果数据天然是聚合文档,写入结构变化频繁,查询主要围绕整篇文档展开,或者需要围绕文档模型进行独立扩展,MongoDB 仍有其合理位置。JSONB 不是免费午餐:过度依赖无约束文档会削弱数据质量,更新大文档也可能放大写入和膨胀成本。

一个可实践的 PostgreSQL 方案

下面的示例假设使用 PostgreSQL 14 或更高版本。它把订单、JSONB 扩展字段、全文搜索和 Outbox 事件放在同一个数据库中。代码可以直接在测试数据库执行;生产环境还需要补充权限、分区、清理策略和监控。

CREATE EXTENSION IF NOT EXISTS pg_trgm;

CREATE TABLE orders (
    id          BIGSERIAL PRIMARY KEY,
    customer_id BIGINT NOT NULL,
    status      TEXT NOT NULL CHECK (status IN ('pending', 'paid', 'cancelled')),
    details     JSONB NOT NULL DEFAULT '{}'::jsonb,
    description TEXT NOT NULL DEFAULT '',
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX orders_details_gin_idx
    ON orders USING GIN (details);

CREATE INDEX orders_description_search_idx
    ON orders USING GIN (to_tsvector('simple', description));

CREATE INDEX orders_sku_trgm_idx
    ON orders USING GIN ((details->>'sku') gin_trgm_ops);

CREATE TABLE outbox_events (
    id           BIGSERIAL PRIMARY KEY,
    aggregate_id BIGINT NOT NULL,
    event_type   TEXT NOT NULL,
    payload      JSONB NOT NULL,
    created_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
    published_at TIMESTAMPTZ
);

BEGIN;

INSERT INTO orders (customer_id, status, details, description)
VALUES (
    42,
    'paid',
    '{"sku": "KB-100", "channel": "web"}',
    'PostgreSQL operations handbook'
)
RETURNING id;

INSERT INTO outbox_events (aggregate_id, event_type, payload)
VALUES (
    currval('orders_id_seq'),
    'order.paid',
    '{"customer_id": 42, "sku": "KB-100"}'
);

COMMIT;

-- 消费者批量领取未发布事件,避免多个消费者重复处理
WITH batch AS (
    SELECT id
    FROM outbox_events
    WHERE published_at IS NULL
    ORDER BY id
    FOR UPDATE SKIP LOCKED
    LIMIT 100
)
UPDATE outbox_events AS e
SET published_at = now()
FROM batch
WHERE e.id = batch.id
RETURNING e.id, e.event_type, e.payload;

这个方案适合把数据库事务和领域事件绑定在一起。消费者真正发布到外部系统时,应采用“读取、发布、确认”或幂等处理策略,而不是在数据库事务里假设网络调用必然成功。上面的 published_at 标记也只是最小示例,生产实现通常会增加重试次数、错误信息、租约或独立的发布状态。

搜索则可以这样写:

SELECT id, customer_id, status, description
FROM orders
WHERE to_tsvector('simple', description)
      @@ plainto_tsquery('simple', 'PostgreSQL')
  AND details @> '{"channel": "web"}'::jsonb
ORDER BY created_at DESC
LIMIT 20;

这里的关键不是把所有专用系统都删除,而是把低复杂度、强一致和中等规模的需求收拢到一个数据边界内。

判断是否应该引入专用系统

可以用下面几组问题做架构决策:

  1. 数据是否必须和核心业务事务原子提交?如果是,优先考虑留在 PG,或采用 Outbox 与专用系统解耦。
  2. 工作负载是否有明显的单一特征?高吞吐追加日志、极低延迟缓存、复杂搜索和大规模列式分析,通常更适合专用系统。
  3. 峰值流量是否会影响主库?不要只看平均 QPS,要评估峰值写入、索引维护、备份、vacuum 和复制延迟。
  4. 团队是否有能力维护第二套系统?专用系统带来更好的局部性能,也带来容量规划、数据同步、故障恢复和权限治理成本。
  5. 失败时能否接受数据延迟或重复?搜索索引和缓存通常可以异步重建,订单、支付和审计数据则不能采用同样的容错假设。

结论:减少系统数量,而不是追求系统数量为一

PostgreSQL 的合理定位是“功能覆盖面很宽的主数据平台”。它可以替代许多低规模、低复杂度的独立组件,也可以成为事件、搜索和半结构化数据的起点。但它并不会自动拥有 Kafka 的事件流吞吐、Redis 的延迟、Elasticsearch 的搜索集群能力或 ClickHouse 的列式分析效率。

更稳妥的落地方式是:先用 PG 解决有明确事务边界的核心数据,测量真实的读写、延迟、数据量和故障恢复需求;当某个工作负载形成清晰瓶颈,再为它引入专用系统。这样得到的是可验证的演进,而不是围绕“万能数据库”口号做架构迁移。


相关推荐