RAG 시스템을 위한 DB 구현 방안 및 쿼리 전략 논의
법령 문서를 다루는 하이브리드 RAG 시스템의 성능을 극대화하기 위해, 네 가지 데이터베이스 각각에 대한 구체적인 구현 방안과 쿼리 전략을 상세히 논의합니다.
사용 데이터베이스
| 용도 | 데이터베이스 | 역할 |
|---|---|---|
| RDB | PostgreSQL | 구조화 및 메타데이터 관리 |
| Vector DB | Milvus | 의미 검색 (Dense Retrieval) |
| 전문 검색 | Elasticsearch | 키워드 검색 (Sparse Retrieval) |
| Graph DB | Neo4j | 관계형 맥락 제공 |
1. PostgreSQL 구현 및 쿼리 전략: 구조화 및 메타데이터 관리
PostgreSQL은 데이터의 정합성(Integrity)과 정확한 메타데이터 관리를 담당합니다.
구현 방안 (Schema)
law_documents 테이블
| 컬럼 | 데이터 타입 | 설명 |
|---|---|---|
doc_id (PK) |
INT / UUID | 법령 문서 고유 ID |
law_name |
VARCHAR | 법령명 (예: 민법, 형법) |
enactment_date |
DATE | 시행일자 |
is_active |
BOOLEAN | 현재 유효 여부 |
law_chunks 테이블
| 컬럼 | 데이터 타입 | 설명 |
|---|---|---|
chunk_id (PK) |
UUID | 청크 고유 ID |
doc_id (FK) |
INT / UUID | 상위 법령 문서 ID |
chapter |
VARCHAR | 장(章) 제목/번호 |
article_num |
INT | 조(條) 번호 |
paragraph_num |
INT | 항(項) 번호 |
chunk_text |
TEXT | 분할된 텍스트 내용 |
쿼리 전략
메타데이터 필터링: Vector DB나 Elasticsearch의 1차 검색 후, 최신 법령 또는 특정 법령 범위로 결과를 제한할 때 사용합니다.
예시 쿼리: 현재 유효한(is_active = TRUE) 민법 조항에서 검색
SELECT *
FROM law_chunks lc
JOIN law_documents ld ON lc.doc_id = ld.doc_id
WHERE ld.law_name = '민법'
AND ld.is_active = TRUE
AND lc.chunk_id IN ('검색된_청크_ID_리스트');
2. Milvus 구현 및 쿼리 전략: 의미 검색
Milvus는 질문과 청크 텍스트 간의 의미적 유사성을 기반으로 문서를 검색합니다.
구현 방안 (Collection)
| 필드 | 데이터 타입 | 설명 |
|---|---|---|
chunk_id (PK) |
VARCHAR / String | PostgreSQL의 chunk_id와 동일 (Join Key) |
embedding |
FLOAT_VECTOR | LLM으로 생성된 텍스트 임베딩 벡터 |
article_num |
INT | 조(條) 번호 (필터링을 위한 경량 메타데이터) |
law_name |
VARCHAR | 법령명 (필터링을 위한 경량 메타데이터) |
쿼리 전략
Dense Retrieval (밀집 검색): 사용자의 질문을 임베딩 벡터로 변환하여 벡터 공간에서 가장 가까운 K개의 청크를 검색합니다.
예시 쿼리: 질문 벡터와 가장 유사한 K=10개의 청크 ID 검색
# Milvus Python SDK
from pymilvus import MilvusClient
# 질문을 벡터로 변환
query_vector = embedder.encode(user_question)
# 유사도 검색 수행
results = milvus_client.search(
collection_name="law_chunks_collection",
data=[query_vector],
anns_field="embedding",
param={"metric_type": "COSINE"},
limit=10,
output_fields=['chunk_id', 'law_name']
)
# 결과에서 chunk_id 추출
chunk_ids = [hit.id for hit in results[0]]
3. Elasticsearch 구현 및 쿼리 전략: 키워드 검색
Elasticsearch는 빠른 전문 검색 및 Sparse Retrieval (희소 검색)을 담당합니다.
구현 방안 (Index Mapping)
| 필드 | 설정 | 설명 |
|---|---|---|
chunk_id |
keyword | 검색 결과의 조인 키 |
chunk_text |
text (korean analyzer) | 한국어 분석기 적용된 텍스트 |
article_num |
integer | 조(條) 번호 |
law_name |
keyword | 법령명 |
Index 설정 예시:
{
"mappings": {
"properties": {
"chunk_id": { "type": "keyword" },
"chunk_text": {
"type": "text",
"analyzer": "korean"
},
"article_num": { "type": "integer" },
"law_name": { "type": "keyword" }
}
}
}
쿼리 전략
BM25 (Sparse Retrieval): 키워드 일치에 기반하여 관련 문서를 찾습니다.
예시 쿼리 (DSL):
{
"query": {
"bool": {
"must": [
{ "match": { "chunk_text": "특정_법적_용어 OR 관련_키워드" } }
],
"filter": [
{ "term": { "article_num": 35 } }
]
}
},
"_source": ["chunk_id", "chunk_text"]
}
하이브리드 검색 (Hybrid Search)
Milvus의 결과(Dense)와 Elasticsearch의 결과(Sparse)를 RRF(Reciprocal Rank Fusion) 등의 알고리즘으로 통합하여 최종 랭킹을 산출합니다.
def reciprocal_rank_fusion(dense_results, sparse_results, k=60):
"""RRF 알고리즘으로 두 검색 결과 통합"""
scores = {}
for rank, doc_id in enumerate(dense_results):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
for rank, doc_id in enumerate(sparse_results):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
return sorted(scores.items(), key=lambda x: x[1], reverse=True)
4. Neo4j 구현 및 쿼리 전략: 관계형 맥락 제공
Neo4j는 법령 간의 구조적 관계를 모델링하고, 검색된 청크에 대한 추가적인 맥락을 제공하여 Re-ranking을 지원합니다.
구현 방안 (Graph Model)
노드 (Nodes)
| 노드 타입 | 설명 | 예시 |
|---|---|---|
(:Law) |
법령 문서 전체 | ‘민법’, ‘형법’ |
(:Article) |
특정 조항 | ‘민법 제45조’ |
(:Concept) |
법률 용어/핵심 개념 | ‘손해배상’, ‘계약’ |
엣지 (Relationships)
| 관계 | 설명 |
|---|---|
(A)-[:REFERENCES]->(B) |
A 조항이 B 조항을 참조함 |
(LawA)-[:SUPERIOR_TO]->(LawB) |
LawA가 LawB의 상위 법령임 |
(Article)-[:CONTAINS_KEYWORD]->(Concept) |
조항이 특정 개념을 포함함 |
쿼리 전략 (Re-ranking 및 맥락 확장)
Neo4j는 1차 검색 결과를 보완하고 순위를 재조정하는 데 사용됩니다.
맥락 탐색 (Cypher 쿼리)
시나리오: 1차 검색된 조항과 직접적으로 참조 관계에 있는 다른 조항들을 K=5개 탐색
// 참조 관계 탐색
MATCH (a:Article {article_num: $searched_article_num})
-[:REFERENCES]->(ref_article:Article)
RETURN ref_article.article_num, ref_article.law_name
LIMIT 5
상위 법령 탐색
// 상위 법령 관계 탐색
MATCH (law:Law {name: $law_name})
<-[:SUPERIOR_TO]-(superior:Law)
RETURN superior.name, superior.doc_id
관련 개념으로 확장 검색
// 특정 개념을 포함하는 모든 조항 탐색
MATCH (c:Concept {name: '손해배상'})
<-[:CONTAINS_KEYWORD]-(article:Article)
RETURN article.article_num, article.law_name
LIMIT 10
Re-ranking 활용
1차 검색된 문서 리스트에 대해:
- Neo4j 쿼리를 실행하여 참조되는 횟수나 상위 법령과의 관계 점수를 부여
- 이 점수를 최종 랭킹에 반영
- LLM에 전달할 문서를 최종 확정
전체 아키텍처 흐름
┌─────────────────────────────────────────────────────────────────┐
│ 사용자 질문 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────┴───────────────────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Milvus │ │ Elasticsearch │
│ (Dense Search)│ │(Sparse Search)│
└───────────────┘ └───────────────┘
│ │
└───────────────────┬───────────────────────────┘
│
▼
┌───────────────────────┐
│ Hybrid Fusion (RRF) │
└───────────────────────┘
│
▼
┌───────────────────────┐
│ Neo4j │
│ (Re-ranking & │
│ Context Expansion) │
└───────────────────────┘
│
▼
┌───────────────────────┐
│ PostgreSQL │
│ (Metadata Filter & │
│ Original Text) │
└───────────────────────┘
│
▼
┌───────────────────────┐
│ LLM │
│ (Answer Generation) │
└───────────────────────┘
요약
| DB | 역할 | 주요 기능 |
|---|---|---|
| PostgreSQL | 메타데이터 관리 | 정합성 보장, 필터링, 원본 텍스트 저장 |
| Milvus | 의미 검색 | 벡터 유사도 기반 Dense Retrieval |
| Elasticsearch | 키워드 검색 | BM25 기반 Sparse Retrieval |
| Neo4j | 관계 탐색 | 법령 간 참조 관계, Re-ranking |
이러한 하이브리드 접근법을 통해, 법률 QnA 시스템은 키워드, 의미, 그리고 법률 구조라는 세 가지 차원에서 질문에 가장 정확한 답변 근거를 확보할 수 있습니다.