5분이 충분하지 않을 때: AI 인제션을 Sync에서 Async로 전환 (그리고 99% Compute 절감)
Source: Dev.to
배경
이전 글에서 저는 Synapse라는 AI 시스템을 소개했습니다. 이 시스템은 제 아내를 위해 만든 것으로, 지식 그래프를 활용해 LLM에 “깊은 메모리”를 제공합니다. 초기 데모에서는 채팅이 끝난 후 약 50 초 안에 그래프가 업데이트되는 모습을 보여줬지만, 실제 사용에서는 근본적인 결함이 빠르게 드러났습니다.
문제점
수십 개의 메시지가 오가는 45분짜리 채팅 세션 동안 “End Session” 버튼이 몇 분씩 회전하고 결국 크래시되었습니다. 원인은 단순한 타임아웃 버그가 아니라 아키텍처 자체였습니다.
초기 동기식 구현
- Convex (Orchestrator) → 내 Python 백엔드에 HTTP POST를 트리거합니다.
- FastAPI (Brain) → Graphiti + Gemini를 호출해 텍스트를 처리합니다.
- FastAPI는 결과가 나올 때까지 기다렸다가 반환합니다.
- Convex는 결과를 데이터베이스에 저장합니다.
이는 전형적인 동기식 요청‑응답 패턴입니다.
실패 이유: Convex Actions에는 실행 제한 시간이 존재합니다(플랜에 따라 5–10 분). 짧은 대화는 1–2 분 안에 끝났지만, 큰 세션은 12–18 분이 소요되어 제한을 크게 초과했습니다.
연쇄적인 실패
- Convex Actions에 지수 백오프 재시도를 추가했습니다.
- 각 재시도마다 새로운 백그라운드 프로세스가 시작되고, 이전 프로세스는 계속 실행돼 토큰 사용량이 두 배가 되었으며 “좀비” 작업이 생겼습니다.
- 사용자는 여전히 오류를 보았고, 백엔드는 과부하되었습니다.
진단
Axiom에 전송된 OpenTelemetry 트레이스는 ingestion 자체가 실패한 것이 아니라 단순히 느리게 진행되고 있음을 보여주었습니다. 큰 세션에서는 일관되게 12–18 분이 걸렸습니다.
비동기 폴링 아키텍처로 전환
클라이언트나 서버가 기다릴 수 있는 시간을 초과하는 작업은 요청과 응답을 분리해야 합니다.
새로운 흐름
- Convex가
POST /ingest를 보냅니다. - FastAPI는 즉시
202 Accepted와jobId를 반환합니다(≈ 300 ms). - FastAPI는 무거운 처리를 백그라운드 작업(
asyncio.create_task)으로 시작합니다. - Convex는 잠시 대기한 뒤 몇 분 간격으로 작업 상태를 폴링합니다.
폴링 전략
- 지수 백오프에서 선형 백오프로 전환했습니다.
- 스케줄: 5 분 후 확인, 그 다음 10 분 후, 이후 매 10 분마다 확인.
- 불필요한 부하와 서버 잡음을 줄여줍니다.
리소스 사용량 비교
| 시나리오 | 작업 시간 | 총 청구 컴퓨트 | 토큰 낭비 |
|---|---|---|---|
| 동기식 | 5 분 (블로킹) → 타임아웃 → 재시도 (추가 5 분) | ~10–15 분 | 높음 (중복 처리) |
| 비동기 폴링 | 트리거 ≈ 300 ms, 폴링 ≈ 300 ms, 최종 가져오기 ≈ 300 ms | < 2 초 | 최소 |
우리는 작업당 약 10 분 정도의 컴퓨트를 낭비하던 상황에서, 실제 실행 시간 2 초 이하로 줄였으며 중복 처리를 완전히 없앴습니다.
교훈
- AI 작업은 본질적으로 느립니다. “빠른” LLM 호출이라도 30 초가 걸릴 수 있고, “깊은” 지식 그래프 업데이트는 15 분이 소요될 수 있습니다.
- 타임아웃만 늘리지 마세요. 요청과 응답을 분리해 시스템을 회복력 있게 만들고 비용을 절감하세요.
- 폴링에 선형 백오프를 적용하면 장기 실행 작업의 예상 소요 시간에 맞춰 서버 트래픽을 줄일 수 있습니다.
코드 저장소
비동기 요청‑응답 패턴 구현은 다음 저장소에서 확인할 수 있습니다:
- 프론트엔드 (Body):
synapse-chat-ai - 백엔드 (Cortex):
synapse-cortex
피드백 요청
다른 분들은 장기 실행 LLM 작업을 어떻게 처리하고 계신지 궁금합니다. X 또는 LinkedIn을 통해 언제든지 연락 주세요.