
프로덕션급 검색(Retrieval)의 구조
요약
프로덕션 환경에서 신뢰할 수 있는 RAG 시스템을 구축하기 위한 엔지니어링적 의사결정 과정을 다룹니다. 단순한 3단계 워크플로우를 넘어 인제스션, 라우팅, 리랭킹, 평가 등 전체 파이프라인의 최적화와 운영 전략을 설명합니다.
핵심 포인트
- RAG 품질은 단일 모델이 아닌 전체 파이프라인의 통합적 설계에 의해 결정됨
- 신뢰할 수 있는 인제스션(Ingestion) 파이프라인 구축이 검색 성능의 기초임
- 쿼리 변환, 메타데이터 필터링, 리랭킹 등 단계별 최적화 전략 필요
- 단순 파싱을 넘어 OCR 및 폴백(Fallback)을 포함한 견고한 데이터 처리 요구
검색 증강 생성 (Retrieval-Augmented Generation, RAG)은 흔히 문서를 분할(split)하고, 청크(chunks)를 임베딩(embed)하며, 가장 유사한 결과를 언어 모델(language model)로 보내는 3단계 워크플로우로 요약됩니다. 이러한 설명은 프로토타입 제작에는 유용하지만, 실제 트래픽 환경에서 검색 시스템이 정확성, 속도, 비용 효율성을 유지할 수 있는지 결정하는 엔지니어링적 의사결정 과정은 숨겨져 있습니다.
프로덕션급 검색은 단계적인 의사결정 및 랭킹(ranking) 시스템입니다. 신뢰할 수 있는 인제스션(ingestion) 경로가 소스 자료를 검색 가능한 상태로 만듭니다. 라우팅(Routing)은 각 요청에 적합한 데이터 시스템을 선택합니다. 문서 구조는 청크(chunk)의 경계를 결정합니다. 임베딩 모델(Embedding models)은 의미론적 공간(semantic space)을 정의합니다. 쿼리 변환(Query transformation)은 잘못 구성된 요청을 개선합니다. 메타데이터(Metadata) 및 액세스 제어(access-control) 필터는 어떤 증거가 적합한지를 결정합니다. 어휘 검색(Lexical search)은 정확한 식별자(identifiers)를 보호하며, 밀집 검색(dense search)은 의미적 유사성(semantic similarity)을 포착합니다. 퓨전(Fusion)은 독립적인 후보 목록들을 결합합니다. 리랭킹(Reranking)은 최종 컨텍스트(context)를 변경할 수 있는 곳에만 추가적인 연산을 할당합니다. 컨텍스트 조립(Context assembly)은 최종 증거 세트를 생성(generation)을 위한 압축되고 추적 가능한 입력값으로 변환합니다. 평가(Evaluation), 관측 가능성(observability), 그리고 라이프사이클 제어(lifecycle controls)는 모든 단계를 측정 가능하고 운영 가능하게 만듭니다.
단일 구성 요소가 다른 구성 요소의 모든 약점을 보완할 수는 없습니다. 강력한 리랭커(reranker)라도 청킹(chunking) 과정에서 파괴된 테이블 행을 복구할 수는 없습니다. 강력한 임베딩 모델이라 할지라도, 특히 식별자가 드물거나, 불투명하거나, 학습 데이터 분포에 없는 경우 모든 정확한 식별자를 안정적으로 검색할 것이라고 기대할 수 없습니다. 컨텍스트 윈도우(context window)가 크다고 해서 부적절한 검색이 무해해지는 것도 아닙니다. 품질은 전체 파이프라인(pipeline)에서 나타납니다.
검색은 인제스션(Ingestion)에서 시작된다
쿼리 시점(query-time)의 시스템은 인제스션 파이프라인이 검색 가능하게 만든 것만을 검색할 수 있습니다. 임베딩이 생성되기 전에, 각 문서는 원래의 페이지 밖에서도 의미를 유지할 수 있는 단위로 파싱(parsed)되어야 합니다. 프로덕션 환경에서 이는 단순한 단일 파서 호출이 아니라, 버전 관리가 되고 재시도(retryable)가 가능한 데이터 파이프라인입니다.
대표적인 인제스션 경로는 다음과 같습니다:
파일 수신 (File received)
↓
MIME 및 유형 감지 (MIME and type detection)
...
디지털 PDF는 이미 사용 가능한 텍스트 레이어 (text layer)를 포함하고 있을 수 있는 반면, 스캔된 PDF는 OCR (광학 문자 인식) 및 신뢰도 기반 검증 (confidence-aware validation)이 필요합니다. 파이프라인은 OCR을 무분별하게 적용하는 대신 이러한 차이를 감지해야 합니다. 암호화되었거나, 손상되었거나, 지원되지 않거나, 또는 부분적으로 파싱된 파일들은 명시적인 상태 (explicit states)를 가져야 합니다. 파서 (parser) 실패 시 두 번째 파서나 OCR 경로와 같은 제어된 폴백 (fallback)을 트리거할 수 있지만, 폴백 결과가 검증 없이 고품질 추출 결과(higher-quality extraction)를 조용히 대체해서는 안 됩니다. 표 (tables), 도표 (figures), 캡션 (captions), 페이지 헤더 (page headers) 및 읽기 순서 (reading order)는 텍스트의 완전성 (plain-text completeness)이 구조적 정확성 (structural correctness)을 보장하지 않기 때문에 각각 별도의 추출 및 품질 검사가 필요합니다.
프로덕션급 인제스션 (ingestion)은 멱등성 (idempotent)을 유지해야 합니다. 동일한 소스 버전을 재처리하더라도 중복된 청크 (chunks)나 벡터 (vectors)가 생성되어서는 안 됩니다. 콘텐츠 해시 (content hashes)를 통해 바이트 단위로 동일하거나 정규화된 콘텐츠 (normalized-content)의 중복을 감지할 수 있으며, 안정적인 문서 및 청크 식별자 (identifiers)를 통해 업데이트와 삭제를 추적할 수 있습니다. 일시적인 실패 (transient failures)는 제한된 재시도 큐 (bounded retry queues)에서 처리되어야 하며, 반복적으로 실패하는 항목은 원래의 오류, 파서 버전, 시도 횟수 및 소스 참조를 포함하여 데드 레터 큐 (dead-letter queue)로 보내야 합니다. 처리 메타데이터 (processing metadata)는 received (수신됨), parsed (파싱됨), validated (검증됨), indexed (인덱싱됨), failed (실패함), superseded (대체됨)와 같은 상태를 구분하여 불완전한 문서가 인지되지 않은 채 서빙 인덱스 (serving index)에 들어가는 것을 방지해야 합니다.
비즈니스 문서의 몇 가지 예시는 다음과 같습니다:
- 계약서 (contract)는 섹션 및 조항 (clause)의 경계를 보존해야 합니다.
- 제품 카탈로그 (product catalog)는 제품명, 코드, 사양 (specifications) 및 설명을 함께 유지해야 합니다.
- 인보이스 표 (invoice table)는 열 헤더 (column header)와 해당 값을 분리해서는 안 됩니다.
- 질의응답 (question-and-answer) 모음은 각 쌍을 하나의 자연스러운 단위로 사용해야 합니다.
- 긴 보고서 (long report)는 추출된 모든 구절에 섹션 제목과 문서 메타데이터를 부착해야 합니다.
이러한 모든 소스들을 비정형 텍스트 (unstructured text)로 평탄화 (flattening)하고 매 500 토큰 (tokens)마다 자르는 방식은 유효한 문자열을 생성할 수는 있지만, 검색 (retrieval)에 필요한 관계성을 파괴할 수 있습니다.
유용한 청크 메타데이터 (chunk metadata)에는 일반적으로 문서 식별자 (document identifier) 및 버전, 페이지 번호, 섹션 제목, 문서 유형, 소스 타임스탬프 (source timestamps), 언어, 공급업체 또는 제품 식별자, 액세스 제어 태그 (access-control tags), 처리 상태, 파서 (parser) 및 청커 (chunker) 버전, 임베딩 모델 (embedding model) 버전, 인덱스 (index) 버전, 그리고 콘텐츠 해시 (content hash)가 포함됩니다. 메타데이터는 필터링 (filtering), 출처 (provenance), 최신성 제어 (freshness controls), 삭제 전파 (deletion propagation), 그리고 안전한 재인덱싱 (re-indexing)을 지원합니다.
청킹 전략 (Chunking Strategies)
고정 크기 청킹 (Fixed-Size Chunking)
고정 크기 청킹 (Fixed-size chunking)은 일정한 토큰 (token) 또는 문자 길이에 따라 텍스트를 나눕니다. 이는 빠르고 예측 가능하며 배치 (batch) 처리가 쉽습니다. 단점은 구조적 맹목성 (structural blindness)입니다. 문장, 표의 행 (table row), 또는 절 (clause)이 두 개의 청크에 걸쳐 분할될 수 있습니다.
고정 크기 청킹은 균질한 산문 (homogeneous prose)에 사용하거나 베이스라인 (baseline)으로서 여전히 유용합니다. 하지만 이를 보편적인 기본값 (universal default)으로 취급해서는 안 됩니다.
재귀적 청킹 (Recursive Chunking)
재귀적 청킹 (Recursive chunking)은 점진적으로 더 작은 경계 (boundaries)를 시도합니다. 섹션 (section), 그다음 문단 (paragraph), 그다음 문장 (sentence), 마지막으로 문자 (characters) 단위로 분할할 수 있습니다. 이 접근 방식은 최대 크기를 강제하면서도 더 자연스러운 구조를 유지합니다.
재귀적 분할 (Recursive splitting)은 단순함과 기본적인 문서 인식 (document awareness) 사이의 균형을 맞추기 때문에 종종 실용적인 범용 선택지가 됩니다. 다만, 이는 여전히 파서 (parser)가 의미 있는 구분자 (separators)를 보존하는지에 달려 있습니다.
의미론적 청킹 (Semantic Chunking)
의미론적 청킹 (Semantic chunking)은 고정된 길이가 아니라 주제가 바뀌는 지점에서 텍스트를 나눕니다. 문장들을 임베딩 (embedded)하고, 인접한 문장들 사이의 유사도 (similarity)를 검사합니다. 유사도가 급격히 감소하면 의미론적 경계 (semantic boundary)를 나타낼 수 있습니다.
이 방법은 일관성 있는 청크를 생성할 수 있지만, 데이터 수집 비용 (ingestion cost)을 증가시키며, 인접한 문장들이 동일한 주제에 속함에도 불구하고 서로 다른 어휘 (vocabulary)를 사용할 경우 텍스트를 과도하게 분할 (over-segment)할 수 있습니다. 따라서 최소 및 최대 청크 크기가 여전히 필요합니다.
후기 청킹 (Late Chunking)
전통적인 청킹 (Traditional chunking)은 임베딩 모델 (embedding model)이 문서를 보기 전에 문서를 분리합니다. 모든 청크는 독립적으로 인코딩되므로, 해당 벡터는 문서의 나머지 부분에 대한 인지 능력을 갖지 못합니다.
후기 청킹 (Late chunking)은 그 순서를 뒤집습니다. 전체 문서, 또는 임베딩 모델이 지원하는 가장 큰 문서 윈도우 (document window)가 먼저 인코딩되어 각 토큰 (token)에 대한 문맥적 벡터 (contextual vector)를 생성합니다. 그런 다음 토큰 벡터들은 청크 경계에 따라 그룹화되어 청크 벡터 (chunk vector)로 풀링 (pooled)됩니다. 따라서 "이 양 (this amount)"을 설명하는 청크는 해당 양의 주체를 식별한 이전 구절의 정보를 유지할 수 있습니다. 이것이 후기 청킹 방법 [1]에서 설명하는 핵심 아이디어입니다.
만약 5,000 토큰 문서가 5000 × 1024 토큰 표현 (representation)을 생성한다면, 처음 500개의 토큰 벡터를 평균 내어 하나의 1 × 1024 청크 벡터로 만들고, 다음 500개를 또 다른 벡터로 만드는 식의 과정을 반복할 수 있습니다. 데이터베이스는 여전히 청크 수준의 벡터를 저장하지만, 해당 벡터들은 문서 수준의 문맥 (document-level context)을 가지고 형성되었습니다.
후기 청킹은 문맥 보존 능력을 향상시킬 수 있지만, 토큰 수준의 표현을 노출하고 문서 길이를 지원하는 임베딩 모델이 필요합니다. 또한 데이터 수집 (ingestion) 복잡도와 메모리 사용량을 증가시킵니다. 후기 청킹을 나중에 멀티 벡터 검색 (multi-vector retrieval) 섹션에서 다룰 쿼리 시점 매칭 기술인 후기 상호작용 (late interaction)과 혼동해서는 안 됩니다.
구조 인식 및 검색 인식 변형 (Structure-Aware and Retrieval-Aware Variants)
위의 네 가지 전략은 일반적인 패턴일 뿐, 완전한 분류 체계는 아닙니다. 비즈니스 문서는 종종 산문 (prose)의 경계 그 이상을 보존하는 청킹 정책이 필요합니다:
- **레이아웃 인식 청킹 (Layout-aware chunking)**은 페이지를 먼저 평탄화(flattening)하는 대신, 감지된 제목, 단락, 목록, 열(columns), 표, 캡션 및 페이지 영역을 따릅니다.
- **계층적 청킹 (Hierarchical chunking)**은 문서, 섹션, 하위 섹션 및 청크 간의 관계를 보존하여 검색(retrieval)이 계층 간을 이동할 수 있도록 합니다.
- **부모-자식 검색 (Parent-child retrieval)**은 정밀도를 위해 작은 자식 청크(child chunks)를 검색한 다음, 문맥(context)을 위해 더 큰 부모 섹션(parent section)을 답변 모델에 반환합니다.
- **문장 윈도우 검색 (Sentence-window retrieval)**은 개별 문장이나 작은 구간을 인덱싱하고, 일치하는 항목 주변의 인접 문장을 포함하여 범위를 확장합니다.
- **표 인식 청킹 (Table-aware chunking)**은 헤더, 행 레이블, 단위 및 셀을 연결된 상태로 유지합니다. 헤더가 없는 행은 스스로 설명하기 어려운 경우가 많습니다.
- **명제 기반 청킹 (Proposition-based chunking)**은 산문(prose)을 더 작고 독립적으로 검증 가능한 주장(claims)으로 변환하며, 인덱싱 비용을 대가로 더 원자적인(atomic) 검색을 가능하게 합니다.
- **문맥적 청킹 (Contextual chunking)**은 임베딩(embedding) 및 어휘 인덱싱(lexical indexing)을 수행하기 전에 각 청크에 해당 문서나 섹션에 대한 간결한 설명을 추가하여 풍부하게 만듭니다.
이러한 방법들은 결합될 수 있습니다. 레이아웃 파서(layout parser)가 먼저 표나 섹션을 식별하고, 계층적 규칙이 부모 관계를 보존하며, 이후 문장 윈도우 확장이 로컬 문맥을 추가할 수 있습니다. 검색을 위한 적절한 단위가 언어 모델로 전송되는 단위와 반드시 같을 필요는 없습니다.

청크 크기(Chunk Size) 및 중첩(Overlap) 선택
청크 크기는 정밀도(precision)와 문맥(context) 사이의 트레이드오프(trade-off) 관계입니다. 더 작은 청크는 개별 사실을 분리하여 검색 정밀도를 높일 수 있습니다. 하지만 문장에 의미를 부여하는 문맥으로부터 해당 진술을 분리해 버릴 위험이 있습니다. 더 큰 청크는 문맥을 보존하지만, 하나의 벡터로 여러 개의 관련 없는 주제를 나타낼 수 있어 의미론적 특이성(semantic specificity)을 감소시킬 수 있습니다.
중첩(Overlap)은 경계 근처의 정보를 보호할 수 있지만, 과도한 중첩은 중복된 결과를 생성하고 저장 공간을 증가시킵니다. 적절한 중첩은 안전장치일 뿐, 문서 인지적 경계(document-aware boundaries)를 대체할 수는 없습니다.
올바른 설정은 실제 코퍼스(corpus)에 대한 검색 테스트를 통해 선택되어야 합니다. 유용한 실험 방식은 골든 쿼리 세트(golden query set)를 대상으로 여러 청크 크기(chunk sizes)와 전략을 비교하는 것입니다. 출력 결과에는 검색 재현율(retrieval recall), 정밀도(precision), 인덱스 크기(index size), 인제스션 시간(ingestion time), 쿼리 지연 시간(query latency), 그리고 언어 모델(language model)로 전송되는 평균 컨텍스트 양이 포함되어야 합니다.
컨텍스트 헤더(Contextual headers)는 또 다른 개선 사항을 제공합니다. 청크 앞에 문서 이름, 섹션, 제품군(product family), 공급업체 또는 기간과 같은 간결한 메타데이터를 접두사(prefix)로 붙일 수 있습니다. 이를 통해 청크를 독립적으로 이해할 수 있게 만들며, 해당 임베딩(embedding)이 의도된 쿼리와 일치할 가능성을 높여줍니다. 생성된 답변이 수정되지 않은 근거를 인용할 수 있도록 원문은 여전히 별도로 보존되어야 합니다.
검색 모델로서의 임베딩 (Embeddings as a Retrieval Model)
임베딩 모델은 텍스트를 고정된 차원의 벡터(vector)로 변환합니다. 학습 과정에서 의미론적으로 관련된 예시들은 서로 가까워지도록 끌어당겨지고, 관련 없는 예시들은 서로 멀어지도록 밀려납니다. 검색은 이렇게 학습된 공간의 기하학적 구조를 사용하여 쿼리에 대한 청크의 순위를 매깁니다.
모델 선택은 벡터 차원이나 일반적인 인기도보다는 대상 작업(target task)에 집중해야 합니다. 관련 기준은 다음과 같습니다:
- 문서 언어에 대한 성능
- 도메인 전문 용어 처리 능력
- 최대 입력 길이
- 지연 시간(latency) 및 배치(batching) 동작
- 배포 제약 사항
- 실제 코퍼스에서의 검색 정확도
여러 후보 모델이 동일한 컬렉션을 임베딩하고 동일한 골든 쿼리를 실행해야 합니다. 승자는 운영 제약 조건 내에서 가장 신뢰할 수 있게 올바른 근거를 검색하는 모델입니다.
도메인 미세 조정 (Domain Fine-Tuning)
임베딩 미세 조정 (Embedding fine-tuning)은 도메인 특화된 양성 (positive) 및 음성 (negative) 쌍을 사용합니다. 양성 쌍은 사용자의 질문과 그에 맞는 제품 설명 또는 계약 조항을 포함할 수 있습니다. 무작위 음성 (random negative)은 관련이 없는 데이터입니다. 신중하게 채굴된 하드 네거티브 (hard negatives)는 쿼리를 충족하지 않으면서도 그럴듯해 보이기 때문에 무작위 음성보다 더 많은 정보를 제공하는 경우가 많습니다. 하지만 이들이 항상 자동으로 더 나은 것은 아닙니다. 잘못 레이블링된 하드 네거티브는 실제로는 유효한 근거를 포함하고 있을 수 있으며, 이 경우 모델이 실제로 관련 있는 쌍을 분리하도록 학습시키는 거짓 음성 (false negative)을 생성하게 됩니다.
제품의 반품 조건에 대한 쿼리의 경우, 보증 범위에 관한 다른 조항이 유용한 하드 네거티브가 될 수 있습니다. 후보 음성 (Candidate negatives)은 인배치 (in-batch) 예시, 이전 단계의 검색기 (retriever), 교사 모델 (teacher-model) 채굴, 또는 혼동을 줄 수 있는 유사 일치 항목을 식별하는 교차 인코더 (cross-encoder)로부터 가져올 수 있습니다. 특히 여러 구절이 동일한 쿼리에 답할 수 있는 경우에는 거짓 음성 필터링 (False-negative filtering)과 샘플에 대한 수동 검토가 중요합니다. 쌍의 품질 (Pair quality), 소스의 다양성 (source diversity), 그리고 쉬운 음성과 어려운 음성 사이의 균형은 종종 학습량만큼이나 중요합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기