LabHub

그래프 데이터베이스 — Neo4j 와 Cypher · 그래프 모델과 적재 · 이론

LOAD CSV 와 MERGE — 적재가 모델을 굳힌다

LabHub 에서 이어서 보기

한 줄 요약

CSV 를 그래프로 올리는 일은 세 가지를 정하는 일이다 — 무엇이 같은 노드인가(제약), 값의 형은 무엇인가(변환), 두 번 돌려도 같은가(MERGE). 셋 중 하나를 빼면 개수는 맞는데 질의가 안 맞는 그래프가 된다.

왜 이게 필요했나

LOAD CSV WITH HEADERS FROM 'file:///people.csv' AS row 는 한 행을 row 라는 맵으로 준다. 여기서 첫 번째 함정이 나온다. CSV 의 값은 전부 문자열이다. row.id"42" 이지 42 가 아니다. 그대로 넣으면 노드는 잘 만들어지고 개수도 맞는데, 나중에 MATCH (p:Person {id: 42}) 가 아무것도 못 찾는다. toInteger(row.id), toFloat, date(row.opened), datetime(row.ts) — 형은 적재하는 사람이 정한다.

두 번째 함정은 관계 적재에 있다. 관계는 양끝 노드를 먼저 MATCH 로 찾고 잇는다. 양끝 중 하나가 없으면? 오류가 나지 않는다. MATCH 가 빈 결과를 내고 그 행은 조용히 건너뛴다. 6,000행을 적재했는데 관계가 5,988개라면 12행이 어딘가로 사라진 것이고, 세어 보지 않으면 영영 모른다.

어떻게 동작하나

순서가 있다. 제약 → 노드 → 관계.

CREATE CONSTRAINT person_id IF NOT EXISTS FOR (p:Person) REQUIRE p.id IS UNIQUE;

유일성 제약은 두 가지 일을 한다. 같은 id 의 노드가 둘 생기는 것을 막고, 그 속성에 인덱스를 만든다. 두 번째가 적재 속도를 정한다. MERGE (p:Person {id: 42}) 는 "있으면 찾고 없으면 만든다" 인데, 찾는 일이 인덱스 없이 이루어지면 행마다 라벨 전체를 훑는다. 2,000명이면 견디지만 200만 명이면 적재가 끝나지 않는다.

MERGECREATE 의 차이는 재실행에서 드러난다.

CREATE (:Person {id: 1})   두 번 돌리면 → 제약이 있으면 오류, 없으면 노드가 둘MERGE  (:Person {id: 1})   두 번 돌리면 → 노드 하나 그대로

MERGE패턴 전체로 찾는다. MERGE (p:Person {id: 1, name: 'Kim'}) 은 id 1 이 있어도 name 이 다르면 못 찾고 새로 만든다. 그래서 MERGE 에는 키만 넣고 나머지는 SET 으로 붙인다. 적재 스크립트가 중간에 죽어도 다시 돌리면 같은 그래프가 되게 하는 것이 이 한 줄의 값어치다.

실무에서 진짜 중요한 것

첫째, 적재가 끝나면 세어 본다. CSV 행 수와 노드·관계 수를 라벨·종류별로 맞춘다. 관계가 모자라면 양끝이 없는 행이 있는 것이고, 그건 데이터의 문제이지 적재의 문제가 아니다 — 하지만 알고 넘어가는 것과 모르고 넘어가는 것은 다르다.

둘째, 형 검사는 질의로 한다. MATCH (p:Person) WHERE NOT p.id IS :: INTEGER RETURN count(p) 가 0 이어야 한다. 문자열 id 는 나중에 조인이 안 맞는 형태로 터져서, 그때는 적재를 의심하지 않는다.

셋째, 큰 파일은 트랜잭션을 쪼갠다. 수십만 행을 한 트랜잭션에 넣으면 힙이 먼저 바닥난다. CALL { ... } IN TRANSACTIONS OF 10000 ROWS 로 나눈다. 이 실습의 파일은 만 행 안팎이라 한 번에 들어가지만, 이 파드의 힙은 512MB 뿐이라는 것을 기억해 두면 좋다.