PostgreSQL + Apache Doris 构建 HTAP 架构,让事务和分析各自发挥所长。本教程从零开始,带你搭建一套完整的实时分析链路:PostgreSQL 负责事务写入,Apache Doris 负责分析查询,全程含可跑通的代码。
关键词:Apache Doris · PostgreSQL · HTAP · 实时分析 · Flink CDC · MOW 引擎 · 数据同步 · 存算分离
前言:为什么 PostgreSQL 的分析查询越来越慢?
很多团队的工作流是这样的:业务数据写进 PostgreSQL,BI 报表直接查 PostgreSQL。一开始还行,但随着数据量上来,慢查询让 DBA 头疼不已。PostgreSQL 本身的 MVCC 机制会积累大量 Dead Tuples,即使是简单的 COUNT 查询也要扫描大量冗余数据。
那能不能把分析任务从 PostgreSQL 剥离出来?答案就是 HTAP 架构:PostgreSQL 专管事务,Apache Doris 专管分析,两者通过实时同步打通。
这套教程会带你从零搭建整个链路。
第 1 步:环境准备
你至少需要以下组件:
| 组件 | 版本要求 | 用途 |
|---|---|---|
| PostgreSQL | 12+ | 事务数据库(写入端) |
| Apache Doris | 2.1+ | 分析数据库(读取端) |
| Flink | 1.18+ | CDC 数据同步 |
| Flink CDC Connector | 3.0+ | PostgreSQL → Doris 实时同步 |
以下假设 PostgreSQL 和 Doris 均已启动并能正常连接。
第 2 步:PostgreSQL 开启 WAL 日志
Flink CDC 通过读取 PostgreSQL 的 WAL(Write-Ahead Log)来捕获增量变更,所以需要先开启逻辑复制:
sql
-- 1. 修改 postgresql.conf,重启生效
-- wal_level = logical
-- max_wal_senders = 4
-- max_replication_slots = 4
-- 2. 创建复制用户
CREATE ROLE flink_cdc WITH LOGIN PASSWORD 'your_password' REPLICATION;
GRANT CONNECT ON DATABASE mydb TO flink_cdc;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO flink_cdc;
-- 3. 创建发布(Publication),选择需要同步的表
CREATE PUBLICATION doris_pub FOR TABLE orders, users, products;
关键点 :wal_level = logical 是必须的,否则 Flink CDC 无法读取变更数据。
第 3 步:在 Apache Doris 中创建目标表
在 Doris 中创建与 PostgreSQL 对应的分析表。这里以订单表为例:
sql
-- 创建 Doris 数据库
CREATE DATABASE IF NOT EXISTS htap_demo;
-- 订单明细表,使用 MOW(Merge-On-Write)引擎
CREATE TABLE IF NOT EXISTS htap_demo.orders (
order_id BIGINT, -- 订单 ID
user_id BIGINT, -- 用户 ID
product_id BIGINT, -- 商品 ID
order_amount DECIMAL(18, 2), -- 订单金额
order_status VARCHAR(32), -- 订单状态:pending/shipped/completed
create_time DATETIME, -- 创建时间
update_time DATETIME -- 更新时间
) ENGINE = OLAP
UNIQUE KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 10
PROPERTIES (
"replication_allocation" = "tag.location.default: 1",
"enable_unique_key_merge_on_write" = "true" -- MOW 引擎,支持实时更新
);
-- 用户表
CREATE TABLE IF NOT EXISTS htap_demo.users (
user_id BIGINT,
user_name VARCHAR(64),
city VARCHAR(64),
create_time DATETIME
) ENGINE = OLAP
UNIQUE KEY(user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 8
PROPERTIES (
"replication_allocation" = "tag.location.default: 1",
"enable_unique_key_merge_on_write" = "true"
);
为什么用 MOW(Merge-On-Write)引擎?
- 更新数据秒级可见,无需等后台 Compaction
- 写入时即完成合并和排序,查询时无需临时计算
- 支持完整的 DML 操作:INSERT / DELETE / UPDATE / UPSERT
第 4 步:配置 Flink CDC 同步任务
这是整条链路的核心。Flink CDC 监听 PostgreSQL 的 WAL 变更,实时写入 Doris。
sql
-- Flink SQL 任务:PostgreSQL → Doris 实时同步
-- 提交到 Flink 集群运行
-- (1) 注册 PostgreSQL CDC 源表
CREATE TABLE pg_orders_source (
order_id BIGINT,
user_id BIGINT,
product_id BIGINT,
order_amount DECIMAL(18, 2),
order_status VARCHAR(32),
create_time TIMESTAMP,
update_time TIMESTAMP,
PRIMARY KEY(order_id) NOT ENFORCED
) WITH (
'connector' = 'postgres-cdc',
'hostname' = 'your-pg-host',
'port' = '5432',
'username' = 'flink_cdc',
'password' = 'your_password',
'database-name' = 'mydb',
'schema-name' = 'public',
'table-name' = 'orders',
'slot.name' = 'doris_orders_slot',
'decoding.plugin.name' = 'pgoutput'
);
-- (2) 注册 Doris Sink 表
CREATE TABLE doris_orders_sink (
order_id BIGINT,
user_id BIGINT,
product_id BIGINT,
order_amount DECIMAL(18, 2),
order_status VARCHAR(32),
create_time TIMESTAMP,
update_time TIMESTAMP
) WITH (
'connector' = 'doris',
'fenodes' = 'your-doris-fe:8030',
'table.identifier' = 'htap_demo.orders',
'username' = 'root',
'password' = '',
'sink.properties.format' = 'json',
'sink.properties.read_json_by_line' = 'true',
'sink.enable-delete' = 'true' -- 开启 DELETE 同步
);
-- (3) 执行同步
INSERT INTO doris_orders_sink SELECT * FROM pg_orders_source;
增量同步策略建议:
| 数据类型 | 同步策略 | 延迟 |
|---|---|---|
| 交易订单、状态变更 | 实时 CDC | ≤ 10 秒 |
| 用户基础信息 | 定时增量(5 分钟) | ≤ 5 分钟 |
| 全量初始化 | DataX / 一次性同步 | 一次性 |
第 5 步:验证同步效果
同步启动后,在 PostgreSQL 写入一条数据,几秒钟后在 Doris 中查询验证:
sql
-- PostgreSQL 写入
INSERT INTO orders VALUES (1001, 2023, 5001, 199.00, 'pending', NOW(), NOW());
-- Doris 中查询(延迟 ≤ 10 秒即可查到)
SELECT * FROM htap_demo.orders WHERE order_id = 1001;
再试一条 UPDATE:
sql
-- PostgreSQL 更新状态
UPDATE orders SET order_status = 'shipped', update_time = NOW() WHERE order_id = 1001;
-- Doris 中验证(MOW 引擎秒级可见)
SELECT order_id, order_status, update_time
FROM htap_demo.orders WHERE order_id = 1001;
-- 预期:order_status 变为 'shipped'
第 6 步:在 Doris 中执行分析查询
数据同步就绪后,分析查询全部交给 Doris:
sql
-- 1. 实时运营看板:各城市近 1 小时订单量
SELECT
u.city,
COUNT(o.order_id) AS order_count,
SUM(o.order_amount) AS total_amount
FROM htap_demo.orders o
JOIN htap_demo.users u ON o.user_id = u.user_id
WHERE o.create_time > NOW() - INTERVAL 1 HOUR
GROUP BY u.city
ORDER BY total_amount DESC;
-- 2. 多维分析:各商品近 7 天每日销量趋势
SELECT
o.product_id,
DATE_TRUNC('day', o.create_time) AS dt,
COUNT(*) AS order_cnt,
AVG(o.order_amount) AS avg_amount
FROM htap_demo.orders o
WHERE o.create_time > NOW() - INTERVAL 7 DAY
GROUP BY o.product_id, dt
ORDER BY dt, order_cnt DESC;
这些查询原本在 PostgreSQL 中可能会消耗大量 CPU 并影响事务性能,现在完全由 Doris 承接。
第 7 步:存储优化 ------ 让热数据快、冷数据省
Doris 支持分层存储策略:
sql
-- 存算分离模式:冷数据自动沉降到对象存储
ALTER TABLE htap_demo.orders
SET (
"storage_policy" = "hot_cold_policy",
"storage_cooldown_time" = "2026-01-01 00:00:00" -- 该时间之前的数据沉降
);
-- 存算一体模式:冷数据迁移到 HDD
ALTER TABLE htap_demo.orders
MODIFY PARTITION p202501 SET ("storage_medium" = "HDD");
核心思路:热数据吃 SSD,冷数据吃廉价存储。在 TB 级日志或历史订单场景中,存储成本可以显著降低。
架构总结
lua
PostgreSQL(TP 事务) Apache Doris(AP 分析)
| |
写入 & 更新 只读分析
| |
+--- Flink CDC ---> 实时同步 ---+
延迟 ≤ 10s
- PostgreSQL:只干事务的活,CPU 和内存不再被分析查询挤占
- Apache Doris:向量化引擎 + MOW 更新 + 分层存储,查询快到亚秒级
- Flink CDC:无侵入式监听 WAL,不增加 PG 的写入负担
常见问题
Q:同步延迟能控制在多少? A:交易类数据通常 ≤ 10 秒;低频数据(如用户资料)可配置 5 分钟定时增量。
Q:MOW 引擎和普通 Unique Key 有什么区别? A:MOW 在写入时即完成合并,更新秒级可见;普通 Unique Key 依赖后台 Compaction,查询时需要合并增量数据,延迟更高。
Q:生产环境推荐怎么规划资源? A:PostgreSQL 保持原有规格不变;Doris 3 节点起(FE x1 + BE x3),根据分析查询量线性扩容 BE 节点。
扩展阅读
- Apache Doris 官方文档 --- MOW 表引擎
- SelectDB --- 存算分离架构详解
- Flink CDC PostgreSQL Connector
- 完整案例:森马服饰从 ES 到阿里云 SelectDB 的演进
关于 Apache Doris :Apache Doris 是高性能实时分析数据库,支持 PB 级数据亚秒级查询,广泛应用于报表分析、Ad-hoc 查询、统一数仓等场景。SelectDB 是 Apache Doris 的商业化公司,提供企业级支持和云服务。欢迎加入 Doris 社区 交流更多实践。