Pendahuluan langsung ke solusi

Permasalahan utama dalam orkestrasi queue worker terdistribusi adalah menjaga konsistensi data saat job mengalami retry, tutup-tutup job duplikat, dan mendeteksi job yang macet atau kehilangan lock. Artikel ini menjelaskan bagaimana tim Go Fiber menggabungkan mekanisme cache konsisten, lock berbasis Redis, dan observabilitas untuk menjaga alur antrian tetap sehat dan dapat dipulihkan.

Koordinasi cache konsisten dan lock

Go Fiber worker menerima job dari antrian—misalnya Redis Stream atau daftar berprioritas—namun sebelum memproses job perlu memastikan tidak ada proses lain yang sedang menangani job sama. Ini dilakukan dengan asumsi bahwa setiap job punya id unik dan metadata status.

Langkah dasar

  • Ambil job dari queue, lalu tulis flag ke cache Redis (misalnya key "job::lock") menggunakan SETNX untuk memastikan lock eksklusif.
  • Simpan timestamp dan status job di cache (misalnya hashmap) agar worker lain bisa memeriksa apakah job sedang diproses atau sudah selesai.
  • Gunakan TTL pada lock untuk memulihkan jika worker mati tanpa melepas lock.

Cache konsisten dicapai dengan prinsip bahwa perubahan status job di Redis (status, progress, result) dilakukan setelah lock diperoleh dan sebelum job benar-benar selesai. Ini membuat observasi status job bisa dilakukan lewat cache tanpa harus menyentuh datasource utama.

Deteksi job duplikat dan stuck

Duplikat job muncul jika job diproses ulang karena worker gagal melepas lock. Solusi tim Go Fiber melibatkan stage deduplikasi berbasis cache:

  • Sebelum memproses, worker memeriksa field status job di cache. Jika status "processing" dan timestamp tidak melewati ambang batas (misalnya 30 detik), worker baru akan menunggu atau melewati job.
  • Jika status stagnan di "processing" dan timestamp sudah melewati TTL lock, worker bisa menjalankan restart job dengan refresh lock setelah memastikan tidak ada worker lain yang sedang memegang lock.

Untuk job stuck, monitoring daemon memeriksa key job::lock yang TTL-nya habis tapi status tidak berubah. Otomatis, daemon bisa memicu retry atau menjatuhkan job ke status error dengan menulis event pada log dan notifikasi.

Contoh implementasi Go Fiber dan Redis

Berikut potongan implementasi handler Go Fiber untuk memperoleh job, lock, dan memprosesnya:

func processJob(c *fiber.Ctx) error {
    jobID := c.Query("job_id")
    lockKey := fmt.Sprintf("job:%s:lock", jobID)

    acquired, err := redisClient.SetNX(ctx, lockKey, "processing", 30*time.Second).Result()
    if err != nil {
        return fiber.ErrInternalServerError
    }
    if !acquired {
        return c.Status(fiber.StatusConflict).SendString("Job sedang diproses")
    }

    defer redisClient.Del(ctx, lockKey)

    // Update status cache
    redisClient.HSet(ctx, fmt.Sprintf("job:%s:meta", jobID), map[string]interface{}{
        "status": "processing",
        "updated_at": time.Now().Unix(),
    })

    if err := runJobLogic(jobID); err != nil {
        redisClient.HSet(ctx, fmt.Sprintf("job:%s:meta", jobID), "status", "failed")
        return fiber.ErrInternalServerError
    }

    redisClient.HSet(ctx, fmt.Sprintf("job:%s:meta", jobID), "status", "done")
    return c.SendString("Job selesai")
}

Contoh di atas menekankan two-phase update: pertama lock (menghindari race), lalu update status. Retry bisa dilakukan dengan menambahkan middleware yang memanggil endpoint ini lagi setelah delay jika response status conflict.

Observabilitas dan operasional

Observabilitas sangat penting untuk mendeteksi lock stale, cache inconsistency, dan backlog. Praktik yang digunakan:

  1. Metrics: Hitung jumlah job jatuh ke status "processing" lebih lama dari ambang batas, jumlah lock expired, dan waktu antar dequeue.
  2. Logs struktural: Catat job_id, status sebelum dan sesudah processing, serta reason saat retry.
  3. Health check: Daemon terpisah memantau TTL lock dan memicu recovery jika timeout tanpa update.

Untuk recovery, prosedur umum:

  • Jika lock stale terdeteksi, daemon menghapus key lock, menandai job sebagai "pending", lalu mem-publish ulang ke queue.
  • Jika cache inconsistency ditemukan (misalnya status "done" tapi job belum diproses), worker mengeksekusi job audit pada penyimpanan utama untuk sinkronisasi.
  • Backlog antrian ditangani dengan auto-scaling worker atau memprioritaskan job berdasarkan SLA.

Kesimpulan dan trade-off

Pola lock+cache di Go Fiber memberikan kontrol kuat untuk retry dan deteksi duplikat tanpa bergantung pada database transaksi berat. Namun perlu diingat:

  • Lock TTL harus cukup lama untuk job normal, tapi tidak terlalu lama agar recovery cepat.
  • Cache inconsistency bisa terjadi jika worker crash sebelum menyelesaikan update status, sehingga monitoring daemon wajib.
  • Skala Redis harus dipantau karena sangat sentral; gunakan cluster-aware client jika throughput tinggi.

Dengan observabilitas dan prosedur recovery yang matang, tim Go Fiber bisa menjaga queue worker tetap koheren di lingkungan terdistribusi.