Masalah utama saat mengoperasikan worker queue di Express.js adalah menjamin konsistensi data ketika banyak job berjalan paralel. Artikel ini membahas cara menggabungkan antrean tugas (Bull MQ atau Bee-Queue) dengan cache Redis dan mekanisme locking agar hasil job tidak saling tumpang tindih, sekaligus menyediakan pola observabilitas untuk deteksi cepat deadlock cache atau job stuck.
1. Memilih pola antrean tugas yang sesuai
Untuk kasus worker background yang memproses task berulang, Bull MQ dan Bee-Queue adalah pilihan populer karena penanganan timeout, retry, dan persistence. Pilih Bull MQ ketika butuh fitur lengkap seperti job rate limiting, repeatable jobs, dan event-driven monitoring. Gunakan Bee-Queue bila Anda mengejar latency rendah dan kontrol sederhana.
Contoh konfigurasi worker Bull di Express:
const Queue = require('bull');
const jobQueue = new Queue('jobs', { redis: { host: 'redis', port: 6379 } });
jobQueue.process(async (job) => {
// pastikan cache dan locking sudah di-handle sebelum menjalankan logika
});
app.post('/enqueue', async (req, res) => {
await jobQueue.add(req.body, { attempts: 3, backoff: 5000 });
res.status(202).send('Job diterima');
});
Setting attempts dan backoff mencegah job hilang akibat kegagalan sementara tanpa perlu logika retry manual.
2. Sinkronisasi cache Redis dan locking
Cache Redis mempercepat query, namun tanpa koordinasi akan timbul race condition saat worker menulis data yang sama. Gunakan key-based locking sebelum memodifikasi state penting:
- Locking dengan Redis: implementasi sederhana menggunakan SETNX dengan TTL agar lock otomatis dilepas jika worker crash.
- Cache aside: worker membaca dari cache sebelum menyusup database, mengubah data di database, lalu menghapus cache setelah commit.
Contoh alur:
- Worker menerima job, mencoba lock:
SET lock:user:123 <token> NX PX 5000. - Jika berhasil, baca cache. Jika cache kosong, ambil dari DB dan set cache dengan TTL.
- Selesaikan job, update DB, dan del cache agar entry lama tidak dipakai.
- Release lock dengan memeriksa token, agar tidak melepaskan lock milik worker lain.
Gunakan helper seperti redlock untuk menghindari manual token management. Lock TTL harus lebih besar dari estimasi waktu job agar tidak kadaluwarsa lebih dulu.
3. Strategi invalidasi cache untuk konsistensi
Cache invalidation sebaiknya dilakukan after-write, bukan before-read. Ketika worker berhasil menulis ke database:
- Hapus cache terkait dengan
DEL cache:key. - Jika operasinya multi-record, gunakan
pipelineRedis untuk mengurangi round trip. - Untuk data yang sering dibaca sekaligus diupdate, pertimbangkan versioning di cache (misalnya key tambahan untuk versi) agar invalidasi menjadi sederhana.
Jika Anda tidak ingin cache kosong menyebabkan latency besar, gunakan background job prefetch yang memanaskan cache setelah invalidasi.
4. Retry aman dan deteksi job stuck
Retry harus mempertimbangkan side-effect. Gunakan idempotent job atau track status sebelum memulai operasi kritis. Beberapa praktik:
- Catat progress di database atau Redis, sehingga job yang gagal bisa melanjutkan dari checkpoint.
- Gunakan Bull event
faileduntuk men-trigger alert jika job gagal karena timeout atau lock. - Jika job mengalami stuck, misalnya lock tidak dilepas, gunakan watchdog yang memeriksa lock TTL dan mengirim notifikasi.
Pastikan job timeout lebih besar dari TTL lock agar tidak terjadi deadlock karena lock otomatis hilang namun job masih berjalan.
5. Observabilitas: metric, logging, dan alert
Monitor queue dan cache untuk mendeteksi masalah operasional:
- Metric: pantau antrean panjang (
waiting), job yang gagal, durasi job, dan hit ratio cache. Gunakan Prometheus exporter dari Bull atau custom metric yang mengukur retries. - Logging: sertakan job ID, status lock, dan error stack trace. Tandai ketika job menunggu lock > threshold.
- Alert: atur alert ketika cache hit ratio turun drastis (mungkin cache stale), atau queue depth terus meningkat tanpa penyelesaian.
Untuk deteksi deadlock cache, monitor key lock aktif dan TTL-nya. Jika TTL terlalu pendek dibanding durasi job, alarmkan tim karena bisa menyebabkan job stuck.
6. Penutup dan praktik terbaik
Integrasi queue, cache, dan locking di Express.js memerlukan desain ulang alur worker agar tetap konsisten di skala besar. Terapkan idempotensi job, invalidasi cache setelah commit, dan observabilitas lengkap. Dengan Bull/Bee Queue sebagai orchestrator job dan Redis sebagai cache serta lock store, tim backend bisa menjaga throughput tinggi sekaligus deteksi dini masalah konsistensi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!