blog.dopana

Back

Graph database lưu dữ liệu dưới dạng nodes (đỉnh) và relationships (cạnh). Khi quan hệ giữa các entities là quan trọng nhất — graph DB vượt trội hơn SQL rất nhiều.

Graph vs Relational#

Ví dụ: tìm bạn bè của bạn bè (friend-of-friend):

-- SQL — 5 JOIN
SELECT DISTINCT fof.*
FROM users u
JOIN friendships f1 ON u.id = f1.user_id
JOIN users f1u ON f1.friend_id = f1u.id
JOIN friendships f2 ON f1u.id = f2.user_id
JOIN users fof ON f2.friend_id = fof.id
WHERE u.id = 1 AND f2.friend_id != u.id;
sql
// Neo4j — 1 pattern
MATCH (u:User {id: 1})-[:FRIENDS_WITH*2]->(fof:User)
WHERE NOT (u)-[:FRIENDS_WITH]->(fof)
RETURN DISTINCT fof
cypher

Graph database: relationships first-class citizens. Trong SQL, relationship là foreign key + JOIN. Trong graph, relationship là thực thể riêng, có thể có properties.

Cypher Query Language#

Nodes & Relationships#

// Create node
CREATE (u:User {id: 1, name: 'Alice', age: 30})

// Create relationship
CREATE (u1:User {name: 'Alice'})-[:FRIENDS_WITH {since: 2024}]->(u2:User {name: 'Bob'})
cypher

Match — Query#

// Find user
MATCH (u:User {name: 'Alice'})
RETURN u

// Friends of Alice
MATCH (alice:User {name: 'Alice'})-[:FRIENDS_WITH]->(friend:User)
RETURN friend.name, friend.age

// Friend-of-friend (2 levels deep)
MATCH (alice:User {name: 'Alice'})-[:FRIENDS_WITH*2]->(fof:User)
RETURN DISTINCT fof.name

// Variable length paths
MATCH path = (alice:User {name: 'Alice'})-[:FRIENDS_WITH*1..3]->(other)
RETURN path
cypher

Create & Update#

Delete#

// Delete relationship
MATCH (u:User {id: 1})-[r:LIKED]->()
DELETE r

// Delete node and relationships
MATCH (u:User {id: 1})
DETACH DELETE u
cypher

Aggregation#

// Count friends per user
MATCH (u:User)-[:FRIENDS_WITH]->(friend)
RETURN u.name, COUNT(friend) AS friendCount
ORDER BY friendCount DESC

// Average age of friends
MATCH (u:User {name: 'Alice'})-[:FRIENDS_WITH]->(friend)
RETURN AVG(friend.age) AS avgFriendAge

// Most liked posts
MATCH (p:Post)<-[:LIKED]-()
RETURN p.title, COUNT(*) AS likes
ORDER BY likes DESC
LIMIT 10
cypher

Recommendation System#

// "Users who liked this post also liked..."
MATCH (target:Post {id: 101})<-[:LIKED]-(user:User)
MATCH (user)-[:LIKED]->(other:Post)
WHERE other.id != 101
RETURN other.title, COUNT(*) AS score
ORDER BY score DESC
LIMIT 5

// Product recommendation (collaborative filtering)
MATCH (customer:Customer {id: 1})-[:PURCHASED]->(:Product)<-[:PURCHASED]-(other:Customer)
MATCH (other)-[:PURCHASED]->(recommendation:Product)
WHERE NOT (customer)-[:PURCHASED]->(recommendation)
RETURN recommendation.name, COUNT(*) AS relevance
ORDER BY relevance DESC
LIMIT 10
cypher

Social Graph#

Node.js với Neo4j#

Use Cases#

Use CaseSQLGraph
Friend-of-friendNhiều JOIN, chậm1 hop, nhanh
RecommendationKhó modellingTự nhiên
Fraud detectionPattern detection phức tạpPattern matching dễ
Network/ITNhiều JOINGraph traversal
Knowledge graphKhông phù hợpRất phù hợp

Docker#

services:
  neo4j:
    image: neo4j:5-community
    ports:
      - "7474:7474"  # Browser UI
      - "7687:7687"  # Bolt protocol
    environment:
      - NEO4J_AUTH=neo4j/password
      - NEO4J_PLUGINS=["apoc"]
    volumes:
      - neo4j_data:/data

volumes:
  neo4j_data:
yaml

Performance#

// Index cho frequent lookups
CREATE INDEX user_id IF NOT EXISTS FOR (u:User) ON (u.id)
CREATE INDEX post_id IF NOT EXISTS FOR (p:Post) ON (p.id)

// Composite index
CREATE INDEX user_name_email IF NOT EXISTS FOR (u:User) ON (u.name, u.email)

// PROFILE — xem query plan
PROFILE
MATCH (u:User {id: 1})-[:FRIENDS_WITH]->(friend)
RETURN friend.name
cypher

Kết Luận#

Graph database chiến thắng khi:

  • Dữ liệu có nhiều quan hệ phức tạp
  • Cần traversal nhiều levels (friend-of-friend)
  • Recommendation, fraud detection
  • Knowledge graph, network analysis

Không dùng graph cho: CRUD đơn giản, báo cáo tổng hợp, dữ liệu dạng bảng — SQL vẫn tốt hơn.

Neo4j + Cypher là lựa chọn tốt nhất cho graph database. Bắt đầu với Docker, học Cypher cơ bản, và dùng khi có vấn đề về connection.

Tài liệu tham khảo#