# Exent Signal - Deploy de Crons e Filas (Produção)

> Este arquivo vive em `storage/app/private/docs/` (gitignored, não é servido
> pelo webserver). Atualize-o sempre que mudar algo na infra de filas/cron.

## Visão geral

O Exent Signal usa **filas Laravel sobre Postgres** (driver `database`) e o
**scheduler Laravel** (cron de 1 minuto) para executar:

| Componente | O quê | Frequência |
|---|---|---|
| **Bootstrap** | Carga inicial de 365 dias, particionada em chunks de 90d | Manual ou automático no bind |
| **Daily Close** | Materializa D-1 (dados de ontem) para todas as integrações | 01:00 BRT diário (`--force --trigger=daily_close`) |
| **Watchdog** | Retry de daily close falho + integrações novas bindadas no dia | Hora em hora (dispatcher sem `--force`, slot-based) |
| **Reconciliação** | Reprocessamento amplo (lookback 30d) | Hora em hora (bucket de N dias) |
| **Cleanup** | Limpeza de SyncRuns antigos | 07:00 diário |

Filas separadas para isolar bootstrap (longo) de incremental (curto):

| Fila | Worker | Numprocs | Timeout | Uso |
|---|---|---|---|---|
| `sync-bootstrap` | `exent-dashboard-bootstrap` | 1 | 600s | `profile=bootstrap` |
| `sync-incremental,default` | `exent-dashboard-incremental` | 2 | 300s | `incremental` + `reconciliation` + `custom` + segurança |

---

## 1. Cron - Laravel scheduler

Adicionar **uma única linha** ao crontab do usuário que roda a aplicação
(geralmente `www-data` em prod, `ep5_developer` em homolog):

```cron
* * * * * cd /var/www/projetos5.exentdev.com.br/st/8.3/dashboard && php artisan schedule:run >> /dev/null 2>&1 #Exent Dashboard - Laravel scheduler (Exent Signal)
```

Como editar:

```bash
crontab -e
# (cole a linha acima e salve)

# Verifica que ficou:
crontab -l | grep dashboard
```

> **Importante:** o scheduler precisa rodar a cada minuto. Não diminua a
> frequência - a granularidade real (15min/incremental, 1h/reconciliação)
> é decidida pelo `routes/console.php`.

O que esse cron dispara está em `routes/console.php`:

- `integrations:dispatch-due --profile=incremental --force --trigger=daily_close` - **01:00 BRT diário** (rotina principal D-1)
- `integrations:dispatch-due --profile=incremental` - hourly (watchdog retry/recovery)
- `integrations:dispatch-due --profile=reconciliation` - hourly
- `sync-runs:cleanup --keep-days=30 --error-days=90` - 07:00 diário

O **daily close** (01:00) usa `--force` para pular o slot check e materializar
D-1 para todas as integrações ativas. O **watchdog** (hourly) captura: (a)
integrações cujo daily close falhou, (b) integrações novas bindadas durante o
dia. Slot anchoring garante que ele é no-op quando o daily close já cobriu.

O dispatcher só dispara integrações com `bootstrap_completed_at != null` -
integrações sem bootstrap são ignoradas em silêncio.

---

## 2. Supervisor - Workers de fila

### 2.1 Instalar supervisor (Ubuntu/Debian)

```bash
sudo apt-get update
sudo apt-get install -y supervisor
sudo systemctl enable supervisor
sudo systemctl start supervisor
```

### 2.2 Arquivo de configuração

Copiar para `/etc/supervisor/conf.d/exent.dashboard.conf`:

```ini
; Workers de filas do Exent Dashboard (Exent Signal)
; - sync-bootstrap: 1 worker dedicado para o setup inicial de 365 dias (chunks 90d).
;                   Timeout 600s; retry_after da conexão database é 720s.
; - sync-incremental: 2 workers para incremental + reconciliação + custom.
;                     Também consome a fila `default` como rede de segurança.
;
; Logs separados por processo via %(process_num)02d para não misturar stdout.

[program:exent-dashboard-bootstrap]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/php /var/www/projetos5.exentdev.com.br/st/8.3/dashboard/artisan queue:work database --queue=sync-bootstrap --sleep=3 --tries=1 --timeout=600 --max-time=3600
directory=/var/www/projetos5.exentdev.com.br/st/8.3/dashboard
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/projetos5.exentdev.com.br/st/8.3/dashboard/storage/logs/worker-bootstrap-%(process_num)02d.log
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=5
stopwaitsecs=620

[program:exent-dashboard-incremental]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/php /var/www/projetos5.exentdev.com.br/st/8.3/dashboard/artisan queue:work database --queue=sync-incremental,default --sleep=3 --tries=1 --timeout=300 --max-time=3600
directory=/var/www/projetos5.exentdev.com.br/st/8.3/dashboard
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/projetos5.exentdev.com.br/st/8.3/dashboard/storage/logs/worker-incremental-%(process_num)02d.log
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=5
stopwaitsecs=320
```

### 2.3 Aplicar a config

```bash
sudo cp /caminho/para/exent.dashboard.conf /etc/supervisor/conf.d/exent.dashboard.conf
sudo chown root:root /etc/supervisor/conf.d/exent.dashboard.conf
sudo chmod 644 /etc/supervisor/conf.d/exent.dashboard.conf

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status | grep exent
```

Esperado (3 processos RUNNING):

```
exent-dashboard-bootstrap:exent-dashboard-bootstrap_00     RUNNING
exent-dashboard-incremental:exent-dashboard-incremental_00 RUNNING
exent-dashboard-incremental:exent-dashboard-incremental_01 RUNNING
```

---

## 3. `config/queue.php` - `retry_after`

**Regra crítica:** `retry_after` da conexão `database` precisa ser **maior**
que o maior `--timeout` dos workers, senão o Laravel re-reserva o job
enquanto ainda está rodando e gera execução duplicada.

Como o worker `sync-bootstrap` usa `--timeout=600`, o `retry_after` precisa
ser >= 660s. Usamos **720s (12min)** como margem segura:

```php
'database' => [
    'driver' => 'database',
    // ...
    'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 720),
    'after_commit' => false,
],
```

Pode sobrescrever via env (`DB_QUEUE_RETRY_AFTER=720`) sem mexer no código.

---

## 4. Restart após deploy

Toda vez que fizer deploy de código novo, **restartar os workers** para que
peguem a nova versão (queue:work cacheia o autoload):

```bash
sudo supervisorctl restart exent-dashboard-bootstrap:*
sudo supervisorctl restart exent-dashboard-incremental:*
```

Ou, do próprio Laravel, sinalizando os workers a terminarem o job atual e
saírem (o supervisor reinicia):

```bash
php artisan queue:restart
```

> O `--max-time=3600` no comando do worker já força reset a cada 1h
> independente, então mesmo sem `queue:restart` o código novo entra em
> circulação em até 1 hora.

---

## 5. Validação rápida

```bash
# 1. Cron está ativo
crontab -l | grep dashboard

# 2. Supervisor está rodando
sudo supervisorctl status | grep exent

# 3. Scheduler responde
cd /var/www/projetos5.exentdev.com.br/st/8.3/dashboard
php artisan schedule:list

# 4. Filas estão drenando (não tem job preso)
php artisan queue:monitor sync-bootstrap,sync-incremental --max=10

# 5. Logs dos workers (cada processo, seu arquivo)
tail -f storage/logs/worker-bootstrap-00.log
tail -f storage/logs/worker-incremental-00.log
tail -f storage/logs/worker-incremental-01.log

# 6. Log do app (jobs, dispatcher, erros)
tail -f storage/logs/laravel.log
```

---

## 6. Bootstrap em chunks (referência)

Quando o usuário clica "Iniciar setup" na UI, o `triggerBootstrap()`:

1. Lê `policy.bootstrap_days` (default 365).
2. Particiona em chunks de `SyncPolicy::BOOTSTRAP_CHUNK_DAYS` (90d).
3. Gera um `bootstrap_group_id` (UUID) único para o grupo.
4. Cria APENAS o primeiro chunk (mais recente) e despacha na `sync-bootstrap`.
5. Cada chunk completo dispara o próximo via `RunIntegrationSyncJob` (chain).
6. Quando o último chunk termina, valida que TODOS tiveram `status=success`
   e `SUM(rows_processed) > 0` antes de marcar `bootstrap_completed_at`.

Por que 90 dias:

- **Exent Hub** rejeita janelas > 90d (`range_too_large`).
- **Google Ads / Meta Ads** aceitam mais, mas drill-downs longos têm 5xx
  e timeouts frequentes.
- 90d cabe no `--timeout=600` do worker bootstrap com folga.

365 dias → 5 chunks (90+90+90+90+5). Ordem decrescente (mais recente primeiro)
para o usuário começar a ver dados úteis logo no primeiro chunk.

---

## 7. Troubleshooting

| Sintoma | Causa provável | Como verificar | Como corrigir |
|---|---|---|---|
| Bootstrap não dispara | Worker `sync-bootstrap` parado | `sudo supervisorctl status \| grep bootstrap` | `sudo supervisorctl start exent-dashboard-bootstrap:*` |
| Incremental nunca roda | Cron não está ativo | `crontab -l \| grep dashboard` | adicionar a linha do scheduler |
| Mesmo job processado 2x | `retry_after` < timeout | `php artisan tinker --execute='echo config(\"queue.connections.database.retry_after\");'` | ajustar para >= 720 |
| Logs misturados de 2 workers | Faltou `%(process_num)02d` no logfile | inspecionar `/etc/supervisor/conf.d/exent.dashboard.conf` | aplicar arquivo desta doc |
| `range_too_large` no Exent Hub | Bootstrap rodou janela > 90d (legado) | `grep range_too_large storage/logs/laravel.log` | re-rodar bootstrap (chunks 90d novos) |
| Workers consomem RAM crescente | Memory leak natural do PHP | `ps aux \| grep queue:work` | `--max-time=3600` já força reset; ou `queue:restart` |

---

## 8. Checklist de deploy em produção

- [ ] Cron `* * * * *` adicionado ao crontab do usuário da app
- [ ] Supervisor instalado e rodando (`systemctl status supervisor`)
- [ ] `/etc/supervisor/conf.d/exent.dashboard.conf` aplicado com os 2 programas
- [ ] `supervisorctl reread && supervisorctl update` executado
- [ ] 3 processos RUNNING (1 bootstrap + 2 incremental)
- [ ] `config/queue.php` com `retry_after >= 720` (ou env `DB_QUEUE_RETRY_AFTER=720`)
- [ ] `php artisan schedule:list` mostra os 4 comandos do dashboard (daily close, watchdog, reconciliação, cleanup)
- [ ] `storage/logs/` tem permissão de escrita pelo usuário do worker
- [ ] Permissões: `chown -R www-data:www-data storage bootstrap/cache`
- [ ] Migrations rodadas (`php artisan migrate --force`)
- [ ] Cache de config/route limpo após mudança em `routes/console.php`
      (`php artisan config:clear && php artisan route:clear`)
- [ ] Teste end-to-end: bind de uma integração, verificar bootstrap automático,
      esperar 15min e ver incremental rodando sozinha
