AI 에이전트 내구성이 있는 큐 (Durable Queue): 작업 도중 중단되는 장기 실행 작업 방지하기
요약
AI 에이전트의 장기 실행 작업 중 중단 문제를 해결하기 위한 '내구성이 있는 큐(Durable Queue)' 아키텍처를 소개합니다. 에이전트의 상태를 메모리가 아닌 지속성 기반의 레코드로 관리하여 복구 가능한 워크플로우를 구축하는 방법을 다룹니다.
핵심 포인트
- 단순 while 루프 방식의 에이전트는 프로세스 재시작 시 상태를 상실함
- 내구성 있는 큐는 작업을 run, step, event, artifact 레코드로 분할 관리함
- 결제나 데이터 임포트 수준의 신뢰성(멱등성, 재시도, 감사 로그)이 필요함
- 상태를 복구 가능한 레코드로 저장하여 작업 중단 및 타임아웃에 대응함
AI 에이전트가 사용자를 실망시키기 위해 반드시 틀릴 필요는 없습니다. 때로는 유용한 작업을 수행하던 도중에 작업자(worker)가 재시작되거나, 모델 호출(model call)이 타임아웃(timeout)되거나, 도구(tool)가 멈춰버려, 전체 작업이 마치 존재하지 않았던 것처럼 사라지기도 합니다.
로그상으로는 작은 실패처럼 보일지 몰라도 고객에게는 매우 큰 문제입니다. 고객은 에이전트에게 계정 분석, 마이그레이션 계획 초안 작성, 문서 처리, 레코드 업데이트 또는 보고서 작성을 요청했습니다. 에이전트는 바쁘게 움직이는 것처럼 보였습니다. 그러다 아무 일도 일어나지 않았습니다.
만약 여러분이 프로덕션(production) 환경의 AI 워크플로우(workflow)를 구축하고 있다면, 해결책은 더 긴 프롬프트(prompt)가 아닙니다. 여러분에게는 내구성이 있는 큐(durable queue)가 필요합니다. 즉, 에이전트의 작업을 하나의 프로세스 내에서 살아있는 취약한 루프(loop)가 아니라, 복구 가능한 상태(recoverable state)로 저장하는 시스템이 필요합니다.
이 가이드는 장기 실행 에이전트가 작업 결과물을 확실히 완수하기를 원하는 개인 개발자, Micro SaaS 빌더, 그리고 AI 제품 팀을 위한 실질적인 아키텍처(architecture)를 안내합니다.
프로덕션 환경에서 에이전트 작업이 중단되는 이유
첫 번째 에이전트 프로토타입(prototype)은 대개 while 루프 형태입니다. 모델을 호출하고, 도구 결과를 추가하며, 모델이 최종 답변을 반환할 때까지 반복하는 방식입니다. 데모용으로는 괜찮습니다. 하지만 실제 고객의 작업을 처리할 때는 위험합니다. 왜냐하면 중요한 모든 정보가 메모리(memory)에 상주하기 때문입니다:
- 대화 상태 (conversation state)
- 현재 도구 호출 (current tool call)
- 재시도 횟수 (retry count)
- 모델 선택 (model choice)
- 사용자 및 테넌트 컨텍스트 (user and tenant context)
- 승인 상태 (approval status)
- 부분적 출력 (partial output)
- 단계 실패 원인 (reason a step failed)
프로세스가 재시작되면 스택(stack)은 사라집니다. 도구 실행은 성공했지만 응답 쓰기에 실패한다면, 작업을 중복해서 수행할 수도 있습니다. 모델 호출이 타임아웃되면 재시도해야 할지, 일시 중지해야 할지, 아니면 작업을 실패로 표시해야 할지 알 수 없게 됩니다.
장기 실행되는 AI 작업은 결제, 데이터 임포트(import), 빌링(billing) 작업, 웹훅(webhook)과 동일한 신뢰성 요구 사항을 가집니다. 즉, 내구성 있는 상태(durable state), 임대(leases), 멱등성(idempotency), 재시도(retries), 그리고 감사 로그(audit logs)가 필요합니다.
AI 에이전트 내구성이 있는 큐(Durable Queue)란 무엇인가?
AI 에이전트 내구성이 있는 큐는 에이전트 작업을 위한 지속성 기반의 실행 계층(persistence-backed execution layer)입니다.
전체 에이전트 작업을 하나의 요청(request)이나 하나의 워커 스택(worker stack) 내에서 실행하는 대신, 작업을 내구성이 있는 레코드(durable records)로 분할합니다:
- 전체 사용자 요청을 위한
run레코드 (run record) - 각 모델 호출(model call), 도구 호출(tool call), 승인(approval) 또는 검증(verification)을 위한
step레코드 (step record) - 모든 중요한 상태 전이(state transition)를 위한
event레코드 (event record) - 파일, 요약, 보고서 또는 생성된 출력물을 위한
artifact레코드 (artifact record) - 무엇이 왜 일어났는지 설명하는
receipt레코드 (receipt record)
일반적인 작업 큐(task queue)는 "이 백그라운드 작업을 실행하세요"라고 말합니다. 하지만 내구성이 있는 에이전트 큐(durable agent queue)는 "모든 단계를 기억하고, 안전하게 재개하며, 중복된 부작용(side effects)을 방지하고, 발생한 일을 증명하며, 필요할 때 인간의 검토를 위해 일시 중지하세요"라고 말합니다.
이러한 차이는 중요합니다. 왜냐하면 단일 사용자 요청에는 검색(retrieval), 계획(planning), 도구 호출(tool calls), 파일 생성, 데이터베이스 업데이트, 승인 및 최종 검증이 포함될 수 있기 때문입니다.
내구성 있는 큐의 멘탈 모델 (The durable queue mental model)
에이전트를 채팅 루프(chat loop)가 아닌 상태 머신(state machine)으로 생각하세요.
실행(run)은 다음과 같은 상태를 거칩니다:
queued -> planning -> waiting_for_tool -> waiting_for_approval
-> verifying -> completed
-> failed
...
각 전이(transition)는 다음의 위험한 동작을 수행하기 전에 기록됩니다. 이를 통해 복구 지점(recovery point)을 확보할 수 있습니다.
모델을 호출하는 동안 워커(worker)가 충돌하더라도, 다른 워커가 마지막으로 커밋된 상태를 조사하여 작업을 계속할 수 있습니다. 만약 도구 호출이 이미 레코드를 생성했다면, 시스템은 멱등성 키(idempotency key)를 감지하여 작업을 두 번 수행하는 것을 방지할 수 있습니다. 만약 특정 단계에서 승인이 필요하다면, 실행은 영리하게 행동하려 애쓰는 대신 일시 중지됩니다.
목표는 실패를 불가능하게 만드는 것이 아닙니다. 목표는 실패를 가시화하고, 복구 가능하게 하며, 안전하게 만드는 것입니다.
간단한 데이터베이스 스키마 (A simple database schema)
복잡한 인프라를 찾기 전에 Postgres를 사용하여 이를 구축할 수 있습니다. 작게 시작하세요.
create table agent_runs (
id uuid primary key,
tenant_id uuid not null,
...
이 스키마는 화려하지 않지만 다음과 같은 기초를 제공합니다:
- 실행(runs)을 쿼리할 수 있음
- 단계(steps)를 재현(replayable)할 수 있음
- 중복된 도구 호출을 차단할 수 있음
- 실패에 구조화된 컨텍스트(context)가 제공됨
- 지원 팀이 발생한 일을 조사할 수 있음
- 나중에 메트릭(metrics)을 계산할 수 있음
규모가 작은 팀에게는 지루한 테이블이 마법 같은 에이전트 메모리보다 훨씬 낫습니다.
희망에 맡기지 말고 리스(lease)로 작업 점유하기
흔히 하는 실수 중 하나는 여러 작업자(worker)가 동일한 작업을 동시에 가져가게 두는 것입니다. 또 다른 실수는 작업이 중단된(dead) 작업자가 작업을 영원히 붙잡고 있게 두는 것입니다.
리스(lease)를 사용하세요. 작업자는 짧은 기간 동안 실행(run)을 점유합니다. 작업자가 계속해서 진행 상황을 만들어내면 리스를 연장합니다. 만약 작업자가 중단되면 리스는 만료되고 다른 작업자가 작업을 재개할 수 있습니다.
update agent_runs
set leased_by = $1,
leased_until = now() + interval '60 seconds',
...
skip locked는 유용합니다. 여러 작업자가 서로를 차단(blocking)하지 않고도 안전하게 작업할 내용을 찾을 수 있기 때문입니다.
리스는 빠르게 복구할 수 있을 만큼 충분히 짧게 유지하되, 불필요한 탈취(noisy takeover)를 피할 수 있을 만큼은 길게 유지해야 합니다. 많은 에이전트 워크플로우(workflow)의 경우, 30초에서 120초 사이가 합리적인 시작점입니다.
에이전트 루프를 복구 가능한 단계로 분할하기
에이전트 실행 전체를 하나의 불투명한(opaque) 작업으로 취급하지 마세요. 의미 있는 각 작업이 일어나기 전과 후에 이를 저장해야 합니다.
type StepType = "model_call" | "tool_call" | "approval" | "verify";
async function executeNextStep(runId: string) {
...
다음 패턴에 주목하세요:
- 의도(intent)를 기록합니다.
- 위험한 작업을 수행합니다.
- 결과를 기록합니다.
- 다음 단계를 결정합니다.
이러한 시퀀스(sequence)는 워크플로우를 검사 가능(inspectable)하게 만듭니다. 또한 예산, 정책, 승인 및 테스트를 추가할 수 있는 깔끔한 지점을 제공합니다.
모든 도구 호출을 멱등(idempotent)하게 만들기
에이전트는 도구(tool)를 호출합니다. 도구는 부수 효과(side effect)를 발생시킵니다. 부수 효과는 신뢰성 버그가 고객의 고통으로 변하는 지점입니다.
만약 에이전트가 동일한 인보이스를 두 번 보내거나, 동일한 파일을 두 번 삭제하거나, 동일한 메시지를 두 번 게시한다면, 사용자는 모델이 똑똑했다는 사실에 전혀 신경 쓰지 않을 것입니다.
각 도구 호출에는 멱등성 키(idempotency key)가 필요합니다. 이 키는 단순히 무작위 재시도(retry) 시도를 나타내는 것이 아니라, 의도된 효과를 나타내야 합니다.
function toolIdempotencyKey(runId: string, toolName: string, args: unknown) {
return hashJson({
runId,
...
도구를 실행하기 전에, 동일한 키가 이미 완료되었는지 확인하세요.
async function runToolStep(step: AgentStep) {
const previous = await db.findCompletedStepByKey(
step.run_id,
...
외부 API의 경우, API가 지원한다면 멱등성 키 (Idempotency key)를 함께 전달하세요. 내부 쓰기 (Internal writes)의 경우, 생성된 행 (Row)에 해당 키를 저장하세요.
멱등성 (Idempotency)은 결제만을 위한 것이 아닙니다. 재시도할 수 있는 모든 에이전트 작업 (Agent action)에 적용됩니다.
실패 유형별 재시도 정책 (Retry policy) 사용
내구성이 있는 큐 (Durable queue)는 모든 것을 맹목적으로 재시도해서는 안 됩니다.
실패 유형에 따라 서로 다른 정책을 사용하세요:
| 실패 유형 | 재시도 여부 | 조치 |
|---|---|---|
| 모델 타임아웃 (Model timeout) | 예 | 백오프 (Backoff)와 함께 재시도 후, 폴백 모델 (Fallback model) 사용 |
| ... |
다음은 간단한 재시도 결정 함수입니다:
function retryDecision(error: AgentError, attempt: number) {
if (error.kind === "rate_limit") {
return { retry: true, delayMs: error.retryAfterMs ?? 60_000 };
...
핵심은 재시도가 숨겨진 비용 누출 (Cost leak)로 이어지는 것을 방지하는 것입니다. 모든 재시도에는 이유와 제한, 그리고 추적 (Trace)이 있어야 합니다.
승인 일시 중지 (Approval pauses)를 일급 큐 상태 (First-class queue states)로 추가
장기 실행 에이전트는 종종 스스로 계속 진행해서는 안 되는 순간에 도달합니다:
- 이메일 전송
- 결제 데이터 변경
- 레코드 삭제
- 콘텐츠 게시
- 고객 노출 설정 업데이트
- 고비용 작업 실행
- 민감한 통합 (Integration) 사용
"필요할 때 승인을 요청하세요"라고 말하는 프롬프트로 이를 처리하지 마세요. 승인을 내구성이 있는 단계 (Durable step)로 저장하세요.
{
"type": "approval",
"status": "waiting",
...
실행 (Run)은 적절한 사람이 작업을 승인, 거절 또는 수정할 때까지 일시 중지 상태로 유지됩니다.
이를 통해 깔끔한 제품 동작을 구현할 수 있습니다:
- 대기하는 동안 워커 (Workers)가 헛돌지 않음
- 사용자가 나중에 재개할 수 있음
- 검토자가 제안된 정확한 작업을 확인 가능
- 감사 로그 (Audit logs)를 통해 누가 무엇을 승인했는지 표시
- 에이전트가 게이트 (Gate)를 몰래 건너뛸 수 없음
승인 게이트는 신뢰를 보호할 때 마찰 (Friction)이 아닙니다. 그것은 워크플로 계약 (Workflow contract)의 일부입니다.
채팅 기록과 별도로 아티팩트 (Artifacts) 저장
장기 실행 에이전트는 메시지 이상의 것을 생성합니다. 파일, 보고서, 디프 (Diffs), SQL 쿼리, 차트, 요약 또는 추출된 데이터를 생성할 수 있습니다.
그러한 아티팩트(Artifacts)들을 거대한 프롬프트 트랜스크립트(Prompt transcript) 안에 묻어두지 마세요. 이를 별도의 레코드(Records)로 저장한 다음, 관련 있는 아티팩트 요약본만 모델에 다시 전달하세요. 이렇게 하면 컨텍스트(Context)를 작게 유지할 수 있으며 최종 출력물을 검증하기가 더 쉬워집니다.
예를 들어, 데이터 정제 에이전트(Data-cleaning agent)는 다음과 같은 것들을 저장할 수 있습니다:
- 업로드된 파일 스냅샷 (Uploaded file snapshot)
- 스키마 요약 (Schema summary)
- 정제 계획 (Cleaning plan)
- 생성된 파이썬(Python) 스크립트
- 검증 보고서 (Validation report)
- 최종 CSV
- 사용자용 요약 (User-facing summary)
모델은 매 호출마다 파일 전체를 필요로 하지 않습니다. 모델에게 필요한 것은 적절한 요약, 적절한 링크, 그리고 적절한 제약 조건(Constraints)입니다.
실행 완료 전 검증하기
내구성이 있는 큐(Durable queue)는 모델이 확신에 찬 최종 답변을 작성했다는 이유만으로 작업을 완료로 표시해서는 안 됩니다.
검증 단계(Verification step)를 추가하세요.
결정론적 작업(Deterministic tasks)의 경우, 검증은 코드로 수행될 수 있습니다:
async function verifyReport(runId: string) {
const artifacts = await db.listArtifacts(runId);
const report = artifacts.find(a => a.artifact_type === "final_report");
...
퍼지 작업(Fuzzy tasks)의 경우, 혼합된 접근 방식을 사용하세요:
- 스키마 검증 (Schema validation)
- 필수 섹션 체크 (Required section checks)
- 인용 체크 (Citation checks)
- 정책 체크 (Policy checks)
- 비용 체크 (Cost checks)
- 좁은 범위의 루브릭(Rubrics)을 위한 LLM-as-judge
- 위험한 출력물에 대한 인간의 검토 (Human review)
유용한 완료 규칙: 에이전트는 큐가 무엇이 변경되었는지, 무엇이 이를 뒷받침하는 증거인지, 그리고 무엇이 미결 상태로 남아 있는지를 설명할 수 있을 때에만 완료된 것으로 간주합니다.
첫날부터 추적해야 할 지표
시작할 때 거대한 관측성 스택(Observability stack)이 필요하지는 않습니다. 큐가 에이전트 작업의 신뢰성을 높이고 있는지 보여주는 지표들을 추적하세요.
다음 항목부터 시작하세요:
- 실행 완료율 (Run completion rate)
- 실행 지속 시간의 중앙값 및 p95 (Median and p95 run duration)
- 실행당 재시도 횟수 (Retries per run)
- 도구 이름별 도구 실패 (Tool failures by tool name)
- 제공자별 모델 실패 (Model failures by provider)
- 승인 대기 시간 (Approval wait time)
- 방지된 중복 도구 호출 (Duplicate tool calls prevented)
- 만료된 임대(Lease) 후 복구된 실행 (Runs recovered after expired lease)
- 완료된 실행당 비용 (Cost per completed run)
- 사용자 가시적 실패율 (User-visible failure rate)
가장 중요한 지표는 성공적인 결과당 비용(Cost per successful outcome)입니다. 저렴하게 실패한 실행은 여전히 낭비입니다.
실질적인 구축 순서
만약 이것을 처음부터 구축하고 있다면, 처음부터 모든 것을 다 해결하려 하지 마세요 (do not boil the ocean). 실행 (runs), 단계 (steps), 이벤트 (events), 임대 (leases), 멱등성 있는 도구 호출 (idempotent tool calls), 타입화된 재시도 (typed retries), 승인 일시 중지 (approval pauses), 아티팩트 (artifacts), 검증 (verification), 그리고 고객에게 보이는 상태 (customer-visible status)부터 시작하세요.
1인 개발자의 경우, Postgres와 하나의 워커 프로세스 (worker process)만으로도 시작하기에 충분합니다. 규모가 큰 팀은 나중에 제품 데이터베이스에 내구성 있는 실행 상태 (durable run state)와 영수증 (receipts)을 유지하면서, 디스패칭 (dispatching)을 전용 큐 (dedicated queues)나 워크플로 엔진 (workflow engines)으로 옮길 수 있습니다.
결론
장기 실행되는 AI 에이전트는 단순히 더 똑똑한 프롬프트 (prompt)가 아닙니다. 그것은 신뢰할 수 없는 모델 (unreliable models), 불안정한 API (flaky APIs), 인간의 승인 (human approvals), 가변적인 비용 (variable cost), 그리고 실제 부작용 (real side effects)을 동반하는 분산 워크플로 (distributed workflow)입니다.
핵심 패턴은 간단합니다: 실행을 영속화하고 (persist the run), 각 단계를 영속화하며 (persist each step), 임대 (leases)를 사용하고, 도구를 멱등하게 만들며 (make tools idempotent), 신중하게 재시도하고 (retry carefully), 승인을 위해 일시 중지하며 (pause for approval), 완료를 검증하고 (verify completion), 영수증을 남기는 것 (leave a receipt)입니다.
FAQ
AI 에이전트 내구성이 있는 큐 (durable queue)란 무엇인가요?
AI 에이전트 내구성이 있는 큐는 에이전트 실행 (runs), 단계 (steps), 이벤트 (events), 아티팩트 (artifacts), 재시도 (retries), 승인 (approvals), 그리고 결과 (results)를 저장하는 영속성 기반의 실행 시스템 (persistence-backed execution system)입니다. 이를 통해 장기 실행되는 AI 작업이 충돌 (crashes)이나 타임아웃 (timeouts) 발생 후에도 재개될 수 있습니다.
일반적인 백그라운드 작업 큐 (background job queue)로 AI 에이전트에게 충분할까요?
일반적인 큐는 좋은 시작점이지만, AI 에이전트는 보통 더 많은 상태 (state)를 필요로 합니다. 단계 이력 (step history), 도구 멱등성 (tool idempotency), 승인 일시 중지 (approval pauses), 모델 사용량 (model usage), 아티팩트 (artifacts), 그리고 검증 영수증 (verification receipts)이 필요합니다. 기본적인 큐는 워커 (worker)를 실행할 수 있지만, 내구성 있는 에이전트 상태 (durable agent state)는 애플리케이션 데이터베이스나 워크플로 저장소 (workflow store)에 존재해야 합니다.
에이전트가 동일한 도구 작업을 반복하는 것을 어떻게 막을 수 있나요?
부작용을 일으키는 모든 도구 호출 (tool call)에 멱등성 키 (idempotency keys)를 사용하세요. 해당 키를 도구 단계 (tool step)와 함께 저장하고, 가능한 경우 이를 외부 API에 전달하세요. 재시도 시에는 작업을 다시 실행하기 전에 동일한 키가 이미 완료되었는지 확인하십시오.
에이전트 워커가 충돌하면 어떻게 해야 하나요?
워커 임대 (worker lease)가 만료되어야 합니다. 다른 워커가 해당 실행 (run)을 점유하여 마지막으로 완료된 단계를 점검하고, 다음 안전한 상태 (safe state)부터 작업을 계속해야 합니다. 만약 이전 단계가 부수 효과 (side effect)를 일으켰을 가능성이 있다면, 새로운 워커는 재시도하기 전에 멱등성 기록 (idempotency records)을 사용해야 합니다.
AI 에이전트는 언제 인간의 승인을 요청해야 하나요?
메시지를 전송하거나, 비용을 지출하거나, 중요한 데이터를 삭제 또는 수정하거나, 외부로 게시하거나, 민감한 통합 (integrations)에 접근하거나, 비용 및 리스크 임계값 (thresholds)을 초과하는 작업에 대해서는 승인을 요구해야 합니다. 승인은 단순히 프롬프트 지침 (prompt instructions)에만 맡겨두지 말고, 내구성이 있는 단계 (durable step)로 저장되어야 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기