Pukul dua siang, pengguna mulai melaporkan checkout gagal, dan tekanan untuk “cepat betulkan” datang dari segala arah. Momen seperti inilah ketika keterampilan diagnosis dibedakan dari keberanian menebak. Chapter penutup section ini merangkai seluruh perangkat observabilitas — log terstruktur, metrics , dan tracing — menjadi playbook diagnosis produksi yang berbasis bukti: dari pola pikir, pemetaan gejala ke pemeriksaan, resep membaca log dengan aman, hingga budaya postmortem yang menjamin insiden yang sama tidak berulang.
Pola Pikir Playbook Diagnosis #
Diagnosis yang baik bergerak dalam empat langkah yang disiplin. Pertama, kumpulkan bukti sebelum berhipotesis: dashboard metrics, aliran log error, dan diff deploy terakhir. Kedua, rumuskan satu hipotesis spesifik yang bisa dibuktikan salah — “lonjakan 5xx bermula dari deploy v2.14.0 yang mengubah validasi stok”, bukan “sepertinya kode error”. Ketiga, uji hipotesis dengan perintah baca-saja: filter log, jalankan EXPLAIN, bandingkan angka antar versi. Keempat, ambil tindakan terkecil yang bisa dipulihkan — rollback satu deploy lebih kecil risikonya daripada hotfix yang menulis ulang logika di tengah tekanan.
Memetakan Gejala ke Pemeriksaan #
Sebagian besar insiden produksi jatuh ke beberapa pola berulang. Tabel ini memetakan gejala yang terlihat ke pemeriksaan pertama yang paling sering menjawab:
| Gejala | Pemeriksaan pertama | Penyebab umum |
|---|---|---|
| Lonjakan respons 5xx | log error per rute, diff deploy terakhir | regression pada deploy baru, dependensi eksternal mati |
| Halaman makin lambat | slow query log, EXPLAIN query teratas | index hilang, query N+1 baru, data tumbuh |
| Antrian job menumpuk | tabel failed_jobs, log worker | job gagal berulang, jumlah worker kurang, poison message |
| CPU server tinggi | status php-fpm, proses termahal | trafik naik, loop boros, request lambat menumpuk |
| Disk cepat penuh | ukuran berkas log, konfigurasi rotasi | level log terlalu rendah, rotasi belum diatur |
Kolom pertama adalah apa yang dilaporkan; kolom kedua adalah ke mana bukti dicari; kolom ketiga hanyalah kandidat — bukan vonis. Peta ini memangkas menit-menit pertama kebingungan menjadi satu tindakan konkret: buka sumber bukti yang paling mungkin bicara.
Membaca Log Produksi dengan Aman #
Log terstruktur JSON memberi kekuatan filter presisi tanpa membuka editor. Berikut sampel enam baris yang akan kita perlakukan sebagai berkas app.log — perhatikan waktunya yang tetap agar setiap hasil resep dapat diverifikasi:
{"waktu":"2026-01-15T08:00:41+07:00","level":"error","pesan":"Gateway timeout","rute":"POST /api/checkout","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}
{"waktu":"2026-01-15T08:00:44+07:00","level":"error","pesan":"Gateway timeout","rute":"POST /api/checkout","trace_id":"b7ad6b7169203331"}
{"waktu":"2026-01-15T08:01:02+07:00","level":"warning","pesan":"Query lambat 2.4 detik","rute":"GET /api/orders","trace_id":"ff00f067aa0ba902b7"}
{"waktu":"2026-01-15T08:01:15+07:00","level":"error","pesan":"Koneksi Redis ditolak","rute":"GET /api/orders","trace_id":"a1b2c3d4e5f60718"}
{"waktu":"2026-01-15T08:01:59+07:00","level":"info","pesan":"Deploy v2.14.0 selesai","rute":"-","trace_id":"-"}
{"waktu":"2026-01-15T08:02:10+07:00","level":"error","pesan":"Gateway timeout","rute":"POST /api/checkout","trace_id":"99aa88bb77cc66dd"}grep menjawab pertanyaan hitung cepat, jq menjawab pertanyaan agregasi. Keempat resep berikut sudah diuji terhadap sampel di atas:
grep -c '"level":"error"' app.log
grep '"level":"error"' app.log | grep -c 'POST /api/checkout'
jq -r 'select(.level == "error") | .waktu[0:16]' app.log | sort | uniq -c
jq -r 'select(.level == "error") | .pesan' app.log | sort | uniq -c | sort -rn | head -5
jq -c 'select(.trace_id == "4bf92f3577b34da6a3ce929d0e0e4736")' app.logResep pertama menghitung 4 baris error; resep kedua menunjukkan 3 di antaranya berasal dari POST /api/checkout; resep ketiga memotong kolom waktu sampai menit lalu mengagregasinya menjadi 2, 1, 1 per menit; resep keempat mengurutkan pesan berdasarkan frekuensi dan menobatkan Gateway timeout dengan 3 kemunculan; resep kelima menarik seluruh baris satu request berdasarkan trace_id. Dari lima perintah ini, bentangan insiden sudah terbaca: error menumpuk di rute checkout, bukan di rute lain, dan satu baris info memperlihatkan deploy v2.14.0 selesai tepat sebelum lonjakan — diff deploy itu pemeriksaan berikutnya.
Untuk agregasi yang lebih terlibat — atau yang perlu diuji sebagai bagian dari tooling internal — logika yang sama dapat ditulis sebagai fungsi PHP murni di atas data terstruktur:
<?php
$barisLog = [
['waktu' => '2026-01-15T08:00:41+07:00', 'level' => 'error', 'pesan' => 'Gateway timeout', 'rute' => 'POST /api/checkout', 'trace_id' => '4bf92f3577b34da6a3ce929d0e0e4736'],
['waktu' => '2026-01-15T08:00:44+07:00', 'level' => 'error', 'pesan' => 'Gateway timeout', 'rute' => 'POST /api/checkout', 'trace_id' => 'b7ad6b7169203331'],
['waktu' => '2026-01-15T08:01:02+07:00', 'level' => 'warning', 'pesan' => 'Query lambat 2.4 detik', 'rute' => 'GET /api/orders', 'trace_id' => 'ff00f067aa0ba902b7'],
['waktu' => '2026-01-15T08:01:15+07:00', 'level' => 'error', 'pesan' => 'Koneksi Redis ditolak', 'rute' => 'GET /api/orders', 'trace_id' => 'a1b2c3d4e5f60718'],
['waktu' => '2026-01-15T08:01:59+07:00', 'level' => 'info', 'pesan' => 'Deploy v2.14.0 selesai', 'rute' => '-', 'trace_id' => '-'],
['waktu' => '2026-01-15T08:02:10+07:00', 'level' => 'error', 'pesan' => 'Gateway timeout', 'rute' => 'POST /api/checkout', 'trace_id' => '99aa88bb77cc66dd'],
];
function hitungLevelPerMenit(array $barisLog, string $level): array
{
$hasil = [];
foreach ($barisLog as $baris) {
if ($baris['level'] !== $level) {
continue;
}
$menit = substr($baris['waktu'], 0, 16);
$hasil[$menit] = ($hasil[$menit] ?? 0) + 1;
}
ksort($hasil);
return $hasil;
}
function pesanTeratas(array $barisLog, string $level, int $jumlah = 2): array
{
$hitung = [];
foreach ($barisLog as $baris) {
if ($baris['level'] === $level) {
$hitung[$baris['pesan']] = ($hitung[$baris['pesan']] ?? 0) + 1;
}
}
arsort($hitung);
return array_slice($hitung, 0, $jumlah, true);
}
echo "Error per menit:\n";
foreach (hitungLevelPerMenit($barisLog, 'error') as $menit => $jumlah) {
echo " $menit -> $jumlah error\n";
}
echo "Pesan error teratas:\n";
foreach (pesanTeratas($barisLog, 'error') as $pesan => $jumlah) {
echo " $jumlah x $pesan\n";
}Output yang Dihasilkan:
Error per menit:
2026-01-15T08:00 -> 2 error
2026-01-15T08:01 -> 1 error
2026-01-15T08:02 -> 1 error
Pesan error teratas:
3 x Gateway timeout
1 x Koneksi Redis ditolakKedua fungsi menghasilkan agregasi yang sama dengan resep jq — substr($baris['waktu'], 0, 16) adalah padanan persis pemotongan .waktu[0:16] — tetapi kini berbentuk kode yang bisa diberi unit test dan digunakan berulang di tooling internal. Perhatikan pula bahwa tidak ada satu pun baris yang diubah: semua perintah dan fungsi di bagian ini bersifat baca-saja terhadap data produksi.
Menyelidiki Query Lambat dengan EXPLAIN #
Ketika gejala adalah halaman yang makin lambat, slow query log adalah pintu masuk: aktifkan long_query_time di server database, ambil query yang paling sering muncul, lalu minta rencana eksekusinya dengan EXPLAIN:
EXPLAIN SELECT * FROM pesanan WHERE user_id = 42 ORDER BY created_at DESC;Rencana eksekusi membocorkan bagaimana database menjalankan query — kolom yang paling menentukan adalah type, key, dan rows:
id | select_type | table | type | possible_keys | key | rows | Extra
1 | SIMPLE | pesanan | ALL | NULL | NULL | 48210 | Using where; Using filesortNilai type = ALL berarti full table scan: database membaca 48.210 baris untuk melayani satu pengguna, dan key = NULL menegaskan tidak ada index yang terpakai. Using filesort menandakan pengurutan dilakukan di luar index — kombinasi klasik query yang sehat saat data kecil lalu melorot saat tabel membesar. Setelah index komposit pada (user_id, created_at) dibuat, rencana yang sehat menunjukkan type = ref, key terisi nama index, dan rows turun drastis ke belasan.
Status php-fpm #
Ketika CPU tinggi atau request antre, halaman status php-fpm memotret kondisi pool proses saat itu. Aktifkan directive pm.status_path = /status (dengan pembatasan akses), lalu buka endpoint tersebut:
pool: www
process manager: dynamic
active processes: 12
idle processes: 8
total processes: 20
max children reached: 0
listen queue: 0
max listen queue: 5
slow requests: 3Tiga angka paling banyak bicara. listen queue yang menetap di atas nol berarti semua proses sibuk dan request baru benar-benar menunggu — sinyal untuk menaikkan pm.max_children atau menemukan request lambat yang mengikat proses. slow requests menghitung request yang melewati request_slowlog_timeout, dan berkas slowlog-nya berisi stack trace proses saat itu — tempat mencari kode yang menggantung. max children reached yang bukan nol mencatat pool pernah kehabisan proses, sehingga pengalihan trafik atau penambahan kapasitas perlu dipertimbangkan. Karena angka-angka ini berdenyut per detik, konsumsi jangka panjangnya lebih tepat dipantau sebagai metrics yang diekspor berkala, bukan dibaca manual.
Memeriksa OpCache #
Untuk halaman yang lambat secara merata, opcache patut diperiksa sebelum menyalahkan kode. Ekstensi Zend OPcache menyimpan bytecode hasil kompilasi di memori bersama; tanpanya, setiap request mengompilasi ulang seluruh berkas PHP. Fungsi opcache_get_status() mengembalikan statistik lengkapnya: memory_used dibanding memory_full (apakah memori opcache cukup), hits dan misses (berapa banyak request dilayani dari cache), serta restarts (berapa sering cache dikosongkan). Hit rate yang sehat di production berkisar sangat tinggi — di atas 99% — dan turun sesaat setelah deploy karena berkas baru harus dikompilasi ulang, itu normal. Kebocoran yang perlu diwaspadai adalah hit rate yang rendah terus-menerus padahal tidak ada deploy: biasanya sinyal memori opcache kurang besar atau jumlah berkas aplikasi tumbuh. Perlu dicatat bahwa fungsi ini hanya tersedia bila ekstensi OPcache aktif di server Anda.
Membandingkan Kanari #
Canary release mengarahkan sebagian kecil trafik ke versi baru sebelum diperluas. Keputusan lanjut atau mundur tidak diambil dari perasaan, melainkan dari perbandingan angka antar versi — dengan memperhitungkan bahwa arah “lebih buruk” tiap metrik berbeda:
<?php
function bandingkanCanary(array $baseline, array $canary, array $arahLebihBuruk, float $ambangPersen = 10.0): array
{
$hasil = [];
foreach ($baseline as $metrik => $nilaiBaseline) {
$nilaiCanary = $canary[$metrik];
$deltaPersen = ($nilaiCanary - $nilaiBaseline) / $nilaiBaseline * 100;
$arahBuruk = $arahLebihBuruk[$metrik] === 'naik' ? $deltaPersen : -$deltaPersen;
$hasil[$metrik] = [
'baseline' => $nilaiBaseline,
'canary' => $nilaiCanary,
'delta_persen' => round($deltaPersen, 1),
'status' => $arahBuruk > $ambangPersen ? 'REGRESI' : 'aman',
];
}
return $hasil;
}
$baseline = [
'error_rate_persen' => 0.5,
'latensi_p95_ms' => 240.0,
'throughput_per_detik' => 120.0,
];
$canary = [
'error_rate_persen' => 2.1,
'latensi_p95_ms' => 249.0,
'throughput_per_detik' => 119.0,
];
$arahLebihBuruk = [
'error_rate_persen' => 'naik',
'latensi_p95_ms' => 'naik',
'throughput_per_detik' => 'turun',
];
foreach (bandingkanCanary($baseline, $canary, $arahLebihBuruk) as $metrik => $h) {
printf(
"%-22s baseline=%-6s canary=%-6s delta=%+6.1f%% %s\n",
$metrik,
$h['baseline'],
$h['canary'],
$h['delta_persen'],
$h['status'],
);
}Output yang Dihasilkan:
error_rate_persen baseline=0.5 canary=2.1 delta=+320.0% REGRESI
latensi_p95_ms baseline=240 canary=249 delta= +3.8% aman
throughput_per_detik baseline=120 canary=119 delta= -0.8% amanPemetaan $arahLebihBuruk membalik tanda delta untuk throughput: turun 0,8% dibaca sebagai perubahan buruk sebesar 0,8%, jauh di bawah ambang 10%. Hasilnya, error rate yang melonjak +320% menjadi satu-satunya REGRESI dan keputusannya jelas — tarik kembali canary itu dan periksa diff kode di sekitar penyebab error. Latensi p95 yang naik tipis dan throughput yang turun tipis justru menegaskan bahwa mesin dan jaringan tidak menjadi tersangka; masalahnya spesifik pada jalur yang gagal.
Budaya Postmortem Tanpa Menyalahkan #
Insiden yang selesai belum benar-benar selesai sampai organisasi belajar darinya. Postmortem adalah dokumen tertulis yang ditulis setelah keadaan pulih, dengan struktur yang sudah teruji:
- Ringkasan — satu paragraf apa yang terjadi, berapa lama, dan siapa yang terdampak.
- Lini masa — urutan kejadian beserta waktu, termasuk tindakan yang diambil (di sinilah catatan saat insiden terpakai).
- Dampak — pengguna, request, pendapatan, atau reputasi yang terpengaruh, dalam angka.
- Akar penyebab — mekanisme teknis yang terbukti, bukan dugaan.
- Tindakan lanjutan — item pencegahan dan deteksi dini, masing-masing dengan pemilik dan tenggat.
Prinsip yang membedakan budaya belajar dari budaya cari kambing hitam adalah blameless: fokus pada sistem, bukan individu. Kalimat “Budi lupa menambah index” ditulis ulang menjadi “proses deploy kita tidak memiliki gerbang pemeriksaan skema database”. Manusia akan selalu melakukan kesalahan kecil; sistem yang baik dirancang agar kesalahan kecil tidak berubah menjadi insiden besar — dan postmortem blameless adalah tempat kelemahan sistem itu diakui jujur tanpa membuat orang berikutnya takut melaporkan.
Checklist Diagnosis Produksi #
Rangkuman berikut siap dijadikan halaman pertama runbook tim Anda. Saat insiden berikutnya datang, jalankan dari atas ke bawah:
- Kumpulkan bukti lebih dulu: dashboard metrics, log error, dan diff deploy terakhir.
- Bentuk satu hipotesis spesifik yang bisa dibuktikan salah.
- Uji hanya dengan perintah baca-saja terhadap data produksi.
- Pisahkan kegagalan fungsional dari masalah performa sejak awal.
- Untuk 5xx, mulai dari log error per rute; untuk lambat, mulai dari slow query log dan
EXPLAIN. - Cek
listen queuedanslow requestsphp-fpm sebelum menambah kapasitas. - Bandingkan canary dengan baseline per metrik, perhatikan arah “lebih buruk” masing-masing.
- Catat setiap tindakan beserta waktunya selama insiden berlangsung.
- Setelah pulih, tulis postmortem blameless dengan tindakan yang punya pemilik.
Ringkasan #
Diagnosis produksi yang andal adalah disiplin, bukan bakat: bukti sebelum hipotesis, hipotesis sebelum tindakan, dan tindakan terkecil yang bisa dipulihkan. Log terstruktur memberi resep pencarian yang murah, metrics dan canary memberi angka pembanding yang jujur, tracing memberi kronologi lintas layanan, dan postmortem blameless mengubah setiap insiden menjadi perbaikan sistem. Dengan seluruh rantai observabilitas ini utuh, kita tidak hanya siap menanggapi masa depan yang bermasalah — kita juga menulis kode yang lebih jarang memicunya, dan kebiasaan itulah yang akan terus terasah di sepanjang sisa perjalanan belajar Anda.