Vector RAG의 한계와 Graph RAG
Vector RAG가 multi-hop 관계 질의에서 왜 한계를 갖는지 분석하고, Graph RAG의 동작 원리와 구체적인 구현 예시를 다룹니다.
문서나 코드처럼 관계 구조가 중요한 데이터에서는 단순 Vector RAG만으로 연결 관계를 안정적으로 추적하기 어렵다. Vector RAG의 구조적 한계를 정리하고, 이를 보완하는 Graph RAG의 동작 원리와 구체적인 구현 예시를 다룬다.
Vector RAG의 한계
1. 관계/경로를 직접 보존·탐색하기 어려움
각 chunk는 독립적으로 임베딩되므로 retrieval 단계에서 chunk 간 관계나 경로를 명시적으로 보존·탐색하기 어렵다.
relation-heavy problem: 데이터 자체보다 데이터 간 관계가 더 중요한 문제
예시로 RBAC IAM 시스템을 보자. IAM에서 최종 권한 판단은 개별 엔티티 하나보다 User-Group-Role-Permission-Resource 간의 경로와 정책 조합에 의해 결정되는 경우가 많다.
[Chunk1] User는 Group에 속함
[Chunk2] Group은 Role을 가짐
[Chunk3] Role은 Permission을 가짐
Chunk1, 3만 검색되면 Chunk2가 누락되어 엉뚱한 해석이 된다. 이 구조의 특징은 다음과 같다.
- 핵심 정보가 개별 node보다 node 간 관계(edge) 에 더 많이 담긴다
- multi-hop이 기본 — 반드시 여러 단계를 거쳐 조회해야 한다
- 결과가 집합이 아닌 경로다 — 특정 리스트가 아니라 경로에 대한 설명이 필요하다
- Explainability가 중요하다
entity-relation problem: 질문이 엔티티와 그 사이의 관계를 중심으로 구성되는 경로 탐색형 문제
// User A가 Resource B에 접근 가능한가?
(User A) --[has permission?]--> (Resource B)
이 문제에서 Vector RAG는 세 가지 한계를 드러낸다.
- Entity disambiguation 실패: "Admin"이라는 키워드가 Role인지 Group인지 구분하지 못한다
- Relation loss: 임베딩 기반 retrieval에서는 관계가 텍스트 의미 안에 암묵적으로 흡수되어 명시적 관계 구조를 유지하기 어렵다
- Vector search는 관계형 DB의 JOIN처럼 명시적 관계 결합을 기본 retrieval 연산으로 제공하지 않는다
2. multi-hop 관계 질의를 retrieval 단계에서 다루기 어려움
Vector retrieval은 개별 chunk의 의미 유사도를 기준으로 동작하므로 multi-hop 관계를 경로 단위로 안정적으로 복원하기 어렵다. 각 hop이 분리되어 연결된 상태로 가져오지 못하고, 중간 노드가 누락되면 전체 reasoning이 깨진다.
Q. 유저가 해당 리소스에 접근 가능한 이유는?
A. User → Group → Role → Permission → Resource (4-hop)
3~4 hop 이상의 관계형 질의를 단순 Vector RAG만으로 일관되게 처리하기 어렵다.
3. Top-K retrieval의 구조적 한계
Top-K로 선택된 K개가 모두 유의미한 chunk라는 보장이 없다. 질문과 직접적인 연관성이 없는 chunk가 상위에 노출되면 핵심 정보가 누락되어 엉뚱한 답변을 낼 확률이 높아진다.
4. context fragmentation
chunk 단위로 쪼개지므로 LLM이 연결해서 해석해야 한다. 관계 복원이 retrieval 단계에서 충분히 이루어지지 않으면 LLM이 누락된 연결을 추론에 의존하게 되고, 그만큼 hallucination 위험이 커진다.
Vector RAG는 관련 정보 조각을 찾는 데는 강하지만, 최종 응답에 필요한 관계 구조나 경로를 retrieval 단계에서 직접 복원하는 데 한계가 있다.
Graph RAG의 구성
그래프 기반으로 정보를 탐색해서 LLM에 전달하는 RAG다.
Vector RAG vs Graph RAG
- Vector RAG: 문서/코드를 벡터로 변환해서 의미가 가까운 데이터를 찾아오는 방식. 포털에서 '사과'를 검색하면 '과일', '농장'이 같이 나오는 것과 유사하다
- Graph RAG: 데이터를 node와 edge로 연결한 Knowledge Graph를 만들어 명시적 관계를 따라 탐색하는 방식. 지하철 노선도처럼 미리 구조를 그려두고 경로를 찾는다
한계점: 처음 시스템을 만들 때 어떤 것을 node로, 어떤 것을 edge로 둘지 Ontology를 설계해야 하므로 구축 난이도가 높다.
따라서 의미 기반 검색이 필요한 영역에는 Vector retrieval을, 명시적 경로 탐색이 필요한 영역에는 Graph retrieval을 적용하는 hybrid 접근이 유의미한 대안이 된다.

*출처 - https://levelup.gitconnected.com/how-to-enhance-rag-with-knowledge-graphs-1c89ccfd3a6b
동작 흐름
내비게이션 비유
- Graph Storage (지도 보관소): Neo4j 같은 그래프 전용 DB
- Graph Traversal (길 찾기 알고리즘): NetworkX가 수행하는 역할
- LLM Generation (내비게이션 성우): 길 찾기 결과를 자연어로 설명
| 항목 | Vector RAG | Graph RAG |
|---|---|---|
| Retrieval 단위 | 문서(chunk) | Subgraph |
| 검색 방식 | Top-K 유사도 | Traversal (경로 탐색) |
| 결과 형태 | 문서 리스트 | 관계 구조 (path) |
Ontology / Entity / Edge 설계
Ontology
그래프에서 어떤 엔티티와 관계를 사용할지 정의한 스키마 — "어떤 질문을 어떤 구조로 풀 것인가"를 결정하는 모델이다.
- 질문 중심으로 설계한다
- 너무 단순하거나 복잡해도 안 된다
IAM Ontology 예시:
- [Entity] User, Group, Role, Permission, Resource
- [Relation] MEMBER_OF, ASSIGNED_ROLE, GRANTS, APPLIES_TO, DENIES
Entity
그래프의 node로 표현되는 실제 객체 — traversal의 anchor가 되는 단위다.
- granularity: Permission을 어떻게 쪼갤 것인가
- identity: Unique ID 필수
- metadata 포함 여부 고려
Edge
엔티티 간의 의미 있는 관계 표현 — traversal 가능한 방향성과 필요 시 조건(condition) 속성을 함께 설계할 수 있다.
GRANTS {
condition: "region=KR"
}
User ──MEMBER_OF──> Group
Group ──ASSIGNED_ROLE──> Role
Role ──GRANTS──> Permission
Permission ──APPLIES_TO──> Resource
Knowledge Graph 구축 파이프라인
Graph RAG에서 실제로 가장 어려운 부분은 탐색이 아니라 비정형 소스에서 그래프를 구축하는 과정이다.
[원본 소스 (코드 / 문서 / 텍스트)]
↓
[파싱 / 엔티티 추출]
- 코드베이스: tree-sitter로 클래스·함수·호출 관계를 AST 파싱
- 비정형 문서: LLM으로 엔티티·관계 추출 (e.g. Microsoft GraphRAG 방식)
↓
[관계 정규화 → JSON / 중간 포맷으로 저장]
↓
[Graph DB 적재]
- 실험 단계: NetworkX (인메모리)
- 실서비스: Neo4j 등 persistent graph DB
↓
[탐색 가능한 Knowledge Graph 완성]
소스 유형별 구축 방식 비교
| 소스 유형 | 추출 방법 | 특징 |
|---|---|---|
| 코드베이스 | tree-sitter (AST 파싱) | 관계가 명시적 — 정확도 높음 |
| 구조화 문서 (IAM 정책 등) | 규칙 기반 파서 | 스키마가 고정된 경우 적합 |
| 비정형 텍스트 | LLM 엔티티·관계 추출 | 유연하지만 추출 품질이 프롬프트에 의존 |
어떤 방식이든 Ontology를 먼저 정의한 상태에서 추출해야 node·edge가 일관되게 쌓인다. 추출 품질이 곧 탐색 품질을 결정하므로, 구축 단계가 전체 Graph RAG 성능의 병목이 되는 경우가 많다.
NetworkX로 IAM 권한 경로 분석하기
NetworkX는 그래프 구조를 메모리 위에서 만들고 node 간 연결 관계를 탐색하는 Python 라이브러리다. 최단 경로 탐색(shortest_path), 연결된 하위 노드 조회(descendants), 네트워크 중심성 분석 등 그래프 이론 알고리즘이 구현되어 있다.
초기 실험 단계에서 별도의 Graph DB 없이 traversal 기반 retrieval을 빠르게 검증할 수 있다. 단, 인메모리 구조이므로 대용량 데이터나 실서비스에서는 Neo4j 등 persistent graph DB로 교체가 필요하다.
목표: 어떤 사용자가, 어떤 경로를 통해, 어떤 리소스 권한을 가지게 되었는가를 분석하는 시스템
예시 시스템 전제
단순한 allow-only 시스템으로 가정한다.
- source user: 권한을 조회할 사용자
- target resource: 접근 여부를 알고 싶은 리소스
- relation: MEMBER_OF / GRANTS / APPLIES_TO 같은 관계 타입
도달 가능한 전체 노드 조회
import networkx as nx
# 방향 그래프: User -> Group -> Role -> Permission -> Resource
# 권한이 어떻게 전달되는가의 방향이 중요하다
G = nx.DiGraph()
# jjun은 game-ops-team 그룹에 속하고,
# game-ops-team은 game-data-editor 역할을 가지며,
# game-data-editor는 game-data.write 권한을 부여받고,
# 그 권한은 game-metrics-api 리소스에 적용된다.
G.add_edge("user:jjun", "group:game-ops-team", relation="MEMBER_OF")
G.add_edge("group:game-ops-team", "role:game-data-editor", relation="ASSIGNED_ROLE")
G.add_edge("role:game-data-editor", "permission:game-data.write", relation="GRANTS")
G.add_edge("permission:game-data.write", "resource:game-metrics-api", relation="APPLIES_TO")
# 추가 경로: jjun이 다른 그룹을 통해서도 권한을 가질 수 있다
G.add_edge("user:jjun", "group:live-ops-admin", relation="MEMBER_OF")
G.add_edge("group:live-ops-admin", "role:log-viewer", relation="ASSIGNED_ROLE")
G.add_edge("role:log-viewer", "permission:log.read", relation="GRANTS")
G.add_edge("permission:log.read", "resource:game-metrics-api", relation="APPLIES_TO")
target_user = "user:jjun"
try:
reachable_nodes = nx.descendants(G, target_user)
for node in reachable_nodes:
depth = nx.shortest_path_length(G, source=target_user, target=node)
if node.startswith("group:"):
node_type = "GROUP"
elif node.startswith("role:"):
node_type = "ROLE"
elif node.startswith("permission:"):
node_type = "PERMISSION"
elif node.startswith("resource:"):
node_type = "RESOURCE"
else:
node_type = "UNKNOWN"
print(f" - [{node_type}] {node} (경로 깊이: {depth})")
except nx.NetworkXNodeNotFound:
print("해당 사용자가 그래프에 존재하지 않습니다.")
nx.descendants() — 특정 사용자가 도달 가능한 권한/리소스 집합 조회

특정 리소스까지의 접근 경로 조회
import networkx as nx
G = nx.DiGraph()
G.add_edge("user:jjun", "group:game-ops-team", relation="MEMBER_OF")
G.add_edge("group:game-ops-team", "role:game-data-editor", relation="ASSIGNED_ROLE")
G.add_edge("role:game-data-editor", "permission:game-data.write", relation="GRANTS")
G.add_edge("permission:game-data.write", "resource:game-metrics-api", relation="APPLIES_TO")
source_user = "user:jjun"
target_resource = "resource:game-metrics-api"
try:
path = nx.shortest_path(G, source=source_user, target=target_resource)
for i in range(len(path) - 1):
from_node = path[i]
to_node = path[i + 1]
relation = G[from_node][to_node]["relation"]
print(f" {from_node} --[{relation}]--> {to_node}")
except nx.NetworkXNoPath:
print("접근 불가: 해당 사용자에서 리소스까지 도달 가능한 권한 경로가 없습니다.")
except nx.NetworkXNodeNotFound:
print("그래프에 존재하지 않는 노드입니다.")
nx.shortest_path() — 특정 리소스에 대한 접근 경로 복원
단, shortest_path()는 경로 하나만 반환한다. 사용자가 여러 그룹을 통해 동일 리소스에 접근 가능한 경우 all_simple_paths()로 전체 경로를 조회해야 한다.

Q. jjun이 game-metrics-api에 접근 가능한 이유는?
jjun은 여러 그룹에 속해 있고, 그 그룹을 통해 role을 받고, 그 role이 permission을 부여하며, 결국 game-metrics-api 리소스에 도달한다. Graph RAG는 이 경로를 명시적으로 설명할 수 있다.
실제 서비스에서는 정책 엔진이 최종 allow/deny를 판정하고, Graph는 그 경로를 설명/분석하는 역할을 담당한다.
정리
Vector RAG는 관련 정보를 찾는 데 강하지만, 관계 구조와 경로가 핵심인 문제에서는 한계가 명확하다. Graph RAG는 이를 보완하는 구조지만, 구축 비용이 높고 Ontology 설계가 선행되어야 한다.
둘은 경쟁 관계가 아니라 보완 관계다. 의미 기반 검색에는 Vector를, 경로 탐색과 관계 설명이 필요한 영역에는 Graph를 조합하는 hybrid 접근이 실용적인 대안이다.