
최근 LLM과 임베딩 모델의 눈부신 발전으로 인해, 임베딩 기반의 벡터 데이터베이스를 활용한 RAG의 유용성은 줄어들기는커녕 오히려 그 한계를 넓혀가며 더욱 중요해지고 있습니다.
특히 최신 임베딩 모델들은 최대 32k에 달하는 방대한 컨텍스트 창을 지원하며, 완전한 LLM 디코더 기반의 임베딩을 수행하여 놀라운 수준의 의미론적 유사도를 추출해냅니다.
예를 들어 Qwen3 8b를 비롯한 고성능 임베딩 모델들은 복잡한 청킹 없이 거대한 문서를 통째로 임베딩해도 높은 품질의 유사도를 보여줍니다.
문서 내에 다양한 의미가 복합적으로 섞여 있어도 원하는 내용을 찰떡같이 찾아내는 수준에 이르렀습니다.
과거에는 데이터 정제나 청킹의 품질이 RAG 성능의 전부라고 해도 과언이 아니었지만, 이제는 그 의존도가 많이 낮아졌습니다.
그야말로 벡터 DB에 코퍼스를 통째로 집어넣고 코사인 유사도만 돌려도 훌륭한 결과가 나오는 시대입니다.
그렇다 보니 이제는 "어떤 벡터 DB를 선택할 것인가?"가 더 큰 고민거리로 다가옵니다.
실무적인 관점에서 보면, 아무리 모델이 발전했어도 코사인 유사도 단독으로는 정확성에 한계가 존재합니다.
따라서 전통적인 키워드 기반의 BM25 검색과 앙상블하는 것을 강력히 추천합니다.
여기에 더해 결과 셋에 대한 리랭커나 LLM 기반의 재정렬 파이프라인도 필수적입니다.
이러한 아키텍처를 구현하려면 벡터 DB가 최소한 다음 두 가지를 지원해야 합니다.
이 기준을 바탕으로 기존 DB들을 살펴보면 아쉬운 점들이 보입니다.
그래서 이 모든 조건을 완벽하게 충족하며 RDB의 장점까지 살릴 수 있는 PostgreSQL + BM25 조합을 최적의 솔루션으로 제안합니다.
PostgreSQL은 내장 FTS를 지원하지만, 검색 순위를 매길 때 ts_rank와 ts_rank_cd 함수만 제공합니다. 이 함수들은 문서 길이 보정이나 근접 거리 알고리즘은 제공하지만, 역문서 빈도 IDF, Inverse Document Frequency 를 제대로 지원하지 않아 흔한 토큰이나 반복 단어에 의해 결과가 쉽게 왜곡되는 치명적인 단점이 있습니다.
토큰의 등장 횟수만으로 점수를 내면 의미론적 검색 품질이 크게 떨어집니다.
따라서 첨부된 이미지의 공식처럼 제대로 된 BM25 알고리즘을 PG 내에서 직접 구현해야 합니다. 이미지의 수식 과 테이블 구조에서 알 수 있듯, 각 토큰에 대한 TF와 IDF를 구하여 연산하는 과정이 필요합니다.
구현 스텝은 다음과 같습니다:
토큰화: 먼저 text 필드의 값을 to_tsvector를 통해 tsvector 타입으로 변환하여 저장합니다. 여기에는 형태소 분석기를 거친 토큰과 해당 토큰의 등장 횟수가 담깁니다.
문서 길이 관리: BM25 공식의 문서 길이 보정을 위해 각 레코드의 고유 토큰 수를 tokenlength 필드에 기록합니다.
평균 길이 관리 (): 전체 테이블에 대한 평균 문서 길이를 알아야 합니다. 별도의 bm25Avg 테이블을 만들어 전체 레코드 수(rs_count), 길이 합(len_sum), 평균 길이(len_avg)를 관리합니다.
레코드 추가/삭제 시 부분 업데이트만 해주면 연산 부하를 줄일 수 있습니다.
bm25TF 테이블: id, token, tf를 기록하여 개별 문서 내 토큰 등장 횟수를 저장합니다.bm25IDF 테이블: token, doc_tf를 기록하여 특정 토큰이 전체 문서 중 몇 개의 문서에 등장했는지(역색인)를 관리합니다.vectortest_bm25 테이블처럼 id, token, tf, idf, bm25 double precision 컬럼을 구성합니다.이렇게 구한 BM25 점수와 코사인 유사도를 적절한 가중치로 합산하면 매우 뛰어난 품질의 검색 결과를 얻을 수 있습니다.
PG의 vector 익스텐션은 HNSW, IVF 인덱스를 모두 지원하며, 차원 수와 거리 측정 방식 L2, Cosine 등 을 자유롭게 설정할 수 있습니다.
특히 최신 임베딩 모델들은 대부분 Matryoshka Representation Learning을 지원합니다.
MRL의 핵심은 고차원 벡터의 앞부분만 잘라서 써도 의미가 꽤 보존된다는 점입니다. 이를 활용해 성능과 정확성의 균형을 맞출 수 있습니다.
효율적인 인덱싱 전략:
1. 전체 차원 예를들어 4096차원 의 원본 벡터는 별도 필드에 보존합니다.
2. 앞쪽 128차원만 잘라내어 HNSW 인덱스 전용 필드를 만듭니다.
3. 검색 시, 먼저 128차원 인덱스를 태워 고속으로 상위 200~300개의 후보군을 추려냅니다.
4. 추려진 후보군에 대해서만 원본 차원과 BM25 점수를 활용해 정교하게 재정렬을 수행합니다.
이 방식은 코사인 유사도 연산이 이미 정규화된 값들을 다루기 때문에 GPU 없이 CPU만으로도 엄청난 속도를 자랑합니다.
직접적인 성능 검증을 위해 나무위키 덤프 데이터셋 14만 5천 건을 청킹 없이 그대로 PG 테이블로 밀어 넣고 테스트를 진행했습니다.
각 문서의 길이는 토큰 기준 200개부터 최대 20k까지 다양했습니다.
결과 요약:
초기에는 저 역시 LangChain 등에서 튜토리얼로 자주 쓰이는 ChromaDB를 사용했습니다.
하지만 데이터가 늘어날수록, 내부 SQLite 기반의 메타데이터 필터링과 벡터 검색이 따로 놀면서 발생하는 극심한 병목 현상 때문에 실무에 적용하기 어렵다는 결론을 내렸습니다.
대안으로 ElasticSearch도 고려했으나, 복잡한 클러스터 운영과 높은 비용 정책이 발목을 잡았습니다.
그 후 PGVector를 도입하면서 현재 진행하는 거의 모든 프로젝트의 RAG 파이프라인을 PostgreSQL로 통합하여 처리하고 있습니다.
서비스 레이어 메모리로 엄청난 양의 레코드를 끌고 와서 Pre-filter나 Post-filter를 적용하는 것은 네트워크 비용과 속도 면에서 한계가 명확합니다.
수십, 수백만 건의 기업용 데이터를 다루는 프로덕션 레벨의 RAG 시스템을 구축해야 한다면, 메타데이터 검색, BM25, 강력한 벡터 인덱싱이 하나의 DB 엔진 안에서 매끄럽게 통합되는 PostgreSQL + PGVector 조합이 현시점에서 가장 매력적이고 현실적인 선택지일 것입니다.