Etiqueta: profiling

  • 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.

  • Trucos PHP para evitar picos de CPU en producción

    Trucos PHP para evitar picos de CPU en producción

    Este artículo resume tácticas prácticas y verificables para reducir picos de CPU en aplicaciones PHP en producción. Se centra en cambios puntuales: bucles, caché, extensiones y preload.

    Introducción

    Los picos de CPU suelen ser consecuencia de patrones repetitivos: bucles descontrolados, reconstrucciones de caché simultáneas, operaciones de cálculo en PHP y bloqueos que consumen ciclos. Aquí verás acciones concretas para reducir su impacto sin reescribir todo el stack.

    Prerrequisitos

    Antes de aplicar las tácticas: asegúrate de tener monitoreo de CPU y trazas, un sistema de caché (p. ej. Redis) y OPcache habilitado. Algunas recomendaciones asumen disponibilidad de extensiones (GMP/BCMath) o mecanismos de event loop si se migran procesos largos.

    opcache.enable=1
    opcache.preload=/var/www/preload.php
    Lenguaje del código: TOML, también INI (ini)

    Desarrollo

    Procedimiento

    A continuación se describen las tácticas principales, con ejemplos y recomendaciones de implementación mínima para reducir picos de CPU.

    1) Controlar bucles y procesamiento por lotes: no iteres colecciones potencialmente enormes en una sola pasada; procesa en chunks y paginación para limitar uso de CPU por iteración.

    <?php
    foreach ($users as $user) {
        process($user);
    }
    Lenguaje del código: PHP (php)
    <?php
    foreach (array_chunk($users, 500) as $batch) {
        foreach ($batch as $user) {
            process($user);
        }
    }
    Lenguaje del código: PHP (php)

    2) Caché: evita el “dogpile” (cache stampede) usando coalescencia de peticiones; solo un worker debe reconstruir el caché mientras los demás leen la versión existente.

    <?php
    if ($lock = $redis->setnx("lock:feed", 1)) {
        $redis->expire("lock:feed", 30);
        $data = buildFeed();
        $redis->set("cache:feed", $data, 300);
    } else {
        $data = $redis->get("cache:feed");
    }
    Lenguaje del código: PHP (php)

    3) Delegar cálculos pesados a extensiones o bibliotecas nativas. Para parsing/validación de JSON o aritmética de gran precisión, usar extensiones C (GMP, BCMath) reduce tiempo de CPU en PHP puro. El uso de funciones específicas (por ejemplo, json_validate() si está disponible) evita sobrecarga por manejo de excepciones.

    4) Evitar sleep() en workflows asíncronos. Reemplaza bucles con sleep por event loops (ReactPHP, Swoole) o por colas con workers que esperan con mecanismos eficientes.

    5) OPcache y preload: habilita OPcache y pre-carga clases/archivos críticos para reducir interpretación por petición y el coste del autoloader en caliente.

    <?php
    require_once __DIR__ . '/vendor/autoload.php';
    require_once __DIR__ . '/src/BigFatClass.php';
    Lenguaje del código: PHP (php)

    6) Detectar fugas ocultas de CPU: logging síncrono excesivo, regex con backtracking y operaciones I/O sin batching pueden generar picos. Prueba patrones costosos y usa loggers asíncronos o batch writers.

    Ejemplos

    Aquí hay ejemplos listos para adaptar a tus jobs o endpoints problemáticos.

    <?php
    // Antes: procesar todo en memoria (riesgo de pico)
    foreach ($users as $user) {
        process($user);
    }
    Lenguaje del código: PHP (php)
    <?php
    // Después: procesar por lotes para limitar CPU por iteración
    foreach (array_chunk($users, 500) as $batch) {
        foreach ($batch as $user) {
            process($user);
        }
    }
    Lenguaje del código: PHP (php)
    <?php
    // Coalescencia de caché en Redis
    if ($lock = $redis->setnx("lock:feed", 1)) {
        $redis->expire("lock:feed", 30);
        $data = buildFeed();
        $redis->set("cache:feed", $data, 300);
    } else {
        $data = $redis->get("cache:feed");
    }
    Lenguaje del código: PHP (php)

    Checklist

    1. Identificar endpoints/jobs con mayor CPU y trazar hot paths.
    2. Reescribir bucles para procesamiento por lotes o paginación.
    3. Implementar coalescencia de caché (lock + fallback) para evitar stampedes.
    4. Delegar cómputo pesado a extensiones nativas cuando sea posible.
    5. Habilitar OPcache y preload para reducir interpretación y autoload en cada petición.
    6. Sustituir sleep() por event loops o colas con espera eficiente.

    Conclusión

    Los picos de CPU se solucionan con disciplina operativa y correcciones puntuales: controlar bucles, proteger la caché, delegar trabajo pesado y optimizar la carga de código. Implanta las medidas gradualmente y valida con métricas.

    Pequeñas correcciones en el código y la configuración suelen eliminar los picos de CPU más rápido que reescribir todo el sistema.