Appearance
Troubleshooting
◄ Anterior: Testing | Índice | Siguiente: Referencia ►
Tabla de Contenidos
- Job Stuck in "running"
- Notificaciones No Llegan
- Multi-Tenant Isolation Issues
- Performance Degradation (Jobs Lentos)
- Worker Crashes
- DOS Protection Triggers
Job Stuck in "running"
Síntoma: Job nunca pasa de status='running'
Causas posibles:
- Worker crashed (OOM, killed, fatal error)
- Worker aún ejecutándose (job muy lento)
- DB update falló
Diagnóstico:
bash
# Verificar logs del worker
tail -f /logs/background-jobs.log | grep "job_id:123"
# Buscar proceso PHP del worker
ps aux | grep "background-worker.php 123"
# Verificar job en BD
psql -c "SELECT * FROM background_jobs WHERE id = 123;"Solución:
Si worker no existe (crashed):
sql
-- Marcar como failed manualmente
UPDATE background_jobs
SET status = 'failed',
error = 'Worker crashed',
completed_at = NOW()
WHERE id = 123;Si worker existe pero tardando mucho:
bash
# Esperar o kill si es necesario
kill -9 {PID}Prevención:
- Cronjob de cleanup de stale jobs (cada 10 minutos)
- Límites de memoria y timeout adecuados
- Logging detallado para debugging
Cronjob (/etc/cron.d/cleanup-stale-jobs):
bash
*/10 * * * * php /var/www/cli/cleanup-stale-jobs.php >> /var/log/stale-jobs.log 2>&1Script de cleanup:
php
// cli/cleanup-stale-jobs.php
$staleMinutes = 60; // Jobs running > 1 hora
$staleJobs = $repo->findStaleJobs($staleMinutes);
foreach ($staleJobs as $job) {
$job->status = 'failed';
$job->error = 'Job timed out (probablemente worker crashed)';
$job->completed_at = date('Y-m-d H:i:s');
$repo->update($job);
// Crear notificación
$notificationService->createFromJobResult($job);
}Notificaciones No Llegan
Síntoma: Job completa pero usuario no recibe notificación
Causas posibles:
- NotificationService no se llama en JobExecutor
- Error al crear notificación (exception silenciada)
- Frontend no consulta notificaciones
Diagnóstico:
bash
# Verificar logs
tail -f /logs/background-jobs.log | grep "notification"
# Verificar en BD
psql -c "SELECT * FROM notifications WHERE metadata->>'job_id' = '123';"
# Verificar frontend polling
# (DevTools → Network → Filtrar por /notifications)Solución:
Si notificación no existe en BD:
php
// Crear manualmente o re-ejecutar NotificationService
$notificationService->createFromJobResult($job);Si notificación existe pero frontend no consulta:
- Verificar intervalo de polling (debe ser <= 10 segundos)
- Verificar query params (unread=true)
- Verificar autenticación (JWT válido)
Prevención:
- Tests de integración end-to-end
- Logging de creación de notificaciones
- Monitoring de notificaciones creadas vs jobs completados
Multi-Tenant Isolation Issues
Síntoma: Job ejecuta en schema incorrecto, accede a datos de otra sucursal
Causas posibles:
- Schema NO guardado en job.payload
- ConnectionManager NO configurado en worker
- Handler NO usa ConnectionManager (conexión directa)
Diagnóstico:
bash
# Verificar schema en job
psql -c "SELECT id, schema FROM background_jobs WHERE id = 123;"
# Verificar logs de ConnectionManager
tail -f /logs/background-jobs.log | grep "search_path"
# Test de aislamiento
# (verificar que job en suc0001 NO accede a suc0002)Solución:
Si schema falta:
php
// JobDispatcher debe guardar schema SIEMPRE
$job = new BackgroundJob(
schema: $request->getHeaderLine('X-Schema'), // CRÍTICO
// ...
);Si ConnectionManager no configurado:
php
// JobExecutor DEBE setear search_path
$this->connectionManager->setSearchPath($job->schema);Prevención:
- Tests de integración multi-tenant OBLIGATORIOS
- Code review checklist: "¿schema configurado?"
- Validación en JobExecutor: exception si schema faltante
Checklist de Code Review:
- [ ] ✅ JobController extrae
X-Schemaheader - [ ] ✅ JobDispatcher recibe
$schemacomo parámetro - [ ] ✅ BackgroundJob tiene campo
schemaNOT NULL - [ ] ✅ JobExecutor recibe
ConnectionManageren constructor - [ ] ✅ JobExecutor llama
setSearchPath()ANTES de ejecutar handler - [ ] ✅ Tests de integración verifican aislamiento multi-tenant
Performance Degradation (Jobs Lentos)
Síntoma: Jobs que antes tardaban 1 minuto ahora tardan 10 minutos
Causas posibles:
- Tabla background_jobs muy grande (millones de rows)
- Índices faltantes o corruptos
- Handler con N+1 queries
- DB con locks o deadlocks
Diagnóstico:
bash
# Verificar tamaño de tabla
psql -c "SELECT pg_size_pretty(pg_total_relation_size('background_jobs'));"
# Verificar índices usados
psql -c "EXPLAIN ANALYZE SELECT * FROM background_jobs WHERE user_id = 1 AND status = 'pending';"
# Monitorear queries lentas
tail -f /var/log/postgresql/postgresql.log | grep "duration:"
# Verificar locks
psql -c "SELECT * FROM pg_locks WHERE NOT granted;"Solución:
Si tabla muy grande:
bash
# Cleanup de jobs antiguos
php cli/cleanup-old-jobs.phpScript de cleanup:
php
// cli/cleanup-old-jobs.php
$sql = "DELETE FROM background_jobs
WHERE completed_at < NOW() - INTERVAL '30 days'";
$stmt = $pdo->prepare($sql);
$stmt->execute();
echo "Deleted " . $stmt->rowCount() . " jobs\n";Si índices faltantes:
sql
-- Crear índices recomendados
CREATE INDEX idx_background_jobs_user_status ON background_jobs (user_id, status);Si N+1 queries en handler:
php
// Reemplazar loops con queries batch
// ANTES (N+1):
foreach ($clienteIds as $id) {
$cliente = $clienteModel->findById($id); // N queries
}
// DESPUÉS (1 query):
$clientes = $clienteModel->findByIds($clienteIds); // 1 queryPrevención:
- Monitoring de execution time por tipo de job
- Alertas cuando P95 > threshold
- Cleanup automático de jobs antiguos
Worker Crashes
Síntoma: Worker process termina sin actualizar job status
Causas comunes:
- Out of Memory (OOM)
- PHP Fatal Error
- Killed by OS (OOM killer)
- Uncaught exception
Diagnóstico:
bash
# Verificar logs del sistema
dmesg | grep -i "killed process"
# Verificar logs de PHP
tail -f /var/log/php-fpm.log
# Verificar memoria disponible
free -hSolución inmediata:
sql
-- Marcar jobs crashed como failed
UPDATE background_jobs
SET status = 'failed',
error = 'Worker crashed (OOM)',
completed_at = NOW()
WHERE status = 'running'
AND started_at < NOW() - INTERVAL '1 hour';Prevención:
- Aumentar
memory_limiten php.ini (ej: 512M) - Limitar tamaño de payload (ej: max 100 items por batch)
- Implementar pagination en handlers grandes
- Monitorear uso de memoria durante ejecución
DOS Protection Triggers
Síntoma: Usuario recibe error 429 Too Many Requests
Causa: Usuario tiene >= 10 jobs pendientes
Diagnóstico:
sql
-- Contar jobs pendientes por usuario
SELECT user_id, COUNT(*) as pending_count
FROM background_jobs
WHERE status = 'pending'
GROUP BY user_id
HAVING COUNT(*) >= 10;Solución:
Para el usuario:
- Esperar a que terminen algunos jobs
- Cancelar jobs innecesarios
Para el sistema:
- Aumentar límite si es necesario (configuración)
- Optimizar handlers para que terminen más rápido
Configuración:
php
// config/features.php
return [
'background_jobs' => [
'max_pending_per_user' => 20, // Aumentar de 10 a 20
],
];Troubleshooting Checklist General
Cuando un Job Falla
Verificar logs:
bashtail -f /logs/background-jobs.log | grep "job_id:123"Verificar en BD:
sqlSELECT * FROM background_jobs WHERE id = 123;Verificar proceso worker:
bashps aux | grep "background-worker.php 123"Verificar schema (multi-tenant):
sqlSELECT schema FROM background_jobs WHERE id = 123;Verificar notificación:
sqlSELECT * FROM notifications WHERE metadata->>'job_id' = '123';