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 🙂 |
