# Arsitektur Background Processing Tangguh dengan Redis Queue dan Supervisor

> Diterbitkan oleh Literally Web ID pada 21 Oct 2026
> Sumber: https://literally.web.id/articles/arsitektur-background-processing-tangguh-dengan-redis-queue-dan-supervisor

---

## 1. Titik Awal Masalah: Mengapa Arsitektur Lama Sering Ambruk di Produksi

Pukul dua malam, ponsel saya bergetar liar. Bukan alarm pagi, melainkan rentetan *alert* PagerDuty yang menandakan latensi API melonjak drastis, koneksi database MySQL jenuh, dan Nginx mulai memuntahkan kode status 502 Bad Gateway. Usut punya usut, tim marketing baru saja mengirimkan *newsletter* berisi jutaan email promo. Tanpa pikir panjang, aplikasi monolith berbasis PHP-FPM itu mencoba memproses pengiriman email, pembuatan laporan PDF, dan sinkronisasi webhook pihak ketiga secara *synchronous* di dalam satu siklus HTTP *request*. Pengguna yang sekadar ingin *login* harus ikut mengantre di belakang jutaan proses pengiriman email tersebut. Sederhana tapi mematikan: aplikasi mencampur aduk tugas yang harus selesai detik itu juga dengan pekerjaan berat di latar belakang.

Pengalaman pahit seperti ini adalah ritual wajib bagi setiap infrastructure engineer sebelum akhirnya sadar bahwa mengandalkan *cron job* dan proses *blocking* murni di dalam web server adalah jalan cepat menuju kehancuran karier. Kita butuh cara untuk memisahkan beban kerja instan dari proses berat di balik layar. Di sinilah arsitektur *background processing* berbasis Redis Queue dan Supervisor masuk sebagai penyelamat. Namun, sebelum kita menyusun konfigurasinya, mari kita bedah bagaimana komponen-komponen ini saling berbisik di balik layar tanpa bantuan sihir.

## 2. Di Balik Layar: Bagaimana Sistem Ini Bekerja Tanpa Sihir

Bayangkan sebuah restoran cepat saji saat jam makan siang. Kasir bertugas menerima pesanan pelanggan dengan cepat dan memberikan nomor antrean. Kasir tidak ikut menggoreng ayam atau meracik minuman; jika kasir ikut memasak, antrean di depan kasir akan mengular dan pelanggan kabur. Di dunia web, aplikasi backend (PHP/Go/Node) bertindak sebagai kasir, sedangkan Redis adalah papan tulis antrean pesanan yang sangat cepat, dan Supervisor bersama *worker daemon* adalah para koki di dapur.

Ketika sebuah tugas latar belakang tercipta—misalnya *dispatch* data pesanan ke sistem logistik—aplikasi kita tidak langsung mengeksekusi logika tersebut. Sebaliknya, aplikasi menyserialize *payload* tugas tersebut dan memasukkannya (push) ke dalam struktur data *List* atau *Stream* di Redis menggunakan perintah seperti `LPUSH`. Dalam hitungan milidetik, kendali HTTP dikembalikan ke pengguna, membuat aplikasi terasa sangat instan.

> [!TIP]
> Redis dipilih bukan karena ia sekadar *cache*, melainkan karena kecepatan *in-memory*-nya yang mampu menangani puluhan ribu operasi tulis dan baca per detik tanpa membuat CPU *spike* berlebihan, jauh lebih ringan dibanding mengandalkan tabel database relasional sebagai antrean.

Di sisi lain, sekumpulan *worker* (proses CLI yang berjalan di latar belakang) terus-menerus memantau Redis menggunakan perintah pemblokiran ringan seperti `BRPOP`. Begitu sebuah data masuk ke Redis, *worker* langsung menyambarnya, melakukan *deserialize*, dan mengeksekusi kode tugas tersebut. Tapi tunggu, apa yang terjadi jika server mendadak *reboot*, atau proses *worker* mendadak mengalami *segmentation fault* karena kehabisan memori? Di sinilah Supervisor mengambil alih kendali penuh untuk memastikan para koki di dapur tidak pernah tidur atau mogok kerja secara permanen.

## 3. Pilihan Pendekatan & Kompromi yang Harus Diambil

Memilih infrastruktur antrean tidak bisa sembarangan menebak. Di luar sana, kita sering dihadapkan pada dilema klasik: memilih Redis, RabbitMQ, atau Apache Kafka? Setiap pilihan membawa konsekuensi arsitektural yang harus ditanggung dengan kepala dingin.

> [!IMPORTANT]
> Jangan pernah menggunakan Redis sebagai antrean jika aplikasi Anda menuntut jaminan *message durability* absolut tanpa celah kehilangan data sekecil apa pun saat terjadi *kernel panic* atau *power failure* mendadak. Untuk kasus keuangan perbankan ketat, RabbitMQ dengan *persistent queues* atau Kafka jauh lebih reliabel. Namun, untuk 95% kasus aplikasi web modern (notifikasi, pengiriman email, *image processing*, integrasi API pihak ketiga), Redis adalah kompromi terbaik antara kecepatan, kemudahan operasional, dan efisiensi sumber daya.

Kompromi berikutnya ada pada manajemen proses. Mengandalkan `nohup` dan perintah terminal manual untuk menjalankan *worker* di production adalah tindakan sabotase diri sendiri. Kita butuh proses *supervisor* yang tidak hanya menjalankan skrip, tapi juga memantau siklus hidupnya (*lifecycle*), melakukan *restart* otomatis ketika *crash*, dan mengatur jumlah *concurrency* secara presisi. Kombinasi Redis sebagai penyimpan antrean dan Supervisor sebagai penjaga gerbang *daemon* adalah standar industri yang terbukti tahan banting menahan gempuran *traffic* tinggi.

## 4. Meracik Lingkungan: Persiapan & Prasyarat Lapangan

Sebelum kita menulis baris konfigurasi, pastikan fondasi sistem operasi Ubuntu/Debian Anda di server produksi sudah siap tempur. Kita asumsikan Anda menggunakan PHP (sebagai contoh ekosistem backend umum dengan Laravel Queue) atau bahasa lain yang terhubung ke Redis.

Pertama, pastikan Redis terinstal dan dikonfigurasi dengan benar agar tidak memakan seluruh RAM server Anda:

```bash
# Update sistem dan instal Redis serta Supervisor
sudo apt update && sudo apt upgrade -y
sudo apt install redis-server supervisor -y

# Verifikasi servis berjalan
sudo systemctl enable redis-server
sudo systemctl enable supervisor
```

Buka konfigurasi Redis di `/etc/redis/redis.conf`, lalu pastikan pengaturan memori dan kebijakan *eviction*-nya aman untuk skenario antrean:

```text
# Batasi penggunaan memori maksimal Redis agar tidak OOM Killer
maxmemory 2gb
maxmemory-policy noeviction
```

Kebijakan `noeviction` sangat penting untuk antrean. Kita lebih baik Redis menolak penulisan baru (`OOM command not allowed`) daripada menghapus data antrean secara sepihak yang mengakibatkan tugas penting hilang selamanya. Restart Redis Anda setelah melakukan perubahan ini:

```bash
sudo systemctl restart redis-server
```

## 5. Langkah Implementasi: Membangun Solusi yang Tangguh

Sekarang saatnya meracik konfigurasi Supervisor agar *worker* backend kita berjalan sebagai *daemon* abadi yang tahan banting. Buat file konfigurasi baru di dalam direktori `/etc/supervisor/conf.d/` dengan nama `app-worker.conf`.

```ini
[program:app-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis --queue=high,default --tries=3 --timeout=90
autostart=true
autorestart=true
stopasgroup=true
killsignal=SIGQUIT
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/worker.log
stopwaitsecs=3600
```

Mari kita bedah beberapa parameter krusial di atas:
- `numprocs=4`: Memerintahkan Supervisor untuk melahirkan 4 instans *worker* secara paralel secara otomatis.
- `stopasgroup=true` dan `killsignal=SIGQUIT`: Sangat penting agar ketika kita melakukan *deployment* ulang dan memerintahkan *restart*, Supervisor tidak membunuh proses anak secara paksa di tengah jalan, melainkan membiarkan *worker* menyelesaikan tugas yang sedang berjalan saat itu hingga tuntas (*graceful shutdown*).
- `command`: Menjalankan perintah *queue worker* dengan prioritas *queue* `high` terlebih dahulu sebelum `default`.

Setelah file tersimpan, daftarkan konfigurasi tersebut ke daemon Supervisor:

```bash
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start app-worker:*
```

Periksa statusnya dengan `sudo supervisorctl status`. Anda akan melihat 4 proses *worker* berjalan mulus siap melahap antrean.

## 6. Membedah Baris Kode Krusial yang Kerap Menjebak

Banyak *engineer* terjebak pada asumsi bahwa begitu *worker* berjalan, semua masalah selesai. Padahal, penulisan kode di dalam *job handler* itu sendiri menyimpan bom waktu jika tidak ditangani secara asinkron dengan benar. Perhatikan contoh penulisan *job* di backend yang buruk versus yang tangguh.

Contoh implementasi *job handler* yang buruk (rawan *memory leak* dan timeout):

```php
// JANGAN LAKUKAN INI DI PRODUCTION
public function handle() {
    // Mengambil 100. sekaligus tanpa chunking memicu memory exhaustion
    $users = User::all(); 
    foreach($users as $user) {
        // Melakukan HTTP request blocking ke API pihak ketiga di dalam loop
        Http::post('https://api.external.com/notify', ['id' => $user->id]);
    }
}
```

Sebaliknya, pecah tugas besar menjadi unit-unit kecil yang independen. Jika Anda perlu memproses jutaan data, buat *job* induk yang melakukan *chunking* dan mendispatch *job-job* kecil ke Redis:

```php
// PENDEKATAN TANGGUH & IDEMPOTEN
public function handle() {
    User::chunk(100, function ($users) {
        foreach ($users as $user) {
            // Dispatch job individual ke antrean Redis
            ProcessSingleUserJob::dispatch($user->id)->onQueue('default');
        }
    });
}
```

> [!IMPORTANT]
> Pastikan setiap *job* bersifat **idempotent**—artinya, jika *job* tersebut dijalankan dua kali karena masalah jaringan atau *retry*, aplikasi tidak akan menduplikasi aksi berbahaya (seperti memotong saldo pengguna dua kali). Gunakan *database transactions* atau *unique locks* berbasis Redis.

## 7. Bertahan di Bawah Tekanan: Skenario Traffic Nyata

Ketika aplikasi mulai dihujani ribuan transaksi per detik, antrean di Redis bisa membengkak dari ratusan menjadi ratusan ribu dalam hitungan menit. Di sinilah strategi penskalaan (*scaling*) diuji. Jika 4 proses *worker* kewalahan, Anda tidak perlu merombak ulang sistem; Anda cukup mengubah konfigurasi `numprocs` di Supervisor menjadi 16, lalu jalankan:

```bash
sudo supervisorctl update
```

Namun, menambah *worker* secara membabi-buta juga bisa merusak database relasional Anda karena kehabisan *connection pool*. Jika database Anda hanya mampu menampung maksimal 50 koneksi bersamaan, dan setiap *worker* membuka 5 koneksi, total 16 *worker* (`16 * 5 = 80`) akan membuat database langsung tumbang akibat *too many connections*.

Solusinya? Gunakan pembatasan laju (*rate limiting*) pada *worker* atau gunakan mekanisme *connection pooling* seperti PgBouncer untuk PostgreSQL atau ProxySQL untuk MySQL. Atur juga parameter `--max-jobs` atau `--memory` pada perintah Supervisor agar *worker* secara berkala membersihkan dirinya sendiri setelah memproses sejumlah *job* tertentu, mencegah *memory leak* yang kerap merayap diam-diam pada aplikasi berbasis bahasa *interpreted*.

## 8. Jebakan Klasik & Cara Menghindarinya Sebelum Meledak

Pengalaman mengajarkan bahwa kesalahan paling sering terjadi bukan pada teknologinya, melainkan pada kebiasaan sepele para pengembang. Berikut adalah daftar jebakan mematikan yang harus Anda hindari:

1. **Mengabaikan Timeout**: *Job* yang memproses unggahan file besar sering kali berhenti di tengah jalan karena mencapai batas waktu *default* (biasanya 60 detik). Selalu sesuaikan parameter `--timeout` di Supervisor atau pada *class job* itu sendiri.
2. **Gagal Menangani Dead Letter Queue (DLQ)**: Ketika sebuah *job* gagal terus-menerus (misalnya karena API pihak ketiga *down*), *job* tersebut akan terus masuk dalam siklus *retry* tanpa henti yang menghabiskan sumber daya CPU. Konfigurasikan batas maksimal percobaan (`--tries=3`) dan arahkan *job* yang gagal total ke tabel atau antrean khusus DLQ untuk diinvestigasi manual.
3. **Lupa Merestart Worker Setelah Deploy**: Kode backend Anda berubah, tapi proses *worker* di Supervisor masih memuat kode lama yang tersimpan di dalam memori PHP/Node. Masukkan perintah `sudo supervisorctl restart app-worker:*` ke dalam skrip *CI/CD deployment* Anda (seperti GitLab CI atau GitHub Actions) setiap kali rilis baru naik ke production.

## 9. Uji Nyali: Mengukur Performa di Titik Batas

Sebelum meresmikan sistem ini ke hadapan publik, jangan pernah percaya begitu saja pada asumsi teori. Lakukan *load testing* menggunakan tools seperti `Apache JMeter`, `Locust`, atau `k6` untuk membombardir endpoint aplikasi yang memicu *background job*.

Pantau metrik secara *real-time* menggunakan perintah penunjang di server:

```bash
# Memantau seberapa cepat Redis memproses antrean
redis-cli monitor

# Memantau penggunaan CPU dan memori worker secara langsung
htop
```

Amati apakah latensi API utama (HTTP response time) tetap stabil di bawah 100ms meskipun Redis sedang sibuk memproses puluhan ribu *job* di latar belakang. Jika latensi utama tetap aman, arsitektur pemisahan beban kerja Anda sukses meredam badai.

## 10. Catatan Lapangan & Checklist Sebelum Rilis

Sebagai penutup dari pengalaman puluhan tahun mengawal infrastruktur skala besar, simpan *checklist* wajib berikut di meja kerja Anda sebelum menekan tombol *merge* ke branch `main`:

- [ ] Apakah Redis sudah dikonfigurasi dengan batas memori maksimal dan kebijakan *noeviction*?
- [ ] Apakah file konfigurasi Supervisor sudah menggunakan `stopasgroup=true` untuk mendukung *graceful shutdown*?
- [ ] Apakah parameter `--tries` dan `--timeout` sudah diuji secara spesifik pada *job* terberat?
- [ ] Apakah mekanisme *deployment* otomatis sudah menyertakan perintah `supervisorctl restart`?
- [ ] Apakah *job handler* Anda sudah dijamin aman dan *idempotent* terhadap kegagalan jaringan ganda?

Jika semua kotak di atas sudah tercentang hijau, Anda tidak perlu lagi terbangun ketakutan pada pukul dua malam. Duduklah kembali, seduh kopi Anda, dan biarkan Redis serta Supervisor bekerja menjaga kestabilan sistem di balik layar.