diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index abcaf43..75a4001 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -246,214 +246,269 @@ Resumo das mudanças aplicadas no painel administrativo para simplificar “Usu > Observação operacional: mantivemos o provisionamento de dispositivos inalterado (token/e‑mail técnico), e o acesso web segue apenas para pessoas. A unificação é de UX/gestão. -## 10) Convex Self-Hosted — Otimizacao de Memoria (OOM) +## 10) Convex Self-Hosted — Problema de Memoria OOM (Historico Completo) -### Problema +> **STATUS: RESOLVIDO DEFINITIVAMENTE em 10/12/2025** -O Convex self-hosted carrega **todas as versoes de todos os documentos** em memoria (SQLite in-memory). Com heartbeats de dispositivos a cada 30s, cada `patch()` criava uma nova versao do documento `machines`, causando: +### Cronologia do Problema -- Acumulo exponencial: ~3500 versoes para ~65 maquinas -- Uso de memoria crescente: 8-10 GiB para poucos dispositivos -- Crashes por OOM e desconexoes WebSocket (codigo 1006) +| Data | Evento | Memoria | +|------|--------|---------| +| Nov/2025 | Problema detectado - exports em loop | ~10GB | +| 09/12/2025 | Tentativa 1: separar heartbeats em tabela dedicada | ~8GB | +| 10/12/2025 | 12 OOM kills em 12 horas | 19-20GB (limite) | +| 10/12/2025 | **Solucao definitiva aplicada** | **395MB** | -### Solucao Implementada (2025-12-09) +### Causa Raiz Identificada (10/12/2025) -**1. Separacao de heartbeats em tabela dedicada** +O Convex self-hosted carrega **TODAS as versoes de TODOS os documentos** em memoria (SQLite in-memory para OCC - Optimistic Concurrency Control). Isso significa: -Nova tabela `machineHeartbeats` armazena apenas `lastHeartbeatAt`: +1. **Cada `patch()` cria uma nova versao** do documento (nao substitui) +2. **Versoes antigas permanecem** no banco e na memoria +3. **Heartbeats a cada 5 min** com inventory de ~92KB por maquina +4. **6 maquinas x 288 heartbeats/dia = 1.728 versoes/dia** +5. **Banco de 450MB expandia para 19GB+ em RAM** (~42x) -```typescript -// convex/schema.ts -machineHeartbeats: defineTable({ - machineId: v.id("machines"), - lastHeartbeatAt: v.number(), -}).index("by_machine", ["machineId"]), +### Solucao Definitiva (10/12/2025) + +#### Parte 1: Mover Crons para Linux (evitar acumulo de `_scheduled_job_logs`) + +```bash +# Crons do Convex COMENTADOS em convex/crons.ts +# Substituidos por endpoints HTTP + crontab Linux ``` -**2. Heartbeat inteligente** +Ver secao 12 para detalhes. -A funcao `heartbeat` agora: -- SEMPRE atualiza `machineHeartbeats` (documento pequeno, upsert) -- SO atualiza `machines` quando ha mudancas reais (hostname, OS, metadata, status) +#### Parte 2: Limpeza Manual do Banco (remover versoes antigas) + +```bash +# 1. Parar Convex +docker service scale sistema_convex_backend=0 + +# 2. Backup +cd /var/lib/docker/volumes/sistema_convex_data/_data +cp db.sqlite3 db.sqlite3.backup-$(date +%Y%m%d-%H%M%S) + +# 3. Limpar versoes antigas (manter apenas a mais recente de cada documento) +sqlite3 db.sqlite3 "DELETE FROM documents WHERE (id, ts) NOT IN (SELECT id, MAX(ts) FROM documents GROUP BY id);" + +# 4. Compactar banco +sqlite3 db.sqlite3 "VACUUM;" + +# 5. Reiniciar +docker service scale sistema_convex_backend=1 +``` + +#### Parte 3: Corrigir Codigo do Heartbeat (evitar novas versoes desnecessarias) + +**Arquivo:** `convex/machines.ts` (linhas 857-895) + +**Problema:** O codigo original adicionava inventory/metrics ao patch se eles EXISTIAM, nao se MUDARAM: ```typescript -// Verificar se ha mudancas reais nos dados -const hasMetadataChanges = Object.keys(metadataPatch).length > 0 -const hasHostnameChange = args.hostname && args.hostname !== machine.hostname -const needsMachineUpdate = hasMetadataChanges || hasHostnameChange || ... - -// So atualizar machines se houver mudancas reais -if (needsMachineUpdate) { - await ctx.db.patch(machine._id, { ...patch }) // SEM lastHeartbeatAt +// ANTES (criava versao mesmo com dados identicos) +if (args.inventory) { + metadataPatch.inventory = mergeInventory(...) } ``` -**3. Filtragem de campos volumetricos** - -`INVENTORY_BLOCKLIST` remove campos que mudam frequentemente ou sao muito grandes: -- `software` (lista de programas instalados) -- `extended` (dados estendidos) - -Hardware (CPU, memoria, placa-mae) **permanece visivel** em `/admin/devices`. - -### Resultado - -| Metrica | Antes | Depois | -|---------|-------|--------| -| Uso de memoria | 8-10 GiB | 4-5 GiB | -| Versoes por maquina | ~50/dia | ~2-3/dia | -| Percentual RAM (20 GiB) | 40-50% | 20-25% | - -### Monitoramento - -```bash -# Verificar uso de memoria -docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}' | grep convex - -# Verificar OOM kills recentes -journalctl -k | grep -i 'oom\|killed' | tail -20 - -# Restart do servico se necessario -docker service update --force sistema_convex_backend -``` - -### Limites de Memoria (Docker Swarm) - -Configurado em `stack.yml`: - -```yaml -services: - convex_backend: - deploy: - resources: - limits: - memory: 20G - reservations: - memory: 8G -``` - -Para alterar via CLI: -```bash -docker service update --limit-memory 20G --reserve-memory 8G sistema_convex_backend -``` - -## 11) Convex Self-Hosted — Erros de shape_inference (WebSocket 1006) - -### Problema - -O Convex self-hosted usa um sistema de **shape inference** para otimizar queries e exports. Quando documentos da mesma tabela tem campos com tipos incompativeis, o sistema falha ao unificar os "shapes". - -**Sintoma observado:** -- WebSocket desconectando com codigo `1006` (abnormal closure) -- Logs com erro `shape_inference` repetidos -- Export em loop: `Export beginning...` repetindo indefinidamente - -**Causa raiz:** -A tabela `_scheduled_job_logs` acumulou milhares de registros de cron jobs. Alguns tinham: -- `logLines: []` (array vazio) -- `logLines: ["mensagem de log"]` (array com strings) - -O shape inference nao consegue unificar `Array` com `Array`, causando falha continua. - -### Solucao Implementada (2025-12-09) - -**1. Garantir que cron jobs sempre produzam logs** - -Adicionamos `console.log()` obrigatorio no inicio de cada handler de cron: +**Solucao:** Comparar dados antes de incluir no patch: ```typescript -// convex/liveChat.ts - autoEndInactiveSessions -handler: async (ctx) => { - console.log("cron: autoEndInactiveSessions iniciado") - // ... resto do codigo -} - -// convex/usbPolicy.ts - cleanupStalePendingPolicies -handler: async (ctx, args) => { - console.log("cron: cleanupStalePendingPolicies iniciado") - // ... resto do codigo +// DEPOIS (so cria versao se dados MUDARAM) +if (args.inventory && typeof args.inventory === "object") { + const currentInventory = currentMetadata.inventory + const newInventoryStr = JSON.stringify(args.inventory) + const currentInventoryStr = JSON.stringify(currentInventory ?? {}) + if (newInventoryStr !== currentInventoryStr) { + metadataPatch.inventory = mergeInventory(currentInventory, args.inventory) + } } ``` -**2. Por que funciona** +### Resultado Final -Com o log obrigatorio, **todos** os registros de `_scheduled_job_logs` terao: -- `logLines: ["cron: iniciado", ...]` +| Metrica | ANTES (10/12 08:00) | DEPOIS (10/12 14:30) | Reducao | +|---------|---------------------|----------------------|---------| +| Banco SQLite | 450MB | **17MB** | **96%** | +| Memoria Convex | 19GB+ (OOM) | **395MB** | **98%** | +| RAM livre servidor | 404MB | **15GB** | +15GB | +| Registros no banco | 56.247 | 21.001 | -63% | +| OOM kills/dia | 12+ | **0** | -100% | -Isso garante consistencia de tipos (sempre `Array`), evitando o conflito de shapes. +### Por que a Solucao Funciona -### Arquivos Modificados +1. **Crons movidos**: Zero novos registros em `_scheduled_job_logs` +2. **Versoes antigas removidas**: Banco limpo, sem historico desnecessario +3. **Codigo corrigido**: Heartbeats com dados identicos = **0 versoes novas** -- `convex/liveChat.ts:751` — log no inicio de `autoEndInactiveSessions` -- `convex/usbPolicy.ts:313` — log no inicio de `cleanupStalePendingPolicies` +**Estimativa de crescimento futuro:** +- Antes: ~1.728 versoes/dia (6 maquinas x 288 heartbeats) +- Depois: ~30 versoes/dia (so quando inventory/metrics MUDA de verdade) +- **Reducao de 98%** no crescimento do banco -### Monitoramento +## 11) Convex Self-Hosted — Erros de shape_inference (RESOLVIDO) -```bash -# Verificar se ha erros de shape_inference nos logs -ssh root@154.12.253.40 "docker service logs sistema_convex_backend 2>&1 | grep -i 'shape_inference' | tail -10" +> **STATUS: RESOLVIDO em 10/12/2025 pela limpeza do banco** -# Verificar status dos cron jobs no dashboard -# Acesse: convex-admin.esdrasrenan.com.br > Scheduled Jobs +### Problema Original + +O Convex usa **shape inference** para otimizar queries. Quando documentos da mesma tabela tem campos com tipos incompativeis, o sistema falha. + +**Causa:** A tabela `_scheduled_job_logs` tinha registros com: +- `logLines: []` (Array) +- `logLines: ["mensagem"]` (Array) + +O shape inference nao consegue unificar esses tipos. + +### Solucao Aplicada + +A limpeza completa do banco (secao 10, Parte 2) **removeu todos os registros problematicos**: + +```sql +-- Isso removeu 4.227 registros de _scheduled_job_logs +DELETE FROM documents WHERE (id, ts) NOT IN (SELECT id, MAX(ts) FROM documents GROUP BY id); ``` -### Notas +### Status Atual -- Este e um bug interno do Convex self-hosted que pode ser corrigido em versoes futuras -- A solucao de adicionar logs obrigatorios e um workaround que nao afeta performance -- Se novos cron jobs forem adicionados, **sempre incluir um console.log no inicio** +- **Zero erros de shape_inference** nos logs desde 10/12/2025 +- **Crons movidos para Linux** = nenhum novo registro em `_scheduled_job_logs` +- Problema **nao deve recorrer** com a arquitetura atual -## 12) Cron Jobs — Movidos para Linux crontab +### Nota Historica -### Problema +Os `console.log()` adicionados em 09/12/2025 nos handlers de cron (para garantir `logLines` nao vazio) **nao sao mais necessarios** pois os crons foram movidos para Linux. Porem, foram mantidos no codigo por seguranca caso os crons sejam reativados no futuro. -Os cron jobs do Convex criam registros em `_scheduled_job_logs` que acumulam versoes em memoria. O Convex self-hosted carrega **todas as versoes de todos os documentos** em RAM, causando uso excessivo de memoria (~19GB para 60k docs). +## 12) Cron Jobs — Arquitetura Final -### Solucao Implementada (2025-12-10) +> **STATUS: MIGRADO para Linux crontab em 10/12/2025** -Os cron jobs foram movidos do Convex para endpoints HTTP no Next.js, chamados via crontab do Linux: +### Por que Migrar -| Cron Job Original | Endpoint HTTP | Frequencia | -|-------------------|---------------|------------| -| `auto-end-inactive-chat-sessions` | `/api/cron/chat-cleanup` | A cada 1 min | -| `cleanup-stale-usb-policies` | `/api/cron/usb-cleanup` | A cada 30 min | +Os cron jobs do Convex criam registros em `_scheduled_job_logs` que: +1. Acumulam versoes em memoria (nunca sao removidos automaticamente) +2. Cron de 1 minuto = 1.440 registros/dia = 43.200/mes +3. Cada registro ocupa espaco no SQLite in-memory -### Configuracao do Crontab na VPS +### Arquitetura Atual + +``` +┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────┐ +│ Linux crontab │────▶│ Next.js API Route │────▶│ Convex Mutation│ +│ (VPS) │ │ /api/cron/* │ │ (funcao real) │ +└─────────────────┘ └──────────────────────┘ └─────────────────┘ + │ │ + │ curl a cada X min │ ConvexHttpClient + ▼ ▼ + Nao cria logs Executa a logica + no Convex sem criar _scheduled_job_logs +``` + +### Mapeamento de Crons + +| Funcao Original | Endpoint HTTP | Frequencia | Status | +|-----------------|---------------|------------|--------| +| `auto-end-inactive-chat-sessions` | `/api/cron/chat-cleanup` | 1 min | **ATIVO** | +| `cleanup-stale-usb-policies` | `/api/cron/usb-cleanup` | 30 min | **ATIVO** | +| `report-export-runner` | - | 15 min | DESABILITADO (flag) | +| `auto-pause-internal-lunch` | - | diario | DESABILITADO (flag) | + +### Configuracao na VPS ```bash -# Acessar a VPS -ssh -i ~/.ssh/codex_ed25519 root@154.12.253.40 +# Crontab atual (root@154.12.253.40) +CRON_SECRET="reports-cron-938fa2e0f663b43bb43203413783d6415e6d0cdfb58c25a749b4c90241c4017b" -# Editar crontab -crontab -e - -# Adicionar as linhas: -CRON_SECRET="seu_token_secreto_aqui" - -# Encerrar sessoes de chat inativas (a cada minuto) +# Chat cleanup - a cada minuto * * * * * curl -s "https://tickets.esdrasrenan.com.br/api/cron/chat-cleanup" -H "x-cron-secret: $CRON_SECRET" >/dev/null 2>&1 -# Limpar policies USB pendentes (a cada 30 min) +# USB policy cleanup - a cada 30 minutos */30 * * * * curl -s "https://tickets.esdrasrenan.com.br/api/cron/usb-cleanup" -H "x-cron-secret: $CRON_SECRET" >/dev/null 2>&1 ``` -### Autenticacao +### Arquivos Relacionados -Os endpoints usam o header `x-cron-secret` para autenticacao. O valor deve ser igual a variavel de ambiente `CRON_SECRET` ou `REPORTS_CRON_SECRET` configurada no Next.js. +- `src/app/api/cron/chat-cleanup/route.ts` - Endpoint para encerrar chats inativos +- `src/app/api/cron/usb-cleanup/route.ts` - Endpoint para limpar policies USB +- `convex/crons.ts` - Crons originais (COMENTADOS, mantidos para referencia) -### Verificar se esta funcionando +## 13) Monitoramento e Manutencao — Guia Pos-Correcao (10/12/2025) + +### Status Esperado (Sistema Saudavel) + +| Metrica | Valor Esperado | Alerta Se | +|---------|----------------|-----------| +| Memoria Convex | < 1GB | > 5GB | +| Banco SQLite | < 50MB | > 200MB | +| OOM kills/dia | 0 | > 0 | +| Erros shape_inference | 0 | > 0 | + +### Comandos de Verificacao ```bash -# Testar endpoint manualmente -curl -s "https://tickets.esdrasrenan.com.br/api/cron/chat-cleanup" \ - -H "x-cron-secret: SEU_TOKEN" | jq +# Verificar memoria do Convex +ssh -i ~/.ssh/codex_ed25519 root@154.12.253.40 \ + "docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}' | grep convex" -# Verificar logs do cron -grep CRON /var/log/syslog | tail -20 +# Verificar tamanho do banco +ssh -i ~/.ssh/codex_ed25519 root@154.12.253.40 \ + "ls -lh /var/lib/docker/volumes/sistema_convex_data/_data/db.sqlite3" + +# Verificar OOM kills (desde meia-noite) +ssh -i ~/.ssh/codex_ed25519 root@154.12.253.40 \ + "journalctl --since '00:00:00' -k | grep -c 'Killed process.*convex'" + +# Verificar erros nos logs +ssh -i ~/.ssh/codex_ed25519 root@154.12.253.40 \ + "docker service logs sistema_convex_backend --tail 50 2>&1 | grep -E 'ERROR|shape_inference'" ``` -### Codigo-fonte +### Quando Fazer Limpeza Manual -- `src/app/api/cron/chat-cleanup/route.ts` -- `src/app/api/cron/usb-cleanup/route.ts` -- `convex/crons.ts` (crons originais comentados) +**NAO e necessario fazer limpeza periodica** com a correcao de codigo aplicada. O sistema deve se manter estavel indefinidamente. + +Fazer limpeza manual apenas se: +1. Banco SQLite ultrapassar 200MB +2. Memoria Convex ultrapassar 5GB +3. Ocorrerem OOM kills + +### Procedimento de Limpeza (Se Necessario) + +```bash +# 1. Verificar estado atual +ssh root@154.12.253.40 "ls -lh /var/lib/docker/volumes/sistema_convex_data/_data/db.sqlite3" + +# 2. Se > 200MB, fazer limpeza: +ssh root@154.12.253.40 << 'EOF' +docker service scale sistema_convex_backend=0 +cd /var/lib/docker/volumes/sistema_convex_data/_data +cp db.sqlite3 db.sqlite3.backup-$(date +%Y%m%d-%H%M%S) +sqlite3 db.sqlite3 "DELETE FROM documents WHERE (id, ts) NOT IN (SELECT id, MAX(ts) FROM documents GROUP BY id); VACUUM;" +docker service scale sistema_convex_backend=1 +EOF +``` + +### Resumo das Correcoes Aplicadas + +| Problema | Solucao | Arquivo | Commit | +|----------|---------|---------|--------| +| Crons acumulando logs | Mover para Linux crontab | `convex/crons.ts`, `src/app/api/cron/*` | 178c7d7 | +| Heartbeat criando versoes | Comparar dados antes de patch | `convex/machines.ts:857-895` | b6f69d7 | +| Versoes antigas no banco | Limpeza manual + VACUUM | N/A (operacao manual) | N/A | + +### Backups Disponiveis (VPS) + +``` +/var/lib/docker/volumes/sistema_convex_data/_data/ +├── db.sqlite3 17MB (atual, limpo) +├── db.sqlite3.backup-20251210-084225 452MB (antes dos crons) +├── db.sqlite3.backup-pre-cleanup-20251210-100336 450MB (antes da limpeza final) +├── db.sqlite3.backup-20251209-234720 447MB (09/12) +└── db.sqlite3.pre-vacuum-20251209 449MB (antes do primeiro vacuum) +``` + +--- + +Ultima atualizacao: **10/12/2025** — Problema de OOM resolvido definitivamente. Sistema estavel com 395MB de memoria (1.93% do limite de 20GB)