Bài này là tham khảo nhanh cho toàn bộ series, không giải thích dài, chỉ lệnh, query, config đã gặp trong 13 bài.

1. PostgreSQL

1.1 EXPLAIN / Analyze

-- Full option (PG 16+)
EXPLAIN (ANALYZE, BUFFERS, VERBOSE, SETTINGS, WAL, FORMAT JSON)
  SELECT ...;

-- Safe UPDATE/DELETE
BEGIN;
EXPLAIN (ANALYZE, BUFFERS) UPDATE ...;
ROLLBACK;

1.2 Monitoring queries

-- Top queries by time
SELECT query, calls, total_exec_time, mean_exec_time, rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC LIMIT 20;

-- Parallel workers (PG 13+)
SELECT leader.pid, leader.query, COUNT(w.pid) AS workers
FROM pg_stat_activity leader
JOIN pg_stat_activity w ON w.leader_pid = leader.pid
GROUP BY leader.pid, leader.query;

-- pg_stat_io (PG 16+)
SELECT backend_type, object, context,
       reads, writes, hits, ROUND(100.0*hits/NULLIF(hits+reads,0),2) AS hit_pct
FROM pg_stat_io;

-- Replication lag (từ primary)
SELECT client_addr, state,
       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes,
       NOW() - reply_time AS lag_time
FROM pg_stat_replication;

-- Replication lag (từ standby)
SELECT pg_is_in_recovery(),
       NOW() - pg_last_xact_replay_timestamp() AS replay_lag;

-- Slot bloat
SELECT slot_name,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),
                      confirmed_flush_lsn)) AS lag
FROM pg_replication_slots;

-- Long-running transactions
SELECT pid, usename, state, query_start,
       NOW() - query_start AS duration, query
FROM pg_stat_activity
WHERE state != 'idle' AND NOW() - query_start > INTERVAL '5 min';

-- Locks
SELECT blocked.pid AS blocked_pid, blocked.query AS blocked_query,
       blocking.pid AS blocking_pid, blocking.query AS blocking_query
FROM pg_stat_activity blocked
JOIN pg_stat_activity blocking ON blocking.pid = ANY(pg_blocking_pids(blocked.pid))
WHERE blocked.state = 'active';

1.3 Index types

-- B-tree (mặc định) cho =, <, >, ORDER BY
CREATE INDEX ON t(col);

-- Covering index (PG 11+)
CREATE INDEX ON t(key) INCLUDE (col1, col2);

-- Partial index
CREATE INDEX ON orders(order_date) WHERE status = 'active';

-- Expression index
CREATE INDEX ON users(lower(email));

-- GIN cho JSONB, array, tsvector
CREATE INDEX ON t USING GIN (jsonb_col jsonb_path_ops);
CREATE INDEX ON t USING GIN (to_tsvector('simple', body));

-- GiST cho ranges, geometry, ltree
CREATE INDEX ON t USING GIST (range_col);

-- BRIN cho time-series / append-only
CREATE INDEX ON events USING BRIN (created_at) WITH (pages_per_range = 32);

-- HNSW cho vector (pgvector 0.5+)
CREATE INDEX ON docs USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

1.4 Config essentials (postgresql.conf)

# Memory (cho máy 32 GB RAM)
shared_buffers = 8GB              # 25% RAM
effective_cache_size = 22GB       # 70% RAM
work_mem = 64MB                   # cho sort/hash
maintenance_work_mem = 2GB        # cho VACUUM/CREATE INDEX

# Write
wal_level = logical               # logical replication + CDC
max_wal_size = 4GB
checkpoint_timeout = 15min
synchronous_commit = on           # = off nếu OK mất vài s khi crash

# Parallelism
max_worker_processes = 16
max_parallel_workers = 8
max_parallel_workers_per_gather = 4

# JIT (PG 11+)
jit = on
jit_above_cost = 500000           # tune cao cho OLTP

# Logging
log_min_duration_statement = 500ms
log_checkpoints = on
log_lock_waits = on
auto_explain.log_min_duration = 1000ms

1.5 PITR (PG 12+)

# Enable trên primary
# archive_mode = on
# archive_command = 'pgbackrest --stanza=main archive-push %p'

# Base backup
pgbackrest --stanza=main backup --type=full

# Restore PITR
pgbackrest --stanza=main --type=time \
  --target="2026-04-15 14:30:00+00" restore

# Tạo file cờ
touch /var/lib/postgresql/16/main/recovery.signal
systemctl start postgresql

2. MySQL / MariaDB

2.1 EXPLAIN

EXPLAIN ANALYZE SELECT ...;      -- MySQL 8.0.18+
EXPLAIN FORMAT=TREE SELECT ...;  -- dễ đọc thứ tự thực hiện

-- Performance Schema
SELECT digest_text, count_star, sum_timer_wait/1e9 AS total_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY sum_timer_wait DESC LIMIT 20;

2.2 Replication (MySQL 8.0.22+)

-- Dùng thuật ngữ replica/source thay master/slave
SHOW REPLICA STATUS\G
START REPLICA;
STOP REPLICA;
CHANGE REPLICATION SOURCE TO SOURCE_HOST='...', SOURCE_LOG_FILE='...';

-- Parallel replication
-- My.cnf replica:
--   binlog_transaction_dependency_tracking = WRITESET
--   replica_parallel_type = LOGICAL_CLOCK
--   replica_parallel_workers = 8
--   replica_preserve_commit_order = ON

2.3 Config essentials (my.cnf)

[mysqld]
# Memory (cho máy 32 GB)
innodb_buffer_pool_size = 22G         # 70% RAM
innodb_buffer_pool_instances = 8
innodb_log_buffer_size = 64M

# Redo log (MySQL 8.0.30+)
innodb_redo_log_capacity = 4G         # thay cho log_file_size * files_in_group

# Flush
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit = 1    # = 2 nếu OK mất vài s khi crash
sync_binlog = 1

# IO
innodb_io_capacity = 2000             # phù hợp SSD gp3
innodb_io_capacity_max = 4000

# Binary logging (cho replication / CDC)
log_bin = mysql-bin
binlog_format = ROW
binlog_row_image = FULL
gtid_mode = ON
enforce_gtid_consistency = ON

# Performance Schema
performance_schema = ON
performance_schema_consumer_statements_digest = ON

2.4 Slow query

-- Bật slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;          -- giây
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- Phân tích
-- pt-query-digest /var/log/mysql/slow.log

3. Redis / Valkey

3.1 Core commands

# Set với TTL (atomic)
SET key value EX 600                    # 600s

# Delete lớn không block
UNLINK bigkey1 bigkey2

# Scan thay vì KEYS (production-safe)
SCAN 0 MATCH user:* COUNT 500

# ACL (Redis 6+)
ACL SETUSER alice on >password ~cache:* +@read +@write
ACL WHOAMI
ACL LIST

# Config online
CONFIG SET maxmemory 4gb
CONFIG SET maxmemory-policy allkeys-lru

3.2 Cache patterns

# TTL jitter + mutex, chống stampede
def get_with_lock(key, recompute, lock_ttl=5):
    v = cache.get(key)
    if v is not None: return v
    if redis.set(f"lock:{key}", "1", nx=True, ex=lock_ttl):
        try:
            v = recompute()
            cache.set(key, v, ttl=3600 + random.randint(-600, 600))
            return v
        finally:
            redis.delete(f"lock:{key}")
    else:
        time.sleep(0.05)
        return get_with_lock(key, recompute, lock_ttl)

# Negative cache
def get_user(uid):
    key = f"user:{uid}"
    v = cache.get(key)
    if v == "__NULL__": return None
    if v: return json.loads(v)
    u = db.users.find_one(uid)
    cache.set(key, json.dumps(u) if u else "__NULL__",
              ttl=60 if not u else 3600 + random.randint(-300, 300))
    return u

3.3 Cluster

# Tạo cluster
redis-cli --cluster create 10.0.0.1:7000 10.0.0.2:7000 10.0.0.3:7000 \
  10.0.0.4:7000 10.0.0.5:7000 10.0.0.6:7000 --cluster-replicas 1

# Thêm node + reshard
redis-cli --cluster add-node 10.0.0.10:7000 10.0.0.1:7000
redis-cli --cluster reshard 10.0.0.1:7000 --cluster-from all \
  --cluster-to <new-id> --cluster-slots 4096 --cluster-yes

# Check
redis-cli --cluster check 10.0.0.1:7000

4. Cassandra / ScyllaDB

-- Data model theo query pattern
CREATE TABLE messages_by_room (
  room_id UUID,
  ts TIMESTAMP,
  msg_id TIMEUUID,
  body TEXT,
  PRIMARY KEY ((room_id), ts, msg_id)
) WITH CLUSTERING ORDER BY (ts DESC, msg_id DESC)
  AND default_time_to_live = 2592000
  AND compaction = {'class': 'TimeWindowCompactionStrategy',
                    'compaction_window_unit': 'DAYS',
                    'compaction_window_size': 1};

-- Consistency level
CONSISTENCY LOCAL_QUORUM;

-- Nodetool, ops đời thường
nodetool status
nodetool compactionstats
nodetool tablehistograms keyspace table
nodetool repair -pr

5. pgvector

CREATE EXTENSION vector;

-- Table
CREATE TABLE docs (id BIGSERIAL, embedding vector(1536));

-- Index
CREATE INDEX ON docs USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

-- Runtime tune
SET hnsw.ef_search = 100;

-- Query (cosine)
SELECT id, 1 - (embedding <=> $1) AS sim
FROM docs ORDER BY embedding <=> $1 LIMIT 10;

-- Hybrid (RRF)
WITH vec AS (SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $1) r FROM docs LIMIT 50),
     kw  AS (SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(...) DESC) r FROM docs LIMIT 50)
SELECT id, SUM(1.0/(60 + r)) AS score
FROM (SELECT * FROM vec UNION ALL SELECT * FROM kw) t
GROUP BY id ORDER BY score DESC LIMIT 10;

6. Outbox / CDC

-- Outbox table
CREATE TABLE outbox (
  id BIGSERIAL PRIMARY KEY,
  aggregate_type TEXT, aggregate_id TEXT,
  event_type TEXT, payload JSONB,
  created_at TIMESTAMPTZ DEFAULT now(),
  processed_at TIMESTAMPTZ
);
CREATE INDEX ON outbox (id) WHERE processed_at IS NULL;

-- Worker polling safely
SELECT id, event_type, payload FROM outbox
WHERE processed_at IS NULL
ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 500;

-- Debezium PG prerequisites
-- postgresql.conf:
--   wal_level = logical
--   max_replication_slots = 4
--   max_wal_senders = 4
CREATE USER debezium WITH REPLICATION PASSWORD '...';
CREATE PUBLICATION dbz_pub FOR TABLE orders, order_items;

7. Benchmark snippets

# pgbench
pgbench -i -s 100 bench
pgbench -c 32 -j 8 -T 600 -P 10 bench

# sysbench
sysbench oltp_read_write --mysql-host=... --threads=64 --time=600 run

# k6 (app-level)
k6 run -u 100 -d 30m script.js

8. Tuning heuristic bỏ túi

Triệu chứngKhả năng caoBước kiểm tra
Query chậm mà nhẹIndex missEXPLAIN ANALYZE, kiểm tra Seq Scan
DB CPU caoQuery heavy / no index / N+1pg_stat_statements, slow log
DB IO caoDataset > RAM, checkpointpg_stat_io, iostat, tăng shared_buffers
Latency spike định kỳCheckpoint / autovacuum / GClog_checkpoints, log_autovacuum
Replica lag tăngReplica yếu, parallel replication offenable parallel replica
Connection exhaustedKhông có poolPgBouncer / ProxySQL / RDS Proxy
Cache miss caoTTL đồng thời, không jitterTTL jitter, mutex, stale-while-revalidate
Vector recall thấpef_search thấptăng ef_search, verify với recall@10

9. Danh sách công cụ ops

Mục đíchCông cụ
Connection pool PGPgBouncer, Supavisor, pgcat
Connection pool MySQLProxySQL, MySQL Router
Schema migrationFlyway, Liquibase, Atlas, Sqitch
Backup PGpgBackRest, WAL-G, Barman
Backup MySQLPercona XtraBackup, mysqldump + binlog
MonitorPrometheus + Grafana, PMM, Datadog DBM
CDCDebezium, Maxwell, pg_easy_replicate
Load testpgbench, sysbench, HammerDB, YCSB, k6
Vector ANNpgvector, Qdrant, Milvus, Weaviate

10. Top 10 anti-patterns cần tránh

  1. SELECT * trong production queries.
  2. WHERE column = ? nhưng function lên column (WHERE lower(email) = ... mà không có expression index).
  3. N+1 query từ ORM.
  4. KEYS pattern trên Redis production.
  5. SET + EXPIRE 2 roundtrip thay vì SET EX.
  6. Dual-write DB + Kafka không qua Outbox/CDC.
  7. Shard key mutable (đổi sau được).
  8. SERIALIZABLE PostgreSQL mà không có retry loop.
  9. Benchmark không warmup / không đủ dài.
  10. Tự viết distributed SQL từ đầu khi có Vitess/Citus.

Đến đây là kết thúc series.

Lưu ý: tất cả config values trong bài này là điểm xuất phát (starting point). Giá trị tối ưu phụ thuộc vào workload, hardware, và data pattern của bạn. Luôn benchmark trước khi áp dụng lên production và thay đổi một biến một lần để đo impact.


Câu hỏi hay gặp

Config này có áp dụng cho cloud DB (RDS, Cloud SQL) không?

Trả lời: Phần lớn có, nhưng một số tham số bị managed service khóa (vd: max_connections trên RDS tính theo instance class). Kiểm tra Parameter Group (AWS) hoặc Database Flags (GCP) để biết tham số nào được phép thay đổi.

Có tool nào tự gợi ý config tối ưu không?

Trả lời: PGTune cho PostgreSQL, MySQLTuner cho MySQL, cả hai là điểm khởi đầu tốt. Nhưng không tin mù quáng: tool dựa trên RAM/CPU, không biết workload thật của bạn. Dùng PGTune → benchmark → tinh chỉnh.

Nên đọc series này theo thứ tự nào nếu chỉ có ít thời gian?

Trả lời: Bài 2 (EXPLAIN/query) → Bài 3 (index) → Bài 14 (cheatsheet này). Ba bài đó cover 80% công việc tối ưu hàng ngày. Bổ sung Bài 5 (transaction) và Bài 9 (monitoring) khi cần.