매일 10,000개 이상의 작업을 처리하는 AI 에이전트 파이프라인을 구축하며 겪은 장애 직전의 경험
요약
매일 1만 개 이상의 작업을 처리하는 AI 에이전트 파이프라인 운영 중 발생한 데이터베이스 병목 현상과 해결 과정을 다룹니다. MongoDB의 skip() 방식이 대규모 데이터셋에서 성능 저하를 일으키는 원인을 분석하고 커서 기반 페이지네이션으로의 전환을 제안합니다.
핵심 포인트
- AI 에이전트의 병목은 모델이 아닌 데이터 액세스 패턴에서 발생할 수 있음
- MongoDB의 skip()은 오프셋까지 모든 문서를 스캔하여 CPU 부하를 유발함
- 대규모 데이터 처리 시 커서 기반 페이지네이션 도입이 필수적임
- 수집, 점수화, 전달의 3단계 레이어 아키텍처 설계 사례 공유
나는 우리 MongoDB Atlas 클러스터의 CPU 그래프가 그 주에 벌써 세 번째로 100%까지 치솟는 것을 지켜보았다. 스크래핑 파이프라인(scraping pipeline)이 깊은 skip() 페이지네이션(pagination)을 사용하고 있었고, 매 실행마다 다음 배치를 찾기 위해 백만 개의 문서를 스캔하고 있었다. 그때 나는 프로덕션 AI 에이전트(production AI agents)의 첫 번째 엄격한 규칙을 배웠다. 병목 현상(bottleneck)은 모델이 아니라, 데이터 액세스 패턴(data access patterns)이라는 것이다.
나는 매일 10,000개 이상의 채용 공고를 수집, 점수화(scoring), 노출하는 프로덕션 채용 게시판 플랫폼을 운영하고 있다. 이것은 실제 서비스 중이며, 실제 사용자들이 이용하고 있다. 그리고 이 시스템은 그 어떤 튜토리얼보다 AI 신뢰성(AI reliability)에 대해 더 많은 것을 가르쳐준 방식으로 고장 나기도 했다. 만약 당신이 대규모 환경에서도 안정적으로 유지되어야 하는 AI 에이전트 파이프라인을 구축하고 있다면, 첫 장애가 발생하기 전에 누군가 나에게 말해줬으면 했던 것들을 공유하겠다.
봇 폭풍(Bot Storm)에서 살아남은 아키텍처
핵심 설계를 살펴보자. 시스템은 수집(ingestion), 점수화(scoring), 전달(delivery)의 세 가지 레이어로 구성되어 있다.
수집(Ingestion) 단계는 크론 스케줄(cron schedule)에 따라 여러 ATS API(Greenhouse, Lever, Ashby, Workable, Recruitee)로부터 데이터를 가져온다. 각 소스는 일관되지 않은 형식의 원시 채용 공고를 반환한다. 어떤 것은 HTML 설명을 포함하고 있고, 어떤 것은 일반 텍스트(plain text)로 되어 있다. 몇몇은 필드 자체가 누락되어 있기도 하다.
점수화(Scoring) 단계는 LLM(Large Language Model)이 개입하는 지점이다. 모든 공고는 GPT-4로 전달되며, 함수 호출(function call)을 통해 직무 제목, 요구 기술, 경력 수준, 근무 형태, 그리고 플랫폼 내 각 후보자 프로필에 대한 관련성 점수와 같은 구조화된 필드(structured fields)를 추출한다. 함수 스키마(function schema)는 다음과 같다:
const extractJobSchema = {
name: 'extract_job_fields',
description: 'Extract structured fields from a raw job listing',
...
전달(Delivery) 단계는 점수화된 공고를 다운스트림 소비자(downstream consumers)에게 제공하는 REST API를 노출한다. 이것은 단순화된 버전이다. 진짜 어려운 부분은 그 외의 모든 것이다.
우리를 무너뜨릴 뻔했던 페이지네이션 문제
첫 번째 주요 실패는 눈에 보이지 않다가 갑자기 나타났습니다. 스크래핑 루프(scraping loop)는 컬렉션을 페이지네이션(pagination)하기 위해 MongoDB의 skip()을 사용했습니다. 문서가 100,000개일 때는 CPU 상태가 괜찮았습니다. 500,000개일 때는 스파이크(spike)가 보이기 시작했습니다. 1,000,000개에 도달하자 스크래핑 실행 중에 클러스터(cluster)의 타임아웃(timeout)이 발생하기 시작했습니다.
이유는 다음과 같습니다: skip()은 작업을 건너뛰지 않습니다. 오프셋(offset)까지의 모든 문서를 스캔합니다. 따라서 900,000개의 문서를 건너뛰는 쿼리는 실제로 900,000개의 문서를 스캔하고, 이를 버린 뒤 다음 배치를 반환하는 방식입니다. 이는 매 스크래핑 주기마다 전체 컬렉션 스캔(full collection scan)을 수행하는 것과 같습니다.
해결책은 커서 기반 페이지네이션(cursor-based pagination)이었습니다. skip(N) 대신, _id에 대한 정렬(sort)과 제한(limit)을 사용하여 { _id: { $gt: lastSeenId } }를 쿼리합니다. 이렇게 하면 인덱스(index)를 직접 사용하게 됩니다. 스캔도 없고, CPU 스파이크도 없습니다.
// 이전 (나쁜 예)
const jobs = await db.collection('jobs')
.find({ source: 'greenhouse' })
...
이를 구현하자 하룻밤 사이에 CPU 그래프가 평탄해졌습니다. 교훈은 이것입니다: 프롬프트(prompt)를 건드리기 전에 데이터 레이어(data layer)를 최적화하십시오.
비용 관리는 파이프라인의 문제입니다
AI 재작성(rewrite) 파이프라인은 또 다른 종류의 실패였습니다. 우리는 가공되지 않은 ATS 채용 공고를 가져와 GPT-4를 사용하여 SEO(검색 엔진 최적화)에 최적화된 콘텐츠로 재작성하는 시스템을 구축했습니다. 품질은 훌륭했습니다. 하지만 비용은 재앙적이었습니다.
하루 10,000개의 리스팅(listing)을 처리할 때, GPT-4를 사용한 각 재작성 비용은 약 0.02달러였습니다. 이는 하루에 200달러에 달합니다. SEO를 개선하지만 직접적인 수익을 창출하지는 않는 기능을 위해서 말입니다. 클라이언트는 이 기능을 중단시켰습니다.
저는 비용을 사후 고려 사항이 아닌, 일급 제약 조건(first-class constraint)으로 생각하는 법을 배웠습니다. 이제 저는 코드 한 줄을 쓰기 전에 모든 LLM 호출을 비용 예산에 따라 평가합니다. 재작성 파이프라인의 경우, 대안으로 DeepSeek V4 Flash를 검토하고 있습니다. 초기 테스트 결과, 약 23배 낮은 비용으로 유사한 품질을 보여주었습니다. 이 성능이 유지된다면 파이프라인은 다시 실행 가능한 수준이 될 것입니다.
점수 산정 (scoring) 파이프라인의 경우, OpenAI의 Batch API를 사용하여 요청을 배치 (batch) 처리합니다. 이를 통해 동일한 모델 품질을 유지하면서 50%의 비용 할인을 받을 수 있습니다. 트레이드오프 (tradeoff)는 지연 시간 (latency)입니다. 배치 결과는 초 단위가 아닌 시간 단위로 돌아오지만, 야간 점수 산정 작업 (nightly scoring jobs)에는 완벽하게 수용 가능한 수준입니다.
에러 핸들링 (Error Handling): 아무도 말하지 않는 부분
AI 에이전트는 전통적인 소프트웨어와는 다른 방식으로 실패합니다. 모델이 잘못된 형식의 JSON 응답을 반환하거나, 배치 중간에 API 속도 제한 (rate limit)이 걸리기도 합니다. 혹은 다운스트림 (downstream) 소스가 예고 없이 스키마 (schema)를 변경할 수도 있습니다.
저는 세 가지 방어 계층을 구축했습니다.
첫째, 모든 LLM 호출은 지수 백오프 (exponential backoff)를 적용한 재시도 (retry) 로직으로 감싸져 있습니다. 이는 단순히 속도 제한 때문만이 아니라, 일시적인 장애 (transient failures)를 대비하기 위함입니다. 모델이 500 에러를 반환하거나 응답이 잘릴(truncated) 수 있습니다. 이럴 때는 새로운 요청으로 재시도합니다.
둘째, 구조화된 출력 (structured output)을 사용하기 전에 반드시 스키마에 따라 검증합니다. 만약 relevance_score 필드가 누락되었거나 범위를 벗어난 경우, 해당 작업은 조용히 게시되는 대신 수동 검토를 위해 플래그 (flag)가 지정됩니다. 이는 잘못된 데이터가 다운스트림으로 전파되는 것을 방지합니다.
셋째, 엔드포인트 (endpoint)별, 모델별 실패율을 모니터링합니다. 특정 모델의 에러율이 15분 단위 윈도우 내에서 5%를 초과하면, 트래픽을 폴백 (fallback) 모델로 자동으로 라우팅합니다. 이 방식은 45분 동안 지속된 GPT-4 장애 상황에서 우리를 구했습니다. 파이프라인이 점수 산정을 위해 GPT-4o-mini로 전환되어 우아하게 성능 저하 (degraded gracefully)를 처리했기 때문에 사용자는 아무런 중단을 느끼지 못했습니다.
async function callWithFallback(prompt: string, primary: string, fallback: string) {
try {
return await callModel(primary, prompt);
...
무엇이 고장 났는지 알려주는 모니터링
보이지 않는 것은 고칠 수 없습니다. 저는 파이프라인의 모든 단계에 구조화된 로깅 (structured logging)과 메트릭 (metrics)을 적용했습니다. 각 LLM 호출은 모델 이름, 토큰 수 (token count), 지연 시간 (latency), 응답 상태를 기록합니다. 각 배치 작업은 처리된 항목 수, 성공률, 그리고 총 비용을 기록합니다.
저는 에러 트래킹 (error tracking)을 위해 Sentry를 사용하고, 프론트엔드 세션 리플레이 (session replay)를 위해 LogRocket을 사용합니다. 하지만 가장 가치 있는 모니터링은 파이프라인의 상태를 실시간으로 보여주는 간단한 대시보드입니다. 즉, 수집 속도 (ingestion rate), 스코어링 처리량 (scoring throughput), 에러율 (error rate), 그리고 리스팅 1,000개당 비용 (cost per 1,000 listings)을 보여주는 대시보드 말입니다.
봇 스톰 (bot storm)이 발생했을 때, 단 하나의 크롤러가 한 세션에서 35GB를 끌어갔을 때, 대시보드는 즉시 대역폭 스파이크 (bandwidth spike)를 보여주었습니다. 저는 5분도 채 되지 않아 Cloudflare 엣지 (edge)에서 WAF 규칙을 통해 해당 크롤러를 차단했습니다. 모니터링이 없었다면, 서버가 타임아웃 (timeout)되기 시작한 몇 시간 뒤에나 이 사실을 발견했을 것입니다.
AI 에이전트를 평가하는 창업자에게 해주고 싶은 말
만약 당신의 팀이 AI 에이전트 파이프라인을 구축하고 있고 규모 확장 시의 신뢰성 (reliability)이 걱정된다면, 데이터 레이어 (data layer)부터 시작하세요. LLM 호출을 추가하기 전에 데이터베이스가 처리량 (throughput)을 감당할 수 있는지 확인하십시오. 쿼리 (query)를 최적화하고, 커서 (cursor)를 추가하며, 공격적으로 캐싱 (cache)을 수행하세요.
그다음에는 비용을 호출당 수치가 아닌 파이프라인의 제약 조건 (constraint)으로 생각하십시오. 가능한 것은 배치 (batch) 처리하세요. 대량 작업에는 더 저렴한 모델을 사용하세요. 하나의 모델 장애가 전체 시스템을 중단시키지 않도록 폴백 라우팅 (fallback routing)을 구축하세요.
AI 부분이 가장 쉬운 부분입니다. 어려운 부분은 그 주변의 모든 것, 즉 데이터 배관 (data plumbing), 에러 처리 (error handling), 모니터링 (monitoring), 비용 계산 (cost math)입니다. 이 부분들을 제대로 해낸다면, 당신의 에이전트 파이프라인은 중요한 순간에 중단 없이 작동할 것입니다.
만약 당신의 팀이 AI 에이전트의 신뢰성 문제로 고군분투하며 그로 인해 배포가 늦어지고 있다면, 그것이 바로 제가 도와드리는 분야입니다. 제가 프로덕션 AI 파이프라인을 구축하는 방식에 대해 기꺼이 의견을 나누고 싶습니다.
작성자: Abdul Rehman, 프로덕션 SaaS, MVP 및 AI 자동화를 구축하는 풀스택 AI 엔지니어. 더 많은 내용은 PrimeStrides에서 확인하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기