Node.js, TypeScript, Supabase를 사용하여 768차원 벡터 검색 엔진 구축하기
요약
본 글은 Node.js, TypeScript, Supabase와 pgvector를 활용하여 효율적인 벡터 검색 엔진을 구축하는 방법을 안내합니다. 문서 청킹 시 문단 구조를 유지하고, 768차원 임베딩을 생성하여 PostgreSQL에 저장한 후, 코사인 유사도를 이용해 관련성 높은 컨텍스트를 추출하는 RAG 아키텍처가 핵심입니다.
핵심 포인트
- 문단 인식 청킹으로 컨텍스트 손실 없이 문서를 분할해야 합니다.
- Supabase의 pgvector는 벡터 저장 및 코사인 유사도 연산을 지원합니다.
- 쿼리 임베딩과 저장된 문서 벡터를 비교하여 관련성 높은 순위(Ranking)를 매깁니다.
- RAG 시스템에서 검색 레이어를 구축하는 실질적인 가이드입니다.
문단 인식 청킹(paragraph-aware chunking), 임베딩, 그리고 PostgreSQL pgvector가 AI 검색을 더욱 효율적으로 만드는 방법.
AI 애플리케이션을 구축할 때 대용량 문서와 함께 작동해야 하는 경우, 빠르게 한 가지 문제가 발생합니다. 바로 LLM에 필요한 정보를 모든 요청마다 전체 문서를 보내지 않고 어떻게 제공하느냐는 것입니다.
더 많은 텍스트를 전송하는 것이 항상 정답은 아닙니다. 이는 토큰 사용량을 증가시키고, 관련 없는 컨텍스트를 도입하며, 검색 관리를 더 어렵게 만들 수 있습니다.
저는 다른 접근 방식을 탐구했습니다. 바로 Node.js, TypeScript, 그리고 Supabase로 구축한 벡터 검색 엔진입니다.
아이디어는 간단합니다. 문서 내용을 임베딩으로 변환하고, 이 벡터들을 저장하며, 사용자의 쿼리와 가장 관련성이 높은 구절을 검색하는 것입니다.
아키텍처
검색 파이프라인은 다음 흐름을 따릅니다:
- 소스 문서를 수집(Ingest)합니다.
- 콘텐츠를 의미 있는 문단 기반 청크로 분할합니다.
- 제목과 컨텍스트 메타데이터를 보존합니다.
- 768차원 임베딩을 생성합니다.
- Supabase의 pgvector에 청크와 임베딩을 저장합니다.
- 들어오는 쿼리를 임베딩하고 코사인 유사도(cosine similarity)를 사용하여 관련 청크 순위를 매깁니다.
- 선택된 컨텍스트를 LLM에 전달합니다.
이 접근 방식은 검색 증강 생성(Retrieval-Augmented Generation, RAG) 시스템의 검색 레이어를 형성합니다.
1. 컨텍스트 손실 없이 문서 청킹하기
흔한 실수는 문서 구조를 고려하지 않고 단순히 고정된 문자 수로 문서를 분할하는 것입니다.
이는 하나의 문단을 그 제목과 분리하거나, 사고의 흐름 중간에 끊어버릴 수 있습니다.
대신 저는 문단 인식 청킹(paragraph-aware chunking)을 사용하고 가능한 모든 곳에서 컨텍스트 헤더를 보존합니다.
예를 들어, 하나의 청크는 다음과 같은 구조를 유지할 수 있습니다:
- 문서: Vector Search Architecture
- 섹션: Similarity Scoring
- 콘텐츠: 코사인 유사도가 쿼리와 문서 임베딩을 어떻게 순위화하는지 설명합니다.
이 메타데이터는 각 청크 자체를 더 의미 있게 만드는 데 도움이 됩니다.
프로덕션 시스템의 경우, 청크 크기와 오버랩은 임의로 선택하기보다는 문서 유형과 검색 품질에 맞춰 테스트해야 합니다.
2. Supabase를 사용하여 768차원 임베딩 저장하기
콘텐츠가 청크(chunk)로 분할되면, 임베딩 모델은 각 청크를 768개의 수치 차원을 포함하는 벡터로 변환합니다.
이러한 벡터는 임베딩 모델에 의해 학습된 의미적 특징을 나타냅니다. 정확한 단어가 다르더라도 유사한 의미를 가진 내용은 벡터 공간에서 가깝게 위치할 수 있습니다.
Supabase는 PostgreSQL 데이터베이스를 제공하며, pgvector 확장은 벡터 저장 및 유사성 연산을 지원합니다.
간소화된 테이블 구조는 다음과 같을 수 있습니다:
create extension if not exists vector;
create table document_chunks (
...
중요: 임베딩 모델은 실제로 768차원의 벡터를 생성해야 합니다. 데이터베이스 차원은 모델 출력과 일치해야 하며, 쿼리 및 문서 임베딩은 호환되는 임베딩 공간에서 나와야 합니다.
3. 코사인 유사도로 결과 순위 매기기 (Ranking)
사용자가 쿼리를 제출하면, 애플리케이션은 쿼리 임베딩을 생성하고 이를 저장된 문서 벡터와 비교합니다.
pgvector를 사용하면 <=> 연산자를 사용하여 코사인 거리를 계산할 수 있습니다:
select
id,
content,
...
여기서 $1은 애플리케이션에서 제공하는 쿼리 벡터를 나타냅니다.
이 쿼리는 코사인 거리에 따라 가장 가까운 상위 다섯 개 청크를 반환합니다. 유사성 표현식(similarity expression)은 결과가 정확하다는 것을 보장하는 교정된 확률(calibrated probability)이 아니라, 편리한 순위 점수입니다.
더 큰 데이터셋의 경우, 벡터 인덱스(vector indexes), 쿼리 계획(query plans), 필터링, 그리고 재현율 대 지연 시간 트레이드오프(recall-versus-latency trade-offs)가 중요해집니다.
4. 검색 속도 유지하기
벡터 검색 시스템은 애플리케이션의 지연 시간 예산 내에서 관련 컨텍스트를 반환할 수 있을 때만 유용합니다.
이 프로젝트의 성능 목표는 200ms 미만의 의미 검색 응답입니다.
이를 제대로 평가하기 위해, 저는 쿼리 지연 시간을 임베딩 생성, 네트워크 오버헤드, 그리고 후속 LLM 응답과 분리하여 측정할 것입니다. 또한 대표적인 쿼리에 걸쳐 p50 및 p95 지연 시간도 추적할 것입니다.
실제 결과는 데이터셋 크기, 인덱스 구성, 하드웨어, 쿼리 패턴 및 네트워크 조건에 따라 달라집니다.
빠른 검색은 관련성(relevance)을 유지해야 합니다. 잘못된 구절을 빠르게 반환하는 것은 원래 문제를 해결하지 못합니다.
5. 이 아키텍처가 중요한 이유
이 설계는 다음과 같은 애플리케이션에 유용합니다:
- 문서 및 코드베이스 검색
- 내부 지식 비서(knowledge assistants)
- 긴 문서 전반의 의미론적 검색(Semantic search)
- 검색 증강 생성 파이프라인(Retrieval-augmented generation pipelines, RAG)
- 다중 모드 애플리케이션(Multimodal applications) 중 선택된 모델이 호환되는 텍스트 및 비텍스트 임베딩을 지원할 때
한 가지 구분이 중요합니다. 768차원 벡터가 자동으로 다중 모드를 의미하지는 않습니다. 다중 모드 검색은 관련 모달리티를 지원하고 호환 가능한 벡터를 생성하는 임베딩 모델을 필요로 합니다.
최종 생각
벡터 검색 엔진을 구축하면서 저는 검색(retrieval)을 LLM 애플리케이션에서 사후 고려 사항이 아니라 그 자체의 엔지니어링 문제로 다루는 법을 배웠습니다.
좋은 청킹(chunking)은 의미를 보존합니다. 임베딩은 의미론적 비교를 가능하게 합니다. pgvector는 PostgreSQL 내부에서 검색을 실용적으로 만듭니다. 그리고 적절한 벤치마킹(benchmarking)은 아키텍처가 성능 목표를 충족하는지 알려줍니다.
다음 과제는 데이터셋이 성장함에 따라 검색 품질, 지연 시간(latency), 비용의 균형을 맞추는 것입니다.
만약 RAG 파이프라인을 구축하고 있다면, 어떤 것이 가장 효과적이었는지 궁금합니다: 작은 청크, 큰 청크, 하이브리드 검색, 또는 재순위 지정(reranking)인가요?
기술 스택: Node.js, TypeScript, Supabase, PostgreSQL, pgvector, 벡터 임베딩, RAG.
node #typescript #ai #database #rag
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기