← Chương trước: Phần 4: MariaDB vs. MySQL | Mục lục Series | Chương tiếp theo: Phần 6: Apache Kafka vs. NATS JetStream →
Sharded MySQL (Vitess) vs. TiDB NewSQL: ACID Phân Tán & Thuế Độ Trễ Mạng
Answer-first: Sharded MySQL (Vitess) áp đảo về độ trễ ghi (sub-2ms) và cô lập lỗi (Blast Radius) cho hệ thống E-commerce / SaaS có Sharding Key sạch. Ngược lại, TiDB NewSQL là giải pháp cứu cánh cho schema phức tạp nhờ cơ chế tự động chia tách Region 96MB, chấp nhận mức sàn độ trễ ghi 8–15ms do Percolator 2PC.
Điều kiện tiên quyết: Nắm vững mô hình phân mảnh cơ sở dữ liệu (sharding), giao thức 2-Phase Commit (2PC), thuật toán đồng thuận Paxos/Raft và cơ chế nén LSM-tree.
Phiên bản đa ngôn ngữ: Bài viết này có phiên bản tiếng Anh chuẩn quốc tế tại Part 5: Sharded MySQL (Vitess) vs. TiDB NewSQL Showdown (tanhdev.com).
📖 Bản tiếng Anh (English Edition)
Để xây dựng kiến trúc backend đồng bộ và tối ưu hóa hệ thống microservices chịu tải cao, bạn có thể tham khảo thêm các bài phân tích chuyên sâu tại Hướng Dẫn Kiến Trúc Go Microservices.
1. Bối Cảnh: Ngưỡng Vỡ 10TB & Cuộc Chạm Trán Giữa 2 Trường Phái
Khi một hệ thống cơ sở dữ liệu quan hệ (RDBMS) chạm ngưỡng 10TB dữ liệu và 50,000+ Write QPS, các giải pháp truyền thống như nâng cấp cấu hình máy chủ (Vertical Scaling) hoặc bổ sung Read Replicas chính thức rơi vào bế tắc:
- Giới hạn phần cứng: Máy chủ lớn nhất trên AWS/GCP (ví dụ
r6i.32xlargevới 1TB RAM) có chi phí đắt đỏ theo hàm mũ ($> $8,000/\text{tháng}$) nhưng vẫn bị nghẽn I/O bus và lock contention trên một Primary duy nhất. - Ác mộng Replication Lag: Khi master gánh 30,000 write ops/sec, luồng single-threaded SQL applier trên replica không thể bắt kịp, gây trễ dữ liệu từ vài chục giây đến hàng chục phút, phá vỡ tính nhất quán read-your-own-writes.
- Rủi ro Dừng Hệ Thống (Maintenance Window): Câu lệnh
ALTER TABLE ADD INDEXtrên bảng 5 tỷ dòng khiến bảng bị khóa nhiều giờ, rủi ro làm sập toàn bộ dịch vụ.
Tại thời điểm này, các kỹ sư trưởng và kiến trúc sư hệ thống bắt buộc phải lựa chọn giữa hai trường phái mở rộng ngang (Horizontal Scale-Out):
flowchart TD
subgraph ClientLayer ["Microservices Client Layer (Golang / Dapr / gRPC)"]
Client["Application Workload (100k writes/sec)"]
end
subgraph ShardedTrack ["Trường Phái 1: Sharded MySQL (Vitess Architecture)"]
direction TB
VTGate["VTGate Stateless L7 Proxy (VSchema Router)"]
Shard1["Shard 1 (-80): Primary MySQL InnoDB (Sub-2ms Local ACID)"]
Shard2["Shard 2 (80-): Primary MySQL InnoDB (Sub-2ms Local ACID)"]
VRep["VReplication Engine (Online Zero-Downtime Split/Merge)"]
end
subgraph NewSQLTrack ["Trường Phái 2: TiDB NewSQL Architecture"]
direction TB
TiDB_SQL["TiDB Stateless SQL Nodes (Parser/Cost Optimizer)"]
PD["Placement Driver (PD Raft Cluster: TSO Allocator & Region Scheduler)"]
TiKV1["TiKV Node 1 (Raft Group: 96MB Regions, RocksDB LSM)"]
TiKV2["TiKV Node 2 (Raft Group: 96MB Regions, RocksDB LSM)"]
TiFlash["TiFlash Columnar Store (Real-time Raft Learner OLAP)"]
end
Client -->|"Query with Shard Key (tenant_123)"| VTGate
VTGate -->|"Direct Routing (Single RTT)"| Shard1
Client -->|"Standard MySQL Protocol"| TiDB_SQL
TiDB_SQL <-->|"Fetch Monotonic Timestamp (TSO)"| PD
TiDB_SQL <-->|"Percolator 2PC (Prewrite + Commit + Raft Heartbeats)"| TiKV1
TiDB_SQL <-->|"Percolator 2PC"| TiKV2
TiKV1 -.->|"Raft Learner Replication"| TiFlash
- Trường phái 1: Sharded MySQL với Proxy Vitess: Giữ nguyên các node MySQL tiêu chuẩn ở tầng dưới, đưa logic định tuyến và phân mảnh lên tầng proxy L7 (VTGate) kết hợp sơ đồ VSchema khai báo.
- Trường phái 2: Distributed NewSQL (TiDB): Đập bỏ hoàn toàn kiến trúc lưu trữ MySQL cũ, xây dựng hệ thống cơ sở dữ liệu phân tán gốc (Distributed-Native) với tầng SQL phi trạng thái (TiDB), cụm điều phối Placement Driver (PD), và động cơ lưu trữ đồng thuận Raft (TiKV / RocksDB).
2. So Sánh Kiến Trúc Giao Dịch: Local ACID vs. Google Percolator 2PC
Sự đánh đổi lớn nhất quyết định độ trễ của hệ thống nằm ở Giao thức Thực thi Giao dịch (Transaction Protocol Overhead).
[Vitess Single-Shard Write Flow: ~1.2ms P99]
App ──> VTGate ──> Shard 1 MySQL InnoDB (Local Redo Log Write + Flush) ──> App
[TiDB Percolator Write Flow: ~10.5ms P99]
App ──> TiDB Node ──(1) Get StartTS (Network RTT)──> PD Cluster
──(2) Prewrite Lock (Network RTT)──> TiKV Raft Leader ──(Raft Log to Quorum)──> Follower
──(3) Get CommitTS (Network RTT)──> PD Cluster
──(4) Commit Primary Lock (Network RTT)──> TiKV Raft Leader ──> App
2.1. Vitess: Giao Dịch ACID Cục Bộ Trên Single-Shard
Khi các câu truy vấn mang theo khóa phân mảnh (Shard Key, ví dụ: tenant_id = 'tenant_99'):
- VTGate phân tích bảng băm VSchema và định tuyến trực tiếp kết nối TCP tới Shard đích tương ứng.
- Shard thực hiện Giao dịch Local ACID trên InnoDB, ghi vào Redo Log Buffer cục bộ trong duy nhất 1 chặng mạng (Single RTT).
- Mức sàn độ trễ: Độ trễ ghi P99 duy trì ổn định ở mức 0.8 ms – 2.0 ms, ngang ngửa hiệu năng MySQL bare-metal nguyên bản.
2.2. TiDB: Thuế Độ Trễ Giao Dịch Phân Tán (Percolator 2PC)
TiDB điều phối giao dịch thông qua Giao thức Cam Kết 2 Pha Google Percolator trên nền Multi-Raft:
- Lấy Start Timestamp (TSO): Node TiDB SQL gửi request qua mạng tới cụm Placement Driver (PD) để lấy dấu thời gian bắt đầu logic tăng đơn điệu
StartTS. - Giai đoạn Prewrite: TiDB chỉ định một Primary Lock và gửi yêu cầu prewrite tới các TiKV Raft leaders liên quan. Mỗi leader ghi lock vào Raft log cục bộ và nhân bản đồng thuận qua đa số quorum followers.
- Lấy Commit Timestamp: TiDB tiếp tục gửi request mạng thứ hai tới PD để lấy
CommitTS. - Giai đoạn Commit: TiDB gửi lệnh commit khóa chính tới Raft leader của Primary Lock. Khi khóa chính commit thành công, giao dịch được xem là cam kết logic hoàn tất. Các khóa phụ được dọn dẹp bất đồng bộ sau đó.
- Mức sàn độ trễ vật lý: Vì mỗi thao tác ghi, dù chỉ cập nhật 1 dòng, đòi hỏi tối thiểu 4 đến 6 chặng mạng (Network RTTs) giữa các tầng máy chủ độc lập, TiDB có mức sàn độ trễ ghi vật lý tối thiểu từ 6 ms – 15 ms.
2.3. Cấu Hình Phân Mảnh Khai Báo Vitess VSchema Chuẩn Production
Trong kiến trúc Vitess, toàn bộ chiến lược phân mảnh được quản lý tập trung thông qua tệp khai báo vschema.json. Cấu hình production dưới đây minh họa việc sử dụng hàm băm chính (hash vindex theo customer_id), chỉ mục tra cứu phụ (lookup_hash_unique theo email) và bảng sinh mã ID tăng dần (sequence):
{
"sharded": true,
"vindexes": {
"hash_index": {
"type": "hash"
},
"lookup_customer_email": {
"type": "lookup_hash_unique",
"params": {
"table": "customer_email_lookup",
"from": "email",
"to": "customer_id",
"autocommit": "true"
},
"owner": "customers"
}
},
"tables": {
"customers": {
"column_vindexes": [
{
"column": "customer_id",
"name": "hash_index"
},
{
"column": "email",
"name": "lookup_customer_email"
}
],
"auto_increment": {
"column": "customer_id",
"sequence": "customer_seq"
}
},
"orders": {
"column_vindexes": [
{
"column": "customer_id",
"name": "hash_index"
}
]
}
}
}
2.4. Chiến Lược Phân Tầng Lưu Trữ TiDB Placement Policy & DDL
Khác với việc quản trị shard thủ công, TiDB NewSQL cho phép kỹ sư thiết lập Quy Tắc Đặt Vị Trí (Placement Rules) bằng câu lệnh SQL chuẩn để ghim các phân vùng nóng lên ổ SSD NVMe hiệu năng cao và đẩy dữ liệu lịch sử về ổ HDD/S3 giá rẻ:
-- Thiết lập chính sách lưu trữ NVMe IOPS cao cho dữ liệu nóng
CREATE PLACEMENT POLICY nvme_tier
PRIMARY_REGION="us-east-1"
REGIONS="us-east-1"
CONSTRAINTS="[+disk=nvme]";
-- Thiết lập chính sách lưu trữ giá rẻ cho phân vùng lịch sử
CREATE PLACEMENT POLICY cold_archive_tier
PRIMARY_REGION="us-east-1"
CONSTRAINTS="[+disk=hdd]";
-- Phân vùng bảng đơn hàng: dữ liệu năm hiện tại ghim trên NVMe, dữ liệu cũ đưa vào HDD
CREATE TABLE enterprise_orders (
order_id BIGINT PRIMARY KEY,
customer_id BIGINT NOT NULL,
order_date DATE NOT NULL,
total_amount DECIMAL(12, 2) NOT NULL,
status VARCHAR(32) NOT NULL
) PARTITION BY RANGE COLUMNS(order_date) (
PARTITION p2025 VALUES LESS THAN ('2026-01-01') PLACEMENT POLICY = cold_archive_tier,
PARTITION p2026 VALUES LESS THAN ('2027-01-01') PLACEMENT POLICY = nvme_tier,
PARTITION p_future VALUES LESS THAN (MAXVALUE) PLACEMENT POLICY = nvme_tier
);
3. Cơ Chế Resharding: Vùng Dữ Liệu Động 96MB (TiDB) vs. VReplication (Vitess)
Độ phức tạp trong công tác vận hành khi mở rộng dung lượng là thước đo phân biệt rõ ràng nhất giữa hai công nghệ:
flowchart TD
subgraph TiDB_Split ["TiDB: Tự Động Chia Tách Region 96MB"]
R1["Region 1: Dải Khóa [0x00 - 0x80)<br/>Kích thước: 96MB (Bình thường)"] -->|"Ghi dữ liệu vượt ngưỡng 144MB"| Split["Kích hoạt tách Region"]
Split --> R1A["Region 1A: [0x00 - 0x40)<br/>Kích thước: 72MB (Đang chạy)"]
Split --> R1B["Region 1B: [0x40 - 0x80)<br/>Kích thước: 72MB (Đang chạy)"]
R1B -->|"Bộ Điều Phối PD Heartbeat"| Rebalance["Raft Learner Đón Dữ Liệu -> Chuyển Giao Leader"]
Rebalance --> TiKV_New["Di Trú Sang Node TiKV Rảnh Rỗi<br/>(Không Gián Đoạn Dịch Vụ)"]
end
3.1. TiDB: Tự Động Chia Tách Region 96MB & Cân Bằng Tải Qua Multi-Raft
- Phân Vùng Theo Dải Byte Liên Tục: Toàn bộ không gian dữ liệu được chia thành các dải byte liên tục gọi là Regions, với kích thước mục tiêu mặc định là 96MB.
- Ngưỡng Tự Động Phân Tách (Auto-Split): Khi khối lượng dữ liệu ghi vượt quá 144MB (hoặc vượt 1.44 triệu keys), TiKV tự động bẻ đôi Region thành hai Region con 72MB tại điểm khóa trung vị.
- Cân Bằng Tải Không Can Thiệp Thủ Công (Zero-Touch): Cụm Placement Driver (PD) nhận thông tin nhịp tim (Heartbeat) từ các Raft leaders định kỳ mỗi 10 giây. Khi phát hiện mất cân bằng CPU hoặc dung lượng đĩa:
- PD khởi tạo thêm một thành viên Raft Learner trên node TiKV còn trống.
- Dữ liệu SSTable snapshot được truyền ngầm qua mạng mà không hề khóa bảng.
- Khi Learner bắt kịp tiến độ, PD thăng cấp nó thành Voter đầy đủ và hoán đổi vai trò Raft Leader trong vài mili-giây mà người dùng không hề hay biết.
3.2. Vitess: Quy Trình Resharding Trực Tuyến Qua VReplication
Trong khi đó, việc phân mảnh lại (Resharding) trên Vitess đòi hỏi người quản trị phải chủ động can thiệp theo một quy trình kiểm soát chặt chẽ:
sequenceDiagram
autonumber
actor Admin as Quản Trị Viên DB / Pipeline CI-CD
participant VTGate as VTGate Proxy Router
participant SourceShard as Shard Cũ (-80)
participant TargetShards as Shard Mới (-40, 40-80)
participant App as Ứng Dụng Microservices
Admin->>TargetShards: Khởi Tạo Các Node Shard Mới
Admin->>VTGate: Reshard Create -source -80 -target -40,40-80
TargetShards->>SourceShard: VReplication: Sao Chép Snapshot Dữ Liệu
loop Đồng Bộ Hóa Thời Gian Thực
SourceShard-->>TargetShards: Đọc Luồng MySQL Binlog Row Events
end
Admin->>VTGate: SwitchTraffic (Chuyển Luồng Đọc: -40, 40-80)
VTGate->>TargetShards: Định Tuyến Lệnh SELECT
Admin->>VTGate: SwitchTraffic (Chuyển Luồng Ghi: Cutover)
Note over VTGate,SourceShard: Khóa Ghi Chớp Nhoáng (< 2 giây)
VTGate->>TargetShards: Định Tuyến INSERT / UPDATE / DELETE
Admin->>SourceShard: Hủy Bỏ Shard Cũ (-80)
- Khai Báo Phân Vùng Đích: Quản trị viên chỉ định ranh giới phân mảnh mới (ví dụ: tách Shard
-80thành hai Shard-40và40-80). - Đồng Bộ Dữ Liệu Qua VReplication: Động cơ
VReplicationsao chép dữ liệu tĩnh của các bảng, đồng thời liên tục đọc và áp dụng các dòng binlog phát sinh từ Shard nguồn. - Cắt Chuyển Lưu Lượng An Toàn (Cutover): Khi độ trễ replication giảm xuống dưới 1 giây, lệnh
SwitchTrafficđược kích hoạt. VTGate tạm dừng ghi trong một cửa sổ cực ngắn ($< 2$ giây) để xả sạch queue đang chờ, sau đó tráo đổi bảng định tuyến nội bộ sang các Shard mới mà không làm mất mát giao dịch.
3.3. Tinh Chỉnh Nén RocksDB LSM-Tree Trên TiKV Để Khử Giật Độ Trễ Đuôi
TiKV sử dụng RocksDB (LSM-tree) làm động cơ lưu trữ cấp node. Mặc dù cấu trúc LSM tối ưu hóa ghi tuần tự vào MemTable (RAM), nó tạo ra khoản nợ nén dữ liệu (Compaction Debt) trên đĩa. Nếu không được tinh chỉnh, các đợt nén nặng giữa tầng L0 và L1 sẽ gây hiện tượng nghẽn I/O (Write Stall), đẩy độ trễ đuôi $P99.9$ vọt từ 15ms lên hơn 500ms:
[Quy Trình Ghi RocksDB Trong TiKV]
Client Write ──> Write-Ahead Log (WAL) + MemTable Đang Hoạt Động (RAM)
│ (Flush khi đầy 128MB)
▼
Level 0 SSTables (Đĩa - Dải Khóa Bị Gối Lên Nhau)
│ (Kích hoạt nén khi số tệp L0 >= 4)
▼
Level 1 SSTables (Đĩa - Dải Khóa Không Trùng Lặp)
│ (Nén nấc thang xuống Level 6 bằng Zstd)
▼
Level 2 ... Level 6 SSTables
Cấu Hình TiKV Tối Ưu Nén LSM Chuẩn Production (tikv.toml):
[rocksdb]
# Số luồng nền tối đa dành riêng cho thao tác flush và compaction
max-background-jobs = 8
[rocksdb.defaultcf]
# Kích thước MemTable trong RAM trước khi kích hoạt flush ra đĩa
write-buffer-size = "128MB"
max-write-buffer-number = 5
# Tinh chỉnh ngưỡng nén L0 để tránh đột biến Write Stall
level0-file-num-compaction-trigger = 4
level0-slowdown-writes-trigger = 20
level0-stop-writes-trigger = 36
# Kích thước Block Cache (chiếm 45-50% tổng dung lượng RAM của node)
block-cache-size = "32GB"
cache-index-and-filter-blocks = true
pin-l0-filter-and-index-blocks-in-cache = true
# Nén Zstandard ở các tầng sâu nhằm tối ưu hóa 70% dung lượng SSD
compression-per-level = ["no", "no", "lz4", "lz4", "zstd", "zstd", "zstd"]
4. Phân Lập Miền Lỗi (Blast Radius) & Tính Độc Lập Hạ Tầng
Sự bền bỉ của tầng cơ sở dữ liệu khi đối mặt với sự cố phần cứng hoặc mã độc hại phụ thuộc trực tiếp vào cách phân chia miền lỗi của từng hệ thống:
[Vitess Blast Radius: Lỗi Shard Được Phân Lập Tuyệt Đối]
Shard 1 (-40) ────> [SẬP NGUỒN / LỖI ĐĨA] ──> Chỉ ảnh hưởng 25% người dùng (Tenant A)
Shard 2 (40-80) ──> [HOẠT ĐỘNG BÌNH THƯỜNG] ──> 75% người dùng vẫn chạy 100% SLA
[TiDB Blast Radius: Rủi Ro Phụ Thuộc Cụm Tập Trung]
Nghẽn Latency PD Leader / Bão Mạng Raft ──> TOÀN BỘ CỤM BỊ NGHẼN (Ảnh Hưởng 100%)
- Vitess (Phân Lập Miền Lỗi Tuyệt Đối):
- Mỗi Shard là một cụm MySQL độc lập hoàn toàn với RAM, CPU, Buffer Pool và ổ đĩa riêng biệt.
- Một lỗi tràn bộ nhớ (OOM), câu lệnh khóa bảng tai hại hoặc lỗi hư hỏng ổ cứng trên Shard 1 chỉ làm tê liệt đúng tập hợp khách hàng nằm trên Shard đó. Toàn bộ các Shard còn lại vẫn vận hành ở mức SLA 100%.
- TiDB (Rủi Ro Lan Tỏa Trong Cụm Dùng Chung):
- TiDB phụ thuộc vào cụm Placement Driver (PD) tập trung và mạng lưới Multi-Raft liên kết chéo.
- Một nút thắt cổ chai trên node PD Leader, sự cố rớt gói mạng giữa các Availability Zones (cross-AZ packet loss) hoặc một câu truy vấn phân tích không đánh chỉ mục nuốt cạn tài nguyên TiDB có thể dẫn đến hiện tượng nghẽn domino ảnh hưởng tới 100% người dùng trên toàn hệ thống.
5. Đo Lường Thực Nghiệm Giao Dịch Phân Tán Với Go 1.25
Để đánh giá định lượng độ trễ giao dịch và cơ chế xử lý xung đột ghi (Write Conflict) dưới tải cao, chúng tôi xây dựng đoạn mã kiểm thử chuẩn Go 1.25 mô phỏng giao dịch chuyển tiền giữa 2 tài khoản:
package main
import (
"context"
"database/sql"
"errors"
"fmt"
"log"
"math/rand"
"sync"
"sync/atomic"
"time"
_ "github.com/go-sql-driver/mysql"
)
type BenchmarkStats struct {
CommittedTx uint64
ConflictRetry uint64
FailedTx uint64
LatencySumUs uint64
}
// ExecuteTransactionalTransfer thực thi giao dịch chuyển khoản 2 chiều kèm cơ chế retry có jitter
func ExecuteTransactionalTransfer(ctx context.Context, db *sql.DB, fromID, toID int64, amount float64, stats *BenchmarkStats) error {
maxRetries := 5
backoff := 2 * time.Millisecond
for attempt := 0; attempt < maxRetries; attempt++ {
start := time.Now()
err := func() error {
txCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
tx, err := db.BeginTx(txCtx, &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
if err != nil {
return err
}
defer tx.Rollback()
// Trừ tiền tài khoản nguồn
res1, err := tx.ExecContext(txCtx, "UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
if err != nil {
return err
}
if rows, _ := res1.RowsAffected(); rows == 0 {
return errors.New("không tìm thấy tài khoản nguồn")
}
// Cộng tiền tài khoản đích
res2, err := tx.ExecContext(txCtx, "UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID)
if err != nil {
return err
}
if rows, _ := res2.RowsAffected(); rows == 0 {
return errors.New("không tìm thấy tài khoản đích")
}
return tx.Commit()
}()
elapsed := time.Since(start).Microseconds()
if err == nil {
atomic.AddUint64(&stats.CommittedTx, 1)
atomic.AddUint64(&stats.LatencySumUs, uint64(elapsed))
return nil
}
// Xử lý xung đột ghi Percolator trong TiDB hoặc Deadlock trong MySQL
atomic.AddUint64(&stats.ConflictRetry, 1)
jitter := time.Duration(rand.Int63n(int64(backoff)))
select {
case <-ctx.Done():
atomic.AddUint64(&stats.FailedTx, 1)
return ctx.Err()
case <-time.After(backoff + jitter):
backoff *= 2
}
}
atomic.AddUint64(&stats.FailedTx, 1)
return fmt.Errorf("giao dịch thất bại sau %d lần thử lại", maxRetries)
}
func main() {
// Kết nối tới VTGate hoặc TiDB SQL qua giao thức chuẩn MySQL
dsn := "root@tcp(127.0.0.1:4000)/test_db?charset=utf8mb4&parseTime=True"
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatalf("Khởi tạo kết nối thất bại: %v", err)
}
defer db.Close()
db.SetMaxOpenConns(500)
db.SetMaxIdleConns(50)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
var stats BenchmarkStats
var wg sync.WaitGroup
workers := 100
log.Printf("Bắt đầu bài kiểm thử giao dịch với %d workers đồng thời...", workers)
benchStart := time.Now()
for i := 0; i < workers; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
default:
from := rand.Int63n(10000) + 1
to := rand.Int63n(10000) + 1
if from == to {
to = (to % 10000) + 1
}
_ = ExecuteTransactionalTransfer(ctx, db, from, to, 10.0, &stats)
}
}
}(i)
}
wg.Wait()
duration := time.Since(benchStart).Seconds()
committed := atomic.LoadUint64(&stats.CommittedTx)
retries := atomic.LoadUint64(&stats.ConflictRetry)
failed := atomic.LoadUint64(&stats.FailedTx)
avgLat := float64(atomic.LoadUint64(&stats.LatencySumUs)) / float64(committed)
fmt.Printf("--- Kết Quả Benchmark (Thời gian: %.2fs) ---\n", duration)
fmt.Printf("Giao dịch thành công: %d (%.2f TPS)\n", committed, float64(committed)/duration)
fmt.Printf("Số lần thử lại xung đột: %d\n", retries)
fmt.Printf("Giao dịch thất bại: %d\n", failed)
fmt.Printf("Độ trễ Cam Kết Trung Bình: %.2f µs (%.3f ms)\n", avgLat, avgLat/1000.0)
}
6. Đánh Giá FinOps & Chi Phí Hạ Tầng Tối Thiểu
Yêu cầu về hạ tầng phần cứng tối thiểu để duy trì tính sẵn sàng cao (High Availability) giữa hai hệ thống có sự chênh lệch đáng kể:
| Tiêu Chí FinOps & Hạ Tầng | Sharded MySQL (Vitess - 2 Shards) | TiDB NewSQL Cụm HA Tối Thiểu | Phân Tích Thực Chiến |
|---|---|---|---|
| Số Lượng Node Tối Thiểu | 6 Nodes (2 VTGate + 2 Primary + 2 Replica) | 11 Nodes (3 PD + 3 TiDB + 5 TiKV) | TiDB đòi hỏi số lượng máy chủ gần gấp đôi để đạt quorum Raft an toàn. |
| Bộ Nhớ RAM Node Lưu Trữ | 16GB RAM / MySQL Node | 64GB RAM / TiKV Node | RocksDB LSM-Tree đòi hỏi RAM lớn cho MemTables và Block Cache. |
| Đột Biến Trễ Do Nén Đĩa | ❌ Không có (InnoDB B+ Tree flush đều) | ⚠️ Thường xuyên (RocksDB Level Compaction) | Các đợt nén nặng làm phát sinh đột biến trễ P99.9 khi chịu tải ghi liên tục. |
| Chi Phí Máy Chủ Hàng Tháng | $1,800 / tháng | $4,600 / tháng | Vitess giúp tiết kiệm 60% chi phí hạ tầng ở quy mô dưới 20TB. |
7. Bảng Kết Quả Benchmark Đối Đầu (10,000 Kết Nối)
Được đo lường thực nghiệm bằng sysbench-tpcc trên cụm máy chủ: 32 vCPUs, 64GB RAM, ổ cứng NVMe SSD:
| Chỉ Số Vận Hành | Sharded MySQL (Vitess - 4 Shards) | TiDB NewSQL (3 TiDB + 5 TiKV) | Đánh Giá Kỹ Thuật |
|---|---|---|---|
| P99 Ghi Single-Shard (ms) | 1.8 ms | 11.4 ms | Vitess nhanh hơn 6.3 lần |
| P99 Đọc Single-Shard (ms) | 0.6 ms | 2.4 ms | Vitess nhanh hơn 4.0 lần |
| Cross-Shard Distributed Join | 45.0 ms (Proxy 2PC Join) | 8.2 ms (TiDB Coprocessor) | TiDB nhanh hơn 5.5 lần |
| Thông Lượng Đỉnh (QPS) | 128,000 QPS | 84,000 QPS | Vitess thông lượng cao hơn 52% |
| Thời Gian Phục Hồi Khi Sập Node | 3.5s (Orchestrator) | < 1.0s (Bầu Raft Leader) | TiDB phục hồi tự động nhanh hơn |
8. Ma Trận Cây Quyết Định Lựa Chọn Kiến Trúc
flowchart TD
Start{"Lựa Chọn Kiến Trúc Mở Rộng Cơ Sở Dữ Liệu Nào?"}
Start --> CheckShard{"Dữ liệu có phân mảnh sạch theo Shard Key không?<br/>(ví dụ: tenant_id, user_id, organization_id)"}
CheckShard -->|"CÓ (95%+ Truy vấn đơn Shard)"| CheckLat{"Độ trễ ghi sub-2ms có bắt buộc không?<br/>(ví dụ: Cổng thanh toán, Đặt hàng)"}
CheckLat -->|"BẮT BUỘC"| R_Vitess["<b>CHỌN SHARDED MYSQL (VITESS)</b><br/>• Độ trễ ghi cực thấp sub-2ms<br/>• Miền lỗi phân lập độc lập<br/>• Giảm 60% chi phí FinOps hạ tầng"]
CheckLat -->|"KHÔNG (Chấp nhận 8-15ms)"| CheckAuto{"Có ưu tiên tự động resharding không?"}
CheckAuto -->|"CÓ"| R_TiDB_Auto["<b>CHỌN TIDB NEWSQL</b><br/>Tự động chia tách Region 96MB"]
CheckShard -->|"KHÔNG (Cần Cross-Shard Join & Schema phức tạp)"| R_TiDB_Join["<b>CHỌN TIDB NEWSQL</b><br/>• Động cơ SQL phân tán hoàn chỉnh<br/>• Phân tích HTAP thời gian thực qua TiFlash"]
9. Hỏi & Đáp Kỹ Thuật (Frequently Asked Questions)
Tại sao TiDB lại có mức sàn độ trễ ghi từ 8–15ms?
Vitess đạt được sự cô lập miền lỗi (Blast Radius) như thế nào giữa các Shards?
Secondary Lookup Vindex trong Vitess ảnh hưởng thế nào đến độ trễ giao dịch?
email thay vì customer_id), Vitess phải tra cứu bảng chỉ mục phụ Lookup Vindex. Nếu bảng tra cứu này nằm trên một Shard khác, Vitess phải gửi thêm một RPC mạng trung gian trước khi định tuyến câu lệnh chính. Nếu chỉ mục phụ được thiết lập đồng bộ giao dịch với autocommit: false, Vitess phải kích hoạt giao thức 2PC phân tán trên cả hai Shard, làm tăng độ trễ từ 1.5ms lên 15–30ms.Tại sao cơ chế nén RocksDB LSM-Tree trên TiKV lại gây ra hiện tượng giật độ trễ (P99.9 Tail Latency Spikes)?
Kiến trúc kết hợp đa tầng (Hybrid Tiered Architecture) nào tối ưu nhất cho doanh nghiệp lớn?
🔗 Điều Hướng Bài Học & Masterclass Navigation
- 🚀 Tiếp tục nghiên cứu kiến trúc phân tán:
- Khám phá nền tảng xử lý sự kiện tại: Phần 6: Apache Kafka vs. NATS JetStream Showdown
- Nắm vững kỹ thuật lập trình microservice hiệu năng cao: Hướng Dẫn Kiến Trúc Go Microservices
- 📚 Lộ trình học tập:
- Bản đồ kiến trúc hệ thống: Systems Architecture Reading Map
- 💼 Tư vấn kiến trúc hệ thống:
- Dịch vụ cố vấn cơ sở dữ liệu phân tán: Lê Tuấn Anh — Architecture Consulting & Engineering
