6 BUILDING KNOWLEDGE GRAPHS · CHAPTER 6

데이터 과학으로
지식 그래프 보강하기

Enriching Knowledge Graphs with Data Science

그래프 데이터 과학의 목표는 그래프 알고리즘으로 지식 그래프에서 인사이트를 얻는 것이다. 이 장은 흔한 그래프 알고리즘의 유형과 그것들이 발굴하는 인사이트, Neo4j Graph Data Science가 알고리즘의 실험·공유·제품화를 위한 단순한 플랫폼을 제공하는 방식, 그리고 실제 지식 그래프 위에서 알고리즘을 실행하는 법을 다룬다 — 무거운 일의 대부분은 시스템이 대신해 준다.

Jesús Barrasa · Jim Webber O'Reilly Media, 2023 Part I. Graph Fundamentals pp. 93–111
Why Graph Algorithms?

왜 그래프 알고리즘인가 — 구조가 말해 주는 것

그래프 알고리즘은 지식 그래프의 구조에 관한 인사이트를 낳는다. 그 인사이트는 소셜 그래프의 영향력 있는 사람일 수도, 철도망의 핵심 분기점일 수도, 사기꾼들의 세포 조직일 수도, 질병 경로의 공통 병원체일 수도 있다. 그래프 알고리즘의 설계와 구현은 전문 분야지만, 사용은 그렇지 않다 — 알고리즘의 목적과 지식 그래프 위에서 실행하는 문법만 이해하면 된다.

대학 시절 그래프 알고리즘을 (어쩌면 애정을 담아) 기억하는 독자도, 처음 접하는 독자도 있을 것이다. 이 장은 그래프에서 할 수 있는 데이터 처리의 종류를 개관하는 것으로 부드럽게 시작해, 흔한 알고리즘의 사용법으로 깊이 들어간다. 장이 끝날 즈음에는 Cypher 질의만큼이나 편안하게 큰 지식 그래프 위에서 그래프 알고리즘을 돌리게 될 것이다.

Note — 실습 환경과 더 깊은 자료

3장과 마찬가지로 예제에는 Neo4j를 선택했다 — 가장 대중적이고 광범위한 그래프 알고리즘 툴킷을 갖췄고 Cypher로 비전문가도 접근하기 쉽다. Neo4j Graph Data Science는 Neo4j Desktop 앱으로 쉽게 설치되고, Neo4j Sandbox는 GDS 접근을 제공하는 학습 환경이며, Neo4j AuraDS는 AuraDB의 자매 서비스인 서비스형 그래프 데이터 과학이다. 셋 중 무엇으로든 이 장의 예제를 따라갈 수 있다. 이 장은 포괄적 안내가 아니며, 심화 자료로는 Graph Data Science for Dummies(Dr. Alicia Frame & Zach Blumenfeld, Wiley) 같은 책이 있다.

Different Classes

그래프 알고리즘의 분류 — 3대 범주와 5대 실용 범주

선택 가능한 그래프 알고리즘은 많고, 각각은 주어진 그래프 위에서 계산을 수행해 기저 토폴로지와 데이터로부터 인사이트를 생성한다. 상황에 맞는 알고리즘을 고르는 첫걸음은 범주로 분류해 어떤 종류의 인사이트를 계산할 수 있는지 조감하는 것이다. 지식 그래프 계산의 관점에서 알고리즘은 세 가지 광의의 범주로 나뉜다.

통계 (Statistical)

노드·관계 수, 관계의 차수 분포, 노드 레이블 유형 등 그래프에 대한 지표를 제공한다. 결과 해석의 맥락이 되어 준다.

분석 (Analytical)

지식 그래프 전체 또는 중요한 하위 구성 요소 위에서 유의미한 패턴이나 잠재된 지식을 표면화한다. 이 장의 주인공이다.

머신러닝 (ML)

그래프 알고리즘의 결과를 ML 모델 훈련의 피처로 쓰거나, ML로 지식 그래프 자체를 진화시킨다. 7장에서 온전히 다룬다.

통계·분석 알고리즘 안에는 지식 그래프 사용 사례에 공통적인 다섯 개의 실용 범주가 있다.

범주무엇을 계산하는가대표 활용
네트워크 전파신호가 그래프를 어떻게 퍼지는지에 대한 깊은 경로 계산지역사회 질병 확산, 공급망 약점 → 봉쇄 최적화·핵심 경로 이중화
영향력 (중심성)서브그래프 사이의 다리(이자 병목)로서 평균적으로 모든 노드에 가까운 노드. 노드 영향력의 척도가 중심성(centrality)정보(또는 교란)를 네트워크에 빠르게 퍼뜨리기에 이상적인 위치의 발견
커뮤니티 탐지약한 링크를 제거하는 방식으로 긴밀히 결속된 집단을 분할함께 읽을 관련 저작, 금융 범죄를 공모하는 사기꾼들, 병원체(허위 정보 포함)가 빠르게 퍼질 커뮤니티
유사도노드 간 알려진 관계·계층 또는 공통 속성의 탐색 — 수동 육안 검사가 통하지 않는 큰 그래프에서특정 구매 이력의 고객에 대한 제품 추천, 비정상적 충돌 수준의 교차로
링크 예측기존 토폴로지와 휴리스틱(예: 삼각형 만들기)으로 누락된 관계를 계산해 그래프를 보강소셜 네트워크의 친구/팔로워/연락처 추천
Note — 같은 입력, 다른 결과: 어떤 알고리즘을 고를까

각 범주 안에는 보통 여러 알고리즘이 있고, 각각은 다르게 동작해 같은 입력에도 대개 다른 결과를 낸다 — 예컨대 커뮤니티 탐지의 WCC(Weakly Connected Components)Louvain은 같은 입력 그래프에 다른 결과를 준다. 무엇을 고를 것인가? 답은 종종 문서를 읽고 맥락에 가장 맞는 알고리즘을 고르는 것이고, 때로는 실험해 보고 가장 좋은 답을 주는 것을 쓰는 것이다.

Graph Data Science Operations

GDS 연산 — 프로젝션, 실행, 그리고 되쓰기

Neo4j Graph Data Science는 Neo4j 그래프 데이터베이스와 편리하게 통합된 그래프 계산 프레임워크다. Neo4j에 저장된 지식 그래프에서 데이터를 프로젝션하고, (여러 CPU를 사용해) 그래프 알고리즘으로 분석을 계산하고, 결과를 검사·공유하거나 기저 지식 그래프에 되써서 보강할 수 있다. GDS와 데이터베이스의 긴밀한 통합은 많은 운영 부담을 제거하고, 알고리즘 실행에 집중하게 하며, 데이터 랭글링을 최소화한다.

지식 그래프 Neo4j (디스크) STEP 1–2 프로젝션 읽기·적재 압축 인메모리 포맷으로 STEP 3 알고리즘 실행 선택한 매개변수 · 병렬 CPU STEP 4 결과 저장 write · stream · mutate write: 그래프에 되쓰기 (노드 보강) — 다운스트림 시스템·호출자에게도 전달 가능 프로젝션 = 서브그래프 · 특정 레이블/관계 타입 · Cypher 패턴 · 그래프 전체
그림 6-1 · 지식 그래프 위에서 그래프 알고리즘을 실행하는 네 단계 — 프로젝션은 한 번 만들면 재생성 없이 여러 번 알고리즘을 실행할 수 있다
Note — GPU가 아니라 CPU·RAM인 이유

업계에서는 높은 병렬성과 대역폭을 노려 일부 알고리즘을 GPU에서 돌리는 것이 흔하다. 그러나 GPU는 알고리즘을 선형대수 문제로 표현할 수 있을 때 가장 적합한데, 그것은 결코 보편적(이거나 자명)이지 않다. 현재 Neo4j GDS는 CPU와 RAM에 고도로 최적화되어 있으며, 특수 하드웨어의 불편 없이 가장 넓은 범위의 알고리즘에 동급 최고 성능을 제공한다 — 대개 GPU 기반 솔루션을 능가한다.

Sidebar — 스케일업 vs 스케일아웃

프로젝션 단계는 그래프(일부)를 압축 포맷으로 메인 메모리에 적재한다. 인메모리라는 사실이 지역성(locality) 이점으로 고성능을 보장한다 — 어느 노드의 이웃도 메모리에 있어 빠르게 접근된다. 매우 큰 그래프는 RAM(병렬 알고리즘은 CPU 코어도)을 스케일업해야 할 수 있다. Hadoop이 개척한 스케일아웃은 처음엔 매력적이지만, 이웃 노드가 원격 서버에 있게 되어 단순한 포인터 역참조가 네트워크 연산으로 바뀐다 — 지역성 이점이 사라져 분산 그래프 처리는 실무적으로 비효율적이다. 집필 시점 기준 스케일업이 가장 빠르고 실용적이며 종종 더 저렴하다. 연구가 성숙하면 상황이 바뀔 수 있고, 그때 GDS 사용자는 구현 세부로부터 격리된 채 성능 이득만 누리게 될 것이다.

Example 6-1 · Cypher에서 GDS 실행 — 프로젝션 + 매개 중심성Cypher
CALL gds.graph.project.cypher(
  'gds-example-graph',                          // ① 프로젝션 이름 (그래프 카탈로그에 저장)
  'MATCH (p:Person)
     RETURN id(p) AS id',                        // ② 노드 질의 — id 컬럼은 계약(contract)
  'MATCH (p1:Person)-[:FRIEND]->(p2:Person)
    RETURN id(p1) AS source, id(p2) AS target,
           "FRIEND" AS type');                   // ③ 관계 질의 — source·target·type 계약

CALL gds.betweenness.write('gds-example-graph',{  // ④ 알고리즘 실행 — write 모드
  writeProperty:'betweennessCentrality'});

질의의 네 요소: ① 프로젝션 이름은 그래프 카탈로그에 저장되어 후속 계산이 접근한다. ② 노드 질의는 프로젝션에 포함될 노드를 고른다 — 레이블로 고르는 방법 등 여러 방식이 있지만 가장 유연한 것은 Cypher 프로젝션이며, 노드 ID는 id 컬럼으로 반환해야 한다는 것이 계약이다. ③ 관계 질의는 포함될 관계를 고르며, 관계는 노드 프로젝션의 노드에 연결되어야 하고(아니면 실패) 시작·끝·타입을 source·target· type 컬럼으로 반환한다. ④ 실행에서 gds.betweenness.write는 결과를 그래프에 되쓴다 — 호출자에게 스트리밍하는 stream, 프로젝션만 보강하는 mutate와 구별된다.

매개 중심성(betweenness centrality)은 주어진 노드를 지나는 경로의 수를 계산해 그래프에서 노드의 중요도를 정한다. 3장의 작은 소셜 네트워크에 실행하면 Karl이 최고 매개 중심성을 갖는다 — Karl은 Rosa와 Fred를 잇는 연결 노드라서, 그가 없으면 소셜 네트워크 전체가 무너진다. Neo4j Bloom 시각화 도구에서는 속성 기반으로 노드의 크기(나 색)를 구성할 수 있어, 중심성이 높을수록 노드를 크게 그려 시각화를 훨씬 정보 풍부하게 만들 수 있다.

매개 중심성만이 아니다 — 경로 찾기, PageRank, Louvain 등 65개 이상의 알고리즘 중에서 고를 수 있고, 각각은 Example 6-1과 똑같이 실행되므로 도메인에 맞는 인사이트를 찾는 실험이 최소한 운영적으로는 단순하다.

Note — 실행 시간의 감각

작은 지식 그래프에서 매개 중심성 계산은 즉시 끝난다. 수만~수십만의 적당한 그래프에서도 실행 시간을 느끼기 어렵다. 그러나 수백만~수십억 규모의 매우 큰 지식 그래프에서는 (알고리즘·프로젝션·하드웨어에 따라) 수 초에서 수 분이 걸릴 수 있다. 실무에서 이것은 문제가 아니다 — 지식 그래프 보강은 대개 주기적으로 수행하기 때문이다. 실험처럼 빠른 피드백이 필요하면 더 작은 프로젝션을 고르거나 RAM·CPU가 더 많은 서버를 선택하라.

Experimenting

Python으로 실험하기 — 영국 철도망 위에서

데이터 과학자의 일에는 실험이 포함된다. 그래프 데이터 과학을 배우려면 좋은 실험 습관 — 가설 위를 빠르게 반복하는 능력 — 도 배워야 한다. 그림 6-1의 아키텍처는 매우 데이터베이스 중심적이라, 알고리즘 실험이 목적일 뿐인데 복잡성에 길을 잃기 쉽다. 다행히 데이터베이스 세부를 추상화해 훨씬 높은 추상화 수준에서 일하게 해 주는 Python 도구가 있다 — 세상을 Python 객체로 보는 것이 데이터 과학자에게는 훨씬 관용적이다.

툴킷 구성: Neo4j와 GDS(당연히), Neo4j GDS Python 드라이버, 그리고 Jupyter Notebook(또는 유사 도구)이다. Python 3이 설치되어 있다면 pip install graphdatascience로 Pythonic API를, pip install notebook으로 Jupyter를 설치한다. 이후 예제는 모두 영국(Great Britain) 철도망에 기반한다 — 크지만 너무 크지 않고, 고도로 상호 연결되어 있으며, 지극히 현실적인 사용 사례라 그래프 데이터 과학 입문자가 탐험하기에 이상적이다. 실험용 소스 데이터는 https://github.com/jbarrasa/gc-2022 에서 받을 수 있다.

Example 6-2 · Python으로 CSV를 Neo4j에 적재 (역 + 선로)Python
from graphdatascience import GraphDataScience

# 데이터베이스 연결
host = "bolt://127.0.0.1:7687"
user = "neo4j"
password = "yolo"

gds = GraphDataScience(host, auth=(user, password), database="neo4j")

# 역(station)을 노드로 적재
gds.run_cypher(
    """
    LOAD CSV WITH HEADERS FROM "nr-stations-all.csv" AS station
    CREATE (:Station {name: station.name, crs: station.crs})
    """
)

# 역 사이의 선로(track)를 관계로 적재
gds.run_cypher(
    """
    LOAD CSV WITH HEADERS FROM "nr-station-links.csv" AS track
    MATCH (from:Station {crs: track.from})
    MATCH (to:Station {crs: track.to})
    MERGE (from)-[:TRACK {distance: round( toFloat(track.distance), 2 )}]->(to)
    """
)

gds.close()

이 코드는 세 가지를 한다 — ① GraphDataScience 인스턴스 생성으로 인증된 DBMS 접근을 얻고, ② gds.run_cypher(임의의 Cypher를 실행하게 해 주는 메서드)로 4장과 같은 LOAD CSV를 돌려 이름(name)과 공식 고유 코드(crs)를 가진 Station 노드를 만들고, ③ crs 코드로 from·to 노드를 조회해 소수점 둘째 자리로 반올림된 distance 속성을 가진 TRACK 관계를 MERGE한다. 드라이버도, 세션도, 다른 기술적 데이터베이스 잡무도 다룰 필요가 없었다 — 그저 코드 속 Python 객체 하나를 썼을 뿐이다.

프로젝션과 최단 경로 — 버밍엄에서 에든버러까지

여행 네트워크에서 가장 흔한 질의는 두 역 사이의 최단(또는 최저 비용) 경로다. 먼저 철도 지식 그래프에서 프로젝션을 만든다 — 알고리즘 계산에 적합한 도메인 특화 인메모리 그래프다. 이 프로젝션은 원본과 위상적으로 유사하지만 노드는 더 적은 데이터(역 이름)만 담고 거리는 관계 속성으로 저장된다. 나중에 더 많은 데이터가 필요하면 더 풍부한 프로젝션을 새로 만들면 되고, 그것은 기존 프로젝션과 나란히 그래프 카탈로그에 추가된다.

Example 6-3 · trains 프로젝션 생성Python
gds = GraphDataScience(host,
    auth=(user, password), database="neo4j")

gds.graph.project.cypher(
    graph_name='trains',
    node_spec='MATCH (s:Station)
               RETURN id(s) AS id',
    relationship_spec=
    """
    MATCH (s1:Station)-[t:TRACK]->(s2:Station)
    RETURN id(s1) AS source, id(s2) AS target,
           t.distance AS distance
    """
)

gds.close()
Example 6-4 · Dijkstra 최단 경로Python
gds = GraphDataScience(host,
    auth=(user, password), database="neo4j")

bham = gds.find_node_id(["Station"],
    {"name": "Birmingham New Street"})
eboro = gds.find_node_id(["Station"],
    {"name": "Edinburgh"})

shortest_path = gds.shortestPath.dijkstra.stream(
    gds.graph.get("trains"),
    sourceNode=bham,
    targetNode=eboro,
    relationshipWeightProperty="distance"
)

print("Shortest distance: %s"
    % shortest_path.get('costs').get(0)[-1])
# → Shortest distance: 298.0

find_node_id와 Cypher 표현식으로 버밍엄 뉴 스트리트(Birmingham New Street)와 에든버러(Edinburgh) 역 노드의 ID를 얻어 각각 bham·eboro에 담고, trains 프로젝션 위에서 Dijkstra를 실행한다. relationshipWeightProperty에는 경로 비용 계산에 쓸 distance 속성을 준다. 실행하면 최단 거리 298.0이 출력되고, Neo4j Browser에서 경로를 시각화할 수도 있다.

역 선택이 궁금한 독자를 위해 — 저자 중 한 명(Jim Webber)이 잉글랜드 버밍엄에서 자랐고, 에든버러는 그저 아름다운 도시다. 그런데 버밍엄은 지리적으로 흥미롭다(영국인들 사이에서는 지루하다는 부당한 평판이 있지만, 산업혁명의 심장으로서 당대의 실리콘밸리였고 그 유산이 많이 남아 있다). 잉글랜드 중앙에 있고, 버밍엄 뉴 스트리트는 에든버러와 교차하는 노선을 포함해 영국 전역의 노선을 서비스하는 분주한 허브 역이다. 그렇다면 버밍엄 뉴 스트리트가 영국 철도망에서 가장 핵심적인 역이라고 추측할 만하고, 그 추측에 근거해 역과 주변 선로에 대한 대규모 투자를 주창할 수도 있다. 그런데 그 추측이 틀렸다면? 다행히 직감을 빠르게 검증할 도구가 손안에 있다.

A Counterintuitive Result

탬워스의 반전 — 알고리즘이 직감을 뒤집을 때

trains 프로젝션에는 분석에 필요한 모든 데이터가 있다. 버밍엄 뉴 스트리트의 중요성을 검증(또는 반증)하려면 중심성을 계산하면 된다.

Example 6-5 · 영국 전 철도역의 중심성 계산Python
gds = GraphDataScience(host, auth=(user, password), database="neo4j")
graph = gds.graph.get("trains")
result = gds.betweenness.stream(graph)
highest_score = result.sort_values(by="score", ascending=False)
    .iloc[0:1].get('nodeId')

n = gds.run_cypher(f"MATCH (s:Station) WHERE ID(s) = {int(highest_score)}
    RETURN s.name")
print("Station with highest centrality: %s" % n["s.name"][0])
# → Station with highest centrality: Tamworth
매개 중심성 vs 연간 승객 — 두 개의 다른 "중요함" BETWEENNESS CENTRALITY Tamworth 1,967,643 Birmingham N.S. 254,706 (약 1/8) ANNUAL PASSENGERS Tamworth 120만 명/년 Birmingham N.S. 4,700만 + 환승 700만 — 런던 밖 최다
Example 6-5의 결과 · 물리망 관점의 중요도(중심성)와 이용량 관점의 중요도(승객)는 서로 다른 질문에 대한 답이다

실행 결과는 Tamworth(탬워스)다 — 시스템에 대한 비공식적 이해에서 기대한 답이 아니다. 실제로 탬워스의 중심성 점수는 1,967,643으로 버밍엄 뉴 스트리트의 254,706보다 거의 여덟 배 높다. 이제 장애 시 철도망에 가장 큰 영향을 미치는 곳은 버밍엄에서 몇 마일 떨어진 탬워스임을 안다. 철도 정책 담당자라면 거기서 시작할 것이다.

Sidebar — 그래프 데이터 과학은 반직관적 결과를 드러낸다

곤혹스러운 사실: 중심성이 높은 탬워스의 승객은 연 120만 명에 불과한 반면, 버밍엄 뉴 스트리트는 4,700만 명(+환승 700만)이다 — 영국에서 여덟 번째로 붐비고 런던 밖에서는 가장 붐비는 역이다. 탬워스가 가장 중요하다는 발견과 상충되어 보인다. 실제로는 두 가지가 작동하고 있을 수 있다. 첫째, 이 중심성 계산은 승객이나 열차 노선이 아니라 연결된 선로만 가중했다 — 물리망의 관점에서 탬워스는 정말로 중요하다. 둘째, 네트워크 효과는 실재한다 — 탬워스의 인프라가 고장 나면 결함이 망을 타고 파급되면서 버밍엄 뉴 스트리트와 그 너머 역들의 대규모 승객에게 영향을 미칠 가능성이 크다.

현실은 단일한 알고리즘 점수만큼 단순하지 않을 때가 많다. 그래프 데이터 과학자는 더 풍부한 데이터나 더 정확한 프로젝션으로 문제를 정제하거나, 가설을 재정식화하고 더 많은 데이터와 알고리즘을 투입할 준비가 되어 있어야 한다. 다행히 Python + GDS의 실험 동선은 테스트 케이스의 빠른 재평가를 허용해, 좋은 답을 향해 반복할 기회를 더 많이 준다.

Production Considerations

프로덕션 고려 사항 — 역할 분리와 인과적 장벽

실험 설정에 만족하고 프로젝션·알고리즘 선택이 끝나면 프로덕션으로 옮길 때가 온다. 이제 Python 객체와 Jupyter Notebook의 세계가 아니라 프로덕션 데이터 엔지니어링의 영역이다. 흔한 구성은 물리 서버를 역할별로 분리해 워크로드를 물리적으로 격리하는 것이다. Neo4j는 이를 위해 프라이머리(primary)세컨더리(secondary) 서버 개념을 지원한다.

PRIMARIES 트랜잭션 처리 · Cypher 질의 클러스터링으로 규모·내결함성 확보 서버 1 서버 2 서버 3 사이징: 큰 RAM + 높은 I/O, 적당한 CPU 비동기 갱신 SECONDARIES 그래프 데이터 과학 · 글로벌 질의 트랜잭션 처리를 늦추지 않는다 GDS 서버 A GDS 서버 B 사이징: 다수의 빠른 CPU + 큰 RAM (병렬 계산)
그림 6-5 · 프라이머리는 지식 그래프를 호스팅하고, 세컨더리는 계산 집약적 그래프 데이터 과학의 일꾼이 된다 — 워크로드에 따라 서버 사이징도 서로 다르게

프라이머리는 트랜잭션 처리를 담당하고 지식 그래프를 클러스터링해 규모와 내결함성을 확보하며, 대개 Cypher 질의도 서비스한다. 원칙적으로 프라이머리에서도 알고리즘을 돌릴 수 있지만, 알고리즘의 계산 워크로드와 진행 중인 데이터베이스 워크로드 사이에 경합이 생긴다. 대신 데이터 과학용 세컨더리 인스턴스 배치를 고려하라 — 세컨더리는 갱신을 트랜잭션이 아니라 비동기로 받으므로 갱신 시점 보장이 느슨한 대신, 트랜잭션 처리를 늦추지 않는다. 사이징도 다르게 한다 — 프라이머리는 큰 RAM과 높은 I/O에 적당한 CPU(데이터베이스 질의·트랜잭션은 CPU 바운드가 드물다), 세컨더리는 빠른 병렬 계산을 위한 다수의 빠른 CPU와 큰 RAM이다.

클러스터 인프라는 서버에 의도된 역할을 태깅하게 해 주므로 사용자는 작업을 가장 적절한 물리 서버로 보낸다. 이 모든 것이 단순하고 대부분 투명하다. 분명히 말하면, Neo4j는 지식 그래프를 위한 하이브리드 트랜잭션· 분석 처리(HTAP) 운영을 매우 단순하게 만든다 — 동일한 데이터를 가진 단일 클러스터이며, ETL도 OLAP 큐브도 다른 복잡한 데이터 엔지니어링도 필요 없다.

세컨더리의 신선도 보장 — 북마크와 인과적 장벽

그래프 알고리즘을 돌리기 전에 세컨더리의 신선도(freshness)를 보장받고 싶을 때가 있다. 3장에서 본 Neo4j의 선택적 인과적 장벽(causal barrier) — 광역 네트워크에 흩어진 서버에서도 사용자는 언제나 최소한 자신의 쓰기를 본다 — 은 프라이머리와 세컨더리 모두에, 트랜잭션 워크로드만큼이나 데이터 과학에도 적용된다.

Example 6-6 · 커밋한 트랜잭션의 북마크 얻기Java
try (Session session = driver.session(AccessMode.WRITE)) {
    try (Transaction tx = session.beginTransaction()) {
        tx.run("CREATE (user:User {userId: {userId},
                 passwordHash: {passwordHash}})",
               parameters("userId", userId,
                          "passwordHash", passwordHash));
        tx.success();
    }

    // 북마크 = 방금 커밋한 트랜잭션의 ID
    String bookmark = session.lastBookmark();
}
Example 6-7 · 북마크로 인과적 장벽 강제Java
{
    try (Transaction tx =
             session.beginTransaction(bookmark)) {
        tx.run("MATCH (user:User {userId: {userId}})
                 RETURN *",
               parameters("userId", userId));
        tx.success();
    }
}
// 해당 트랜잭션이 현재 서버에서 처리되기 전에는
// 연산이 진행되지 않는다 — 세컨더리에서도
// 언제나 최소한 자신의 쓰기를 본다
Enriching the Knowledge Graph

지식 그래프 보강 — stream을 write로 바꾸는 순간

프로덕션 데이터 과학이 실험 국면과 달라지는 또 하나의 지점은 결과로 무엇을 하는가다. 실험에서는 노트(물리적이든 디지털이든)에 결과를 기록하는 것이 선호되지만, 프로덕션에서는 결과를 활용해 시스템을 개선해야 한다. Example 6-5에서 중심성 결과는 gds.betweenness.stream(graph)으로 Python 클라이언트에 스트리밍되어 콘솔에 출력되었다. 결과가 유용했다면 운영화할 수 있는 방식으로 포착하고 싶을 것이다. 방법은 간단하다 — stream 메서드를 mutate(프로젝션 갱신) 또는 write(기저 지식 그래프 보강 — 계산이 어디서 실행되었든)로 바꾸면 된다.

Example 6-8 · write로 중심성 점수를 그래프에 되쓰고 검증Python
gds = GraphDataScience(host, auth=(user, password), database="neo4j")
graph = gds.graph.get("trains")
result = gds.betweenness.write(gds.graph.get("trains"), writeProperty="betweenness")

total_stations = gds.run_cypher("MATCH (s:Station) RETURN count(s) AS total_stations")
print(f'Total number of stations: {total_stations.iloc[0][0]}')

processed_stations = gds.run_cypher(
  """
  MATCH (s:Station)
  WHERE s.betweenness IS NOT NULL
  RETURN count(s) AS stations_processed
  """)

print(f'Number of stations with betweenness score: {processed_stations.iloc[0][0]}')

# → Total number of stations: 2593
# → Number of stations with betweenness score: 2593

이 코드는 중심성 계산을 수행하되 결과를 콘솔에 보내는 대신 기저 지식 그래프에 되써서, ML을 위한 그래프 피처 엔지니어링 같은 미래의 사용 사례를 위해 그래프를 보강한다. 콘솔 메시지(2593개 역 전부에 betweenness 점수)로 보강을 검증할 수 있다. 탐색적 그래프 분석에서 데이터 과학으로, 실험 정제에서 프로덕션 준비로의 이동은 이처럼 직선적이다 — 훈련받은 데이터 과학자가 아닌 독자도 이 동선을 따라 충분히 전진할 수 있다.

Summary

요약 — 지루하기는커녕, 정량화된 인사이트

이 장에서 그래프 알고리즘을 배웠다(어쩌면 다시 배웠다). 지루하고 기계적인 것이기는커녕, 그래프 알고리즘을 이용한 실험은 정량화 가능한 인사이트를 표면화한다. 그리고 코딩이나 데이터 엔지니어링은 거의 필요하지 않다는 것도 보았다.

1

3대 범주, 5대 실용 범주. 통계·분석·ML로 크게 나뉘고, 실무에서는 네트워크 전파·영향력(중심성)·커뮤니티 탐지·유사도·링크 예측이 핵심이다. 같은 범주라도 WCC와 Louvain처럼 결과가 다르니 문서와 실험으로 고른다.

2

GDS의 4단계. 프로젝션 읽기 → 인메모리 적재 → 알고리즘 실행 → 결과 저장(write/stream/mutate). 프로젝션의 계약은 노드 id, 관계 source·target·type이며, 한 번 만든 프로젝션 위에서 알고리즘을 반복 실행할 수 있다.

3

스케일업이 답이다. 인메모리 지역성이 성능의 원천이므로 RAM·CPU를 키운다. 분산(스케일아웃)은 포인터 역참조를 네트워크 연산으로 바꿔 비효율적이며, GDS는 GPU 기반 솔루션도 대개 능가한다.

4

알고리즘은 직감을 검증한다. 버밍엄 뉴 스트리트가 아니라 탬워스가 최고 중심성(1,967,643 vs 254,706)이었다 — 물리망 관점과 이용량 관점은 다른 질문이다. 더 풍부한 데이터·프로젝션으로 정제하고 가설을 재정식화하라.

5

프로덕션 = 역할 분리 + 인과적 장벽. 프라이머리(트랜잭션)와 세컨더리(GDS)를 분리 사이징하는 단일 클러스터 HTAP — ETL·OLAP 큐브 불필요. 북마크로 세컨더리에서도 자신의 쓰기를 보장받고, stream→write 전환으로 결과를 운영화한다.

Next — 7장 예고

다음 장에서는 그래프 알고리즘을 ML과 혼합한다. 알고리즘이 만든 그래프 피처로 더 나은 예측 모델을 구축하고, 알고리즘이 지식 그래프를 직접 보강하는 그래프 네이티브 머신러닝(7장, Graph-Native Machine Learning)을 사용한다.