Database Sharding — Chiến Lược Phân Mảnh Dữ Liệu Khi Hệ Thống Vượt Ngưỡng

Posted on: 4/18/2026 1:11:32 AM

Mục lục

1. Tại sao cần Database Sharding?

Khi ứng dụng phát triển từ hàng nghìn lên hàng triệu người dùng, database đơn lẻ (single-node) sẽ đạt đến giới hạn về storage, throughput và latency. Vertical scaling (nâng cấp phần cứng) chỉ giải quyết được đến một mức nhất định — bạn không thể mua CPU vô hạn cho một máy chủ duy nhất.

Database Sharding (hay Horizontal Partitioning) là kỹ thuật chia một database lớn thành nhiều phần nhỏ hơn gọi là shard, mỗi shard chạy trên một server riêng biệt. Mỗi shard chứa một tập con (subset) của toàn bộ dữ liệu, nhưng có cùng schema.

100x Write throughput tăng khi shard hợp lý
<10ms Query latency trên shard nhỏ
Storage scalability theo lý thuyết
70% Hệ thống lớn dùng sharding (theo DB-Engines 2026)
graph TB
    Client[👤 Client Application] --> Router[🔀 Shard Router / Proxy]
    Router --> S1[📦 Shard 1
User ID 1-1M] Router --> S2[📦 Shard 2
User ID 1M-2M] Router --> S3[📦 Shard 3
User ID 2M-3M] Router --> SN[📦 Shard N
User ID ...] S1 --> R1[🔄 Replica 1a] S2 --> R2[🔄 Replica 2a] S3 --> R3[🔄 Replica 3a] style Client fill:#e94560,stroke:#fff,color:#fff style Router fill:#2c3e50,stroke:#fff,color:#fff style S1 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style S2 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style S3 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style SN fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style R1 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50 style R2 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50 style R3 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50

Kiến trúc Database Sharding cơ bản với Shard Router và Read Replicas

2. Các chiến lược Sharding

2.1 Hash-based Sharding

Áp dụng một hàm hash lên shard key để xác định shard đích. Đây là chiến lược phổ biến nhất vì đảm bảo phân bổ dữ liệu đều đặn.

shard_id = hash(shard_key) % number_of_shards

-- Ví dụ: user_id = 12345
-- hash(12345) = 7829431
-- shard_id = 7829431 % 4 = 3 → Shard 3

✅ Ưu điểm

Phân bổ đều dữ liệu, tránh hotspot. PlanetScale khuyến nghị hash-based là chiến lược mặc định cho hầu hết use case vì đơn giản và hiệu quả.

⚠️ Nhược điểm

Range query kém hiệu quả (phải scan tất cả shard). Khi thêm/bớt shard, cần rehash lại dữ liệu — đây là lý do Consistent Hashing ra đời.

2.2 Range-based Sharding

Chia dữ liệu theo khoảng giá trị liên tục của shard key. Phù hợp cho dữ liệu có tính thứ tự tự nhiên (timestamp, ID tuần tự, địa lý).

Shard A
orders 2024-01 → 2024-06
Shard B
orders 2024-07 → 2024-12
Shard C
orders 2025-01 → 2025-06
Shard D
orders 2025-07 → 2026-04

Ưu điểm: Range query rất nhanh (chỉ đọc 1-2 shard). Dễ hiểu, dễ quản lý.

Nhược điểm: Dễ tạo hotspot — shard chứa dữ liệu mới nhất luôn bị write nhiều nhất. Ví dụ: shard chứa orders tháng hiện tại chịu 90% traffic.

2.3 Directory-based Sharding

Dùng một bảng lookup (directory) riêng để map mỗi shard key → shard location. Linh hoạt nhất nhưng directory trở thành single point of failure.

graph LR
    App[Application] --> Dir[(📋 Directory
Lookup Table)] Dir --> |"user_id 1-500K"| S1[Shard 1] Dir --> |"user_id 500K-800K"| S2[Shard 2] Dir --> |"user_id 800K-2M"| S3[Shard 3] Dir --> |"VIP users"| S4[Shard VIP] style App fill:#e94560,stroke:#fff,color:#fff style Dir fill:#2c3e50,stroke:#fff,color:#fff style S1 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style S2 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style S3 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style S4 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50

Directory-based Sharding cho phép ánh xạ linh hoạt, kể cả logic đặc biệt (VIP shard)

Directory-based thường kết hợp với cache (Redis, Memcached) để giảm latency lookup. Khi cần di chuyển dữ liệu giữa các shard, chỉ cần cập nhật directory — không cần rehash.

2.4 Geographic Sharding

Phân bổ dữ liệu theo vị trí địa lý của user — dữ liệu ở gần nơi nó được truy cập nhất. Đặc biệt quan trọng cho ứng dụng global và yêu cầu data residency (GDPR, PDPA).

Chiến lượcPhân bổ đềuRange QueryReshardingUse case tiêu biểu
Hash-based⭐⭐⭐⭐⭐User data, session store
Range-based⭐⭐⭐⭐⭐⭐Time-series, log data
Directory-based⭐⭐⭐⭐⭐⭐⭐⭐Multi-tenant SaaS
Geographic⭐⭐⭐⭐⭐⭐Global app, data residency

3. Consistent Hashing & Virtual Nodes

Vấn đề lớn nhất của hash-based sharding đơn giản (hash(key) % N) là khi thêm hoặc bớt shard, hầu hết key phải được di chuyển (rehash). Với N = 4 shard thêm 1 shard thành N = 5, khoảng 80% dữ liệu phải migrate — không chấp nhận được ở production.

Consistent Hashing giải quyết vấn đề này bằng cách tổ chức hash space thành một vòng tròn (hash ring). Mỗi shard chiếm một vị trí trên ring, và mỗi key được map đến shard gần nhất theo chiều kim đồng hồ.

graph TB
    subgraph Ring["🔵 Hash Ring (0 → 2³²)"]
        direction TB
        N1["🟢 Node A
position: 0°"] N2["🔴 Node B
position: 90°"] N3["🟡 Node C
position: 180°"] N4["🟣 Node D
position: 270°"] end K1["Key X → hash: 45°"] -.->|"nearest clockwise"| N2 K2["Key Y → hash: 200°"] -.->|"nearest clockwise"| N4 K3["Key Z → hash: 350°"] -.->|"nearest clockwise"| N1 style N1 fill:#4CAF50,stroke:#fff,color:#fff style N2 fill:#e94560,stroke:#fff,color:#fff style N3 fill:#ff9800,stroke:#fff,color:#fff style N4 fill:#9c27b0,stroke:#fff,color:#fff style K1 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style K2 fill:#f8f9fa,stroke:#9c27b0,color:#2c3e50 style K3 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50

Consistent Hashing Ring — thêm/bớt node chỉ ảnh hưởng key trên node lân cận

Khi thêm Node E vào vị trí 135°, chỉ các key trong khoảng (90°, 135°] phải di chuyển từ Node C sang Node E — chỉ ~25% dữ liệu của 1 node, thay vì 80% toàn hệ thống.

Virtual Nodes (VNodes)

Trong thực tế, chỉ 4-5 node vật lý trên ring sẽ tạo phân bổ không đều. Virtual Nodes giải quyết bằng cách mỗi node vật lý đảm nhận nhiều vị trí trên ring:

-- Node A có 3 virtual nodes tại vị trí: 0°, 120°, 240°
-- Node B có 3 virtual nodes tại vị trí: 40°, 160°, 280°
-- → Phân bổ đều hơn rất nhiều

-- Khi Node A bị remove:
-- Dữ liệu tại 0° → chuyển sang virtual node gần nhất (Node B tại 40°)
-- Dữ liệu tại 120° → chuyển sang Node C tại 160°
-- Dữ liệu tại 240° → chuyển sang Node B tại 280°
-- → Load được phân tán đều cho các node còn lại

💡 Thực tiễn

Cassandra sử dụng 256 virtual nodes mặc định cho mỗi node vật lý. DynamoDB của Amazon cũng dùng consistent hashing với virtual nodes cho partition management. Khi thiết kế mới, khuyến nghị bắt đầu với 128-256 VNodes/node để đảm bảo phân bổ đều ngay từ đầu.

4. Nghệ thuật chọn Shard Key

Shard key là quyết định quan trọng nhất khi thiết kế sharding — chọn sai sẽ tạo hotspot, cross-shard query tràn lan, và việc sửa lại cực kỳ tốn kém. Dưới đây là các tiêu chí cốt lõi:

4.1 High Cardinality

Shard key phải có nhiều giá trị phân biệt. user_id (hàng triệu giá trị) tốt hơn country_code (chỉ ~200 giá trị). Cardinality thấp nghĩa là dữ liệu sẽ tập trung vào ít shard.

4.2 Even Distribution

Dữ liệu phải phân bổ đều giữa các shard. Ví dụ created_date là shard key xấu cho range-based vì dữ liệu mới luôn dồn vào shard cuối.

4.3 Query Pattern Alignment

Shard key nên match với WHERE clause phổ biến nhất. Nếu 90% query filter theo tenant_id, đó nên là shard key — mỗi query chỉ hit 1 shard.

4.4 Stability (Immutable)

Shard key không nên thay đổi. Nếu user đổi email và email là shard key, row phải migrate sang shard khác — tốn kém và dễ lỗi.

Shard KeyCardinalityDistributionStabilityĐánh giá
user_idRất caoĐều (hash)Không đổi⭐ Tốt nhất cho multi-user app
tenant_idTrung bìnhCó thể lệchKhông đổi⭐ Tốt cho SaaS, cần monitor size
order_idRất caoĐềuKhông đổi⭐ Tốt cho e-commerce
created_atCaoLệch (mới = nóng)Không đổi⚠️ Chỉ tốt cho archive data
countryThấp (~200)Rất lệchCó thể đổi❌ Tránh dùng một mình
emailCaoĐềuCó thể đổi❌ Không ổn định

⚠️ Compound Shard Key

Khi không có shard key đơn nào hoàn hảo, dùng compound key. Ví dụ: (tenant_id, user_id) đảm bảo dữ liệu cùng tenant nằm trên cùng shard (tốt cho isolation), đồng thời hash trên user_id để phân bổ đều trong tenant lớn.

5. Thách thức khi Sharding

5.1 Cross-Shard Queries

Query cần dữ liệu từ nhiều shard là thách thức lớn nhất. Ví dụ: SELECT * FROM orders WHERE product_id = 42 khi shard key là user_id — phải scatter query đến TẤT CẢ shard rồi gather kết quả.

sequenceDiagram
    participant App as Application
    participant Router as Shard Router
    participant S1 as Shard 1
    participant S2 as Shard 2
    participant S3 as Shard 3

    App->>Router: SELECT * FROM orders WHERE product_id = 42
    Router->>S1: Forward query
    Router->>S2: Forward query
    Router->>S3: Forward query
    S1-->>Router: 15 rows
    S2-->>Router: 8 rows
    S3-->>Router: 22 rows
    Router->>Router: Merge + Sort + Limit
    Router-->>App: Final result (45 rows)

Scatter-Gather pattern — cross-shard query phải fan out đến tất cả shard

Giải pháp: Thiết kế shard key theo query pattern chính. Nếu cần query theo nhiều dimension, dùng denormalization (duplicate data across shard) hoặc secondary index shard riêng.

5.2 Distributed Transactions

Transaction span nhiều shard cần giao thức phân tán như 2-Phase Commit (2PC) — chậm và phức tạp. Ưu tiên thiết kế sao cho transaction chỉ nằm trong 1 shard.

-- ❌ Cross-shard transaction (chậm, phức tạp)
BEGIN DISTRIBUTED TRANSACTION
  UPDATE shard_1.accounts SET balance = balance - 100 WHERE user_id = 1;
  UPDATE shard_3.accounts SET balance = balance + 100 WHERE user_id = 999;
COMMIT

-- ✅ Thiết kế tốt hơn: dùng Saga pattern
-- Step 1: Debit user 1 (shard 1) → emit event
-- Step 2: Credit user 999 (shard 3) → consume event
-- Step 3: Nếu step 2 fail → compensating transaction ở shard 1

5.3 Resharding (Rebalancing)

Khi dữ liệu tăng và cần thêm shard, việc di chuyển dữ liệu giữa các shard (resharding) cực kỳ tốn kém. Các bước thường gặp:

  1. Double-write: Ghi song song vào cả shard cũ và mới
  2. Backfill: Copy dữ liệu lịch sử từ shard cũ sang shard mới
  3. Verify: So sánh checksum giữa 2 bên
  4. Cutover: Chuyển traffic sang schema shard mới
  5. Cleanup: Xóa dữ liệu dư thừa ở shard cũ

✅ Pre-splitting

Nếu hôm nay cần 3 shard, hãy bắt đầu với 4 (power of 2). Consistent hashing hoạt động tốt hơn khi số node là lũy thừa 2. Nhiều team bắt đầu với 8-16 logical shard dù chỉ cần 2-3 physical server — mỗi server host nhiều logical shard, khi cần scale chỉ cần di chuyển logical shard sang server mới.

5.4 Unique Constraints Across Shards

Đảm bảo UNIQUE constraint toàn cục (ví dụ: email unique trên tất cả shard) không thể dùng database constraint đơn giản. Giải pháp:

  • Separate uniqueness table: Một bảng nhỏ centralized chỉ chứa (email → shard_id)
  • Globally unique ID: Snowflake ID, UUID, hoặc ULID encode shard info vào ID
  • Application-level check: Query trước khi insert, chấp nhận race condition hiếm

6. So sánh giải pháp: Vitess, Citus, CockroachDB, TiDB

Năm 2026, có 4 giải pháp sharding nổi bật, mỗi cái phục vụ use case khác nhau:

Tiêu chíVitessCitusCockroachDBTiDB
Nền tảngMySQL middlewarePostgreSQL extensionCustom (Postgres-compatible)Custom (MySQL-compatible)
Sharding modelExplicit (manual key)Distributed tablesAutomatic range-basedAutomatic (Region-based)
ReshardingManual, onlineRebalancer toolTự độngTự động (split/merge)
Cross-shard queryHạn chếTốtRất tốtRất tốt
ConsistencyPer-shard (MySQL)Per-node (Postgres)Strong (Raft)Strong (Raft)
HTAPKhôngColumnar addonKhôngCó (TiFlash)
Triển khaiKubernetes-nativeManaged + self-hostManaged + self-hostManaged + self-host
Xuất phátYouTube/GoogleMicrosoft (Azure)Cockroach LabsPingCAP
Best forMySQL scale-out có kiểm soátPostgres + analyticsMulti-region, Postgres-firstMySQL-first, HTAP workload
graph TB
    subgraph Decision["🤔 Chọn giải pháp nào?"]
        Start{Database hiện tại?}
        Start -->|MySQL| MySQL_Q{Cần auto-sharding?}
        Start -->|PostgreSQL| PG_Q{Cần multi-region?}
        Start -->|Mới hoàn toàn| New_Q{Ưu tiên gì?}

        MySQL_Q -->|Có| TiDB_R[✅ TiDB]
        MySQL_Q -->|Không, muốn kiểm soát| Vitess_R[✅ Vitess]

        PG_Q -->|Có| CRDB_R[✅ CockroachDB]
        PG_Q -->|Không| Citus_R[✅ Citus]

        New_Q -->|HTAP| TiDB_R2[✅ TiDB]
        New_Q -->|Multi-region| CRDB_R2[✅ CockroachDB]
        New_Q -->|Postgres ecosystem| Citus_R2[✅ Citus]
    end

    style Start fill:#e94560,stroke:#fff,color:#fff
    style MySQL_Q fill:#2c3e50,stroke:#fff,color:#fff
    style PG_Q fill:#2c3e50,stroke:#fff,color:#fff
    style New_Q fill:#2c3e50,stroke:#fff,color:#fff
    style TiDB_R fill:#4CAF50,stroke:#fff,color:#fff
    style TiDB_R2 fill:#4CAF50,stroke:#fff,color:#fff
    style Vitess_R fill:#4CAF50,stroke:#fff,color:#fff
    style CRDB_R fill:#4CAF50,stroke:#fff,color:#fff
    style CRDB_R2 fill:#4CAF50,stroke:#fff,color:#fff
    style Citus_R fill:#4CAF50,stroke:#fff,color:#fff
    style Citus_R2 fill:#4CAF50,stroke:#fff,color:#fff

Decision tree chọn giải pháp sharding phù hợp

Case study: Ninja Van chọn TiDB thay vì Vitess

Ninja Van — nền tảng logistics Đông Nam Á xử lý hàng triệu đơn hàng/ngày — đã chuyển từ MySQL sang TiDB thay vì chọn Vitess hoặc CockroachDB. Lý do chính:

  • TiDB tương thích MySQL protocol → migration cost thấp
  • Auto-sharding không cần define shard key thủ công
  • TiFlash cho phép chạy analytics trực tiếp trên operational data (HTAP)
  • Horizontal scale trên Kubernetes không cần resharding thủ công

7. Khi nào KHÔNG nên Sharding?

Sharding thêm complexity đáng kể. Trước khi shard, hãy chắc chắn đã thử hết các giải pháp đơn giản hơn:

Bước 1: Query Optimization
Index đúng, rewrite slow query, sử dụng EXPLAIN ANALYZE. Đa số bottleneck DB là do query tệ, không phải thiếu hardware.
Bước 2: Read Replicas
80% app là read-heavy. Thêm read replica có thể tăng throughput 3-5x mà không cần sharding. Hầu hết managed database (RDS, Cloud SQL) hỗ trợ sẵn.
Bước 3: Caching Layer
Redis/Memcached trước DB giảm 70-90% read traffic. Kết hợp CDN cho static content. Chi phí thấp, triển khai nhanh.
Bước 4: Vertical Scaling
Nâng cấp instance lên 64 vCPU, 256GB RAM, NVMe SSD. Cloud provider cho phép scale up trong vài phút. PostgreSQL trên hardware tốt xử lý được hàng triệu row/giây.
Bước 5: Table Partitioning
Partitioning TRONG cùng 1 database (không phải sharding). PostgreSQL native partitioning hoặc MySQL partition by range/hash — đơn giản hơn sharding nhiều.
Bước 6: Sharding
Chỉ khi TẤT CẢ các bước trên không đủ: single-node DB thực sự không đáp ứng được write throughput, storage vượt TB-level, hoặc yêu cầu multi-region data locality.

⚠️ Sharding Premature là Anti-Pattern

Nhiều team shard quá sớm vì "phòng xa". Thực tế: một PostgreSQL single-node trên hardware hiện đại (2026) xử lý tốt tới vài TB dữ liệuhàng chục nghìn transaction/giây. Sharding khi chưa cần tạo ra overhead về operational complexity mà không mang lại giá trị.

8. Kết luận

Database Sharding là công cụ mạnh mẽ nhưng đi kèm trade-off lớn về complexity. Hãy nhớ:

  • Hash-based là chiến lược mặc định tốt nhất cho phần lớn use case
  • Consistent Hashing với Virtual Nodes giải quyết bài toán resharding
  • Shard Key phải có high cardinality, stable, và match query pattern chính
  • Cross-shard query luôn đắt — thiết kế schema để tối thiểu chúng
  • Giải pháp hiện đại như TiDB, CockroachDB cung cấp auto-sharding, giảm gánh nặng vận hành
  • Luôn thử query optimization → read replica → caching → vertical scaling trước khi nghĩ đến sharding

Trong thời đại distributed systems 2026, hiểu sharding không chỉ để áp dụng — mà còn để biết khi nào không cần nó. Đó mới là dấu hiệu của một kiến trúc sư hệ thống trưởng thành.

Nguồn tham khảo: