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
- La cola se guarda en un transient con una clave prefijada, p. ej. myplugin_task_queue.
- 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.
- 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.
- El procesador de la cola saca N tareas por lote y las procesa. Se usa un lock (otro transient) para evitar ejecuciones concurrentes.
- 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 🙂 |