pg_durable 深度教學 — PostgreSQL 內建持久化執行引擎
§1 專案定位與解決的問題
1.1 pg_durable 是什麼
pg_durable 是 Microsoft 推出的 PostgreSQL 擴充套件(extension),以 Rust 語言(透過 pgrx 框架)實作,將 durable execution(持久化執行) 模式直接嵌入 PostgreSQL 資料庫內部。
durable execution 已是業界標準模式(Temporal、Azure Durable Functions、AWS Step Functions 皆為此類),pg_durable 的獨特價值在於:不需要任何外部服務——沒有 Redis、沒有 message queue、沒有獨立的 orchestrator——所有 workflow 狀態直接存在 PostgreSQL 內部表中。
1.2 它解決什麼痛點
目前許多團隊處理長時間背景工作的方式:
- pg_cron + jobs table + status columns + polling worker:需要自己管理重試、部分完成、狀態追蹤
- 外部 orchestrator(Airflow / Temporal / Step Functions)回呼 Postgres:增加基礎設施複雜度
- Queue + workers + 獨立 state table:retry 和 partial failure 邏輯散落各處
- plpgsql procedure:crash 後必須從頭重跑
pg_durable 將 workflow 定義、checkpoint、retry state、progress tracking 全部收進 PostgreSQL,消除上述所有拼接層。
1.3 核心設計理念
- Compute close to data:計算與資料同位,減少網路跳躍
- SQL-native:用 SQL 運算子組合工作流,不需學新語言
- Zero infrastructure overhead:一個 extension 取代整套 orchestration stack
- Exactly-once semantics via checkpoint:每個步驟完成後持久化,crash 後從上次成功處繼續
1.4 關鍵數據
| 指標 | 值 |
|---|---|
| GitHub Stars | 1,448 |
| Forks | 32 |
| 主要語言 | Rust(透過 pgrx) |
| 授權 | PostgreSQL License |
| 支援 PG 版本 | 17, 18 |
| 首次發佈 | 2026-02-13 |
| 最後活動 | 2026-06-06 |
| 相關雲服務 | Azure HorizonDB |
§2 核心概念
2.1 Durofut(Durable Future)
pg_durable 的核心資料結構是 Durofut——一個 JSON 格式的 function graph 節點。每個 Durofut 包含:
node_type:節點類型(SQL / THEN / IF / JOIN / LOOP / HTTP 等)left_node/right_node:子節點(形成 DAG)query:SQL 查詢或設定 JSONresult_name:具名結果(用於跨步驟傳遞資料)
2.2 兩階段架構
Phase 1 — Graph Construction(同步、在使用者 transaction 內):
- 使用者呼叫
df.start(...)時,DSL 運算子將 SQL 表達式組合為 Durofut JSON tree - 系統將 graph 拆解為 nodes,寫入
duroxide.instances和duroxide.nodes表 - 回傳 instance_id
Phase 2 — Orchestration Execution(非同步、background worker):
- Background worker 從
orchestrator_queue取出任務 - 使用
lock_token機制確保 exactly-once 語意 - 逐節點執行:SQL query → checkpoint → 下一個節點
- 支援平行 fan-out、條件分支、迴圈、HTTP 呼叫
2.3 DSL 運算子
| 運算子 | 意義 | 範例 |
|---|---|---|
~> | Then(序列) | 'SELECT 1' ~> 'SELECT 2' |
|=> | Named result(具名輸出) | 'SELECT id FROM t' |=> 'batch' |
|~> | Fan-out(平行分支) | step1 |~> step2 |~> step3 |
df.join(...) | Fan-in(等待全部完成) | df.join(a, b, c) |
df.race(...) | Fan-in(等待任一完成) | df.race(a, b) |
df.when(...) | 條件分支 | df.when('cond', then_step, else_step) |
df.loop(...) | 迴圈 | df.loop(body, 'exit_condition') |
df.http(...) | 外部 HTTP 呼叫 | df.http('https://api.example.com', 'POST') |
df.wait(...) | 暫停等待 | df.wait('1 hour') |
§3 系統架構圖
graph TD
subgraph UserSession["使用者 Session(Phase 1)"]
A["SQL: df.start(...)"] --> B["DSL 運算子
~> |=> |~> join race when loop"]
B --> C["Durofut JSON Tree
(function graph)"]
C --> D["寫入 duroxide.instances
+ duroxide.nodes"]
D --> E["回傳 instance_id"]
end
subgraph BGWorker["Background Worker(Phase 2)"]
F["orchestrator_queue
取出任務"] --> G["lock_token 鎖定
(exactly-once)"]
G --> H{"節點類型?"}
H -->|SQL| I["execute_sql
(SPI 執行)"]
H -->|HTTP| J["df.http()
(SSRF 防護)"]
H -->|JOIN/RACE| K["平行 fan-out
→ fan-in"]
H -->|IF/WHEN| L["條件分支"]
H -->|LOOP| M["迴圈"]
I --> N["Checkpoint
持久化狀態"]
J --> N
K --> N
L --> N
M --> N
N --> O{"還有下一個
節點?"}
O -->|Yes| F
O -->|No| P["標記 Completed
寫入 df.instances"]
end
D -.->|enqueue| F
subgraph Monitoring["監控與查詢"]
Q["df.instances — 查詢狀態"]
R["df.metrics() — 執行統計"]
S["df.explain() — 視覺化 plan"]
end
P -.-> Q
graph LR
subgraph CoreModules["Rust 核心模組"]
lib["lib.rs
PG extension 入口
GUC 設定
2546 行"]
dsl["dsl.rs
DSL 運算子實作
1090 行"]
types["types.rs
Durofut 結構
變數替換
1436 行"]
worker["worker.rs
Background worker
604 行"]
ssrf["ssrf.rs
SSRF IP blocklist
domain allowlist
1007 行"]
monitoring["monitoring.rs
df.metrics()
477 行"]
explain["explain.rs
df.explain()
746 行"]
exec["execute_function_graph.rs
核心執行引擎
1040 行"]
end
lib --> dsl
lib --> worker
dsl --> types
worker --> exec
exec --> types
exec --> ssrf
lib --> monitoring
lib --> explain
§4 安裝與環境設定
4.1 從 Debian 套件安裝(推薦)
Tagged releases 在 GitHub release assets 提供 .deb 套件:
1# 下載 .deb(以 PG 17 為例)
2wget https://github.com/microsoft/pg_durable/releases/download/v0.2.2/pg-durable-postgresql-17_0.2.2-1_amd64.deb
3
4# 安裝
5sudo dpkg -i pg-durable-postgresql-17_0.2.2-1_amd64.deb
設定 postgresql.conf:
1shared_preload_libraries = 'pg_durable'
重啟 PostgreSQL,然後在目標資料庫建立 extension:
1CREATE EXTENSION pg_durable;
4.2 從原始碼編譯
前置需求:
- PostgreSQL 17 或 18(含 dev headers)
- Rust toolchain(rustup)
- pgrx CLI
1# 安裝 pgrx
2cargo install --locked cargo-pgrx
3cargo pgrx init --pg17 $(which pg_config)
4
5# 編譯與安裝
6cargo pgrx install --release
4.3 Docker 開發環境
1# 使用 docker-compose
2docker-compose up -d
3
4# 或使用 devcontainer(VS Code / GitHub Codespaces)
5# .devcontainer/devcontainer.json 已預設好
4.4 Azure HorizonDB
pg_durable 已內建於 Azure HorizonDB,無需安裝:
1https://aka.ms/AzureHorizonDB
§5 使用方式與範例
5.1 基本序列工作流
1-- 三步驟序列執行:建立 temp table → 插入資料 → 查詢
2SELECT df.start(
3 'CREATE TEMP TABLE IF NOT EXISTS demo(id int, val text)'
4 ~> $$INSERT INTO demo VALUES (1, 'hello'), (2, 'world')$$
5 ~> 'SELECT count(*) FROM demo'
6);
5.2 具名結果與變數傳遞
1-- |=> 運算子將查詢結果命名,後續步驟用 $name 引用
2SELECT df.start(
3 'SELECT id FROM documents WHERE processed = false LIMIT 100' |=> 'batch'
4 ~> 'UPDATE documents SET processed = true WHERE id = ANY($batch)'
5);
5.3 平行 Fan-out / Fan-in
1-- 三個查詢平行執行,全部完成後 join
2SELECT df.start(
3 df.join(
4 'SELECT count(*) FROM users' |=> 'user_count',
5 'SELECT count(*) FROM orders' |=> 'order_count',
6 'SELECT sum(revenue) FROM sales' |=> 'total_revenue'
7 )
8 ~> $$SELECT format('Users: %s, Orders: %s, Revenue: %s',
9 $user_count, $order_count, $total_revenue)$$
10);
5.4 條件分支
1SELECT df.start(
2 'SELECT count(*) > 1000 FROM orders' |=> 'is_large'
3 ~> df.when(
4 '$is_large = true',
5 'SELECT run_batch_process()', -- then
6 'SELECT run_simple_process()' -- else
7 )
8);
5.5 外部 HTTP 呼叫(需啟用 http feature)
1-- 呼叫 embedding API
2SELECT df.start(
3 'SELECT content FROM documents WHERE id = 42' |=> 'text'
4 ~> df.http(
5 'https://api.openai.com/v1/embeddings',
6 'POST',
7 '{"input": "$text", "model": "text-embedding-3-small"}',
8 '{"Authorization": "Bearer ${OPENAI_KEY}"}'::jsonb
9 ) |=> 'embedding'
10 ~> $$UPDATE documents SET embedding = '$embedding'::vector WHERE id = 42$$
11);
5.6 查詢執行狀態
1-- 查詢所有 instance 的狀態
2SELECT * FROM df.instances;
3
4-- 查詢特定 instance
5SELECT * FROM df.instances WHERE instance_id = 'xxx';
6
7-- 取得執行統計
8SELECT * FROM df.metrics();
9
10-- 視覺化執行 plan
11SELECT df.explain('instance_id_here');
§6 資安掃描報告
🟢 整體評等:綠燈(安全設計良好)
pg_durable 在安全方面展現了高品質的工程實踐:
6.1 SSRF 防護(強)
| 項目 | 評估 |
|---|---|
| IP blocklist | 🟢 硬編碼於 ssrf.rs,涵蓋 RFC 1918 / loopback / link-local / IPv6 保留地址 |
| Domain allowlist | 🟢 編譯時期決定,超級使用者也無法繞過 |
| 預設行為 | 🟢 無 feature flag 時,所有 HTTP 呼叫被阻擋 |
| Redirect 防護 | 🟢 阻擋重導向(防止 SSRF bypass) |
ssrf.rs 提供四級安全模式:
- (預設)全部阻擋:df.http() 在 DSL 時期就報錯
http-allow-azure-domains:只允許 Azure 網域http-allow-test-domains:加上 github.com + httpbingo.org(測試用)http-allow-all:開發用,禁止用於生產
6.2 SQL 注入防護(強)
| 項目 | 評估 |
|---|---|
| SPI 參數化 | 🟢 全面使用 Spi::run_with_args() 做參數綁定 |
| search_path 硬化 | 🟢 v0.2.0 升級腳本將所有 PL/pgSQL 函數加上 SET search_path = pg_catalog, df, pg_temp |
| Schema 隔離 | 🟢 使用獨立的 df schema(公開 API)和 duroxide schema(內部) |
6.3 權限模型(良好)
| 項目 | 評估 |
|---|---|
| SECURITY INVOKER | 🟢 所有 df.* 函數預設 SECURITY INVOKER |
| df.http() 權限 | 🟢 v0.2.0+ 預設不授權給 PUBLIC,需明確 GRANT |
| RLS 支援 | 🟢 有完整的 E2E 測試(15_rls.sql) |
| 使用者隔離 | 🟢 有完整的 E2E 測試(13_user_isolation.sql) |
| Delegated grants | 🟢 有明確的權限委派測試(18_delegated_grants.sql) |
6.4 Unsafe Rust 使用(可接受)
| 項目 | 評估 |
|---|---|
unsafe 區塊數量 | 🟡 約 10 處,全部在 pgrx FFI 呼叫上(GetUserId、PostPortNumber、process_shared_preload_libraries_in_progress) |
| 評估 | 🟢 這些是 pgrx 框架與 PostgreSQL C API 互動的標準做法,非自創 unsafe 邏輯 |
6.5 機密管理(適當)
| 項目 | 評估 |
|---|---|
.env.example | 🟢 僅含 ACR registry 設定,無真實 secret |
| 硬編碼 credential | 🟢 未發現任何硬編碼密碼或 API key |
build.rs | 🟡 使用 Command::new("date") 取得編譯時間戳——無安全風險 |
6.6 已知安全議題(Open Issues)
- #185:
SECURITY DEFINER與df.start()的互動需要更好的文件說明,以及新增start_use_session_userGUC - #214:
df.http()allowlist 不管控其他 SQL extension 的網路行為(可能造成混淆)
6.7 結論
pg_durable 的安全設計是 PostgreSQL extension 領域中的優秀範例。SSRF 防護(編譯時鎖定、superuser 也無法繞過)和 SQL 注入防護(全面參數化 + search_path 硬化)都遠超一般水準。少量 unsafe Rust 皆為 pgrx FFI 標準用法,非安全疑慮。
§7 程式碼結構導覽
7.1 檔案統計
| 類型 | 檔案數 | 說明 |
|---|---|---|
| Rust (.rs) | 19 | 核心邏輯 |
| SQL (.sql) | 69 | Schema migration + E2E 測試 |
| Shell (.sh) | 14 | 測試腳本、CI 工具 |
| 總檔案數 | 235 | — |
| 核心 Rust 程式碼 | ~9,000 行 | src/ 下所有 .rs |
| SQL 測試 | ~6,000 行 | tests/e2e/sql/ |
7.2 關鍵目錄
1pg_durable/
2├── src/ # Rust 核心
3│ ├── lib.rs # PG extension 入口、GUC、SQL function 定義
4│ ├── dsl.rs # DSL 運算子(~>, |=>, join, race, when, loop)
5│ ├── types.rs # Durofut 結構、變數替換引擎
6│ ├── worker.rs # Background worker 生命週期
7│ ├── ssrf.rs # SSRF 防護(IP blocklist + domain allowlist)
8│ ├── monitoring.rs # df.metrics() 實作
9│ ├── explain.rs # df.explain() 視覺化
10│ ├── client.rs # HTTP client
11│ ├── registry.rs # Function registry
12│ ├── activities/ # Activity 執行器
13│ │ └── execute_sql.rs # SQL 節點執行
14│ └── orchestrations/ # Orchestration 引擎
15│ └── execute_function_graph.rs # 核心 graph traversal
16├── sql/ # PostgreSQL schema + migration
17│ ├── pg_durable--0.1.1.sql # 初始 schema(3729 行)
18│ ├── pg_durable--0.1.1--0.2.0.sql # 權限硬化升級
19│ ├── pg_durable--0.2.0--0.2.1.sql
20│ └── pg_durable--0.2.1--0.2.2.sql
21├── tests/e2e/sql/ # E2E SQL 測試(21+ 檔案)
22├── scripts/ # 測試、部署、CI 腳本
23├── examples/ # 使用範例
24│ ├── azure-functions/ # 搭配 Azure Functions
25│ ├── azure-http-domains/ # Azure 服務整合
26│ ├── invoice-approval/ # 發票審批工作流
27│ └── operational-scenarios/ # 運維場景
28├── docs/ # 技術文件
29│ ├── ARCHITECTURE.md # 架構深度說明
30│ └── api-reference.md # API 參考
31└── prompts/ # AI agent prompts(merge / release / test 等)
§8 與同類工具的比較
| 特性 | pg_durable | Temporal | Airflow | pg_cron + DIY |
|---|---|---|---|---|
| 部署複雜度 | 🟢 單一 extension | 🔴 獨立叢集 | 🔴 獨立服務 | 🟡 需自建 |
| 語言 | SQL | Go/Java/Python | Python DAG | SQL + app code |
| Checkpoint 機制 | 🟢 自動 | 🟢 自動 | 🟡 task 級別 | 🔴 手動 |
| 資料位置 | 🟢 同 DB | 🔴 外部 | 🔴 外部 | 🟢 同 DB |
| Crash recovery | 🟢 自動 | 🟢 自動 | 🟡 task 重試 | 🔴 手動 |
| 平行執行 | 🟢 原生 | 🟢 原生 | 🟢 原生 | 🔴 手動 |
| HTTP 呼叫 | 🟡 需 feature flag | 🟢 任意 | 🟢 任意 | 🟢 任意 |
| 適用規模 | 中小型 | 大型 | 大型 | 小型 |
| 跨系統編排 | 🔴 SQL 為主 | 🟢 任意 | 🟢 任意 | 🔴 有限 |
結論:pg_durable 的 sweet spot 是「資料已在 Postgres、workflow 可用 SQL 表達、不想維護額外基礎設施」的團隊。若 workflow 主要跨多個異質系統,Temporal 或 Airflow 仍較適合。
§9 實戰建議與最佳實踐
9.1 何時使用 pg_durable
- 資料已在 PostgreSQL,workflow 步驟主要是 SQL 操作
- 需要 crash-safe 的批次處理或 ETL pipeline
- 團隊規模較小,不想維護 Temporal / Airflow 叢集
- AI/ML pipeline 需要 per-row / per-document 的 durable execution
9.2 設計原則
- 步驟粒度適中:每個步驟應該是一個有意義的 checkpoint 點,太細會增加 overhead,太粗會降低恢復精度
- 善用 named results:
|=>運算子讓資料在步驟間流動,避免 temp table 污染 - 平行 fan-out 要收斂:
|~>必須搭配df.join()或df.race()收斂 - HTTP 呼叫要考慮冪等性:步驟重試時,外部 API 呼叫應該是冪等的
9.3 監控
1-- 定期查看 metrics
2SELECT * FROM df.metrics();
3
4-- 找出失敗的 instances
5SELECT * FROM df.instances WHERE status = 'failed';
6
7-- 視覺化特定執行的 plan
8SELECT df.explain('instance_id');
9.4 版本升級
1-- 升級 extension(例如 0.2.1 → 0.2.2)
2ALTER EXTENSION pg_durable UPDATE TO '0.2.2';
升級腳本會自動執行 schema migration,包括安全硬化(search_path、權限調整)。
§10 開發與測試
10.1 本地開發環境
1# Clone repo
2git clone https://github.com/microsoft/pg_durable.git
3cd pg_durable
4
5# 使用 devcontainer(推薦)
6# VS Code: Reopen in Container
7
8# 或手動設定
9cargo install --locked cargo-pgrx
10cargo pgrx init --pg17 $(which pg_config)
10.2 測試
1# 全部測試
2bash scripts/test-all-local.sh
3
4# 單元測試
5bash scripts/test-unit.sh
6
7# E2E 測試(Docker)
8bash scripts/test-e2e-docker.sh
9
10# E2E 測試(本地)
11bash scripts/test-e2e-local.sh
12
13# 升級測試
14bash scripts/test-upgrade.sh
15
16# 覆蓋率報告
17bash scripts/test-coverage.sh
18
19# SQL 靜態分析(pgspot)
20bash scripts/run-pgspot.sh
10.3 E2E 測試涵蓋範圍
| 測試檔案 | 涵蓋內容 |
|---|---|
01_core_primitives.sql | 基本序列、平行、named results |
02_conditionals.sql | IF/WHEN 條件分支 |
03_loops.sql | LOOP 迴圈 |
04_variables_and_results.sql | 變數替換、結果傳遞 |
06_http_and_ssrf.sql | HTTP 呼叫、SSRF 防護 |
07_signals.sql | Signal 機制 |
11_cross_connection.sql | 跨連線行為 |
12_extension_lifecycle.sql | Extension 安裝/升級/移除 |
13_user_isolation.sql | 使用者隔離、SECURITY DEFINER |
14_database.sql | 多資料庫 |
15_rls.sql | Row-Level Security |
17_superuser_guc.sql | Superuser GUC 設定 |
18_delegated_grants.sql | 權限委派 |
21_typed_results.sql | 型別化結果 |
47_http_dsl_disabled.sql | HTTP 功能停用時的行為 |
§11 未來發展與社群
11.1 近期發展方向(從 Issues 觀察)
- pgrx 升級(#209):升級到 pgrx v0.18.1 以支援更新的 PG 版本
- Schema 重組(#201, #202):將 duroxide schema 和 operators 整理到更清晰的命名空間
- 安全強化(#185):完善 SECURITY DEFINER 互動文件、新增
start_use_session_userGUC - HTTP allowlist 文件(#214):釐清 df.http() allowlist 的適用範圍
11.2 Azure HorizonDB 整合
pg_durable 是 Microsoft 新推出的 Azure HorizonDB(PostgreSQL 雲服務)的核心元件之一。這代表 pg_durable 有 Microsoft 的長期維護承諾和企業級使用場景背書。
11.3 貢獻指南
- 遵循
CONTRIBUTING.md和 Microsoft CLA - 使用
prompts/目錄下的 AI agent prompts 輔助開發 - PR 前跑
bash scripts/test-all-local.sh+bash scripts/run-pgspot.sh
11.4 適合的應用場景展望
- Biomedical data pipeline:基因定序資料批次處理,per-sample durable execution
- 金融 ETL:交易結算、對帳、報表生成的 crash-safe pipeline
- IoT 資料處理:感測器資料 ingest → transform → aggregate → alert
- AI/ML embedding pipeline:搭配 pgvector,chunk → embed → upsert 全程 durable
文件產生日期:2026-06-08 來源:https://github.com/microsoft/pg_durable 授權:PostgreSQL License INTERNAL ONLY
Comments