
LLM이 20년 된 시스템 디자인을 무너뜨리고 있는 이유
요약
LLM과 에이전트의 등장은 '상태가 없는 연산(stateless compute)'과 '데이터베이스 중심의 상태 관리'라는 기존 클라우드 네이티브 아키텍처의 근간을 흔들고 있습니다. 에이전트의 장기 실행 작업, 상태 유지 연산, 양방향 상호작용 특성으로 인해 기존의 stateless API 방식으로는 대응하기 어려운 라우팅 및 상호작용 문제가 발생하고 있습니다.
핵심 포인트
- LLM 에이전트는 기존의 stateless한 요청-응답 모델을 벗어나 장기 실행되는 비동기 프로세스 특성을 가짐
- 에이전트의 메모리는 데이터베이스 상태와는 다른 형태의 '상태 유지 연산(Stateful compute)'을 요구함
- 사용자와 에이전트 간의 양방향 상호작용은 단순한 API 쿼리가 아닌 프로세스와의 대화 형식을 띰
- 현재 Durable execution 프레임워크가 실행의 내구성은 해결하지만, 프로세스와의 실시간 상호작용(라우팅) 문제는 여전히 해결하지 못함
- 폴링(Polling) 방식은 지연 시간, 데이터베이스 부하, UX 저하 등의 문제를 가진 임시방편적 해결책임
지난 10년 동안의 '클라우드 네이티브 (cloud-native)' 아키텍처는 20년 된 가정 위에 구축되었습니다. 즉, 상태 (state)는 데이터베이스 (database)에 존재하며, 연산 (compute)은 상태가 없다 (stateless)는 가정입니다. 확장을 원한다면 데이터베이스를 수직적으로 확장 (vertically scale, 더 큰 머신 확보)하거나, 데이터를 파티셔닝 (partition)할 수 있도록 데이터베이스 스키마 (schema)를 설계하고, 애플리케이션 서버를 수평적으로 확장 (horizontally scale, 더 많은 박스 추가)해야 합니다. 어떤 요청이든 어떤 서버로든 도달할 수 있으며, 로드밸런서 (loadbalancer)는 상관하지 않고, 데이터베이스가 단일 진실 공급원 (single source of truth)이 됩니다.
LLM (Large Language Models)과 에이전트 (agents)는 조용히 이 가정을 위반하고 있으며, 이 아키텍처를 점점 더 다루기 어렵게 만들고 있습니다. 한꺼번에 일어나는 것은 아니지만, 세 가지 미묘한 방식으로 나타납니다:
장기 실행 작업 (Long running work): 10분 동안 작업을 수행하는 에이전트는 '요청 (request)'이 아니라, 장기 실행되는 비동기 프로세스 (async process)입니다.
상태 유지 연산 (Stateful compute): 에이전트는 대화의 여러 턴을 수행할 수 있고, 여러 도구 호출 (tool calls)을 처리할 수 있으며, 축적된 컨텍스트 (context)에 의존합니다. 그 상태는 진정한 의미의 '데이터베이스 상태 (database state)'가 아니라, 에이전트의 메모리 (memory)입니다.
양방향 상호작용 (Bi-directional interaction): 사용자는 에이전트가 생각하는 과정을 지켜보고, 이를 중단시키거나 방향을 재설정하기를 원합니다. 이것은 상태가 없는 API (stateless API)와 데이터베이스에 대한 쿼리 (query)가 아니라, 프로세스 (process)와의 대화입니다.
내구 실행 (Durable execution)이 문제의 일부를 해결한다
내구 실행 (Durable execution; Temporal, Inngest, Restate)은 실행 (execution) 부분에 대한 업계의 현재 해답입니다. 이는 *프로세스 (process)*를 내구성이 있고 탄력적으로 만듭니다. 하지만 우리는 여전히 그 밑단이 상태가 없는 (stateless) 것처럼 가장하고 있습니다. 이것은 *실행 (execution)*에는 효과적이지만, 상호작용 (interaction) 문제는 해결하지 못합니다.
따라서 클라이언트가 내구 실행 프레임워크 (durable execution framework)에서 실행 중인 프로세스와 대화하고 싶어 하는 순간, 당신은 다시 동일한 라우팅 (routing) 문제에 직면하게 됩니다. 그래서 모두가 폴링 (polling)으로 되돌아갑니다. 내구 실행 프로세스가 데이터베이스에 기록한 최신 업데이트를 가져오기 위해 쿼리 엔드포인트 (query endpoint)를 폴링하는 방식입니다.
이는 보편적인 임시방편이지만, 폴링이 나쁜 것과 똑같은 이유들로 여전히 좋지 않습니다. 즉, 얼마나 자주 폴링할지에 대한 지연 시간 (latency) 선택 문제, 데이터베이스 부하, 낭비되는 요청, 스트리밍 (streaming)을 위한 최악의 UX 등이 그것입니다.
궁극적으로, 폴링 (polling)은 데이터베이스를 메시지 버스 (message bus)처럼 취급하는 것입니다. 이는 실제 메시지 버스가 존재하기 전에 사람들이 하던 방식입니다. 폴링은 대화하고 싶은 대상을 어떻게 지정해야 할지 모를 때 하는 행동입니다. 즉, 라우팅 (routing) 문제에 대한 임시방편입니다.
누락된 라우팅 기본 요소 (routing primitive)
우리가 웹 서비스 (web services)를 구축하는 방식에 대한 근본적인 가정이 깨지고 있습니다. 우리는 너무 오랫동안 이 아키텍처 (architecture)를 사용하여 설계해 왔기에, 이것이 하나의 선택지였다는 사실조차 잊어버렸습니다. 하지만 우리는 HTTP, 로드밸런서 (loadbalancers), 그리고 상태가 없는 서버 설계 (stateless server design)가 해결할 수 없는 근본적인 라우팅 기본 요소 (routing primitive)를 놓치고 있습니다.
우리는 다음과 같이 말할 수 있기를 원합니다: “워크플로 X에 대한 출력을 생성하고 있는 누구에게든 이 메시지를 전달하라”.
그것이 어떤 박스인지, 어떤 서버 복제본 (server replica)인지, 어떤 프로세스 (process)인지 알 필요 없이 말입니다.
혹시 WebSockets일까요?
위의 이미지를 보면, “라우팅 기본 요소 (Routing primitive)”라고 표시된 상자에 WebSockets가 꽤 쉽게 들어갈 수 있을 것처럼 보입니다. WebSockets는 장시간 지속되는 양방향 연결 (bi-directional connection)을 지원하는 전송 (transport) 방식이며, 서버 프로세스를 클라이언트와 연결합니다.
하지만 WebSockets에는 문제가 있습니다. 그것은 연결 (connection)이지 주소 (address)가 아니라는 점입니다. WebSockets는 클라이언트와 서버 사이에 직접적인 연결을 생성함으로써 라우팅 문제를 한 번 해결합니다. 하지만 그 연결이 끊어지면 '주소'도 사라집니다. 라우팅할 주소가 없기 때문에 동일한 프로세스에 다시 연결할 수 없습니다.
그것은 pub/sub 채널입니다
발행/구독 (pub/sub) 채널은 소유권의 방향을 뒤집습니다. 서버 프로세스도, 클라이언트도 주소 지정이 불가능합니다.
대신 전송 (transport) 방식이 주소 지정이 가능해집니다. 클라이언트와 서버 모두 채널의 이름을 통해 pub/sub 채널에 연결하며, 양방향의 상태 유지 통신 (stateful communication)을 수행합니다. 채널이 곧 주소이며, WebSockets와 달리 채널은 연결이 아닙니다. 채널은 데이터를 잃거나 동일한 프로세스로 라우팅하는 능력을 상실하지 않고도 연결을 끊었다가 다시 연결할 수 있는 내구성이 있는 (durable) 채널입니다.
내구성이 있는 실행을 위한 내구성이 있는 전송 (Durable transport for durable execution)
Temporal 예제로 다시 돌아가 보면, 내구성이 있는 워크플로 액티비티(activity, 또는 단계)는 워크플로 ID의 이름을 딴 pub/sub 채널에 연결될 것입니다. 클라이언트는 동일한 채널에 연결하여 업데이트를 받고, 인터럽트(interrupt)나 스티어링(steering) 메시지를 보낼 것입니다. 워크플로는 내구성이 있고 채널 또한 내구성이 있으므로, 워크플로 프로세스나 클라이언트 연결이 끊어지더라도 동일한 채널에 다시 연결하여 서로 계속 통신할 수 있습니다.
데이터를 데이터베이스(database)를 통해 전달할 필요도 없고, 폴링(polling)을 할 필요도 없으며, 실제로 작업을 수행 중인 내구성이 있는 프로세스에 접근하지 못할까 봐 걱정할 필요도 없습니다.
하지만 이것은 사실 LLM에 관한 이야기가 아닙니다
LLM은 단지 이 문제를 더 눈에 띄게 만들 뿐입니다. 이전에는 연결이 끊기더라도 요청 비용이 충분히 저렴하여 재시도(retry)할 수 있었고, 결정론적(deterministic)인 응답을 받을 가능성이 높았습니다. 동일한 요청에 대해 동일한 응답을 받을 수 있다고 기대할 수 있다면 재시도는 더 간단해집니다.
하지만 LLM의 경우는 그렇지 않습니다. LLM의 응답은 결정론적이지 않으며, 비용도 저렴하지 않습니다. 토큰(token) 비용을 지불하고 있다면, 클라이언트가 기차 터널에 들어가 연결이 끊겼다는 이유로 토큰을 낭비하고 싶지 않을 것입니다. 또한 클라이언트 연결 문제에 대한 탄력성(resilience)을 확보하기 위해 모든 토큰을 데이터베이스를 통해 전달해야 하는 상황도 원치 않을 것입니다.
비결정론적이고 비용이 많이 드는 특성 때문에, LLM은 현재 우리 아키텍처의 한계를 더 명확하게 드러내며, HTTP + 상태가 없는 서버(stateless server) + 로드밸런서(loadbalancer) + 데이터베이스(database) 조합의 트레이드오프(trade-off)를 더욱 고통스럽게 만듭니다.
상태가 없는 웹(stateless web)이 틀린 것은 아닙니다. 다만 장기 실행되고, 상태를 유지하며(stateful), 상호작용하는 프로세스를 원하는 에이전트형 애플리케이션(agentic applications)에는 좋은 설계가 아닐 뿐입니다. 우리에게는 데이터베이스뿐만 아니라 프로세스에 직접 접근할 수 있는 라우팅 프리미티브(routing primitive)를 포함하는 새로운 아키텍처가 필요합니다.
내구성이 있는 실행(Durable execution) + pub/sub + 상태가 없는 HTTP; 각각이 자신의 역할을 수행하는 구조가 필요합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 HN Design Systems의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기