RAG 파이프라인의 토큰 세금
대규모로 검색 증강 생성(Retrieval-Augmented Generation, RAG) 시스템을 실행할 때, 실제 병목 현상은 임베딩 모델이나 벡터 인덱스가 아닙니다. 바로 검색과 생성 사이의 토큰 오버헤드입니다. 저는 에이전트 메모리 서비스가 일상적인 쿼리에서 200K 컨텍스트 창에 도달하면서 이 사실을 어렵게 배웠습니다. 그중 60%는 중복된 JSON 메타데이터와 압축되지 않은 도구 출력물에서 발생했습니다.
아키텍처는 표준적이었습니다: 의미 검색(semantic search)을 위한 pgvector, Python 오케스트레이션 레이어, 그리고 폴백 복원력(fallback resilience)을 위해 https://b-lost.com/v1의 다중 공급자 게이트웨이를 통해 라우팅된 Claude Sonnet이었습니다. 문제는 높은 동시성 실행 중에 발생했습니다. 요청당 중앙값 토큰 수가 2주 만에 운영 부하를 겪으면서 순수하게 누적된 검색 청크와 에이전트 단계 로그 때문에 45K에서 180K로 급증했습니다.
LLM 전 압축(Compression)의 역할
여기서 headroomlabs-ai/headroom이 중요해졌습니다. 원시 검색 결과와 JSON 블롭을 그대로 LLM 컨텍스트에 던져 넣는 대신, 우리는 벡터 검색 직후 프롬프트 조립 전에 압축 단계를 삽입했습니다. 이 라이브러리는 세 가지 핵심 감소 패턴을 처리합니다:
- 검색된 청크 간의 의미적 중복 제거(Semantic deduplication) (거의 동일한 문단을 걸러내기 위한 코사인 유사도 임계값 사용)
- JSON 구조 평탄화(JSON structure flattening) (불필요한 키를 제거하고 안전한 경우 중첩된 객체를 인라인 처리)
- 에이전트 도구 출력물에 대한 로그 요약(Log summarization) (장황한 스택 트레이스를 근본 원인 추출물로 대체)
다음은 검색 파이프라인에서의 통합 지점입니다:
import psycopg
from pgvector.psycopg import register_vector
from headroom import compress_chunks
...
HNSW 잠금 함정
한 가지 운영상의 함정: 대량 임베딩 삽입(분당 10K개 이상의 벡터) 과정에서 PostgreSQL의 HNSW 인덱스 잠금(index locking)으로 인해 심각한 쓰기 경합(write contention)이 발생했습니다. 그 결과, P95 삽입 지연 시간(insert latency)이 3.2초까지 급증했습니다. 해결책은 직관에 반하는 방식이었습니다. 배치 쓰기 동안 인덱스를 비활성화하고 나중에 재구축하는 것이었습니다:
-- 대량 삽입 전
DROP INDEX IF EXISTS memory_chunks_embedding_idx;
...
m=16과 ef_construction=64 매개변수는 매우 중요합니다. 이 값들을 높이면 재현율(recall)이 향상되지만, 인덱스 빌드 시간이 두 배로 늘어납니다. A/B 테스트 결과 m=16에서 recall@10이 0.91에 수렴하는 것을 확인한 후, 저희는 이 값들로 결정했습니다.
임베딩 정규화(Embedding Normalization) 문제점
또 다른 근본적인 실패 원인은 임베딩 모델을 변경한 후 재현율이 0.89에서 0.62로 떨어진 것이었습니다. 원인은 비정규화된 벡터였습니다. pgvector의 코사인 거리 연산자(cosine distance operator)는 단위 벡터(unit vectors)를 기대하지만, 저희가 새로 사용한 모델(파인튜닝된 bge-large-en-v1.5)은 비정규화된 임베딩을 반환했습니다. 해결책은 다음과 같습니다:
import numpy as np
def normalize_embedding(embedding):
...
정규화를 거친 후, 재현율은 0.87로 회복되었는데, 이는 추론 속도가 40% 빨라진 이점을 고려할 때 충분히 납득할 만한 수준이었습니다.
운영 지표(Production Metrics)
headroom의 압축 레이어를 통합한 후:
- 중앙값 토큰 수: 180K → 72K (60% 감소)
- P95 LLM 지연 시간: 8.2초 → 3.1초 (더 짧은 컨텍스트로 디코딩 속도 향상)
- 요청당 비용: $34 → $14 (입력 토큰 감소)
압축 적용률(compression hit rate)은 데이터 유형별로 다릅니다: JSON 도구 출력물은 8595%가 압축되는 반면, 자연어 검색 청크는 4050%가 압축됩니다. 구문 보존을 위해 코드 블록은 공격적인 압축에서 제외되었습니다.
해결되지 않은 딜레마
하나의 열린 질문이 있습니다: 의미적 중복 제거(semantic deduplication)는 언제 답변 품질을 저해할까요? 우리는 거의 동일한 두 청크(chunk)가 최종 답변에 중요한 미묘한 차이점을 포함하고 있는 사례를 목격했습니다. 하나는 사용 중단된 (deprecated) API 매개변수를 언급했고, 다른 하나는 그렇지 않았습니다. 압축 계층(compression layer)이 이들을 병합하면서, 대규모 언어 모델(LLM)은 사용 중단 경고를 놓쳤습니다. 우리는 경계에 있는(borderline) 청크를 더 많이 유지하기 위해 유사도 임계값(similarity threshold)을 0.92로 실험하고 있습니다 (기존 0.96에서 하향 조정). 하지만 이는 명확한 답이 없는 정밀도-재현율 트레이드오프(precision-recall trade-off) 문제입니다. 다른 사람들은 컨텍스트가 과부하되는(bloating context) 문제 없이 프로덕션 RAG 시스템에서 근접 중복 검색 결과(near-duplicate retrieval results)를 어떻게 처리하고 있을까요?