아무도 말하지 않는 AI 에이전트의 신뢰성 문제
요약
AI 에이전트가 자율적으로 행동하는 시대가 도래했지만, 이를 뒷받침할 인프라 계층의 신뢰성 문제가 심각합니다. 상태 관리 부재와 중복 실행 등의 문제를 해결하기 위해 분산 시스템의 내구적 실행(durable execution) 개념이 필요함을 강조합니다.
핵심 포인트
- AI 에이전트는 단순 출력을 넘어 API 호출 등 실제 행동을 수행함
- 현재 인프라는 에이전트의 자율적이고 지속적인 작동을 보장하지 못함
- 재시도 로직 과정에서 발생하는 부작용(side effects)과 중복 실행 문제 발생
- 모델의 지능보다 신뢰할 수 있는 실행(reliable execution) 인프라가 핵심
서론: 미래는 에이전트 중심적이지만 - 스택은 불완전하다
소프트웨어는 시스템이 무엇을 _할 수 있는지_를 변경함으로써 항상 진화해 왔습니다. 우리는 배치 작업(batch jobs)에서 대화형 애플리케이션(interactive applications)으로, 모놀리스(monoliths)에서 분산 시스템(distributed systems)으로, 그리고 온프레미스(on-prem) 서버에서 탄력적인 클라우드 인프라(elastic cloud infrastructure)로 이동했습니다. 각 변화는 단순히 성능을 향상시킨 것이 아니라, 소프트웨어가 신뢰성 있게 완수할 수 있는 범위를 확장했습니다.
우리는 이제 새로운 전환점에 진입하고 있습니다: 응답하는 소프트웨어에서 행동하는 소프트웨어로의 전환입니다.
AI 에이전트(AI agents)는 단순히 출력을 계산하는 것을 넘어 현실 세계에서 행동을 실행하는 최초의 시스템입니다. 이들은 API를 호출하고, 돈을 이동시키며, 데이터베이스를 업데이트하고, 워크플로우(workflows)를 트리거하며, 이전의 소프트웨어 시스템이 결코 가졌던 적 없는 수준의 자율성(autonomy)을 가지고 작동합니다.
하지만 바로 이 지점에서 전환이 무너집니다.
지능 계층(intelligence layer)은 더 나은 모델, 더 나은 프롬프팅(prompting), 더 나은 도구 사용(tool use)을 통해 빠르게 발전했습니다. 하지만 에이전트 아래의 인프라 계층(infrastructure layer)은 이를 따라잡지 못했습니다. 이 시스템들은 상태가 없는(stateless), 최선 노력(best-effort) 실행을 위해 설계된 도구 위에서 지속적이고 자율적으로 작동하도록 요구받고 있습니다.
이러한 불일치는 오직 실패할 때만 눈에 띕니다: 상태를 잃어버리는 충돌(crashes), 부작용(side effects)을 중복시키는 재시도(retries), 그리고 안전하게 재개할 수 없는 워크플로우(workflows)가 그것입니다. 분산 시스템이 수년 전 트랜잭션(transactions), 이벤트 로그(event logs), 그리고 내구적 실행(durable execution)을 통해 해결했던 동일한 문제들이 새로운 형태로 다시 나타나고 있지만, 동일한 보장(guarantees)은 제공하지 못하고 있습니다.
이것이 에이전트 중심의 미래에서 빠져 있는 조각입니다. 더 똑똑한 모델이 아니라, 신뢰할 수 있는 실행(reliable execution)입니다.
예시
한 고객이 에이전트에게 환불을 요청합니다. 에이전트는 주문을 조회하고, 환불이 유효하다고 결정한 뒤, 결제 API를 호출합니다. API는 결제 취소를 처리합니다. 그 후, 결제 제공업체가 200 OK를 반환하는 시점과 에이전트가 그 사실을 기록하는 사이의 수백 밀리초(milliseconds) 동안, 에이전트를 실행 중인 프로세스가 종료됩니다. OOM kill, 배포(deploy), 스팟 인스턴스(spot instance) 회수, 또는 Kubernetes 포드(pod) 축출(evicted) 중 무엇이 원인이든 상관없습니다. 실제 운영 환경(production)에서는 이 모든 일이 발생합니다.
오케스트레이션 계층(orchestration layer)은 작업이 완료되지 않았음을 감지합니다. 그리고 합리적인 조치를 취합니다. 바로 재시도(retry)를 하는 것입니다. 에이전트는 처음부터 다시 시작하여, 주문을 조회하고, 환불이 유효하다고 판단한 뒤, 결제 API(payments API)를 두 번째로 호출합니다.
고객은 환불을 두 번 받게 됩니다.
버그를 작성한 사람은 아무도 없습니다. 개별 구성 요소들은 모두 올바르게 동작했습니다. 모델은 두 번 모두 올바르게 추론했습니다. 결제 API는 요청받은 대로 정확히 두 번 수행했습니다. 재시도 로직(retry logic)은 재시도 로직이 해야 할 일을 했습니다. 그럼에도 불구하고 시스템 전체는 재무적으로 부정확하고, 외부로 드러나며, 되돌릴 수 없는 결과를 초래했습니다.
이것은 프롬프팅(prompting)의 문제가 아닙니다. 모델의 문제도 아닙니다. 이것은 인프라(infrastructure)의 문제이며, 분산 시스템 공학(distributed systems engineering)이 지난 20년 동안 해결 방법을 배워온 것과 동일한 범주의 인프라 문제입니다. 현재 세대의 AI 에이전트에서 불안한 점은, 그 방대한 지식 체계가 얼마나 철저하게 무시되고 있는가 하는 점입니다.
데모와 운영 환경 사이의 간극 (The Demo-to-Production Gap)
이 문제가 보이지 않는 이유는, 거의 모든 에이전트가 평가되는 환경에서는 에이전트들이 아주 잘 작동하는 것처럼 보이기 때문입니다. 그 환경은 개발자의 머신이나 노트북 위에서 실행되는 단일 프로세스(single process)이며, 한 번에 하나의 작업만을 수행하고, 완료될 때까지 동시성(concurrency)이나 충돌(crash)이 없으며, 사람이 출력 스트림(output stream)을 지켜보고 있는 상태입니다.
전형적인 에이전트 루프(agent loop)를 생각해 보십시오. 프레임워크 특유의 장식들을 걷어내면, 오늘날 운영 환경(production)에 있는 거의 모든 에이전트 시스템은 다음과 같은 형태의 변형입니다:
state = initial_context(task)
while not done(state):
action = model.decide(state) # LLM 호출: 도구 선택 + 인자(arguments)
...
데모 환경에서 이 루프는 결점이 없습니다. state 변수는 작업의 전체 이력을 보유합니다. 각 도구 호출(tool call)이 발생하면 그 결과가 추가되고, 모델은 전체 궤적(trajectory)을 확인하며, 루프는 수렴(converge)합니다. 당신은 모델이 생각하는 과정을 지켜볼 수 있습니다. 마치 하나의 완성된 시스템처럼 느껴집니다.
이것은 시스템이 아닙니다. 그것은 단지 시간이 오래 걸리고 중간에 네트워크에 접속하는 함수 호출 (function call)일 뿐입니다. 그리고 당신이 이것을 노트북 (notebook)에서 운영 환경 (production)과 유사한 무언가로 옮기는 순간, 세 가지 가정이 조용히 깨집니다.
프로세스는 불멸이라고 가정됩니다. state는 프로세스 메모리 (process memory)에 존재합니다. 루프는 중단 없이 initial_context에서 finalize까지 실행될 것이라고 가정합니다. 하지만 에이전트 (agent) 작업은 수 초에서 수 분, 때로는 수 시간 동안 지속되며, 프로세스가 재시작되는 모든 환경에서 "장시간 실행 (long-running)"과 "인메모리 (in-memory)"는 모순됩니다. 배포 (deploys)는 일어납니다. 호스트 (hosts)는 죽습니다. 오토스케일러 (autoscalers)는 스케일 인 (scale in)을 수행합니다. 수 분이 걸리는 작업이 최소 한 번 중단될 확률은 0이 아니며, 규모가 커지면 그 확률은 결코 작지 않습니다. 프로세스가 종료되면 state는 사라집니다. 에이전트가 수행한 모든 것—모든 도구 호출 (tool call), 모든 결과, 모든 추론 단계(reasoning step)—은 어떤 부작용 (side effects)이 이미 발생했는지에 대한 지식을 포함하여 모두 증발해 버립니다.
도구 호출 (tool calls)은 순수하다고 가정됩니다. 루프는 execute(action)을 마치 읽기 (read) 작업인 것처럼 취급합니다. 즉, 호출하고 값을 얻을 뿐, 아무런 결과도 초래하지 않는다고 보는 것입니다. 하지만 챗봇 (chatbot)과 달리 _에이전트 (agent)_의 핵심은 도구 호출이 _순수하지 않다 (not pure)_는 점에 있습니다. 도구 호출은 돈을 움직이고, 행 (rows)을 작성하며, 이메일을 보내고, 인프라 (infrastructure)를 프로비저닝하며, 티켓을 생성하고, 그 자체로 하류 효과 (downstream effects)를 유발하는 제3자 API (third-party APIs)를 호출합니다. execute는 실제 세계에 영향을 미치며 되돌릴 수 없는 루프의 부분입니다. 이를 순수 함수 (pure function)처럼 취급하는 것이 바로 충돌 후 재시도 (crash-and-retry)를 이중 환불로 만드는 정확한 원인입니다.
실행은 정확히 한 번만 일어난다고 가정됩니다. 데모 루프에는 재시도 (retry)가 없습니다. 데모에서는 아무것도 실패하지 않기 때문입니다. 하지만 운영 환경 (production)에서는 큐 (queue) 레벨, 오케스트레이션 (orchestration) 레벨, 로드 밸런서 (load balancer), 클라이언트 SDK (client SDK), 또는
이것들은 프롬프트(prompt)만으로 해결할 수 있는 예외적인 사례(edge cases)가 아닙니다. 이것들은 구조적인 문제입니다. 보편적으로 구현되는 에이전트 루프(agent loop)에는 지속성(durability)에 대한 개념이 없으며, 어떤 작업이 이미 세상에 반영(committed)되었는지에 대한 개념도 없고, 재시작(restart)이 아닌 재개(resume)를 할 수 있는 방법도 없습니다. 데모가 잘 작동하는 이유는 데모가 누락된 인프라가 중요하게 작용했을 모든 조건들을 제거했기 때문입니다.
실패 모드(Failure Modes), 제대로 명명하기
에이전트가 어떻게 실패하는지 정확하게 정의하는 것이 도움이 됩니다. 왜냐하면 "에이전트는 신뢰할 수 없다"와 같은 모호한 용어는 "더 나은 모델을 사용하라"와 같은 모호한 해결책을 불러오기 때문입니다. 실패는 구체적이며, 시스템 엔지니어링(systems engineering) 분야에서 잘 알려진 명칭들을 가지고 있습니다.
중복된 부작용 (Duplicate side effects). 부작용을 일으키는 작업(side-effecting operation)이 한 번 이상 수행됩니다. 이는 재시도(retry) 과정에서 완료 여부가 지속적으로 기록되지 않은 작업을 다시 실행했기 때문입니다. 이중 환불이 교과서적인 사례이지만, 일반적인 형태는 어디에나 있습니다: 하나여야 할 데이터베이스 행(row)이 두 개가 되거나, 이메일이 두 번 발송되거나, 서버가 두 번 프로비저닝(provisioned)되거나, 웹훅(webhook)이 두 번 전달되는 경우입니다. 이것은 비용과 신뢰를 가장 직접적으로 깎아먹는 실패 모드입니다.
충돌 후 상태 유실 (Lost state after crashes). 에이전트의 작업 메모리(working memory), 즉 궤적(trajectory), 중간 결론, 부분적인 진행 상황은 오직 프로세스 메모리(process memory)에만 존재하며 프로세스가 종료되면 파괴됩니다. 지속적인 로그(durable log)가 없기 때문에, 시스템은 가장 기본적인 복구 질문인 "충돌 전에 이미 어떤 일이 일어났는가?"에 답할 수 없습니다. 이 답이 없다면, 선택지는 처음부터 다시 시작하거나(중복된 부작용의 위험을 감수함), 포기하는 것(작업을 잃고 사용자를 작업 도중에 방치함)뿐입니다.
일관성 없는 실행 (Inconsistent execution). 원래의 작업이 완료되기 전에 재시도 (retry)가 발생하거나, 큐 (queue)가 동일한 메시지를 두 번 전달하여 에이전트의 두 복사본이 동시에 실행될 때, 이들은 조정(coordination) 없이 공유 상태 (shared state)를 관찰하고 변경합니다. 하나는 다른 하나가 막 변경하려는 값을 읽습니다. 둘 다 자신이 유일한 실행자라고 믿습니다. 그 결과는 분산 데이터베이스 (distributed databases)가 방지하기 위해 존재하는 것과 동일한 계열의 경합 조건 (race conditions) 및 쓰기-쓰기 충돌 (write-write conflicts)입니다. 다만 이제는 매 실행마다 서로 다른 행동을 취할 수 있는 확률적 의사결정자 (probabilistic decision-maker)에 의해 이러한 문제들이 생성되고 있다는 점이 다릅니다.
복구 불가능한 워크플로 (Unrecoverable workflows). 다단계 에이전트 작업이 중간에 실패하면, 세상은 부분적으로 변경된 상태로 남게 됩니다. 예를 들어, 결제는 취소되었지만 재고는 다시 채워지지 않았거나, 계정은 생성되었지만 환영 이메일은 발송되지 않았거나, 5개의 마이크로서비스 (microservices) 중 3개만 호출된 상태가 되는 식입니다. 작업이 어디까지 진행되었는지에 대한 기록도 없고, 작업을 계속하거나 되돌릴 수 있는 안전한 방법도 없습니다. 워크플로는 꽉 막혀버리며, 사람이 직접 그 부분적인 상태를 역공학 (reverse-engineer)하여 처리해야 합니다.
이 모든 문제들은 분산 시스템 (distributed systems) 분야에서 각각의 명칭과 문헌, 그리고 충분히 검증된 해결책을 가지고 있습니다. 그 해결책 중 새로운 것은 하나도 없습니다. 새롭고 기이한 점은, 소프트웨어의 한 카테고리 전체가 마치 그러한 문헌이 존재하지 않는 것처럼 구축되고 있다는 사실입니다.
분산 시스템이 이미 해결한 것들
"에이전트 (agent)"가 루프 내의 LLM을 의미하기 훨씬 전부터, 업계는 충돌 (crashes), 재시도 (retries), 그리고 동시성 (concurrency)이 존재하는 상황에서도 부수 효과 (side-effecting)를 일으키는 일련의 작업들을 신뢰성 있게 수행하는 것을 유일한 목적으로 하는 시스템들을 구축해 왔습니다. 결제 처리기 (payment processors), 주문 이행 파이프라인 (order-fulfillment pipelines), 은행 원장 (bank ledgers), 프로비저닝 시스템 (provisioning systems), 그리고 워크플로 엔진 (workflow engines)은 모두 현재 에이전트들이 처한 것과 정확히 동일한 영역에서 작동합니다. 이들이 수렴한 기술들은 생소한 것이 아닙니다. 그것들은 기초적이며, 직접적으로 적용 가능합니다.
이벤트 소싱 (Event sourcing). 현재 상태만을 저장하는 대신, 그 상태를 생성한 순차적이고 불변하는(immutable) 이벤트 로그를 저장합니다. 상태는 로그의 _투영 (projection)_일 뿐, 진실의 근원 (source of truth)이 아닙니다. 로그가 진실의 근원입니다. 이 단 한 번의 역전이 신뢰할 수 있는 실행 (reliable execution)에서 가장 중요한 아이디어인데, 그 이유는 상태를 언제든 재구성할 수 있음을 의미하기 때문입니다. 이벤트만 있다면, 무슨 일이 어떤 순서로 일어났는지 완전한 충실도 (fidelity)를 가지고 복구할 수 있습니다. 충돌 (crash)이 발생하면 투영된 상태 (메모리 내 상태)는 파괴되지만, 로그는 파괴되지 않습니다. 그러면 로그를 통해 다시 구축하고 계속 진행하면 됩니다.
재생 가능한 실행 (Replayable execution). 만약 이벤트 로그가 비즈니스 이벤트뿐만 아니라 모든 비결정론적 연산 (non-deterministic operation)의 입출력, 모든 외부 호출, 모든 무작위 선택, 모든 시계 읽기(clock read)를 포착한다면, 실행을 결정론적으로 _재생 (replay)_할 수 있습니다. 연산을 다시 수행하는 대신 기록된 결과를 다시 입력하는 방식입니다. 이것이 Temporal과 같은 워크플로 엔진 (workflow engines)의 배후에 있는 메커니즘입니다. 워크플로 코드는 일반적인 순차적 명령형 로직 (imperative logic)으로 작성되지만, 런타임 (runtime)은 모든 외부 상호작용의 결과를 기록합니다. 따라서 충돌 후에도 이미 완료된 단계에는 기록된 결과를 대입함으로써, 이미 일어난 일을 다시 실행하지 않고도 정확히 실패 지점에 도달할 수 있도록 코드를 처음부터 다시 실행할 수 있습니다. 프로그래머는 일반적인 함수처럼 보이는 것을 작성하고, 런타임은 그 밑단에서 이를 내구성 있게 (durable) 만듭니다.
내구성이 있는 큐 (Durable queues). 작업은 메모리에 유지되지 않습니다. 명시적인 전달 의미론 (delivery semantics), 확인 응답 (acknowledgments), 가시성 타임아웃 (visibility timeouts)을 가진 영구 저장소 (persistent store)에 대기열에 추가됩니다. 작업은 확인 응답이 이루어질 때까지 완료된 것으로 간주되지 않습니다. 작업자가 확인 응답을 하기 전에 충돌하면, 해당 작업은 다시 가시화되어 다른 작업자가 이를 가져갑니다. 큐는 작업이 성공할 때까지 재시도될 것을 보장하며, 바로 이 점 때문에 큐의 하위 단계에 있는 모든 것은 여러 번 시도되는 상황을 견딜 수 있도록 구축되어야 합니다.
Idempotency keys (멱등성 키). 최소 한 번 전달 (at-least-once delivery)은 달성 가능한 보장인 반면, 정확히 한 번 전달 (exactly-once delivery)은 일반적으로 불가능하기 때문에, 표준적인 방어책은 작업을 멱등하게 (idempotent) 만드는 것입니다. 즉, 작업을 두 번 수행하는 것이 한 번 수행하는 것과 동일한 효과를 갖도록 하는 것입니다. 전형적인 구현 방식은 멱등성 키 (idempotency key)를 사용하는 것입니다. 이는 부수 효과 (side-effect)를 일으키는 요청에 부착되는 고유 식별자로, 수신 측에 저장됩니다. 이를 통해 동일한 키를 가진 두 번째 요청이 들어오면 동작을 다시 수행하는 대신 첫 번째 요청의 결과를 반환합니다. Stripe의 API가 대표적인 사례입니다. 동일한 Idempotency-Key를 두 번 보내면 두 번째 결제가 발생하는 대신 원래의 결제 결과가 반환됩니다. 두 번째 호출이 첫 번째 호출의 재전송 (replay)으로 인식되기 때문에 이중 환불은 발생하지 않습니다.
Saga / compensation patterns (Saga / 보상 패턴). 다단계 워크플로 (multi-step workflow)를 원자적 (atomic)으로 만들 수 없고 여러 외부 시스템에 걸쳐 있는 경우(대개 불가능합니다), 각 단계에 대해 보상 동작 (compensating action)을 정의합니다 (결제에 대한 환불, 생성에 대한 삭제, 차감에 대한 재입고 등). 만약 워크플로가 중간에 실패하면, 엔진은 이미 완료된 단계들에 대해 보상 동작을 실행하여 시스템을 다시 일관된 상태 (consistent state)로 유도합니다. 이것이 바로 트랜잭션을 공유하지 않는 시스템들 사이에서 트랜잭션 동작 (transactional behavior)에 근접한 결과를 얻는 방법입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기