df.start() 한 번 부르면 — 크래시·재시작·단계 실패 후에도 마지막 체크포인트부터 재개됩니다. Rust(pgrx)로 작성됐고 Azure HorizonDB에 내장될 만큼 실전 지향입니다. (저장소: microsoft/pg_durable · Rust · TrendShift 1위(06-06) · PostgreSQL License)이 레포가 무엇을 하는 물건인가.
pg_durable 함수는 SQL 단계들의 그래프입니다. Postgres가 각 단계를 실행하며 체크포인트를 남기고, DB가 죽었다 살아나도 이미 성공한 단계는 다시 하지 않습니다. "컴퓨트를 데이터 곁으로"라는 Microsoft의 방향이 가장 구체화된 물건 중 하나.
한 줄 맛보기 — 문서 100건을 집어 처리 완료 표시하는 듀러블 함수:
SELECT df.start(
'SELECT id FROM documents WHERE processed = false LIMIT 100' |=> 'batch'
~> 'UPDATE documents SET processed = true WHERE id = ANY($batch)'
);
|=>는 "결과를 batch라는 이름으로 저장", ~>는 "그다음에"라는 뜻의 합성 연산자입니다. 복사해서 돌려보면 인스턴스 ID가 돌아오고, 진행 상황은 df.instances 테이블을 SELECT하면 보입니다 — 모니터링 대시보드도 결국 SQL입니다.
트렌딩 이유 · 경쟁 대비 장점.
듀러블 실행은 Temporal·Step Functions가 표준으로 만든 패턴이지만, 전부 별도 오케스트레이터 서비스를 요구합니다. pg_durable의 제안은 급진적입니다 — 상태가 이미 Postgres에 있는 팀이라면, 오케스트레이터도 Postgres여야 한다. AI 파이프라인 붐(임베딩 생성→pgvector 적재 같은 행 단위 듀러블 작업 폭증)과 맞물려 06-06 트렌딩 1위에 올랐습니다.
| 비교 항목 | 외부 오케스트레이터 (Temporal·Airflow) | pg_durable |
|---|---|---|
| 인프라 | 별도 클러스터 + SDK + 콜백 | Postgres 확장 하나 — 추가 서비스 0 |
| 워크플로 정의 | Go/Java/Python 코드 | SQL + 합성 연산자 |
| 상태 조회 | 전용 UI/API | df.instances를 그냥 SELECT — 백업·권한도 DB와 동일 |
| 적합 범위 | 다종 시스템 가로지르는 복잡 로직 | 데이터 근처의 SQL-모양 워크플로 |
긴 배치 중간에 재시작이 나면 성공한 작업을 다시 돌리고, 실패한 행 하나가 수동 정리로 번지고, 긴 트랜잭션이 락과 WAL을 키웁니다. 워크플로 로직이 SQL·워커·큐·대시보드에 흩어집니다.
각 단계가 듀러블하게 기록되니 재시작은 "이어하기"가 되고, 앱 계층의 워커·큐 소비자·스케줄러 글루가 통째로 사라질 수 있습니다. README가 "언제 쓰지 말아야 하나"(단일 SQL이면 과잉, 서브밀리초 응답엔 부적합, 확장 설치 불가 환경 등)까지 명시하는 점도 신뢰 포인트.
Rust로 짠 Postgres 확장 — pgrx 생태계의 대형 실례.
| 레이어 | 기술 | 역할 |
|---|---|---|
| 확장 본체 | Rust + pgrx 0.16 | Postgres 확장을 Rust로 작성하는 프레임워크 — C 대신 안전한 Rust로 cdylib 빌드. |
| 지원 버전 | PostgreSQL 17·18 (공식), 13~16 feature 플래그 | shared_preload_libraries 등록 + CREATE EXTENSION. |
| 실행 엔진 | 오케스트레이션/액티비티 분리 (duroxide 계열 모델) | 워크플로 그래프 해석은 orchestrations/, 실제 일은 activities/. |
| 백그라운드 워커 | Postgres BGW | DB 프로세스 안에서 단계를 집어 실행 — 외부 워커 불필요. |
| DSL | src/dsl.rs — ~>·|=> 연산자 | SQL 문자열 합성으로 그래프 정의. 문법은 docs/grammar.md. |
| HTTP 단계 | df.http() + src/ssrf.rs | 외부 API 호출 단계 — SSRF 가드와 도메인 허용목록(feature로 제어)이 내장. |
| 배포 | deb 패키지·Docker·devcontainer | PG 17/18용 패키지 자동 릴리스. Azure HorizonDB에 기본 탑재. |
df.start() 한 번이 거치는 길.
구조의 핵심은 오케스트레이션과 액티비티의 분리입니다 — Temporal과 같은 사상인데 무대만 DB 안입니다. 오케스트레이터(그래프 진행 결정)는 결정적이어야 하고, 부수효과 있는 일(SQL 실행·HTTP 호출)은 전부 액티비티로 격리돼 각각 체크포인트를 가집니다. 덕분에 재개 시 "어디까지 했는지"가 모호하지 않습니다. 병렬 fan-out(독립 쿼리 여러 개 동시 실행 후 join)도 그래프 표현으로 자연스럽게 풀립니다.
또 하나 눈여겨볼 것은 src/ssrf.rs — DB 안에서 HTTP를 쏘는 기능은 SSRF (Server-Side Request Forgery, 서버가 내부망으로 요청을 쏘게 속이는 공격)의 통로가 되기 쉬운데, 도메인 허용목록을 컴파일 타임 feature(http-allow-azure-domains 등)로 강제합니다. "편의 기능엔 반드시 보안 가드"의 모범입니다.
게임의 세이브 포인트와 같습니다. 보스전(긴 배치) 중간중간 자동 저장되니, 게임이 꺼져도(DB 크래시) 처음부터가 아니라 마지막 세이브에서 이어 합니다. pg_durable은 그 세이브 파일을 게임기 본체(Postgres) 안에 저장하는 방식이고, Temporal은 별도의 클라우드 세이브 서버를 두는 방식입니다.
어떤 폴더가 무슨 일을 하나.
| 위치 | 주목할 점 |
|---|---|
docs/ | 스펙·문법·보안·데드락 이슈 분석까지 — 설계 결정의 이유가 문서로 남는 MS 스타일. |
src/activities/ | 듀러블 실행의 본질(부수효과 격리 + 체크포인트)이 파일 구조로 드러남. |
.agents/skills/ | DB 확장에도 에이전트 스킬 동봉 — "AI가 pg_durable SQL을 짜게" 하려는 포석. |
docs/DUROXIDE_PG_DEADLOCK_ISSUE.md | 실제로 만난 데드락의 해부 — 분산 시스템 디버깅 읽을거리로 수준급. |
이 레포에서 배울 만한 것 + 어디를 보면 되는지.
오케스트레이션(결정적)/액티비티(부수효과) 분리, 단계별 체크포인트, 재개 의미론 — Temporal 문서로 배우면 추상적인 개념이 여기선 파일 몇 개의 Rust로 만져집니다.
shared_preload_libraries, BGW 생명주기, 확장 업그레이드 스크립트(extension_lifecycle.md) — Postgres 확장 개발의 실무 전 과정을 한 레포에서 봅니다.
~>(순차)·|=>(이름 붙은 결과)라는 두 연산자로 그래프를 표현하는 절제된 DSL — grammar.md와 dsl.rs를 대조하며 "작은 문법"의 설계를 배울 수 있습니다.
허용 도메인을 런타임 설정이 아니라 빌드 feature로 박는 선택(http-allow-azure-domains) — "설정 실수로 열리는 구멍"을 빌드 단계에서 봉쇄하는 접근입니다.
돌리려면 무엇이 필요한가.
| 항목 | 요구사항 |
|---|---|
| PostgreSQL | 17 또는 18 (공식 패키지 기준) — deb 패키지는 amd64. |
| 설치 | 패키지 설치 → shared_preload_libraries에 pg_durable 추가 → 재시작 → CREATE EXTENSION. |
| 개발 빌드 | Rust + pgrx 툴체인. devcontainer/docker-compose 제공으로 진입장벽 낮음. |
| 매니지드 환경 | 확장 설치 권한 필요 — 불가 환경이면 Azure HorizonDB(기본 탑재) 같은 옵션. |
난이도별로 손에 익히는 단계.
레포의 compose를 띄우고 README의 Quick Example을 실행 → SELECT * FROM df.instances로 인스턴스 상태를 관찰합니다.
일부러 느린 다단계 워크플로(pg_sleep 끼운 3단계)를 시작하고 중간에 컨테이너를 죽였다 살려, 어느 단계부터 재개되는지 df 테이블로 추적합니다 — 듀러블 실행의 핵심 가치를 몸으로 확인.
독립 쿼리 3개(사용자 수·주문 수·매출 합)를 병렬 실행 후 join하는 README의 대시보드 시나리오를 직접 작성해 봅니다 — 그래프 표현력 연습.
문서 청크 → df.http()로 임베딩 API 호출 → pgvector upsert의 행 단위 듀러블 파이프라인을 만들고, API가 간헐 실패할 때 재시도가 어떻게 동작하는지 관찰합니다.
한 주씩 따라가는 계획.
| 주차 | 주제 | 학습 자료 |
|---|---|---|
| 1주차 | 듀러블 실행 개념 + Quick Example | README · USER_GUIDE.md |
| 2주차 | DSL 문법과 그래프 모델 | docs/grammar.md · docs/SCENARIOS.md |
| 3주차 | 아키텍처 — 오케스트레이션/액티비티/워커 | docs/ARCHITECTURE.md · src/ 정독 |
| 4주차 | pgrx 확장 개발 입문 | pgrx 공식 튜토리얼 · build.rs |
| 5주차 | 비교 연구 — Temporal·DBOS·pg_cron과의 트레이드오프 | 각 프로젝트 문서 · 실습 2·4 |
본문에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| 듀러블 실행 | 단계마다 상태를 영속화해 장애 후 이어 달리는 실행 모델. |
| 체크포인트 | "여기까지 성공"을 기록한 지점 — 재개의 기준. |
| 오케스트레이션 / 액티비티 | 진행 결정(결정적) / 실제 부수효과 작업 — 듀러블 실행의 표준 분리. |
| ~> / |=> | pg_durable DSL — 순차 연결 / 결과에 이름 붙이기. |
| df.instances | 실행 중·완료된 워크플로 인스턴스를 담는 테이블 — SQL로 모니터링. |
| 백그라운드 워커(BGW) | Postgres 안에서 도는 상주 프로세스 — 외부 워커를 대체. |
| pgrx | Rust로 Postgres 확장을 만드는 프레임워크. |
| SSRF | 서버를 속여 내부망으로 요청을 쏘게 하는 공격 — df.http()의 도메인 가드 이유. |
| shared_preload_libraries | Postgres 시작 시 로드할 라이브러리 목록 — BGW 쓰는 확장의 필수 등록처. |
| fan-out | 독립 작업 여러 개를 병렬로 펼쳤다 합치는 패턴. |