1. Menu Kita Hari Ini & Alat yang Perlu Disiapkan
Pernahkah Anda mendapati tagihan server bengkak atau laporan dari tim keuangan bahwa proses invoice bulanan berjalan tiga kali lipat secara bersamaan? Di ruang server, ini adalah pemandangan klasik yang kerap bikin jantungan. Masalahnya sepele namun mematikan: kita menaruh script cron standar di /etc/cron.d/ pada tiga node backend berbeda di belakang load balancer Nginx. Ketika jarum jam menunjuk pukul 00:00, ketiga server tersebut serentak mengeksekusi tugas yang sama. Hasilnya? Race condition, database jebol, dan kita sibuk memadamkan kebakaran.
Dalam 15 menit ke depan, kita akan merakit sebuah resep arsitektur terdistribusi kelas enterprise. Kita akan menggabungkan kekuatan Redis sebagai distributed lock berkecepatan tinggi dan Node.js/Go sebagai worker pool yang tangguh untuk memastikan tugas terjadwal hanya berjalan tepat sekali, di satu node yang aktif, tanpa peduli berapa banyak server yang Anda scaling.
Siapkan terminal Anda, pastikan Redis server sudah menganga di port 6379, dan mari kita mulai meraciknya dari nol.
2. Peta Jalan Singkat: Apa yang Sedang Kita Rakit
Sebelum tangan kita kotor oleh barisan kode, mari kita intip cetak biru dapur kita hari ini. Perjalanan kita terbagi menjadi tiga fase utama yang mengalir mulus:
- Membedah Masalah: Kita pahami mengapa cron tradisional gagal total di lingkungan modern multi-server.
- Mengunci Mutlak: Kita bangun mekanisme distributed locking menggunakan Redis (
SETNXdengan TTL) untuk mencegah eksekusi ganda. - Eksekusi Paralel: Kita racik worker pool menggunakan Node.js dan Go yang elegan, siap melahap antrean tugas secara aman.
Mari kita bedah fondasi pertama di bawah ini, mengalir langsung dari pemahaman masalah menuju solusi teknis.
3. Langkah 1: Mempersiapkan Fondasi & Environment
Kita mulai dengan menyiapkan wadah kerjanya. Pastikan Node.js (versi 18+) atau Go (versi 1.20+) sudah terpasang di mesin Anda, beserta instance Redis yang siap sedia. Buat sebuah direktori kerja baru dan inisialisasi project kita:
mkdir -p distributed-cron-lab && cd distributed-cron-lab
npm init -y
npm install ioredis dotenv
[!IMPORTANT] Pastikan Redis Anda dikonfigurasi dengan persistensi yang sehat atau minimal memiliki alokasi memori yang cukup (
maxmemory-policy noeviction), karena kunci lock kita bergantung pada stabilitas Redis.
Setelah lingkungan siap, kita butuh cara agar aplikasi kita tahu siapa yang berhak mengeksekusi tugas. Di sinilah Redis menunjukkan taringnya sebagai penjaga gerbang yang andal, membawa kita langsung ke mekanisme penguncian di bab berikutnya.
4. Langkah 2: Meracik Konfigurasi Inti Tanpa Basa-basi
Jantung dari penjadwalan terdistribusi ini adalah Distributed Lock. Kita tidak bisa mengandalkan memori lokal server. Kita butuh sumber kebenaran tunggal (single source of truth). Redis menyediakan perintah atomik yang sangat pas untuk skenario ini.
Berikut adalah implementasi lock manager menggunakan Node.js dan pustaka ioredis:
const Redis = require('ioredis');
const redis = new Redis();
class DistributedScheduler {
constructor(lockKey, ttlMs) {
this.lockKey = lockKey;
this.ttlMs = ttlMs;
this.identifier = Math.random().toString(36).substring(7);
}
async acquireLock() {
// SET key value NX PX ttl memastikan operasi bersifat atomik
const result = await redis.set(this.lockKey, this.identifier, 'PX', this.ttlMs, 'NX');
return result === 'OK';
}
async releaseLock() {
// Gunakan Lua script untuk memastikan kita hanya menghapus lock milik kita sendiri
const luaScript = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`;
await redis.eval(luaScript, 1, this.lockKey, this.identifier);
}
}
module.exports = DistributedScheduler;
[!TIP] Penggunaan Lua script saat melepas lock sangat krusial untuk mencegah race condition di mana sebuah worker menghapus lock milik worker lain yang sudah mengambil alih setelah TTL habis.
Dengan penjaga gerbang yang solid ini, tugas kita berikutnya adalah memastikan worker di balik layar tidak saling berebut, melainkan bekerja secara harmonis mengantarkan payload pekerjaan.
5. Langkah 3: Sentuhan Akhir Keamanan & Optimasi
Sekarang kita gabungkan lock manager tadi ke dalam worker utama yang berjalan setiap menit (dipicu oleh cron sistem biasa, namun kini aman dari duplikasi). Buat file worker.js:
const DistributedScheduler = require('./DistributedScheduler');
async function runHeavyCronJob() {
const scheduler = new DistributedScheduler('cron:monthly-billing-lock', 30000);
const acquired = await scheduler.acquireLock();
if (!acquired) {
console.log(`[${new Date().toISOString()}] Server lain sedang mengeksekusi tugas ini. Melewati...`);
process.exit(0);
}
try {
console.log(`[${new Date().toISOString()}] Lock didapat! Memulai proses penagihan massal...`);
// Simulasi proses berat
await new Promise(resolve => setTimeout(resolve, 10000));
console.log('[+] Proses penagihan selesai dengan sukses.');
} catch (error) {
console.error('[-] Terjadi kegagalan pada cron job:', error);
} finally {
await scheduler.releaseLock();
console.log('[+] Lock dilepas kembali.');
process.exit(0);
}
}
runHeavyCronJob();
Letakkan script ini di crontab Anda (crontab -e):
* * * * * /usr/bin/node /path/to/distributed-cron-lab/worker.js >> /var/log/cron-job.log 2>&1
Konfigurasi ini memastikan bahwa meskipun cron berjalan di 10 server berbeda secara serentak setiap menit, hanya satu server yang berhasil mengunci Redis dan menjalankan fungsi penagihan.
6. Tes Rasa: Memastikan Semuanya Berjalan Sempurna
Saatnya menguji resep kita di dunia nyata. Buka dua terminal terpisah di komputer Anda, dan jalankan script worker.js secara bersamaan:
Terminal A:
node worker.js
Terminal B (jalankan sepersekian detik setelah Terminal A):
node worker.js
Perhatikan responsnya:
- Terminal A akan mencetak:
Lock didapat! Memulai proses penagihan massal... - Terminal B akan langsung mencetak:
Server lain sedang mengeksekusi tugas ini. Melewati...dan keluar tanpa error.
Itu adalah tanda bahwa mekanisme distributed locking kita berfungsi presisi layaknya jam Swiss!
7. Pertolongan Pertama: Jika Terjadi Kendala Kecil
[!WARNING] Kendala Umum: Bagaimana jika worker yang memegang lock tiba-tiba mengalami crash atau Out of Memory (OOM) sebelum sempat memanggil
releaseLock? Solusi: Jangan panik. ParameterPX 30000(TTL 30 detik) yang kita pasang di awal bertindak sebagai deadman switch. Jika dalam 30 detik worker tidak memperpanjang masa aktifnya, Redis akan otomatis menghapus kunci tersebut, membebaskan jalur untuk worker lain di eksekusi berikutnya.