Etiqueta: monitorización

  • Reciclaje de memoria en workers PHP de larga ejecución

    Reciclaje de memoria en workers PHP de larga ejecución

    Resumen: técnicas prácticas para controlar y reciclar memoria en procesos PHP de larga ejecución (queues, daemons, RoadRunner/Swoole) y mantener la huella de memoria estable.

    Introducción

    Los workers PHP de larga ejecución (colas, daemons, RoadRunner, Swoole) tienden a crecer en memoria con el tiempo: referencias no liberadas, objetos grandes retenidos y fragmentación del gestor de memoria provocan aumento del RSS y degradación del rendimiento.

    Este artículo condensa estrategias prácticas para reciclar memoria sin recurrir a reinicios agresivos por defecto: límites controlados, limpieza explícita y patrones de procesamiento que reducen la presión de memoria.

    • Referencias retenidas en closures y objetos con referencias circulares.
    • Variables estáticas que acumulan cachés ilimitados.
    • Procesamiento en lotes demasiado grandes (arrays/objetos en memoria).
    • Fragmentación interna del gestor de memoria de Zend o fugas en extensiones (ej.: pdo_mysql).

    Prerrequisitos

    Antes de aplicar estas técnicas asegúrate de tener control sobre la ejecución del worker y acceso a las extensiones/funciones necesarias.

    • PHP CLI con soporte para pcntl si vas a implementar reinicios controlados.
    • Capacidad de supervisión/respawn (supervisor, systemd, o el propio orquestador de RoadRunner/Swoole).
    • Acceso a herramientas de monitorización (ps, pmap, logs de memoria).

    Desarrollo

    Procedimiento

    1. Establecer límites y reinicios controlados.
    2. Eliminar referencias y forzar recolección de ciclos.
    3. Procesar en chunks para evitar picos de memoria.
    4. Reutilizar objetos pesados (pooling) en vez de recrearlos constantemente.
    5. Monitorizar tendencias y métricas de uso de memoria.

    A continuación ejemplos de implementación de cada punto crítico.

    1) Reinicio controlado: comprobar uso de memoria y salir para que el supervisor recree el proceso.

    <?php
    // dentro del loop principal del worker
    if (memory_get_usage(true) > 256 * 1024 * 1024) {
        // salir para que el supervisor o manager respawnee un proceso limpio
        exit(1);
    }
    Lenguaje del código: PHP (php)

    2) Limpieza explícita de referencias y recolección de ciclos: unset no siempre basta si existen referencias circulares.

    <?php
    // después de procesar una tarea pesada
    unset($largeStructure);
    // forzar recolección de ciclos si hay objetos con referencias circulares
    gc_collect_cycles();
    Lenguaje del código: PHP (php)

    3) Chunking: evitar cargar datasets completos en memoria. Procesa por lotes y libera cada lote.

    <?php
    foreach (array_chunk($rows, 5000) as $chunk) {
        process($chunk);
        unset($chunk);
        gc_collect_cycles();
    }
    Lenguaje del código: PHP (php)

    4) Reutilización (pooling): evita crear y destruir objetos pesados frecuentemente. Mantén conexiones o parsers en pool cuando sea seguro.

    5) Monitorización: usa herramientas del sistema y funciones internas para trazar tendencias de memoria y detectar fugas tempranas.

    ps -o pid,rss,cmd -C php
    # o para inspección puntual de PID
    pmap <PID> | tail -n 1
    Lenguaje del código: Bash (bash)

    Ejemplos

    Ejemplo de loop de worker que combina chequeo de memoria, chunking y recolector de ciclos.

    <?php
    // worker.php - bucle simplificado
    while (true) {
        $rows = fetchPendingRows(50000); // pseudocódigo
    
        foreach (array_chunk($rows, 5000) as $chunk) {
            process($chunk);
            unset($chunk);
            gc_collect_cycles();
        }
    
        // chequeo de memoria: salir si supera 256MB para que el supervisor reinicie
        if (memory_get_usage(true) > 256 * 1024 * 1024) {
            exit(1);
        }
    
        // dormir o esperar por nueva tarea
        usleep(100000);
    }
    Lenguaje del código: PHP (php)

    Checklist

    1. Habilitar y probar reinicios controlados (pcntl o supervisión externa).
    2. Identificar y eliminar referencias circulares; usar gc_collect_cycles tras tareas pesadas.
    3. Procesar en chunks y liberar cada lote explícitamente.
    4. Reutilizar objetos pesados mediante pooling cuando sea seguro.
    5. Registrar y monitorizar memoria (memory_get_peak_usage, ps, pmap) para detectar tendencias.

    Trata la memoria como una moneda: mézclala, recíclala y no la derroches en procesos de larga vida.

    Conclusión

    Workers PHP de larga ejecución son útiles, pero requieren disciplina: límites claros, limpieza explícita y monitorización. Implementa reinicios controlados, limpia referencias, procesa por chunks y registra tendencias para mantener la huella de memoria saneada.

    Si no tienes información para afirmar compatibilidades o comportamientos específicos de versiones en tu entorno, prueba primero en un entorno de staging y mide antes de aplicar en producción.

  • Evolución de las colas en Laravel: workers, deferred y background

    Evolución de las colas en Laravel: workers, deferred y background

    Este artículo resume la evolución del sistema de colas en Laravel: desde los workers tradicionales hasta el driver background basado en concurrencia. Explica usos, diferencias y ejemplos prácticos de implementación.

    Introducción

    Laravel ha transitado de un sistema de colas basado en workers persistentes a opciones que reducen la complejidad operativa: drivers deferred y background (impulsado por concurrencia).

    Prerrequisitos

    Antes de aplicar cualquiera de las estrategias descritas, confirma lo siguiente:

    • Proyecto Laravel con entorno PHP configurado.
    • Driver de colas definido (Redis, database, etc.) si planeas usar workers tradicionales.
    • En caso de background driver, la configuración de conexiones en config/queue.php debe incluir la conexión ‘background’ (ver ejemplo).

    Desarrollo

    Procedimiento

    A continuación se describen las tres aproximaciones principales y cómo elegir entre ellas según la carga y la tolerancia a fallos.

    1. Workers tradicionales: ejecutan jobs mediante procesos persistentes (p. ej. php artisan queue:work redis), adecuados para cargas pesadas y con control avanzado de reintentos y fallos.
    2. Driver deferred: ejecuta el job después de enviar la respuesta HTTP, sin worker. Adecuado para tareas ligeras y no críticas.
    3. Driver background (concurrencia): ejecuta jobs en procesos PHP separados, sin worker persistente; equilibrio entre robustez y simplicidad operativa.

    Ventajas y limitaciones resumidas:

    • Workers tradicionales: alto rendimiento y control; requieren DevOps para gestionar procesos.
    • Deferred: baja sobrecarga; limitado en reintentos y duración de tarea.
    • Background: sin workers dedicados, más robusto que deferred; útil para tareas de duración media.
    php artisan queue:work redis
    Lenguaje del código: Bash (bash)
    <?php
    return [
        'connections' => [
            'background' => [
                'driver' => 'background',
            ],
        ],
    ];
    Lenguaje del código: PHP (php)

    El ejemplo anterior muestra cómo declarar una conexión ‘background’ en config/queue.php. Su dispatch usa la conexión para ejecutar el job en otro proceso PHP.

    Ejemplos

    Ejemplo de clase Job que implementa ShouldQueue y se serializa para su ejecución en background o por un worker.

    <?php
    namespace App\Jobs;
    
    use Illuminate\Bus\Queueable;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Foundation\Bus\Dispatchable;
    use Illuminate\Queue\SerializesModels;
    
    class RecordDelivery implements ShouldQueue
    {
        use Dispatchable, Queueable, SerializesModels;
    
        public function __construct(public $order)
        {
        }
    
        public function handle()
        {
            // lógica de procesamiento
        }
    }
    Lenguaje del código: PHP (php)

    Dispatch desde un controlador usando la conexión background para evitar bloquear la petición HTTP.

    <?php
    use App\Jobs\RecordDelivery;
    
    class DeliveryController
    {
        public function store(Request $request)
        {
            $order = Order::create([...]);
            RecordDelivery::dispatch($order)->onConnection('background');
    
            return response()->json(['status' => 'processing'], 202);
        }
    }
    Lenguaje del código: PHP (php)

    Checklist

    1. Determina la criticidad de la tarea (alta → worker tradicional).
    2. Si la tarea es ligera y no crítica, considera deferred.
    3. Para equilibrio operativo (sin workers pero más robusto que deferred), usa el driver background.
    4. Configura reintentos, timeouts y monitoreo según el método elegido.

    Conclusión

    La evolución de las colas en Laravel ofrece alternativas para distintos perfiles de carga operativa. Elegir entre workers, deferred o background depende de la carga, la tolerancia a fallos y la capacidad operativa del equipo.

    Si buscas simplificar la operación sin renunciar a capacidad de procesamiento, el driver background —impulsado por concurrencia— es una opción intermedia interesante.

  • Go y PHP: cómo diseñar un sistema híbrido eficiente

    Go y PHP: cómo diseñar un sistema híbrido eficiente

    Este artículo describe una aproximación práctica para integrar Go y PHP en un sistema híbrido: cuándo delegar tareas, cómo comunicar microservicios y cómo desplegar con Docker.

    Incluye arquitectura, retos comunes, ejemplos de código (PHP, servicio en Go, Dockerfiles, docker-compose) y una checklist para implementación.

    Introducción

    Cuando una aplicación PHP comienza a mostrar cuellos de botella por procesamiento concurrente o tareas CPU-intensivas, una reescritura completa puede ser costosa y arriesgada.

    Una alternativa es un diseño híbrido: conservar PHP para la interfaz web y operaciones CRUD habituales, y delegar a Go las tareas de alto rendimiento y concurrencia.

    Prerrequisitos

    Antes de implementar la integración, confirma lo siguiente en tu entorno:

    • Servicio PHP con cliente HTTP (ej.: Guzzle) y ORM para la base de datos.
    • Servicio Go independiente para procesamiento concurrente.
    • Contenerización con Docker para ambos servicios y orquestación mínima (docker-compose o similar).
    • Definición clara de API (endpoints, formatos JSON, timeouts y reintentos).

    Desarrollo

    La pieza clave es separar responsabilidades y diseñar una comunicación eficiente entre servicios para evitar latencias y fallos de cascada.

    Arquitectura recomendada: microservicios independientes que intercambian JSON vía HTTP/REST. PHP se queda con las rutas web y consultas habituales; Go procesa trabajos intensivos y responde de forma asíncrona cuando sea pertinente.

    Procedimiento

    Pasos prácticos para integrar ambos servicios en producción:

    1. Identificar rutas y operaciones CPU-intensivas o altamente concurrentes que se puedan delegar a Go.
    2. Definir API REST clara entre PHP y Go con contrato JSON, códigos de estado y manejo de errores.
    3. Implementar timeouts y reintentos en el cliente HTTP de PHP; evitar bloquear las peticiones de usuario por procesos largos.
    4. Contenerizar ambos servicios y usar orquestación para escalado independiente.
    5. Monitorear latencia, errores y uso de recursos; ajustar goroutines y límites de concurrencia en Go.

    En entornos donde la respuesta inmediata no es requerida, considera que Go inicie el trabajo de forma asíncrona y devuelva un estado “aceptado” para que PHP continúe sin bloqueo.

    Ejemplos

    A continuación hay ejemplos extraídos de un caso real: cliente PHP (Guzzle), servicio en Go, Dockerfiles y docker-compose.

    Ejemplo: llamada desde PHP usando Guzzle (manejar timeout y convertir la respuesta JSON).

    <?php
    use GuzzleHttp\Client;
    $client = new Client();
    $response = $client->post('http://go-service/api/process', [
        'json' => ['data' => $data],
        'timeout' => 5,
    ]);
    $processedData = json_decode($response->getBody()->getContents(), true);
    Lenguaje del código: PHP (php)

    Servicio Go: endpoint que acepta JSON y lanza el procesamiento de forma concurrente. (Se muestra como texto plano para mantener compatibilidad del bloque.)

    package main
    import (
        "encoding/json"
        "fmt"
        "net/http"
        "time"
    )
    
    type RequestData struct {
        Data string `json:"data"`
    }
    
    func processData(w http.ResponseWriter, r *http.Request) {
        var reqData RequestData
        decoder := json.NewDecoder(r.Body)
        if err := decoder.Decode(&reqData); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        go func(data string) {
            time.Sleep(2 * time.Second) // Simula procesamiento
            fmt.Println("Processed data:", data)
        }(reqData.Data)
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(map[string]string{"status": "processing started"})
    }
    
    func main() {
        http.HandleFunc("/api/process", processData)
        http.ListenAndServe(":8080", nil)
    }
    Lenguaje del código: texto plano (plaintext)

    Dockerfile para el servicio PHP (ejemplo presente en la referencia):

    FROM php:8.0-apache
    COPY . /var/www/html/
    RUN docker-php-ext-install pdo pdo_mysql
    EXPOSE 80
    Lenguaje del código: Dockerfile (dockerfile)

    Dockerfile para el servicio Go (ejemplo presente en la referencia):

    FROM golang:1.19-alpine
    WORKDIR /app
    COPY . .
    RUN go build -o go-service
    CMD ["./go-service"]
    EXPOSE 8080
    Lenguaje del código: Dockerfile (dockerfile)

    docker-compose para levantar ambos servicios juntos:

    version: '3'
    services:
      php-service:
        build:
          context: ./php
        ports:
          - "8081:80"
      go-service:
        build:
          context: ./go
        ports:
          - "8080:8080"
    Lenguaje del código: YAML (yaml)

    Checklist

    1. Detectar operaciones costosas y definir contratos API para delegarlas.
    2. Implementar timeouts, reintentos y manejo de errores en PHP.
    3. Contenerizar servicios y configurar orquestación para escalado independiente.
    4. Monitorizar latencia, uso de CPU/memoria y ajustar concurrencia en Go.
    5. Probar fallos en la comunicación y definir fallbacks razonables en PHP.

    Conclusión

    Un enfoque híbrido Go + PHP permite aprovechar lo mejor de cada lenguaje sin rehacer todo el sistema. La clave es separar responsabilidades, diseñar APIs robustas y automatizar despliegues.

    Si decides seguir esta ruta, prioriza pruebas de integración, políticas de reintento y observabilidad para minimizar impactos en la experiencia de usuario.