初探 Citus 並實現多租戶
Citus 是一個100%開源的 PostgreSQL 擴充功能,能橫向擴展(scale-out) PostgreSQL。 你可以從單一節點開始,逐步擴展成分散式叢集。 因為 Citus 是擴充功能(非分支),會保留原生的 PostgreSQL…
初探 Citus 並實現多租戶

Citus 是一個100%開源的 PostgreSQL 擴充功能,能橫向擴展(scale-out) PostgreSQL。 你可以從單一節點開始,逐步擴展成分散式叢集。 因為 Citus 是擴充功能(非分支),會保留原生的 PostgreSQL 相容性、功能、生態系統工具和擴充功能。
主要功能:
- 分散式資料表(基於row或schema的分片)
- 維度與共享資料的參考表
- 共置聯結與分散式查詢規劃器
- 跨worker的平行查詢執行
- 柱狀儲存選項
- 來自任意節點的查詢(自 Citus 11.0+)
架構如下圖

協調者(Coordinator)對於每個查詢,要麼選擇以下其中之一:
- 將其路由至單一worker(當所有所需資料皆位於該worker時),或
- 在worker之間將其平行化 (當資料跨分區時)
Citus 支援兩種分片(row base、schema base)模型,每種都有不同的取捨

Row base簡單說就是所有租戶共用相同資料表(如Order),但透過 tenant_id 做分片,citus會依tenant_id做hash,將同一租戶資料放在相同worker,這可避免掃全叢集,稱為Query Locality(查詢區域性),大規模SaaS都採該模式為主流,但缺點就是租戶不能客製schema。

Schema base簡單說每個租戶有自己的 schema(非共用相同資料表),每租戶有mini database但底層相同為PostgreSQL cluster,主要缺點是migration麻煩、metadata可能爆炸(租戶數量*資料表數量)。

資料表類型如下圖

共置(Colocation)
將相關資料表的分片放在一起(相同的雜湊映射)以便進行本地連接,可減少跨節點資料移動,如果分片規則不同,就需要跨節點搬資料(Shuffle Data),大量的資料交換就是Repartition Join,這也是使用分散式資料庫最高成本操作(大部分瓶頸都是network I/O),一定要盡力避免(小表可善用reference table)。
查詢平行性與執行流程
協調器會將多分片查詢分解成每個分片的任務,優先下放Query至worker(非拉資料回來至協調器處理)。Citus效能處理是優先避免跨節點,而非最大化平行處理,任務執行嘗試平衡:
- 並行 (每個工作者的平行連線數)
- 連線負荷 (緩慢啟動提升)
- 工作者資源節省 (閒置連線上限)

簡單理解,協調器會將SQL拆解多個小任務,並分配下放到相對應worker執行,同時盡量避免跨節點進行資料搬移,四大原則如下圖

下面我參考 Docker (Mac or Linux) 快速建立 Citus cluster並簡單使用ASP.NET Core MVC測試多租戶呈現不同portal
curl -L https://raw.githubusercontent.com/citusdata/docker/master/docker-compose.yml > docker-compose.yml
COMPOSE_PROJECT_NAME=citus POSTGRES_PASSWORD=root docker compose up -d
# scale-out worker
docker compose up -d --scale worker=2
DROP TABLE questions CASCADE;
DROP TABLE tenants CASCADE;
CREATE TABLE tenants (
id uuid NOT NULL,
domain text NOT NULL,
name text NOT NULL,
description text NOT NULL,
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL
);
CREATE TABLE questions (
id uuid NOT NULL,
tenant_id uuid NOT NULL,
title text NOT NULL,
votes int NOT NULL,
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL
);
ALTER TABLE tenants ADD PRIMARY KEY (id);
-- 要加入 tenant_id,Unique要在shard成立
ALTER TABLE questions ADD PRIMARY KEY (id, tenant_id);
-- Citus 使用租戶ID 進行分片 row base
SELECT create_distributed_table('tenants', 'id');
SELECT create_distributed_table('questions', 'tenant_id');
-- 驗證partmethod是否為h : Hash Distributed ,確認為分散式資料表
-- n : Reference Table
SELECT
logicalrelid::regclass AS table_name,
partmethod,
partkey
FROM pg_dist_partition;
-- 也可透過citus_tables確認
-- 規模= 小型 shard數:32 ,中型 shard數:64, 大型 shard數:128~256
SELECT *
FROM citus_tables;
-- 確認所有worker nodes
SELECT * FROM citus_get_active_worker_nodes();
-- Rebalance Shards(告訴citues 自動搬 shard),如果後面有新增work,PRD建議離峰做
-- shard size 建議 1 ~ 10 GB,過大容易形成災難
SELECT rebalance_table_shards();
-- Rebalance 策略
SELECT *
FROM pg_dist_rebalance_strategy;
-- 查shard, hash range
SELECT
shardid,
logicalrelid::regclass,
shardminvalue,
shardmaxvalue
FROM pg_dist_shard;
-- 查看 shard 在哪個 worker
SELECT
p.logicalrelid::regclass AS table_name,
s.shardid,
n.nodename,
n.nodeport
FROM pg_dist_shard s
JOIN pg_dist_placement dp
ON s.shardid = dp.shardid
JOIN pg_dist_node n
ON dp.groupid = n.groupid
JOIN pg_dist_partition p
ON s.logicalrelid = p.logicalrelid
ORDER BY table_name, shardid;
-- fake data
INSERT INTO tenants VALUES (
'c620f7ec-6b49-41e0-9913-08cfe81199af',
'rico.local',
'rico test',
'Ask anything code-related!',
now(),
now());
INSERT INTO tenants VALUES (
'b8a83a82-bb41-4bb3-bfaa-e923faab2ca4',
'fifi.local',
'Database Questions',
'Figure out why your connection string is broken.',
now(),
now());
INSERT INTO questions VALUES (
'347b7041-b421-4dc9-9e10-c64b8847fedf',
'c620f7ec-6b49-41e0-9913-08cfe81199af',
'How do you build apps in ASP.NET Core?',
1,
now(),
now());
INSERT INTO questions VALUES (
'a47ffcd2-635a-496e-8c65-c1cab53702a7',
'b8a83a82-bb41-4bb3-bfaa-e923faab2ca4',
'Using postgresql for multitenant data?',
2,
now(),
now());
Demo:
[embed]
✨VS Code x GitHub Copilot — AI 企業級協作開發實戰與應用

reference:
[embed]
메타데이터
- post_id
- b6967cf69737
- slug
- 初探-citus-並實現多租戶-b6967cf69737
- url
- https://medium.com/ricos-note/%E5%88%9D%E6%8E%A2-citus-%E4%B8%A6%E5%AF%A6%E7%8F%BE%E5%A4%9A%E7%A7%9F%E6%88%B6-b6967cf69737
- canonical_url
- https://medium.com/ricos-note/%E5%88%9D%E6%8E%A2-citus-%E4%B8%A6%E5%AF%A6%E7%8F%BE%E5%A4%9A%E7%A7%9F%E6%88%B6-b6967cf69737
- author_url
- https://medium.com/@iamrico1
- status
- ok
- fetched_at
- 2026-07-12 01:39:19