# Analisis: Queue Terlewat (15k Diproses Hanya 11k)

Dokumen ini menganalisis mengapa dari ~15.000 job yang seharusnya diproses, hanya ~11.000 yang benar-benar selesai, meskipun sudah ada fitur retry.

---

## Ringkasan Penyebab

| Penyebab | Dampak | Prioritas |
|----------|--------|-----------|
| **1. Redis + worker mati/timeout** | Job sudah di-pop dari Redis; saat worker kill, job **hilang permanen** (tidak masuk failed_jobs) | Tinggi |
| **2. Worker `--tries=3` vs Job `$tries=5`** | Setelah 3 kali gagal (throw), worker pindahkan ke failed_jobs; retry ke-4 dan 5 tidak pernah jalan | Sedang |
| **3. Job timeout 60s** | Job lama (API lambat, media besar) bisa di-kill worker → dengan Redis job hilang | Tinggi |
| **4. Job “sukses” tanpa kirim** | Early return (message sudah sent/failed, message not found) → job dihapus dari queue, tidak retry | Sedang |
| **5. Gagal terminal (max retry, policy, balance)** | Job selesai dengan `markFailed()` → masuk hitungan “diproses” tapi status failed | Informasi |

---

## 1. Redis Queue + Worker Mati = Job Hilang

**Konfigurasi saat ini (supervisor):**
```bash
queue:work redis --queue=whatsapp --tries=3 --timeout=60
```

Dengan **Redis** sebagai driver:

- Saat worker **pop** job, job **dikeluarkan** dari list Redis.
- Jika worker **mati** (timeout kill, OOM, `kill -9`, restart) **sebelum** job selesai atau `release()`:
  - Job **tidak** dikembalikan ke Redis.
  - Job **tidak** otomatis masuk `failed_jobs`.
  - Job **hilang** dan tidak akan pernah di-retry.

Ini menjelaskan sebagian besar ~4.000 job “hilang”: job sempat diambil worker, lalu worker mati sebelum selesai.

**Perbandingan dengan database driver:**

- Database: job di-**reserve** (update `reserved_at`), tidak dihapus. Setelah `retry_after` (mis. 90s), job bisa diambil lagi. Worker mati → job tetap ada di DB dan akan di-retry.
- Redis: job di-pop → hilang dari queue; kalau worker mati, tidak ada mekanisme bawaan yang mengembalikan job.

---

## 2. Worker `--tries=3` vs Job `$tries=5`

- **Supervisor:** `--tries=3` → worker akan retry job yang **throw exception** paling banyak 3 kali, lalu pindah ke `failed_jobs`.
- **SimpleWhatsAppJob:** `$tries = 5` (dari config `whatsapp_queue.queue.max_retries`).

Artinya:

- Retry ke-4 dan ke-5 **tidak pernah** jalan, karena worker sudah berhenti di attempt ke-3.
- Semua job yang butuh retry ke-4/5 akan masuk `failed_jobs` setelah 3 attempt, bukan di-retry sampai 5 kali.

Ini bisa menambah jumlah job yang “gagal” atau “hilang” dari sudut pandang “harusnya masih di-retry”.

---

## 3. Job Timeout 60 Detik

- Worker: `--timeout=60`.
- `SimpleWhatsAppJob` **tidak** set property `$timeout`, jadi pakai default worker (60s).

Jika satu job jalan > 60s (API Meta lambat, upload media besar, rate limit delay lama):

- Worker akan **kill** proses job.
- Dengan Redis, job sudah di-pop → job **hilang** (tidak kembali ke queue, tidak ke failed_jobs).

Semakin banyak job yang lama, semakin banyak yang “hilang” tanpa jejak.

---

## 4. Job “Sukses” Tanpa Benar-Benar Kirim

Di `SimpleWhatsAppJob::handle()`:

- `if (!$message) { return; }` → job dianggap selesai, dihapus dari queue.
- `if (in_array($message->queue_status, ['sent', 'failed'])) { return; }` → skip (idempotency), job dihapus.

Ini **benar** (cegah duplikasi), tapi:

- Jika ada **duplikat dispatch** (message_id sama dikirim 2x ke queue), yang kedua hanya “skip” dan tidak kirim. Dari 15k event “job run” bisa hanya 11k yang benar-benar kirim, sisanya skip/early exit.
- “Terlewat” di sini = terhitung diproses, tapi tidak menambah angka kirim.

---

## 5. Gagal Terminal (Bukan Bug, Tapi Penting untuk Hitungan)

Job yang sudah **selesai** dengan status gagal (setelah retry habis / policy / balance):

- `markFailed()` → `status = failed`, `queue_status = failed`, job dihapus dari queue.
- Ini **diproses** (run handle), tapi **bukan** “sent”. Jadi kalau metrik “diproses” = 15k dan “sent” = 11k, selisihnya bisa kombinasi: gagal terminal + early skip + job hilang (Redis + worker mati).

---

## Rekomendasi Perbaikan

### A. Kurangi Job Hilang: Pakai Database Queue untuk WhatsApp

Untuk antrian kritis (WhatsApp), lebih aman pakai **database** sebagai driver:

- Job di-**reserve**, bukan di-pop permanen.
- Worker mati/timeout → setelah `retry_after` job bisa diambil lagi.
- Tidak ada “hilang” karena Redis + kill.

Contoh supervisor (ganti `redis` → koneksi default yang pakai database):

```bash
# Pakai database queue untuk whatsapp (job tidak hilang saat worker mati)
php artisan queue:work database --queue=whatsapp --tries=5 --timeout=120 --retry_after=180 --sleep=3 --max-time=3600
```

Pastikan di `.env`:

- `QUEUE_CONNECTION=database` untuk default, **atau**
- Buat koneksi terpisah untuk `whatsapp` yang pakai database, dan jalankan worker untuk queue `whatsapp` dengan koneksi itu.

### B. Sinkronkan Worker Tries dengan Job Tries

- Naikkan worker ke **5** agar sama dengan `SimpleWhatsAppJob::$tries`:
  ```bash
  --tries=5
  ```
- Atau turunkan `max_retries` di config ke 3 jika ingin batas retry 3 (tetap usahakan worker tries ≥ job tries).

### C. Naikkan Timeout dan Set di Job

- Worker: `--timeout=120` (atau 90) agar job punya waktu cukup (API/upload).
- Di `SimpleWhatsAppJob` set property timeout agar eksplisit:
  ```php
  public $timeout = 120; // atau dari config('whatsapp_queue.queue.timeout', 120)
  ```

Ini mengurangi kemungkinan job di-kill di tengah jalan (terutama dengan database queue, job yang di-kill akan bisa di-retry setelah `retry_after`).

### D. Monitor Failed Jobs dan Status Message

- Cek tabel `failed_jobs`: berapa banyak yang gagal setelah 3/5 retry.
- Cek `messages` dengan `status = 'failed'` atau `queue_status = 'failed'`: berapa yang gagal terminal.
- Bandingkan: jumlah job yang di-dispatch (mis. dari log/campaign) vs `messages.status = 'sent'` vs `failed_jobs` vs “hilang” (tidak sent dan tidak di failed_jobs) → bisa estimasi berapa yang hilang karena Redis + worker mati.

### E. (Opsional) Redis Persistence

Jika tetap pakai Redis:

- Aktifkan **AOF** atau **RDB** di Redis agar data queue bisa persist dan tidak hilang saat Redis restart.
- Tetap saja, job yang sudah di-pop lalu worker mati **tetap** hilang; persistence hanya bantu untuk job yang masih di list.

---

## Checklist Verifikasi

1. [ ] Cek `failed_jobs`: berapa banyak untuk `whatsapp` / SimpleWhatsAppJob dalam periode 15k tadi.
2. [ ] Cek `messages` dengan `queue_status IN ('pending','delayed','sending')` yang sudah lama (stuck).
3. [ ] Cek `messages` dengan `status = 'failed'` dan `error_message` (gagal terminal).
4. [ ] Bandingkan: total message yang di-enqueue (mis. dari campaign/batch) vs `status = 'sent'` vs `failed` vs stuck.
5. [ ] Ganti ke database queue untuk `whatsapp` atau setidaknya naikkan `--tries` dan `--timeout`, lalu deploy dan pantau lagi.

Dengan langkah di atas, angka “15k diproses hanya 11k” bisa dijelaskan (gagal terminal, skip, dan **hilang karena Redis + worker mati/timeout**), dan ke depan job tidak mudah “hilang” lagi berkat database queue dan timeout/tries yang selaras.
