14 min read
AI assisted

GraphDB 8종 벤치마크 (2/2) — 워크로드 매트릭스와 최종 선택 기준

8개 그래프 엔진 OLTP·메모리·분석·차별화 쿼리 측정과 워크로드별 의사결정 표

Series — GraphDB Benchmark, Eight Engines
  1. 1. GraphDB 8종 벤치마크 (1/2) — RCTE 290x 격차의 원인 분해
  2. 2. GraphDB 8종 벤치마크 (2/2) — 워크로드 매트릭스와 최종 선택 기준

1편에서는 RCTE가 Apache AGE보다 290배 빠른 이유를 분해했습니다. cypher() wrapper가 매 호출마다 13ms를 소비하는 구조, PostgreSQL 플래너가 Cypher AST를 매번 SQL로 재컴파일하는 비용 누적이었습니다. 그런데 1편이 남긴 질문이 있었습니다. "그러면 다른 그래프 DB들은 어떤가?" AGE가 유독 느린 것인지, 아니면 그래프 DB 전반이 RCTE에 못 미치는 것인지. 2편에서는 측정 범위를 8개 엔진으로 넓히고, OLTP 처리량 외에 메모리 제약·분석 워크로드·차별화 쿼리 세 축을 추가해 "어떤 상황에 어느 엔진을 써야 하는가"에 답합니다.


측정 도구를 바꾼 이유

Phase 1~5는 JMeter로 측정했습니다. JMeter는 Java 기반 부하 생성기로, JSR223 Groovy 스레드를 통해 각 DB별 Java 드라이버를 호출하는 방식이었습니다. Phase 5에서 MemGraph를 처음 측정했을 때 u=50에서 5,881 RPS가 나왔고, 이게 Neo4j(5,442 RPS)보다 겨우 8% 높은 수치였습니다. 당시에는 두 엔진이 비슷한 성능이라고 판단했습니다.

Phase 6에서 부하기를 Go native binary로 교체하면서 그 판단이 틀렸음을 알게 됐습니다.

Phase 5 (JMeter) → Phase 6 (Go) 변화
MemGraph: 5,881 → 22,462 RPS (+282%)
Neo4j:    5,442 → 14,541 RPS (+167%)
RCTE:    12,334 → 22,581 RPS (+83%)
AGE:         89 →     78 RPS (-12%)

JMeter가 Cypher 엔진에 수십 ms씩 오버헤드를 더하고 있었습니다. JVM Stop-the-World GC, JSR223 스크립트 매번 컴파일, Groovy 리플렉션 비용이 쌓인 결과입니다. AGE는 오히려 소폭 감소했는데, AGE의 병목이 서버 측 cypher() wrapper에 있어 클라이언트 오버헤드 제거가 도움이 안 됐기 때문입니다.

이 경험이 보여준 것은 측정 도구 자체가 결과를 왜곡할 수 있다는 점입니다. Go native binary는 고루틴 기반(~2KB 스택 vs JVM 스레드 ~512KB)이라 u=50 동시성에서 JVM의 스레드 관리 오버헤드가 없습니다. 이후 모든 Phase 6+ 결과는 Go harness 기준입니다.


8개 엔진 전체 OLTP 결과

동일 조건 — 50K docs / 108K chunks / 587K entities / 1.14M edges, GCP n2-standard-8 × 8 VM, u=50 × 180초 — 기준 결과입니다.

엔진 u=50 RPS 에러율 핵심 특성
RCTE (PG 17) 22,581 0% 평면 SQL + btree 인덱스
MemGraph 22,462 0% 100% in-RAM Cypher 서버
Neo4j 5.20 14,541 0% record store + doubly-linked edges
SQL/PGQ (PG 19-dev) 3,157 0% GRAPH_TABLE 변환 overhead 있음
AGE 1.6.0 78 6.9% cypher() wrapper 매 호출 비용
AgensGraph 2.16 catastrophic PG 16 fork, Q7에서 완전 멈춤
FalkorDB catastrophic 99.3% Redis single-thread, pool exhaustion
pgRouting 2.0 0% Q11 단독 측정, 매 호출 그래프 재구성

숫자 나열만으로는 의미가 불분명합니다. 각 결과가 나온 이유를 기술적으로 짚겠습니다.

RCTE와 MemGraph가 22.5K에서 동률인 이유

두 엔진이 22,500 RPS 근방에서 멈춥니다. 이게 서버 한계라면 p50 latency가 증가해야 합니다. 실측에서 두 엔진 모두 u=50에서도 p50 ≤ 1ms를 유지했습니다 — 서버가 포화된 게 아니라는 신호입니다. 부하기(n2-standard-4, 4 vCPU)의 CPU 한계일 가능성이 있습니다. 두 엔진의 쿼리 처리 경로가 각각 다른데도 같은 수치에서 수렴한다는 점이 이를 시사합니다.

RCTE는 왜 빠른가. 쿼리는 평면 정규화 테이블(rcte_mentions(chunk_id, entity_id))에 대한 JOIN입니다.

-- Q4 2-hop: Chunk → Entity → Chunk
SELECT DISTINCT m2.chunk_id
FROM rcte_mentions m1
JOIN rcte_mentions m2 ON m1.entity_id = m2.entity_id
WHERE m1.chunk_id = $1 AND m2.chunk_id <> $1
LIMIT 50;

PostgreSQL planner가 이 쿼리를 hash join 또는 index lookup으로 처리합니다. (entity_id) 인덱스가 있으면 m1에서 entity_id를 찾는 비용이 O(log n)이고, m2에서 같은 entity_id를 찾는 것도 마찬가지입니다. 그래프 추상화 레이어가 없어 PG buffer pool이 직접 인덱스 페이지를 캐시합니다.

MemGraph는 왜 빠른가. 전체 그래프가 메모리에 있습니다. 노드는 skiplist 기반 인메모리 구조이고, 엣지는 노드당 adjacency list로 관리됩니다. 1-hop 탐색은 포인터 따라가기이고, Cypher planner가 LIMIT push-down을 지원해 50개를 찾는 순간 탐색을 멈춥니다. 디스크 I/O가 0입니다.

AGE가 78 RPS인 이유

AGE의 cypher() 함수는 PL/pgSQL 래퍼입니다. 내부 동작을 단계별로 풀면:

1. cypher() 호출 → PL/pgSQL 런타임 진입 (~0.5ms)
2. Cypher 문자열 파싱 → AST 생성 (~2ms)
3. AST → SQL 변환 (planner 비용) (~3ms)
4. 생성된 SQL 실행 (실제 쿼리 시간) (~1-5ms)
5. agtype 변환 및 반환 (~3ms)

1~3번과 5번이 매 호출마다 반복되는 fixed overhead입니다. 실측에서 Q1 단순 lookup에서도 AGE p50이 11ms였고, Neo4j p50이 7ms였습니다. 실제 쿼리가 거의 같은데 4ms 차이가 나는 이유가 wrapper 비용입니다.

u=50에서 50개 동시 요청이 각각 이 overhead를 겪으면서 처리량이 78 RPS로 포화됩니다. 한 서버 스레드가 처리할 수 있는 건당 비용이 고정돼 있으므로 throughput = 1 / per_call_cost × worker_threads 공식에서 per_call_cost가 크면 throughput이 낮을 수밖에 없습니다.

FalkorDB가 99.3% 에러를 내는 이유

FalkorDB는 Redis 모듈로 동작합니다. Redis의 핵심 설계 원칙은 단일 스레드 이벤트 루프입니다. GRAPH.QUERY 명령은 Redis 메인 스레드에서 직렬로 실행됩니다.

u=1: 클라이언트 1개 → Redis main thread 100% 활용 → 35 RPS
u=10: 클라이언트 10개 → 직렬화, backpressure 시작 → 267 RPS
u=50: 클라이언트 50개 → connection pool exhaustion → 99.3% fail

u=1에서 u=10까지는 7.8배 증가했지만 선형이 아닙니다. 이미 포화 신호입니다. u=50에서는 커넥션 풀이 고갈되면서 대부분의 요청이 연결 자체를 못 잡고 즉시 실패합니다. 에러 p99가 7ms인 이유가 이것입니다 — 쿼리 실행이 아니라 connection refused가 7ms 안에 반환됩니다.

FalkorDB의 GraphBLAS 스파스 행렬 엔진은 배치 분석에서는 강점을 발휘합니다. 하지만 GraphRAG의 동시 retrieval 패턴에는 구조적으로 맞지 않습니다.


메모리 제약 실험 — 세 엔진, 세 가지 실패 방식

프로덕션 환경에서 메모리를 줄여야 하는 상황은 생각보다 흔합니다. Kubernetes pod resource limit, 비용 절감, 소규모 인스턴스로의 마이그레이션. RCTE, Neo4j, MemGraph를 컨테이너 메모리 32GB → 4GB → 1GB로 조이며 측정했습니다.

엔진 32GB RPS 4GB RPS 1GB RPS 1GB 상태
RCTE 22,470 22,652 22,370 (−0.4%) 정상
Neo4j 12,927 12,357 (−4.4%) JVM 시작 불가
MemGraph 22,656 22,773 93.5% err OOM-kill 9회

세 엔진이 완전히 다른 이유로 실패합니다.

RCTE가 1GB에서 -0.4%에 그치는 이유

PostgreSQL의 메모리 아키텍처는 두 레이어입니다 — shared_buffers(PG 관리 버퍼 풀)와 OS page cache. 1GB 컨테이너에서 shared_buffers를 128MB로 설정해도 OS page cache가 호스트 물리 메모리(31GB)에서 조달됩니다.

중요한 점은 cgroup v2 메모리 limit이 RSS(Resident Set Size)만 제약한다는 것입니다. page cache는 호스트 커널이 관리하는 익명 메모리가 아니라 파일 캐시이므로 컨테이너 limit에 직접 포함되지 않습니다. PG가 데이터 파일을 read()할 때 OS가 해당 페이지를 호스트 page cache에 올려두고, 다음 read()에서 메모리에서 바로 반환합니다. 1GB 컨테이너 안의 PG 프로세스가 실제로 쓰는 RSS는 154MB였습니다.

이 설계 덕분에 RCTE는 컨테이너 메모리 압박에 놀라울 만큼 강합니다.

Neo4j가 1GB에서 시작조차 못하는 이유

Neo4j 5.20 + GDS 2.6.9의 JVM heap 최소 요구량은 약 512MB입니다. 여기에 JVM 런타임(Netty networking, class loading, GC metadata)이 추가됩니다. 1GB 컨테이너에서 heap=512MB, page_cache=256MB로 설정하면 JVM이 초기화되는 과정에서 메모리가 부족해 기동 자체가 실패합니다.

흥미로운 것은 4GB에서의 결과입니다. heap=2GB, page_cache=1GB 설정에서 baseline 대비 -4.4% 저하에 그쳤습니다. Neo4j는 JVM heap 안에 별도의 object cache를 유지해 page cache가 1GB로 줄어도 heap 캐시가 보완합니다. 점진적 degradation은 잘 설계돼 있지만, JVM 자체의 hard floor가 존재한다는 점은 배포 계획에 반영해야 합니다.

MemGraph가 1GB에서 OOM-kill 루프에 빠지는 이유

MemGraph의 핵심 특성은 "100% in-RAM"입니다. 그래프 데이터는 디스크에 쓸 수 있지만 모든 쿼리 처리는 메모리에 올라온 데이터에 대해서만 동작합니다. 745K 노드와 1.14M 엣지가 메모리에 있을 때 MemGraph 컨테이너 RSS는 820MB였습니다.

1GB 컨테이너 한계의 96%를 차지합니다. 이 상태에서 쿼리 처리 중 임시 메모리 할당(join 결과 버퍼, 정렬 등)이 발생하면 한계를 초과합니다. 커널이 OOM-killer를 발동하고 MemGraph 프로세스가 죽습니다. 컨테이너가 자동 재시작되면 데이터를 다시 로드하고, 이 과정에서 로드가 계속 들어오면 다시 OOM-kill. 120초 측정 중 9회 재시작, 성공 응답 비율 6.5%였습니다.

RCTE, Neo4j와 결정적으로 다른 점은 graceful degradation이 없다는 것입니다. RCTE는 메모리가 줄어도 OS page cache가 완충합니다. Neo4j는 점진적으로 느려집니다. MemGraph는 데이터를 버릴 방법이 없어 결국 죽습니다. 이 실패 방식은 운영 위험으로 직결됩니다 — 충분한 메모리 헤드룸을 확보하지 않으면 예고 없는 서비스 중단이 발생합니다.


분석 워크로드 — FalkorDB의 역전

OLTP에서 catastrophic 실패를 기록한 FalkorDB가 분석 워크로드에서는 완전히 다른 면을 보였습니다. PageRank(maxIter=20), BFS(전수), Louvain을 단일 클라이언트 wall-time으로 측정했습니다.

알고리즘 Neo4j GDS 2.6.9 FalkorDB algo.* FalkorDB 우위
PageRank (20 iter) 4.98초 0.57초 8.7배
BFS (단일 시드, 전수) 2.32초 0.38초 6.1배
Louvain 16.73초 미지원

OLTP에서 Neo4j보다 수십 배 느렸던 FalkorDB가 분석에서는 8.7배 빠릅니다. 이유는 FalkorDB의 실행 엔진인 GraphBLAS에 있습니다.

GraphBLAS는 그래프 알고리즘을 희소 행렬(sparse matrix) 연산으로 표현합니다. PageRank는 인접 행렬 A와 랭크 벡터 r의 반복 곱셈입니다: r' = αAr + (1-α)/n. 1.14M 엣지 그래프의 인접 행렬은 587K × 587K 크기지만 실제로는 1.14M개의 비-영원소만 있는 극단적으로 희소한 행렬입니다. GraphBLAS의 희소 행렬-벡터 곱은 이 비-영원소만 처리하므로 전체 행렬 연산보다 훨씬 효율적입니다.

반면 Neo4j GDS는 JVM 힙에서 노드별로 순회하며 PageRank를 계산합니다. JVM의 GC 일시정지, 객체 접근 간접참조 비용이 발생합니다.

중요한 것은 이 우위가 멀티스레딩에서 오지 않는다는 점입니다. sysmon 0.1s 샘플링으로 측정한 FalkorDB의 CPU 사용량은 8코어 중 0.4코어였습니다. 745K 노드 규모에서 스파스 행렬 곱은 단일 코어에서 끝납니다. FalkorDB의 분석 우위는 알고리즘 효율이지 병렬화가 아닙니다.

RCTE와 PostgreSQL family로 PageRank를 돌리면 어떻게 될까

PL/pgSQL로 PageRank를 구현하는 것은 기술적으로 가능합니다. 하지만 실용적이지 않습니다. PostgreSQL 단일 backend는 "상태 갱신 + 반복 수렴" 패턴에 구조적으로 불리합니다. 각 iteration이 임시 테이블 생성 + hash aggregate이고, PostgreSQL은 단일 backend라 병렬화가 안 됩니다. 745K 노드 기준으로 Neo4j GDS 대비 1~2 자릿수 느릴 것으로 예상됩니다.

실용적인 대안은 오프라인 배치 패턴입니다: PG 평면 테이블 데이터를 NetworkX나 igraph로 export → Leiden/Louvain 계산 → community_id 컬럼만 PG에 재적재 → 런타임 쿼리는 단순 컬럼 lookup. LightRAG의 커뮤니티 탐지도 이 패턴으로 처리하면 됩니다.


차별화 쿼리 — 그래프 DB가 진짜 필요한 순간

Q1Q7이 "얼마나 빠르게 이웃 노드를 가져오는가"였다면, Q8Q15는 "이 쿼리를 RCTE로 처리할 수 있는가"를 테스트합니다. u=10 × 60초 기준입니다.

쿼리 의미 Neo4j (u=10) RCTE (u=10) 격차
Q8 variable-length [*1..4] 깊이 1~4 가변 경로 2,820 RPS 0.2 RPS 14,100배
Q11 shortestPath (depth ≤ 6) chunk 간 최단 경로 5,739 RPS 0.02 RPS 286,950배
Q13 multi-filter + EXISTS 존재 검사 포함 필터링 55.6 RPS 137.8 RPS RCTE 2.5배 우위
Q15 doc-scoped multi-join 문서 범위 복합 join 10,797 RPS 6,015 RPS Neo4j 1.8배

14,000배, 300,000배 격차는 단순한 성능 차이가 아닙니다. RCTE가 이 쿼리들을 처리하지 못하는 구조적 이유가 있습니다.

WITH RECURSIVE가 variable-length path에서 폭발하는 이유

SQL WITH RECURSIVE CTE는 BFS를 테이블 레벨에서 구현합니다. 기본 구조는 다음과 같습니다.

WITH RECURSIVE traversal AS (
    -- 시작 노드 (depth=0)
    SELECT chunk_id, entity_id, 0 AS depth
    FROM rcte_mentions WHERE chunk_id = $1
    
    UNION ALL
    
    -- depth+1 확장
    SELECT m.chunk_id, m.entity_id, t.depth + 1
    FROM rcte_mentions m
    JOIN traversal t ON m.entity_id = t.entity_id
    WHERE t.depth < 4  -- max depth
)
SELECT DISTINCT chunk_id FROM traversal LIMIT 50;

문제는 두 가지입니다.

첫 번째: visited set 자동 추적이 없습니다. 순환이 있는 그래프에서 이미 방문한 노드를 재방문할 수 있습니다. A → B → C → A → B → C ... 무한 루프. LIMIT 절로 어느 정도 제어되지만, LIMIT에 도달하기 전에 카디널리티가 폭발할 수 있습니다.

두 번째: LIMIT push-down이 없습니다. PostgreSQL planner는 WITH RECURSIVE를 "먼저 전부 계산, 그 다음 LIMIT"으로 처리합니다. 각 depth에서 중간 결과가 완전히 구체화(materialize)됩니다. 1.14M 엣지 그래프에서 depth=4까지 가면 frontier 크기가 기하급수적으로 늘어납니다.

Cypher는 근본적으로 다릅니다:

  • lazy iterator: LIMIT에 도달하는 순간 탐색을 멈춥니다
  • visited set: 내장돼 있어 사이클을 자동으로 건너뜁니다
  • LIMIT push-down: 실행 계획 수립 시 LIMIT이 전파됩니다

Q8(depth 1~4, LIMIT 50)에서 Neo4j는 50개를 찾는 즉시 멈추는 반면, RCTE는 depth 4까지 도달 가능한 모든 경로를 구체화하려 합니다. Q11(shortestPath)도 마찬가지입니다 — Neo4j의 shortestPath는 Dijkstra/BFS에 visited set이 내장된 구현이고, RCTE로 동일한 것을 구현하면 단일 쿼리가 50초를 넘습니다.

LightRAG 워크로드에서 이 약점이 활성화되지 않는 이유

중요한 맥락이 있습니다. Q8, Q11에서 RCTE가 수만~수십만 배 느린 것은 사실이지만, LightRAG와 GraphRAG retrieval은 이 패턴을 사용하지 않습니다.

LightRAG의 런타임 쿼리를 분류하면:

  • Q2 (chunk → entity), Q3 (entity → chunk): 1-hop, RCTE 압도적
  • Q4 (2-hop), Q6 (co-entity): 2-hop까지, RCTE 충분
  • Q5 (doc expand), Q15 (multi-join): RCTE 충분

variable-length path와 shortestPath가 retrieval flow에 없는 이유는 semantic relevance와 hop 거리가 무관하기 때문입니다. 청크 A와 청크 B 사이의 그래프 최단 경로가 짧다고 해서 의미적으로 관련이 있는 게 아닙니다. LightRAG는 벡터 유사도로 진입 엔티티를 찾고, 그 이후는 1~2 hop 이웃 수집입니다.

RCTE가 약한 Q8/Q11이 LightRAG 워크로드 밖에 있습니다. 이 사실이 "LightRAG 백엔드로는 RCTE가 최선"이라는 결론의 근거입니다.

Neo4j가 필요한 워크로드는 따로 있습니다:

  • Anti-fraud: 거래 그래프에서 N-hop 떨어진 의심 계정 찾기 (variable-length)
  • Supply chain: 특정 부품이 어느 완성품에 들어가는지 추적 (BFS traversal)
  • Recommendation: 구매 이력 그래프에서 잠재 경로 탐색 (latent path)

이런 패턴에서 RCTE는 실용적으로 사용 불가능합니다. 14,000배 격차는 성능 차이가 아니라 도구 적합성의 문제입니다.

AGE의 차별화 쿼리 결과 — 구조적 한계 확인

AGE는 차별화 쿼리 8개 중 6개에서 u=10에서 단일 쿼리 wall time이 20분을 초과했습니다. Q9(polymorphic 1~3 hop), Q10(cycle detection), Q12(bucket aggregation), Q13, Q14, Q15 전부 STUCK입니다.

AGE의 문제는 처리량이 낮은 수준에서 멈추지 않습니다. cypher() wrapper의 동시성 contention이 복잡한 패턴에서는 worker 전체를 블록합니다. u=10 환경에서 10개 워커가 각각 20분짜리 쿼리를 붙들고 있으면 어떤 다른 요청도 처리되지 않습니다.

AGE 290배 격차의 원인 분리: Phase 8에서 cypher() wrapper를 제거한 AgensGraph(PG 16 fork, native Cypher executor)를 추가 측정했습니다. Q1 단순 lookup에서 AGE 대비 100배 이상 빠른 4,795 RPS(u=1)를 기록했습니다 — wrapper 비용이 실제로 크다는 확인. 그런데 Q7 multi-seed UNWIND에서는 AGE와 동일하게 분 단위로 멈췄습니다. 결론: AGE 290배 격차는 cypher() wrapper 비용(단순 패턴)과 PG 플래너의 그래프 패턴 처리 한계(복합 패턴) 두 가지가 모두 원인입니다. wrapper를 제거해도 복합 패턴 한계는 그대로입니다.


pgRouting — 호출 모델이 다른 도구

pgRouting은 GIS/도로망 라우팅을 위한 PostgreSQL extension입니다. 2006년 OSM 경로 탐색 도구로 시작했고, pgr_dijkstra, pgr_aStar 등 50종 가까운 알고리즘을 SQL 함수로 노출합니다. 같은 1.14M edges 데이터에서 Q11 shortestPath를 측정했습니다.

엔진 Q11 RPS (u=10) p50 p99
Neo4j 5,739 0.17ms
pgRouting 2.0 5,035ms 6,234ms

단일 쿼리가 평균 5초입니다. Neo4j 대비 2,870배 느립니다. 쿼리는 단순합니다.

SELECT count(*) FROM pgr_dijkstra(
    'SELECT id, source, target, cost, reverse_cost FROM pgr_edges',
    $src::bigint, $dst::bigint,
    directed := false
)

왜 이렇게 느린가. pgRouting의 호출 모델을 이해해야 합니다.

pgr_dijkstra() 호출
  → 1. 'SELECT id, source, target, cost, reverse_cost FROM pgr_edges' 실행
        (1.14M 행 전체 fetch — 매 호출마다)
  → 2. fetched rows로 in-memory Boost Graph Library 그래프 재구성
        (매 호출마다)
  → 3. Dijkstra 알고리즘 실행
        (이 부분은 수 ms — 실제 알고리즘은 빠름)
  → 4. 결과 반환

1번과 2번이 매 호출마다 반복됩니다. 1.14M 행을 디스크에서 읽어 메모리에 그래프를 재구성하는 비용이 알고리즘 실행 비용의 수백 배입니다. 5초 wall time 중 약 4.8초가 fetch + build에 소요됩니다.

이 호출 모델은 도로망 라우팅에서는 문제가 없습니다. 도로망은 edge 수가 보통 수천~수십만 개이고, pgRouting이 실제로 사용되는 pgr_driving_distancepgr_TSP 같은 배달 최적화 시나리오는 단건 호출이 많습니다. 지식 그래프(1.14M edges)에 동시 다발적으로 호출하는 것은 pgRouting의 설계 범위 밖입니다.


최종 의사결정 표

측정 결과를 워크로드별로 정리합니다.

워크로드 1순위 2순위 피해야 할 선택
LightRAG / GraphRAG retrieval (1~2 hop) RCTE Neo4j (Cypher 친숙도) AGE, FalkorDB
메모리 ≤ 4GB 제약 환경 RCTE Neo4j (점진적 degradation) MemGraph (cliff-edge OOM)
variable-length path / shortestPath Neo4j RCTE (구조적 불가)
Batch analytics (PageRank / Louvain) Neo4j GDS FalkorDB algo.* (보조) RCTE / AGE
OLTP + Analytics 혼합 RCTE + 오프라인 배치 Neo4j 단일 엔진
Cypher 표현력 + 높은 처리량 MemGraph Neo4j
도로망 / GIS 라우팅 pgRouting RCTE

RCTE 권장 스택 (LightRAG 기준): PostgreSQL 17 + pgvector + RCTE 평면 테이블 + 오프라인 NetworkX/igraph 커뮤니티 탐지(community_id 컬럼 재적재). 단일 PostgreSQL 인스턴스로 GraphRAG 풀스택을 완성할 수 있습니다.

Neo4j가 정당한 선택인 경우: (1) 워크로드에 variable-length path나 shortestPath가 포함될 가능성이 있는 경우. (2) 팀이 Cypher로 쿼리를 작성하고 있어 SQL 전환 비용이 큰 경우. (3) LightRAG가 아닌 knowledge graph reasoning이나 fraud detection 시스템을 구축하는 경우.


이 측정의 경계

본 결론은 이웃노드탐색-heavy 지식그래프 검색 워크로드에 한정됩니다. Microsoft GraphRAG처럼 Leiden community detection이 retrieval flow 핵심인 시스템은 결론이 다를 수 있습니다. pgvector + HDBSCAN 오프라인 배치가 semantic cluster를 graph-topology cluster 대신 제공할 수 있지만, 그것은 별도 실험이 필요합니다.

사내 운영 매뉴얼 100개 문서(LightRAG 추출, 엔티티 1,493개)로 그래프 스키마와 쿼리 패턴을 별도 검증했습니다. 동일한 엔진 순위와 패턴이 확인됐습니다. 이 검증 과정은 lightrag-pg-rcte 포스트에서 다룹니다.