Điều kiện tiên quyết: Đây là Phần 4 của Khóa Học System Design. Lội lại Phần 3: Chiến Lược Caching để ngấm mớ luật lệ tầng cache trước khi mò xuống vũng lầy lưu trữ (storage).
Answer-first: Đập vụn DB (Database sharding) là màn phân phát dữ liệu dạt sang hai bên sườn (horizontally) rải đều ra mấy cái mảnh vỡ độc lập (independent partitions/shards) bám theo một cái chìa khóa dẫn đường (shard key), cốt để xoa dịu cái màn dẫm đạp chửi bới nhau lúc ghi dữ liệu (reducing write contention) và mở đường cho dung lượng phình to tỷ lệ thuận đâm lủng trần nhà (linear storage growth). Chấm mút chọn sai cái shard key là đẻ ra ngay mấy cái ổ kiến lửa (hot spots) lòi le, hậu quả còn bết bát thảm khốc hơn cả việc chẳng thèm sharding.
Bơm Dọc (Vertical) vs Bơm Ngang (Horizontal) — Bao Giờ Mới Quay Xe?
Answer-first: Bơm Dọc (Vertical scaling - scale-up) là nhét thêm thuốc lắc, đắp thêm tạ (resources) đè đầu đúng 1 con server đơn côi — dễ ăn dễ trúng thưởng nhưng lại đụng ngay cái nóc nhà vật lý cứng ngắc (hard physical ceiling) và cái chi phí hóa đơn cắm đầu nhảy vọt phi tuyến tính (non-linear cost growth). Bơm Ngang (Horizontal scaling - scale-out) là vác tiền đi mua thêm cả mớ server ráp vô — chẳng sợ đụng trần (no theoretical ceiling), chi phí nhích đều đều tà tà (linear cost), ngặt nỗi cái giá phải trả là chuốc lấy mớ bòng bong cực nhọc điên rồ lúc vận hành (significantly higher operational complexity).
Nấc Thang Tiết Độ (Scaling Migration Ladder)
| Giai Đoạn | Thuốc Giải (Solution) | Rác Dữ Liệu Cỡ (Data Size) | Mức Bắn Tỉa (Write Throughput) | Độ Bầm Dập (Complexity) |
|---|---|---|---|---|
| 1 | Móc 1 cục DB + nắn bóp pool (pool tuning) | < 500 GB | < 5k QPS | Quá Mềm (Very Low) |
| 2 | Mướn lính đọc ké (Read Replicas - leader-follower) | 500 GB–2 TB | < 20k QPS (rặt đọc - read-heavy) | Mềm (Low) |
| 3 | Chặt khúc Table (Table Partitioning) | 1–10 TB | < 50k QPS | Vừa (Medium) |
| 4 | Đập vụn Sharding cấp Application | > 5 TB | > 50k QPS | Chua (High) |
| 5 | Quất NewSQL (TiDB, Spanner) | chẳng có nóc (Unlimited) | > 100k QPS | Vừa (Có hầu gái lo hết - managed) |
[!IMPORTANT] Đừng có lanh chanh đi shard quá sớm (Don’t shard prematurely). Hồi xửa hồi xưa Shopee cũng lóc cóc bắt đầu với chóc 1 cục MySQL trơ trọi. Kẻ khổng lồ Netflix cũng hì hục cõng hàng triệu miệng ăn nhét chung 1 cục Oracle DB trước khi khăn gói dọn nhà. Cầm đèn chạy trước ô tô đú đởn sharding sớm mọc ra nguyên cái đống bùi nhùi truy vấn leo rào (cross-shard query complexity) bóp chết sạch bách năng suất code của đám thợ đụng, trong khi cái traffic ngoài đời chả thấm tháp tới mức khát khao tới vậy.
So Găng B-Tree vs LSM-Tree — Nội Soi Bụng Dạ Đám Động Cơ Lưu Trữ (Storage Engine Internals)
Answer-first: Cây B-Tree (Nhà InnoDB, PostgreSQL) được đẻ ra cưng nựng mớ công chuyện thèm đọc khát nước (read-heavy workloads) ngắm chuẩn xác bắn tỉa từng hột (point lookups) với độ trễ thấp là là mặt đất. Cây LSM-Tree (Nhà RocksDB, Cassandra, TiKV) lại sinh ra để vác cái cày bừa mớ tạp âm đục khoét (write-heavy workloads) bằng chiêu trò gom mẻ ngâm trong RAM (buffering writes in memory) trước khi xả van tống hàng loạt xếp hàng ngay ngắn (sequentially) xuống đĩa cứng (disk).
B-Tree (Đám InnoDB, PostgreSQL Heap)
graph TD
Root["Củ (Root Node) [50 | 100]"] --> L1["Ruột (Internal) [10 | 30]"]
Root --> L2["Ruột [60 | 80]"]
Root --> L3["Ruột [110 | 150]"]
L1 --> Leaf1["Lá (Leaf) [1,5,8,10] →"]
L1 --> Leaf2["Lá [15,20,25,30] →"]
L2 --> Leaf3["Lá [55,60,65,70] →"]
style Root fill:#cce5ff,stroke:#004085
style Leaf1 fill:#d4edda,stroke:#28a745
style Leaf2 fill:#d4edda,stroke:#28a745
style Leaf3 fill:#d4edda,stroke:#28a745
- Móc ruột tìm hột (Point lookup): O(log N) — trượt phát từ củ tới lá.
- Quét rác ngang (Range scan): Trơn tuột (Efficient) — mớ lá rễ ngoắc nhau chằng chịt bằng xích kép (doubly-linked).
- Phình to nứt nẻ lúc đút đồ vô (Write amplification): Tổ bố (High) — mỗi bận nhét vô (INSERT) lỡ bực mình là xé nát trang (page splits) bắt xoay xoay xếp xếp lại từ đầu (rebalancing).
- Vết thương hở (Problem): Cái nết nhét đồ vô đầu đàng ngõ xóm (Random writes - chèn ngang lưng chừng một cái range) quật ngược cái ổ đĩa gào khóc (IO amplification) vì dính ngay bài ghi đĩa lổm chổm đứt đoạn (non-sequential disk writes).
LSM-Tree (Đám RocksDB, Cassandra, TiKV)
Cứ ghi là ném tọt vô cái lồng ấp RAM MemTable (đã xếp thẳng thớm - sorted) ôm ấp trước, bao giờ ứ họng bự chảng thì tuồn xả xuống đống SSTables nguội ngắt đóng băng (immutable) dưới ổ cứng. Đám lâu la đi dọn dẹp quét rác lầm lụi ngầm (Background compaction) lôi mấy cục SSTables ra nấu cháo gộp chung (merges):
graph LR
Write([Nhét Vô]) --> MT["MemTable\n(Ngâm RAM, Chỉnh Tề Sorted)"]
MT -->|"Đầy Bụng Thì Ọi (Flush)"| L0["L0 SSTables\n(Vứt lộn xộn, đè lên nhau overlapping)"]
L0 -->|Nấu Cháo Dọn Rác (Compaction)| L1["L1 SSTables\n(Xếp gọn gàng, chia đất rõ ràng)"]
L1 -->|Nấu Cháo Tiếp (Compaction)| L2["L2 SSTables\n(Phình to gấp 10)"]
Read([Móc Ra]) --> MT
Read --> L0
Read --> L1
style MT fill:#fff3cd,stroke:#f0a500
- Màn Ghi (Write): Gắn đuôi vào chóp cắm đầu chạy tuồn tuột tít tắp (Purely sequential append) → tốc độ bàn thờ, dẹp loạn mấy vụ random IO.
- Gánh Nặng Mò Mẫm (Read amplification): Lục lọi mớ rác qua 9 tầng địa ngục SSTable levels — may mà có bùa Bloom Filters ra cản mũi xoa dịu (mitigated).
- Hóa đơn nấu cháo (Compaction overhead): Tiêu xài phung phí CPU ngầm với IO chắp vá lúc dọn dẹp trộn mớ levels.
Quẹo Lựa Hốt Đứa Nào (When to Choose Each)
| Bãi Chiến Trường (Workload) | Lò Bát Quái (Storage Engine) | Tiếng Lòng (Reason) |
|---|---|---|
| Lò nổ chốt đơn OLTP (orders, payments) | B-Tree (PostgreSQL/MySQL InnoDB) | Chọt một phát lủng màng nhĩ độ trễ thấp (Low-latency point lookups) |
| Ngồi thiền ghi nhật ký (Time-series - metrics, logs) | LSM (Cassandra, ClickHouse) | Cắm đầu chép kinh luồn một hàng vèo vèo (Sequential write-heavy) |
| Thập Cẩm OLTP + OLAP | TiDB (TiKV = LSM kẹp thêm rễ phụ B-Tree - secondary indexes) | NewSQL hốt trọn ổ (best of both) |
| Tống chìa-khóa nã liên thanh (High-throughput key-value) | RocksDB / BadgerDB | Thuật dập rác của LSM (LSM write optimization) |
Tuyển Lựa Chọn Lọc Chìa Khóa (Shard Key) Sao Cho Êm Thắm
Answer-first: Một cái shard key được việc (good shard key) BẮT BUỘC phải ôm trọn 3 món: (1) Mức chênh lệch bao bự (high cardinality) — lòi ra hằng hà sa số các mặt mũi khác nhau (unique values) để rắc cho đều (even distribution); (2) San bằng chiến tuyến mớ ghi chép (write distribution) — cấm tiệt vụ chui rúc bu xúm lại đúng một chỗ (no single value receives disproportionate writes - né xa mấy cái chìa khóa tính bằng giờ giấc thời gian); (3) Cục bộ bấu víu xoi mói (query locality) — mấy cái màn chọc móc sờ mó (common queries) hay xài rờ một phát là trúng phóc đúng 1 hố (one shard).
3 Binh Pháp Chia Năm Xẻ Bảy (Three Partitioning Strategies)
1. Băm Nát Theo Khoảng (Range Partitioning) — Ngai vàng cho mớ rác thời gian (time-series)
-- Dân chơi PostgreSQL xẻ bảng theo khúc thời gian (Range Partitioning by time - sinh ra để hầu đám time-series)
CREATE TABLE transaction_log (
id UUID NOT NULL,
user_id BIGINT NOT NULL,
amount NUMERIC(15, 2) NOT NULL,
status VARCHAR(50) NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (id, created_at) -- Thằng created_at cắn răng chịu trận phải lọt vào PK để xài cho partitioned tables
) PARTITION BY RANGE (created_at);
CREATE TABLE transaction_log_2026_06 PARTITION OF transaction_log
FOR VALUES FROM ('2026-06-01 00:00:00+00') TO ('2026-07-01 00:00:00+00');
CREATE TABLE transaction_log_2026_07 PARTITION OF transaction_log
FOR VALUES FROM ('2026-07-01 00:00:00+00') TO ('2026-08-01 00:00:00+00');
-- Chỉ mục chui lủi bó gối (Partition-local index) — vỏn vẹn lăm le khoanh vùng mớ rác riêng rẽ của 1 bãi partition
CREATE INDEX idx_txn_log_user_2026_06
ON transaction_log_2026_06 (user_id, created_at DESC);
Ăn điểm (Advantage): Múa búa DROP vứt xó bay luôn nguyên cụm partition xẻo mớ rác già cỗi cái rụp (instantly delete old data - chẳng thèm rảnh nợ hút bụi vacuum). Ngồi lục lọi xới tung (Queries) chọc đúng một lỗ thời gian nhất định (specific time range) nó chỉ quét (scan) gọn gàng khoanh mớ bãi partition trúng mánh (partition pruning).
Nát bét (Disadvantage): Dính hố rác lòi le (Write hot spot) — toàn bộ mẻ rác mới ra lò chảy ào ào tụm lại vô cái miệng cống partition mới há miệng nhất (most recent partition).
2. Băm Nhừ Theo Bùa (Hash Partitioning) — Rải bão chọc ngoáy nhẵn nhụi (even write distribution)
CREATE TABLE user_events (
id BIGSERIAL,
user_id BIGINT NOT NULL,
event VARCHAR(100),
occurred_at TIMESTAMPTZ DEFAULT NOW()
) PARTITION BY HASH (user_id);
CREATE TABLE user_events_0 PARTITION OF user_events
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE user_events_1 PARTITION OF user_events
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE user_events_2 PARTITION OF user_events
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE user_events_3 PARTITION OF user_events
FOR VALUES WITH (MODULUS 4, REMAINDER 3);
3. Cán Bộ Phân Phối Application-Level (Application-Level Shard Router - Directory-based)
// Trò đẩy rác chia chác ngầm trong Application bằng Go
type ShardRouter struct{}
func (r *ShardRouter) GetShardDSN(userID int64) string {
switch {
case userID < 1_000_000:
return "postgres://shard-1:5432/users"
case userID < 2_000_000:
return "postgres://shard-2:5432/users"
default:
return "postgres://shard-3:5432/users"
}
}
Màn Ảo Thuật TiDB Percolator — Giao Thức Chốt Đơn Phân Tán (Distributed Commit Protocol)
Answer-first: TiDB lôi cái trò Hai Bước Lên Giường (Two-Phase Commit - 2PC) của Percolator đắp lên lưng con TiKV — một vựa khóa-giá trị (key-value) tung tóe (distributed). Mánh này quăng ra một rổ giao dịch ACID đứt gãy tứ tán (distributed ACID transactions) mà êm ru khỏi phiền hà vác loa oang oang dàn xếp gọi đám lính lác ứng dụng (application-level coordination).
Vòng 1 — Nhào Vô Bọc Trụ (Prewrite):
Khách chọn ra một thằng chìa khóa cái (primary key - PK) mâm lôi theo một rổ chìa khóa lẻ (secondary keys)
Khóa mõm cắm sừng (Writes primary lock) nện xuống TiKV rắc thêm miếng nhãn thời gian start_ts (timestamp từ lò PD/TSO)
Táng mớ khóa khóa lẻ đè lên trói vào chân thằng chìa khóa cái (referencing the primary key)
Vòng 2 — Phán Trảm Chốt Đơn (Commit):
Lỡ mà mớ khóa trói gô trước đó (prewrite locks) trót lọt xuôi xèo (succeed):
Đóng dấu mộc thành công (commit record) lên sọ thằng chìa khóa cái kẹp nhãn commit_ts
Đứa con giao dịch (Transaction) được chính thức giáng thế khai sinh là ĐÃ CHỐT ngay chóc cái tích tắc này (committed at this exact moment)
Chạy ngầm đánh du kích (Asynchronously): vác chổi dọn sạch sẽ mớ còng số tám rác rưởi trói đám khóa lẻ (secondary locks)
Nhỡ xui xẻo có hột prewrite nào sập gãy (fails):
Rút quân cuốn cờ quay xe (Roll back) phế hết mớ còng khóa lỡ tay đóng vào (acquired locks)
[!NOTE] Độ lề mề chờ chốt sổ (commit latency) của TiDB lai rai quẩn quanh ~2–5ms so với con số rùa lết ~0.5ms của mẻ MySQL trơ trọi 1 node — này gọi là ân oán tất lẽ phải trả (inherent trade-off) cho trò đú đởn phân tán ACID. Nhà PayPay đành đoạn xách vali rời bỏ 64 bãi rác MySQL cồng kềnh sang nương tựa TiDB và cắn răng nuốt cục tức (accepted) cái màn trễ nải kéo dài này (latency increase) rốt cuộc lại được chắp cánh bù lỗ gỡ gạc bằng một rổ thong dong sảng khoái vứt nhẹ gánh nặng vận hành (operational simplicity gain). Bơm đồ qua lại nhảy hố mớ shard giờ nhẹ tựa lông hồng (transparent). Y xì đúc như cái bài dọn nhà của Lò nổ Alipay OceanBase architecture, nguyên con cỗ máy gửi phận bấu víu (relies on) nương tựa vào mớ bùa đồng thuận phân tán dai dẳng bám sống chết (robust distributed consensus algorithm - Raft/Paxos) để nín thở bảo kê lấy cái tính nhất quán.
Rèn Giũa Tối Ưu Bể Kết Nối Trong Go (Connection Pool Tuning) — database/sql
Answer-first: Cái bể kết nối (connection pool) database/sql bắt buộc phải xào nấu vặn vẹo (configured) sao cho đo ni đóng giày lọt vừa cái dạ dày phình to (capacity) của cục database nhà bạn. Cái hố xí rác rưởi (misconfiguration) thiên hạ khoái nhảy vô nhìu nhất: bỏ quên chẳng thèm chốt số MaxOpenConns (thả rong bứt cương ngầm định vô hạn unlimited), kéo theo cái đám rác ứng dụng (application) điên cuồng phá xích mở tung cả hằng hà sa số lỗ kết nối và dí đầu thằng PostgreSQL ngập ngụa tắt thở (crash).
Mô Hình Kết Nối PostgreSQL vs MySQL
| Góc Cạnh (Property) | PostgreSQL | MySQL |
|---|---|---|
| Khuôn mẫu làm tình (Connection model) | Một chọc đẻ một lính (Process-per-connection - dập khuôn fork on connect) | Một luồng đẻ một vòi (Thread-per-connection) |
| Phí tham quan mỗi phát kết nối (Memory per connection) | 5–10 MB (ngốn vào mảng nhớ ảo mộng virtual memory, rác bám vào thùng chứa chung shared buffers overhead) | Tóp teo cỡ ~1–2 MB |
| Đụng nóc kịch kim kẹt cứng (Practical max connections) | Lẹt đẹt ~100–500 là đã thấy phình bụng (saturates) cạn sạch RAM | Thong thả ~1000–5000 |
| Bể bơi cõng rác bắt buộc (Required pooler) | Vác PgBouncer ra chặn cửa (bắt buộc phải có - mandatory) | Thích thì chêm ProxySQL (optional) |
[!WARNING] Con cưng PostgreSQL: 500 cái ống hút kết nối × 10 MB = Ngốn cái vèo 5 GB RAM mớ này chỉ rớt rụng nuôi báo cô 3 cái đồ quỷ phí bôi trơn kết nối (connection overhead). Đi đêm đi lậu trên đường ray production thì 100% bắt buộc dựng hàng rào PgBouncer cài bằng chế độ giao dịch (transaction mode) giương mắt chực chờ phía trước chóp con PostgreSQL. Thằng PgBouncer dọn dẹp nhồi nhét (multiplexes) hàng tá cả trăm cái mớ cáp cắm lổm chổm của ứng dụng (application connections) bóp ngộp luồn chung đút vô chỉ lèo tèo ~50 cái hố kết nối (actual DB connections) thật sự găm vào DB.
Bùa Chú Tuyệt Đỉnh Rèn Bể Kết Nối (Optimal Connection Pool Configuration) Trong Go
package database
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/lib/pq"
)
type DBConfig struct {
DSN string
MaxOpenConns int
MaxIdleConns int
ConnMaxLifetime time.Duration
ConnMaxIdleTime time.Duration
}
func InitDB(cfg DBConfig) (*sql.DB, error) {
db, err := sql.Open("postgres", cfg.DSN)
if err != nil {
return nil, fmt.Errorf("ăn hành mở cửa thất bại (failed to open db): %w", err)
}
// Điều 1: MaxOpenConns = lựa cục min gặm nhắm(DB max_connections × 0.8, (Số lõi CPU × 2) + mớ vòng xoay đĩa spindles)
// Ví dụ xài mót: DB set max_connections=100 → Tém tém MaxOpenConns=80
db.SetMaxOpenConns(cfg.MaxOpenConns)
// Điều 2: MaxIdleConns ôm dính trọn = MaxOpenConns để khóa mõm khỏi vướng bận xé xé nhét nhét (avoid connection churn)
// Nếu rớt trúng MaxIdleConns < MaxOpenConns, cả rổ cáp chực lố (excess connections) bị đá ra đường tiễn vong (closed) sau cữ mỗi bận nháy request
db.SetMaxIdleConns(cfg.MaxIdleConns)
// Điều 3: ConnMaxLifetime < mớ timeout ngâm giấm idle của DB và cổng chốt hạ timeout của tường lửa firewall
// Mớ con cưng AWS RDS / Cloud SQL ôm khư khư bám cái hạn chót ngâm mốc meo lười nhác 1-hour idle timeout
// Chèn búa chốt số lifetime thành 30 phút rảnh rỗi dọn dẹp tiễn vong kết nối chết trôi chủ động (retire connections proactively)
db.SetConnMaxLifetime(cfg.ConnMaxLifetime)
// Điều 4: ConnMaxIdleTime — xả van đá chóp đống kết nối ỳ ạch đóng bụi (release idle connections) giữa thời buổi đói kém ế khách rảnh rang rác (low traffic)
db.SetConnMaxIdleTime(cfg.ConnMaxIdleTime)
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("cắm đầu húc lỗi không ới ời gọi ping được (failed to ping db): %w", err)
}
log.Printf("DB pool: maxOpen=%d maxIdle=%d lifetime=%v idleTime=%v",
cfg.MaxOpenConns, cfg.MaxIdleConns, cfg.ConnMaxLifetime, cfg.ConnMaxIdleTime)
return db, nil
}
// Bùa Chú Production (ProductionConfig) — đo ni nhét vừa một lò xưởng ăn rác cỡ ~500 RPS
func ProductionConfig(dsn string) DBConfig {
return DBConfig{
DSN: dsn,
MaxOpenConns: 80,
MaxIdleConns: 80,
ConnMaxLifetime: 30 * time.Minute,
ConnMaxIdleTime: 15 * time.Minute,
}
}
[!TIP] Vạch mặt đám bắt trộm ống chui (Detecting connection leaks): Nước sông chảy ròng ròng
db.Stats().WaitCountnhảy số cao chót vót và con maInUse ≈ MaxOpenConns, ấy đích thị là thảm họa chọc lủng bể nước ống xì rác (connection leak) — lũ goroutines ăn no ngủ say rồi bỏ lơ chẳng thèm kêu ớirows.Close()hay rúc gầmdefer tx.Rollback(). Khảm ngọc vào tay BẮT BUỘC châm bùadefer rows.Close()ập tắp lự ngay sau phao chóp cứu rỗi chọc ngoáydb.Query().
func GetOrders(db *sql.DB, userID int64) ([]Order, error) {
rows, err := db.Query("LÔI mớ rác SELECT id, amount TỪ hang ổ orders NƠI MÀ user_id = $1", userID)
if err != nil {
return nil, err
}
defer rows.Close() // LỆNH TỬ TỘI CRITICAL: phải nhớ tay móc van chốt xả cửa cho bọn ống nhồi nhét bò húc quay tọt rớt về cái phễu (return connection to pool)
var orders []Order
for rows.Next() {
var o Order
if err := rows.Scan(&o.ID, &o.Amount); err != nil {
return nil, err
}
orders = append(orders, o)
}
// Ngoắc tay vạch lá soi bói rows.Err() — giơ nẹp chống lưng nhặt xác mấy con bọ bug rớt ra trên đường bò húc sờ mò (catches errors that occur during iteration - như bão đánh lòi phèo đường truyền rác network issues)
return orders, rows.Err()
}
Hỏi Nhanh Đáp Gọn (FAQ)
Giữa hai cái nách đắp thịt nhét xương bơm nhồi nhét Bơm Dọc (vertical) và Bơm Ngang (horizontal) rốt cuộc là cái nợ khỉ gió khác nhau chỗ nào?
Bơm Dọc (Vertical scaling) nhét gồng thêm ụ CPU/RAM nhồi tọt vào họng đúng 1 mâm server. Ăn nhanh húp lẹ làm trong 1 nốt nhạc (Fast to implement), chẳng phải xoắn não sửa lại mã chọt mã đục mớ code, khổ dâm cái nỗi (but has) là nó cóc chịu nghe lời đụng vách tường kẹt trần (hard physical ceiling) cạn nguồn nhét xác, mà cái hóa đơn đập vào mặt thì nhồi nhảy lố bịch vọt lên bành trướng phình lồi chẳng có ranh giới đong đếm nổi (grow non-linearly - xách rớ gắp cụt cục RAM bự 2× là lột tiền thốn khốn nạn vọt hơn ngọn núi x2 lần). Bơm Ngang (Horizontal scaling) vác tiền bao nuôi nguyên lò mướn đẻ thêm lính lác servers — giá rổ rác gánh nợ lên đều đặn vỗ về (linear cost growth), chẳng biết ngóc mỏ giới hạn nào (no ceiling), rước bực vào thân gánh đủ thảm họa chẻ năm xẻ bảy nhét mớ rác (data partitioning), gọi khản cổ oang oang dàn binh bố trận phân phát đôn đốc (distributed coordination), và vác nợ thêm một mâm đống rác rưởi hỗn độn điên đảo lúc vận hành (significantly more operational complexity).
Xắn quần bươi mỏ moi móc chiêu gì mò ra cục khóa trói đày đọa (shard key) đỉnh chóp?
Một hột shard key gánh vác đàng hoàng tử tế phải cõng 3 luật thép: (1) Chênh lệch phân mảnh bành trướng khủng (High cardinality) — dư rác đa dạng nhiều mớ rác lẻ tẻ lởm chởm khác bọt (many unique values) tha hồ vãi chài thảy đồng điệu vãi phân bổ (even data distribution); (2) Tuyệt bóng vắng bóng cái hũ ngập rác dập lòi rác (No write hot spot) — né né bóp né cái chỗ mâm chui nhủi mà mọi nhát ghi lao điên cuồng vác thúng dồn ập ném vô 1 mâm (avoid keys where one value receives all writes - lòi phèo cái trò created_at cắm rễ chọc ổ active tables); (3) Luồng chọc ổ gom 1 chỗ (Query locality) — mớ chiêu trò chọc bới mò mẫm hay ăn xôi nhất (the most common queries) nhón trúng chóc rinh vô đúng vỏn vẹn lăm le 1 cục phân hố shard. Hũ user_id hay rinh ngai vàng quật ngã nhào thóp con mẻ created_at ở chốn đâm chém chốt hàng (e-commerce) bới móc vì nó né cái mâm lửa đốt mông dồn ứ cục ghi dồn nhét đè xéo lồng lộn (time-based write hot spot).
Khi rảnh nợ vác con mẻ TiDB đè chém gọt đá mớ rác đẻ ra vặt cẳng MySQL sharding?
Chọt xách TiDB khi: bãi rác thúi hoắc vượt trần > 1 TB thèm khát mớ trò quay rác SQL bùi nhùi nát óc rối ren (complex SQL queries), cần bùa chú ban phước lết phình to nằm bẹp (horizontal scaling) chả cần vác mồ hôi nhễ nhại đục rẽ phân lô đất tự cày (manual re-sharding), hay thèm khát cái giao dịch phán trảm văng xác phân mảnh đứt đoạn chắp vá gộp chùm (distributed ACID transactions) trói gô rải mây khói trên mớ rác trộn bùn giữa nhiều kho tables mà chả thèm cất tiếng chắp vá xạo lòng vác 2PC lên tận tai Application-level. Đừng dại đâm vào hố rác vác TiDB (Don’t use TiDB) khi: đếm lết đoạt mạng từng mili-giây mong thèm độ trễ < 2ms (latency < 2ms is critical - cái vòng chốt sổ của TiDB ngâm giấm rặn cỡ ~3ms), hay rủi vớ vẩn rước vào ổ cái lò team chẳng có thằng đệ lính lác nào am tường mài đũng quần cày kinh ngâm cứu biết vận hành TiDB (lacks TiDB operational expertise).
🔗 Bay Sang Bài Tới: Phần 5: Kiến Trúc Hướng Sự Kiện & Cái Quái Thai Kafka Trong Go (Event-Driven Architecture & Kafka in Go) — Cái Bát Quái Mẫu Bể Thợ Đụng (Worker Pool pattern), xì áp lùi ngược đánh đuổi rác nhờ vào cái kênh lót bụng trói nghẹt (backpressure via buffered channels), và Luật Giang Hồ Chốt Đơn Đét 1 Nháy (Exactly-Once Semantics).