프로덕션 확장 — 큐(Queue), 체크포인트(Checkpoint), 내구성(Durability)

수천 개의 동시 실행(concurrent run)을 받아내는 멀티 에이전트 시스템(multi-agent system)으로 확장하려면 내구성 있는 실행(durable execution)이 필요합니다. LangGraph 런타임(runtime)은 thread_id를 키(key)로 삼아 슈퍼 스텝(super-step)마다 체크포인트(checkpoint)를 기록합니다(기본 저장소는 Postgres입니다). 워커(worker)가 죽으면 리스(lease)가 해제되고 다른 워커가 이어서 처리(resume)합니다. 에이전트는 사람의 입력을 기다리며 무기한 잠들어 있을 수도 있습니다. MegaAgent(arXiv:2408.09955)는 에이전트마다 생산자-소비자(producer-consumer) 큐를 두고 세 가지 상태(Idle / Processing / Response)와 두 계층의 조율(intra-group chat + inter-group admin chat)을 결합해 운영했습니다. LLM 스트리밍(streaming) 작업에서는 파이버/비동기(fiber/async) 방식이 작업당 스레드(thread-per-job)를 두는 방식보다 더 잘 맞습니다. 스레드는 다음 토큰(token)을 기다리느라 99%의 시간을 놀고 있지만, 파이버는 입출력(I/O) 시점에 자발적으로(cooperatively) 양보합니다. 반론도 있습니다. 아쉬프리트 베디(Ashpreet Bedi)의 "Scaling Agentic Software"는 부하(load)가 실제로 증명되기 전까지는 FastAPI + Postgres, 그 외에는 아무것도 더하지 말 것을 권합니다. 단순한 아키텍처(architecture)는 우리가 생각하는 것보다 멀리 갑니다. 이 강의에서는 내구성 있는 체크포인트 로그(log), 상태 전이를 가진 에이전트별 작업 큐(per-agent work queue), 비동기 대 스레드 비교 데모를 직접 만들고, 실용적인 "단순하게 시작하라(start simple)" 원칙을 함께 정리합니다.

유형: Learn + Build 언어: Python (stdlib, asyncio, sqlite3) 선수 지식: Phase 16 · 09 (병렬 Swarm과 네트워크형 아키텍처), Phase 16 · 13 (공유 메모리) 예상 시간: 약 75분

문제

세 개의 에이전트가 인메모리 이벤트 루프(in-memory event loop) 위에서 잘 돌아가는 프로토타입(prototype) 멀티 에이전트 시스템이 노트북 한 대에서 동작하고 있다고 가정합니다. 이제 이를 프로덕션(production)으로 옮깁니다.

  • 에이전트가 몇 시간씩 실행될 수 있습니다(긴 리서치, 사람의 개입(human-in-the-loop)이 필요한 대기 등).
  • 워커 프로세스(worker process)가 종종 죽습니다. 재시작하면 상태(state)를 잃습니다.
  • 피크 부하(peak load)가 평균의 10배에 달합니다. 수평 확장(horizontal scaling)이 필요합니다.
  • 사용자는 에이전트 실행(agent-run) 단위로 비용을 지불합니다. 과금에는 정확히 한 번(exactly-once) 의미론(semantics)이 필요합니다.

인메모리 이벤트 루프는 이 중 어느 것도 해결해 주지 않습니다. 그 아래에 내구성 있는 실행 계층(durable execution layer)이 필요합니다. 2026년 시점의 정석적인(canonical) 선택지는 다음과 같습니다.

  1. 체크포인트를 가진 워크플로 엔진(workflow engine) — Temporal, LangGraph 런타임 등.
  2. 상태 저장소(state store)와 결합된 메시지 큐(message queue) — Postgres + SQS/RabbitMQ 등.
  3. 액터 모델(actor-model) 프레임워크 — MegaAgent의 에이전트별 생산자-소비자 구조 등.
  4. 직접 짠 FastAPI + Postgres 조합 — 베디(Bedi)의 주장입니다.

이 강의에서는 위 네 가지 각각의 축소판(miniature)을 만들어 봅니다.

사전 테스트

2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요

1.'내구성 있는 실행(durable execution)'이란 무엇이고, 멀티 에이전트 시스템에 왜 필요한가요?

2.Bedi의 'Scaling Agentic Software'는 대부분의 팀이 에이전트 인프라를 과잉 설계한다고 주장합니다. 권장 출발점은 무엇인가요?

0/2 답변 완료

개념

내구성 있는 실행 패턴

내구성 있는 실행 엔진은 각 "스텝(step)"(LangGraph 용어로는 슈퍼 스텝(super-step)) 이후의 전체 프로그램 상태를 영속화(persist)합니다. 충돌이 나면 다음과 같이 처리됩니다.

worker crashes mid-step
  -> lease timeout
  -> another worker picks up the thread_id
  -> resumes from last checkpoint
  -> no duplicate side effects

이 흐름이 동작하기 위해 필요한 조건은 다음과 같습니다.

  • 직렬화 가능한 상태(Serializable state). 모든 에이전트 상태가 영속화될 수 있어야 합니다. 살아 있는 데이터베이스 연결(database connection)을 가지고 있는 함수 클로저(function closure)는 그대로 살아남지 못합니다.
  • 결정론적 재개(Deterministic resume). 같은 상태와 같은 입력이 주어지면 에이전트는 같은 행동을 만들어 내야 합니다. LLM 호출처럼 비결정적인 동작은 외부의 결정론적 오라클(deterministic oracle)에 위임해야 합니다.
  • 멱등한 부수 효과(Idempotent side effects). 외부 호출(도구 호출, 결제 등)은 그 자체로 멱등하거나, 중복 제거(deduplication) 키를 사용해 같은 효과만 내야 합니다.

LangGraph는 슈퍼 스텝마다 체크포인트를 기록합니다. Temporal은 액티비티(activity)마다 기록합니다. Restate는 이벤트 소싱(event-sourced) 저널(journal)을 사용합니다. 세 가지 모두 같은 패턴의 구현입니다.

LangGraph 런타임

각 에이전트는 thread_id를 가집니다. 상태는 타입이 정의된 딕셔너리(typed dict)이고, 슈퍼 스텝마다 체크포인트 테이블(table)에 한 행(row)이 기록됩니다. 재개할 때 런타임은 처음부터 다시 실행하지 않고 마지막 체크포인트에서 재생(replay)합니다. 에이전트는 사람의 입력을 기다리며 interrupt()를 호출할 수 있고, 런타임은 그 상태를 영속화한 뒤 워커를 해제(release)합니다. 입력이 도착하면 어느 워커가 재개하든 상관없습니다.

이것이 2026년 4월 기준으로 참고할 만한 프로덕션 설계(reference production design)입니다.

MegaAgent의 에이전트별 큐

arXiv:2408.09955 논문은 한 클러스터(cluster)에서 수천 개의 동시 에이전트를 굴린 확장 실험을 설명합니다. 구조는 다음과 같습니다.

agent i:
  state ∈ {Idle, Processing, Response}
  in_queue   <- messages addressed to agent i
  out_queue  -> replies + side effects

coordinators:
  intra-group chat  (agents in the same group)
  inter-group admin chat  (high-level routing)

두 계층의 조율(two-layer coordination)은 그룹 내부 대화(intra-group conversation)는 빽빽하게(dense) 유지하면서, 그룹 사이의 통신(inter-group)은 듬성듬성(sparse) 유지합니다. 수천 개 에이전트에서도 비용을 선형(linear)으로 유지하기 위해 쓰이는 패턴입니다.

비동기 대 작업당 스레드

LLM 호출은 입출력에 묶인(I/O-bound) 작업입니다. 다음 토큰을 기다리는 스레드는 시간의 99%를 놀면서 보냅니다. 스레드 하나는 대략 1MB의 메모리(RAM)를 스택(stack)으로 차지하므로, 동시 호출 10,000개라면 스택만으로 10GB가 됩니다.

파이버(Python asyncio, Go의 고루틴(goroutine), Rust의 tokio)는 입출력 시점에 자발적으로 양보합니다. 같은 10,000개의 호출도 한 프로세스 안에 여유 있게 들어갑니다. LLM 에이전트 규모(scale)에서 비동기는 단순한 최적화(optimization)가 아니라 아키텍처 그 자체입니다.

예외도 있습니다. 임베딩(embedding) 계산이나 토크나이저(tokenizer) 트릭처럼 CPU에 묶인(CPU-bound) 후처리(post-processing)는 여전히 스레드나 별도 프로세스가 필요합니다. 입출력 계층과 CPU 계층은 분리해서 다룹니다.

베디(Bedi)의 반론

"Scaling Agentic Software"(아쉬프리트 베디, 2026)는 대부분의 팀이 실측된 부하 없이 너무 일찍부터 과잉 설계(over-engineering)에 들어간다고 지적합니다. 실용적인 기본값(default)은 다음과 같습니다.

  • FastAPI + Postgres.
  • 각 에이전트 실행은 데이터베이스의 한 행이고, 상태는 낙관적 동시성(optimistic concurrency)으로 그 자리에서(in-place) 갱신합니다.
  • 백그라운드 작업(background job)은 pg_notify 또는 간단한 Celery 워커로 처리합니다.
  • 재시도(retry) 정책은 애플리케이션 코드 안에 둡니다.

다룰 만한 규모의 작업에서 동시 에이전트 실행이 약 100개 미만이라면 이 정도로도 충분한 경우가 많습니다. 측정을 통해 한계를 확인했을 때 업그레이드합니다.

원칙은 이렇습니다. 내구성 있는 실행 프레임워크는 단순한 아키텍처로는 풀 수 없는 구체적인 문제가 생겼을 때 도입합니다. 너무 이른 도입(premature adoption)은 결국 보상 없는 의식(ceremony)에 시간을 태우게 만듭니다.

정확히 한 번 의미론(exactly-once semantics)

유료로 실행되는 에이전트에는 "사실상 정확히 한 번(exactly-once effective)" 보장이 필요합니다. 이는 "최소 한 번(at-least-once) 전달 + 멱등한 소비자(idempotent consumer)" 조합으로 만들어집니다. 엔지니어링 측면에서 필요한 동작은 다음과 같습니다.

  • 실행마다 중복 제거 키(dedup key per run). 모든 부수 효과(side-effect) 호출에 포함시킵니다.
  • 아웃박스 패턴(Outbox pattern). 부수 효과를 먼저 테이블에 기록하고, 별도의 프로세스가 그 테이블을 읽어 실행합니다. 두 단계 모두 멱등하게 만듭니다.
  • 보상 트랜잭션(Compensating transactions). 부수 효과는 성공했는데 그 추적 기록이 실패한 경우, 보상 작업(compensate)을 예약(schedule)합니다.

이것들은 LLM에 한정된 것이 아니라 데이터베이스 엔지니어링의 일반 패턴입니다. LLM이 추가로 부과하는 비용(LLM tax)은 LLM 호출이 느리다는 점뿐이고, 나머지는 표준적인 분산 시스템(distributed systems) 기법들입니다.

무지개 배포(Rainbow deployment)

Anthropic의 멀티 에이전트 리서치 시스템(multi-agent research system)은 "무지개 배포(rainbow deployment)"를 사용합니다. 에이전트 런타임의 여러 버전을 동시에 실행해, 코드를 배포할 때마다 오래 걸리는 에이전트를 죽일 필요가 없게 합니다. 새로운 버전은 트래픽의 일부에 카나리(canary)로 흘려보내고, 옛 버전은 그 위에서 돌던 에이전트들이 모두 끝나는 시점에 은퇴(retire)시킵니다.

오래 살아 있는 상태 보존형(stateful) 시스템에서는 표준적인 방식입니다. 2026년 시점의 변화 지점은, 에이전트가 몇 시간씩 살아남을 수 있으므로 배포 주기 자체가 이를 수용해야 한다는 점입니다.

정석적인 프로덕션 체크리스트

  • 내구성 있는 상태(체크포인트, 스냅샷 또는 아웃박스 + 재생 가능한 로그).
  • 멱등한 부수 효과.
  • LLM 호출을 위한 비동기 입출력 계층.
  • 중복 제거 키와 결합된 최소 한 번 전달.
  • 상태 보존형 작업을 위한 무지개/카나리 배포.
  • 관측성(Observability): 에이전트별 추적(trace), 슈퍼 스텝 감사 로그(audit), 재시도 횟수 카운터.

직접 만들기

code/main.py는 다음을 구현합니다.

  • CheckpointStorethread_id를 키로 하는 SQLite 기반 체크포인트 로그입니다. 슈퍼 스텝마다 한 행을 추가합니다.
  • run_with_checkpoint(agent, thread_id) — 실행 도중 충돌을 흉내 내고, 두 번째 워커가 마지막 체크포인트에서 이어서 실행합니다.
  • AgentQueue — 작은 작업 큐를 가진 에이전트별 Idle / Processing / Response 상태 머신(state machine)입니다.
  • demo_async_vs_threads() — 500개의 동시 시뮬레이션된 "LLM 호출"을 asyncio와 스레드로 각각 실행해, 벽시계 시간(wall-clock)과 근사적인 최대 메모리 사용량을 보고합니다.

실행 방법은 다음과 같습니다.

python3 code/main.py

예상 출력: 시뮬레이션된 충돌 뒤에 체크포인트 재개가 성공합니다. 비동기 버전은 500개의 동시 호출을 1초 미만에 처리합니다. 스레드 버전의 벽시계 시간 차이는 실행 환경에 따라 달라질 수 있지만, 작업당 스레드 방식은 동시 실행 단위마다 스택 메모리 부담(overhead)이 크다는 점도 함께 확인할 수 있습니다.

사용해보기

outputs/skill-scaling-advisor.md는 내구성 있는 실행 방식의 선택을 안내합니다. FastAPI + Postgres, LangGraph 런타임, Temporal, 직접 만든 구현 중 무엇이 적절한지 부하, 상태 보존 요구 사항(state-retention needs), 배포 빈도(deploy frequency)를 기준으로 보정(calibrate)합니다.

배포 전 확인

정석적인 프로덕션 안정화 항목입니다.

  • 단순하게 시작합니다(베디의 원칙). FastAPI + Postgres로 시작하고, 측정에서 실패가 드러날 때까지 그대로 유지합니다.
  • 최적화 전에 모든 것을 계측(instrument)합니다. 실행당 지연 시간 히스토그램, 스텝당 시간, 재시도 횟수, 실패 카테고리화를 남깁니다.
  • 부수 효과는 아웃박스 패턴으로 처리합니다. 결제와 외부 API 호출에서 특히 중요합니다.
  • 무지개 배포를 사용합니다. 배포 중에 실행 중인(in-flight) 에이전트 실행을 죽이지 않습니다.
  • 내구성 있는 실행 엔진(Temporal / LangGraph / Restate)은 한 시간 단위의 사람 대기(human-in-the-loop), 리전 간(cross-region) 조율, 복잡한 재시도/보상 정책 같은 구체적인 문제를 만났을 때 도입합니다.
  • 입출력 계층에는 비동기를 사용합니다. 스레드는 CPU에 묶인 후처리에만 사용합니다.

연습문제

  1. (쉬움) code/main.py를 실행합니다. 체크포인트 재개가 정상적으로 동작하는지 확인하고, 비동기와 스레드의 동시성 차이를 측정해 봅니다.
  2. (중간) 아웃박스(outbox) 테이블을 구현합니다. 모든 도구 호출은 우선 아웃박스에 기록하고, 별도의 고루틴이나 태스크(task)가 실행하게 합니다. 같은 도구 호출을 두 번 실행해 멱등성을 검증합니다.
  3. (중간) 무지개 배포를 흉내 내 봅니다. 두 가지 런타임 버전을 동시에 띄우고, 새로 생성되는 thread_id의 절반씩을 각 버전으로 분배(route)합니다. 옛 버전에서 실행 중이던 스레드가 끊기지 않는지 확인합니다.
  4. (어려움) 아래의 LangGraph 런타임 문서를 읽습니다. 직접 짠 FastAPI + Postgres 버전으로 복제하기에 가장 시간이 오래 걸릴 런타임 기능은 무엇입니까? 그것이 도입할 이유가 되는지, 아니면 뒤로 미뤄도 되는 항목인지 판단해 봅니다.
  5. (어려움) MegaAgent(arXiv:2408.09955) 논문의 3장을 읽습니다. 두 계층 조율(그룹 내부 + 그룹 간 관리 채팅(inter-group admin chat))이 명시되어 있습니다. 이를 두 큐 패밀리(queue family)를 가진 메시지 큐에 어떻게 대응(map)시킬지 스케치해 봅니다.

핵심 용어

용어흔한 설명실제 의미
내구성 있는 실행(Durable execution)"프로그램 상태를 영속화한다"엔진이 슈퍼 스텝마다 상태를 기록하여 충돌 복구를 결정론적으로 만든다.
슈퍼 스텝(Super-step)"트랜잭션 경계"체크포인트 사이의 작업 단위이다. LangGraph 용어이다.
thread_id"에이전트 실행 식별자"체크포인트와 재개 로직을 묶는 키이다.
멱등성(Idempotency)"다시 시도해도 안전함"같은 부수 효과를 여러 번 수행해도 한 번 수행한 것과 같은 결과를 만든다.
아웃박스 패턴(Outbox pattern)"부수 효과를 분리한다"의도를 테이블에 기록하고, 별도 실행기가 수행한 뒤 완료로 표시한다.
최소 한 번 전달(At-least-once delivery)"중복이 있을 수 있다"메시지 큐의 의미론이다. 중복 제거 키가 있어야 사실상 한 번 처리에 가까워진다.
무지개 배포(Rainbow deploy)"여러 버전이 겹친다"오래 살아 있는 작업을 위해 여러 런타임 버전을 동시에 운영하는 방식이다.
비동기 파이버(Async fiber)"협력적으로 양보한다"사용자 모드 동시성이다. 입출력 위주 부하에서 스레드보다 훨씬 가볍다.
체크포인트(Checkpoint)"상태 스냅샷"슈퍼 스텝 경계에서 직렬화된 상태이다. 재개의 출발점이 된다.

더 읽을거리

실습 코드

이 강의의 실습 코드 1개

main
Code

산출물

이 강의에서 생성된 프롬프트, 스킬, 코드 산출물 1개

scaling-advisor

Advise on durable-execution choice for a multi-agent production system. Picks between FastAPI + Postgres, LangGraph runtime, Temporal, Restate, or custom based on concrete load and state-retention needs.

Skill

확인 문제

3문제 · 모두 맞추면 완료 표시가 가능합니다

1.에이전트가 결제 API를 호출한 뒤 결과를 기록하기 전에 충돌했습니다. 재시작 시 결제를 다시 시도합니다. 이중 과금을 방지하는 패턴은 무엇인가요?

2.수 시간짜리 리서치 작업을 수행 중인 에이전트가 있는 상태에서 에이전트 런타임의 새 버전을 배포해야 합니다. 진행 중인 에이전트를 죽이지 않는 배포 전략은 무엇인가요?

3.LLM 호출은 I/O에 묶여 있어 스레드가 다음 토큰을 기다리느라 99%의 시간을 놀고 있습니다. 동시 호출 10,000개에서 작업당 스레드(thread-per-job)는 약 10GB의 스택 메모리를 사용합니다. 아키텍처적 해결책은 무엇인가요?

0/3 답변 완료

추가 문제 풀기

AI가 강의 내용을 바탕으로 새로운 문제를 생성합니다