Worker backend yang membaca Redis Stream dari queue X mengalami timeout job secara konsisten. Langsung berangkat dari gejala dan data observasi, artikel ini menjelaskan langkah debugging untuk menemukan apakah penyebabnya adalah mis-acknowledgment, consumer lag, atau pending message, lalu memberikan perbaikan kode serta konfigurasi Redis dan worker agar timeout tersebut hilang.

Konteks Stack dan Gejala Timeout Worker Job

Tim menggunakan stack Go untuk worker, Bun sebagai ORM SQL, dan Redis Stream untuk mengatur pipeline job. Worker melakukan fetch pesan, memanggil layanan eksternal, lalu menyimpan hasilnya ke database. Timeout terjadi pada bagian pemrosesan sebelum worker berhasil XACK pesan.

Gejala yang terlihat: job berjalan hingga beberapa detik, lalu worker mengeluarkan timeout dan reconnect; job tidak dihapus dari stream sehingga selalu muncul kembali. Ini jelas menunjukkan timeout worker job yang kita telusuri.

Langkah Reproduksi Timeout

  1. Publikasikan 10 pesan ke Redis Stream jobs:default dengan payload sederhana.
  2. Start worker Go yang menggunakan XREADGROUP dengan block timeout 5 detik.
  3. Biarkan worker memproses, lalu hentikan service eksternal (misal HTTP downstream) untuk menyebabkan delay.
  4. Worker akhirnya memunculkan timeout dan job tetap berada di stream dengan status pending.

Langkah ini memproduksi kembali timeout dan membuat worker tidak mampu memproses job karena tidak mengeluarkan XACK.

Data Observability: Log, Trace, dan Metric

Observability menjadi kunci. Log worker menyebutkan "processing job db_insert" disusul error timeout dari HTTP client. Trace distributed menunjukkan span HTTP call menunggu 18 detik sebelum timeout, namun span Redis belum selesai karena XACK tidak terpanggil.

Metric yang diamati:

  • Pending messages pada STREAMS.PENDING jobs:default menunjukkan 10 message tidak diselesaikan.
  • Consumer lag via XINFO GROUPS bertambah saat worker timeout.
  • Retry counter di application metric naik karena job selalu diproses ulang.

Gabungan log dan metric jelas menunjuk ke kondisi di mana worker menahan stream entry tanpa menyelesaikannya.

Mendeteksi Root Cause: Mis-ack atau Consumer Lag?

Analisa root cause dimulai dengan memeriksa proses XREADGROUP. Karena worker menunggu respon HTTP, ia tidak memanggil XACK. Saat timeout, pesan tetap pending. Observasi dengan XINFO CONSUMERS menampilkan pending > 0 pada consumer yang perlu diack manual.

Cara mendeteksi:

  • Gunakan XINFO PENDING jobs:default group1 untuk melihat ID message dan lama waktu pending.
  • Trace menunjukkan bahwa worker memegang message > max-processing-time dan tidak merilisnya.
  • Jika banyak pesan pending, ini tanda consumer lag karena worker tidak menyelesaikan job.

Root cause: worker menunggu external API terlalu lama sehingga tidak mencapai XACK, menyebabkan message tetap pending dan akhirnya timeout worker job.

Perbaikan Kode, Konfigurasi Redis, dan Validasi Ulang

Perubahan Kode

Terapkan deadline context dan circuit breaker saat memanggil API eksternal:

ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second)
defer cancel()
resp, err := httpClient.Do(req.WithContext(ctx))
if err != nil {
    log.Error().Err(err).Msg("external call timeout")
    return err
}
// pastikan selalu ack jika sudah selesai
if err := redisClient.XAck(ctx, stream, group, msgID).Err(); err != nil {
    log.Error().Err(err).Msg("failed to ack message")
}

Pastikan XACK hanya dipanggil setelah seluruh langkah selesai sukses atau disertai mekanisme retry serta dead letter.

Penyesuaian Konfigurasi Redis dan Worker

  • Redis: Set stream-max-length sesuai throughput, atur maxmemory untuk menghindari evictions pada stream.
  • Worker: Kurangi block timeout menjadi 2 detik sehingga worker sering mengecek pending, dan tetapkan read-timeout lebih pendek daripada overall worker timeout.
  • Tambahkan proses pending message reclaimer seperti goroutine untuk XCLAIM pesan yang terlalu lama di-hold.

Validasi Ulang

Setelah perbaikan:

  1. Uji ulang skenario timeout dengan menurunkan kecepatan response API.
  2. Periksa XINFO PENDING untuk memastikan angka kembali nol.
  3. Monitor metric job success dan pastikan tidak ada penumpukan worker timeout job.

Validasi ini memastikan worker mampu menyelesaikan job atau me-release pesan agar konsumen lain dapat mengambilnya.

Kesimpulan dan Tips Debugging

Timeout worker job pada Redis Stream seringkali berasal dari penguncian XACK akibat proses downstream lama. Observabilitas, khususnya pending count dan trace waktu, membantu membedakan antara mis-ack, consumer lag, atau masalah resource.

Tips tambahan:

  • Implementasikan per-message timeout sebelum melakukan heavy task.
  • Gunakan XCLAIM untuk menanggulangi worker crash.
  • Selalu log ID pesan saat gagal agar mudah mencocokkan dengan XINFO.

Pendekatan ini menjaga worker tetap responsif sekaligus mempertahankan konsistensi Redis Stream.