30

그래프 데이터 모델링 패턴: 좋은 KG vs 나쁜 KG

Neo4j & Cypher

학습 목표

관계 vs 속성 vs 노드: 무엇을 무엇으로 표현할지 판단한다 Reification(관계의 속성 표현) 패턴을 익힌다 시간 데이터, 버전 관리, 계층 관계 모델링 패턴을 안다

같은 사실, 다른 모델링

"3호기에서 2024-01-15 오전 9시에 E-201 알람이 발생했고, 김기술이 30분 만에 해결했다."

이 한 문장을 KG로 어떻게 모델링할 것인가? 답이 여러 개다. 그리고 선택이 쿼리 성능과 확장성을 좌우한다.


패턴 1: 관계의 속성으로 (Edge Properties)

// 좋은 점: 단순, 빠른 조회
// 나쁜 점: 관계 자체를 노드로 참조 불가
(eq:Equipment {name:"3호기"})-[:OCCURRED {
  timestamp: "2024-01-15T09:00:00",
  resolvedBy: "김기술",
  duration: 30
}]->(f:Fault {alarmCode:"E-201"})

언제 쓰나: 관계 자체에 다른 노드를 연결할 일이 없을 때.


패턴 2: Reification (사건을 노드로)

// 좋은 점: 사건에 다른 노드 연결 가능 (사진, 코멘트, 관련 사건)
// 나쁜 점: 조인이 늘어남, 쿼리가 길어짐
(eq:Equipment {name:"3호기"})-[:HAD_EVENT]->(ev:FaultEvent {
  id: "evt-2024-0115-001",
  timestamp: "2024-01-15T09:00:00",
  duration: 30
})
(ev)-[:OF_FAULT]->(f:Fault {alarmCode:"E-201"})
(ev)-[:RESOLVED_BY]->(w:Worker {name:"김기술"})
(ev)-[:HAS_ATTACHMENT]->(photo:File {url:"..."})

언제 쓰나: 사건에 시간/작업자/첨부 등 부가 정보가 많을 때. 제조 정비 이력은 거의 항상 이 패턴.


패턴 3: 시간 데이터 모델링

시간은 KG에서 가장 까다로운 주제. 3가지 접근:

// (A) 단순 타임스탬프: 노드/관계 속성
(eq)-[:HAD_STATUS {at:"2024-01-15T09:00"}]->(s:Status {name:"고장"})

// (B) 시간 트리(Time Tree): 시간 자체를 노드 계층으로
(:Year {value:2024})-[:HAS_MONTH]->(:Month {value:1})
  -[:HAS_DAY]->(:Day {value:15})-[:HAS_HOUR]->(:Hour {value:9})
(ev:FaultEvent)-[:OCCURRED_AT]->(:Hour {value:9})
// → "2024년 1월 발생 알람 전체" 쿼리가 매우 빠름

// (C) 시간 구간(Interval): 시작-종료 모델
(ev)-[:STARTED_AT]->(:Timestamp {iso:"2024-01-15T09:00"})
(ev)-[:ENDED_AT]->(:Timestamp {iso:"2024-01-15T09:30"})

권장: 제조 KG는 (A) 단순 속성 + (B) 시간 트리 보조 인덱스 조합.


패턴 4: 계층(Hierarchy) 모델링

공장 → 라인 → 설비 → 부품. 계층은 두 가지로 표현 가능.

// 방법 A: 명시적 관계 (권장)
(:Factory)-[:HAS_LINE]->(:Line)-[:HAS_EQUIPMENT]->(:Equipment)
  -[:HAS_COMPONENT]->(:Component)-[:HAS_PART]->(:Part)

// 방법 B: 단일 관계 + 깊이
(:Asset {level:1})-[:CONTAINS]->(:Asset {level:2})
  -[:CONTAINS]->(:Asset {level:3})

A vs B:

  • A는 의미가 명확, 쿼리가 직관적
  • B는 깊이 가변적인 경우 유리 (예: 회사 조직도)
  • 제조 자산 계층은 A 권장 (의미가 다르면 관계도 달라야)

흔한 안티 패턴

// 안티 패턴 1: 너무 많은 노드 라벨에 동일 속성
// (:CNCMill {hours:1000}), (:CNCLathe {hours:2000}), (:Drill {hours:500})
// → 공통 라벨 :Equipment로 통합 + type 속성으로 구분

// 안티 패턴 2: 문자열로 관계 표현
// (eq)-[:CONNECTED_TO {type:"causes"}]->(f)
// → 의미별로 별도 관계 타입 사용: -[:CAN_CAUSE]->

// 안티 패턴 3: 거대 슈퍼노드 (수십만 엣지)
// (:Company)-[:HAS_EMPLOYEE]->(수십만 명)
// → 중간 노드(부서, 라인)로 분할

인덱스와 제약 조건

// 고유 식별자 제약
CREATE CONSTRAINT eq_id IF NOT EXISTS
  FOR (e:Equipment) REQUIRE e.id IS UNIQUE;

// 검색 성능용 인덱스
CREATE INDEX eq_name_idx IF NOT EXISTS
  FOR (e:Equipment) ON (e.name);

// 복합 인덱스
CREATE INDEX part_lifespan_idx IF NOT EXISTS
  FOR (p:Part) ON (p.currentHours, p.lifespan);

// 전문 검색용 인덱스 (이름/설명 like 검색)
CREATE FULLTEXT INDEX fault_search IF NOT EXISTS
  FOR (f:Fault) ON EACH [f.name, f.description];

규칙: WHERE 절이나 MATCH 패턴에 자주 쓰는 속성은 무조건 인덱스.


우리 프로젝트 권장 패턴

계층:    명시적 관계 (HAS_LINE, HAS_EQUIPMENT, HAS_PART)
사건:    Reification (FaultEvent, MaintenanceEvent 노드)
시간:    속성 + 별도 Day/Month 노드 보조
상태:    별도 Status 노드 (운전중/고장/정비중)
인덱스: id UNIQUE, name/alarmCode 인덱스 필수
AI로 학습하기 — 꿀팁
Reification 패턴 적용 정확성 검증AI 학습 팁

관계의 속성을 표현하는 Reification 패턴이 제조 데이터에서 올바르게 설명되었는지 검증하세요.

Neo4j에서 '설비 A가 2024-03-15에 부품 B를 교체했다'는 정보를 모델링할 때 Reification 패턴을 적용해야 한다는 설명이 맞는지 검증해줘. 관계에 직접 속성 추가 방식(REPLACED_WITH {date: ...})과 중간 노드(Maintenance) 삽입 방식의 차이점, Neo4j에서 권장 방식은 무엇인지 설명해줘.
이 팁이 도움이 됐나요?
핵심 포인트
  • 관계의 속성 vs Reification: 부가 정보 많으면 사건을 노드로
  • 시간 트리(Time Tree) 패턴은 시계열 쿼리 성능을 극대화
  • 안티 패턴 회피: 슈퍼노드, 문자열 관계 타입, 라벨 남발
  • 인덱스/제약은 처음부터 설계 — 나중에 추가하면 데이터 마이그레이션 비용 발생