Pukul dua pagi, pagerduty berteriak nyaring di dashboard infrastruktur kami. Notifikasi dari Sentry meluber, menandakan ribuan job latar belakang mengalami duplikasi eksekusi. Bayangkan skenario nyata: sebuah platform e-commerce dengan jutaan pengguna, di mana script cron standar kita di-deploy ke lima instance auto-scaling backend berbasis PHP-FPM di balik load balancer Nginx. Ketika jam menunjukkan tepat pukul 00:00, kelima server tersebut secara serentak mengeksekusi cron job untuk mengirimkan email rekap harian ke jutaan pelanggan. Hasilnya? Pelanggan menerima email yang sama persis sebanyak lima kali, database MySQL kewalahan menerima lonjakan koneksi (Too many connections), dan Redis cache mengalami lock contention parah. Saat itulah perdebatan sengit antara mempertahankan Cron klasik berbasis OS versus mengadopsi Distributed Locking berbasis Redis Redlock dimulai di ruang server virtual kami.
Mari kita bedah langsung performa kedua pendekatan ini di atas arena pertempuran.
| Metrik Performa | Cron Standar OS | Redis Redlock Terdistribusi |
|---|---|---|
| Kecepatan Eksekusi (Latensi Awal) | 10/10 (Sangat Instan) | 7/10 (Butuh Roundtrip Redis) |
| Konsumsi RAM Server | 10/10 (Hampir Nol) | 8/10 (Bergantung pada Node Redis) |
| Kompleksitas Setup & Konfigurasi | 9/10 (Sangat Mudah) | 6/10 (Butuh Pemahaman Algoritma) |
| Keamanan Multi-Server (Anti-Duplikasi) | 2/10 (Rentan Race Condition) | 9/10 (Sangat Aman) |
| Skalabilitas Infrastruktur | 4/10 (Monolitik/Single Node) | 9/10 (Cloud-Native Skalabel) |
1. Pertarungan Filosofi: Dua Cara Pandang Berbeda dalam Memecahkan Masalah
Di satu sisi sudut ring, kita punya Cron Standar OSโveteran yang telah mengabdi selama puluhan tahun di dunia Linux. Filosofinya sederhana: "Percaya pada waktu sistem". Cukup tulis perintah di /etc/cron.d/, tentukan ekspresi waktunya (misal * * * * *), dan biarkan daemon cron menjalankan tugasnya. Masalahnya muncul ketika infrastruktur modern kita bertransformasi menjadi arsitektur cloud-native yang berjalan di atas Kubernetes atau multi-instance Auto Scaling Group (ASG) di AWS. Cron tidak peduli apakah instansinya ada satu atau seratus; jika ada seratus server menyala, cron job yang sama akan dieksekusi seratus kali secara paralel.
Di sudut seberangnya, berdiri Redis Redlock + Worker Pool, pendekatan modern yang menuntut disiplin tinggi. Filosofinya berubah total: "Jangan pernah percaya waktu server; percaya pada konsensus terdistribusi". Sebelum sebuah background job dieksekusi oleh worker, sistem wajib meminta izin (locking) eksklusif ke kluster Redis menggunakan algoritma Redlock. Jika satu instance berhasil mendapatkan kunci dengan TTL (Time-to-Live) tertentu, instance lain harus mengalah atau melewati tugas tersebut. Perbedaan filosofi ini berdampak langsung pada bagaimana kita menulis kode dan mengelola konfigurasi harian di bawah tekanan deadline.
2. Ronde 1: Ergonomi Menulis Kode & Pengalaman Konfigurasi Sehari-hari
Bicara soal kenyamanan developer, Cron klasik memenangkan hati para pemula karena kesederhanaannya. Kita cukup membuka terminal, mengetik crontab -e, lalu memasukkan baris perintah seperti ini:
# Menjalankan pembersihan cache setiap jam 3 pagi
0 3 * * * /usr/bin/php /var/www/html/artisan schedule:run >> /dev/null 2>&1
Konfigurasinya cepat, tidak butuh dependensi tambahan, dan langsung jalan. Namun, begitu sistem berkembang dan kita harus memindahkan aplikasi ke arsitektur load-balanced yang dilindungi oleh Nginx dan Cloudflare, cron lokal menjadi bom waktu. Jika server utama down dan sistem melakukan failover, cron di server cadangan akan tiba-tiba aktif dan menabrak proses yang sedang berjalan.
Sebaliknya, mengimplementasikan Redis Redlock menuntut sedikit lebih banyak keringat di awal. Kita harus membangun mekanisme penguncian di level aplikasi sebelum worker memproses antrean. Berikut adalah contoh implementasi boilerplate menggunakan Go dan Redis client resmi untuk memastikan hanya ada satu worker yang memproses tugas tertentu di seluruh kluster:
package main
import (
"context"
"fmt"
"time"
"github.com/go-redis/redis/v8"
)
func acquireLock(ctx context.Context, client *redis.Client, lockKey string, ttl time.Duration) (bool, string) {
lockVal := "unique_worker_instance_id_xyz"
success, err := client.SetNX(ctx, lockKey, lockVal, ttl).Result()
if err != nil || !success {
return false, ""
}
return true, lockVal
}
func releaseLock(ctx context.Context, client *redis.Client, lockKey, lockVal string) {
script := `if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end`
client.Eval(ctx, script, []string{lockKey}, lockVal)
}
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379\