pg_durable Azure HorizonDB 내의 지속성 실행 엔진입니다. 이를 통해 장기 실행 다단계 SQL 워크플로(파이프라인, ETL 작업, AI 호출, 예약된 작업, 승인 흐름 포함)를 정의하고 Postgres를 종료하지 않고도 Durable Functions 같은 전용 오케스트레이터에서 기대하는 것과 동일한 안정성 보장으로 워크플로를 실행할 수 있습니다.
pg_durable 는 지속성 AI 파이프라인 아래의 실행 계층이기도 합니다. AI 파이프라인을 사용한다면, pg_durable는 충돌 후에도 중단되지 않고, 실패 시 재시도하며, 마지막으로 완료된 단계부터 재개할 수 있게 해줍니다.
메모
pg_durable는 프리뷰 상태입니다.
"지속성"의 의미
지속성 함수 pg_durable 는 모든 단계마다 디스크에 유지됩니다. 이렇게 하면 일반 BEGIN ... COMMIT 블록 또는 cron 작업에서 얻을 수 없는 특정 보장 집합이 제공됩니다.
- 데이터베이스 충돌 및 재시작에도 견딥니다. 완료된 단계는 서버가 백업될 때 다시 실행되지 않습니다. 진행 중인 단계는 마지막 검사점에서 다시 시작됩니다. 보류 중인 단계는 작업자가 다시 온라인 상태가 되면 실행됩니다.
- 긴 대기에서 살아남습니다. 워크플로는 몇 시간 동안 대기하거나, cron 스케줄이 되기를 기다리거나, 외부 신호를 기다리며 멈춰 있어도, 중단한 지점에서 다시 이어서 실행될 수 있습니다.
- 실패에서 살아남습니다. 실패한 단계는 전체 함수를 다시 실행하지 않고 자동으로 다시 시도될 수 있습니다.
- ID를 캡처합니다. 함수는 백그라운드 작업자의 권한이 아니라 함수를 시작한 사용자의 권한으로 실행됩니다. 다중 테넌트 워크로드는 격리된 상태로 유지됩니다.
- SQL에서 관찰 가능한 상태를 유지합니다. HorizonDB에서 다른 모든 작업에 사용하는 것과 동일한 인터페이스인
SELECT구문을 통해 상태, 기록, 실행 횟수 및 출력을 확인할 수 있습니다.
내구성이 자동으로 수행되지 않는 작업: 비멱등 외부 작업을 자체적으로 다시 시도해도 안전하지 않습니다. 어떤 단계에서 요금이 부과되는 외부 API를 호출하는 경우, 해당 단계를 멱등하게 설계합니다(예를 들어 멱등성 키를 전달하는 방식으로).
pg_durable 사용하는 경우
pg_durable는 해당 작업을 해야 할 때 사용하세요:
- 중간에서 실패할 정도로 충분히 오래 걸립니다(수백만 개 행에 걸친 임베딩 생성, 다단계 ETL 작업, 백필).
- 이미 성공한 부분을 다시 실행하지 않고 오류 발생 시에 다시 시도해야 합니다.
- 일정에 따라 실행해야 합니다(매시간, 평일 오전 9시).
- 외부 이벤트(승인, 웹후크, 다른 시스템의 신호)를 기다려야 합니다.
- 분기, 조인 또는 레이싱을 사용하여 여러 단계를 조정합니다.
- 현재 외부 오케스트레이터 + Postgres 데이터베이스로 구현되며 대부분의 작업은 데이터베이스 부분입니다.
워크로드가 짧은 단일 트랜잭션 구문이라면 pg_durable가 필요하지 않습니다. 일반 INSERT / UPDATE을(를) 사용하세요.
작동 방식
지속성 함수는 SQL DSL을 사용하여 빌드하고 제출하는 단계의 그래프입니다 df.start(). 그래프가 유지되고 백그라운드 작업자가 해당 그래프를 실행합니다.
두 가지 주요 아이디어:
-
함수 그래프 및 실행 상태는 HorizonDB 자체와
df스키마에duroxide저장됩니다. 백업, 지정 시간 복원 및 고가용성이 모두 워크플로 상태에 자동으로 적용됩니다. 관리할 별도의 오케스트레이터 상태가 없습니다. - 백그라운드 작업자는
shared_preload_libraries에 의해 시작됩니다. 이후CREATE EXTENSION확장을 검색하고 함수 실행을 시작합니다. 데이터베이스가 다시 시작되면 작업자가 실행 중인 인스턴스에 다시 연결하고 다시 시작합니다.
메모
pg_durable 내의 실행 엔진은 Rust에 대한 Microsoft 오픈 소스 지속성 실행 런타임인 Duroxide(지속성 작업 프레임워크 및 Temporal에서 영감을 받은)을 기반으로 합니다.
duroxide 스키마 이름은 이를 반영합니다. 여기서 Duroxide는 오케스트레이션 기록, 상관 관계 ID 및 재생 상태를 유지합니다.
pg_durable에서 얻는 결정적 재생, 상관 관계 이벤트 ID 및 내구성 타이머 보장은 Duroxide에서 직접 제공됩니다.
pg_durable 사용
Azure HorizonDB에서 pg_durable 사용하도록 설정하려면 먼저 매개 변수 그룹을 구성한 다음 각 데이터베이스에 확장을 만듭니다.
다음 설정 문서를 사용합니다.
- 서버에 대한 매개 변수 그룹을 만듭니다.
-
shared_preload_libraries을(를)pg_durable을(를) 포함하도록 설정합니다. -
azure.extensions을(를)pg_durable을(를) 포함하도록 설정합니다. - 매개 변수 그룹을 서버에 적용합니다.
- 각 대상 데이터베이스에 연결하고 다음을 실행합니다.
사용하려는 각 데이터베이스에 확장을 만듭니다.
CREATE EXTENSION IF NOT EXISTS pg_durable;
CREATE EXTENSION 는 df 스키마(함수 그래프 및 모니터링 뷰) 및 duroxide 스키마(실행 상태)를 프로비전합니다. 백그라운드 작업자는 몇 초 내에 확장을 검색하고 함수를 실행할 준비가 된 것입니다.
첫 번째 지속성 함수
-- Start a one-step durable function
SELECT df.start('SELECT ''Hello, durable world!''');
-- Returns an 8-character instance ID, for example: a1b2c3d4
-- Check status
SELECT df.status('a1b2c3d4');
-- Get the result
SELECT df.result('a1b2c3d4');
한 단계 함수도 내구성이 있습니다. 데이터베이스가 df.start() 후에 다시 시작되고 워커가 이를 가져가기 전에 재시작되더라도, 함수는 여전히 실행됩니다.
메모
df.start() 는 워크플로를 비동기적으로 제출하고 즉시 반환합니다. 다단계 워크플로의 경우 부작용을 검증하기 전에 완료를 확인하려면 df.list_instances(), df.instance_info(), df.status() 또는 df.result()를 사용합니다.
프로그램 모델
지속성 함수는 단계, 연산자 및 기본 제공 함수에서 빌드된 그래프입니다. 일반 SQL 문자열은 자동 래핑되므로 명시적으로 호출 df.sql() 할 필요가 없습니다.
연산자
| Operator | Meaning | Example |
|---|---|---|
~> |
시퀀스 - 왼쪽 실행 후 오른쪽 실행 | 'SELECT 1' ~> 'SELECT 2' |
& |
조인 - 병렬로 실행, 모든 작업이 완료될 때까지 대기 | 'SELECT 1' & 'SELECT 2' |
| |
레이스 - 병렬로 실행, 첫 번째 승리 | fast_query | df.sleep(30) |
?>
!>
|
If / else - 부울 조건에 따라 분기 | cond ?> then_branch !> else_branch |
@> |
루프 - 영원히 반복(접두사 연산자) | @> body |
|=> |
이름 - 단계의 결과 저장 | 'SELECT id FROM users LIMIT 1' |=> 'user_id' |
유용한 기본 제공 기능
| Function | Purpose |
|---|---|
df.sleep(seconds) |
N초 동안 일시 중지합니다. 재시작 후에도 유지됩니다. |
df.wait_for_schedule(cron) |
다음에 cron 식이 일치할 때까지 기다립니다. |
df.wait_for_signal(name, timeout) |
외부 df.signal() 가 도착할 때까지 차단합니다. |
df.http(url, method, body, headers, timeout) |
일시적 오류 시 다시 시도하여 HTTP 호출을 지속성 작업으로 만듭니다. |
df.if(cond, then, else) |
조건부 분기. |
df.loop(body, cond) |
SQL 조건이 진실된 동안 반복합니다. |
df.join(a, b) / df.race(a, b) |
병렬 및 레이스 실행. |
df.join3(a, b, c) |
3방향 병렬 실행의 경우 |
df.start(body, label, database) |
지속성 함수를 제출하고 해당 인스턴스 ID를 반환합니다. |
df.cancel(id, reason) |
실행 중인 인스턴스를 취소합니다. |
df.status(id) / df.result(id) |
결과를 검사합니다. |
df.explain(input) |
시각화를 위한 함수 그래프를 렌더링합니다. |
모든 pg_durable 기능에 대해 자세히 알아보세요.
변수
|=> 는 이름으로 단계의 결과를 캡처합니다. 이후 단계에서는 이 단계를 .로 $name참조합니다.
SELECT df.start(
'SELECT 100 AS amount' |=> 'total'
~> 'SELECT $total * 2 AS doubled'
);
사용 예제
재시도 기능이 있는 다중 단계 ETL
매일 정리하고, 로드하고, 인덱싱하고, 기록하는 ETL:
SELECT df.start(
'DELETE FROM target WHERE loaded_at < now() - INTERVAL ''1 day'''
~> 'INSERT INTO target SELECT * FROM staging'
~> 'REINDEX TABLE target'
~> 'INSERT INTO etl_log (job, finished_at) VALUES (''nightly'', now())',
'nightly-etl'
);
데이터베이스가 DELETE와 INSERT 사이에 다시 시작되면 워커는 INSERT부터 재개되며 DELETE는 다시 실행하지 않습니다.
예약된 작업(cron)
평일 오전 9시에 유지 관리 작업을 실행합니다.
SELECT df.start(
@> (
df.wait_for_schedule('0 9 * * 1-5')
~> 'CALL refresh_materialized_views()'
),
'weekday-refresh'
);
이 작업을 중지하려면 함수를 cancel 실행할 수 있습니다.
SELECT df.cancel('a1b2c3d4', 'stop test cron job');
시간 제한이 있는 승인 워크플로
외부 승인 신호에 대해 최대 24시간을 기다린 다음 커밋하거나 거부합니다.
SELECT df.start(
'SELECT order_id, total FROM orders WHERE id = 1' |=> 'order'
~> df.wait_for_signal('approval', 86400) |=> 'sig'
~> df.if(
'SELECT NOT ($sig::jsonb->>''timed_out'')::boolean
AND ($sig::jsonb->''data''->>''approved'')::boolean',
'UPDATE orders SET status = ''approved'' WHERE id = $order_id',
'UPDATE orders SET status = ''rejected'' WHERE id = $order_id'
),
'order-approval'
);
-- Later, approve from anywhere
SELECT df.signal('a1b2c3d4', 'approval',
'{"approved": true, "approver": "jane@contoso.com"}');
지속성 HTTP 호출
df.http() 는 외부 호출을 지속성 활동으로 만듭니다. 5xx 응답, 네트워크 오류 및 시간 제한은 자동으로 다시 시도됩니다.
SELECT df.start(
df.http('https://api.example.com/users/123', 'GET') |=> 'user'
~> 'INSERT INTO users_cache (data) VALUES (($user::jsonb->>''body'')::jsonb)',
'fetch-user'
);
pg_durable 허용된 HTTP 보안에 대해 자세히 알아보세요.
관찰 및 조작
SQL에서 모든 항목을 쿼리할 수 있습니다. 배울 별도의 UI 또는 서비스가 없습니다.
-- All instances
SELECT * FROM df.list_instances();
-- Filter by status
SELECT * FROM df.list_instances() WHERE status = 'Running';
SELECT * FROM df.list_instances() WHERE status = 'Failed';
-- Detail for one instance
SELECT * FROM df.instance_info('a1b2c3d4');
-- Execution history (useful for retried or looped functions)
SELECT * FROM df.instance_executions('a1b2c3d4', 20);
-- The function graph as it ran
SELECT * FROM df.instance_nodes('a1b2c3d4');
-- System-wide metrics
SELECT * FROM df.metrics();
작업자가 활성 상태인지 확인하려면 다음을 수행합니다.
SELECT epoch_id, last_seen_at, now() - last_seen_at AS time_since_last_heartbeat
FROM df._worker_epoch;
time_since_last_heartbeat 15초 미만은 작업자가 정상임을 의미합니다. 더 크거나 행이 전혀 없는 것은 작업자가 다운되었거나 초기화되지 않았음을 의미합니다.
Visual Studio Code 워크플로 모니터링
Visual Studio Code용 PostgreSQL 확장 기능에는 Pipelines & Workflows 보기의 Workflows 탭이 포함되어 있으며, 여기에서 편집기에서 pg_durable 워크플로 인스턴스를 검사하고 실행 상태를 모니터링할 수 있습니다.
워크플로 창 열기
- Visual Studio Code에서 PostgreSQL 확장 프로그램을 엽니다.
- 개체 탐색기 데이터베이스를 마우스 오른쪽 단추로 클릭합니다.
- 파이프라인 및 워크플로를 선택합니다.
- 워크플로 탭 을 선택합니다.
왼쪽 창에는 PG 지속성 실행이 나열되고 가운데 창에는 선택한 워크플로 인스턴스에 대한 세부 정보가 표시됩니다.
워크플로 실행 검사
워크플로 실행을 선택하면 요약을 검토하여 유효성을 검사합니다.
-
상태:
completed,running또는failed. - 실행 ID: 인스턴스의 고유 식별자입니다.
- 시작된 시간 및 기간: 실행 진행률 및 성능을 추적합니다.
- 세부 정보 패널: 추가 실행 메타데이터.
사용 가능한 탭을 사용하여 자세히 알아봅니다.
- 그래프: 워크플로 구조 및 단계 흐름을 보여 주는 시각적 단계별 실행 보기입니다.
- 타이밍: 성능 분석 및 병목 현상 식별을 위한 기간 중심 보기입니다.
- 결과: 워크플로 실행의 출력 및 결과 지향 세부 정보입니다.
AI 파이프라인과 관련된 워크플로의 경우 파이프라인 정의 보기 작업(사용 가능한 경우)을 사용하면 워크플로 실행에서 파이프라인 정의로 다시 연결할 수 있습니다. 이 작업은 실행 간에 동작을 비교하거나 회귀를 조사하는 데 유용합니다.
ID 및 격리
지속성 함수는 작업자의 권한이 아니라 제출한 사용자의 권한으로 실행됩니다.
pg_durable는 제출 시점에 session_user와 current_user를 모두 캡처하므로 SET ROLE 컨텍스트에서 제출된 함수는 그 유효 역할로 실행됩니다.
이것은 다음을 의미합니다.
- 사용자는 액세스 권한이 이미 있는 데이터만 보고 수정합니다.
- 슈퍼 사용자 이외의 사용자는 지속성 함수를 제출하여 권한을 에스컬레이션할 수 없습니다.
- 역할 및 권한 부여 모델이 올바른 한 다중 테넌트 워크로드는 격리된 상태로 유지됩니다.
복제본, 백업 및 PITR과의 상호 작용
- 백업 및 PITR. 함수 그래프(
df스키마) 및 실행 상태(duroxide스키마)는 일반 테이블에 저장되며 HorizonDB 백업에 포함됩니다. 특정 시점 복원은 둘 다 복원합니다. - 읽기 복제본. 백그라운드 작업자는 주 서버에서만 실행됩니다. 읽기 복제본은
df.*모니터링 뷰를 조회할 수 있지만 함수를 실행하지는 않습니다. - 장애 조치(Failover). 장애 조치 후 새 주 복제본의 작업자는 이전 주 복제본이 중단한 지점부터 작업을 이어갑니다. 실행 중인 인스턴스는 마지막 검사점에서 다시 시작됩니다.
외부 오케스트레이터와 비교
| 양상 | 외부 오케스트레이터 | pg_durable |
|---|---|---|
| Deployment | 별도의 서비스, 별도의 ID, 별도의 상태 저장소 | 하나의 데이터베이스 |
| 상태 내구성 | 오케스트레이터의 스토리지 계층 | 데이터와 같은 수준의 백업, HA 및 PITR |
| 아이덴티티 | 작업자가 서비스 ID로 실행 | 제출하는 사용자로 실행되는 함수 |
| 실패 모드 | 오케스트레이터와 데이터베이스 간의 네트워크 | 없음 - 동일한 프로세스 |
| 적합한 대상 | 많은 서비스에 영향을 주는 시스템 간 오케스트레이션 | 대부분의 작업이 Postgres에 있거나 근처에 있는 워크로드 |
pg_durable 는 시스템 간 파이프라인에 대한 외부 오케스트레이터를 대체하려고 하지 않습니다. 대부분의 작업이 데이터베이스 작업(포함, 변환, AI 호출, 예약된 유지 관리)이며 다른 서비스를 추가하는 것이 이점보다 비용이 더 많이 드는 경우에 적합합니다.
미리 보기 중 제한 사항
-
df.http()5xx 및 네트워크 오류에서 재시도합니다. 처리할 워크플로에 4xx 응답이 반환됩니다. 자동으로 다시 시도되지 않습니다. - 백그라운드 작업자는 인스턴스당 단일 데이터베이스를 서비스합니다. 다중 데이터베이스 팬아웃은 작업자 데이터베이스에서 실행되는 함수를 통해
df.start(..., database => 'other_db')지원됩니다. - 함수 정의 및 실행 상태는
pg_durable중에는 의 주요 버전 간에 이식할 수 없습니다. 업그레이드하기 전에 실행 중인 인스턴스를 드레이닝하거나 취소합니다.