25

Neo4j 성능 튜닝: 100만 노드 KG를 빠르게 다루기

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%가 황금 비율