Como crear una cola de tareas simple con transients y WP_CRON en WordPress

Contents

Introducción

Este artículo explica en detalle cómo crear una cola de tareas simple en WordPress usando transients para almacenar la cola y WP_CRON para procesarla de forma periódica. La aproximación es fácil de implementar, ligera y útil para tareas que no requieren ejecución inmediata ni muy alta fiabilidad (por ejemplo: envío de correos en lote, sincronizaciones externas no críticas, procesamiento asíncrono de imágenes, etc.). Se incluyen ejemplos de código listos para usar y consideraciones prácticas para producción.

Por qué combinar transients y WP_CRON

  • Transients: permiten almacenar datos temporales en la base de datos (o en objetos cache como Memcached/Redis si están configurados). Son perfectos para una cola pequeña/mediana y evitan crear tablas adicionales.
  • WP_CRON: ofrece un sistema de tareas programadas integrado en WordPress. No necesita cron del sistema aunque, para mayor fiabilidad, conviene complementarlo con un cron real que ejecute wp-cron.php.

Diseño general de la cola

  1. La cola se guarda en un transient con una clave prefijada, p. ej. myplugin_task_queue.
  2. Cada elemento de la cola es un array asociativo que describa la tarea (tipo, datos, intentos, timestamp, metadata, etc.). Se recomienda serializar o codificar en JSON.
  3. Cuando se añade una tarea, se actualiza el transient y se programa (si no hay uno programado) un evento cron que ejecute el procesador.
  4. El procesador de la cola saca N tareas por lote y las procesa. Se usa un lock (otro transient) para evitar ejecuciones concurrentes.
  5. Si una tarea falla, se reintenta según política (reintentos máximos, backoff exponencial) o se registra como fallida.

Implementación paso a paso

1) Convenciones y nombres

  • Transient de la cola: myplugin_task_queue
  • Transient de bloqueo: myplugin_task_queue_lock
  • Hook del cron: myplugin_process_queue
  • Tamaño de lote por ejecución: configurable, p. ej. 10

2) Función para encolar tareas

Esta función añade una tarea al final de la cola (FIFO). Actualiza el transient y programa el cron si no hay uno programado.

 ,      // tipo de tarea
        payload  => null,    // datos de la tarea
        created  => time(),
        attempts => 0,
        max_attempts => 3,
        backoff  => 5,       // segundos iniciales para reintento o usar lógica exponencial
    )
    task = wp_parse_args( task, task_defaults )

    // Añadir tarea al final (FIFO)
    queue[] = task

    // Guardar la cola con un TTL largo (por ejemplo 1 día WP_CRON la procesará antes)
    set_transient( key, queue, DAY_IN_SECONDS )

    // Programar procesamiento inmediato si no hay evento programado
    if ( ! wp_next_scheduled( myplugin_process_queue ) ) {
        wp_schedule_single_event( time()   5, myplugin_process_queue )
    }
}
?>

3) Procesador de la cola (hook WP_CRON)

El procesador toma un lote de tareas, establece un lock para evitar concurrencia y procesa cada tarea. Si una tarea falla, se re-enfila con incremento de intentos y posible retardo.

getMessage() )
            success = false
        }

        if ( success ) {
            // Tarea procesada correctamente -> no volver a añadir
        } else {
            // Falló: incrementar intentos y re-enfilar si no excede max_attempts
            task[attempts] = isset( task[attempts] ) ? task[attempts]   1 : 1
            if ( task[attempts] < task[max_attempts] ) {
                // Calcular backoff simple (ej. exponencial)
                wait = task[backoff]  pow( 2, task[attempts] - 1 )
                // Añadir metadata para indicar tiempo futuro de ejecución
                task[available_after] = time()   wait
                // Re-enfilar al final
                remaining_queue[] = task
            } else {
                // Superó reintentos: registrar fallo persistente o enviar a dead-letter
                error_log( Task failed permanently:  . wp_json_encode( task ) )
            }
        }

        processed  
    }

    // Filtrar tareas que no están aún disponibles (available_after > now) y conservar orden
    now = time()
    new_queue = array()
    foreach ( remaining_queue as t ) {
        if ( ! empty( t[available_after] )  t[available_after] > now ) {
            // Mantener la tarea en la cola tal cual (esperará a la próxima ejecución)
            new_queue[] = t
        } else {
            new_queue[] = t
        }
    }

    // Guardar la cola actualizada
    if ( ! empty( new_queue ) ) {
        set_transient( queue_key, new_queue, DAY_IN_SECONDS )
        // Volver a programar el cron para procesar el siguiente lote si aún quedan tareas
        if ( ! wp_next_scheduled( myplugin_process_queue ) ) {
            wp_schedule_single_event( time()   10, myplugin_process_queue )
        }
    } else {
        delete_transient( queue_key )
    }

    // Liberar lock
    delete_transient( lock_key )
}
?>

4) Funciones de ejemplo para handlers de tareas

Se recomienda implementar funciones separadas que realicen el trabajo real por tipo de tarea.

 isset( payload[body] ) ? payload[body] : array(),
        timeout => 15,
    ) )
    if ( is_wp_error( response ) ) {
        return false
    }
    code = wp_remote_retrieve_response_code( response )
    return ( code >= 200  code < 300 )
}
?>

5) Hooks de activación/desactivación del plugin

Programar el cron al activar y limpiar al desactivar.


Gestión de concurrencia y locks

El uso de un transient como lock previene que múltiples procesos cron (por visitas concurrentes que disparen WP_CRON) procesen la cola al mismo tiempo. Puntos a considerar:

  • El lock debe tener un TTL mayor que el tiempo esperado de ejecución del lote para evitar liberación prematura. Si el trabajo puede tardar mucho, aumentar el TTL o usar un mecanismo más robusto (por ejemplo una fila en base de datos con SELECT … FOR UPDATE si se necesita atomicidad fuerte).
  • Comprobar el timestamp dentro del lock ayuda a detectar locks huérfanos y recuperarlos si han quedado atascados.

Retries y backoff

Implementar reintentos evita perder trabajo por fallos temporales (caída de API, mailserver). Recomendaciones:

  • Guardar en cada tarea los campos attempts, max_attempts y available_after.
  • Usar backoff exponencial: wait = base 2^(attempts-1).
  • Enviar tareas con fallos repetidos al “dead-letter” (registro, opción de administrador, log) para análisis manual.

Batching y límites

No procesar demasiadas tareas por ejecución en WP_CRON: el límite lo fijamos con batch_size. Ventajas:

  • Evitar timeouts y consumo excesivo de memoria.
  • Dejar espacio para que WP_CRON vuelva a ejecutar y continuar la cola.

Consideraciones de producción

  • WP_CRON no es perfecto: sólo se dispara en visitas. Para colas importantes, configurar un cron del sistema que ejecute wp-cron.php cada minuto:

    /1 wget -q -O – https://tusitio.com/wp-cron.php?doing_wp_cron >/dev/null 2>1

  • Transients y tamaño: si las tareas contienen grandes cargas, puede ser mejor utilizar una tabla personalizada o una opción con paginación algunos objetos cache pueden tener límites de tamaño.
  • Persistencia: los transients pueden expirar automáticamente usar TTL largo y programar procesador regularmente. Para colas críticas, una tabla dedicada es más apropiada.
  • Atomicidad: get_transient / set_transient no son atómicos. En entornos con múltiples servidores y caches externas, asegúrate de que el backend de transients (Redis/Memcached) soporte operaciones atómicas o usa locking robusto.
  • Monitorización: registrar métricas (tareas procesadas, fallos) y exponer una página en el admin para ver la cola y vaciarla o reintentar tareas.

Ejemplo completo compacto

Resumen de funciones esenciales para integrar rápidamente:

,payload=>null,created=>time(),
        attempts=>0,max_attempts=>3,backoff=>5
    ) )
    queue[] = task
    set_transient( key, queue, DAY_IN_SECONDS )
    if ( ! wp_next_scheduled( myplugin_process_queue ) ) {
        wp_schedule_single_event( time()   5, myplugin_process_queue )
    }
}
add_action( myplugin_process_queue, myplugin_process_queue_callback )

function myplugin_process_queue_callback() {
    queue_key = myplugin_task_queue
    lock_key  = myplugin_task_queue_lock
    batch_size = 10
    if ( get_transient( lock_key ) ) return
    set_transient( lock_key, time(), 60 )
    queue = get_transient( queue_key )
    if ( ! is_array( queue )  empty( queue ) ) {
        delete_transient( lock_key )
        return
    }
    processed = 0
    remaining = queue
    new_queue = array()
    while ( processed < batch_size  ! empty( remaining ) ) {
        task = array_shift( remaining )
        now = time()
        if ( ! empty( task[available_after] )  task[available_after] > now ) {
            // No disponible aún -> reinsertar al final
            new_queue[] = task
            processed  
            continue
        }
        ok = false
        try {
            // Dispatch básico
            if ( task[type] === send_email ) {
                ok = myplugin_handle_send_email( task[payload] )
            } else {
                ok = true
            }
        } catch ( Exception e ) {
            ok = false
        }
        if ( ! ok ) {
            task[attempts]  
            if ( task[attempts] < task[max_attempts] ) {
                task[available_after] = time()   task[backoff]  pow(2, task[attempts] - 1)
                new_queue[] = task
            } else {
                error_log( Task failed permanently:  . wp_json_encode( task ) )
            }
        }
        processed  
    }
    // añadir las tareas restantes no procesadas
    foreach ( remaining as r ) new_queue[] = r
    if ( ! empty( new_queue ) ) {
        set_transient( queue_key, new_queue, DAY_IN_SECONDS )
        if ( ! wp_next_scheduled( myplugin_process_queue ) ) {
            wp_schedule_single_event( time()   10, myplugin_process_queue )
        }
    } else {
        delete_transient( queue_key )
    }
    delete_transient( lock_key )
}
?>

Buenas prácticas finales

  • Probar la cola con tareas reales en staging antes de producción.
  • Exponer métricas y logs para poder localizar cuellos de botella o tareas con fallos frecuentes.
  • Si la carga crece, migrar a una solución más robusta (tabla propia, RabbitMQ, Redis lists, etc.).
  • Documentar el formato de las tareas y los handlers disponibles.

Resumen

Crear una cola simple con transients y WP_CRON es una solución rápida y práctica para muchas necesidades asíncronas en WordPress. La implementación básica consiste en encolar tareas en un transient, programar un evento WP_CRON y procesar la cola en lotes usando un lock para evitar concurrencia. Añadiendo reintentos, backoff y monitorización se obtiene una solución suficientemente robusta para la mayoría de casos no críticos.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *