비용 부담 없이 매일 10,000개 이상의 매물을 평가하는 AI 파이프라인 구축기
요약
대규모 채용 매물을 처리하는 과정에서 발생하는 OpenAI API 비용 문제를 해결하기 위한 AI 파이프라인 구축 사례를 다룹니다. 데이터 정규화, 커서 기반 페이지네이션을 통한 MongoDB 성능 최적화, 그리고 Function Calling을 활용한 구조화된 데이터 추출 방법을 설명합니다.
핵심 포인트
- 대규모 LLM 호출 시 비용 효율성을 고려한 아키텍처 설계의 중요성
- MongoDB skip() 대신 커서 기반 페이지네이션을 통한 성능 최적화
- 자유 형식 프롬프트 대신 Function Calling을 통한 결정론적 JSON 출력 확보
- 다양한 ATS 소스로부터의 데이터 정규화 및 인제스션 파이프라인 구축
비용 문제가 눈앞에 닥친 순간
전체 채용 공고 백로그를 대상으로 LLM 평가 파이프라인을 처음 실행했을 때, 실시간으로 치솟는 OpenAI API 비용을 지켜보았습니다. 100개의 테스트 매물에 대해서는 완벽하게 작동했던 방식이, 하루 10,000개 규모에서는 경제적으로 불가능했습니다.
이것은 사이드 프로젝트가 아니었습니다. 주요 ATS(Applicant Tracking System) 소스로부터 매물을 처리하며 클라이언트를 위해 구축 중이던 실제 운영 중인 채용 게시판 플랫폼이었습니다. 사용자들은 점수가 필요했고, 클라이언트는 시스템이 수익성을 갖추기를 원했습니다. 그리고 저는 대규모로 LLM을 호출하는 방식에 대해 모든 것을 재고해야 했습니다.
단순한 접근 방식은 간단했습니다. 매물을 가져와 프롬프트와 함께 GPT-4로 보내고, 점수를 돌려받는 것이었습니다. 간단하지만 비쌌습니다. 대규모 환경에서 이러한 패턴은 제품을 경제적으로 생존 불가능하게 만들었을 것입니다.
그래서 저는 파이프라인을 바닥부터 다시 구축했습니다. 최종 아키텍처는 다음과 같습니다.
인제스션(Ingestion): 평가를 뒷받침하는 데이터 파이프라인
무언가를 평가하기 전에, 대규모의 깨끗한 데이터가 필요합니다. 이 플랫폼은 공개 API를 사용하여 5개의 주요 ATS 제공업체로부터 데이터를 가져옵니다. Greenhouse, Lever, Ashby, Workable, 그리고 Recruitee는 모두 OAuth 없이 매물 데이터를 노출하므로 인제스션(Ingestion) 과정이 간단하지만, 각기 다른 형태의 데이터를 반환합니다.
인제스션 레이어는 모든 데이터를 제목(title), 회사(company), 설명(description), 위치(location), 게시일(posted date), 메타데이터(metadata)와 같은 표준 스키마로 정규화합니다. 그런 다음 평가 파이프라인이 읽어갈 수 있도록 MongoDB 컬렉션에 기록합니다.
여기서 첫 번째 실패는 페이지네이션(pagination)이었습니다. 컬렉션을 읽을 때 오프셋 기반 페이지네이션을 위해 MongoDB의 skip()을 사용하고 있었습니다. 100만 개 이상의 문서가 있는 상황에서, 깊은 skip 호출은 Atlas CPU 스파이크를 유발했습니다. skip()은 계산을 건너뛰는 것이 아니라, 오프셋까지의 모든 문서를 스캔하기 때문입니다. 매물을 더 많이 인제스션할수록 상황은 더 악화되었습니다.
해결책은 _id 필드를 사용하는 커서 기반 페이지네이션 (cursor-based pagination)이었습니다. 문서를 건너뛰는 대신, 쿼리가 "이 문서 이후의 다음 100개 문서를 주세요"라고 요청하는 방식입니다. 스캔이 발생하지 않습니다. CPU 급증도 없습니다. 이 변경 사항을 구현하는 데는 오후 한나절이 걸렸고, 매주 장애를 일으키던 문제를 영구적으로 해결했습니다.
하지만 페이지네이션은 시작에 불과했습니다. 진짜 도전 과제는 아직 남아 있었습니다.
LLM 스코어링: 자유 형식 프롬프트 대신 함수 호출 (Function Calling) 사용
스코어링 (scoring) 파이프라인을 위해서는 구조화되고 예측 가능한 출력이 필요했습니다. "JSON 객체를 반환하라"는 지침이 포함된 자유 형식 프롬프트 (freeform prompts)는 취약합니다. 어느 날은 LLM이 주석을 추가하기로 결정하고, 다음 날은 키 (key) 이름을 바꿔버립니다. 그러면 다운스트림 (downstream) 시스템이 망가집니다.
함수 호출 (Function calling)이 이 문제를 해결했습니다. 채용 공고를 후보자 프로필과 대조하여 점수를 매길 때 제가 사용하는 스키마 (schema)는 다음과 같습니다:
const scoringFunctions = [
{
name: 'score_job_match',
...
함수 호출을 사용하면 LLM은 매번 결정론적인 (deterministic) JSON 구조를 반환합니다. 파싱 에러 (parsing errors)도 없고, 스키마에 대한 환각 (hallucinations)도 없습니다. 그저 데이터베이스에 직접 파이프라인으로 연결할 수 있는 깨끗한 데이터가 있을 뿐입니다.
하지만 완벽한 출력 구조를 갖추었음에도 불구하고, 비용 문제는 여전히 남아 있었습니다.
비용 관리: 작동을 가능하게 만든 세 가지 전략
이 지점에서 대부분의 사람들은 프로덕션 AI를 포기합니다. OpenAI 청구서를 보고 당황하여 기능을 폐기하거나, 결함이 있는 버전을 출시해 버립니다. 저 또한 작동하는 접근 방식을 찾기 전까지 이 두 단계를 모두 거쳤습니다.
전략 1: 가능한 모든 것을 배치 (Batch) 처리하기
OpenAI의 배치 API (Batch API)는 처리 지연을 대가로 50%의 비용 절감을 제공합니다. 스코어링의 경우, 이는 괜찮은 선택입니다. 매물 점수가 몇 초 내에 나올 필요는 없습니다. 몇 시간 내에만 나오면 됩니다. 배치 엔드포인트 (endpoint)는 실시간 API와 동일한 페이로드 (payload)를 수용합니다. 저는 500개의 스코어링 요청을 큐 (queue)에 쌓아 배치 파일로 제출하고, 30~60분 후에 결과를 수집합니다. 매물당 비용은 즉시 떨어지고 처리량 (throughput)은 그대로 유지됩니다.
전략 2: 모델 계층화 (Tier your models)
모든 매물에 GPT-4 수준의 분석이 필요한 것은 아닙니다. 요구 기술이 명확한 단순한 매물은 GPT-4o mini로 점수를 매깁니다. 복잡한 임원급 역할이나 모호한 설명이 포함된 경우에는 GPT-4로 보냅니다. 라우팅 로직 (routing logic)은 간단합니다. 설명이 500단어 미만이고 요구 기술이 잘 정의되어 있다면 저렴한 모델을 사용합니다. 그렇지 않으면 상위 모델로 에스컬레이션 (escalate)합니다.
이 전략만으로도 측정 가능한 정확도 손실 없이 매물당 평균 비용을 약 70% 절감했습니다. 핵심 통찰은 대부분의 시스템에 있는 데이터의 대부분은 단순하다는 것입니다. 오직 극히 일부만이 무거운 모델을 필요로 합니다. 다수를 위해 설계하십시오.
전략 3: 공격적인 캐싱 (Cache aggressively).
매물의 점수가 이미 매겨졌고 변경 사항이 없다면, 다시 점수를 매기기 위해 비용을 지불하지 마십시오. 저는 매물 콘텐츠의 해시 (hash) 값과 후보자 프로필 ID를 키 (key)로 하는 캐시 계층 (cache layer)을 구축했습니다. 파이프라인은 LLM 호출을 하기 전에 캐시를 확인합니다. 반복되는 매물에 대한 히트율 (hit rate)은 약 40%에 달합니다. 이는 비용이 전혀 들지 않는 요청이 40%라는 의미입니다.
이 세 가지 전략을 모두 사용했음에도 불구하고, 고객의 AI 재작성 파이프라인은 중단되었습니다. 100만 개 이상의 매물 규모에서 발생하는 비용은 여전히 예산에 비해 너무 높았습니다. 이것이 프로덕션 AI (production AI)의 현실입니다. 비용 문제는 한 번에 해결되지 않습니다. 계속해서 최적화하거나, 충분히 저렴한 모델을 찾아 다시 시도해야 합니다. 이것이 제가 현재 DeepSeek V4 Flash를 통해 평가하고 있는 내용입니다.
REST API 계층: 점수화된 데이터의 소비 가능화
점수가 매겨진 매물들은 단순히 데이터베이스에 머물러 있지 않습니다. 하위 소비자 (downstream consumers)가 쿼리할 수 있는 REST API를 통해 제공됩니다.
API는 점수 범위, 위치, 기술 및 게시 날짜에 대한 필터 (filters)를 허용합니다. 각 엔드포인트 (endpoint)는 쿼리 패턴을 기록하여 제가 인덱스 (indexes)를 최적화하고 인기 있는 쿼리를 캐싱할 수 있도록 합니다. 응답 형식은 점수 필드가 최상위 레벨에 있는 평탄한 JSON (flat JSON) 구조로 되어 있어, 프론트엔드 개발자와 통합 파트너가 변환 과정 없이 쉽게 사용할 수 있습니다.
// 점수화된 매물에 대한 API 응답 형태
{
"id": "listing_abc123",
...
API 계층은 속도 제한 (rate limiting), API 키를 통한 인증 (auth), 그리고 요청 검증 (request validation)도 처리합니다. 이 부분은 사용자 및 통합 개발자(integrators)가 직접 접하는 영역이므로 빠르고 신뢰할 수 있어야 합니다. 모든 LLM 작업은 몇 시간 전 배치 윈도우 (batch window) 동안 이미 완료되었기 때문에, 모든 엔드포인트는 50ms 이내에 점수를 반환합니다.
내가 다르게 했을 점
만약 내가 오늘 이 시스템을 다시 시작한다면, GPT-4만 사용하는 단계를 완전히 건너뛰고 첫날부터 모델 계층화 (model tiering)를 시작할 것입니다. "일단 가장 좋은 모델로 작동하게 만들자"라는 방식의 비용은 실재하며, 이는 설계 단계가 아닌 실전 상황에서 최적화를 하도록 압박을 가하게 됩니다.
또한, 점수화 파이프라인 (scoring pipeline) 이후가 아니라 그 이전에 캐시 계층 (cache layer)을 구축했을 것입니다. 사후에 캐싱을 추가하는 것은 이미 비용을 지불한 점수들을 다시 계산하게 만든다는 것을 의미했습니다. 처음부터 캐시를 구축했다면 첫 달에 수천 달러를 아낄 수 있었을 것입니다.
그리고 고객과 비용에 관한 대화를 더 일찍 나누었을 것입니다. 코드를 작성하기 전에 비용 제약 조건에 대해 합의했더라면, 중단되었던 재작성 파이프라인 (rewrite pipeline)은 다르게 설계되었을 것입니다. 하지만 이것은 엔지니어링이 아닌 커뮤니케이션에 관한 교훈입니다.
프로덕션 AI (Production AI)는 가장 똑똑한 모델을 사용하는 것이 아닙니다. 첫날부터 비용, 지연 시간 (latency), 그리고 신뢰성을 고려하여 설계하는 것입니다. 모델 선택도 중요하지만, 아키텍처 (architecture)가 더 중요합니다.
만약 당신의 팀이 AI 기능을 구축하면서 "내 컴퓨터에서는 잘 되는데"와 "현금을 낭비하지 않고 대규모로 작동한다" 사이의 벽에 부딪히고 있다면, 그것이 바로 제가 도와드리는 문제입니다. 제가 프로덕션 AI 파이프라인을 구축하는 방식은 primestrides.com에서 확인할 수 있습니다. 귀하의 구체적인 과제에 대해 함께 의견을 나눌 수 있다면 기쁘겠습니다.
작성자: Abdul Rehman, 프로덕션 SaaS, MVP 및 AI 자동화를 구축하는 풀스택 AI 엔지니어. 더 자세한 내용은 PrimeStrides에서 확인하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기