Como implementar object cache con wp_cache en PHP en WordPress

Contents

Introducción: ¿qué es el object cache en WordPress y por qué usar wp_cache_?

El object cache es una capa de almacenamiento en memoria (o persistente) destinada a guardar objetos PHP ya procesados —consultas, resultados caros, estructuras serializables— para evitar recomputarlos o reconsultar la base de datos. WordPress expone una API simple basada en funciones wp_cache_ (wp_cache_get, wp_cache_set, wp_cache_add, wp_cache_delete, wp_cache_replace, wp_cache_incr, wp_cache_decr, wp_cache_flush, etc.) que permite leer y escribir en esa caché de objetos.

Por defecto WordPress mantiene esta caché solamente durante la ejecución de la petición (caché no persistente). Para convertirla en una caché persistente entre peticiones —lo deseable en sitios de producción— se instala un drop-in llamado object-cache.php en el directorio wp-content, o se usan plugins que proveen dicho drop-in conectando a Redis, Memcached, APCu u otros backends.

Conceptos clave

  • Persistente vs no persistente: persistente = datos sobreviven entre peticiones (Memcached/Redis/APCu). No persistente = solo memoria de la petición actual.
  • Grupos: cada clave puede pertenecer a un grupo (ej. posts, users, my_plugin) para organización y borrado selectivo.
  • Global groups: grupos marcados como globales persisten a través de objetos cacheados por sitios en instalaciones multisite.
  • Non-persistent groups: grupos que NO deben persistir aunque exista un backend persistente (útil para datos sensibles o que deben reconstruirse siempre).
  • Saneamiento de claves: evitar claves largas con datos no controlados, establecer prefijos/versionado (salt) para bustear caché en despliegues.
  • Invalidación: pensar en cómo y cuándo invalidar (save_post, delete_term, hooks de actualización, deploy).

Funciones principales de la API y su uso

wp_cache_get(key, group = , force = false, found = null) Recupera un valor. found puede indicar si realmente existía.
wp_cache_set(key, data, group = , expire = 0) Almacena un valor (sobreescribe).
wp_cache_add(key, data, group = , expire = 0) Agrega solo si no existe (no sobrescribe).
wp_cache_replace(key, data, group = , expire = 0) Reemplaza solo si ya existe.
wp_cache_delete(key, group = ) Elimina una clave concreta.
wp_cache_incr(key, offset = 1, group = ) / wp_cache_decr(…) Incrementa/Decrementa valores numericos atómicamente si el backend lo soporta.
wp_cache_flush() Vacía la caché completa (use con cuidado).

Patrón típico de uso (ejemplo)

// Ejemplo en un plugin o tema:
cache_key = user_profile_ . user_id            // construir clave con cuidado
group     = my_plugin_profiles

profile = wp_cache_get( cache_key, group )
if ( false === profile ) {
    // operación cara: consulta DB externa o API
    profile = my_expensive_user_profile_fetch( user_id )

    // guardar en caché por 1 hora
    wp_cache_set( cache_key, profile, group, HOUR_IN_SECONDS )
}

// usar profile

Cómo habilitar un object cache persistente

1) Elegir backend: Redis, Memcached, APCu, etc. Instalar la extensión PHP correspondiente y un servidor (ej. redis-server o memcached).
2) Instalar un plugin maduro (por ejemplo Redis Object Cache) o desplegar un drop-in manual: crear wp-content/object-cache.php que implemente las funciones wp_cache_ y use el cliente Redis/Memcached. El archivo object-cache.php es el mecanismo oficial para reemplazar la implementación del cache de objetos.

Consideraciones antes de usar una caché persistente

  • Asegurarse de que los objetos almacenados son serializables (no recursos, handlers abiertos, closures no serializables).
  • Evitar almacenar objetos extremadamente grandes considerar normalizar o fragmentar en claves.
  • Planificar invalidación: borrar claves relevantes en hooks como save_post, wp_update_comment, term_ hooks, etc.
  • Usar un cache key salt (p. ej. constante WP_CACHE_KEY_SALT) para invalidar toda la caché automáticamente en despliegues cambiando la sal.
  • Monitorizar hit/miss y tamaño. Memcached/Redis proveen estadísticas también hay plugins para integrarlas en WP.

Ejemplo práctico: drop-in mínimo con Memcached

A continuación un ejemplo funcional y minimalista de wp-content/object-cache.php usando la extensión PHP Memcached. Este ejemplo no es una solución de producción completa (falta manejo avanzado de errores, reconexión, locking para stampede, métricas, etc.), pero sirve como base educativa.

memc = new Memcached()
                // Servidor por defecto: 127.0.0.1:11211. Cambiar según entorno.
                this->memc->addServer( 127.0.0.1, 11211 )
            } else {
                // Fallback: implementar lógica de no-persistencia si no hay extensión
                this->memc = null
            }
            // Prefijo para bust cache en despliegues: definir WP_CACHE_KEY_SALT en wp-config.php si se desea.
            this->cache_prefix = defined( WP_CACHE_KEY_SALT ) ? WP_CACHE_KEY_SALT . : : 
        }

        private function make_key( key, group ) {
            group = ( group ===  ) ? default : group
            // usar md5 del grupo para acortar y evitar caracteres raros
            return this->cache_prefix . md5( group ) . : . key
        }

        public function add( key, data, group = , expire = 0 ) {
            if ( this->is_non_persistent( group )  ! this->memc ) {
                return false
            }
            return this->memc->add( this->make_key( key, group ), data, (int) expire )
        }

        public function set( key, data, group = , expire = 0 ) {
            if ( this->is_non_persistent( group )  ! this->memc ) {
                return false
            }
            return this->memc->set( this->make_key( key, group ), data, (int) expire )
        }

        public function get( key, group = , force = false, found = null ) {
            if ( ! this->memc ) {
                found = false
                return false
            }
            val = this->memc->get( this->make_key( key, group ) )
            found = this->memc->getResultCode() !== Memcached::RES_NOTFOUND
            return val
        }

        public function delete( key, group =  ) {
            if ( ! this->memc ) {
                return false
            }
            return this->memc->delete( this->make_key( key, group ) )
        }

        public function replace( key, data, group = , expire = 0 ) {
            if ( ! this->memc ) {
                return false
            }
            return this->memc->replace( this->make_key( key, group ), data, (int) expire )
        }

        public function incr( key, offset = 1, group =  ) {
            if ( ! this->memc ) {
                return false
            }
            return this->memc->increment( this->make_key( key, group ), offset )
        }

        public function decr( key, offset = 1, group =  ) {
            if ( ! this->memc ) {
                return false
            }
            return this->memc->decrement( this->make_key( key, group ), offset )
        }

        public function flush() {
            if ( ! this->memc ) {
                return false
            }
            return this->memc->flush()
        }

        public function close() {
            if ( this->memc ) {
                this->memc->quit()
            }
        }

        public function add_global_groups( groups ) {
            if ( is_array( groups ) ) {
                this->global_groups = array_merge( this->global_groups, groups )
            } else {
                this->global_groups[] = groups
            }
        }

        public function add_non_persistent_groups( groups ) {
            if ( is_array( groups ) ) {
                this->non_persistent_groups = array_merge( this->non_persistent_groups, groups )
            } else {
                this->non_persistent_groups[] = groups
            }
        }

        private function is_non_persistent( group ) {
            return in_array( group, this->non_persistent_groups, true )
        }
    }
}

// Instanciar el objeto cache global
global wp_object_cache
wp_object_cache = new WP_Object_Cache_Memcached()

// Las funciones que WordPress espera llamar:
function wp_cache_add( key, data, group = , expire = 0 ) {
    global wp_object_cache
    return wp_object_cache->add( key, data, group, expire )
}

function wp_cache_set( key, data, group = , expire = 0 ) {
    global wp_object_cache
    return wp_object_cache->set( key, data, group, expire )
}

function wp_cache_get( key, group = , force = false, found = null ) {
    global wp_object_cache
    return wp_object_cache->get( key, group, force, found )
}

function wp_cache_delete( key, group =  ) {
    global wp_object_cache
    return wp_object_cache->delete( key, group )
}

function wp_cache_replace( key, data, group = , expire = 0 ) {
    global wp_object_cache
    return wp_object_cache->replace( key, data, group, expire )
}

function wp_cache_incr( key, offset = 1, group =  ) {
    global wp_object_cache
    return wp_object_cache->incr( key, offset, group )
}

function wp_cache_decr( key, offset = 1, group =  ) {
    global wp_object_cache
    return wp_object_cache->decr( key, offset, group )
}

function wp_cache_flush() {
    global wp_object_cache
    return wp_object_cache->flush()
}

function wp_cache_add_global_groups( groups ) {
    global wp_object_cache
    if ( method_exists( wp_object_cache, add_global_groups ) ) {
        wp_object_cache->add_global_groups( groups )
    }
}

function wp_cache_add_non_persistent_groups( groups ) {
    global wp_object_cache
    if ( method_exists( wp_object_cache, add_non_persistent_groups ) ) {
        wp_object_cache->add_non_persistent_groups( groups )
    }
}

Notas sobre el ejemplo

  • Defina WP_CACHE_KEY_SALT en wp-config.php si quiere un mecanismo de versión (p. ej. WP_CACHE_KEY_SALT = mi_version_2025_09_24).
  • Si no existe la extensión memcached el drop-in mantiene la API pero no es persistente: en ese caso wp_cache_get devolverá false y no se producirá fallo fatal, aunque conviene avisar en logs.
  • En producción prefiera bibliotecas consolidadas o plugins oficiales que implementan reconexión, locking para cache stampede, manejo de clusters y compatibilidad multisite.

Estrategias prácticas y mejores prácticas

  1. Versionado de claves (key salt): cambiar la sal en un deploy invalida toda la caché sin tener que vaciar manualmente el backend.
  2. Granularidad: cachear objetos y no páginas enteras con object cache. Para full-page caching usar soluciones de página (reverse proxy, Varnish, plugins de cache de página).
  3. Invalidación en hooks: borrar claves relacionadas cuando se actualiza contenido (save_post, edit_user_profile, edit_term, etc.).
  4. No almacenar recursos: solo datos serializables convertir objetos complejos a arrays si es necesario.
  5. Evitar colisiones: prefijar claves del plugin/tema usar un grupo propio y salt sensible a entorno (staging/production).
  6. Cache stampede: para operaciones costosas, combine wp_cache_add (add solo si no existe) con locking o strategies como recalculate in background o keep stale while recomputing.
  7. Monitorización: comprobar hit ratio y tamaño del cache ajustar TTL (expire) según patrón de acceso.

Comparación breve: transients vs object cache

  • Transients usan la API de opciones por defecto y son útiles para datos con TTL. Si existe un object cache persistente y un plugin que lo integra, transients pueden guardarse en el backend persistente (depende del drop-in/plugin).
  • La API wp_cache_ es más ligera y orientada a almacenamiento temporal dentro de ejecución o persistente si se instala object-cache.php.

Errores comunes y cómo resolverlos

  • No se escribe en caché: comprobar que la extensión PHP está instalada, que object-cache.php existe en wp-content y que el servidor (Redis/Memcached) está accesible.
  • Objetos corruptos al unserialize: revisar que el mismo código/clases esté disponible al recuperar datos evitar almacenar instancias de clases con closures.
  • Clave collision o claves demasiado largas: usar prefijos y hash de grupo/clave.
  • Caché desincronizada tras deploy: cambiar WP_CACHE_KEY_SALT o realizar un flush programado tras deploy.

Recursos y recomendaciones finales

  • En entornos empresariales, usar Redis o Memcached administrados y plugins oficiales que implementen características avanzadas (reconexión, fallbacks, stats).
  • Probar localmente la implementación con un conjunto de métricas (hit/miss, latencia de get/set, memoria usada).
  • Documentar las claves y grupos que use cada plugin/tema para facilitar la depuración.

Fragmento de ejemplo: invalidación al guardar post

// Borrar caché relacionada al guardar un post
add_action( save_post, function( post_id, post ) {
    // Si es una revisión, ignorar
    if ( wp_is_post_revision( post_id ) ) {
        return
    }
    // Borrar cachés por clave y/o grupo
    wp_cache_delete( post_ . post_id, posts )
    wp_cache_delete( recent_posts, my_plugin )
}, 10, 2 )

Este tutorial cubre desde los conceptos hasta un ejemplo práctico de drop-in con Memcached, patrones de uso y buenas prácticas. Implementar un object cache correctamente mejora drásticamente el rendimiento en sitios con carga alta y operaciones costosas, siempre que se diseñe la invalidación y el versionado de claves con cuidado.



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 *