Debugging backend saat dependensi compute eksternal mendadak lambat biasanya bukan sekadar soal menambah retry atau menaikkan timeout. Dalam sistem produksi, perlambatan pada layanan compute eksternal dapat merambat ke seluruh backend: worker sibuk terlalu lama, antrean menumpuk, retry memperparah beban, koneksi habis, dan akhirnya latensi API utama ikut naik.

Kasus seperti ini makin relevan ketika kapasitas compute besar menjadi komoditas yang diperebutkan. Berita bisnis tentang perusahaan besar yang berlomba mengamankan kapasitas GPU atau compute—misalnya yang sering dibahas media seperti CNBC terkait Google, SpaceX, atau xAI—bukan berarti aplikasi kita pasti terdampak langsung. Namun tren itu mengingatkan satu hal penting: dependensi compute eksternal adalah risiko operasional. Jika backend Anda bergantung pada layanan inference, rendering, scoring, transcoding, atau komputasi berat lain di luar kontrol penuh tim Anda, maka kelambatan mendadak harus diasumsikan sebagai skenario normal yang perlu dihadapi.

Artikel ini membahas studi kasus debugging dari sudut pandang engineer backend: gejala awal, timeline insiden, hipotesis yang sempat salah, cara membaca bukti dari log/metric/trace, menemukan akar masalah, lalu memperbaikinya dengan timeout berjenjang, circuit breaker, backpressure, idempotency, bounded queue, fallback, dan observability.

Studi kasus: ketika compute eksternal melambat, seluruh backend ikut goyah

Bayangkan arsitektur berikut:

  • API publik menerima request dari client.
  • Beberapa endpoint melakukan sinkronisasi data cepat.
  • Untuk operasi berat, backend mengirim job ke queue.
  • Worker queue memanggil layanan compute eksternal untuk memproses data.
  • Hasil compute disimpan ke database lalu status dikembalikan ke client.

Secara normal, pola ini sehat. Masalah muncul ketika layanan compute eksternal tetap available secara teknis, tetapi latensinya naik drastis. Karena tidak benar-benar down, banyak mekanisme proteksi gagal aktif atau justru salah bereaksi.

Gejala yang terlihat di produksi

  • Timeout ke layanan compute eksternal meningkat.
  • Jumlah job di antrean naik terus dan tidak turun.
  • Worker aktif penuh, tetapi throughput turun.
  • Retry otomatis melonjak tajam.
  • Latensi endpoint API yang tampaknya tidak terkait ikut naik.
  • Pool koneksi ke database atau HTTP client mulai penuh.
  • Error rate tidak selalu langsung meledak; kadang yang terlihat pertama justru slow success.

Ini pola yang berbahaya karena tim sering fokus pada error 5xx, padahal akar masalah awalnya adalah latency amplification.

Timeline insiden: bagaimana masalah berkembang

Berikut contoh timeline yang realistis.

T+0 menit: latency compute eksternal naik

Layanan eksternal yang biasanya merespons dalam hitungan ratusan milidetik mendadak menjadi beberapa detik. Belum ada kegagalan total, sehingga health check sederhana masih lolos.

T+5 menit: worker mulai menahan job lebih lama

Setiap worker memproses job lebih lambat. Karena concurrency tetap, kapasitas efektif turun. Queue depth mulai naik.

T+10 menit: retry menambah tekanan

Job yang timeout di-retry otomatis. Jika retry tidak dibatasi dan tidak diberi jitter, sistem menciptakan lalu lintas tambahan ke layanan yang sudah lambat. Akibatnya, beban naik justru saat kapasitas eksternal sedang turun.

T+15 menit: API ikut melambat

Beberapa request API mungkin menunggu status job, membaca tabel yang makin sering di-update worker, atau memakai HTTP client/pool yang sama dengan komponen lain. Resource contention mulai terasa. Dari luar terlihat seperti “API utama bermasalah”, padahal sumbernya adalah dependensi compute.

T+20 menit: antrean berubah dari buffer menjadi jebakan

Queue yang awalnya berfungsi meredam lonjakan trafik kini menampung lebih banyak job daripada yang mampu diselesaikan. Tanpa batas ukuran antrean, backlog terus tumbuh, memperpanjang waktu tunggu, dan membuat hasil akhirnya tidak lagi relevan bagi user.

Hipotesis awal yang salah dan kenapa sering menyesatkan

Dalam insiden seperti ini, beberapa dugaan awal sering terdengar masuk akal tetapi ternyata tidak tepat.

“Database pasti lambat”

Memang database sering ikut panas karena lebih banyak update status, retry bookkeeping, atau polling. Namun database bisa jadi hanya korban sekunder. Jika fokus terlalu cepat ke query tuning tanpa melihat dependency map, tim bisa menghabiskan waktu di tempat yang salah.

“Naikkan jumlah worker saja”

Ini sering memperburuk situasi. Kalau bottleneck ada di layanan compute eksternal, menambah worker hanya menaikkan concurrency ke layanan yang sedang lambat. Hasilnya: lebih banyak timeout, lebih banyak koneksi terbuka, dan backlog tumbuh lebih cepat.

“Naikkan timeout supaya lebih banyak request sukses”

Timeout yang lebih panjang memang kadang menurunkan error jangka pendek, tetapi sering memperparah resource starvation. Thread, goroutine, event-loop task, koneksi HTTP, dan slot worker akan tertahan lebih lama. Secara sistemik, ini menaikkan tail latency dan membuat efek rambatan lebih luas.

“Retry harus diperbanyak supaya lebih andal”

Retry tanpa kontrol adalah pola kegagalan klasik. Ketika akar masalahnya adalah kapasitas yang menurun atau latency yang melonjak, retry agresif sama saja dengan menyerang ulang komponen yang sedang kesulitan.

Bukti dari log, metrics, dan traces

Root cause yang benar biasanya baru terlihat jika ketiga sumber observabilitas ini dibaca bersama. Mengandalkan salah satu saja sering menimbulkan bias.

1. Metrics: cari perubahan bentuk, bukan hanya angka error

Mulailah dari empat sinyal dasar: latency, traffic, errors, saturation.

  • Latency: perhatikan p95/p99 call ke compute eksternal, bukan hanya rata-rata.
  • Traffic: lihat apakah jumlah panggilan ke eksternal naik karena retry.
  • Errors: bedakan timeout, connection reset, 5xx, dan client-side cancellation.
  • Saturation: cek queue depth, jumlah worker sibuk, panjang backlog, ukuran connection pool, dan CPU/memori.

Pola yang mencurigakan biasanya seperti ini:

  • Latency ke layanan eksternal naik lebih dulu.
  • Queue depth naik beberapa menit sesudahnya.
  • Retry rate naik setelah timeout threshold terlewati.
  • Latency API publik naik paling akhir.

Urutan ini penting karena membantu membedakan penyebab dan dampak.

2. Logs: korelasikan timeout, retry, dan request ID

Log yang berguna bukan log verbose acak, tetapi log terstruktur yang dapat dikorelasikan. Minimal, setiap log outbound call perlu memuat:

  • request_id atau trace_id
  • job_id
  • dependency name
  • timeout value
  • attempt number
  • duration
  • result: success, timeout, cancelled, upstream_5xx

Contoh log terstruktur:

{
  "ts": "2026-06-08T10:15:01Z",
  "trace_id": "4b12...",
  "job_id": "job_9812",
  "dependency": "external-compute",
  "attempt": 2,
  "timeout_ms": 3000,
  "duration_ms": 3004,
  "result": "timeout"
}

Dari sini, Anda bisa menjawab pertanyaan penting:

  • Apakah timeout terjadi di attempt pertama atau baru setelah retry?
  • Apakah semua timeout berada dekat angka timeout yang dikonfigurasi?
  • Apakah durasi meningkat bertahap atau langsung melonjak?
  • Apakah worker menghabiskan sebagian besar waktu untuk menunggu I/O?

3. Trace: lihat rantai dampaknya

Distributed tracing sangat membantu untuk melihat bahwa request API A memicu job B, job B memanggil compute eksternal, lalu callback atau polling memperpanjang durasi request terkait. Trace juga membantu menemukan fan-out tersembunyi, misalnya satu request API ternyata menghasilkan beberapa panggilan compute sekaligus.

Dalam banyak insiden, trace menunjukkan fakta yang tidak terlihat dari dashboard ringkas: komponen yang tampak independen ternyata berbagi dependency atau resource pool yang sama.

Root cause: bukan sekadar timeout, tetapi kegagalan desain pengendalian beban

Pada studi kasus ini, akar masalah umumnya bukan hanya “layanan eksternal lambat”. Itu hanya pemicu. Root cause sistemik biasanya kombinasi beberapa hal:

  • Timeout terlalu panjang atau tidak berjenjang.
  • Retry otomatis tanpa batas yang ketat, tanpa jitter, dan tanpa awareness terhadap jenis error.
  • Queue tidak dibatasi sehingga backlog tumbuh tanpa kontrol.
  • Worker concurrency tidak disesuaikan dengan kapasitas nyata dependency.
  • Tidak ada circuit breaker untuk menghentikan banjir request saat dependency sedang buruk.
  • Tidak ada fallback atau degradasi layanan.
  • Observability tidak memisahkan latency internal dan latency dependency eksternal.

Dengan kata lain, layanan eksternal yang melambat hanyalah percikan. Kebakaran besar terjadi karena backend tidak memiliki mekanisme pembatasan dan isolasi kegagalan yang memadai.

Perbaikan yang bisa langsung diterapkan

1. Terapkan timeout berjenjang, bukan satu timeout besar

Jangan gunakan satu angka timeout besar untuk seluruh operasi. Pisahkan setidaknya:

  • timeout koneksi
  • timeout baca/respons
  • deadline total request atau job

Kenapa ini penting? Karena kegagalan koneksi dan respons lambat adalah masalah yang berbeda. Koneksi yang macet perlu diputus cepat. Respons yang lambat mungkin masih diberi toleransi lebih kecil dari SLA internal. Selain itu, deadline total mencegah pekerjaan hidup terlalu lama dan menahan resource.

Contoh pseudocode:

deadline_total = 5s
connect_timeout = 300ms
read_timeout = 2s

result = callExternalCompute(
  payload,
  connectTimeout=connect_timeout,
  readTimeout=read_timeout,
  overallDeadline=deadline_total
)

Trade-off: timeout terlalu agresif bisa menurunkan success rate saat dependency hanya sedikit lambat. Timeout terlalu longgar memperburuk saturation. Nilainya harus diturunkan dari data latency aktual, terutama p95/p99 yang sehat.

2. Tambahkan circuit breaker untuk menghentikan spiral kegagalan

Circuit breaker mencegah backend terus mengirim request ke dependency yang sedang gagal atau terlalu lambat. Saat ambang kegagalan terlampaui, breaker membuka sementara dan request baru langsung gagal cepat atau dialihkan ke fallback.

Pola sederhananya:

  • Closed: traffic normal.
  • Open: request ke dependency dihentikan sementara.
  • Half-open: hanya sebagian kecil request uji yang diizinkan.

Ini bekerja karena memotong umpan balik negatif: lebih sedikit request tertahan, lebih sedikit koneksi terbuka, dan worker bisa kembali memproses hal lain.

Kesalahan umum: circuit breaker dipasang, tetapi fallback-nya tetap melakukan polling atau retry ke dependency yang sama melalui jalur lain. Hasilnya, breaker terlihat aktif namun beban aktual tidak turun.

3. Gunakan backpressure, bukan menerima semua beban

Jika input datang lebih cepat daripada kapasitas proses, sistem harus memberi sinyal “cukup”. Backpressure bisa diterapkan di beberapa lapisan:

  • batasi jumlah job baru per tenant atau per endpoint
  • tolak request baru saat queue depth melewati ambang
  • kurangi concurrency worker untuk dependency tertentu
  • prioritaskan job yang lebih penting atau lebih cepat selesai

Backpressure lebih sehat daripada membiarkan antrean tumbuh tanpa batas, karena backlog panjang sering hanya menunda kegagalan sambil memperparah dampaknya.

4. Jadikan queue bounded, bukan penampung tak terbatas

Queue sangat berguna sebagai buffer, tetapi buffer harus punya batas. Bounded queue memaksa Anda menentukan perilaku saat sistem penuh:

  • drop job prioritas rendah
  • reject request dengan status yang jelas
  • simpan untuk diproses nanti dengan SLA berbeda
  • coalesce pekerjaan duplikat

Tanpa batas, backlog bisa menjadi utang operasional yang menunda pemulihan bahkan setelah dependency eksternal kembali normal.

5. Pastikan retry aman, terbatas, dan sadar konteks

Retry hanya layak untuk kegagalan yang benar-benar sementara. Terapkan aturan berikut:

  • batasi jumlah retry dengan keras
  • gunakan exponential backoff
  • tambahkan jitter agar retry tidak serempak
  • jangan retry semua jenis error
  • hormati deadline total operasi

Contoh pseudocode retry yang lebih aman:

max_attempts = 3
base_delay = 200ms

for attempt in 1..max_attempts:
  resp = call_external()
  if resp.success:
    return resp

  if not is_retryable(resp.error):
    break

  sleep(with_jitter(base_delay * 2^(attempt-1)))

return failure

Kesalahan umum: retry dilakukan di banyak lapisan sekaligus—misalnya HTTP client, service layer, dan queue worker. Jika tiap lapisan melakukan 3 kali retry, total request ke dependency bisa membengkak secara eksponensial.

6. Terapkan idempotency agar retry tidak merusak data

Saat retry diperlukan, operasi harus aman diulang. Idempotency mencegah duplikasi efek samping seperti transaksi ganda, penulisan hasil ganda, atau callback ganda.

Praktik yang umum:

  • gunakan idempotency key untuk tiap job atau request bisnis
  • simpan status pemrosesan berdasarkan key tersebut
  • pastikan worker dapat mendeteksi bahwa hasil untuk key yang sama sudah pernah diproses

Contoh skema sederhana:

idempotency_key = hash(user_id + document_id + operation_type)

if already_processed(idempotency_key):
  return previous_result

mark_processing(idempotency_key)
result = do_work()
save_result(idempotency_key, result)

Tanpa idempotency, retry yang tampak “aman” bisa berubah menjadi insiden data integrity.

7. Sediakan fallback atau mode degradasi layanan

Tidak semua fitur harus gagal total saat compute eksternal lambat. Beberapa alternatif yang sering berguna:

  • kembalikan status accepted dan proses asinkron, bukan menunggu hasil sinkron
  • sajikan hasil cache lama dengan penanda bahwa data belum terbaru
  • gunakan model atau proses yang lebih ringan dengan kualitas lebih rendah
  • nonaktifkan fitur tambahan yang tidak kritis

Fallback yang baik harus jujur terhadap user dan tidak menyamarkan data usang sebagai hasil final.

8. Perkuat observability di level dependency

Jika dashboard hanya menampilkan latensi endpoint API, Anda terlambat. Pisahkan metrik untuk:

  • outbound latency per dependency
  • success/error/timeout per dependency
  • retry count per dependency
  • queue depth, queue age, dan processing duration
  • worker concurrency dan utilization
  • circuit breaker state
  • fallback usage rate

Tambahkan alert yang memicu bukan hanya saat error tinggi, tetapi juga saat queue age atau timeout ratio naik melewati ambang tertentu.

Contoh alur implementasi yang lebih tahan insiden

Berikut contoh pola alur yang lebih sehat untuk worker yang memanggil compute eksternal:

  1. Terima job dengan idempotency key.
  2. Cek apakah hasil sudah ada atau masih diproses.
  3. Jika queue melewati ambang, tolak job prioritas rendah atau jadwalkan ulang.
  4. Sebelum memanggil dependency, cek status circuit breaker.
  5. Lakukan panggilan dengan timeout koneksi, timeout baca, dan deadline total.
  6. Jika gagal karena error yang dapat di-retry, lakukan retry terbatas dengan backoff + jitter.
  7. Jika gagal terus, simpan status gagal yang jelas dan aktifkan fallback bila tersedia.
  8. Publikasikan metric dan trace pada setiap tahap.

Contoh pseudocode ringkas:

function processJob(job):
  if isDuplicate(job.idempotencyKey):
    return loadPreviousResult(job.idempotencyKey)

  if queueIsOverloaded() and job.priority == "low":
    return rejectOrDefer(job)

  if circuitBreaker.isOpen("external-compute"):
    return fallbackResult(job)

  try:
    result = retryWithBackoff(maxAttempts=3, deadline=5s, fn=() => {
      return externalCompute.call(
        job.payload,
        connectTimeout=300ms,
        readTimeout=2s
      )
    })

    saveResult(job.idempotencyKey, result)
    return result
  catch TimeoutError:
    recordFailure(job, "timeout")
    return fallbackResult(job)
  catch NonRetryableError:
    recordFailure(job, "non_retryable")
    throw

Angka pada contoh di atas hanyalah ilustrasi pola, bukan rekomendasi universal. Nilai aktual harus disetel berdasarkan SLA bisnis, profil latency dependency, dan kapasitas worker Anda.

Checklist RCA untuk insiden dependency compute eksternal

Setelah insiden reda, lakukan root cause analysis yang benar-benar operasional. Jangan berhenti di kalimat “provider eksternal lambat”. Gunakan checklist berikut:

Scope dan dampak

  • Layanan atau endpoint mana saja yang terdampak?
  • Apakah dampaknya hanya latency, atau juga kehilangan data, duplikasi, dan timeout user?
  • Tenant atau segmen trafik mana yang paling terdampak?

Timeline

  • Kapan latency dependency mulai naik?
  • Kapan queue depth mulai menanjak?
  • Kapan retry meledak?
  • Kapan API publik mulai terpengaruh?
  • Kapan mitigasi pertama diterapkan, dan apa efeknya?

Bukti teknis

  • Metrik apa yang membuktikan urutan sebab-akibat?
  • Log apa yang menunjukkan timeout, retry, atau circuit breaker state?
  • Trace mana yang memperlihatkan jalur kritis dan resource contention?

Akar masalah sistemik

  • Apakah timeout terlalu longgar atau terlalu seragam?
  • Apakah retry terjadi di lebih dari satu lapisan?
  • Apakah queue tidak dibatasi?
  • Apakah tidak ada mekanisme backpressure?
  • Apakah fallback tidak ada atau tidak berfungsi?
  • Apakah observability tidak cukup detail?

Tindakan pencegahan

  • Konfigurasi apa yang harus diubah?
  • Dashboard dan alert apa yang harus ditambah?
  • Test kegagalan apa yang harus dimasukkan ke staging atau game day?
  • Runbook apa yang perlu diperjelas?

Pelajaran penting untuk developer backend

  • Jangan menganggap dependency eksternal selalu stabil. Bahkan layanan besar yang jarang down bisa mengalami degradasi latency.
  • Slow dependency lebih sulit dari dependency down. Saat masih menjawab sebagian request, sistem sering masuk zona abu-abu yang memicu backlog dan retry storm.
  • Retry bukan fitur keandalan jika tidak dibatasi. Retry yang salah adalah pengganda kegagalan.
  • Queue bukan solusi tanpa batas. Queue hanya memindahkan tekanan waktu, bukan menghilangkan bottleneck.
  • Observability harus mengikuti dependency map. Jika Anda tidak bisa melihat outbound latency per dependency, Anda sedang buta terhadap sumber masalah utama.
  • Desain untuk degradasi yang terkendali. Lebih baik menolak sebagian pekerjaan secara jelas daripada membiarkan seluruh sistem melambat.

Penutup

Debugging backend saat dependensi compute eksternal mendadak lambat menuntut cara pikir sistemik. Fokusnya bukan hanya pada request yang timeout, tetapi pada bagaimana latency eksternal menjalar menjadi queue backlog, retry storm, resource contention, dan akhirnya kegagalan layanan yang lebih luas.

Jika Anda hanya memperbaiki satu angka timeout, insiden serupa kemungkinan akan kembali. Yang lebih efektif adalah membangun lapisan proteksi yang saling melengkapi: timeout berjenjang, circuit breaker, backpressure, bounded queue, retry yang disiplin, idempotency, fallback, dan observability yang memadai. Dengan pendekatan ini, dependency eksternal yang melambat tidak harus berubah menjadi insiden besar di seluruh backend.