Masalah job ganda muncul ketika worker Redis Queue mengambil job yang sama lebih dari sekali karena restart, timeout, atau retry otomatis. Solusi cepat adalah menambahkan distributed lock pada job yang sedang diproses, agar worker lain tidak mengeksekusi ulang job itu sebelum lock dilepas.
Dalam artikel ini kita langsung membahas bagaimana distributed locking berbasis Redis (SETNX/Redlock) mencegah duplikasi tanpa mengorbankan throughput, lengkap dengan diagram arsitektur, langkah implementasi TTL, mekanisme retry aman, monitoring, serta trade-off ketersediaan vs konsistensi.
Mengapa job queue Redis bisa jalan ganda saat worker restart atau retry
Redis queue bekerja dengan pola "ambil job" (BLPOP/BRPOP) lalu eksekusi. Jika worker tiba-tiba restart sebelum menandai job selesai, job tersebut tetap berada di luar queue atau malah di-ack tapi belum selesai. Ketika worker lain mulai memproses ulang job yang sama, terjadi duplikasi. Retry otomatis (misal queue consumer mencoba ulang setelah timeout) memperbesar kemungkinan ini.
Kuncinya: Redis tidak punya mekanisme built-in untuk memastikan hanya satu worker yang mengerjakan job secara eksklusif, terutama jika job tidak idempotent. Distributed lock memberi jaminan bahwa hanya satu worker yang memegang akses eksekusi pada waktu tertentu.
Arsitektur queue-worker-lock
Diagram sederhana alur:
Job Producer -> Redis Queue -> Worker A ----+
| Lock (SETNX job_id)
+-> Worker executes job
If success: DEL lock, ack job
+-> Worker B waits, melihat lock -> skipSetiap worker sebelum mulai eksekusi menulis lock ke Redis dengan kunci unik (misal job:id) dan TTL. Jika lock sudah ada, worker lain menganggap job sedang diproses dan tidak menjalankannya.
Komponen utama
- Producer: menulis job ke queue Redis dengan payload termasuk job_id.
- Worker: saat job diambil, mencoba SETNX job:id dengan TTL singkat dan nilai unik (UUID).
- Lock: berbasis Redis SETNX + EXPIRE atau Redlock untuk cluster.
Langkah implementasi kunci dengan TTL
Gunakan pola berikut agar lock tidak menyumbat sistem bila worker mati:
- SETNX job:id dengan UUID. Jika berhasil (nilai 1), worker lanjut. Jika gagal, worker skip job karena sedang diproses.
- Set TTL (misal 30 detik) saat sukses menulis lock. Gunakan satu command Redis (
SET job:id "uuid" NX PX 30000) jika didukung. - Selama eksekusi, worker bisa memperpanjang TTL jika perlu, tapi jangan perpanjang terlalu sering—cukup sebelum TTL habis.
- Jika worker selesai sukses, cek nilai lock sama dengan UUID sendiri, lalu
DEL job:id; ini mencegah worker lain menghapus lock orang lain. - Jika job gagal, jangan langsung hapus lock; biarkan TTL habis lalu biarkan parent queue melakukan retry.
Contoh pseudo-code Python (redis-py):
lock_key = f"job_lock:{job['id']}"
lock_value = str(uuid.uuid4())
acquired = redis.set(lock_key, lock_value, nx=True, px=30000)
if not acquired:
return # job sedang diproses
try:
process(job)
if redis.get(lock_key) == lock_value:
redis.delete(lock_key)
finally:
redis.delete(lock_key) # guard turunan jika masih milik kitaGunakan script Lua jika perlu menghapus hanya bila value cocok, untuk menjaga atomicity.
Retry aman dan fallback
Untuk retry aman:
- Biarkan sistem queue sendiri yang menangani retry (misal menggunakan
retry queueatau penjadwalan ulang job). - Jangan reset lock secara manual; biarkan TTL habis lalu job muncul lagi di queue.
- Catat setiap retry dalam metadata dan gunakan deduplication key agar worker memahami apakah job sudah pernah dijalankan.
- Jika job terus gagal karena lock tidak dilepas, periksa apakah TTL terlalu panjang atau worker mati tanpa melepaskan lock.
Monitoring: latency, deadlock, dan retried job
Untuk menjaga sistem sehat:
- Latency: pantau berapa lama job menunggu lock. Jika lama, ada potensi worker lain menahan lock terlalu lama.
- Deadlock: gunakan monitoring TTL expired. Jika lock tidak dilepas karena worker mati, jumlah lock dengan TTL habis tapi job masih retry bisa jadi indikasi masalah.
- Retrie job: gunakan counter retry; jika job melebihi batas, kirim alert manual atau pindahkan ke dead letter queue.
Selain metrik, tambahkan log ketika lock gagal diambil dan ketika lock dilepas karena timeout—ini membantu debug duplikasi.
Trade-off ketersediaan vs konsistensi
Distributed lock menambah konsistensi (mencegah job parallel), namun bisa mengorbankan ketersediaan: jika Redis tidak tersedia, worker tidak dapat memproses job. Pastikan:
- Gunakan timeouts pendek pada SETNX agar worker tidak menunggu terlalu lama.
- Jika menggunakan Redlock, pahami bahwa konsistensi meningkat tapi masih bergantung pada quorum Redis.
- Siapkan fallback (misal log error dan retry nanti) jika lock tidak bisa diperoleh.
Keputusan: jika job tidak idempotent dan duplikasi berbahaya, konsistensi lebih penting; jika throughput tinggi dan duplikasi bisa ditoleransi, mungkin lock tidak perlu.
Checklist operasional menjaga sistem tetap sehat
- Pastikan TTL lock cukup panjang untuk job eksekusi normal, tapi tidak terlalu panjang untuk menghindari deadlock.
- Monitor jumlah lock aktif, expired lock, dan job yang retry lebih dari ambang batas.
- Gunakan script Lua untuk memastikan delete lock hanya dilakukan oleh pemilik awal.
- Audit worker restart: pastikan graceful shutdown menghapus lock sebelum berhenti.
- Siapkan alert Redis down, timeout koneksi, dan queue backlog sebagai early warning.
Dengan pola distributed lock yang tepat, duplikasi job di queue Redis bisa dicegah tanpa menahan throughput, sambil tetap menjaga operasional mudah dipantau dan teupl.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!