25분
Neo4j 성능 튜닝: 100만 노드 KG를 빠르게 다루기
Neo4j & Cypher
Neo4j 성능 튜닝: 100만 노드 KG를 빠르게 다루기
온톨로지 & Knowledge Graph > Neo4j & Cypher
학습 목표
EXPLAIN/PROFILE로 쿼리 실행 계획을 분석한다 느린 쿼리를 인덱스/패턴 재작성으로 최적화한다 대용량 데이터 로딩 베스트 프랙티스를 안다
"우리 KG 쿼리가 30초 걸려요"
제조 현장에서 가장 흔한 KG 도입 실패 원인 1위: 성능. 100만 노드까지는 거의 모든 패턴이 빠르다. 그 이상부터 차이가 벌어진다.
EXPLAIN vs PROFILE
// EXPLAIN: 실행 계획만 보기 (실제 실행 X)
EXPLAIN
MATCH (e:Equipment {name: "CNC밀링 3호기"})-[:HAS_PART]->(p:Part)
RETURN p.name;
// PROFILE: 실제 실행 + 통계
PROFILE
MATCH (e:Equipment {name: "CNC밀링 3호기"})-[:HAS_PART]->(p:Part)
RETURN p.name;
// 결과 해석 포인트
// - db hits: 데이터베이스 접근 횟수 (낮을수록 좋음)
// - rows: 처리한 행 수
// - NodeByLabelScan: 라벨 전체 스캔 (느림) ← 인덱스 없을 때
// - NodeIndexSeek: 인덱스 사용 (빠름)
흔한 성능 문제 3가지
문제 1: 인덱스 미사용 (전체 스캔)
// 나쁨: name 인덱스 없으면 모든 Equipment 스캔
MATCH (e:Equipment {name: "CNC밀링 3호기"})...
// 해결: 인덱스 생성
CREATE INDEX eq_name FOR (e:Equipment) ON (e.name);
문제 2: Cartesian Product
// 나쁨: 두 MATCH 절이 연결 안 됨 → 카르테시안 곱
MATCH (e:Equipment), (w:Worker)
WHERE e.name = "3호기" AND w.name = "김기술"
RETURN e, w;
// → 모든 Equipment × 모든 Worker 조합 생성
// 좋음: 단일 MATCH 또는 명확한 연결
MATCH (e:Equipment {name:"3호기"})<-[:OPERATES]-(w:Worker {name:"김기술"})
RETURN e, w;
문제 3: 깊은 가변 길이 탐색
// 나쁨: 깊이 제한 없음 (수백만 노드 탐색 가능)
MATCH path = (e:Equipment)-[*]->(downstream)
RETURN path;
// 좋음: 깊이 제한 + 종료 조건
MATCH path = (e:Equipment {id:"cnc_003"})-[*1..4]->(downstream)
WHERE downstream:Process
RETURN path
LIMIT 100;
대용량 데이터 로딩 전략
// 1. 인덱스/제약 먼저 생성 (생성 후 로딩보다 빠름)
CREATE CONSTRAINT eq_id FOR (e:Equipment) REQUIRE e.id IS UNIQUE;
CREATE INDEX eq_name FOR (e:Equipment) ON (e.name);
// 2. LOAD CSV + 배치 처리 (USING PERIODIC COMMIT은 deprecated → CALL { } IN TRANSACTIONS)
:auto LOAD CSV WITH HEADERS FROM 'file:///equipment.csv' AS row
CALL {
WITH row
MERGE (e:Equipment {id: row.id})
SET e.name = row.name,
e.model = row.model,
e.installDate = date(row.installDate)
} IN TRANSACTIONS OF 10000 ROWS;
// 3. APOC로 대용량 관계 일괄 생성
CALL apoc.periodic.iterate(
"LOAD CSV WITH HEADERS FROM 'file:///parts.csv' AS row RETURN row",
"MATCH (e:Equipment {id: row.equipmentId})
MERGE (p:Part {partNumber: row.partNumber})
MERGE (e)-[:HAS_PART]->(p)
SET p.name = row.name",
{batchSize: 5000, parallel: true}
);
메모리 설정
# neo4j.conf (대용량 그래프용)
server.memory.heap.initial_size=4G
server.memory.heap.max_size=8G
server.memory.pagecache.size=16G # 데이터 크기의 80%를 권장
# RAM 32GB 머신 기준 권장 분배
# - OS: 4GB
# - JVM Heap: 8GB (쿼리 처리용)
# - Page Cache: 16GB (데이터 캐시용)
# - 여유: 4GB
쿼리 패턴 최적화 체크리스트
- 시작점 노드에 인덱스가 있는가?
- 가변 길이 경로(
*)에 깊이 제한이 있는가? - WHERE 절이 MATCH 패턴 내부에서 가능한가? (필터 푸시다운)
- 불필요한 RETURN 컬럼은 없는가?
- LIMIT가 결과 분량을 적절히 제한하는가?
-
EXISTS {}서브쿼리로 불필요한 조인을 제거했는가?
제조 KG의 현실적 규모
중소 공장 (50개 설비): 수만 노드, 수십만 관계 → 모든 쿼리 1초 이내
중견 공장 (500개 설비): 수십만 노드, 수백만 관계 → 인덱스 필수
대기업 (5,000+ 설비): 수백만 노드, 수천만 관계 → APOC/배치 처리 필수
우리 프로젝트는 첫 번째 범주. 성능 걱정보다 모델링 품질에 집중하자.
AI로 학습하기 — 꿀팁
🤖느린 Cypher 쿼리 최적화 체크리스트AI 학습 팁
AI에게 100만 노드 제조 KG에서 느린 Cypher를 진단하고 최적화하는 단계별 체크리스트를 생성하게 요청하세요.
Neo4j에서 100만 노드 규모의 제조 설비 KG를 운영할 때 쿼리 성능을 진단하고 최적화하는 체크리스트를 만들어줘. EXPLAIN/PROFILE 사용법, 인덱스 생성 대상 속성 선정 기준, Cartesian Product 방지, LIMIT 조기 적용 패턴을 포함한 10항목 이상의 마크다운 체크리스트로 출력해줘.
이 팁이 도움이 됐나요?
핵심 포인트
- • PROFILE로 실행 계획 확인, NodeIndexSeek가 보이도록 인덱스 설계
- • Cartesian Product는 명확한 연결로 회피
- • 대용량 로딩은 인덱스 먼저 + 배치 트랜잭션 + APOC 활용
- • Page Cache 메모리 ≥ 데이터 크기의 80%가 황금 비율