1. 핵심 개념: 메타데이터 필터링 (Metadata Filtering)

1.1 왜 필요한가? (Pure Vector Search의 한계)

벡터 검색(Vector Search)은 데이터의 '의미'를 찾아주는 강력한 기술이지만, 데이터 양이 많아질수록 치명적인 단점이 발생합니다.

  • 속도 저하: 수십만, 수백만 개의 벡터와 일일이 거리를 계산해야 하므로 연산 비용이 높습니다.
  • 정확도 이슈: 문맥은 비슷하지만 내가 원하지 않는 조건의 데이터(예: 품절된 상품, 옛날 뉴스 등)까지 검색될 수 있습니다.

이 문제를 해결하는 것이 바로 메타데이터 필터링(Metadata Filtering), 즉 **"조건(Postgres)으로 먼저 거르고, 의미(Vector)로 찾는 기술"**입니다. 이를 기술적으로는 **사전 필터링(Pre-filtering)**이라고도 부릅니다.

 

1.2 쉬운 예시: 옷 가게에서 옷 찾기

내가 **"파란색 여름 셔츠 중 내 스타일인 것"**을 찾는 상황을 가정해 봅시다.

  1. 1차 필터링 (PostgreSQL 담당 - 메타데이터):
    • 매장 전체(1,000벌)를 하나하나 대보지 않습니다.
    • 먼저 **'여름 코너'**의 **'셔츠 진열대'**에서 '파란색' 옷만 싹 골라냅니다.
    • 결과: 후보군이 1,000개에서 10개로 확 줄어듭니다. (연산 대상 최소화)
  2. 2차 벡터 검색 (Vector 담당 - 유사도):
    • 딱 그 10벌 중에서만 내가 입고 있는 옷과 스타일(벡터)이 가장 비슷한 옷을 찾습니다.
  3. 결론:
    • 무거운 벡터 연산 대상을 획기적으로 줄여주므로 속도가 빨라집니다.
    • 엉뚱한 계절이나 색상의 옷이 추천될 확률을 0%로 만들어 정확도가 높아집니다.

2. 도구의 선택: 왜 pgvector인가?

벡터 검색을 위해 별도의 DB(Pinecone, Milvus 등)를 도입하는 대신, 기존 RDB인 PostgreSQL에 확장 플러그인인 **pgvector**를 사용하는 것이 효율적입니다.

pgvector 방식 (All-in-One)

  • SQL 한 줄로 **필터링(WHERE)**과 **유사도 검색(ORDER BY)**을 동시에 처리할 수 있습니다.
  • 데이터 관리 포인트가 하나로 통합되어 유지보수가 쉽고, 트랜잭션 관리가 용이합니다.
SQL
 
-- pgvector를 사용한 하이브리드 검색 쿼리 예시
SELECT content, category
FROM mental_care_tips
WHERE category = '수면'              -- [1] 메타데이터 필터링 (Pre-filtering)
ORDER BY embedding <-> '[0.12, ...]' -- [2] 벡터 거리 계산 (필터링된 데이터 내에서만 수행)
LIMIT 3;

3. 실전 RAG 워크플로우: 검색 및 답변 (Retrieval & Generation)

사용자가 **"잠이 안 오는데 차 추천해줘"**라고 물었을 때, 메타데이터를 어떻게 확보하느냐에 따라 두 가지 방식으로 구현됩니다.

CASE A. 사용자가 직접 카테고리를 선택한 경우 (Explicit)

UI에서 '수면' 버튼을 누르고 질문을 입력한 상황. 가장 빠르고 정확합니다.

  1. 사용자 입력: 카테고리(수면) + 질문 텍스트("차 추천해줘")
  2. 임베딩 생성 (Server): 질문 텍스트만 임베딩 모델에 넣어 벡터로 변환합니다.
  3. DB 검색 요청:
    • 서버가 DB에 쿼리를 전송합니다: WHERE category='수면' AND Vector Similarity
  4. DB 내부 작동 (Pre-filtering):
    • 인덱스를 타서 '수면' 데이터만 먼저 남긴 후, 벡터 거리를 계산합니다.
  5. 답변 생성: 검색된 전문 지식을 바탕으로 LLM이 답변을 작성합니다.

CASE B. 사용자가 말로만 질문한 경우 (Implicit / LLM Extraction)

UI 선택 없이 채팅창에 바로 "수면에 좋은 차 추천해줘"라고 입력한 상황.

  1. 사용자 입력: "수면에 좋은 차 추천해줘"
  2. 메타데이터 추출 (LLM):
    • 질문을 먼저 가벼운 LLM에게 보냅니다.
    • 프롬프트: "이 질문의 의도를 파악해서 카테고리를 태깅해줘."
    • 결과: LLM이 질문을 분석하여 category='수면'이라는 조건을 뽑아냅니다. (Function Calling 활용)
  3. 임베딩 생성 (Server): 질문 텍스트를 벡터로 변환합니다.
  4. DB 검색 요청:
    • LLM이 뽑아준 category='수면' 조건과 벡터를 합쳐서 DB에 쿼리를 전송합니다.
  5. 답변 생성: 이후 과정은 CASE A와 동일하게 진행됩니다.

4. 마무리

결국 효율적인 RAG 시스템의 핵심은 **"얼마나 똑똑하게 검색 범위를 줄여주느냐"**에 달려 있습니다. PostgreSQL의 pgvector를 활용하면 기존의 강력한 SQL 필터링 기능과 최신 벡터 검색 기술을 하나의 DB에서 간편하게 결합할 수 있습니다.

+ Recent posts