Metode sistematis mengoptimasi query lambat: membaca EXPLAIN dan EXPLAIN ANALYZE mulai dari sequential scan hingga index scan dan nested loop, mengenali parameter buffer (shared_buffers, work_mem) yang benar-benar berpengaruh, dan urutan langkah tuning dari index sampai analisis plan lewat studi kasus query yang turun dari 40 detik ke milidetik

Backup (episode 5) melindungi data; tuning menjaga data itu tetap cepat. Query lambat adalah keluhan nomor satu yang diterima DBA — dan kabar baiknya, hampir semua kasus bisa dipecahkan dengan metode, bukan tebakan. Kuncinya satu: jangan pernah menebak; baca rencana eksekusi.
PostgreSQL dan MySQL menyediakan alat baca rencana eksekusi: EXPLAIN. Episode ini mengajarkan kalian membaca plan itu, memahami angka-angka penting, dan mengikuti alur tuning yang terbukti: dari menemukan query lambat, menganalisis plan, menambah index/parameter yang tepat, sampai memverifikasi perbaikan.
EXPLAIN menunjukkan rencana eksekusi tanpa menjalankan query; EXPLAIN ANALYZE menjalankan query sekaligus menampilkan waktu nyata:
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT o.id, u.email, o.total_cents
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'pending'
ORDER BY o.created_at DESC;Output khas sebelum ada index yang tepat:
Sort (cost=2500.12..2505.30 rows=2072 width=40)
Sort Key: o.created_at DESC
-> Nested Loop (cost=0.00..2400.00 rows=2072 width=40)
-> Seq Scan on orders o (cost=0.00..1600.00 rows=2072 ...)
Filter: (status = 'pending'::text)
-> Index Scan using users_pkey on users u
Index Cond: (id = o.user_id)Perhatikan kata kunci Seq Scan pada tabel besar — itu tanda bahaya yang artinya database memindai semua baris orders. Ini juga alasan mengapa query ini lambat di produksi.
| Operator | Arti | Perlu perhatian jika |
|---|---|---|
Seq Scan | Scan penuh seluruh tabel | Tabel besar dan sering dipakai |
Index Scan | Scan via index, random access | Biasanya bagus |
Index Only Scan | Semua kolom ada di index | Paling cepat |
Bitmap Heap Scan | Kombinasi index + heap | Bagus untuk selektivitas menengah |
Nested Loop | Join per baris | Mahal jika tabel luar besar |
Hash Join | Join via hash table | Bagus untuk join besar satu-shot |
Sort / Sort Key | Urutan eksplisit | Cek apakah index sudah mencakup urutan |
Kunci membaca: ikuti alur dari bawah ke atas; perhatikan rows= (estimasi) vs baris sebenarnya — jika estimasi meleset jauh dari kenyataan, masalahnya sering statistik usang (ANALYZE belum jalan) atau parameter planner.
Query di atas menjalankan Seq Scan pada orders (2 juta baris). Urutan perbaikannya:
Langkah 1 — buat index yang sesuai pola query (dari pelajaran episode 4: compound (status, created_at)):
CREATE INDEX idx_orders_status_created
ON orders (status, created_at DESC);Langkah 2 — analisis ulang:
EXPLAIN (ANALYZE, BUFFERS)
SELECT o.id, u.email, o.total_cents
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'pending'
ORDER BY o.created_at DESC;Index Scan using idx_orders_status_created on orders o
Index Cond: (status = 'pending'::text)
-> Nested Loop (cost=...) rows=2072 ...Perhatikan: Seq Scan berubah menjadi Index Scan, dan Sort hilang karena index sudah menyediakan urutan created_at DESC. Waktu eksekusi turun dari puluhan detik ke beberapa milidetik — hanya dengan satu index yang pas.
Langkah 3 — jika masih lambat, cek work_mem: hash join dan sort besar memakai work_mem per operasi. Jika plan menunjukkan "spilled to disk", naikkan:
EXPLAIN (ANALYZE, BUFFERS) SELECT count(*) FROM orders
GROUP BY user_id;GroupAggregate ... Sort Method: external merge Disk: 1400kBSort Method: external merge Disk berarti sort terlempar ke disk — naikkan work_mem di postgresql.conf (ingat aturan episode 3: nilai dikali operasi paralel).
Tiga parameter yang paling sering diutak-atik DBA, beserta panduannya:
| Parameter | Peran | Aturan praktis |
|---|---|---|
shared_buffers | Cache halaman bersama | ~25% RAM; di atas itu sering kontraproduktif |
effective_cache_size | Estimasi cache OS + DB | ~75% RAM; memengaruhi pilihan index vs seq scan |
work_mem | Memori per sort/hash | Mulai 4-16 MB; naikkan bertahap bila ada disk spill |
Parameter lain seperti max_parallel_workers_per_gather hanya membantu query paralel tertentu. Prinsip tuning yang jujur: ubah satu parameter, ukur, baru ubah yang lain. Mengubah lima parameter sekaligus membuat kalian tidak tahu mana yang bekerja.
Ini runbook yang bisa kalian pakai untuk query lambat apa pun:
pg_stat_statements, atau keluhan (episode 7).EXPLAIN ANALYZE; catat operator dominan dan rows estimasi vs nyata.ANALYZE).work_mem.Tip
Aktifkan ekstensi pg_stat_statements sejak sekarang: ia merekam query teratas berdasarkan total waktu eksekusi — bahan baku paling jujur untuk menemukan query yang layak di-tuning, tanpa menebak. Ini akan menjadi sekutu kalian di episode 7 dan 21.
EXPLAIN tanpa ANALYZE untuk menilai performa: rencana bisa terlihat sama untuk query yang sangat lambat — selalu pakai EXPLAIN ANALYZE saat menilai waktu nyata.work_mem terlalu besar: dialokasikan per operasi per koneksi; di sistem 200 koneksi, nilai gila bisa menghabiskan RAM.ANALYZE setelah load besar: statistik usang = rencana buruk; jadwalkan ANALYZE otomatis via autovacuum.Inti yang harus dibawa pulang:
EXPLAIN ANALYZE adalah mata DBA: baca operator (Seq Scan vs Index Scan), rows, dan tanda disk spill.shared_buffers, effective_cache_size, work_mem — ubah dengan bukti, bukan tebakan.Di episode 7 selanjutnya kita membangun monitoring & alerting — metrik yang harus dipantau (latency, throughput, koneksi, I/O), memanfaatkan slow query log dan pg_stat_statements, serta membangun dashboard Prometheus + Grafana dan alerting pganalyze. Data tuning kalian butuh data — dan monitoring adalah sumbernya. Sampai jumpa di episode 7!