Gejala: Latensi tinggi meskipun throughput meningkat

Tim kami menerima laporan bahwa API notifikasi menjadi tidak responsif secara konsisten, walaupun metrik throughput job queue menunjukkan jumlah job yang diproses tetap tinggi. Pengguna akhir mengalami timeout di endpoint – yang secara internal memanggil job Laravel Queue berbasis Redis – sehingga pengiriman email atau push notification terhambat. Pengujian cepat pun menunjukkan bahwa meskipun sistem mampu memproses banyak job per detik, waktu respon untuk request tertentu tetap bertahan lebih dari lima detik.

Dalam pengamatan awal, ada raihan optimasi performa: pemisahan worker menjadi lebih banyak proses, tuning Redis untuk latensi cache, dan menambah jumlah pekerja. Namun, masalah latensi tetap walau pengukuran throughput memperlihatkan angka tinggi. Inilah inti dari pelajaran “When Impressive Performance Gains Do Not Matter” – peningkatan throughput tidak berarti bila bug logika atau kepadatan resource lain belum ditangani.

Investigasi: Data tracing dan log detail

Langkah pertama adalah mengumpulkan data dari tracing request dan job. Kami menambahkan logging waktu masuk dan selesai job di handler Laravel, serta mengaktifkan span distribusi dari tracer internal. Dari log ditemukan bahwa setiap job memanggil layanan pihak ketiga yang melakukan blocking HTTP call, dan beberapa job menunggu selama 15 detik menunggu respons.

Selanjutnya, memeriksa queue retry dan dead-letter memperlihatkan bahwa sejumlah job bertambah terus karena job gagal timeout pada retry kedua. Meskipun worker terus memproses job lain (sehingga throughput tinggi), beberapa job lama menunggu respon layanan eksternal, menyebabkan request API tetap tergantung.

Analisis resource dan dependency

Monitoring CPU dan memori pada worker menunjukkan pemakaian stabil. Namun, ada kelebihan thread blocking akibat Http::timeout(30) default, yang menahan koneksi HTTP hingga waktu timeout tercapai. Ketidakpastian response service eksternal menjadi bottleneck; worker tidak melakukan circuit breaker atau fallback dan justru menyebabkan backlog.

Selain itu, Redis menunjukkan kumpulan job yang berada di antrean “processing” lebih lama dari yang seharusnya karena worker tidak me-release job ketika panggilan HTTP tertahan. Ini berarti optimasi di level worker concurrency tidak menyelesaikan masalah karena akar penyebabnya bukan CPU atau throughput, melainkan dependency call yang melambat.

Root Cause: Blocked external call dan penyusunan retry tanpa timeout adaptif

Root cause diidentifikasi sebagai kombinasi dua hal:

  • Koneksi ke layanan eksternal tidak memiliki timeout pendek atau circuit breaker, membuat worker blok terlalu lama ketika layanan tersebut lambat.
  • Retry job dibuat tanpa backoff adaptif dan memaksa job untuk tetap menumpuk di Redis, menahan resources serta membuat latency API tinggi.

Walaupun kami telah memperjuangkan optimasi throughput, akar masalahnya adalah kerja tunggu eksternal yang tidak dibatasi dan strategi retry yang tidak mempertimbangkan waktu rata-rata resurfacing latency.

Solusi konkret dan perbaikan

Berikut langkah perbaikan yang diterapkan:

  1. Gunakan HTTP timeout dan circuit breaker: Ganti panggilan HTTP default dengan konfigurasi timeout rendah dan integrasikan circuit breaker sederhana agar worker segera mengembalikan error jika layanan eksternal tidak merespons dalam waktu pendek.
  2. Implementasi backoff retry dan job deduplication: Tambahkan mekanisme exponential backoff sebelum retry job, serta tambahkan logika dedup untuk menghindari memproses job identik yang menyebabkan antrian menumpuk.
  3. Gunakan queue chaining untuk deteksi awal: Buat job light-weight pertama yang hanya memvalidasi dependency sebelum menjalankan job utama. Jika dependency lambat, logika ini bisa mem-peringati tanpa memblokir worker utama.
  4. Monitor tracing untuk response time dependency: Gunakan tracing untuk memantau latensi call eksternal; jika latensi naik, otomatis kurangi concurrency worker ke layanan tersebut agar tidak semakin menumpuk.

Perubahan ini mengurangi latensi API secara drastis karena worker tidak lagi memblokir terlalu lama. Optimasi performa tambahan (seperti menambah jumlah worker atau tuning Redis) sekarang hanya meningkatkan kapasitas tanpa menutupi bug yang sebenarnya.

Pembelajaran dari artikel “When Impressive Performance Gains Do Not Matter”

Kasus ini menegaskan pelajaran penting: skalabilitas dan throughput saja tidak menyelesaikan bug; saat dependency utama memiliki latensi menyimpang, penambahan kapasitas menjadi sia-sia. Lebih baik fokus pada root cause, seperti blocking I/O yang tidak terkontrol dan strategi retry yang buruk, sebelum melakukan optimasi performance.

Optimasi performa menjadi berarti hanya ketika sistem sudah stabil secara logika. Jika tidak, efeknya hanya menutupi gejala sementara dan memperpanjang waktu debugging.

Penutup: debug dulu, optimasi kemudian

Debugging backend harus dimulai dari gejala konkret, seperti job yang menunggu respon dependency. Setelah akar masalah ditemukan, perbaikan bisa mencakup timeout, ciruit breaker, atau arsitektur job yang berbeda. Baru kemudian lakukan optimasi performa untuk meningkatkan kapasitas. Memahami batas ini akan menghindarkan tim dari waktu terbuang untuk perbaikan yang tidak menyentuh akar bug.

Semoga studi kasus ini membantu Anda melihat kapan optimasi performa tidak cukup dan mengapa debugging mendalam adalah tahapan pertama yang tak boleh dilewatkan.