대규모 프로덕션 RAG: 일일 10,000개 이상의 리스팅 처리에서 얻은 교훈
저는 채용 게시판을 위한 RAG 파이프라인을 구축했습니다. 스테이징 환경에서는 잘 작동했지만, 실제 부하가 걸리자 어려움을 겪었습니다. 매일 수천 개의 리스팅을 처리하려면 단순히 좋은 벡터 스토어만으로는 부족합니다. 시스템이 어디에서 무너지는지 반드시 이해해야 합니다.
청킹(chunking), 임베딩(embeddings), 비용, 그리고 관측성(observability)에 관한 저의 교훈을 공유합니다.
- 청킹 전략을 짐작으로 정하지 마세요
대부분의 튜토리얼은 청킹을 단순한 설정값으로 취급합니다. 하지만 프로덕션 환경에서는 청킹 전략이 정확도와 비용을 결정합니다.
저는 채용 리스팅에 대해 세 가지 방법을 테스트했습니다:
- 고정 크기 청크(Fixed-size chunks): 실패했습니다. "자격 요건(requirements)"이나 "혜택(benefits)" 같은 섹션을 무작위 지점에서 잘라버렸습니다. 이는 노이즈가 섞인 검색 결과를 초래합니다.
- 의미론적 청킹(Semantic chunking): 더 나았지만 일관성이 없었습니다. 어떤 청크는 너무 길고, 어떤 것은 너무 짧았습니다.
- 오버랩을 포함한 재귀적 문자 분할(Recursive character splitting with overlap): 가장 효과적이었습니다. 줄바꿈과 문장을 기준으로 분할했습니다. 400 토큰 크기에 50 토큰의 오버랩을 사용했습니다. 이를 통해 두 청크에 걸쳐 있는 문장이 연결된 상태를 유지할 수 있었습니다.
전문가 팁: 청킹 전에 데이터를 정규화하세요. Greenhouse나 Lever와 같은 서로 다른 소스는 각기 다른 형식을 반환합니다. 청커(chunker)가 일관된 구조를 인식할 수 있도록 텍스트를 먼저 정제해야 합니다.
- 임베딩: 비용 vs. 정확도
Ollama를 통한 Llama 3.1과 OpenAI의 text-embedding-3-small을 비교 테스트했습니다. 로컬 모델은 무료였지만 "주식 보상(equity compensation)"과 같은 도메인 특화 용어 처리에 어려움을 겪었습니다. 결과적으로 노이즈가 섞인 결과가 나왔습니다. OpenAI는 비용이 더 들었지만 정확한 매칭을 제공했습니다. 저는 나중에 잘못된 검색으로 인해 발생하는 LLM 호출 비용이 더 크기 때문에 OpenAI를 선택했습니다.
시간을 절약하기 위해 요청을 배치(batch) 처리합니다. 한 번의 호출에 최대 100개의 청크를 보냅니다. 이를 통해 지연 시간을 줄이고 파이프라인의 속도를 유지합니다.
- 벡터 스토어의 트레이드오프(Trade-off)
프로토타이핑 단계에서는 설정이 빠른 Pinecone을 사용했습니다. 하지만 규모가 커지면서 비용이 너무 높아졌습니다.
그래서 PostgreSQL 내부의 pgvector로 전환했습니다.
- 설정하는 데 더 많은 노력이 필요했습니다.
- 엄청난 비용을 절감했습니다.
- 트랜잭션 일관성을 제공했습니다. 임베딩이 채용 데이터와 동일한 데이터베이스에 저장되므로, 단일 진실 공급원(single source of truth)을 가질 수 있습니다. 두 개의 서로 다른 시스템을 동기화할 필요가 없습니다.
- LLM 비용 제어하기
모든 리스팅을 GPT-4o로 점수화하는 것은 비용이 많이 듭니다. 저는 비용을 낮추기 위해 세 가지 전략을 사용했습니다:
- OpenAI Batch API: 점수화 작업을 밤사이에 처리합니다. 이를 통해 큰 할인 혜택을 받을 수 있습니다.
- 캐싱(Caching): 반복되는 후보자 프로필에 대한 결과를 캐싱합니다.
- 모델 계층화(Model tiering): "영업 사원(Sales Representative)"과 같은 일반적인 직무에는 GPT-4o-mini를 사용합니다. 정밀도가 필수적인 니치(niche) 직무에만 GPT-4o를 사용합니다.
- 관측성(Observability)을 먼저 구축하세요
제 파이프라인은 한때 아무런 오류 메시지 없이 조용히 실패한 적이 있습니다. 잘못된 형식의 데이터가 빈 청크를 생성했고, 시스템은 이를 오류 없이 건너뛰었습니다.
저는 상관관계 ID(correlation ID)를 포함한 구조화된 로깅을 추가하여 이 문제를 해결했습니다. 이를 통해 데이터 수집부터 점수화까지 하나의 리스팅을 추적할 수 있게 되었습니다. 마침내 어떤 데이터 소스가 실패를 유발하는지 확인할 수 있었습니다.
가장 큰 교훈: 대부분의 문제는 AI가 아니라 지저분한 데이터에서 발생합니다. 데이터 파이프라인(data plumbing)부터 먼저 정비하세요.
선택 사항 학습 커뮤니티: https://t.me/GyaanSetuAi
