¿Cómo funciona el almacenamiento en caché de objetos?

2 dic 2012, 23:23:26
Vistas: 21.4K
Votos: 24

Busco una respuesta definitiva aquí. Cuando el almacenamiento en caché de objetos está habilitado, ¿dónde terminan almacenándose las opciones y los transientes?

Por defecto, ambos se almacenan en la base de datos. Pero he escuchado algunas referencias de que memcache los almacenará en otro lugar y APC hará algo completamente diferente. ¿Dónde se persistirán estos datos exactamente en ambos casos?

1
Comentarios

El artículo que menciona @toscho ahora está disponible en archive.org: Explorando la API de Cache de WordPress

here here
11 nov 2015 02:29:03
Todas las respuestas a la pregunta 4
6
37

WordPress, por defecto, realiza una forma de "Caché de Objetos" pero su duración es solo para una carga de página.

Las opciones son realmente un buen ejemplo de esto. Consulta esta respuesta para más información. El resumen:

  1. Comienza una página
  2. Todas las opciones se cargan con una simple sentencia SELECT option_name, option_value from $wpdb->options
  3. Solicitudes posteriores para esas opciones (por ejemplo, una llamada a get_option) no acceden a la base de datos porque se almacenan con la API de caché de WP.

Las opciones siempre "viven" en la base de datos y siempre se persisten allí — esa es su fuente "canónica". Dicho esto, las opciones se cargan en la caché de objetos, por lo que cuando solicitas una opción hay un 99% de probabilidad de que esa solicitud nunca llegue a la base de datos.

Los transitorios son un poco diferentes.

WordPress te permite reemplazar la API de caché con un drop-in — un archivo que se coloca directamente en tu carpeta wp-content. Si creas tu propio drop-in de caché o usas un plugin existente, puedes hacer que la caché de objetos persista más allá de una sola carga de página. Cuando haces eso, los transitorios cambian un poco.

Echemos un vistazo a la función set_transient en wp-includes/option.php.

<?php
/**
 * Establece/actualiza el valor de un transitorio.
 *
 * No necesitas serializar valores. Si el valor necesita ser serializado, entonces
 * se serializará antes de ser establecido.
 *
 * @since 2.8.0
 * @package WordPress
 * @subpackage Transient
 *
 * @uses apply_filters() Llama al hook 'pre_set_transient_$transient' para permitir sobrescribir el
 *  valor transitorio a almacenar.
 * @uses do_action() Llama a los hooks 'set_transient_$transient' y 'setted_transient' en caso de éxito.
 *
 * @param string $transient Nombre del transitorio. Se espera que no esté escapado para SQL.
 * @param mixed $value Valor del transitorio. Se espera que no esté escapado para SQL.
 * @param int $expiration Tiempo hasta la expiración en segundos, por defecto 0
 * @return bool False si el valor no se estableció y true si se estableció.
 */
function set_transient( $transient, $value, $expiration = 0 ) {
    global $_wp_using_ext_object_cache;

    $value = apply_filters( 'pre_set_transient_' . $transient, $value );

    if ( $_wp_using_ext_object_cache ) {
        $result = wp_cache_set( $transient, $value, 'transient', $expiration );
    } else {
        $transient_timeout = '_transient_timeout_' . $transient;
        $transient = '_transient_' . $transient;
        if ( false === get_option( $transient ) ) {
            $autoload = 'yes';
            if ( $expiration ) {
                $autoload = 'no';
                add_option( $transient_timeout, time() + $expiration, '', 'no' );
            }
            $result = add_option( $transient, $value, '', $autoload );
        } else {
            if ( $expiration )
                update_option( $transient_timeout, time() + $expiration );
            $result = update_option( $transient, $value );
        }
    }
    if ( $result ) {
        do_action( 'set_transient_' . $transient );
        do_action( 'setted_transient', $transient );
    }
    return $result;
}

Hmmm ¿$_wp_using_ext_object_cache? Si es verdadero, WordPress usa la caché de objetos en lugar de la base de datos para almacenar transitorios. Entonces, ¿cómo se establece como verdadero? Es hora de explorar cómo WP configura su propia API de caché.

Puedes rastrear casi todo hasta wp-load.php o wp-settings.php — ambos son cruciales para el proceso de arranque de WordPress. En nuestro caso, hay algunas líneas relevantes en wp-settings.php.

// Inicia la caché de objetos de WordPress, o una caché de objetos externa si el drop-in está presente.
wp_start_object_cache();

¿Recuerdas lo del drop-in mencionado antes? Veamos wp_start_object_cache en wp-includes/load.php.

<?php
/**
 * Inicia la caché de objetos de WordPress.
 *
 * Si existe un archivo object-cache.php en el directorio wp-content,
 * lo usa como una caché de objetos externa.
 *
 * @access private
 * @since 3.0.0
 */
function wp_start_object_cache() {
    global $_wp_using_ext_object_cache, $blog_id;

    $first_init = false;
    if ( ! function_exists( 'wp_cache_init' ) ) {
        if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {
            require_once ( WP_CONTENT_DIR . '/object-cache.php' );
            $_wp_using_ext_object_cache = true;
        } else {
            require_once ( ABSPATH . WPINC . '/cache.php' );
            $_wp_using_ext_object_cache = false;
        }
        $first_init = true;
    } else if ( !$_wp_using_ext_object_cache && file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {
        // A veces advanced-cache.php puede cargar object-cache.php antes de que se cargue aquí.
        // Esto rompe la verificación function_exists de arriba y puede resultar en que $_wp_using_ext_object_cache
        // se establezca incorrectamente. Verifica nuevamente si existe una caché externa.
        $_wp_using_ext_object_cache = true;
    }

    // Si la caché soporta resetear, resetea en lugar de iniciar si ya está inicializada.
    // Resetear indica a la caché que los IDs globales han cambiado y puede necesitar actualizar claves
    // y limpiar cachés.
    if ( ! $first_init && function_exists( 'wp_cache_switch_to_blog' ) )
        wp_cache_switch_to_blog( $blog_id );
    else
        wp_cache_init();

    if ( function_exists( 'wp_cache_add_global_groups' ) ) {
        wp_cache_add_global_groups( array( 'users', 'userlogins', 'usermeta', 'user_meta', 'site-transient', 'site-options', 'site-lookup', 'blog-lookup', 'blog-details', 'rss', 'global-posts', 'blog-id-cache' ) );
        wp_cache_add_non_persistent_groups( array( 'comment', 'counts', 'plugins' ) );
    }
}

Las líneas relevantes de la función (las que se relacionan con $_wp_using_ext_object_cache que altera cómo se almacenan los transitorios).

if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {
    require_once ( WP_CONTENT_DIR . '/object-cache.php' );
    $_wp_using_ext_object_cache = true;
} else {
    require_once ( ABSPATH . WPINC . '/cache.php' );
    $_wp_using_ext_object_cache = false;
}

Si object-cache.php existe en tu directorio de contenido, se incluye y WP asume que estás usando una caché externa persistente — establece $_wp_using_ext_object_cache como verdadero.

Si estás usando una caché de objetos externa, los transitorios la usarán. Lo que lleva a la pregunta de cuándo usar opciones versus transitorios.

Simple. Si necesitas que los datos persistan indefinidamente, usa opciones. Se "cachéan", pero su fuente canónica es la base de datos y nunca desaparecerán a menos que un usuario lo solicite explícitamente.

Para datos que deben almacenarse por un período de tiempo determinado, pero que no necesitan persistir más allá de un tiempo de vida especificado, usa transitorios. Internamente, WP intentará usar una caché de objetos externa persistente si puede; de lo contrario, los datos irán a la tabla de opciones y se recolectarán como basura a través del pseudo-cron de WordPress cuando expiren.

Otras preocupaciones/preguntas:

  1. ¿Está bien hacer muchas llamadas a get_option? Probablemente. Incurren en la sobrecarga de llamar a una función, pero es probable que no accedan a la base de datos. La carga de la base de datos es a menudo una preocupación mayor en la escalabilidad de aplicaciones web que el trabajo que hace tu lenguaje de elección generando una página.
  2. ¿Cómo sé cuándo usar transitorios vs. la API de Caché? Si esperas que los datos persistan por un período establecido, usa la API de transitorios. Si no importa si los datos persisten (ej. no lleva mucho tiempo calcular/obtener los datos, pero no debería suceder más de una vez por carga de página), usa la API de caché.
  3. ¿Se cacachéan realmente todas las opciones en cada carga de página? No necesariamente. Si llamas a add_option con su último argumento opcional como no, no se cargan automáticamente. Dicho esto, una vez que las obtienes una vez, van a la caché y las llamadas posteriores no accederán a la base de datos.
3 dic 2012 07:19:47
Comentarios

nitpick 1: No todas las opciones se cargan al inicio de la página, solo aquellas marcadas como "autoload=yes" al crearse. El valor por defecto para ese parámetro en add_option es 'yes' y la mayoría de los desarrolladores de plugins no se molestan en entender la diferencia de usar un 'no' allí, lo que hace que tu declaración sea prácticamente cierta.

Mark Kaplun Mark Kaplun
3 dic 2012 07:32:29

Incluso las opciones no autocargadas se almacenan en caché después de ser obtenidas una vez. Puede que no se carguen inicialmente, pero sí van a la caché de objetos después de eso. ¡Incluso las opciones que no existen se almacenan en caché! https://github.com/WordPress/WordPress/blob/master/wp-includes/option.php#L58-L71 Sin embargo, agregué una nota sobre la opción de autoload.

chrisguitarguy chrisguitarguy
3 dic 2012 07:37:21

ese era el nitpick 2 ;)

Mark Kaplun Mark Kaplun
3 dic 2012 07:42:31

Gracias por el excelente artículo y por el tiempo dedicado a resumir todo esto.

prosti prosti
15 dic 2016 15:54:31

¡Qué respuesta tan brillante y útil, gracias!

And Finally And Finally
11 jun 2020 11:42:04

@chrisguitarguy: ¿Qué sucede cuando se cambia $_wp_using_ext_object_cache durante el tiempo de ejecución, por ejemplo usando wp_using_ext_object_cache(false)? ¿Esto deshabilita temporalmente la caché para las llamadas posteriores a la base de datos/caché durante esta carga de página?

tim tim
5 ene 2022 19:15:14
Mostrar los 1 comentarios restantes
7

Existen 4 tipos de caché que conozco

  1. Trivial - Siempre está activo y entra en efecto antes que cualquier otro tipo de caché. Almacena los elementos en caché en un array de PHP, lo que significa que consume memoria de tu sesión de ejecución PHP, y que el caché se vacía después de que termina la ejecución de PHP. Es decir, incluso sin usar ningún otro caché, si llamas a get_option('opt') dos veces seguidas, solo harás una consulta a la base de datos la primera vez y la segunda vez el valor se devolverá desde la memoria.

  2. Archivo - Los valores en caché se almacenan en archivos en algún lugar bajo tu directorio raíz. Creo que ha demostrado no ser efectivo en términos de rendimiento a menos que tengas un disco muy rápido o almacenamiento de archivos mapeados en memoria.

  3. APC (u otro caché basado en aceleradores PHP) - Los valores en caché se almacenan en la memoria de tu máquina host y fuera de tu asignación de memoria PHP. El mayor riesgo potencial es que no hay alcance de los datos y si ejecutas dos sitios, potencialmente cada uno puede acceder a los datos en caché del otro, o sobrescribirlos.

  4. Memcache - Es un caché basado en red. Puedes ejecutar el servicio de caché en cualquier parte de la red y probablemente almacene valores en la memoria de su host. Probablemente no necesites memcache a menos que tengas un balanceador de carga en acción.

Por cierto, el caché de objetos almacena mucho más que opciones, guardará casi cualquier cosa que se haya recuperado de la base de datos usando la API de alto nivel de WordPress.

3 dic 2012 07:13:52
Comentarios

Sé que la respuesta es bastante antigua, pero añadiría también el excelente Redis.

Cranio Cranio
12 abr 2016 13:32:27

@Cranio, tienes razón pero... redis es básicamente una variante de memcache con almacenamiento, y por lo tanto es una base de datos (NoSQL). Esto en mi opinión es realmente malo ya que si el nodo falla o no puede actualizarse, podrías obtener información obsoleta. Tiene una opción para desactivar el comportamiento de base de datos, pero no estoy seguro de si está activada o desactivada por defecto.

Mark Kaplun Mark Kaplun
12 abr 2016 13:42:19

Es un reemplazo perfecto para Memcached (incluso mejor), ¿qué más necesitas? Con mucho, el uso más común que he visto es simplemente como almacenamiento clave-valor en RAM (sí, aparte de eso, los datos pueden hacerse persistentes, el clustering está en camino, y tiene capacidades de gestión de colas, pero todo el mundo añade Redis como una excelente opción de caché para WP)

Cranio Cranio
12 abr 2016 15:45:58

todos también pueden saltar del puente ;) pero la complejidad adicional no es necesaria para el caching

Mark Kaplun Mark Kaplun
12 abr 2016 16:04:28

Eso no tiene ningún sentido; lo que quieres es caching en RAM, Redis hace caching en RAM, punto; y lo hace maravillosamente. No hay absolutamente ninguna complejidad adicional si no quieres adentrarte en ello. Así que, señor, realmente no logro entender su punto.

Cranio Cranio
13 abr 2016 13:41:08

LOL si necesitas 5 líneas de código para codificar X, y yo te ofrezco las 5 líneas más 10 adicionales, por definición tiene mayor complejidad. No hay razón para cambiar de tecnologías solo por la moda. Si puedes demostrar que Redis es mejor de alguna manera relacionada con el object caching, entonces quizás tengas un punto, pero no lo es.

Mark Kaplun Mark Kaplun
13 abr 2016 13:53:11

Eso, de nuevo, carece totalmente de sentido en el esfuerzo de NO incluir Redis en la lista. Por, presumiré, razones estrictamente personales que contradicen completamente el propósito de StackOverflow. No es una guerra infantil de religión sobre qué es mejor, se trata de mostrar opciones y tecnologías disponibles. Para el resto: http://stackoverflow.com/questions/10558465/memcached-vs-redis

Cranio Cranio
13 abr 2016 13:56:13
Mostrar los 2 comentarios restantes
0

Excelente pregunta.

Creo que falta la parte sobre cómo WordPress utiliza la clase WP_Object_Cache, así que la agregaré.

De la documentación:

DEF: El Object Cache de WordPress se utiliza para ahorrar viajes a la base de datos. El Object Cache almacena todos los datos de la caché en memoria y hace que los contenidos de la caché estén disponibles mediante una clave, que se utiliza para nombrar y luego recuperar los contenidos de la caché.

Aquí está la estructura de WP_Object_Cache.

Descripción de la imagen

Nota: + es público, - privado, # protegido.

Utilizas el método stats() para mostrar estadísticas generales sobre el objeto de caché global y lo que contiene. Aquí está la salida:

Aciertos de caché: 110
Fallos de caché: 98

Grupo: options - ( 81.03k )
Grupo: default - ( 0.03k )
Grupo: users - ( 0.41k )
Grupo: userlogins - ( 0.03k )
Grupo: useremail - ( 0.04k )
Grupo: userslugs - ( 0.03k )
Grupo: user_meta - ( 3.92k )
Grupo: posts - ( 1.99k )
Grupo: terms - ( 1.76k )
Grupo: post_tag_relationships - ( 0.04k )
Grupo: category_relationships - ( 0.03k )
Grupo: post_format_relationships - ( 0.02k )
Grupo: post_meta - ( 0.36k )

Esto es lo que obtuve al principio de una plantilla como single.php.

Nota que la variable que nos interesa es: global $wp_object_cache.

El miembro privado $cache contiene los datos reales de la caché.

En programación, las estructuras de caché están en todas partes. En su forma más simple pueden reconocerse como pares clave-valor. Cubos, estructuras NoDB, índices de bases de datos. El objetivo final del Object Cache de WordPress no era tener la estructura más simple posible, pero aún así se pueden reconocer pares clave-valor.

Como estaba en single.php cuando imprimí la caché:

print_r($wp_object_cache->cache['posts']);

Obtuve una única publicación en caché.

    [last_changed] => 0.34169600 1481802075
    [get_page_by_path:2516f01e446b6c125493ec7824b63868:0.34169600 1481802075] => 0
    [2831] => WP_Post Object
        (
            [ID] => 2831
            [post_author] => 1 
            ... aquí va el objeto de la publicación en caché
        )

El objeto sería el valor, y la clave de caché sería

get_page_by_path:2516f01e446b6c125493ec7824b63868:0.34169600 1481802075

Aquí puedes revisar la estructura de $cache_key:

Archivo: /wp-includes/post.php
4210: /**
4211:  * Recupera una página dada su ruta.
4212:  *
4213:  * @since 2.1.0
4214:  *
4215:  * @global wpdb $wpdb Objeto de abstracción de la base de datos de WordPress.
4216:  *
4217:  * @param string       $page_path Ruta de la página.
4218:  * @param string       $output    Opcional. El tipo de retorno requerido. Uno de OBJECT, ARRAY_A o ARRAY_N, que corresponden a
4219:  *                                un objeto WP_Post, un array asociativo o un array numérico, respectivamente. Por defecto OBJECT.
4220:  * @param string|array $post_type Opcional. Tipo de publicación o array de tipos de publicación. Por defecto 'page'.
4221:  * @return WP_Post|array|null WP_Post (o array) en éxito, o null en fallo.
4222:  */
4223: function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
4224:   global $wpdb;
4225: 
4226:   $last_changed = wp_cache_get_last_changed( 'posts' );
4227: 
4228:   $hash = md5( $page_path . serialize( $post_type ) );
4229:   $cache_key = "get_page_by_path:$hash:$last_changed";
4230:   $cached = wp_cache_get( $cache_key, 'posts' );
4231:   if ( false !== $cached ) {
4232:       // Caso especial: '0' es una `$page_path` inválida.
4233:       if ( '0' === $cached || 0 === $cached ) {
4234:           return;
4235:       } else {
4236:           return get_post( $cached, $output );
4237:       }
4238:   }
15 dic 2016 15:53:34
0

Las opciones siempre se almacenan en la base de datos, mientras que los transitorios pueden almacenarse solo en la memoria compartida si APC y un plugin que implemente el almacenamiento en caché de APC en WP está instalado. Memcache también utiliza memoria.

Las opciones también se almacenan en memoria y se cargan desde allí cuando es posible (si no, se realiza una consulta a la base de datos).

3 dic 2012 06:54:53