Skip to content

Troubleshooting

◄ Anterior: Testing | Índice | Siguiente: Referencia ►


Tabla de Contenidos


Job Stuck in "running"

Síntoma: Job nunca pasa de status='running'

Causas posibles:

  1. Worker crashed (OOM, killed, fatal error)
  2. Worker aún ejecutándose (job muy lento)
  3. 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>&1

Script 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:

  1. NotificationService no se llama en JobExecutor
  2. Error al crear notificación (exception silenciada)
  3. 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:

  1. Schema NO guardado en job.payload
  2. ConnectionManager NO configurado en worker
  3. 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-Schema header
  • [ ] ✅ JobDispatcher recibe $schema como parámetro
  • [ ] ✅ BackgroundJob tiene campo schema NOT NULL
  • [ ] ✅ JobExecutor recibe ConnectionManager en 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:

  1. Tabla background_jobs muy grande (millones de rows)
  2. Índices faltantes o corruptos
  3. Handler con N+1 queries
  4. 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.php

Script 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 query

Prevenció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 -h

Solució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_limit en 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

  1. Verificar logs:

    bash
    tail -f /logs/background-jobs.log | grep "job_id:123"
  2. Verificar en BD:

    sql
    SELECT * FROM background_jobs WHERE id = 123;
  3. Verificar proceso worker:

    bash
    ps aux | grep "background-worker.php 123"
  4. Verificar schema (multi-tenant):

    sql
    SELECT schema FROM background_jobs WHERE id = 123;
  5. Verificar notificación:

    sql
    SELECT * FROM notifications WHERE metadata->>'job_id' = '123';

◄ Anterior: Testing | Índice | Siguiente: Referencia ►