Database Benchmarking and Performance Testing â Tools, Metrics, Methodology
In this tutorial, you'll learn about Database Benchmarking and Performance Testing. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Database benchmarking is the practice of measuring database performance under controlled workloads using standardized tools and metrics â including throughput, latency, concurrency, and response time distributions â to compare systems objectively and identify bottlenecks.
What You'll Learn
You'll understand benchmarking methodology, tools (pgbench, sysbench, HammerDB, YCSB), key metrics (TPS, QPS, p50/p99 latency, connection scalability), workload modeling, and how to avoid common benchmarking pitfalls that produce misleading results.
Why It Matters
Making database decisions based on feelings or marketing claims leads to expensive mistakes. DodaZIP processes millions of file compression operations daily; benchmarking different database configurations saved 40% in infrastructure costs by proving that a smaller, well-tuned instance outperformed a larger, poorly-configured one.
Real-World Use
A fintech startup chose MongoDB based on popularity, then spent 6 months fighting consistency issues. After benchmarking PostgreSQL vs MongoDB for their specific workload pattern, they found PostgreSQL handled 3x more transactions per second with 5x lower latency. Benchmarking before production would have saved months of rework.
Database Benchmarking Learning Path
flowchart LR A[SQL Query Optimization] --> B[Database Indexing] B --> C[Database Benchmarking] C --> D[Performance Tuning] C:::current classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
Prerequisites: Understanding of SQL Query Optimization and Database Indexing. Familiarity with PostgreSQL or MySQL administration is recommended.
Key Benchmarking Metrics
Before running benchmarks, understand what each metric measures and its limitations.
| Metric | Definition | What It Tells You |
|---|---|---|
| TPS (Transactions Per Second) | Number of complete transactions per second | Overall throughput |
| QPS (Queries Per Second) | Number of queries/statements per second | Query throughput |
| Latency (p50) | Median response time | Typical user experience |
| Latency (p99) | 99th percentile response time | Worst-case user experience |
| Max connections | Maximum concurrent connections before failure | Scalability ceiling |
| IOPS | Disk input/output operations per second | Storage bottleneck indicator |
# A benchmark result showing key metrics
benchmark_result = {
"workload": "OLTP Read-Write Mix",
"tps": 4500, # 4,500 transactions per second
"p50_latency_ms": 2.1, # Half of requests finish in 2.1ms
"p95_latency_ms": 8.5, # 95% finish in 8.5ms
"p99_latency_ms": 25.3, # 1% of requests take >25ms (tail latency)
"errors": 0, # Zero errors during test
}
Benchmarking Tools
pgbench (PostgreSQL)
pgbench is a built-in PostgreSQL benchmarking tool that runs a TPC-B-like workload.
# Initialize the benchmark database with 100,000 rows
pgbench -i -s 100 --host=localhost --port=5432 \
--username=postgres benchmark_db
# Run benchmark: 50 concurrent clients, 100,000 transactions
pgbench --host=localhost --port=5432 --username=postgres \
--client=50 --jobs=4 --transactions=100000 --time=60 \
benchmark_db
Expected output:
starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 100
query mode: simple
number of clients: 50
number of threads: 4
number of transactions per client: 100000
number of transactions actually processed: 2894502
latency average = 1.036 ms
latency stddev = 0.452 ms
tps = 48241.672239 (including connections establishing)
tps = 48256.901851 (excluding connections establishing)
Custom Workload with Custom Script
# pgbench with a custom SQL script for realistic workload
cat > custom_bench.sql << 'EOF'
\set aid random(1, 1000000)
\set bid random(1, 1000)
\set delta random(-5000, 5000)
BEGIN;
-- Simulate a transaction processing pipeline
SELECT * FROM accounts WHERE aid = :aid;
UPDATE accounts SET abalance = abalance + :delta WHERE aid = :aid;
SELECT abalance FROM accounts WHERE aid = :aid;
INSERT INTO ledger (aid, bid, delta, mtime)
VALUES (:aid, :bid, :delta, CURRENT_TIMESTAMP);
END;
EOF
pgbench --file=custom_bench.sql --client=50 --time=60 benchmark_db
Expected output:
transaction type: custom_bench.sql
number of clients: 50
number of threads: 4
number of transactions actually processed: 1950420
latency average = 1.536 ms
tps = 32507.003412
sysbench (MySQL/Linux)
sysbench is a multi-purpose benchmark tool supporting MySQL, PostgreSQL, and system benchmarks.
# Install sysbench
sudo apt-get install sysbench
# Prepare benchmark data (1 million rows, 8 tables)
sysbench /usr/share/sysbench/oltp_read_write.lua \
--mysql-host=127.0.0.1 --mysql-user=root --mysql-password=secret \
--mysql-db=benchmark_db \
--table-size=1000000 --tables=8 \
prepare
# Run benchmark: 64 threads, 300 seconds
sysbench /usr/share/sysbench/oltp_read_write.lua \
--mysql-host=127.0.0.1 --mysql-user=root --mysql-password=secret \
--mysql-db=benchmark_db \
--table-size=1000000 --tables=8 \
--threads=64 --time=300 --report-interval=10 \
run
# Clean up
sysbench /usr/share/sysbench/oltp_read_write.lua \
--mysql-host=127.0.0.1 --mysql-user=root --mysql-password=secret \
--mysql-db=benchmark_db \
cleanup
Expected output (sample):
[ 10s ] thds: 64 tps: 2850.34 qps: 51301.21 (r/w/o: 35900.85/10260.24/5140.12) lat (ms,95%): 38.15 err/s: 0.00 reconn/s: 0.00
[ 20s ] thds: 64 tps: 2912.18 qps: 52419.24 (r/w/o: 36693.47/10483.85/5241.92) lat (ms,95%): 36.72 err/s: 0.00 reconn/s: 0.00
...
SQL statistics:
queries performed:
read: 10770120
write: 3077172
other: 1538586
total: 15385878
transactions: 769293 (2563.42 per sec.)
queries: 15385878 (51268.42 per sec.)
YCSB (NoSQL Databases)
Yahoo Cloud Serving Benchmark (YCSB) is designed for NoSQL databases like MongoDB, Cassandra, and Redis.
# Download and run YCSB against MongoDB
wget https://github.com/brianfrankcooper/YCSB/releases/download/0.17.0/ycsb-0.17.0.tar.gz
tar -xzf ycsb-0.17.0.tar.gz
cd ycsb-0.17.0
# Load data: 1 million records
./bin/ycsb load mongodb -s -P workloads/workloada \
-p mongodb.url=mongodb://localhost:27017/ycsb \
-p recordcount=1000000 \
-p operationcount=100000
# Run workload A (50% read, 50% update)
./bin/ycsb run mongodb -s -P workloads/workloada \
-p mongodb.url=mongodb://localhost:27017/ycsb \
-p recordcount=1000000 -p operationcount=100000 \
-p threads=50
Expected output:
[OVERALL], RunTime(ms), 60050
[OVERALL], Throughput(ops/sec), 16652.79
[READ], Operations, 50012
[READ], AverageLatency(us), 1812.34
[READ], p99Latency(us), 8521.00
[UPDATE], Operations, 49988
[UPDATE], AverageLatency(us), 2845.67
[UPDATE], p99Latency(us), 12340.00
Latency Distribution Analysis
Understanding latency distribution is more important than average latency.
import matplotlib.pyplot as plt
import numpy as np
# Example: Compare p50, p95, p99 latency across databases
databases = ['PostgreSQL', 'MySQL', 'MongoDB', 'Cassandra']
p50 = [1.2, 1.8, 2.1, 3.5]
p95 = [3.5, 8.2, 12.4, 15.8]
p99 = [15.6, 45.3, 68.2, 120.5]
x = np.arange(len(databases))
width = 0.25
fig, ax = plt.subplots()
ax.bar(x - width, p50, width, label='p50')
ax.bar(x, p95, width, label='p95')
ax.bar(x + width, p99, width, label='p99')
ax.set_ylabel('Latency (ms)')
ax.set_title('Latency Distribution by Database')
ax.set_xticks(x)
ax.set_xticklabels(databases)
ax.legend()
Expected behavior: Databases with similar p50 latency can have dramatically different p99 latency. A database with p50=2ms but p99=200ms is unreliable for user-facing applications.
Warm-up and Steady State
Always warm up the database before measuring. Cold cache benchmarks are misleading.
# BAD: Benchmark immediately after restart (cold cache)
pgbench -c 50 -T 60 benchmark_db
# Result: tps = 500 (cache misses dominate)
# GOOD: Warm up, then benchmark
pgbench -c 50 -T 120 benchmark_db # Warm-up: 2 minutes
pgbench -c 50 -T 300 benchmark_db # Measurement: 5 minutes
# Result: tps = 45000 (steady state)
Common Benchmarking Errors
1. Testing on the Wrong Hardware
Benchmarking on your laptop (8GB RAM, SSD) does not predict production performance (64GB RAM, NVMe RAID). Always benchmark on hardware matching your production target.
2. Forgetting Connection Pool Overhead
Benchmarks that create a new database connection per query show unrealistic numbers. Include Connection Pool overhead (HikariCP, pgBouncer) in your test if you use it in production.
# BAD: Benchmark without connection pooling
# Each request opens/closes a connection â unrealistic
# GOOD: Include pgBouncer in the test path
# Application â pgBouncer â PostgreSQL
# This matches the production architecture
3. Testing Only Average Latency
Average latency hides problems. A system with p50=1ms and p99=5000ms has serious issues despite looking good on average. Always measure p95, p99, and p999.
4. Not Isolating the Benchmark Environment
Running benchmarks on a production server or a shared VM gives unreliable results. Other processes consume CPU, memory, and disk I/O, skewing measurements.
5. Testing Only One Workload Pattern
Real applications have mixed workloads. Test read-only, write-only, and read-write mixes. Use your actual query patterns, not generic benchmarks.
6. Stopping Too Early
Some databases (especially with JIT Compilation or adaptive optimizations) get faster over time. Run benchmarks for at least 10-30 minutes after warm-up.
7. Ignoring Client-Side Bottlenecks
Your benchmark client can become the bottleneck. Monitor CPU, memory, and network on the client machine. Use multiple benchmark clients if needed.
# Monitor client-side resources during benchmark
# If CPU on the benchmark client is 100%, the client is the bottleneck
top -p $(pgrep pgbench)
# Use more client threads spread across multiple machines
# for high-throughput benchmarks
Benchmarking Methodology Checklist
- [ ] Define clear goals (compare databases, tune configuration, find bottleneck)
- [ ] Use production-like hardware and dataset size
- [ ] Warm up the database (2x the benchmark duration minimum)
- [ ] Include connection pool overhead
- [ ] Measure p50, p95, p99, p999 â not just average
- [ ] Test read-only, write-only, and mixed workloads
- [ ] Run each test 3 times and report the median
- [ ] Document every configuration parameter
- [ ] Monitor both server and client resource usage
- [ ] Test with realistic data distribution (not uniform random)
Practice Questions
1. What is the difference between TPS and QPS?
TPS measures complete transactions (which may include multiple queries). QPS measures individual query/statement executions. One Transaction might include 5 queries, so QPS is typically much higher than TPS.
2. Why is p99 latency more important than average latency?
Average latency hides variability. A database with 1ms average could have 99% of requests at 0.5ms and 1% at 50ms. The 1% of slow requests directly impact user experience for unlucky users. p99 shows the worst-case experience.
3. What is a "warm-up" phase in benchmarking?
The warm-up phase runs the workload before measurement starts. This ensures caches are populated, buffer pools are filled, and JIT Compilation is complete. Cold cache benchmarks are not representative of steady-state performance.
4. How does dataset size affect benchmark results?
A small dataset (100MB) fits entirely in memory â benchmarks show unrealistic performance. Production datasets (50GB+) exceed RAM and trigger disk I/O. Always benchmark with datasets at least 2x your available RAM.
5. Challenge: Design a benchmark for a database migration decision.
Your company uses MySQL for an analytics workload and is considering moving to PostgreSQL. Design a benchmark methodology. Answer: Replicate the full production dataset (2TB) to both systems. Create a benchmark script using your actual 10 slowest queries. Run with 50 concurrent users. Measure p50, p95, p99 latency and throughput. Test both with default and tuned configurations. Warm up for 30 minutes, measure for 60 minutes. Repeat 3 times and take the median. Include connection pooling in the test path. Document all configurations for reproducibility.
FAQ
Try It Yourself
Run a complete benchmark comparison between PostgreSQL and MySQL:
- Install both PostgreSQL 16 and MySQL 8 on the same hardware
- Load 10 million rows of similar data into both
- Use pgbench (PostgreSQL) and sysbench (MySQL) with matching workloads
- Run both with default configuration, then with tuned configuration
- Compare TPS, p50, p95, and p99 latency
- Document your findings: which database wins and why?
What's Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro