Pengenalan Masalah Timeout Berantai

Dalam konteks API upload file besar menggunakan Go Fiber, timeout berantai muncul ketika satu permintaan tersendat dan menarik antrean request lainnya. Gejala utamanya adalah timeout handler yang terus naik, goroutine leak, serta antrean request yang menumpuk tanpa diselesaikan. Fokus utama artikel ini adalah menjelaskan bagaimana mendiagnosis dan memperbaiki skenario tersebut secara praktis.

Pertanyaan inti yang dijawab: apa penyebab timeout berantai saat upload file besar di Go Fiber, dan perbaikan nyata apa yang bisa diterapkan untuk menghindari kerusakan berantai? Berikut pendekatan yang telah diuji pada lingkungan produksi.

Gejala Timeout Berantai

Perhatikan gejala berikut saat API upload mulai macet:

  • Handler timeout mulai naik karena permintaan tidak selesai dalam waktu konfigurasi (misalnya 30 detik).
  • Goroutine leak terdeteksi via `runtime.NumGoroutine()` karena goroutine terus menunggu IO tanpa deadline.
  • Antrean request menumpuk dan koneksi klien baru menunggu, yang memperlambat response time secara keseluruhan.

Gejala-gejala ini saling memperkuat; handler pertama yang terlambat menyebabkan koneksi pool habis, lalu request berikutnya menunggu hingga timeout, lalu panic atau restart service.

Investigasi Penyebab Utama

1. Middleware yang Blocking

Middleware seperti autentikasi berbasis disk atau pemindahan data besar tanpa streaming bisa memblokir worker Fiber. Karena Fiber menggunakan pool goroutine terbatas, satu request yang lama menahan worker dan menyebabkan request lain menunggu.

2. Connection Pool Penuh

Jika upload melibatkan panggilan ke database atau layanan eksternal, koneksi akan bertambah di pool. Tanpa batasan, goroutine yang menunggu respon eksternal memegang slot pool, menyulitkan request baru.

3. Tidak Ada Context Cancel

Upload besar sering dilakukan lewat streaming. Apabila handler tidak memonitor context pelanggan (misalnya ctx.Context().Done()), goroutine tetap berjalan meskipun klien memutus sambungan.

Ringkasan Root Cause

  1. Middleware lama/blocking memegang worker secara serial.
  2. Handler upload menunggu resource eksternal tanpa timeout.
  3. Request baru terkunci karena pool goroutine dan koneksi penuh.
  4. Konteks request tidak dibatalkan, menyebabkan goroutine menumpuk.

Langkah Perbaikan Praktis

1. Refactor Handler Upload ke Model Asinkron

Pisahkan penerimaan request dan pemrosesan upload berat. Gunakan worker pool internal dan channel untuk menghindari blocking Fiber worker secara langsung.

func uploadWorker(ctx context.Context, jobs <-chan *fiber.Ctx) {
    for { 
        select {
        case <-ctx.Done():
            return
        case c := <-jobs:
            // gunakan alat streaming untuk baca body dan simpan ke storage
            c.Context().SetBodyStreamWriter(...) // contoh
        }
    }
}

Handler Fiber hanya mendorong request ke channel dan mengembalikan respons awal (misalnya 202 Accepted) atau polling id task. Ini menjaga worker Fiber tetap tersedia.

2. Batasi Konkurensi dan Timeout Eksternal

Gunakan semaphore ketika mengakses resource seperti storage atau database. Terapkan context dengan timeout yang sesuai sebelum memanggil layanan eksternal.

sem := semaphore.NewWeighted(10)

if err := sem.Acquire(ctx, 1); err != nil {
    return c.Status(fiber.StatusTooManyRequests).SendString("maksimum konkurensi tercapai")
}
defer sem.Release(1)

Timeout eksternal dapat ditambahkan dengan context.WithTimeout sehingga koneksi tidak menunggu selamanya.

3. Optimasi Middleware

Usahakan middleware tidak melakukan pekerjaan berat saat upload besar. Misalnya, validasi autentikasi cukup membaca header saja, lalu delegasi ke layanan lain. Jika perlu payload parsing, lakukan setelah handler utama sudah siap memproses.

4. Monitor Context Cancel

Gunakan context Fiber untuk mendeteksi pembatalan klien. Contoh pattern:

ctx, cancel := context.WithCancel(c.Context())
defer cancel()

go func() {
    <-c.Context().Done()
    cancel()
}()

select {
case <-ctx.Done():
    return fiber.ErrRequestTimeout
case result := <-processUpload(...):
    return c.Status(fiber.StatusOK).JSON(result)
}

Ini mencegah goroutine upload terus berjalan setelah klien disconnect.

Verifikasi dengan Benchmark dan Observability

Setelah perbaikan, verifikasi dengan:

  • Benchmark menggunakan hey atau wrk untuk simulasi upload paralel. Fokus pada latency puncak dan jumlah request berhasil.
  • Observability -- pakai Prometheus atau OpenTelemetry untuk memonitor goroutine count, request duration, dan semaphore queue length.
  • Tracing untuk melihat path lengkap request dan deteksi blocking.

Jika latency menurun dan goroutine stabil, itu indikasi perbaikan berhasil.

Tip Mitigasi Jangka Panjang

  • Implementasikan circuit breaker pada dependency eksternal agar request tidak menumpuk saat downstream lambat.
  • Gunakan upload chunked dengan token untuk mencegah satu permintaan mengunci resource terlalu lama.
  • Rancang observability standar yang memberi alert ketika request duration melebihi threshold tertentu atau pool goroutine mendekati limit.
  • Uji beban secara berkala khusus untuk upload besar agar regresi dapat terdeteksi cepat.

Kesimpulan

Timeout berantai di Go Fiber saat upload file besar biasanya disebabkan oleh middleware blocking, limit pool, dan handler yang tidak mempertimbangkan context cancel. Dengan memisahkan proses upload, membatasi konkurensi, menambahkan timeout, serta memperhatikan observability, kita bisa menghentikan siklus timeout berantai dan menjaga API tetap responsif. Mitigasi jangka panjang seperti circuit breaker dan chunked upload memastikan sistem tetap stabil seiring bertambahnya beban.