Ir al contenido principal
Laravel, shipping fast.

Capitulo 9

Monitoreo y observabilidad

Julian Beaujardin

No serás el primero en notar la mayoría de las caídas. Un comerciante refrescando su panel lo nota antes. Una llamada a Statamic empieza a agotar su tiempo a las dos de la mañana y no hay nadie despierto para verlo. Un pico de límite de frecuencia aparece en un informe mensual tres días después de importar. El hueco entre «algo se rompió» y «sabes que algo se rompió» es la única parte de una caída que de verdad controlas. Ciérralo.

Este capítulo cubre dos trabajos que la gente colapsa constantemente en uno: monitorización y observabilidad. La monitorización responde a preguntas que se te ocurrió hacer por adelantado: ¿está en pie la base de datos, sube la tasa de errores, está atascada la cola? La observabilidad responde a la pregunta que no se te ocurrió: ¿por qué las peticiones de este token concreto empezaron a agotar su tiempo a las 9:47 de esta mañana, y qué llamada externa estaba en vuelo cuando pasó? Un health check no te dirá por qué se cayó el sitio de un comerciante. Una traza de su petición fallida sí.

La monitorización te dice que algo va mal. La observabilidad te dice qué.

Health checks que significan algo

Un health check que siempre devuelve {"status": "ok"} es peor que no tener ninguno, porque te miente con confianza. Tu balanceador sigue enviando tráfico a una máquina cuyo pool de conexiones está agotado, porque lo único que ese health check verificó de verdad es que el proceso web todavía podía renderizar JSON.

El health check compartido de la flota verifica en cambio las cosas concretas que impiden que una petición se complete.

// api-infrastructure/src/Http/Controllers/HealthController.php
final readonly class HealthController
{
    public function __invoke(): Responsable
    {
        $databaseStatus = (new DatabaseHealthCheck)->check();
        $cacheStatus = (new CacheHealthCheck)->check();
        $queueStatus = (new QueueHealthCheck)->check();

        return new ModelResponse(
            data: new HealthResource(
                resource: HealthDTOMapper::toDTO(
                    databaseStatus: $databaseStatus,
                    cacheStatus: $cacheStatus,
                    queueStatus: $queueStatus,
                ),
            ),
            status: $this->status($databaseStatus, $cacheStatus, $queueStatus),
        );
    }

    // status() maps the three booleans to HTTP_OK or HTTP_SERVICE_UNAVAILABLE
}

Todas las aplicaciones de API de la flota traen este controlador del paquete compartido en lugar de escribir el suyo, y devuelve HTTP_SERVICE_UNAVAILABLE en cuanto falla cualquiera de las tres comprobaciones, y HTTP_OK sólo cuando pasan las tres.

  • DatabaseHealthCheck: llama a DB::getPdo() y luego ejecuta SELECT 1. Si el pool está agotado o las credenciales están mal, esto falla de inmediato en lugar de esperar a que una consulta real agote su tiempo más adelante.
  • CacheHealthCheck: en el driver genérico escribe una clave aleatoria, la lee y confirma que el valor da la vuelta antes de olvidarla; en Redis hace un ping. Un fallo de caché en una clave aleatoria no demuestra que el almacén sea alcanzable. Una escritura y lectura correctas sí.
  • QueueHealthCheck: comprueba el driver realmente configurado, contando filas en la tabla de cola o haciendo ping a Redis. Una conexión sync siempre pasa, porque no hay un proceso worker aparte que pudiera estar caído.

¿Por qué tres comprobaciones en lugar de un booleano? Porque un solo booleano te dice que el servicio no está sano. Tres te dicen qué dependencia no lo está, y esa es la diferencia entre un arreglo de cinco minutos y uno de veinte adivinando.