
Sharding là lựa chọn cuối cùng để mở rộng quy mô cơ sở dữ liệu. Bạn không sharding vì bạn muốn; bạn sharding vì bộ ghi chính của bạn đã bão hòa, việc mở rộng theo chiều dọc đã đạt đến giới hạn vật lý (hoặc giới hạn hóa đơn AWS), và các bản sao chỉ đọc không thể giải quyết các nút thắt cổ chai về thông lượng ghi.
Khi bạn vượt qua ngưỡng đó, bạn đánh đổi các đảm bảo quan hệ của một nút đơn (giao dịch ACID, khóa ngoại liên bảng, ràng buộc duy nhất toàn cục) để lấy khả năng mở rộng theo chiều ngang. Hướng dẫn này sẽ trình bày cách các hệ thống hiện đại thực sự sharding các cơ sở dữ liệu quan hệ, cách chọn khóa sharding, và cách xử lý các chế độ lỗi của hệ thống phân tán sau đó.
Analyze queries for sargability, missing composite indexes & offsets
Analyze SQL queries for performance anti-patterns, wildcards, and slow offsets. Generates optimized queries and DDL composite indexes for PostgreSQL, MySQL, and SQLite.
Trước khi chia cơ sở dữ liệu của bạn thành các shard, hãy đảm bảo bạn đã thực hiện hết các bước trung gian sau:
Scaling Stages:
1. Index Optimization & Slow Query Tuning (EXPLAIN ANALYZE)
│ (hit CPU/IOPS limits)
▼
2. Read/Write Splitting (Primary for writes, Replicas for reads)
│ (write throughput saturates primary node)
▼
3. Table Partitioning (PostgreSQL declarative partitioning / MySQL partition)
│ (single machine disk/memory/lock contention saturated)
▼
4. Functional Partitioning (Extract Auth, Billing, Analytics to isolated DBs)
│ (a single domain table like `orders` or `events` exceeds 1 node capacity)
▼
5. Horizontal Sharding (Split identical table schema across N database nodes)
Nếu một bảng đơn vượt quá 500 triệu hàng, các thao tác ghi tạo ra tranh chấp khóa liên tục, và độ trễ sao chép WAL/redo tăng nhanh hơn khả năng tiếp nhận của các bản sao, bạn cần sharding theo chiều ngang.
Khóa Sharding xác định shard cơ sở dữ liệu nào chứa một hàng nhất định. Chọn sai khóa sẽ dẫn đến các điểm nóng, các truy vấn scatter-gather liên shard, và những cuộc di chuyển ác mộng.
Các hàng được định tuyến dựa trên các phạm vi giá trị liên tục (ví dụ: created_at phạm vi ngày hoặc phạm vi ID 1–1,000,000 -> Shard 1, 1,000,001–2,000,000 -> Shard 2).
Chuyển khóa sharding qua một hàm băm mật mã hoặc hàm băm phi mật mã nhanh (như MurmurHash3 hoặc xxHash) modulo số lượng shard, hoặc ánh xạ lên một vòng băm nhất quán với các nút ảo:
\text{Shard ID} = \text{hash}(\text{sharding\_key}) \pmod N
import mmh3
class ShardRouter:
def __init__(self, shard_count: int):
self.shard_count = shard_count
def get_shard_id(self, entity_id: str) -> int:
# MurmurHash3 ensures uniform distribution across shards
hash_val = mmh3.hash(str(entity_id))
return abs(hash_val) % self.shard_count
router = ShardRouter(shard_count=8)
print(router.get_shard_id("user_98314")) # -> Shard 3
print(router.get_shard_id("user_98315")) # -> Shard 7
WHERE created_at BETWEEN x AND y) phải phát sóng tới tất cả các shard (scatter-gather).Một dịch vụ tra cứu trung tâm, được lưu vào bộ nhớ đệm cao (ví dụ: Redis + cơ sở dữ liệu siêu dữ liệu) ánh xạ entity_id tới shard_id.
Trong sharding cấp ứng dụng, lớp dịch vụ của bạn quản lý các nhóm kết nối cho mỗi shard và định tuyến các truy vấn dựa trên ngữ cảnh:
import asyncpg
from typing import Dict, Any
class ShardedDatabaseManager:
def __init__(self, shard_configs: Dict[int, str]):
self.configs = shard_configs
self.pools: Dict[int, asyncpg.Pool] = {}
async def initialize(self):
for shard_id, dsn in self.configs.items():
self.pools[shard_id] = await asyncpg.create_pool(
dsn, min_size=5, max_size=20, timeout=10.0
)
def get_shard_for_tenant(self, tenant_id: str) -> int:
import hashlib
h = int(hashlib.md5(tenant_id.encode()).hexdigest(), 16)
return h % len(self.pools)
async def execute_query(
self, tenant_id: str, query: str, *args
) -> list[Dict[str, Any]]:
shard_id = self.get_shard_for_tenant(tenant_id)
pool = self.pools[shard_id]
async with pool.acquire() as conn:
records = await conn.fetch(query, *args)
return [dict(r) for r in records]
async def scatter_gather(self, query: str, *args) -> list[Dict[str, Any]]:
"""Broadcast read query to all shards concurrently and aggregate results."""
import asyncio
async def query_shard(shard_id: int, pool: asyncpg.Pool):
async with pool.acquire() as conn:
return await conn.fetch(query, *args)
tasks = [query_shard(sid, pool) for sid, pool in self.pools.items()]
results = await asyncio.gather(*tasks, return_exceptions=False)
flat = []
for r in results:
flat.extend([dict(row) for row in r])
return flat
Bạn không còn có thể dựa vào AUTO_INCREMENT hoặc các chuỗi BIGSERIAL của PostgreSQL vì mỗi shard sẽ tạo ra các khóa chính trùng lặp.
Giải pháp:
[Timestamp (41 bits) | Worker ID (10 bits) | Sequence (12 bits)]. Tạo ra hàng triệu ID duy nhất có thể sắp xếp mỗi giây mà không cần khóa cơ sở dữ liệu.Snowflake 64-Bit Structure:
[ 1 bit sign ] [ 41 bits timestamp (ms) ] [ 10 bits machine/shard ID ] [ 12 bits sequence ]
Nếu orders được sharding bởi tenant_id và products là toàn cục, việc kết nối chúng trực tiếp trong SQL trên các máy chủ vật lý khác nhau là không thể.
users, orders, và order_items đều chứa tenant_id làm khóa sharding của chúng, các truy vấn giới hạn trong một tenant_id duy nhất sẽ thực hiện dưới dạng các kết nối cục bộ 100% trên shard đó.currencies, countries, plan_tiers) được sao chép vào mọi shard. Các cập nhật được phát sóng đến tất cả các nút thông qua Change Data Capture (Debezium/Kafka).orders từ Shard A, tìm nạp product_ids duy nhất, thực hiện truy vấn hàng loạt SELECT * FROM products WHERE id = ANY(...) trên cơ sở dữ liệu danh mục, và ghép các DTO lại với nhau trong dịch vụ backend.Khi một hoạt động kinh doanh chạm đến hai shard khác nhau (ví dụ: chuyển tiền từ Tài khoản trên Shard 1 sang Tài khoản trên Shard 2), BEGIN ... COMMIT tiêu chuẩn không thể đảm bảo tính nguyên tử.
Một điều phối viên giao dịch hỏi tất cả các shard tham gia: "Bạn có thể commit không?" (Giai đoạn Chuẩn bị). Nếu tất cả trả lời CÓ, điều phối viên ghi lại commit và đưa ra "Commit!" (Giai đoạn Commit).
Thay thế các khóa phân tán đồng bộ bằng các giao dịch bù trừ không đồng bộ:
1. [Shard 1] Deduct $100 from Account A -> Write "MoneyDebited" event to local `outbox` table (Atomic local commit)
2. CDC worker reads `outbox` -> Publishes to Kafka
3. Worker reads Kafka -> [Shard 2] Credit $100 to Account B
4. If Shard 2 fails permanently -> Emit "CreditFailed" event -> Compensating transaction on Shard 1 to refund $100 to Account A
Thay vì duy trì các lớp định tuyến tùy chỉnh trong mã ứng dụng của bạn, các kiến trúc hiện đại thường dựa vào các lớp proxy hoặc lưu trữ phân tán:
| Phương pháp | Ví dụ | Cách hoạt động | Tốt nhất cho |
|---|---|---|---|
| Proxy trong suốt | Vitess (MySQL), Citus (Postgres) | Nằm giữa ứng dụng và MySQL/Postgres tiêu chuẩn. Ứng dụng nói SQL tiêu chuẩn; proxy phân tích AST và định tuyến các shard. | Mở rộng các cơ sở dữ liệu quan hệ nguyên khối hiện có mà không cần viết lại SQL ứng dụng. |
| SQL phân tán (Gốc) | CockroachDB, TiDB, Google Spanner | Công cụ lưu trữ được phân tán gốc (đồng thuận Raft + cây LSM/Pebble). Các shard (phạm vi) tự động chia tách và cân bằng lại. | Các hệ thống cloud-native mới cần ghi theo chiều ngang với các giao dịch ACID tiêu chuẩn. |
| Sharding cấp ứng dụng | Custom Router / Hibernate Shards / SQLAlchemy Routing | Ứng dụng chọn chuỗi kết nối một cách rõ ràng dựa trên ID thực thể. | Thông lượng cực cao mà chi phí độ trễ của proxy (1–2ms) là không thể chấp nhận được. |
Đến một lúc nào đó, 8 shard của bạn sẽ hết dung lượng và bạn phải mở rộng lên 16 shard mà không làm gián đoạn nền tảng:
Một nền tảng thương mại điện tử đang phát triển nhanh chóng gặp phải tình trạng tắc nghẽn thông lượng ghi (write throughput) trên cơ sở dữ liệu chính của mình. Họ đã tối ưu hóa các truy vấn, triển khai các bản sao đọc (read replicas) và phân vùng các bảng lớn. Bảng `orders`, vốn là trung tâm của hoạt động kinh doanh, hiện đã vượt quá 500 triệu hàng và gây ra tranh chấp khóa (lock contention) liên tục. Chiến lược sharding nào phù hợp nhất để giải quyết vấn đề thông lượng ghi ngay lập tức đồng thời giảm thiểu rủi ro phát sinh các điểm nóng ghi (write hotspots) mới?
Làm sạch log AI CLI, tạo biểu thức cron, giải mã JWT và tính quyền chmod offline.

Hướng dẫn toàn diện về postgresql change data capture (cdc): debezium, kafka connect & transactional outbox với kiến trúc cấp độ production và ví dụ code.
Đọc tiếp
Hướng dẫn kiến trúc chuyên sâu về kết nối PostgreSQL: PgCat so với PgBouncer so với Supavisor dưới tải cao với các ví dụ thực tế đã được kiểm chứng.
Đọc tiếp
Hướng dẫn toàn diện về kiến trúc & tinh chỉnh pgbouncer: transaction pooling, prepared statements & session overhead với kiến trúc cấp độ sản xuất và ví dụ mã.
Đọc tiếp