Skip to content
logo

블로그 검색 실험 (1) — 검색 방법론 개요와 실험 설계

2026-04-04Updated 2026-04-1111 min read·
#AI
#RAG
#Search
#BM25
#Vector-Database
#Hybrid-Search
#Embedding
#Experiment

280개 한국어+영어 블로그 포스트를 테스트베드로 활용하여 다양한 검색 방법론을 실험하는 시리즈의 첫 번째 글. 키워드 검색부터 벡터 검색, 하이브리드 검색까지 각 방법의 원리를 정리하고, 실험 설계와 평가 기준을 수립한다.

Summary

개인 블로그 280개 포스트를 대상으로 다양한 검색 방법론의 성능을 비교 실험하는 시리즈를 시작한다. 이 글에서는 실험 대상인 6가지 검색 방법의 원리를 정리하고, 테스트 데이터셋의 특성을 분석하며, 정량적·정성적 평가 기준과 실험 계획을 수립한다.

들어가며

RAG(Retrieval-Augmented Generation) 시스템에서 검색 품질은 최종 응답 품질을 직접적으로 좌우한다. 아무리 좋은 LLM을 써도 엉뚱한 문서를 가져오면 답변도 엉뚱하다.

그런데 "어떤 검색 방법이 가장 좋은가?"에 대한 답은 데이터 특성에 따라 크게 달라진다. 영어 위키피디아를 대상으로 한 벤치마크 결과가 한국어+영어 혼용 기술 블로그에 그대로 적용되지 않는다. 그래서 내 데이터로 직접 실험해보기로 했다.

이 시리즈에서는 내 블로그 280개 포스트를 테스트베드로 삼아 다양한 검색 방법을 구현하고, 어떤 방법이 — 그리고 어떤 조합이 — 가장 효과적인지 실험한다.

시리즈 구성

주제내용
(1) 실험 설계 (이 글)방법론 개요 + 실험 설계검색 방법 원리, 데이터셋 분석, 평가 기준
(2) 키워드 검색Ripgrep vs BM25 vs BM25+Kiwi정규식, TF-IDF, 한국어 형태소 분석 효과
(3) 벡터 검색임베딩 모델별 · 청킹 전략별 비교BGE-M3, multilingual-e5, OpenAI 등
(4) 하이브리드 검색조합별 성능 비교RRF, weighted fusion, 최적 조합 탐색
(5) 결론최종 결과 정리데이터 특성별 권장 조합, 교훈

테스트 데이터셋: 내 블로그

실험 대상은 이 블로그 자체다. Obsidian으로 작성하고 Next.js로 렌더링하는 마크다운 기반 블로그이며, 검색 실험에 흥미로운 특성을 여럿 갖고 있다.

기본 통계

항목
총 포스트 수280개
작성 기간2023년 ~ 2026년
언어 비율한국어 ~70%, 영어 ~20%, 혼용 ~10%
평균 포스트 길이~3,000–5,000 단어 (편차 큼)
최장 포스트~30,000 단어
포맷Markdown + YAML frontmatter

카테고리 분포

content/
├── AI/          ← ~127개 (45%)
├── Dev/         ← 일반 개발
├── Study/       ← 학습 기록
├── Projects/    ← 프로젝트 회고
├── Tools/       ← 도구 사용법
├── Events/      ← 행사/컨퍼런스
└── Others/      ← 분류 외

AI 카테고리에 절반 가까이 쏠려 있다. 이 불균형은 검색 실험에서 중요한 변수가 된다 — "AI" 같은 일반적인 키워드로 검색하면 거의 모든 글이 걸리기 때문이다.

메타데이터 구조

모든 포스트에 YAML frontmatter가 있다:

---
title: '챗봇 성능 최적화: 캐싱 전략으로 비용 절감과 속도 향상'
date: 2025-09-07
tags:
- AI
- Caching
- Chatbot
draft: false
enableToc: true
description: 챗봇 시스템에서 캐싱 전략을 적용하여...
summary: ...
published: 2025-09-07
modified: 2025-09-07
---

이 메타데이터는 그 자체로 검색 대상이기도 하고, 다른 검색 방법의 필터 조건으로도 활용된다.

검색 실험에 영향을 주는 데이터 특성

특성설명검색에 미치는 영향
한국어+영어 혼용본문은 한국어, 기술 용어는 영어형태소 분석 필수, 다국어 임베딩 모델 필요
교착어"데이터베이스를", "데이터베이스의", "데이터베이스"형태소 분석 없으면 같은 단어를 다르게 인식
기술 전문 용어"HNSW", "ReAct", "pgvector"일반 임베딩 모델이 이런 용어를 얼마나 잘 표현하는지
코드 블록Python, YAML, SQL 등 코드 포함코드 내 키워드 검색 vs 자연어 검색 간 차이
Obsidian 위키링크[[다른 글 제목]] 형태 내부 링크문서 간 관계 그래프 구성 가능
길이 편차1,000단어 ~ 30,000단어긴 문서는 청킹이 필수, 짧은 문서는 통째로 검색
카테고리 불균형AI 45%, 나머지 분산범용 키워드의 precision 저하
Info

이 데이터셋은 "소규모 + 한국어 + 기술 블로그"라는 니치한 특성을 갖고 있다. 대규모 영어 벤치마크(MS MARCO, Natural Questions 등)와는 성격이 다르며, 바로 그 점이 직접 실험하는 이유다.

실험 대상: 검색 방법론 6가지

방법 1: Ripgrep — 정규식 키워드 검색

가장 원시적이면서 가장 확실한 방법. 텍스트를 있는 그대로 매칭한다.

원리:

  • 파일 시스템을 직접 스캔하며 정규식 패턴과 일치하는 라인을 찾음
  • Rust 기반으로 280개 파일을 10ms 이내에 전수 검색
  • 매칭 횟수를 스코어로 활용 (많이 등장하는 파일 = 더 관련 있음)

예상 강점:

  • 정확한 키워드, 에러 메시지, 함수명 검색에 최강
  • 구현 가장 단순, 외부 의존성 거의 없음
  • 코드 블록 내 검색에 강함

예상 약점:

  • "에이전트"로 검색하면 "agent"를 못 찾음 (동의어 불가)
  • "LLM 에이전트 아키텍처"로 검색하면 "ReAct 패턴" 글을 못 찾음 (시맨틱 불가)
  • 한국어 조사에 취약: "데이터베이스를" ≠ "데이터베이스"

방법 2: BM25 — 통계적 키워드 랭킹

정보 검색(IR)의 클래식. TF-IDF를 개선한 확률적 랭킹 모델.

원리:

BM25(d,q)=tqIDF(t)f(t,d)(k1+1)f(t,d)+k1(1b+bdavgdl)BM25(d, q) = \sum_{t \in q} IDF(t) \cdot \frac{f(t, d) \cdot (k_1 + 1)}{f(t, d) + k_1 \cdot (1 - b + b \cdot \frac{|d|}{avgdl})}
  • IDF(t)IDF(t): 단어 tt가 전체 코퍼스에서 얼마나 희귀한지 (희귀할수록 높은 가중치)
  • f(t,d)f(t, d): 문서 dd에서 단어 tt의 출현 빈도
  • d/avgdl|d| / avgdl: 문서 길이 정규화 (긴 문서 페널티)

Ripgrep과 다른 점은 코퍼스 전체를 고려한 통계적 중요도를 반영한다는 것이다. "AI"처럼 모든 글에 있는 단어는 점수가 낮고, "HNSW"처럼 소수 글에만 있는 단어는 높다.

예상 강점:

  • 키워드 관련도 랭킹이 Ripgrep보다 정교
  • 문서 길이 정규화로 긴 글이 불리하지 않음

예상 약점:

  • Ripgrep과 동일한 시맨틱 한계
  • 한국어 토크나이징 문제 동일 (공백 분리 시)

방법 3: BM25 + 한국어 형태소 분석 (Kiwi)

방법 2에 한국어 형태소 분석기를 결합. 이 실험에서 가장 궁금한 비교 중 하나다 — 형태소 분석이 얼마나 차이를 만드는가?

원리:

  • kiwipiepy로 텍스트를 형태소 단위로 분리
  • 명사(NNG, NNP), 동사(VV), 형용사(VA), 외래어(SL)만 추출
  • 추출된 형태소를 BM25 토큰으로 사용
입력: "벡터 데이터베이스를 비교했다"

공백 분리: ["벡터", "데이터베이스를", "비교했다"]
Kiwi 분석: ["벡터", "데이터베이스", "비교", "하다"]

실험 포인트:

  • 형태소 분석 유무에 따른 한국어 검색 품질 차이는?
  • 영어 기술 용어가 섞인 문장에서 Kiwi가 제대로 동작하는가?
  • Kiwi vs 단순 정규식 토크나이저([a-zA-Z가-힣0-9]+) 성능 차이는?

방법 4: 벡터 검색 (임베딩 + VDB)

텍스트를 고차원 벡터로 변환하고, 벡터 간 거리로 유사도를 측정.

원리:

  1. 임베딩 모델이 텍스트 → 벡터 변환 (예: 1024차원)
  2. 쿼리도 같은 모델로 벡터 변환
  3. 코사인 유사도로 가장 가까운 벡터(=문서)를 검색

실험 변수 — 임베딩 모델:

모델차원한국어 지원특징
BGE-M31024우수dense + sparse 동시 출력
multilingual-e5-large1024양호범용 다국어
OpenAI text-embedding-3-small1536보통API 기반, 유료
ko-sroberta-multitask768우수한국어 특화

실험 변수 — 청킹 전략:

전략설명
문서 통째로청킹 없이 문서 전체를 하나의 벡터로
고정 크기 (512 토큰)기계적으로 자르기
마크다운 시맨틱 (H2 기반)헤딩 구조를 기준으로 분할
문단 단위\n\n 기준 분할

실험 변수 — 벡터 DB:

Qdrant를 기본으로 사용한다. 선택 이유:

기준QdrantPinecone
무료 tier1GB, always-on2M 벡터, scale-to-zero
Cold start없음1-2초 (idle 후)
하이브리드 검색Named Vectors + RRF 네이티브지원하지만 덜 유연
로컬 개발Docker / in-memory 모드불가
Tip

Pinecone serverless는 idle 후 scale-to-zero되므로, 저트래픽 상황에서 첫 쿼리마다 cold start가 발생한다. 실험 중 반복 테스트에서도 이 지연이 거슬리므로, 로컬에서 바로 실행 가능한 Qdrant가 실험 환경으로도 유리하다.

예상 강점:

  • "에이전트"로 검색 → "ReAct 패턴", "Plan-and-Execute" 글 발견 (시맨틱 유사도)
  • 동의어, 다국어 매칭

예상 약점:

  • 정확한 키워드 매칭은 오히려 부정확할 수 있음
  • 임베딩 모델의 한국어 품질에 의존
  • 청킹 전략에 따라 결과가 크게 달라질 수 있음

방법 5: 메타데이터 필터

frontmatter의 구조화된 데이터를 직접 활용하는 방법.

원리:

  • 태그, 카테고리, 날짜 등 메타데이터로 정확한 필터링
  • 다른 검색 방법과 결합 가능 (예: "2025년 이후 AI 카테고리에서 벡터 검색")

단독 vs 결합:

  • 단독: "RAG 태그가 붙은 글 전부" → 정확하지만 랭킹 없음
  • 결합: 벡터 검색 + 메타데이터 필터 → Qdrant payload filter로 동시 처리

실험 포인트:

  • 메타데이터 필터를 pre-filter로 걸면 벡터 검색 정확도가 올라가는가?
  • 태그의 품질(일관성)이 필터 효과에 얼마나 영향을 주는가?

방법 6: 하이브리드 검색 (Fusion)

위 방법들을 조합하고 결과를 통합.

퓨전 방법:

방법원리장점
RRF (Reciprocal Rank Fusion)순위 기반, 1k+rank\frac{1}{k + rank}로 합산스코어 스케일 무관
Weighted Sum각 방법의 정규화 점수를 가중 합산방법별 가중치 조절 가능
Cascade1차 검색 → 상위 N개만 2차 검색비용 절감

실험할 조합:

조합구성
ABM25+Kiwi + 벡터 검색
BRipgrep + 벡터 검색
CBM25+Kiwi + 벡터 검색 + 메타데이터 필터
D전체 5가지 통합
EQdrant 네이티브 하이브리드 (dense + sparse in BGE-M3)
Info

조합 E가 특히 흥미로운 실험이다. BGE-M3는 한 번의 인코딩으로 dense vector와 sparse vector(lexical weights)를 동시에 출력한다. 이걸 Qdrant Named Vectors에 넣으면 별도 BM25 파이프라인 없이 하이브리드 검색이 가능하다. 과연 전통적인 BM25+Kiwi 대비 어떤 결과를 보이는지.

평가 기준

테스트 쿼리 셋

실험의 핵심은 "무엇으로 평가할 것인가"다. 다음 4가지 유형의 테스트 쿼리를 준비한다:

Type 1 — 정확 키워드 (Exact Keyword)

쿼리기대 결과
create_react_agent해당 함수를 사용하는 글
ModuleNotFoundError에러를 다룬 글
pgvectorpgvector 관련 글 전부

Type 2 — 자연어 질문 (Natural Language)

쿼리기대 결과
"LLM 에이전트 아키텍처 비교"ReAct, Plan-and-Execute, Supervisor 글
"PDF에서 텍스트 추출하는 방법"PDF 파서 비교, OCR 관련 글
"벡터 DB 성능 차이"pgvector vs Qdrant vs Milvus 글

Type 3 — 다국어 혼용 (Cross-lingual)

쿼리기대 결과
"agent 만들기"에이전트/agent 모두 포함된 글
"RAG 파이프라인 구축"RAG 관련 글 (한영 혼용)
"embedding model comparison"임베딩 모델 비교 글 (한국어 작성)

Type 4 — 구조화 필터 (Structured)

쿼리기대 결과
"2025년 이후 RAG 관련 글"날짜 + 태그 필터
"AI 카테고리 최신 5개"카테고리 + 정렬
"LangChain 태그 글 중 성능 관련"태그 필터 + 시맨틱

정량 평가 지표

지표설명측정 방법
Precision@K상위 K개 결과 중 관련 문서 비율수동 레이블링
Recall@K전체 관련 문서 중 상위 K개에 포함된 비율수동 레이블링
MRR (Mean Reciprocal Rank)첫 번째 관련 문서의 순위 역수 평균자동 계산
nDCG@10순위 가중 관련도 점수관련도 등급(0/1/2) 부여
Latency쿼리 응답 시간 (ms)자동 측정

정성 평가

숫자만으로 포착하기 어려운 부분도 기록한다:

  • Surprise: 예상 못한 관련 문서를 찾아준 경우 (좋은 serendipity)
  • Noise: 명백히 무관한 문서가 상위에 나오는 경우
  • Coverage: 쿼리 유형별로 어떤 방법이 강한지/약한지 패턴
  • 한국어 특이 케이스: 형태소 분석 유무에 따른 체감 차이

Ground Truth 구축

280개 문서에 대한 완전한 ground truth를 만드는 것은 비현실적이다. 대신:

  1. 테스트 쿼리 30~50개를 유형별로 준비
  2. 각 쿼리에 대해 관련 문서를 수동으로 레이블링 (0: 무관, 1: 관련, 2: 매우 관련)
  3. 모든 검색 방법의 결과를 블라인드 셔플해서 평가 (어떤 방법의 결과인지 모르는 상태로)
Warning

자기 블로그라 편향이 생기기 쉽다. "내가 이 글 쓸 때 이 의도였으니까 관련 있어"라는 주관이 개입될 수 있다. 블라인드 평가로 최대한 완화하되, 본질적으로 N=1 실험이라는 한계는 인정한다.

실험 환경

인프라

구성 요소기술환경
벡터 DBQdrant로컬: Docker / in-memory, 운영: Qdrant Cloud Free
임베딩BGE-M3 (기본), 비교 모델들로컬 GPU or CPU
형태소 분석kiwipiepy로컬
키워드 검색ripgrep로컬 (시스템 도구)
에이전트LangGraph최종 통합 시
블로그Next.js + Obsidian contentVercel

코드 구조

실험 코드는 syshin0116.dev/agent/ 에 위치한다:

agent/src/agent/lib/
├── ripgrep_search.py      # 방법 1: Ripgrep
├── bm25_search.py         # 방법 2, 3: BM25 (±Kiwi)
├── vector_search.py       # 방법 4: Qdrant 벡터 검색 (구현 예정)
├── frontmatter_index.py   # 방법 5: 메타데이터 필터
├── hybrid_fusion.py       # 방법 6: 하이브리드 퓨전 (구현 예정)
├── content_loader.py      # 마크다운 로딩 + 파싱
├── wikilink_graph.py      # 위키링크 그래프 탐색
└── types.py               # 공통 타입

다음 편 예고

(2) 키워드 검색 실험에서는 Ripgrep, BM25(공백 분리), BM25+Kiwi 세 가지를 동일한 쿼리 셋으로 비교한다. 특히 한국어 형태소 분석의 효과를 정량적으로 측정하고, 키워드 검색만으로 어디까지 커버 가능한지 한계를 확인한다.

참고자료

Comments