무상태성(Statelessness)을 넘어: 왜 LLM이 전통적인 시스템 설계를 위협하고 있는가
지난 20년 동안 확장 가능한 웹 아키텍처의 청사진은 놀라울 정도로 일관되어 왔습니다. 상태(state)는 데이터베이스에 저장되고, 연산(compute)은 무상태(stateless)로 유지되는 방식입니다. 이 모델에서는 로드 밸런서 뒤에 있는 어떤 서버로도 요청을 보낼 수 있으며, 데이터베이스는 절대적인 단일 진실 공급원(single source of truth) 역할을 합니다. 이러한 설계 덕분에 애플리케이션 서버를 소모성 상품처럼 취급함으로써 인터넷이 수십억 명의 사용자에게 확장될 수 있었습니다.
하지만 대규모 언어 모델(LLM)과 자율 에이전트의 등장은 이 오래된 가정을 무너뜨리기 시작했습니다. "무상태 연산" 모델이 전통적인 CRUD 애플리케이션에는 적합할지 모르지만, 에이전트형 AI(agentic AI)의 요구 사항에는 점점 더 맞지 않게 되고 있습니다.
무상태 아키텍처의 세 가지 균열
에이전트 워크플로우는 전통적인 무상태 요청-응답 사이클이 효율적으로 처리할 수 없는 세 가지 구체적인 과제를 도입합니다:
- 장시간 실행되는 작업(Long-Running Work): 주제를 조사하고 보고서를 작성하는 것과 같은 복잡한 작업을 수행하는 AI 에이전트는 완료하는 데 10분이 걸릴 수도 있습니다. 이것은 더 이상 HTTP 의미에서의 "요청"이 아니라, 장시간 실행되는 비동기 프로세스입니다.
- 상태 유지 연산(Stateful Compute): 에이전트는 축적된 컨텍스트, 이전 대화의 메모리, 그리고 여러 도구 호출의 결과에 의존합니다. 이 상태는 단순히 "데이터베이스 상태"(사용자 프로필와 같은)가 아니라, 실행 중인 프로세스의 활성 메모리입니다.
- 양방향 상호작용(Bi-directional Interaction): 사용자는 단순히 최종 답변만을 원하는 것이 아닙니다. 사용자는 에이전트가 "생각"하는 것을 지켜보거나, 진행 상황을 중단시키거나, 실시간으로 궤적을 변경하고 싶어 합니다. 이는 상호작용을 무상태 API에 대한 쿼리에서 실행 중인 프로세스와의 라이브 대화로 변화시킵니다.
라우팅 문제: 폴링(Polling)만으로는 부족한 이유
이 문제의 실행 부분을 처리하기 위해 업계는 "지속 가능한 실행(durable execution)" 프레임워크(Temporal, Inngest 또는 Restate 등)로 눈을 돌렸습니다. 이러한 도구들은 프로세스를 탄력적으로 만들어, 서버가 충돌하더라도 워크플로우가 중단된 지점부터 재개될 수 있도록 보장합니다.
하지만 지속 가능한 실행은 프로세스 문제를 해결할 뿐, 상호작용 문제를 해결하지는 않습니다. 표준 로드 밸런서와 HTTP는 특정 실행 중인 프로세스로 요청을 라우팅할 수 없기 때문에, 개발자들은 종종 폴링 방식으로 되돌아갑니다. 클라이언트는 지속 가능한 프로세스가 업데이트를 작성했는지 확인하기 위해 데이터베이스 엔드포인트를 폴링합니다.
원본 자료에서 언급되었듯이, 이는 본질적으로 데이터베이스를 메시지 버스로 취급하는 것이며, 이는 근본적인 라우팅 결핍에 대한 임시방편입니다. 폴링은 지연 시간을 초래하고, 데이터베이스 부하를 증가시키며, 스트리밍 데이터에 대해 좋지 않은 사용자 경험을 생성합니다.
새로운 라우팅 프리미티브(Routing Primitive) 탐색
폴링을 넘어서기 위해서는 단순히 서버나 데이터베이스가 아닌 프로세스를 다룰 수 있는 라우팅 프리미티브가 필요합니다. 목표는 특정 서버 복제본이나 IP 주소를 알 필요 없이, "현재 워크플로우 X를 위해 출력을 생성하고 있는 누구에게든 이 메시지를 전달하라"라고 말할 수 있는 것입니다.
왜 웹소켓(WebSockets)이 정답이 아닌가
웹소켓은 양방향 통신을 제공하지만, 그것은 *연결(connection)*이지 *주소(address)*가 아닙니다. 사용자가 터널에 들어가는 등의 이유로 웹소켓 연결이 끊어지면 주소를 잃게 됩니다. 외부 라우팅 메커니즘 없이는 정확히 동일한 프로세스 상태로 다시 연결할 수 있는 내장된 방법이 없습니다.
Pub/Sub 채널의 필요성
더욱 견고한 솔루션은 이름이 지정된 pub/sub 채널을 사용하는 것입니다. 이 모델에서는 클라이언트나 서버 프로세스 모두가 주소가 아니며, *전송 채널(transport channel)*이 주소가 됩니다. 클라이언트와 서버 모두 지정된 채널에 연결됩니다. 연결이 끊어지면 클라이언트는 단순히 동일한 이름의 채널에 다시 연결하여 데이터 스트림을 재개하기만 하면 됩니다.
지속 가능한 실행과 지속 가능한 전송(pub/sub)을 결합함으로써, 개발자는 워크플로우가 탄력적이고 통신이 원활한 에이전트형 애플리케이션을 구축할 수 있으며, 탄력성을 위해 모든 토큰을 데이터베이스를 통해 전달해야 하는 필요성을 제거할 수 있습니다.
반론 및 업계의 관점
상태 유지 라우팅으로의 전환에 비판이 없는 것은 아닙니다. 일부 엔지니어들은 이러한 "문제"들이 새로운 것이 아니라고 주장합니다. 장시간 실행되는 작업, 웹훅(webhooks), 작업 ID(job IDs)는 비동기 작업을 처리하기 위해 수십 년 동안 사용되어 왔습니다. 비판론자들은 데이터베이스의 "단일 진실 공급원"이 시스템 설계의 핵심으로 남아 있어야 하는 검증된 패턴이라고 제안합니다.
다른 이들은 이미 이러한 문제를 해결하고 있는 기존 기술들을 지적합니다:
- Virtual Actors: 액터 모델(Elixir 또는 Azure Orleans에서 볼 수 있음)은 특정 상태 유지 엔티티에 주소를 지정하는 방법을 제공합니다.
- Durable Objects: Cloudflare의 Durable Objects는 단일 위치에서 상태와 연산을 조정하는 방법을 제공합니다.
- Session Pinning: 클라이언트가 특정 서버에 계속 연결되어 있도록 보장하는 전통적인 방법입니다.
왜 LLM이 이를 시급하게 만드는가
만약 이러한 패턴들이 이전에 존재했다면, 왜 지금이 중요한 것일까요? 차이점은 LLM의 특성에 있습니다. LLM은 **비결정론적(non-deterministic)**이며 비용이 많이 듭니다.
전통적인 시스템에서는 연결이 끊어지면 요청을 다시 시도하여 동일한 결과를 얻을 수 있는 경우가 많습니다. 하지만 LLM의 경우, 요청을 다시 시도하면 다른 답변이 생성될 수 있고 더 많은 토큰 비용이 발생할 수 있습니다. 네트워크의 일시적인 깜빡임 때문에 값비싼 연산 자원을 낭비하거나 복잡하고 비결정론적인 사고 체인의 상태를 잃을 여유가 없습니다.
LLM이 이러한 문제를 발명한 것은 아니지만, LLM은 이러한 문제들을 증폭시켜 20년 된 무상태 웹 아키텍처의 트레이드오프를 그 어느 때보다 고통스럽고 명확하게 만들고 있습니다.