PostgreSQL 18 Citus 分布式集群部署指南
在 3 台 Ubuntu 22.04 服务器上基于 PostgreSQL 18 + Citus 14.2 扩展从 0 到 1 搭建 Shared-Nothing 水平分片集群(1 协调器 + 2 工作节点),覆盖扩展源安装、预加载、挂载工作节点、分布表与引用表分片及分布式执行计划实测。目标:掌握 PostgreSQL 借助 Citus 实现横向扩展的方法。
PostgreSQL 18 Citus 分布式集群部署指南
目标:在 3 台 Ubuntu 22.04 服务器上基于 PostgreSQL 18 + Citus 14.2 扩展从 0 到 1 搭建 Shared-Nothing 水平分片分布式集群(1 协调器 + 2 工作节点),覆盖 Citus 扩展源安装、shared_preload_libraries 预加载、协调器挂载工作节点、分布表/引用表分片、分布式查询计划与跨节点分片数据落盘实测。
环境信息
- 操作系统:Ubuntu 22.04.5 LTS(3 台服务器)
- 系统架构:x86_64
- PostgreSQL 版本:18.6-1.pgdg22.04+2(PostgreSQL Community Server / PGDG 源)
- Citus 扩展版本:14.2-1(Citus Data 官方 apt 源)
- 集群拓扑:1 协调器 + 2 工作节点(Shared-Nothing)
citus-node110.0.0.2 —— 协调器 Coordinatorcitus-node210.0.0.3 —— 工作节点 Worker 1citus-node310.0.0.4 —— 工作节点 Worker 2
- 数据目录:
/var/lib/postgresql/18/main - 主配置文件:
/etc/postgresql/18/main/postgresql.conf - 认证配置:
/etc/postgresql/18/main/pg_hba.conf
💡 三台服务器处于同一内网,主机名
citus-node1/2/3互相可解析,后续统一用主机名互访。Citus 对应用基本透明:你连接的是协调器citus-node1,分布式查询由它自动拆分到工作节点执行并汇总结果。
一、Citus 是什么、与流复制/逻辑复制有何不同
Citus 不是独立的数据库,而是 PostgreSQL 的一个开源扩展,把 PostgreSQL 变成可横向扩展的分布式数据库:
- Shared-Nothing 架构:数据按分片键(如主键)用一致性哈希切分成互不相交的分片,分散到多个工作节点,每个节点只存一部分数据,扩容即加节点重平衡。
- 协调器透明路由:应用只连协调器;协调器持全局元数据(
pg_dist_*),把查询下推到工作节点并行执行并汇总,对应用几乎透明(兼容 PG 协议与 SQL)。 - 适合海量数据、高并发写入(单机瓶颈被水平分片化解)。
与 PostgreSQL 另外两种集群模式的本质区别:
| 维度 | Citus(本数据分片) | 流复制 | 逻辑复制 |
|---|---|---|---|
| 架构 | Shared-Nothing 分片 | 主备 WAL 复制 | 发布/订阅 |
| 数据关系 | 各节点分片互不相交(并集=全量) | 从库=主库完整物理副本 | 订阅端=发布端部分/全表副本 |
| 核心价值 | 横向扩展存储与计算 | 高可用·读写分离·容灾 | 跨版本·按需同步 |
| 多表关联 | 需按分片键 co-location | 任意 | 目标端自行维护 |
| 典型场景 | 海量高并发 OLTP/OLAP 扩展 | 基础高可用、备份 | 跨版本升级、数据汇集 |
二、三台服务器安装 PostgreSQL 18 + Citus 扩展(从 0 到 1)
三台服务器步骤相同,都应装 PG 服务器与 Citus 扩展(工作节点同样需要扩展)。以下以 citus-node1 为例,另两台执行一致操作。
步骤 1. 更新源并安装前置工具
在 citus-node1 上执行:
sudo apt-get update
sudo apt-get install -y curl ca-certificates gnupg
步骤 2. 添加 PostgreSQL 官方源(PGDG)
在 citus-node1 上执行:
sudo install -d /usr/share/postgresql-common/pgdg
sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc
sudo sh -c 'echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
sudo apt-get update
步骤 3. 添加 Citus Data 官方源
Citus 扩展在独立的 Citus 官方 apt 源中,用官方脚本自动添加仓库与签名密钥。在 citus-node1 上执行:
curl -fsSL https://install.citusdata.com/community/deb.sh | sudo bash
执行结果(关键行):
Installing /etc/apt/sources.list.d/citusdata_community.list... done.
Importing Citus Data Community gpg key... done.
Running apt-get update... done.
The repository is set up! You can now install packages.
验证可用扩展包(可选,在 citus-node1 上执行):
apt-cache policy postgresql-18-citus-14.2
执行结果(关键行):
postgresql-18-citus-14.2:
Installed: (none)
Candidate: 14.2-1
步骤 4. 安装 PostgreSQL 18 与 Citus 扩展
在 citus-node1 上执行:
sudo apt-get install -y postgresql-18 postgresql-18-citus-14.2
💡
postgresql-18-citus-14.2把 Citus 扩展(citus.so、SQL 脚本等)装入/usr/lib/postgresql/18/lib/。装完同样会自动initdb初始化18/main集群。
步骤 5. 配置 shared_preload_libraries 预加载 citus
Citus 要求把扩展加入 shared_preload_libraries(需重启生效)。编辑 /etc/postgresql/18/main/postgresql.conf,三台都执行:
sudo sed -i "s/^#shared_preload_libraries.*/shared_preload_libraries = 'citus'/" /etc/postgresql/18/main/postgresql.conf
# 若文件尚未存在该行,则追加
echo "shared_preload_libraries = 'citus'" | sudo tee -a /etc/postgresql/18/main/postgresql.conf
放行内网互信(追加到 pg_hba.conf,三台都执行):
sudo tee -a /etc/postgresql/18/main/pg_hba.conf <<'EOF'
# Citus cluster peer net
host all all 10.0.0.0/16 trust
host all all 127.0.0.1/32 trust
EOF
启动并验证预加载(citus-node1/2/3 各自执行):
sudo pg_ctlcluster 18 main start
pg_lsclusters
sudo -u postgres psql -tAc "SHOW shared_preload_libraries;"
执行结果(三台一致):
Ver Cluster Port Status Owner Data directory Log file
18 main 5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log
citus
步骤 6. 三节点创建 Citus 扩展
Citus 集群里,协调器与每个工作节点都要在目标数据库创建扩展。在 citus-node1 / citus-node2 / citus-node3 各自执行:
sudo -u postgres psql -c "CREATE EXTENSION citus;"
验证版(任选一节点执行):
sudo -u postgres psql -c "SELECT extname, extversion FROM pg_extension WHERE extname = 'citus';"
执行结果:
CREATE EXTENSION
extname | extversion
---------+------------
citus | 14.2-1
(1 row)
三、协调器挂载工作节点,组成分布式集群
只需在协调器上执行,把两个工作节点注册进集群元数据。
步骤 7. 添加工作节点
在 citus-node1 上执行:
sudo -u postgres psql -c "SELECT * FROM citus_add_node('citus-node2', 5432);"
sudo -u postgres psql -c "SELECT * FROM citus_add_node('citus-node3', 5432);"
执行结果:
citus_add_node
----------------
1
(1 row)
citus_add_node
----------------
2
(1 row)
💡 每次返回的新
groupid(1、2)即每个工作节点的内部编号,协调器据此管理分片归属。
步骤 8. 确认活动工作节点
在 citus-node1 上执行:
sudo -u postgres psql -c "SELECT * FROM citus_get_active_worker_nodes();"
执行结果:
node_name | node_port
-------------+-----------
citus-node3 | 5432
citus-node2 | 5432
(2 rows)
两个工作节点已注册可见,分布式集群成型。
四、分布表:水平分片实测
步骤 9. 设置分片数与副本数,创建分布表
在 citus-node1 上执行(设置在分布式表元数据所在的数据库,即默认 postgres 库):
sudo -u postgres psql -d postgres -c "SET citus.shard_count = 4; SET citus.shard_replication_factor = 1;"
sudo -u postgres psql -d postgres -c "CREATE TABLE orders (id bigint primary key, customer text, amount numeric(10,2), created timestamptz default now());"
按 id 做哈希分片,把表变成分布式表(在 citus-node1 上执行):
sudo -u postgres psql -d postgres -c "SELECT create_distributed_table('orders', 'id', 'hash');"
执行结果:
create_distributed_table
--------------------------
(1 row)
💡
shard_count=4把表切成 4 个分片,平均分布到 2 个工作节点;shard_replication_factor=1每分片 1 份副本(无冗余)。Citus 会先在协调器建普通表,再按分片键把行路由到不同工作节点的分片表orders_<id>。
步骤 10. 写入数据并验证协调器汇总
在 citus-node1 上执行:
sudo -u postgres psql -d postgres -c "INSERT INTO orders VALUES (1,'alice',100),(2,'bob',250.5),(3,'carol',79.9),(4,'dave',1200),(5,'eve',33.33),(6,'frank',66.6),(7,'grace',77.7),(8,'heidi',88.8);"
sudo -u postgres psql -d postgres -c "SELECT count(*) FROM orders;"
执行结果:
INSERT 0 8
count
-------
8
(1 row)
💡 应用只连接协调器,
count(*)会被 Citus 下推到每个分片并行统计再汇总,返回 8。
步骤 11. 查看分片在工作节点的分布(Shared-Nothing 证据)
在 citus-node1 上执行,查看 4 个分片各自落在哪个工作节点:
sudo -u postgres psql -d postgres -c "SELECT s.shardid, n.nodename AS worker, n.groupid FROM pg_dist_shard s JOIN pg_dist_placement p ON p.shardid=s.shardid AND p.shardstate=1 JOIN pg_dist_node n USING (groupid) WHERE s.logicalrelid='orders'::regclass ORDER BY s.shardid;"
执行结果:
shardid | worker | groupid
---------+-------------+---------
102008 | citus-node2 | 1
102009 | citus-node3 | 2
102010 | citus-node2 | 1
102011 | citus-node3 | 2
(4 rows)
4 个分片两两分配到 2 个工作节点,无冗余(每分片仅 1 份)——数据被切分而非复制,这就是 Shared-Nothing 水平分片。
步骤 12. 到工作节点直接验证分片表与行数
分片表是物理存在于各工作节点 postgres 库中的真实表。citus-node2 上查看并计数:
sudo -u postgres psql -d postgres -tAc "SELECT count(*) FROM orders_102008;"
sudo -u postgres psql -d postgres -tAc "SELECT count(*) FROM orders_102010;"
执行结果(citus-node2):
3
1
citus-node3 上执行:
sudo -u postgres psql -d postgres -tAc "SELECT count(*) FROM orders_102009;"
sudo -u postgres psql -d postgres -tAc "SELECT count(*) FROM orders_102011;"
执行结果(citus-node3):
3
1
分片行数 3+1(node2)+3+1(node3)= 8,与协调器总数一致——数据按哈希均摊到各 worker,且被路由写入的不是同一份副本。
步骤 13. 查看分布式执行计划
在 citus-node1 上执行,观察协调器如何拆分任务:
sudo -u postgres psql -d postgres -c "EXPLAIN (COSTS OFF) SELECT count(*) FROM orders;"
执行结果:
QUERY PLAN
----------------------------------------------------------------
Aggregate
-> Custom Scan (Citus Adaptive)
Task Count: 4
Tasks Shown: One of 4
-> Task
Node: host=citus-node2 port=5432 dbname=postgres
-> Aggregate
-> Seq Scan on orders_102008 orders
(8 rows)
💡 Citus 把一条 SQL 拆成 4 个 Task 下推给各工作节点的分片(如上把对
orders_102008的聚合推到 citus-node2),再在协调器汇总——这就是分布式并行执行。
五、引用表(Reference Table):全库广播复制
引用表是另一类 Citus 表:整张表在每个工作节点上各存一份完整副本,适合与分布表按分片键无关的小维表做关联,避免跨节点数据迁移。
步骤 14. 创建引用表
在 citus-node1 上执行:
sudo -u postgres psql -d postgres -c "CREATE TABLE regions (region_id int primary key, region text not null);"
sudo -u postgres psql -d postgres -c "INSERT INTO regions VALUES (1,'East'),(2,'West');"
sudo -u postgres psql -d postgres -c "SELECT create_reference_table('regions');"
执行结果:
NOTICE: Copying data from local table...
NOTICE: copying the data has completed
create_reference_table
------------------------
(1 row)
步骤 15. 确认引用表分片广播到全部工作节点
在 citus-node1 上执行:
sudo -u postgres psql -d postgres -c "SELECT s.shardid, n.nodename FROM pg_dist_shard s JOIN pg_dist_placement p ON p.shardid=s.shardid AND p.shardstate=1 JOIN pg_dist_node n USING (groupid) WHERE s.logicalrelid='regions'::regclass ORDER BY s.shardid;"
执行结果:
shardid | worker
---------+-------------
102012 | citus-node2
102012 | citus-node3
(2 rows)
💡 同一 shardid
102012同时出现在 citus-node2 和 citus-node3——这就是引用表"广播复制到每个工作节点"的特征(分布表则每个分片只落在单一 worker)。
六、常见问题
Q1:create_distributed_table 报 "function does not exist"
原因:目标数据库未创建 citus 扩展(CREATE DATABASE 不会被 Citus 传播到其他节点)。解决:在要做分布表的数据库上先 CREATE EXTENSION citus;,再建分布表;协调器与工作节点都要在该库建扩展。
Q2:加了节点后数据没自动均衡
新节点的数据不自动迁移。用 SELECT rebalance_table_shards(); 手工触发分片重平衡;分布表可通过 alter_distributed_table 调整分片数/副本数(shard_count、shard_replication_factor)。
Q3:分布表能 join 普通大表吗
跨节点 join 代价高。最佳实践:大表都用同一个分片键(同列)建立分布表实现 co-location(同置),小维表用引用表广播,让 join 都在本地完成。
Q4:Citus 需要 3 个节点吗
最少 1 协调器 + 1 工作节点即可运行;此处 2 个工作节点用于演示分片分布与故障冗余扩展。更多数据/更大并发加节点后用 rebalance_table_shards() 扩容。
Q5:Citus 有高可用/副本吗
有。citus.shard_replication_factor 设为 2 可让每个分片保留 2 份副本(分散在不同节点);更完整的 HA 通常叠加在 Citus 之上,工作节点本身再做流复制,或配合 PostgreSQL 分布式高可用方案。