🗄️ 데이터베이스

Neo4j

Graph Database

세계에서 가장 널리 사용되는 그래프 데이터베이스. 노드(Node)와 관계(Relationship)로 연결된 데이터를 Cypher 쿼리 언어로 직관적으로 탐색합니다.

📖 상세 설명

Neo4j는 2007년에 출시된 네이티브 그래프 데이터베이스로, 속성 그래프(Property Graph) 모델을 사용합니다. 노드와 관계에 속성을 저장하고, 관계 기반 데이터 탐색에 최적화된 인덱스 프리 인접성(Index-Free Adjacency)을 제공합니다.

(Alice:User)-[:FOLLOWS]->(Bob:User)-[:LIKES]->(Post:Content)

속성 그래프 모델 (Property Graph)

  • 노드(Node): 엔티티를 표현. 라벨(Label)로 분류, 속성(Property)으로 데이터 저장
  • 관계(Relationship): 노드 간 연결. 방향성, 타입, 속성을 가짐
  • 라벨(Label): 노드 분류 (User, Product, Order 등)
  • 속성(Property): 키-값 쌍으로 노드/관계에 데이터 저장

주요 활용 사례

소셜 네트워크
팔로워, 친구 관계, 그룹 멤버십 모델링 및 친구 추천
추천 시스템
협업 필터링, 유사 사용자/상품 기반 추천
사기 탐지
거래 네트워크 분석, 이상 패턴 탐지
지식 그래프
엔티티 관계 모델링, RAG 시스템, 온톨로지
네트워크 인프라
IT 인프라 의존성, 영향 분석
공급망 관리
부품 추적, BOM 전개, 리스크 분석

💻 코드 예제

Cypher - 스키마 및 데이터 생성
// 인덱스 및 제약조건 생성 CREATE CONSTRAINT user_email_unique FOR (u:User) REQUIRE u.email IS UNIQUE; CREATE INDEX user_name_idx FOR (u:User) ON (u.name); // 노드 생성 CREATE (alice:User { id: 'u-001', name: 'Alice', email: 'alice@example.com', created_at: datetime() }); CREATE (bob:User {id: 'u-002', name: 'Bob', email: 'bob@example.com'}); CREATE (charlie:User {id: 'u-003', name: 'Charlie', email: 'charlie@example.com'}); // 관계 생성 MATCH (a:User {name: 'Alice'}), (b:User {name: 'Bob'}) CREATE (a)-[:FOLLOWS {since: date('2024-01-15')}]->(b); // 게시물과 좋아요 관계 CREATE (p:Post {id: 'p-001', title: 'Graph DB 입문', content: '...'}) WITH p MATCH (u:User {name: 'Alice'}) CREATE (u)-[:WROTE {at: datetime()}]->(p);
Cypher - 소셜 네트워크 쿼리
// 친구의 친구 추천 (2-hop, 내가 아직 팔로우하지 않은 사람) MATCH (me:User {name: 'Alice'})-[:FOLLOWS]->(friend)-[:FOLLOWS]->(fof:User) WHERE me <> fof AND NOT (me)-[:FOLLOWS]->(fof) WITH fof, COUNT(friend) AS mutual_friends RETURN fof.name, mutual_friends ORDER BY mutual_friends DESC LIMIT 10; // 특정 사용자까지의 최단 경로 MATCH (start:User {name: 'Alice'}), (end:User {name: 'Charlie'}) MATCH path = shortestPath((start)-[:FOLLOWS*..6]->(end)) RETURN path, length(path) AS hops; // 인기 게시물 (좋아요 수 기준) MATCH (p:Post)<-[:LIKED]-(u:User) WITH p, COUNT(u) AS like_count ORDER BY like_count DESC LIMIT 10 RETURN p.title, like_count;
Cypher - 추천 시스템
// 협업 필터링: 비슷한 취향의 사용자가 좋아한 상품 추천 MATCH (me:User {id: 'u-001'})-[:PURCHASED]->(product:Product)<-[:PURCHASED]-(similar:User) MATCH (similar)-[:PURCHASED]->(rec:Product) WHERE NOT (me)-[:PURCHASED]->(rec) WITH rec, COUNT(DISTINCT similar) AS score ORDER BY score DESC LIMIT 10 RETURN rec.name, rec.category, score; // 카테고리 기반 추천 MATCH (me:User {id: 'u-001'})-[:INTERESTED_IN]->(cat:Category)<-[:BELONGS_TO]-(product:Product) WHERE NOT (me)-[:PURCHASED]->(product) RETURN product.name, cat.name, product.price ORDER BY product.rating DESC LIMIT 20;
Python - neo4j 드라이버 사용
from neo4j import GraphDatabase from contextlib import contextmanager class Neo4jRepository: def __init__(self, uri: str, user: str, password: str): self.driver = GraphDatabase.driver(uri, auth=(user, password)) def close(self): self.driver.close() @contextmanager def session(self): session = self.driver.session() try: yield session finally: session.close() def create_user(self, user_id: str, name: str, email: str): with self.session() as session: session.run(""" MERGE (u:User {id: $id}) SET u.name = $name, u.email = $email, u.updated_at = datetime() """, id=user_id, name=name, email=email) def follow_user(self, from_id: str, to_id: str): with self.session() as session: session.run(""" MATCH (a:User {id: $from_id}), (b:User {id: $to_id}) MERGE (a)-[r:FOLLOWS]->(b) ON CREATE SET r.since = date() """, from_id=from_id, to_id=to_id) def get_recommendations(self, user_id: str, limit: int = 10) -> list: with self.session() as session: result = session.run(""" MATCH (me:User {id: $user_id})-[:FOLLOWS]->(friend)-[:FOLLOWS]->(fof:User) WHERE me <> fof AND NOT (me)-[:FOLLOWS]->(fof) WITH fof, COUNT(friend) AS score RETURN fof.id AS id, fof.name AS name, score ORDER BY score DESC LIMIT $limit """, user_id=user_id, limit=limit) return [dict(record) for record in result] def find_shortest_path(self, from_id: str, to_id: str, max_hops: int = 6): with self.session() as session: result = session.run(f""" MATCH (start:User {{id: $from_id}}), (end:User {{id: $to_id}}) MATCH path = shortestPath((start)-[:FOLLOWS*..{max_hops}]->(end)) RETURN [n IN nodes(path) | n.name] AS path, length(path) AS hops """, from_id=from_id, to_id=to_id) record = result.single() return dict(record) if record else None # 사용 예시 repo = Neo4jRepository("neo4j://localhost:7687", "neo4j", "password") repo.create_user("u-001", "Alice", "alice@example.com") repo.create_user("u-002", "Bob", "bob@example.com") repo.follow_user("u-001", "u-002") recommendations = repo.get_recommendations("u-001") print(recommendations) repo.close()
APOC - 유틸리티 프로시저
// APOC: 대량 데이터 로드 CALL apoc.periodic.iterate( "LOAD CSV WITH HEADERS FROM 'file:///users.csv' AS row RETURN row", "MERGE (u:User {id: row.id}) SET u.name = row.name, u.email = row.email", {batchSize: 1000, parallel: true} ); // APOC: 경로 확장 (BFS/DFS) MATCH (start:User {name: 'Alice'}) CALL apoc.path.expand(start, "FOLLOWS", "User", 1, 3) YIELD path RETURN path; // APOC: 가상 관계 생성 (유사도 기반) MATCH (u1:User)-[:PURCHASED]->(p:Product)<-[:PURCHASED]-(u2:User) WHERE u1 <> u2 WITH u1, u2, COUNT(p) AS common_products WHERE common_products >= 3 CALL apoc.create.vRelationship(u1, 'SIMILAR_TO', {score: common_products}, u2) YIELD rel RETURN u1.name, u2.name, rel.score;

💬 현업 대화 예시

백엔드 개발자
"친구 추천 기능을 SQL로 구현하려니 JOIN이 6단계까지 필요해서 쿼리가 너무 복잡해요."
아키텍트
"그런 관계 탐색은 Neo4j가 딱이야. MATCH (me)-[:FOLLOWS*2]->(fof) 한 줄이면 2-hop 친구를 다 찾아. 성능도 관계형보다 훨씬 좋고."
데이터 엔지니어
"금융 사기 탐지 시스템에서 자금 흐름 추적이 필요한데요. 계좌 간 5단계 연결까지 분석해야 해요."
시니어 개발자
"Neo4j의 shortestPath나 가변 길이 경로 탐색 쓰면 돼. (a)-[:TRANSFER*1..5]->(b)로 5홉까지 추적하고, 순환 패턴 탐지도 가능해. APOC의 path.expand 쓰면 더 세밀하게 제어할 수 있어."
ML 엔지니어
"RAG 시스템에 지식 그래프를 넣으려고 하는데, Neo4j가 적합할까요?"
데이터 사이언티스트
"지식 그래프 구축엔 Neo4j가 표준이야. 엔티티-관계 모델링하고, LLM이 생성한 쿼리로 컨텍스트 검색하면 돼. LangChain에 Neo4j 통합도 잘 되어 있어."

⚠️ 주의사항

⚠️ 가변 길이 경로 성능

-[:REL*]->처럼 상한 없는 가변 길이 경로는 성능에 치명적입니다. 반드시 -[:REL*1..5]->처럼 최대 홉 수를 제한하세요.

⚠️ 인덱스 필수

MATCH의 시작점이 되는 노드 속성에는 반드시 인덱스를 생성하세요. CREATE INDEX FOR (u:User) ON (u.id). 인덱스 없이 전체 노드 스캔하면 성능이 급락합니다.

⚠️ 라이선스

Neo4j Community Edition은 오픈소스(GPL)지만 단일 인스턴스만 지원합니다. 클러스터링, 고가용성, 역할 기반 접근 제어가 필요하면 Enterprise Edition(상용) 라이선스가 필요합니다.

🔗 관련 용어

📚 더 배우기