Como usar transients para cachear consultas pesadas en PHP en WordPress

Contents

Introducción: ¿por qué usar transients en WordPress?

Cuando tu sitio WordPress ejecuta consultas pesadas (consultas complejas a la base de datos, joins de postmeta, cálculos agregados, llamadas externas, o consultas que devuelven grandes conjuntos de datos), cada visita puede provocar una carga considerable y ralentizar la web. Los transients son una API nativa de WordPress diseñada para almacenar en caché datos temporales con un tiempo de expiración. Usarlos correctamente reduce el número de consultas costosas, mejora el tiempo de respuesta y disminuye la carga del servidor.

Conceptos clave

  • Transients: pares clave/valor temporales con tiempo de expiración. Funciones: get_transient, set_transient, delete_transient.
  • Site transients: transients a nivel de red en instalaciones Multisite: get_site_transient, set_site_transient, delete_site_transient.
  • Almacenamiento: por defecto se guardan en la tabla wp_options (opciones autoload=false) y en instalaciones con object cache persistente (Redis/Memcached) se almacenan en ese motor.
  • Invalidación: borrar o regenerar transients cuando los datos subyacentes cambian (hooks: save_post, delete_post, updated_post_meta, etc.).

Buenas prácticas antes de empezar

  • Prefija tus claves de transient con el slug de tu tema o plugin para evitar colisiones (ej. myplugin_heavyquery_).
  • Genera claves determinísticas basadas en los parámetros de la consulta para evitar interrumpir distintas variantes de la misma consulta.
  • Elige un tiempo de expiración razonable: no todo debe durar horas contenido muy dinámico puede tener TTL cortos (60–300s) y estadísticas agregadas pueden tolerar TTL largos (1h–24h).
  • Siempre usa prepared statements o las APIs de WP para evitar inyecciones SQL si usas wpdb.
  • Considera el uso de set_site_transient en Multisite cuando los datos son compartidos por toda la red.

Ejemplo 1: Cachear una consulta WP_Query pesada

Situación: quieres obtener los 50 posts más comentados en la última semana con meta queries y tax queries. Esa consulta es costosa y se puede cachear.

// Nombre de la clave: prefija y añade un hash de los argumentos.
cache_key = mytheme_top_commented_ . md5( serialize( args ) )

// Intenta obtener del transient
top_commented = get_transient( cache_key )

if ( false === top_commented ) {
    // El transient no existe o ha expirado — ejecutar la consulta
    args = array(
        posts_per_page => 50,
        orderby        => comment_count,
        order          => DESC,
        date_query     => array(
            array(
                after => 1 week ago,
            ),
        ),
        meta_query     => array(
            // ejemplo de meta_query complejo...
        ),
        tax_query      => array(
            // ejemplo de tax_query...
        ),
        no_found_rows  => true, // mejora rendimiento si no necesitas paginación
        fields         => ids, // si sólo necesitas IDs
    )

    query = new WP_Query( args )
    top_commented = query->posts

    // Guardar en transient por 1 hora (3600 segundos)
    set_transient( cache_key, top_commented, HOUR_IN_SECONDS )
}

// top_commented contiene el resultado (IDs o posts según fields)

Explicación

  • Se usa md5(serialize(args)) para generar una clave única basada en los parámetros de la consulta.
  • El parámetro no_found_rows evita la sobrecarga si no hay paginación.
  • El uso de fields => ids reduce memoria si solo necesitas identificadores.

Ejemplo 2: Cachear una consulta raw con wpdb

A veces es necesario ejecutar queries con joins complejos. Este ejemplo muestra cómo cachear con transients y usar wpdb->prepare para seguridad.

global wpdb

start = 2025-01-01
end   = 2025-01-31
cache_key = myplugin_sales_total_ . md5( start .  . end )

cached = get_transient( cache_key )
if ( false === cached ) {
    sql = wpdb->prepare(
        
        SELECT SUM( pm.meta_value   0 ) as total_sales
        FROM {wpdb->posts} p
        INNER JOIN {wpdb->postmeta} pm ON p.ID = pm.post_id
        WHERE p.post_type = %s
        AND pm.meta_key = %s
        AND p.post_date BETWEEN %s AND %s
        ,
        shop_order,
        _order_total,
        start,
        end
    )

    result = wpdb->get_var( sql )
    cached = floatval( result )

    // Guardar por 6 horas
    set_transient( cache_key, cached, 6  HOUR_IN_SECONDS )
}

// cached ahora contiene la suma total

Consejos de seguridad y rendimiento

  • Siempre usa wpdb->prepare para valores externos en consultas raw.
  • Evita SELECT selecciona solo campos necesarios.
  • Si la consulta devuelve mucha información, cachea solo lo imprescindible (IDs o agregados) y resuelve detalles por demanda.

Invalidación de transients: cuándo y cómo eliminarlos

Caching es inútil si no invalidas cuando los datos cambian. Las estrategias de invalidación más comunes:

  1. Invalidación reactiva: eliminar el transient en los hooks donde se actualizan datos relevantes (save_post, delete_post, updated_post_meta, etc.).
  2. Expiración temporal: dar TTL razonable para que los datos se refresquen automáticamente.
  3. Re-generación programada: usar wp_cron o un job externo para recalcular transients periódicamente.

Ejemplo: eliminar cache al guardar un post

function mytheme_clear_top_commented_cache( post_id ) {
    // Comprueba si es autosave, revisión, o si no tienes permiso
    if ( wp_is_post_autosave( post_id )  wp_is_post_revision( post_id ) ) {
        return
    }

    // Si usas claves con prefijo fijo, puedes eliminar todos los transients de ese prefijo.
    global wpdb
    prefix = mytheme_top_commented_

    // Atención: esta consulta borra opciones y puede ser costosa en sitios con millones de opciones.
    like = wpdb->esc_like( _transient_ . prefix ) . %
    wpdb->query(
        wpdb->prepare(
            DELETE FROM wpdb->options WHERE option_name LIKE %s OR option_name LIKE %s,
            _transient_ . prefix . %,
            _transient_timeout_ . prefix . %
        )
    )

    // Alternativa más segura: si conservas la clave original en alguna relación, usa delete_transient(key)
}
add_action( save_post, mytheme_clear_top_commented_cache )

Nota: eliminar transients por LIKE sobre wp_options funciona, pero en sitios grandes puede ser peligroso (carga en base de datos). Mejor si conoces las claves exactas, borrarlas individualmente con delete_transient.

Patrón recomendado: claves determinísticas y listas de dependencias

Una técnica muy robusta es mantener un mapa de dependencias o una clave de versión. En lugar de borrar muchos transients individuales, cambias un version key y construyes todas las claves incluyendo esa versión. Al incrementar la versión, todas las claves se invalidan implícitamente.

// Ejemplo simple de cache buster por tipo de contenido
function my_cache_buster_key( context = global ) {
    version = get_option( myplugin_cache_version_ . context, 1 )
    return version
}

// Al guardar contenido relevante incrementas la versión
function my_increment_cache_version( post_id ) {
    // lógica para determinar contexto...
    context = posts
    key = myplugin_cache_version_ . context
    v = (int) get_option( key, 1 )
    update_option( key, v   1 )
}
add_action( save_post, my_increment_cache_version )

// Al crear la clave de transient:
cache_key = myplugin_list_ . my_cache_buster_key( posts ) . _ . md5( serialize( args ) )

Transients vs. Object Cache persistente

  • Si tu instalación tiene un object cache persistente (Redis, Memcached) y el drop-in de WP_OBJECT_CACHE, los transients se almacenan en ese backend y son mucho más rápidos.
  • Si no hay object cache, los transients se guardan en wp_options y la lectura/escritura implica consultas a la base de datos.
  • Para datos de alta frecuencia, considera usar directamente wp_cache_get / wp_cache_set (esta API opera siempre sobre object cache si no hay caché persistente, es volátil y por petición).

Uso de site transients en Multisite

Para datos compartidos por toda la red (por ejemplo, resultados agregados entre sitios), usa set_site_transient/get_site_transient. Funcionan igual, pero el scope es para la red.

// Ejemplo set_site_transient
key = network_stats_ . md5( args )
stats = get_site_transient( key )
if ( false === stats ) {
    // calcular stats
    set_site_transient( key, stats, 12  HOUR_IN_SECONDS )
}

Estrategias avanzadas

  • Regeneración asíncrona: cuando un transient expira, muestra la versión antigua y dispara un job en background para regenerarlo (mejora UX). Puedes usar WP Cron, Action Scheduler o llamadas AJAX privadas.
  • Cache warming: después de borrarlo, puedes disparar una función que regenere la cache para evitar picos tras el primer visitante.
  • Cache fragmentado: en lugar de cachear una gran estructura, cachea sub-piezas (IDs, conteos) y combina al vuelo para reducir invalidaciones.
  • Monitorización: registra métricas de cache hit/miss si tu proyecto lo requiere para detectar oportunidades de optimización.

Ejemplo: regeneración asíncrona con wp_schedule_single_event

function myplugin_maybe_warm_cache( cache_key, callable ) {
    data = get_transient( cache_key )
    if ( false === data ) {
        // Si quieres evitar la latencia para el visitante, puedes devolver un placeholder
        // y programar la regeneración inmediatamente.
        if ( ! wp_next_scheduled( myplugin_warm_cache_event, array( cache_key ) ) ) {
            wp_schedule_single_event( time()   5, myplugin_warm_cache_event, array( cache_key ) )
            set_transient( cache_key . _warming, true, 300 )
        }
        return null // placeholder
    }
    return data
}

add_action( myplugin_warm_cache_event, myplugin_handle_warm_cache )
function myplugin_handle_warm_cache( cache_key ) {
    // Lógica para recalcular
    data = call_user_func( myplugin_recalc_func, cache_key )
    set_transient( cache_key, data, HOUR_IN_SECONDS )
}

Métricas y pruebas

Mide antes y después: tiempos de páginas, número de queries (SAVEQUERIES), uso de CPU/memoria. Si es posible, prueba en staging con datos reales. Asegúrate de que la estrategia no introduce inconsistencias visibles en la UI.

Errores comunes a evitar

  • No invalidar caches cuando los datos cambian — produce datos obsoletos.
  • Usar TTL extremadamente largos para contenidos que cambian con frecuencia.
  • Borrar transients en bloque con queries DELETE en opciones en entornos con muchas opciones sin precaución.
  • No preparar correctamente inputs en consultas raw — riesgo de inyección SQL.
  • Confiar en wp_cache_get si no tienes object cache persistente para almacenamiento entre peticiones.

Resumen

Los transients son una herramienta poderosa para cachear consultas pesadas en WordPress. Usándolos con claves bien construidas, TTL adecuados y estrategias de invalidación (hooks, versiones, o regeneración asíncrona) se puede reducir significativamente la carga de la base de datos y mejorar la experiencia de usuario. Para cargas altas, combina transients con un object cache persistente (Redis/Memcached) y aplica pruebas de rendimiento antes de desplegar cambios en producción.



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 *