---
title: "마이크로소프트, Postgres 안에서 오래 가는 워크플로 돌리는 pg_durable 공개"
published: 2026-06-05T15:59:57.000Z
canonical: https://jeff.news/article/3779
---
# 마이크로소프트, Postgres 안에서 오래 가는 워크플로 돌리는 pg_durable 공개

Microsoft가 Postgres 안에서 durable execution을 구현하는 오픈소스 확장 pg_durable을 공개했음. cron, worker, queue, status table을 엮어 만들던 백그라운드 작업을 SQL 함수 그래프로 정의하고, 각 단계를 checkpoint로 저장해 재시작·실패 후에도 이어서 실행하게 해줌.

## Postgres 안에서 durable workflow를 돌리겠다는 확장임

- Microsoft가 pg_durable이라는 PostgreSQL 확장을 오픈소스로 공개했음
  - 목적은 긴 백그라운드 작업을 cron, worker, queue, status table 조합으로 억지로 엮지 않게 하는 것임
  - 상태가 이미 Postgres에 있는 팀이 SQL 안에서 workflow를 정의하고, Postgres가 단계별 실행과 checkpoint를 맡게 하는 구조임

- 핵심 아이디어는 “workflow를 SQL graph로 정의하고, 각 step 사이를 durable checkpoint로 남긴다”임
  - DB가 crash, restart, failover를 겪거나 step 하나가 실패해도 마지막 checkpoint부터 다시 이어감
  - 사용자는 직접 retry counter, progress table, polling worker를 만들 필요가 줄어듦

- Microsoft는 이걸 Azure HorizonDB에도 내장했다고 밝힘
  - HorizonDB는 Microsoft의 새 PostgreSQL cloud service로 소개됨
  - pg_durable은 “compute를 data 가까이 가져간다”는 Microsoft 쪽 메시지와 맞물려 있음

> [!IMPORTANT]
> pg_durable의 포인트는 “작업 상태를 앱 바깥에 따로 흩뿌리지 않고 Postgres 안에 남긴다”는 거임. 이미 데이터와 작업 대상이 Postgres에 있다면 운영 복잡도를 꽤 줄일 수 있음.

## 어디에 쓰라고 만든 건가

- 대표 타깃은 backend/data engineer, DBA, SRE 쪽임
  - 데이터 근처에서 workflow를 돌리고 싶은 backend/data engineer
  - restart 후에도 살아남고 SQL로 감사 가능한 runbook 자동화를 원하는 DBA/SRE
  - row, document, batch 단위로 durable execution이 필요한 data/AI pipeline 팀

- 예시 use case는 꽤 현실적임
  - vector embedding pipeline에서 문서를 chunk하고, embedding API를 호출하고, pgvector에 upsert하는 흐름
  - ingest pipeline에서 stage, deduplicate, transform, publish를 큰 batch로 처리하는 흐름
  - scheduled maintenance에서 bloat를 감지하고, 알림을 보내고, 승인 대기 후 다음 action을 실행하는 흐름
  - fan-out aggregation에서 독립 query를 병렬 실행한 뒤 결과를 join하는 흐름
  - SQL에서 외부 API enrichment, classification, webhook성 호출을 하는 흐름

- 기존 방식에서 흔히 생기는 문제를 정면으로 겨냥함
  - pg_cron과 jobs table, status column, retry counter, polling worker를 조합하는 방식은 금방 지저분해짐
  - Airflow, Temporal, Step Functions, Argo 같은 외부 orchestrator를 붙이면 Postgres로 다시 callback하는 접착 코드가 늘어남
  - queue, worker, separate state table을 두면 partial failure와 replay 기준이 흐려짐
  - 긴 transaction은 lock과 WAL을 키우고 batch job을 취약하게 만듦

## 사용 모델은 SQL DSL + background worker

- workflow 정의는 SQL스럽게 생긴 DSL로 작성함
  - df.start()로 instance를 시작함
  - ~>와 |=> 같은 composable operator로 step을 연결함
  - df.if(), df.join(), df.loop() 같은 primitive도 제공됨

- 간단한 예시는 “처리 안 된 문서 100개를 가져와 처리 완료로 바꾸는 durable function”임
  - `SELECT id FROM documents WHERE processed = false LIMIT 100` 결과를 batch로 받고
  - 다음 step에서 `UPDATE documents SET processed = true WHERE id = ANY($batch)`를 실행하는 식임
  - step 사이 checkpoint가 있으니 중간 restart 후에도 어디까지 했는지 추적 가능함

```mermaid
sequenceDiagram
    participant 앱 as 애플리케이션
    participant 디에스엘 as pg_durable SQL DSL
    participant 워커 as 백그라운드 워커
    participant 런타임 as duroxide 런타임
    participant 저장소 as Postgres 상태 테이블
    앱->>디에스엘: df.start()로 워크플로 시작
    디에스엘->>저장소: 인스턴스와 노드 기록
    워커->>런타임: 실행할 단계 요청
    런타임->>저장소: 단계 결과 체크포인트
    워커->>런타임: 실패 또는 재시작 후 재개
    런타임->>저장소: 마지막 체크포인트부터 이어 실행
```

- 내부 구조는 Postgres 확장 안에 orchestration runtime을 넣은 형태임
  - pg_durable extension은 pgrx로 만들어짐
  - background worker가 Postgres 서버 안에서 duroxide runtime을 호스팅함
  - duroxide-pg가 instance, history, work queue 같은 runtime state를 duroxide.* schema에 저장함
  - 사용자-facing DSL과 상태는 df.* schema 쪽에 있음

## 설치와 운영 조건은 가볍지만 공짜는 아님

- 현재 release asset은 PostgreSQL 17과 18용 Debian package를 제공함
  - 패키지 이름은 `pg-durable-postgresql-<PG major>_<pg_durable version>-1_<arch>.deb` 형태임
  - amd64용으로 제공됨
  - extension library, control file, SQL upgrade file을 해당 PostgreSQL 설치 경로에 넣음

- 설치 후에는 shared_preload_libraries 설정이 필요함
  - `pg_durable`을 shared_preload_libraries에 추가해야 함
  - PostgreSQL을 restart해야 함
  - 그 다음 configured pg_durable database에서 `CREATE EXTENSION pg_durable;`을 실행함
  - 기본 pg_durable database는 `postgres`임

- source build도 가능하지만 Rust와 pgrx 조건이 있음
  - PostgreSQL 17 또는 18이 필요함
  - Rust nightly가 필요함
  - cargo-pgrx 0.16.1이 필요함
  - dev container에는 Rust, cargo-pgrx, PostgreSQL 17이 미리 들어 있음

- 테스트 체계도 꽤 구체적으로 잡혀 있음
  - `cargo fmt --check`로 format check를 함
  - `cargo clippy`, unit test, `cargo pgrx test pg17`, pg_regress test, E2E test를 CI에서 돌림
  - SQL regression test는 sql/과 expected/에 있고, E2E는 tests/e2e/ 쪽에 있음

## 권한과 보안 쪽은 특히 신경 써야 함

- `CREATE EXTENSION pg_durable`만으로 PUBLIC에 권한을 주지는 않음
  - admin이 application role에 명시적으로 access를 부여해야 함
  - `SELECT df.grant_usage('app_role');` 같은 형태를 제공함
  - 또는 `pg_durable_user` 같은 indirection role을 만들고 여러 app role에 membership을 줄 수 있음

- Row-Level Security(RLS)로 각 사용자의 instance와 node 접근을 제한함
  - 사용자는 df.instances와 df.nodes에 SELECT/INSERT를 가짐
  - df.cancel()을 위한 instance status, updated_at column-level UPDATE가 있음
  - submitted_by identity column은 사용자가 수정할 수 없음

- background worker role은 superuser여야 함
  - 기본 GUC인 `pg_durable.worker_role`은 `postgres`임
  - 이 worker는 모든 사용자의 instance를 관리해야 하므로 RLS를 우회함
  - superuser가 RLS를 우회하더라도 DSL function은 명시적 filter로 호출 사용자의 scope를 유지함

> [!WARNING]
> extension upgrade 후에는 grant를 다시 챙겨야 함. `GRANT EXECUTE ON ALL FUNCTIONS`는 실행 시점에 존재하는 함수에만 적용되기 때문에, `ALTER EXTENSION pg_durable UPDATE` 뒤에는 `df.grant_usage('role')`를 다시 실행해야 새 함수 접근이 열림.

## 언제 쓰면 좋고, 언제 피해야 하나

- 잘 맞는 경우는 workflow가 이미 Postgres 데이터 중심일 때임
  - 작업이 `INSERT ... SELECT`나 ordinary SQL statement에 가깝다면 특히 자연스러움
  - vector embedding, ingest, maintenance, per-row AI pipeline처럼 DB 상태와 작업 상태가 붙어 있는 경우가 좋음
  - 운영 가시성도 df.instances 같은 Postgres table에서 나오니 auth와 backup 모델을 같이 가져갈 수 있음

- 반대로 모든 orchestration을 대체하는 도구는 아님
  - sub-millisecond synchronous request handling이 필요하면 목적이 다름
  - Postgres extension 설치나 background worker 실행이 불가능한 환경이면 못 씀
  - workflow가 Postgres 밖의 여러 heterogeneous system에 걸쳐 있으면 범용 orchestrator가 더 맞을 수 있음
  - arbitrary application logic, non-HTTP SDK, 복잡한 in-memory control flow가 필요하면 SQL step으로 깔끔하게 안 떨어질 수 있음

- 임의 코드가 필요할 때의 우회책도 제시함
  - SQL function으로 감싸거나
  - HTTP endpoint로 노출한 뒤 df.http()로 호출하거나
  - 그 부분만 general-purpose orchestrator에 맡기는 식임

- 아직 preview 상태라는 점은 빼먹으면 안 됨
  - 버그 리포트와 기능 요청은 GitHub Issues로 받음
  - 보안 취약점은 public issue에 올리지 말고 SECURITY.md 절차를 따르라고 안내함
  - Microsoft로 telemetry를 보내지는 않는다고 밝힘

---

## 기술 맥락

- pg_durable이 해결하려는 문제는 “긴 작업의 상태를 어디에 둘 거냐”예요. 기존에는 jobs table, queue, worker, retry counter, dashboard가 흩어지기 쉬웠고, 실패 후 어디서부터 재시작할지 앱 코드가 직접 판단해야 했거든요. pg_durable은 그 상태를 Postgres 내부 checkpoint로 옮겨요.

- 이 선택이 잘 먹히는 이유는 작업 대상 데이터가 이미 Postgres에 있을 때예요. embedding pipeline이나 ingest pipeline처럼 row, document, batch를 만지는 작업은 상태와 데이터가 같은 DB 안에 있으면 감사, 권한, 백업 모델이 단순해져요. 대신 Postgres가 단순 저장소를 넘어 실행 런타임 역할까지 맡게 된다는 트레이드오프가 있어요.

- Airflow나 Temporal과의 차이는 범위예요. 외부 orchestrator는 여러 시스템을 가로지르는 복잡한 workflow에 강하지만, Postgres 중심 작업에는 callback과 상태 동기화 코드가 늘 수 있어요. pg_durable은 그런 범용성을 줄이는 대신 SQL-native workflow와 zero extra service infrastructure를 택한 거예요.

- 운영에서는 extension이라는 사실이 중요해요. shared_preload_libraries, background worker, superuser role, RLS, extension upgrade 후 grant 재적용까지 챙겨야 하거든요. 앱 서버에 라이브러리 하나 추가하는 느낌보다는, Postgres 운영 정책 안에 새 실행 엔진을 들이는 결정에 가까워요.

## 핵심 포인트

- pg_durable은 PostgreSQL 17·18용 확장으로, SQL 기반 durable workflow를 Postgres 내부에서 실행함
- 각 step 사이에 checkpoint를 남겨 crash, restart, failover, step failure 이후에도 마지막 지점부터 재개함
- df.start, ~>, |=> 같은 DSL로 workflow graph를 정의하고 df.instances, df.nodes 같은 테이블에서 상태를 조회함
- Azure HorizonDB에 내장돼 있으며, 내부적으로 pgrx, duroxide, duroxide-pg를 사용함
- preview 상태라 운영 도입 전 권한, RLS, background worker, extension upgrade grant 재적용을 신경 써야 함

## 인사이트

Postgres를 단순 저장소가 아니라 작업 오케스트레이션 런타임으로 끌어올리려는 시도임. 모든 걸 DB 안에 넣는 게 정답은 아니지만, 이미 상태가 Postgres에 있고 작업이 SQL 중심이라면 Temporal이나 Airflow까지 꺼내기 애매한 구간을 꽤 정조준함.
