“Don’t trust anyone’s benchmark but your own — and especially not vendor’s.” — DBA motto
Benchmark database là lĩnh vực đầy cạm bẫy: số vendor quảng cáo thường chạy trên phần cứng đặc biệt, workload synthetic, không phản ánh production. Bài này đi từ cách setup đúng, chạy đúng công cụ, đến đọc kết quả đúng — mục tiêu: ra quyết định dựa trên số liệu, không dựa trên marketing.
1. Nguyên tắc vàng
- Benchmark workload bạn thực sự có — không phải workload vendor chọn.
- Giữ môi trường giống production: cùng cloud region, cùng instance type, cùng OS tuning, cùng PG/MySQL version.
- Warmup trước khi đo — 5-10 phút cho cache đầy.
- Run duration đủ dài: tối thiểu 30 phút; ngắn hơn sẽ bị checkpoint/GC làm nhiễu.
- Đo p50/p95/p99 latency, không chỉ throughput — p99 là cái user cảm nhận.
- Observer effect: profiler/monitoring cũng có chi phí. Tắt debug log, giảm verbosity trước khi đo.
- Reproduce 3 lần, lấy median. Nếu variance > 10% → có vấn đề.
2. Công cụ theo engine
2.1 PostgreSQL — pgbench
Build-in, đơn giản nhất cho OLTP-like workload (TPC-B).
# Khởi tạo dataset với scale factor 100 (~1.6GB)
pgbench -i -s 100 -U postgres bench
# Workload mặc định: mix read/write giống TPC-B
pgbench -c 32 -j 8 -T 300 -P 10 bench
# -c 32 : 32 concurrent clients
# -j 8 : 8 worker threads
# -T 300: chạy 300s
# -P 10 : log progress mỗi 10s
# Read-only (select-only)
pgbench -c 32 -j 8 -T 300 -S bench
# Custom script
cat > my-workload.sql <<'EOF'
\set uid random(1, 10000)
BEGIN;
SELECT * FROM users WHERE id = :uid;
UPDATE users SET last_seen = now() WHERE id = :uid;
COMMIT;
EOF
pgbench -c 64 -j 16 -T 600 -f my-workload.sql bench
2.2 MySQL — sysbench
Chuẩn de-facto cho MySQL.
# Chuẩn bị dataset
sysbench oltp_read_write \
--mysql-host=db.internal \
--mysql-user=bench \
--mysql-password=*** \
--mysql-db=bench \
--table-size=1000000 \
--tables=16 \
prepare
# Chạy 10 phút
sysbench oltp_read_write \
--mysql-host=db.internal \
--mysql-user=bench --mysql-password=*** --mysql-db=bench \
--tables=16 --table-size=1000000 \
--threads=64 \
--time=600 --report-interval=10 \
run
# Read-only
sysbench oltp_read_only --threads=64 --time=600 run
# Write-only (insert-heavy)
sysbench oltp_write_only --threads=32 --time=600 run
2.3 HammerDB — TPC-C / TPC-H trên MySQL, PG, SQL Server, Oracle
GUI + CLI. Dùng khi cần chuẩn TPC (đọc được bởi vendor, auditor):
# CLI example
cd hammerdb-4.11
./hammerdbcli
# Trong shell HammerDB
diset connection pg_host db.internal
diset tpcc pg_count_ware 100
buildschema
loadscript
vuset vu 32
vurun
2.4 YCSB — NoSQL / KV benchmark
Phổ biến cho MongoDB, Cassandra, Redis, DynamoDB. 5 workload mặc định (A-F) + custom.
# Cassandra workload A (50/50 read-write)
./bin/ycsb load cassandra-cql -s \
-p hosts=node1,node2,node3 \
-p recordcount=10000000 \
-P workloads/workloada
./bin/ycsb run cassandra-cql -s \
-p hosts=node1,node2,node3 \
-p operationcount=50000000 \
-p threadcount=64 \
-P workloads/workloada
2.5 ClickBench — OLAP benchmark thực dụng
ClickHouse team xây dựng, đánh giá OLAP engines (ClickHouse, DuckDB, Druid, Pinot, PostgreSQL) trên 1 dataset thực tế + 43 query. Link: github.com/ClickHouse/ClickBench.
2.6 Vector DB — ann-benchmarks
So sánh index ANN (HNSW, IVFFlat, DiskANN, ScaNN) trên 10+ dataset công khai (GloVe, SIFT, DEEP).
3. Đo gì ngoài throughput
3.1 Latency distribution
# sysbench output:
# [ 600s ] thds: 64 tps: 3245.67 qps: 64913.4 (r/w/o: 45439.3/12982.7/6491.4)
# lat (ms,95%): 34.95 err/s: 0.00 reconn/s: 0.00
Nhìn cả p50, p95, p99, p999, max. Vendor thường chỉ khoe average → vô nghĩa.
Tool đo latency tốt:
pgbench -P 10in progress cả tps + lat/stddev.sysbench --report-interval=10 --histogram.mysqlslap --concurrency=N --iterations=M --verbose.- Prometheus + Grafana histogram cho bức tranh tổng thể.
3.2 Resource usage song song
Benchmark không đo resource là benchmark vô nghĩa. Chạy cùng lúc:
# Disk I/O
iostat -xm 1
# CPU/Memory
vmstat 1
pidstat -p $(pgrep postgres) 1
# PostgreSQL
watch -n 1 'psql -c "SELECT state, count(*) FROM pg_stat_activity GROUP BY state"'
# Network
nload
Benchmark thành công là benchmark bottleneck rõ: CPU-bound, IO-bound, hay network-bound? Nếu “all green” → chưa đủ load, tăng concurrency.
3.3 Query-specific metrics
Với PG:
-- Reset counter trước run
SELECT pg_stat_reset();
SELECT pg_stat_statements_reset();
-- Chạy benchmark...
-- Sau benchmark:
SELECT query, calls, total_exec_time, mean_exec_time, rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
4. Cạm bẫy điển hình
4.1 Không có warmup
Chạy ngay → cache trống, plan cold, connection pool lạnh. Kết quả first-minute thấp rồi cao lên → misleading. Luôn warmup 5-10 min trước khi bắt đầu đếm.
4.2 Client cùng máy với DB
Bottleneck có thể ở client (CPU, network loopback, fd limit) chứ không phải DB. Tách client và DB sang 2 instance riêng, cùng VPC/AZ.
4.3 Connection pool sai
Quá ít connection → underload DB. Quá nhiều → context switching, connection contention. Quy tắc: threads = cores * 2-4. Với PG thường pool = 2 * cpu_count + disk_count.
4.4 Chạy quá ngắn
30s benchmark không bắt được checkpoint (PG WAL flush), autovacuum, buffer pool eviction. 30 phút tối thiểu cho OLTP, 1-2 giờ cho write-heavy.
4.5 Không tính network latency
Đo pg_bench trên localhost khác xa RDS cross-AZ. Benchmark phải cùng network topology với production.
4.6 Dataset too small
Nếu toàn bộ dataset fit trong shared_buffers, bạn benchmark RAM, không phải DB. Dataset phải > 2-3× RAM để bắt IO thực tế.
4.7 Không lặp lại
Một lần chạy có noise. 3 run, lấy median, report variance.
4.8 So sánh version / config khác nhau
Đổi 2 biến cùng lúc (vd vừa upgrade PG16 vừa tăng work_mem) → không biết biến nào ảnh hưởng. Đổi một biến tại một thời điểm.
5. Template report benchmark
Khi trình bày cho team / vendor:
Benchmark: PostgreSQL 16.2 vs 17.0 OLTP
Hardware: c7i.4xlarge (16 vCPU, 32 GB RAM), io2 Block Express 10k IOPS
Workload: pgbench -s 500 -c 32 -j 8 -T 1800, run 3 times
Tuning: shared_buffers=8GB, work_mem=64MB, max_connections=200
OS: Ubuntu 24.04, kernel 6.8, no swap
Results (median of 3):
PG 16.2: 18,432 tps, p95 5.2ms, p99 12.8ms, CPU 72%, IO util 68%
PG 17.0: 19,876 tps (+7.8%), p95 4.9ms, p99 11.4ms, CPU 71%, IO util 66%
Variance: < 3% tps, < 5% p99
Conclusion: PG 17 cho workload này cải thiện ~8% throughput và giảm 10% p99.
3 cột sống còn: dataset size, workload type, durations. Thiếu 1 trong 3 là không reproducible.
6. Benchmark compared database — case study
Khi so sánh PostgreSQL vs CockroachDB:
- Cùng hardware, cùng region.
- Cùng workload: pgbench với script custom mà production chạy (không phải TPC-B thuần).
- Cùng concurrency sweep: 8, 16, 32, 64, 128, 256 threads.
- Ghi 3 kết quả: tps, p95, p99, CPU, IO.
- Vẽ throughput-latency curve: tìm điểm “knee” — throughput saturate nhưng latency chưa scale out.
- Đánh giá cost: tps / $/month — số này thuyết phục hơn tps thuần tuý.
7. Load test application level — khác benchmark DB
Tools cho HTTP load test: k6, Locust, wrk2, Gatling, Artillery.
// k6 example — tạo tải end-to-end kết hợp benchmark DB
import http from "k6/http";
import { sleep } from "k6";
import { randomIntBetween } from "https://jslib.k6.io/k6-utils/1.2.0/index.js";
export const options = {
stages: [
{ duration: "5m", target: 100 },
{ duration: "30m", target: 100 },
{ duration: "5m", target: 0 },
],
thresholds: {
http_req_duration: ["p(95)<500"],
http_req_failed: ["rate<0.01"],
},
};
export default function () {
const uid = randomIntBetween(1, 10000);
http.get(`https://api.example.com/users/${uid}`);
sleep(0.5);
}
End-to-end benchmark gồm cả HTTP, app, DB, cache, network — thực tế hơn benchmark chỉ DB. Dùng khi đo impact của một change tổng thể (ORM layer, caching strategy…).
8. Workflow đề xuất
flowchart TD
A[Xác định câu hỏi<br/>e.g. PG16 vs PG17 trên workload X] --> B[Setup môi trường identical<br/>Hardware, OS, config]
B --> C[Chọn dataset<br/>size > 2-3x RAM]
C --> D[Workload script<br/>mirror production query mix]
D --> E[Warmup 5-10 phút]
E --> F[Run benchmark 30+ phút]
F --> G{3 runs giống nhau?}
G -->|No| H[Debug variance<br/>network, noisy neighbor, config]
G -->|Yes| I[Report: tps, p95, p99, CPU, IO]
I --> J[Quyết định dựa trên<br/>throughput + latency + cost]
9. Checklist chạy benchmark đúng
- Xác định mục tiêu cụ thể (câu hỏi yes/no)
- Môi trường = production (instance type, region, OS, version)
- Tách client và DB sang instance khác nhau
- Dataset > 2-3× RAM
- Workload mirror production query mix
- Warmup 5-10 min
- Run ≥ 30 min
- Lặp 3 lần, lấy median, report variance
- Đo p50/p95/p99, CPU, IO, network simultaneously
- Đổi một biến tại một thời điểm
- Report đủ: hardware, config, workload, duration
Kết luận
Benchmark không phải để khoe số tps lớn — benchmark để ra quyết định. Một benchmark đúng là:
- Có câu hỏi rõ ràng.
- Môi trường reproduce được.
- Đo đủ metric ngoài throughput.
- Variance thấp, lặp lại được.
Tốn 2-3 ngày làm cho đúng đáng hơn 1 tuần cãi nhau với data sai. Khi đã có số đúng, quyết định migrate / upgrade / re-architect sẽ tự hiện ra.
Bài cuối series sẽ là cheatsheet tổng hợp — gom lại tất cả lệnh, config, tuning guideline đã xuất hiện trong 13 bài thành 1 trang tham khảo nhanh.