홈

© 2026 Ki Chang. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

소개JSON Formatter개인정보처리방침이용약관

© 2026 Ki Chang. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

소개|JSON Formatter|개인정보처리방침|이용약관

개인 블로그에 AI 검색 달기 (1) - 왜 하이브리드 검색인가

정기창·2026년 2월 1일

블로그 검색, 뭔가 부족하다

블로그에 검색 기능을 달았지만 뭔가 아쉬웠습니다. MongoDB의 텍스트 인덱스를 사용한 키워드 검색은 분명 동작했지만, 제가 원하는 검색 경험과는 거리가 있었습니다.

예를 들어 "서버 운영"을 검색하면 정확히 "서버 운영"이라는 단어가 포함된 글만 나왔습니다. "Proxmox 홈서버 구축기"나 "Coolify로 배포 자동화하기" 같은 글은 서버 운영과 밀접한 관련이 있는데도 검색 결과에서 빠졌습니다. 사용자가 찾고 싶은 것은 '서버 운영'이라는 정확한 문자열이 아니라, 서버 운영이라는 개념과 관련된 글이었을 텐데 말입니다.

키워드 검색의 한계

전통적인 키워드 검색은 몇 가지 근본적인 한계가 있습니다.

1. 어휘 불일치 문제 (Vocabulary Mismatch)

사용자가 검색하는 단어와 문서에 있는 단어가 다르면 검색이 안 됩니다. "배포"를 검색했는데 글에는 "deploy"라고 써 있다면? "에러"를 검색했는데 "오류"라고 되어 있다면? 같은 의미인데도 검색에서 누락됩니다.

2. 의미적 연관성 무시

"React 성능 최적화"를 검색할 때, "useMemo 사용법"이나 "리렌더링 방지"에 대한 글도 관련이 있습니다. 하지만 키워드 검색은 이런 의미적 연관성을 전혀 이해하지 못합니다.

3. 동의어와 유의어

"인증"을 검색하면 "authentication", "로그인", "JWT" 같은 관련 글도 나와야 자연스럽습니다. 하지만 키워드 검색은 정확히 "인증"이라는 글자가 있는 문서만 찾습니다.

벡터 검색이라는 대안

벡터 검색(Vector Search)은 텍스트를 숫자 벡터로 변환해서 의미적 유사성을 계산합니다. "서버 운영"과 "Proxmox 홈서버"는 문자열로는 완전히 다르지만, 벡터 공간에서는 가까운 위치에 있습니다. 이 거리를 측정해서 검색 결과를 반환하는 것이 벡터 검색의 핵심입니다.

키워드 검색:
"서버 운영" → 정확히 "서버 운영" 포함된 문서만

벡터 검색:
"서버 운영" → [0.23, -0.15, 0.87, ...] (3072차원 벡터)
              ↓
         의미적으로 유사한 문서들
         - Proxmox 홈서버 구축 (유사도: 0.89)
         - Coolify 배포 자동화 (유사도: 0.82)
         - Docker 컨테이너 관리 (유사도: 0.78)

텍스트를 벡터로 변환하는 것을 임베딩(Embedding)이라고 하는데, Google의 Gemini embedding-001 모델이 이 작업을 해줍니다. 3072차원의 벡터로 텍스트의 의미를 압축하는 것입니다.

그런데 벡터 검색만으로는 부족하다

벡터 검색이 만능은 아닙니다. 몇 가지 약점이 있었습니다.

정확한 키워드 매칭이 약함

사용자가 "NestJS"를 검색했을 때, 정확히 NestJS에 대한 글이 최상위에 나와야 합니다. 하지만 벡터 검색은 "Express", "Node.js 백엔드" 같은 의미적으로 유사한 글도 비슷한 점수를 줄 수 있습니다. 사용자가 정확히 "NestJS"라고 입력했다면, NestJS가 제목이나 본문에 명시적으로 있는 글이 먼저 나와야 자연스럽습니다.

고유명사와 전문 용어

"Proxmox", "Coolify", "TipTap" 같은 고유명사는 벡터 검색이 제대로 처리하지 못할 수 있습니다. 이런 용어들은 키워드 매칭이 더 정확합니다.

하이브리드 검색: 둘의 장점을 결합

결국 선택한 방법은 하이브리드 검색(Hybrid Search)입니다. 벡터 검색과 키워드 검색을 동시에 수행하고, 두 결과를 결합하는 방식입니다.

하이브리드 검색 흐름:

사용자 쿼리: "서버 운영"
        │
        ├─→ 벡터 검색 (의미적 유사성)
        │   결과: A(0.89), B(0.82), C(0.78), D(0.65)
        │
        └─→ 키워드 검색 (텍스트 매칭)
            결과: E(5.2), A(3.1), F(2.8)
        │
        ▼
    RRF 알고리즘으로 결합
        │
        ▼
    최종 결과: A, E, B, C, F, D

두 검색 결과를 결합할 때는 RRF(Reciprocal Rank Fusion) 알고리즘을 사용합니다. 각 검색 결과에서의 순위를 기반으로 최종 점수를 계산하는 방식인데, 어느 한쪽에 치우치지 않으면서도 두 검색의 장점을 살릴 수 있습니다.

// RRF 점수 계산
// k는 상수 (보통 60), rank는 해당 검색 결과에서의 순위
const rrfScore = 1 / (k + rank);

// 최종 점수 = 벡터 RRF + 키워드 RRF (가중치 적용)
const finalScore = vectorWeight * vectorRRF + keywordWeight * keywordRRF;

기술 스택 선택

하이브리드 검색을 구현하기 위해 다음 기술 스택을 선택했습니다.

MongoDB Atlas Vector Search

이미 블로그 데이터가 MongoDB에 있었기 때문에, 별도의 벡터 데이터베이스를 도입하는 것보다 MongoDB Atlas의 Vector Search 기능을 사용하는 것이 자연스러웠습니다. Pinecone이나 Weaviate 같은 전용 벡터 DB도 고려했지만, 데이터 동기화 복잡성과 비용을 생각하면 MongoDB 하나로 통합하는 것이 개인 블로그 규모에서는 합리적인 선택이었습니다.

Google Gemini embedding-001

임베딩 모델은 Google의 Gemini embedding-001을 선택했습니다. 3072차원 벡터를 생성하며, 무료 티어에서도 분당 15회 요청이 가능합니다. 블로그 글 수십 개 정도의 규모에서는 비용이 전혀 들지 않습니다.

NestJS 백엔드

기존 백엔드가 NestJS로 구성되어 있어서 자연스럽게 여기에 검색 서비스를 추가했습니다. 임베딩 생성, 벡터 검색, 키워드 검색, RRF 결합까지 모두 백엔드에서 처리합니다.

전체 아키텍처

┌─────────────────────────────────────────────────────────────┐
│                    프론트엔드 (Next.js)                       │
│  ┌─────────────────────────────────────────────────────┐    │
│  │              검색 모달 (SearchModal)                  │    │
│  │  - 디바운스 700ms                                    │    │
│  │  - 키보드 네비게이션                                  │    │
│  │  - 429 에러 핸들링                                   │    │
│  └─────────────────────────────────────────────────────┘    │
└──────────────────────────┬──────────────────────────────────┘
                           │ GET /blog-posts/hybrid-search
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                    백엔드 (NestJS)                           │
│  ┌─────────────────────────────────────────────────────┐    │
│  │              EmbeddingService                        │    │
│  │  1. 쿼리 → Gemini 임베딩 변환                        │    │
│  │  2. MongoDB $vectorSearch (벡터 검색)                 │    │
│  │  3. 키워드 매칭 (제목/요약/태그)                      │    │
│  │  4. RRF 알고리즘으로 결과 결합                        │    │
│  │  5. 검색 로그 저장                                    │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                             │
│  Rate Limiting: 분당 5회                                     │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                    MongoDB Atlas                             │
│  ┌──────────────────┐  ┌──────────────────┐                 │
│  │   blog_posts     │  │   search_logs    │                 │
│  │  - content       │  │  - query         │                 │
│  │  - embedding     │  │  - searchType    │                 │
│  │   (3072차원)     │  │  - durationMs    │                 │
│  │  - text index    │  │  - resultCount   │                 │
│  │  - vector index  │  │                  │                 │
│  └──────────────────┘  └──────────────────┘                 │
└─────────────────────────────────────────────────────────────┘

다음 글에서는

이번 글에서는 하이브리드 검색을 선택한 이유와 전체 아키텍처를 살펴봤습니다. 다음 글에서는 실제 구현 과정을 다룹니다.

  • MongoDB Atlas Vector Search 인덱스 설정
  • $vectorSearch 연산자의 필터 제한사항 (null 값 처리 이슈)
  • NestJS에서 임베딩 서비스 구현
  • RRF 알고리즘 구현과 유사도 임계값 튜닝

벡터 검색을 처음 도입하면서 겪은 시행착오들, 특히 MongoDB Atlas의 $vectorSearch가 null 필터를 제대로 처리하지 못하는 문제를 해결한 과정도 공유하겠습니다.

하이브리드 검색벡터 검색MongoDB AtlasGemini Embeddings검색 엔진NestJS

관련 글

개인 블로그에 AI 검색 달기 (2) - MongoDB Atlas Vector Search 구현

MongoDB Atlas Vector Search 인덱스 설정부터 NestJS에서 하이브리드 검색을 구현하는 과정. $vectorSearch의 null 필터 제한사항과 RRF 알고리즘, 유사도 임계값 튜닝까지.

관련도 97%

블로그에 임베딩 기반 관련 글 추천 시스템 구축하기

Gemini Embedding API와 코사인 유사도를 활용해 블로그에 관련 글 추천 시스템을 구축한 경험을 공유합니다. 태그 기반의 단순한 방식에서 벗어나, 글의 실제 내용을 분석해 더 정확한 관련 글을 추천하는 방법을 소개합니다.

관련도 91%

내가 Next.js ISR을 선택한 이유: 블로그 SEO, 그 고민의 시작과 해결

Next.js ISR을 선택하여 블로그 SEO 문제를 해결하는 방법을 알아보세요. React CSR의 한계를 극복하고, 검색 엔진 최적화와 소셜 미리보기를 완벽 지원하는 ISR의 핵심 원리를 소개합니다.

관련도 87%