Summary

Quartz 기반 기술 블로그를 PostgreSQL 18과 pgvector를 활용하여 벡터화하고 지능형 검색 시스템을 구축하는 프로젝트다. 이전 Qdrant 기반 구현을 개선하여 Dense(벡터) + Sparse(BM25) Hybrid Search를 SQL 네이티브로 구현하고, Parent-Child Document 구조로 문서 계층을 유지하며, 헤딩 기반 의미적 청킹으로 검색 품질을 극대화한다. PostgreSQL 단일 데이터베이스로 벡터, 메타데이터, 관계형 데이터를 통합 관리하여 복잡한 쿼리와 트랜잭션을 지원한다.

프로젝트 배경

벡터 스토어 선택 과정

개인적으로 경험해보고 싶었던 로컬에서 구축 가능한 벡터 스토어는 크게 세 가지가 있다:

  1. Qdrant: 고성능 벡터 검색에 특화, 현재 진행 중인 다른 두 프로젝트에서 이미 사용 중
  2. Milvus: 대규모 엔터프라이즈급 벡터 DB, 수백만~수십억 개의 벡터를 다루는 대규모 시스템에 적합
  3. pgvector: PostgreSQL 익스텐션, 관계형 DB의 장점과 벡터 검색을 결합

이번 블로그 프로젝트에서는 pgvector를 선택했다. Qdrant는 다른 프로젝트에서 이미 사용 중이어서 새로운 기술 스택을 경험하고 싶었고, Milvus는 개인 블로그 규모(수백 개 문서)에는 과도한 스펙이다. 마침 PostgreSQL을 이번 AppHub 프로젝트의 메인 데이터베이스로 사용하기로 했기 때문에, 벡터 검색까지 통합할 수 있는 pgvector가 가장 적합했다.

PostgreSQL 18 + pgvector의 장점

통합 데이터 관리:

  • 벡터, 메타데이터, 관계형 데이터를 단일 DB에서 관리
  • 별도 벡터 DB 불필요, 운영 복잡도 감소
  • ACID 트랜잭션으로 데이터 일관성 보장

진정한 Hybrid Search:

  • Dense Search (벡터 코사인 유사도)와 Sparse Search (PostgreSQL FTS)를 SQL 네이티브로 결합
  • 단일 쿼리로 의미적 검색과 키워드 검색 동시 수행
  • 복잡한 JOIN, 필터링, 집계를 벡터 검색과 함께 활용

PostgreSQL 18의 성능 개선:

  • 벡터 연산 성능 향상 (SIMD 최적화)
  • 병렬 쿼리 성능 개선
  • JSONB 인덱싱 및 쿼리 속도 향상

pgvector의 HNSW 인덱스:

  • Approximate Nearest Neighbor (ANN) 검색으로 O(log n) 복잡도 달성
  • 수십만 개 벡터도 밀리초 단위로 검색 가능

시스템 아키텍처

전체 구조도

┌─────────────────────────────────────────────────────────┐
│                    Quartz 블로그 (Markdown)               │
│          content/AI/, content/Study/, content/Tools/    │
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────┐
│              1. Markdown 파싱 & 전처리                    │
│  - Frontmatter 추출 (title, tags, date, description)    │
│  - 헤딩 기반 청킹 (LangChain MarkdownHeaderTextSplitter) │
│  - TOC 생성 (계층 구조 파악)                              │
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────┐
│              2. 임베딩 생성 (OpenAI API)                  │
│  - text-embedding-3-large (3072차원)                    │
│  - Parent: 전체 문서 요약 벡터                            │
│  - Child: 각 청크별 벡터                                  │
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────┐
│         3. PostgreSQL 18 + pgvector 적재                 │
│  - parent_documents: 문서 전체 메타데이터 + 요약 벡터     │
│  - child_documents: 청킹된 섹션 + 콘텐츠 벡터             │
│  - HNSW 인덱스: 빠른 ANN 검색                            │
│  - FTS 인덱스: BM25 텍스트 검색                          │
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────┐
│          4. Hybrid Search 쿼리 실행                      │
│  - Dense Search: 벡터 코사인 유사도                       │
│  - Sparse Search: PostgreSQL FTS (BM25)                 │
│  - Weighted Fusion: 0.7 * Dense + 0.3 * Sparse         │
└────────────────────┬────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────┐
│         5. LLM Reranker & 결과 반환                      │
│  - 관련성 평가 (1-10점)                                   │
│  - 불필요한 결과 제거 (5점 미만 필터링)                    │
│  - Parent 정보 결합 (전체 문서 맥락 제공)                  │
└─────────────────────────────────────────────────────────┘

Parent-Child Document 구조

Parent-Child 패턴의 핵심 가치

Parent Document: 문서 전체의 메타데이터와 요약을 담은 상위 문서로, 제목, 태그, 카테고리, AI 생성 요약, 목차 구조 등을 포함한다.

Child Document: 의미적으로 분할된 각 섹션으로, 실제 검색 대상이 되는 콘텐츠를 담고 있다. 각 Child는 Parent에 대한 참조를 유지하여 전체 문서 맥락을 제공한다.

장점:

  • 문맥 보존: 청크된 섹션에서도 전체 문서 정보 접근 가능
  • 계층적 탐색: 관련 섹션을 찾은 후 같은 문서의 다른 섹션 탐색
  • 정확한 검색: 섹션 레벨에서 정밀한 매칭, 문서 레벨에서 종합적 이해
  • 효율적 임베딩: 전체 문서를 한 번에 임베딩하지 않고 의미 단위로 분할

구조 예시:

Parent: content/AI/2025-09-14-한국자동차���학회-논문-특화-파서-시스템.md
├── title: "한국자동차공학회 논문 특화 파서 시스템 분석"
├── tags: [AI, PDF-Parser, RAG, ...]
├── summary: "한국자동차공학회 논문을 위한 전문 문서 파싱..."
├── toc: {level: 1, title: "프로젝트 개요", children: [...]}
└── children:
    ├── Child 1: "프로젝트 개요" 섹션 (H1)
    ├── Child 2: "시스템 구성 요소" 섹션 (H1)
    │   ├── Child 2-1: "PDF 파서 클라이언트" 서브섹션 (H2)
    │   └── Child 2-2: "DOI 추출 및 크롤링 도구" 서브섹션 (H2)
    ├── Child 3: "기술 아키텍처" 섹션 (H1)
    └── ...

데이터베이스 스키마 설계

1. Parent Documents 테이블

전체 문서의 메타데이터와 요약 정보를 저장하는 핵심 테이블.

컬럼 그룹컬럼명타입설명
기본idUUIDPrimary Key (자동 생성)
file_pathTEXT파일 경로 (UNIQUE)
file_nameTEXT파일명
메타데이터titleTEXT문서 제목
dateDATE작성일
modified_dateDATE수정일
tagsTEXT[]태그 배열
categoryTEXT카테고리 (AI, Study, Tools)
subcategoryTEXT서브카테고리 (Docker, SeSAC)
descriptionTEXT메타 설명
AI 생성summaryTEXTLLM 생성 요약
tocJSONB목차 구조 (계층적)
통계word_countINTEGER단어 수
reading_timeINTEGER예상 독서 시간 (분)
상태is_draftBOOLEAN초안 여부
enable_tocBOOLEANTOC 활성화
벡터summary_embeddingvector(3072)문서 요약 벡터
full_text_searchtsvectorFTS 벡터 (자동 생성)
타임스탬프created_atTIMESTAMP생성 시간
updated_atTIMESTAMP수정 시간

인덱스 구성:

  • 일반 인덱스: file_path, category, tags (GIN), date, full_text_search (GIN)
  • 벡터 인덱스: HNSW (m=16, ef_construction=64)
    • m: 그래프 연결성 (높을수록 정확하지만 메모리 증가)
    • ef_construction: 인덱스 구축 시 탐색 깊이 (높을수록 정확도 증가)

FTS 가중치:

  • 제목 (A) > 설명 (B) > 요약 (C) 순으로 검색 우선순위 부여

2. Child Documents 테이블

헤딩 기반으로 청킹된 각 섹션의 실제 콘텐츠를 저장.

컬럼 그룹컬럼명타입설명
기본idUUIDPrimary Key
parent_idUUIDParent 문서 참조 (CASCADE 삭제)
청킹chunk_indexINTEGER청크 순서 (0부터)
heading_levelINTEGERH1=1, H2=2, H3=3
heading_textTEXT헤딩 텍스트
section_pathTEXT[]계층 경로 [“H1”, “H2”, “H3”]
콘텐츠contentTEXT실제 텍스트 내용
content_typeTEXTtext, code, table, list
char_countINTEGER문자 수
멀티모달has_imagesBOOLEAN이미지 포함 여부
has_tablesBOOLEAN테이블 포함 여부
has_codeBOOLEAN코드 포함 여부
벡터content_embeddingvector(3072)콘텐츠 벡터 (실제 검색 대상)
content_ftstsvectorFTS 벡터 (자동 생성)
타임스탬프created_atTIMESTAMP생성 시간

인덱스 구성:

  • parent_id, chunk_index, heading_level, content_fts (GIN)
  • HNSW 벡터 인덱스 (m=16, ef_construction=64)

3. Keywords 테이블 (선택적)

문서 간 연관성 분석 및 키워드 기반 추천.

컬럼명타입설명
idSERIALPrimary Key
keywordTEXT키워드 (UNIQUE)
frequencyINTEGER전체 등장 빈도
keyword_embeddingvector(3072)키워드 벡터
created_atTIMESTAMP생성 시간

Document-Keyword 연결 테이블:

  • document_id (FK) + keyword_id (FK) + tf_idf_score

활용:

  • “관련 키워드 문서” 추천
  • 키워드 트렌드 분석
  • 의미적 연관 키워드 탐색

4. Search Logs 테이블

검색 쿼리와 사용자 피드백 저장하여 시스템 개선.

컬럼명타입설명
idSERIALPrimary Key
queryTEXT검색 쿼리
query_embeddingvector(3072)쿼리 벡터
search_typeTEXThybrid, dense, sparse
results_countINTEGER결과 개수
clicked_document_idUUID클릭한 문서 (FK)
user_feedbackTEXTpositive, negative, neutral
search_time_msINTEGER검색 소요 시간 (ms)
created_atTIMESTAMP검색 시간

활용:

  • 인기 검색어 분석
  • 검색 결과 CTR 분석
  • Reranker 학습 데이터
  • A/B 테스트 (Dense vs Sparse vs Hybrid)

Hybrid Search 구현 상세

Dense Search (벡터 검색)

특징:

  • 의미적 유사도: 단어 정확 일치 불필요, 의미가 비슷하면 검색됨
  • 다국어 지원: 임베딩 모델의 다국어 지원 활용
  • HNSW 인덱스: O(log n) 복잡도로 빠른 ANN 검색

핵심 연산:

-- 코사인 거리 연산자 (<=>)
1 - (c.content_embedding <=> $1::vector) AS cosine_similarity

한계: 특정 키워드나 전문 용어 검색 성능 저하 가능

특징:

  • 정확한 키워드 매칭: 특정 단어가 포함된 문서 검색
  • 빠른 성능: GIN 인덱스 활용
  • 스테밍 지원: “running”과 “run” 동일하게 취급

핵심 연산:

-- FTS 매칭 연산자 (@@)
WHERE c.content_fts @@ plainto_tsquery('simple', $1)
ORDER BY ts_rank_cd(c.content_fts, query) DESC

한계: 의미적 유사성 이해 불가, 동의어 검색 어려움

Hybrid Search (RRF 결합)

Reciprocal Rank Fusion (RRF) 공식:

RRF_score(doc) = Dense_rank + Sparse_rank
where rank = 1 / (k + position)

구현 요약:

단계설명핵심 쿼리
1. Dense 검색벡터 유사도 Top 20ORDER BY embedding <=> $1
2. Sparse 검색FTS 매칭 Top 20WHERE content_fts @@ query
3. RRF 계산순위 기반 점수 합산1/(60+ROW_NUMBER())
4. 결과 결합FULL OUTER JOINCOALESCE(d.id, s.id)
5. 정렬 반환RRF 점수 내림차순ORDER BY rrf_score DESC

가중치 튜닝 전략:

시나리오Dense 비중Sparse 비중적용 케이스
개념적 질문높음 (0.8)낮음 (0.2)“RAG란 무엇인가?”
특정 키워드중간 (0.5)중간 (0.5)“pgvector 설치 방법”
전문 용어낮음 (0.4)높음 (0.6)“HNSW 알고리즘”
일반 검색기본 (0.7)기본 (0.3)균형잡힌 설정

RRF vs Weighted Fusion

RRF는 점수 범위가 다른 검색 방식을 정규화 없이 결합 가능. Supabase 권장 방식으로 상수 60은 경험적 최적값 (조정 가능).

청킹 전략: 헤딩 기반 의미적 분할

LangChain MarkdownHeaderTextSplitter

분할 규칙:

헤딩 레벨마크다운역할예시
H1#주요 섹션”## 프로젝트 개요”
H2##서브섹션”### PDF 파서 클라이언트”
H3###세부 항목”#### 핵심 기능”

청킹 결과 예시:

Chunk 0:
  계층: H1 ["프로젝트 개요"]
  내용: 학술 연구를 위한 전문적인 문서 처리...

Chunk 1:
  계층: H1 > H2 ["시스템 구성 요소", "PDF 파서 클라이언트"]
  내용: 학술 논문 PDF를 구조화된 마크다운으로...

Chunk 2:
  계층: H1 > H2 ["시스템 구성 요소", "DOI 추출 및 크롤링"]
  내용: 웹 크롤링을 통한 보완 데이터 수집...

청킹 최적화 전략

고려 사항:

요소제약영향
LLM 컨텍스트너무 크면 노이즈, 너무 작으면 맥락 손실검색 품질
임베딩 한계OpenAI 8191 토큰 제한처리 가능 크기
검색 정확도작은 청크는 정밀, 큰 청크는 포괄적트레이드오프

권장 설정:

  • 헤딩 기반 분할 우선: 자연스러운 의미 단위 유지
  • ⚠️ 최대 길이 제한: 1500자 초과 시 문장 단위 추가 분할
  • ⚠️ 최소 길이 보장: 100자 미만 청크는 병합

헤딩 구조 정규화:

  • H1 없는 문서: 첫 H2를 H1으로 승격
  • H2 없이 H3만 있는 경우: H3를 H2로 승격
  • 일관된 계층 구조 유지

Multi-Query + Reranker 구현

Multi-Query 생성 프로세스

목적: 단일 질문을 5개 다양한 관점의 쿼리로 확장하여 검색 범위 극대화

생성 전략:

전략설명예시
동의어 활용같은 의미, 다른 표현”벡터 검색” → “의미적 검색”
상위 개념더 일반적인 표현”pgvector” → “벡터 데이터베이스”
하위 개념더 구체적인 표현”RAG” → “Retrieval Augmented Generation”
관련 개념연관된 주제”임베딩” → “HNSW 인덱스”
실용적 표현구현 중심 표현”이론” → “구현 방법”

예시 변환:

원본 쿼리: "pgvector를 사용한 벡터 검색 구현 방법"

생성된 5개 쿼리:
1. PostgreSQL pgvector 익스텐션 설치 및 설정
2. 벡터 데이터베이스 구축 실전 가이드
3. 의미적 검색을 위한 임베딩 저장소 구현
4. HNSW 인덱스를 활용한 빠른 ANN 검색
5. RAG 시스템의 벡터 스토어 아키텍처

병렬 검색 실행

프로세스:

  1. 임베딩 배치 생성: 5개 쿼리를 한 번에 OpenAI API 호출
  2. 병렬 검색: asyncio.gather()로 5개 Hybrid Search 동시 실행
  3. 결과 통합: 중복 제거 (문서 ID 기준)

시간 효율성:

  • ❌ 순차 실행: 5 × 200ms = 1000ms
  • ✅ 병렬 실행: max(5) ≈ 250ms
  • 🚀 75% 시간 단축

LLM Reranker: 노이즈 필터링

Reranker의 핵심 목적

Hybrid Search는 많은 후보를 반환하지만 관련성 낮은 결과 다수 포함. LLM Reranker로 불필요한 결과를 걸러내어 품질 > 양 전략 수행.

평가 기준:

점수판정설명처리
9-10최우수질문에 직접 답하는 핵심 정보✅ 채택
7-8우수관련성 높음, 답변에 도움✅ 채택
5-6보통어느 정도 관련, 부차적 정보⚠️ 임계값
3-4낮음키워드만 겹침, 실제 관련성 낮음❌ 제거
1-2무관질문과 무관❌ 제거

필터링 효과:

  • 입력: Hybrid Search 50개 결과
  • 제거: 5점 미만 약 30개 (60%)
  • 출력: 고품질 20개 (40%)
  • 결과: LLM 컨텍스트 40% 감소 → 속도/정확도 동시 향상

구현 과정

1. 환경 설정 (PostgreSQL 18 + pgvector)

# PostgreSQL 18 설치 및 pgvector 익스텐션 설치
brew install postgresql@18
brew services start postgresql@18
 
# pgvector 설치 (최신 버전)
git clone https://github.com/pgvector/pgvector.git
cd pgvector && make && make install
 
# DB 생성 및 익스텐션 활성화
psql postgres -c "CREATE DATABASE quartz_blog;"
psql quartz_blog -c "CREATE EXTENSION vector;"

PostgreSQL 18의 pgvector 최적화

PostgreSQL 18에서는 벡터 연산이 SIMD 최적화되어 이전 버전보다 약 2-3배 빠른 성능을 보입니다. pgvector 0.7.0 이상 버전을 사용하면 HNSW 인덱스 성능도 대폭 향상됩니다.

2. 데이터 파이프라인 (GitHub Actions 자동화)

Git push 시 변경된 마크다운 파일만 자동으로 재임베딩:

# .github/workflows/update-vectors.yml
name: Update Vector Database
 
on:
  push:
    paths:
      - 'content/**/*.md'
 
jobs:
  update-vectors:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2  # 이전 커밋과 비교
 
      - name: Get changed markdown files
        id: changed-files
        run: |
          echo "files=$(git diff --name-only HEAD^ HEAD | grep '\.md$' | tr '\n' ' ')" >> $GITHUB_OUTPUT
 
      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
 
      - name: Install dependencies
        run: |
          pip install asyncpg openai langchain python-frontmatter
 
      - name: Update embeddings for changed files
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          POSTGRES_URL: ${{ secrets.POSTGRES_URL }}
        run: |
          python scripts/update_embeddings.py --files "${{ steps.changed-files.outputs.files }}"

처리 과정:

  1. 변경 감지: git diff로 수정/추가된 .md 파일만 추출
  2. Frontmatter 파싱: title, tags, date, description 등 메타데이터 추출
  3. 헤딩 기반 청킹: MarkdownHeaderTextSplitter로 H1/H2/H3 구조 유지하며 분할
  4. 임베딩 생성: OpenAI API 배치 처리로 비용 최소화
  5. DB 업데이트:
    • 기존 문서: ON CONFLICT 절로 UPSERT 처리
    • 삭제된 문서: DELETE CASCADE로 child_documents도 자동 삭제

3. Hybrid Search 구현 (RRF 방식)

**Reciprocal Rank Fusion (RRF)**을 활용한 Dense + Sparse 결합:

-- Supabase 스타일의 Hybrid Search (PostgreSQL 18 최적화)
WITH dense_search AS (
    SELECT
        c.id,
        c.parent_id,
        c.content,
        c.heading_text,
        1 / (60 + ROW_NUMBER() OVER (ORDER BY c.content_embedding <=> $1)) AS dense_rank
    FROM child_documents c
    JOIN parent_documents p ON c.parent_id = p.id
    WHERE p.is_draft = false
    ORDER BY c.content_embedding <=> $1::vector
    LIMIT 20
),
sparse_search AS (
    SELECT
        c.id,
        c.parent_id,
        c.content,
        c.heading_text,
        1 / (60 + ROW_NUMBER() OVER (ORDER BY ts_rank_cd(c.content_fts, query) DESC)) AS sparse_rank
    FROM child_documents c
    JOIN parent_documents p ON c.parent_id = p.id,
         plainto_tsquery('simple', $2) query
    WHERE c.content_fts @@ query
      AND p.is_draft = false
    ORDER BY ts_rank_cd(c.content_fts, query) DESC
    LIMIT 20
)
SELECT
    COALESCE(d.id, s.id) AS id,
    COALESCE(d.parent_id, s.parent_id) AS parent_id,
    COALESCE(d.content, s.content) AS content,
    COALESCE(d.heading_text, s.heading_text) AS heading_text,
    COALESCE(d.dense_rank, 0.0) + COALESCE(s.sparse_rank, 0.0) AS rrf_score
FROM dense_search d
FULL OUTER JOIN sparse_search s ON d.id = s.id
ORDER BY rrf_score DESC
LIMIT 10;

RRF vs Weighted Fusion

Supabase 공식 문서에서 권장하는 RRF 방식은 점수 범위가 다른 Dense/Sparse 검색을 정규화 없이 결합할 수 있어 더 안정적입니다. 상수 60은 경험적으로 최적화된 값으로, 조정 가능합니다.

4. Multi-Query + Reranker

  1. Multi-Query 생성: LLM으로 원본 쿼리를 5개 다양한 관점으로 확장
  2. 병렬 검색: 5개 쿼리를 asyncio로 동시 실행
  3. 중복 제거: 동일 문서 ID 필터링
  4. LLM Reranking: GPT-4o-mini로 관련성 평가 (5점 미만 제거)

5. API 및 프론트엔드

FastAPI 검색 API 구축 후 Quartz 블로그에 검색 UI 컴포넌트 통합

고민 사항 및 해결 방안

1. 임베딩 모델 선택

모델차원장점단점권장 사용
text-embedding-3-large3072성능 우수, 다국어 강함비용 높음초기 구축
text-embedding-3-small1536비용 효율적, 빠른 속도정확도 약간 낮음대규모 운영
오픈소스 (bge-m3 등)가변무료, 커스터마이징 가능자체 호스팅 필요비용 민감

전략: large로 시작 → 비용 최적화 시 small로 전환 → 필요시 오픈소스 실험

2. 청킹 전략 세부 조정

문제: 일부 문서는 헤딩 구조가 불규칙 (H1 없음, H2만 다수 등)

해결 방안:

  • ✅ H1 없는 문서: 첫 H2를 H1으로 자동 승격
  • ✅ H3만 있는 경우: H3를 H2로 승격
  • ✅ 일관된 계층 구조 강제 유지

3. 다국어 지원

현재: 영어 FTS (to_tsvector('english', ...))

개선 옵션:

방법설명장점단점
simple 사전to_tsvector('simple', ...)한국어 호환, 빠름스테밍 없음
한국어 형태소 분석기mecab, komoran 통합정확한 토큰화설정 복잡
하이브리드언어별 분기 처리최적 성능구현 복잡도 증가

권장: simple 사전으로 시작 → 필요 시 형태소 분석기 추가

4. 실시간 업데이트

구현 완료: GitHub Actions로 자동 업데이트

  • 트리거: content/**/*.md 파일 변경 시
  • 변경 감지: git diff로 수정/추가/삭제 파일만 처리
  • 증분 업데이트: 전체 재처리 없이 변경분만 임베딩
  • 비용 최적화: 변경된 파일만 OpenAI API 호출

5. 비용 관리

OpenAI API 비용 예상:

항목단가예상 사용량비용
임베딩 생성$0.00013/1K tokens200개 글 × 2K tokens$0.052
검색 쿼리$0.000026/query1000 queries/월$0.026
월 합계$0.078

절감 방안:

  • 🔄 쿼리 임베딩 캐싱: Redis로 동일 쿼리 재사용
  • 📦 배치 처리: API 호출 최소화
  • 💾 인기 쿼리 사전 계산: 자주 검색되는 쿼리 미리 임베딩

확장 가능성

1. 멀티모달 검색

이미지와 코드 블록도 벡터화하여 검색 대상 확장.

추가 테이블 구조:

테이블주요 컬럼용도
document_imagesimage_path, description, image_embedding이미지 검색
code_snippetslanguage, code, code_embedding코드 검색

활용 시나리오:

  • 이미지 내용 기반 검색 (“차트가 포함된 문서”)
  • 특정 언어 코드 검색 (“Python asyncio 예제”)
  • 멀티모달 Cross-search (텍스트 → 이미지 결과 포함)

2. 지식 그래프 연동

문서 간 연결 관계를 그래프로 표현하여 탐색 강화.

구현 방안:

요소설명예시
document_links문서 간 링크 관계internal_link, related, cited_by
그래프 탐색”이 문서와 관련된 문서들”재귀 쿼리 (WITH RECURSIVE)
시각화Neo4j, Graphviz 연동지식 맵 생성

활용:

  • 관련 문서 추천 (같은 토픽)
  • 인용 관계 분석
  • 지식 네트워크 시각화

3. 개인화된 검색

사용자별 검색 히스토리 기반 결과 조정.

구현 요소:

기능데이터활용
선호 태그preferred_tags[]태그 기반 boosting
클릭 이력clicked_documents[]관심 문서 우선 순위
검색 히스토리search_history JSONB패턴 분석

개인화 전략:

  • 자주 보는 태그의 문서 상위 노출
  • 이전 클릭 문서와 유사한 결과 우선
  • 검색 패턴 학습으로 추천 개선

결론

PostgreSQL 18 + pgvector를 활용한 벡터 검색 시스템은 이전 Qdrant 기반 구현의 한계를 극복하고 다음과 같은 장점을 제공한다:

  1. 통합 데이터 관리: 벡터, 메타데이터, 관계형 데이터를 단일 DB에서 관리
  2. 진정한 Hybrid Search: SQL 네이티브로 Dense + Sparse 검색 결합
  3. 강력한 쿼리 능력: JOIN, 집계, 복잡한 필터링 등 SQL의 모든 기능 활용
  4. 트랜잭션 지원: ACID 보장으로 데이터 일관성 향상
  5. 비용 효율성: 별도 벡터 DB 불필요, 운영 복잡도 감소
  6. 확장성: 멀티모달, 지식 그래프, 개인화 등 다양한 확장 가능

다음 단계는 실제 구현을 진행하면서 성능 측정 및 최적화를 통해 사용자 경험을 극대화하는 것이다.

참고 자료

공식 문서 및 가이드

  • pgvector GitHub: PostgreSQL 벡터 익스텐션 공식 저장소

    • HNSW 인덱스 설정 및 최적화 방법
    • PostgreSQL 18 최적화 팁
    • 다양한 거리 함수 (코사인, L2, 내적) 비교
  • Supabase Hybrid Search Guide: Supabase의 RRF 기반 Hybrid Search 구현 가이드

    • Reciprocal Rank Fusion (RRF) 알고리즘 설명
    • PostgreSQL FTS와 pgvector 통합 방법
    • 실전 SQL 쿼리 예제 및 성능 최적화
  • Growth Coder - pgvector 활용 가이드: 한국어 pgvector 실전 가이드

    • Parent-Child Document 패턴 구현
    • 임베딩 모델 선택 및 비용 최적화
    • 한국어 FTS 설정 방법

관련 프로젝트 및 기술

핵심 기술 키워드

  • pgvector: PostgreSQL 벡터 익스텐션
  • HNSW (Hierarchical Navigable Small World): 효율적인 ANN 검색 인덱스
  • RRF (Reciprocal Rank Fusion): Hybrid Search 결과 결합 알고리즘
  • PostgreSQL FTS (Full Text Search): 내장 전문 검색 기능
  • Parent-Child Document: RAG 청킹 전략