Durable Execution

크래시·재시작·페일오버를 넘어서 중단 지점부터 재개되는 실행 모델


핵심 개념

Durable execution은 워크플로의 각 단계를 체크포인트하고 결정적 재생(deterministic replay)으로 복구해, 프로세스가 죽어도 처음부터가 아니라 마지막 체크포인트부터 이어가는 실행 모델이다. Temporal, Azure Durable Functions, 그리고 최근의 pg_durable이 이 계열이다.

핵심은 두 가지 분리다.

  • 워크플로 상태를 프로세스 메모리가 아닌 durable 스토어에 둔다
  • 한 단계의 실패가 전체 재실행을 요구하지 않게 단계 경계를 명시적으로 만든다

왜 필요한가 — 두 가지 워크어라운드

Postgres 팀이 백그라운드 작업(변환, 야간 롤업, 임베딩 생성, 승인 대기)이 필요해질 때 거치는 전형적 경로:

1) 전부 하나의 함수에 우겨넣기

  • 커넥션 하나가 내내 묶임
  • 전부 하나의 거대 트랜잭션 → 긴 락, 부분 진행 가시성 없음
  • 연결이 끊기거나 DB가 재시작하면 실행 전체가 소실
  • step별 재시도·병렬·스케줄·human-in-the-loop 일시정지가 없음

2) 밖으로 빼기

  • 잡 큐, 폴링 워커, 상태 테이블, step 조율, 재시도 로직, 크래시 복구 sweep, 정리 잡
  • 비즈니스 로직을 건드리기도 전에 분산 시스템을 새로 짓고 운영하게 된다

둘 다 같은 빠진 프리미티브에 대한 워크어라운드다: 데이터가 있는 곳에서 도는 durable·비동기 백그라운드 작업.

작동 원리 (pg_durable 사례)

  • DSL + duroxide 런타임을 Postgres background worker에 호스팅
  • df.start(...)가 즉시 instance ID를 반환하고 작업은 옆에서 실행 → 커넥션·트랜잭션을 막지 않음. df.status()/df.result()로 나중에 조회
  • 비동기이므로 자동으로 워크플로를 discrete step으로 분할. 각 step이 자기 세션·트랜잭션에서 돌고 진행분을 커밋한 뒤 다음으로 넘긴다 — 거대 트랜잭션을 열어두지 않는다
  • step은 Postgres에 체크포인트되고 deterministic replay로 복구. 실패한 step만 재시도
  • 실행 상태가 DB에 살기 때문에 DB의 durability·HA·백업·PITR을 그대로 상속한다. 이것이 외부 오케스트레이터 대비 가장 큰 구조적 이점

DSL 연산자

연산자의미
~>순차
&병렬 (fan out, 전부 대기)
|레이스 (먼저 끝난 것)
?> / !>조건 if/else
@>durable 루프 (재시작을 견딤)
|=>step 결과를 변수로 캡처

df.http()(allowlist 엔드포인트), df.wait_for_schedule()(cron), df.wait_for_signal()(외부 이벤트 대기) 같은 함수가 있어 스케줄 잡과 human-in-the-loop 승인을 같은 모델로 표현한다.

언제 쓰고 언제 안 쓰는가

DB 내장 durable execution을 쓸 때 — 워크플로의 무게중심이 DB 상태에 밀착해 있을 때

  • 임베딩 파이프라인 (chunk → embed, 변경분만)
  • ETL 잡, 스케줄 유지보수, 큐형 백그라운드 잡
  • 읽고 쓰는 row가 같은 DB에 있고, DB의 백업·PITR 우산 아래 두고 싶을 때

전용 오케스트레이터(Airflow, Temporal)를 쓸 때

  • 무게중심이 Postgres 밖에 있을 때
  • 이기종 서비스로 팬아웃할 때
  • SQL step·분기·루프·HTTP 호출에 깔끔히 매핑되지 않는 임의의 애플리케이션 로직

AI 파이프라인과의 접점

임베딩 파이프라인은 앱 티어에서 예측 가능한 방식으로 실패한다 — 배치 중간의 일시적 API 오류에 공유 체크포인트가 없고, chunk는 썼는데 부모 row를 processed로 표시하기 전에 워커가 죽고, 바뀐 row만 재임베딩할 깔끔한 방법이 없다.

durable execution 위에 올리면 각 AI step이 durable 노드가 되어 embed()가 실패해도 chunk()를 재실행하지 않고, on_change 트리거로 변경분만 처리한다. source/steps/sink/trigger 정의와 실행 이력이 전부 SQL이 되어 기존 트랜잭션·백업·PITR의 보호를 받는다. → RAG

Idempotency와의 관계

durable execution은 idempotency를 대체하지 않고 전제한다. deterministic replay가 성립하려면 재생되는 step이 같은 입력에 같은 결과를 내야 하고, 재시도되는 step은 여러 번 실행돼도 안전해야 한다. DSL이 재시도를 자동화할수록 각 step의 idempotency 설계 책임은 오히려 커진다.

연관 개념


Source: Introducing Durable Functions in PostgreSQL