Supabase만으로 구현하는 내구성이 있는 워크플로우(Durable workflows)와 내구성이 있는 AI 에이전트
요약
Supabase와 resonate-pg를 활용하여 별도의 인프라 없이 데이터베이스만으로 내구성 있는 워크플로우와 AI 에이전트를 구현하는 방법을 소개합니다. Postgres, Edge Functions, pg_cron 등을 결합해 오케스트레이터와 큐를 통합하는 효율적인 아키텍처를 제안합니다.
핵심 포인트
- 별도의 오케스트레이터나 큐 없이 Postgres만으로 내구성 있는 실행 엔진 구현 가능
- Supabase의 Edge Functions를 워커로 활용하여 상태를 체크포인트로 기록
- pg_cron과 pg_net을 이용해 타이머 및 작업 호출 메커니즘 구축
- 무료 티어 환경에서도 매우 빠르고 간편한 설치 및 실행 가능
내구성 있는 실행 (Durable execution)은 보통 새로운 인프라를 의미합니다. 즉, 실행을 위한 오케스트레이터 (orchestrator), 여기에 데이터를 공급할 큐 (queue), 그리고 작업을 깨울 스케줄러 (scheduler)가 필요합니다. resonate-pg는 이 모든 것을 여러분이 이미 보유하고 있는 데이터베이스로 통합합니다. 이것은 단 하나의 SQL 파일 — 1,351줄의 PL/pgSQL — 로 구현된 Resonate Server이며, pg_cron이 설치된 모든 Postgres 16+ 환경에 이를 적용하기만 하면 내구성 있는 실행 엔진 (durable execution engine)이 됩니다.
Supabase에서는 이것이 놀라운 결과를 가져옵니다. 기본 프로젝트에 이미 시스템이 필요로 하는 모든 것이 갖춰져 있기 때문입니다:
- Postgres가 엔진을 실행합니다. 내구성 있는 약속 (Durable promises), 작업 (tasks), 타이머 (timers), 그리고 cron 스케줄 (cron schedules)은 모두 행 (rows)입니다. pg_cron이 유일한 시계이며, pg_net이 유일한 전송 수단 (transport)입니다.
- Edge Functions가 워커 (workers) 역할을 합니다. 데이터베이스가 HTTP를 통해 이들을 호출합니다. 이들은 각 단계를 Postgres에 체크포인트 (checkpoint)로 기록하고 종료합니다. 이들은 상태 (state)를 유지하지 않습니다.
- Realtime은 브라우저로 워크플로우 진행 상황을 스트리밍하며, 인증 (auth), 스토리지 (storage), 벡터 (vectors)가 모두 동일한 프로젝트 내에 존재합니다.
오케스트레이터 서비스도, 큐도, 스케줄러도, 두 번째 데이터베이스도 필요 없습니다. 우리는 이를 처음부터 설정하고, 실행하고, 의도적으로 충돌을 일으키며 모든 것을 측정했습니다. 우리가 발견한 결과는 다음과 같습니다.
내구성 있는 워크플로우를 구축하는 데 5분
아무것도 없는 상태에서의 전체 설치 과정은 다음과 같습니다:
supabase projects create my-durable-app
supabase link --project-ref <ref>
supabase db query --linked "create extension if not exists pg_cron; create extension if not exists pg_net;"
...
그 다음, TypeScript SDK를 사용하여 평범해 보이는 함수를 작성합니다:
import { type Context, Resonate } from "jsr:@resonatehq/supabase";
const resonate = new Resonate();
...
그리고 SQL에서 이를 시작합니다:
select resonate.invoke('countdown-1', 'countdown', '[3]'::jsonb,
'https://<ref>.functions.supabase.co/countdown');
ctx.run은 각 단계가 완료될 때마다 해당 체크포인트(checkpoint)를 Postgres에 기록합니다. ctx.sleep은 마감 기한(deadline)이 포함된 행을 작성하고 반환합니다. 즉, 호출은 종료되며 향후 60초(또는 7일) 동안 워크플로우는 해당 행으로서만 존재하게 됩니다. pg_cron이 타이머를 해결(resolve)하면 pg_net이 새로운 호출을 깨우고, 이 호출은 데이터베이스에서 완료된 단계들을 재실행(replay)하며 계속 진행됩니다.
수치 데이터
아래의 모든 수치는 최적화된 서버가 아닌, 호스팅된 무료 티어 (free-tier) Supabase 프로젝트(us-west-1)에서 측정되었습니다.
| 항목 | 측정값 |
|---|---|
resonate.sql 적용 (서버 설치) | ~3 s |
| ... |
엔진 자체는 거의 비용이 들지 않습니다. 관찰 가능한 지연 시간(latency)은 Supabase의 인프라(plumbing)에서 발생합니다: pg_net의 HTTP 푸시, Edge Function의 시작 시간, 그리고 5초 타이머 틱(tick)입니다.
의도적으로 워커(worker)를 종료했습니다
중요한 주장은 장애 복구(crash recovery)이므로, 우리는 강제로 실패를 유도했습니다. 워크플로우가 단계 A를 체크포인트한 후, Supabase의 런타임이 작업 도중 워커를 강제 종료(hard-kill)할 때까지 비지 루프(busy-loop)를 돌게 한 뒤, (나중에 다시 시도할 때) 단계 B를 실행하도록 했습니다. 각 단계는 로그 행을 작성하므로, 재실행 시 이를 확인할 수 있습니다.
데이터베이스의 타임스탬프를 기준으로 발생한 상황은 다음과 같습니다:
- 15:32:16.224 — 단계 A가 체크포인트를 기록한 후, 워커가 사망합니다.
- 작업 임대(task lease, 기본 5분)가 만료됩니다. Postgres는 다음 틱(tick)에서 작업을 다시 방출합니다.
- 15:37:21.557 — 새로운 호출이 재개되어 A를 건너뜁니다 (A의 결과는 체크포인트로부터 재실행됩니다 — 로그에는 정확히 하나의 "A" 행만 나타납니다). 이후 B를 실행하고 워크플로우를 완료합니다.
어떤 것도 두 번 실행되지 않았습니다. 작업 도중 발생하는 강제 종료로부터의 복구는 작업 임대(task lease) 시간에 의해 제한됩니다.
보장 사항을 정확히 명시하자면: 단계는 정확히 한 번 체크포인트(checkpointed exactly once) 됩니다. 즉, 완료된 단계는 재실행 시 절대 다시 실행되지 않습니다. 부수 효과(side effect)가 발생한 후 체크포인트가 기록되기 전에 중단된 단계는 다시 실행될 것이므로, 부수 효과는 모든 내구성 있는 실행(durable execution) 시스템과 마찬가지로 최소 한 번(at-least-once) 실행을 보장합니다. 부수 효과를 멱등적(idempotent)으로 만들거나, 부수 효과를 단계에서 수행하는 마지막 작업으로 만드세요.
제3의 서비스 없는 내구성 있는 AI 에이전트
이것을 단순한 워크플로우 데모 이상으로 만드는 부분은, 해당 저장소(repo)의 example/research입니다. 이는 Anthropic SDK를 기반으로 구축된 리서치 에이전트(research agent)이며, 프로젝트 내부에서 완전히 실행됩니다.
plan (LLM 호출 1회, 엄격한 도구 스키마 (strict tool schema))
→ N개의 검색을 팬 아웃 (fan out), 각 검색은 개별 Edge Function 호출로 수행
→ 인용된 보고서 합성 (LLM 호출 1회)
검색이 실행되는 동안 부모 프로세스는 폴링(polling)을 하거나 연결을 유지하지 않습니다. 대신 자식 프로세스의 완료를 기다리는 Postgres의 일시 중단된(suspended) 행(row)으로 존재합니다. 우리의 실행 계획은 7개의 검색을 계획했고, 이를 병렬로 실행하여 190초 만에 1,051단어 분량의 인용된 보고서를 반환했습니다.
7개의 검색 중 3개는 호출당 타임아웃(timeout)에 걸렸습니다. 하지만 실행이 중단되지는 않았습니다. 이 예제는 실패한 검색을 건너뛸 수 있는 것으로 처리하므로, 에이전트는 성공한 4개의 검색 결과로부터 보고서를 합성했습니다. 이것이 내구성 있는 에이전트(durable-agent)의 핵심을 한 문장으로 요약한 것입니다 — 실패 처리가 감시 프로세스(babysitting process)가 아닌 애플리케이션 로직(application logic)이 됩니다. 오래 걸리는 LLM 단계는 재배포(redeploys)와 Edge Function의 실행 시간 제한(wall-clock limits)을 견뎌내며, 재시도(retries)는 처음부터 다시 시작하는 대신 체크포인트(checkpointed)를 통해 이루어지고, 몇 시간 동안 작동하는 에이전트는 실제로 사고(thinking)하는 순간에만 컴퓨팅 비용을 발생시킵니다.
한 단계 더 나아간 완전하게 작동하는 예제 저장소도 있습니다. 이는 읽기 전용 SQL 도구와 인간 참여형 일시 중지(human-in-the-loop pause) 기능을 갖춘 내구성 있는 에이전트 루프(생각 → 도구 → 관찰)를 제공합니다. 에이전트가 ask_human을 호출하면 워크플로우는 일시 중단됩니다. 30초가 걸리든 3일이 걸리든, 이는 컴퓨팅 자원을 전혀 사용하지 않는 하나의 Postgres 행일 뿐이며, 누군가 단 한 번의 SQL 호출로 응답하는 즉시 재개됩니다. 모든 LLM 턴(turn)과 도구 실행은 체크포인트이므로, 에이전트는 대화 도중 발생하는 충돌(crashes)이나 재배포(redeploys) 상황에서도 생존합니다. 코드 및 가이드: https://github.com/resonatehq-examples/example-durable-agent-supabase-ts
명확하게 밝히는 주의사항 (Sharp edges)
- 타이머는 5초마다 틱(tick)이 발생합니다. Sleep 및 타임아웃은 다음 틱에 해결됩니다 (평균 +2.5초를 측정했습니다). 이것은 스케줄러(scheduler)이지 스톱워치가 아닙니다.
- 태스크 임대(task lease) 시간은 5분이며, 심(shim)은 하트비트(heartbeat)를 보내지 않습니다. 임대 시간보다 단 한 단계만 늦어져도 중복된 작업이 동시에 실행될 수 있습니다. 버전 펜스(version fence) 덕분에 하나의 완료 처리만 승인되지만, 양쪽의 사이드 이펙트(side effects)는 모두 실행됩니다. 단계를 임대 시간보다 짧게 유지하거나, 단계를 분할하십시오.
- Edge Functions에는 실제 실행 시간(wall-clock) 제한이 있습니다 — 무료 티어는 150초, 유료 티어는 400초입니다. 하나의 긴 호출을 만드는 대신 팬 아웃(Fan out)을 사용하십시오. 그것이 이 모델의 용도입니다.
--no-verify-jwt가 필요합니다 (pg_net은 인증 헤더를 첨부할 수 없습니다). 위조된 태스크 메시지는 데이터베이스에 의해 거부되지만, 엔드포인트 자체는 공개적으로 접근 가능합니다. 프로덕션 환경에서는 핸들러에서 공유 비밀(shared-secret) 헤더를 확인하십시오.- 결과 준비 알림(Result-ready notifications)은 최대 한 번(at-most-once) 보장됩니다 (태스크 전달 자체는 재시도됩니다). 알림을 놓치면 최대 60초의 지연 시간이 발생할 수 있지만, 이는 SDK가 리스너를 재등록하므로 정확성(correctness)의 문제는 아닙니다.
- 프로토콜의 검색/목록(search/list) 호출은 아직 구현되지 않았으며,
gc()는 멱등성 윈도우(idempotency-window)를 위해 히스토리를 희생합니다. 이 기능들에 의존하기 전에 README를 읽어보십시오.
직접 시도해보기
- 서버 + 예제: https://github.com/resonatehq/resonate-pg (Apache-2.0)
- Human-in-the-loop 기능이 포함된 완전한 구현의 내구성 있는 에이전트: https://github.com/resonatehq-examples/example-durable-agent-supabase-ts
- SDK shim: https://jsr.io/@resonatehq/supabase
- 내구성 있는 실행(durable execution)의 작동 원리: https://docs.resonatehq.io/?utm_source=devto&utm_medium=social&utm_campaign=resonate-on-supabase-2026-07
- 질문: https://resonatehq.io/discord
카운트다운 예제는 빈 Supabase 프로젝트에서 실행 중인 내구성 있는 워크플로우 (durable workflow)를 구축하기까지 약 5분이 소요됩니다. 이는 마케팅용 수치가 아닌, 실제로 측정된 시간입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기