1 Caché Básica
Esta es una introducción a tres tipos de técnicas de caché: de página, de acción y
de fragmento. Por defecto, Rails proporciona caché de fragmentos. Para usar
caché de página y de acción, deberá agregar actionpack-page_caching
y
actionpack-action_caching
a su Gemfile
.
Por defecto, la caché solo está habilitada en su entorno de producción. Puede probar
la caché localmente ejecutando rails dev:cache
, o configurando
config.action_controller.perform_caching
en config/environments/development.rb
a true
.
NOTA: Cambiar el valor de config.action_controller.perform_caching
solo
tendrá efecto en la caché proporcionada por Action Controller.
Por ejemplo, no afectará la caché de bajo nivel, que abordamos
a continuación.
1.1 Caché de Página
La caché de página es un mecanismo de Rails que permite que la solicitud de una página generada se cumpla por el servidor web (es decir, Apache o NGINX) sin tener que pasar por toda la pila de Rails. Si bien esto es muy rápido, no se puede aplicar a todas las situaciones (como páginas que requieren autenticación). Además, debido a que el servidor web sirve un archivo directamente desde el sistema de archivos, deberá implementar la expiración de la caché.
La caché de página se ha eliminado de Rails 4. Consulte la gema actionpack-page_caching.
1.2 Caché de Acción
La caché de página no se puede utilizar para acciones que tienen filtros previos, por ejemplo, páginas que requieren autenticación. Aquí es donde entra en juego la caché de acción. La caché de acción funciona como la caché de página, excepto que la solicitud web entrante llega a la pila de Rails para que los filtros previos puedan ejecutarse antes de que se sirva la caché. Esto permite que se ejecute la autenticación y otras restricciones mientras se sirve el resultado de la salida de una copia en caché.
La caché de acción se ha eliminado de Rails 4. Consulte la gema actionpack-action_caching. Consulte la descripción general de la expiración de caché basada en claves de DHH para el método preferido actualmente.
1.3 Caché de Fragmento
Las aplicaciones web dinámicas suelen construir páginas con una variedad de componentes que no todos tienen las mismas características de caché. Cuando diferentes partes de la página necesitan ser almacenadas en caché y expiradas por separado, se puede usar la Caché de Fragmento.
La Caché de Fragmento permite que un fragmento de lógica de vista se envuelva en un bloque de caché y se sirva desde la tienda de caché cuando llegue la siguiente solicitud.
Por ejemplo, si desea almacenar en caché cada producto en una página, puede usar este código:
<% @products.each do |product| %>
<% cache product do %>
<%= render product %>
<% end %>
<% end %>
Cuando su aplicación recibe su primera solicitud a esta página, Rails escribirá una nueva entrada de caché con una clave única. Una clave se ve algo como esto:
views/products/index:bea67108094918eeba42cd4a6e786901/products/1
La cadena de caracteres en el medio es un resumen del árbol de plantillas. Es un resumen de hash calculado en función del contenido del fragmento de vista que está almacenando en caché. Si cambia el fragmento de vista (por ejemplo, cambia el HTML), el resumen cambiará, caducando el archivo existente.
Se almacena una versión de caché, derivada del registro del producto, en la entrada de caché. Cuando se modifica el producto, la versión de caché cambia y se ignoran los fragmentos en caché que contienen la versión anterior.
CONSEJO: Las tiendas de caché como Memcached eliminarán automáticamente los archivos de caché antiguos.
Si desea almacenar en caché un fragmento bajo ciertas condiciones, puede usar
cache_if
o cache_unless
:
<% cache_if admin?, product do %>
<%= render product %>
<% end %>
1.3.1 Caché de Colección
El ayudante render
también puede almacenar en caché plantillas individuales renderizadas para una colección.
Incluso puede superar el ejemplo anterior con each
leyendo todas las plantillas en caché
a la vez en lugar de una por una. Esto se hace pasando cached: true
al renderizar la colección:
html+erb
<%= render partial: 'products/product', collection: @products, cached: true %>
Todos los templates en caché de renderizaciones anteriores se buscarán de una vez con mucha mayor velocidad. Además, los templates que aún no se hayan guardado en caché se escribirán en caché y se buscarán en la siguiente renderización.
1.4 Caché de muñecas rusas
Es posible anidar fragmentos en caché dentro de otros fragmentos en caché. Esto se conoce como caché de muñecas rusas.
La ventaja del caché de muñecas rusas es que si se actualiza un solo producto, se pueden reutilizar todos los demás fragmentos internos al regenerar el fragmento externo.
Como se explicó en la sección anterior, un archivo en caché caducará si cambia el valor de updated_at
para un registro en el que el archivo en caché depende directamente. Sin embargo, esto no caducará ninguna caché en la que el fragmento esté anidado.
Por ejemplo, considera la siguiente vista:
<% cache product do %>
<%= render product.games %>
<% end %>
Que a su vez renderiza esta vista:
<% cache game do %>
<%= render game %>
<% end %>
Si se cambia cualquier atributo de game
, el valor de updated_at
se establecerá en el tiempo actual, lo que expirará la caché. Sin embargo, como no se cambiará updated_at
para el objeto de producto, esa caché no caducará y tu aplicación servirá datos obsoletos. Para solucionar esto, vinculamos los modelos con el método touch
:
class Product < ApplicationRecord
has_many :games
end
class Game < ApplicationRecord
belongs_to :product, touch: true
end
Con touch
establecido en true
, cualquier acción que cambie updated_at
para un registro de juego también lo cambiará para el producto asociado, lo que expirará la caché.
1.5 Caché de parciales compartidos
Es posible compartir parciales y su caché asociada entre archivos con diferentes tipos MIME. Por ejemplo, el caché de parciales compartidos permite a los escritores de plantillas compartir un parcial entre archivos HTML y JavaScript. Cuando las plantillas se recopilan en las rutas de archivos del resolutor de plantillas, solo incluyen la extensión del lenguaje de plantillas y no el tipo MIME. Debido a esto, las plantillas se pueden usar para múltiples tipos MIME. Tanto las solicitudes HTML como las de JavaScript responderán al siguiente código:
render(partial: 'hotels/hotel', collection: @hotels, cached: true)
Cargará un archivo llamado hotels/hotel.erb
.
Otra opción es incluir el nombre completo del parcial a renderizar.
render(partial: 'hotels/hotel.html.erb', collection: @hotels, cached: true)
Cargará un archivo llamado hotels/hotel.html.erb
en cualquier tipo MIME de archivo, por ejemplo, podrías incluir este parcial en un archivo JavaScript.
1.6 Gestión de dependencias
Para invalidar correctamente la caché, debes definir correctamente las dependencias de caché. Rails es lo suficientemente inteligente como para manejar casos comunes, por lo que no tienes que especificar nada. Sin embargo, a veces, cuando estás trabajando con helpers personalizados, por ejemplo, necesitas definirlos explícitamente.
1.6.1 Dependencias implícitas
La mayoría de las dependencias de plantillas se pueden derivar de las llamadas a render
en la propia plantilla. Aquí hay algunos ejemplos de llamadas a render
que ActionView::Digestor
sabe cómo decodificar:
render partial: "comments/comment", collection: commentable.comments
render "comments/comments"
render 'comments/comments'
render('comments/comments')
render "header" se traduce a render("comments/header")
render(@topic) se traduce a render("topics/topic")
render(topics) se traduce a render("topics/topic")
render(message.topics) se traduce a render("topics/topic")
Por otro lado, algunas llamadas deben cambiarse para que el caché funcione correctamente. Por ejemplo, si estás pasando una colección personalizada, deberás cambiar:
render @project.documents.where(published: true)
a:
render partial: "documents/document", collection: @project.documents.where(published: true)
1.6.2 Dependencias explícitas
A veces tendrás dependencias de plantillas que no se pueden derivar en absoluto. Este es típicamente el caso cuando la renderización ocurre en helpers. Aquí tienes un ejemplo:
<%= render_sortable_todolists @project.todolists %>
Deberás usar un formato de comentario especial para indicar eso:
<%# Template Dependency: todolists/todolist %>
<%= render_sortable_todolists @project.todolists %>
En algunos casos, como en una configuración de herencia de tabla única, es posible que tengas varias dependencias explícitas. En lugar de escribir cada plantilla, puedes usar un comodín para que coincida con cualquier plantilla en un directorio:
<%# Template Dependency: events/* %>
<%= render_categorizable_events @person.events %>
En cuanto a la caché de colecciones, si la plantilla parcial no comienza con una llamada de caché limpia, aún puedes beneficiarte de la caché de colecciones agregando un formato de comentario especial en cualquier lugar de la plantilla, como:
<%# Template Collection: notification %>
<% my_helper_that_calls_cache(some_arg, notification) do %>
<%= notification.name %>
<% end %>
1.6.3 Dependencias Externas
Si utilizas un método auxiliar, por ejemplo, dentro de un bloque en caché y luego actualizas ese método auxiliar, también tendrás que actualizar la caché. Realmente no importa cómo lo hagas, pero el MD5 del archivo de plantilla debe cambiar. Una recomendación es simplemente ser explícito en un comentario, como:
<%# Dependencia del Auxiliar Actualizada: 28 de julio de 2015 a las 7pm %>
<%= some_helper_method(person) %>
1.7 Caché de Bajo Nivel
A veces necesitas almacenar en caché un valor o el resultado de una consulta en lugar de almacenar en caché fragmentos de vista. El mecanismo de caché de Rails funciona muy bien para almacenar cualquier información serializable.
La forma más eficiente de implementar la caché de bajo nivel es utilizando el método Rails.cache.fetch
. Este método realiza tanto la lectura como la escritura en la caché. Cuando se pasa solo un argumento, se obtiene la clave y se devuelve el valor de la caché. Si se pasa un bloque, ese bloque se ejecutará en caso de que no se encuentre en la caché. El valor de retorno del bloque se escribirá en la caché con la clave de caché dada, y ese valor de retorno se devolverá. En caso de que se encuentre en la caché, se devolverá el valor almacenado en caché sin ejecutar el bloque.
Considera el siguiente ejemplo. Una aplicación tiene un modelo Product
con un método de instancia que busca el precio del producto en un sitio web competidor. Los datos devueltos por este método serían perfectos para la caché de bajo nivel:
class Product < ApplicationRecord
def competing_price
Rails.cache.fetch("#{cache_key_with_version}/competing_price", expires_in: 12.hours) do
Competitor::API.find_price(id)
end
end
end
NOTA: Observa que en este ejemplo utilizamos el método cache_key_with_version
, por lo que la clave de caché resultante será algo como products/233-20140225082222765838000/competing_price
. cache_key_with_version
genera una cadena basada en el nombre de clase del modelo, el id
y los atributos updated_at
. Esta es una convención común y tiene la ventaja de invalidar la caché cada vez que se actualiza el producto. En general, cuando utilizas la caché de bajo nivel, necesitas generar una clave de caché.
1.7.1 Evita Cachear Instancias de Objetos de Active Record
Considera este ejemplo, que almacena una lista de objetos de Active Record que representan superusuarios en la caché:
# super_admins es una consulta SQL costosa, así que no la ejecutes con demasiada frecuencia
Rails.cache.fetch("super_admin_users", expires_in: 12.hours) do
User.super_admins.to_a
end
Debes evitar este patrón. ¿Por qué? Porque la instancia podría cambiar. En producción, los atributos pueden diferir, o el registro puede ser eliminado. Y en desarrollo, funciona de manera poco confiable con almacenes de caché que recargan el código cuando realizas cambios.
En su lugar, almacena el ID u otro tipo de datos primitivos. Por ejemplo:
# super_admins es una consulta SQL costosa, así que no la ejecutes con demasiada frecuencia
ids = Rails.cache.fetch("super_admin_user_ids", expires_in: 12.hours) do
User.super_admins.pluck(:id)
end
User.where(id: ids).to_a
1.8 Caché de Consultas SQL
La caché de consultas es una característica de Rails que almacena en caché el conjunto de resultados devuelto por cada consulta. Si Rails encuentra la misma consulta nuevamente en esa solicitud, utilizará el conjunto de resultados almacenado en caché en lugar de ejecutar la consulta nuevamente en la base de datos.
Por ejemplo:
class ProductsController < ApplicationController
def index
# Ejecutar una consulta de búsqueda
@products = Product.all
# ...
# Ejecutar la misma consulta nuevamente
@products = Product.all
end
end
La segunda vez que se ejecuta la misma consulta en la base de datos, en realidad no se va a acceder a la base de datos. La primera vez que se devuelve el resultado de la consulta se almacena en la caché de consultas (en memoria) y la segunda vez se obtiene de la memoria.
Sin embargo, es importante tener en cuenta que las cachés de consultas se crean al comienzo de una acción y se destruyen al final de esa acción, por lo que solo persisten durante la duración de la acción. Si deseas almacenar los resultados de la consulta de manera más persistente, puedes hacerlo con la caché de bajo nivel.
2 Almacenes de Caché
Rails proporciona diferentes almacenes para los datos en caché (además de la caché SQL y de página).
2.1 Configuración
Puedes configurar el almacén de caché predeterminado de tu aplicación estableciendo la opción de configuración config.cache_store
. Otros parámetros se pueden pasar como argumentos al constructor del almacén de caché:
config.cache_store = :memory_store, { size: 64.megabytes }
Alternativamente, puedes establecer ActionController::Base.cache_store
fuera de un bloque de configuración.
Puedes acceder a la caché llamando a Rails.cache
.
2.1.1 Opciones de la Piscina de Conexiones
De forma predeterminada, :mem_cache_store
y
:redis_cache_store
están configurados para usar
piscinas de conexiones. Esto significa que si estás utilizando Puma u otro servidor con subprocesos, puedes tener varios subprocesos realizando consultas al almacén de caché al mismo tiempo.
Si desea desactivar la agrupación de conexiones, configure la opción :pool
en false
al configurar la caché:
config.cache_store = :mem_cache_store, "cache.example.com", pool: false
También puede anular la configuración de agrupación predeterminada proporcionando opciones individuales a la opción :pool
:
config.cache_store = :mem_cache_store, "cache.example.com", pool: { size: 32, timeout: 1 }
:size
- Esta opción establece el número de conexiones por proceso (por defecto es 5).:timeout
- Esta opción establece el número de segundos que se esperará una conexión (por defecto es 5). Si no hay ninguna conexión disponible dentro del tiempo de espera, se generará unTimeout::Error
.
2.2 ActiveSupport::Cache::Store
ActiveSupport::Cache::Store
proporciona la base para interactuar con la caché en Rails. Esta es una clase abstracta y no se puede utilizar por sí sola. En su lugar, debe utilizar una implementación concreta de la clase vinculada a un motor de almacenamiento. Rails incluye varias implementaciones, que se documentan a continuación.
Los principales métodos de API son read
, write
, delete
, exist?
y fetch
.
Las opciones pasadas al constructor de la caché se tratarán como opciones predeterminadas para los métodos de API correspondientes.
2.3 ActiveSupport::Cache::MemoryStore
ActiveSupport::Cache::MemoryStore
mantiene las entradas en memoria en el mismo proceso de Ruby. La caché tiene un tamaño limitado especificado enviando la opción :size
al inicializador (por defecto es 32Mb). Cuando la caché supera el tamaño asignado, se realizará una limpieza y se eliminarán las entradas menos utilizadas.
config.cache_store = :memory_store, { size: 64.megabytes }
Si está ejecutando varios procesos de servidor Ruby on Rails (lo cual es el caso si está utilizando Phusion Passenger o el modo agrupado de puma), las instancias de los procesos del servidor Rails no podrán compartir datos de caché entre sí. Esta caché no es adecuada para implementaciones de aplicaciones grandes. Sin embargo, puede funcionar bien para sitios pequeños con poco tráfico y solo un par de procesos de servidor, así como para entornos de desarrollo y pruebas.
Los nuevos proyectos de Rails están configurados para utilizar esta implementación en el entorno de desarrollo de forma predeterminada.
NOTA: Dado que los procesos no compartirán datos de caché al utilizar :memory_store
, no será posible leer, escribir o expirar manualmente la caché a través de la consola de Rails.
2.4 ActiveSupport::Cache::FileStore
ActiveSupport::Cache::FileStore
utiliza el sistema de archivos para almacenar las entradas. Debe especificar la ruta al directorio donde se almacenarán los archivos de la caché al inicializarla.
config.cache_store = :file_store, "/ruta/al/directorio/de/caché"
Con esta caché, varios procesos de servidor en el mismo host pueden compartir una caché. Esta caché es adecuada para sitios con poco a mediano tráfico que se sirven desde uno o dos hosts. Los procesos de servidor que se ejecutan en hosts diferentes podrían compartir una caché utilizando un sistema de archivos compartido, pero esta configuración no se recomienda.
Dado que la caché crecerá hasta que el disco esté lleno, se recomienda eliminar periódicamente las entradas antiguas.
Esta es la implementación de caché predeterminada (en "#{root}/tmp/cache/"
) si no se proporciona una configuración explícita de config.cache_store
.
2.5 ActiveSupport::Cache::MemCacheStore
ActiveSupport::Cache::MemCacheStore
utiliza el servidor memcached
de Danga para proporcionar una caché centralizada para su aplicación. Rails utiliza la gema dalli
incluida de forma predeterminada. Actualmente, esta es la caché más popular para sitios web en producción. Se puede utilizar para proporcionar un clúster de caché único y compartido con un rendimiento y redundancia muy altos.
Al inicializar la caché, debe especificar las direcciones de todos los servidores memcached
en su clúster, o asegurarse de que la variable de entorno MEMCACHE_SERVERS
se haya configurado correctamente.
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
Si no se especifica ninguna, asumirá que memcached
se está ejecutando en localhost
en el puerto predeterminado (127.0.0.1:11211
), pero esta no es una configuración ideal para sitios más grandes.
config.cache_store = :mem_cache_store # Utilizará $MEMCACHE_SERVERS, luego 127.0.0.1:11211
Consulte la documentación de Dalli::Client
para obtener los tipos de direcciones admitidos.
El método write
(y fetch
) en esta caché acepta opciones adicionales que aprovechan las características específicas de memcached
.
2.6 ActiveSupport::Cache::RedisCacheStore
ActiveSupport::Cache::RedisCacheStore
aprovecha el soporte de Redis para la eliminación automática cuando alcanza la memoria máxima, lo que le permite comportarse de manera similar a un servidor de caché Memcached.
Nota de implementación: Redis no expira las claves de forma predeterminada, así que asegúrese de utilizar un servidor de caché Redis dedicado. ¡No llene su servidor Redis persistente con datos de caché volátiles! Lea detenidamente la guía de configuración del servidor de caché Redis en Redis cache server setup guide.
Para un servidor Redis solo de caché, establezca maxmemory-policy
en una de las variantes de allkeys
. Redis 4+ admite la eliminación menos utilizada (allkeys-lfu
), que es una excelente opción predeterminada. Redis 3 y versiones anteriores deben utilizar la eliminación menos reciente utilizada (allkeys-lru
).
Establezca los tiempos de espera de lectura y escritura de la caché relativamente bajos. Regenerar un valor en caché a menudo es más rápido que esperar más de un segundo para recuperarlo. Los tiempos de espera de lectura y escritura tienen un valor predeterminado de 1 segundo, pero se pueden establecer más bajos si su red tiene una latencia consistentemente baja.
De forma predeterminada, el almacén de caché no intentará reconectarse a Redis si la conexión falla durante una solicitud. Si experimenta desconexiones frecuentes, es posible que desee habilitar los intentos de reconexión.
Las lecturas y escrituras en caché nunca generan excepciones; simplemente devuelven nil
en su lugar, comportándose como si no hubiera nada en la caché. Para evaluar si su caché está generando excepciones, puede proporcionar un error_handler
para informar a un servicio de recopilación de excepciones. Debe aceptar tres argumentos de palabras clave: method
, el método del almacén de caché que se llamó originalmente; returning
, el valor que se devolvió al usuario, normalmente nil
; y exception
, la excepción que se rescató.
Para comenzar, agregue la gema de Redis a su Gemfile:
gem 'redis'
Finalmente, agregue la configuración en el archivo config/environments/*.rb
relevante:
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }
Un almacén de caché de Redis más complejo y de producción puede verse así:
cache_servers = %w(redis://cache-01:6379/0 redis://cache-02:6379/0)
config.cache_store = :redis_cache_store, { url: cache_servers,
connect_timeout: 30, # Por defecto, 20 segundos
read_timeout: 0.2, # Por defecto, 1 segundo
write_timeout: 0.2, # Por defecto, 1 segundo
reconnect_attempts: 1, # Por defecto, 0
error_handler: -> (method:, returning:, exception:) {
# Reportar errores a Sentry como advertencias
Sentry.capture_exception exception, level: 'warning',
tags: { method: method, returning: returning }
}
}
2.7 ActiveSupport::Cache::NullStore
ActiveSupport::Cache::NullStore
está limitado a cada solicitud web y borra los valores almacenados al final de una solicitud. Está destinado a ser utilizado en entornos de desarrollo y pruebas. Puede ser muy útil cuando tiene código que interactúa directamente con Rails.cache
pero el almacenamiento en caché interfiere con la visualización de los resultados de los cambios de código.
config.cache_store = :null_store
2.8 Almacenes de caché personalizados
Puede crear su propio almacén de caché personalizado simplemente extendiendo ActiveSupport::Cache::Store
e implementando los métodos correspondientes. De esta manera, puede intercambiar cualquier cantidad de tecnologías de almacenamiento en caché en su aplicación de Rails.
Para usar un almacén de caché personalizado, simplemente configure el almacén de caché como una nueva instancia de su clase personalizada.
config.cache_store = MyCacheStore.new
3 Claves de caché
Las claves utilizadas en una caché pueden ser cualquier objeto que responda a cache_key
o to_param
. Puede implementar el método cache_key
en sus clases si necesita generar claves personalizadas. Active Record generará claves basadas en el nombre de la clase y el ID del registro.
Puede usar Hashes y Arrays de valores como claves de caché.
# Esta es una clave de caché válida
Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])
Las claves que utiliza en Rails.cache
no serán las mismas que las que se utilizan realmente en el motor de almacenamiento. Pueden modificarse con un espacio de nombres o alterarse para adaptarse a las restricciones del backend de tecnología. Esto significa, por ejemplo, que no puede guardar valores con Rails.cache
y luego intentar extraerlos con la gema dalli
. Sin embargo, tampoco necesita preocuparse por exceder el límite de tamaño de memcached o violar las reglas de sintaxis.
4 Soporte para GET condicional
Las solicitudes GET condicionales son una característica de la especificación HTTP que proporciona una forma para que los servidores web indiquen a los navegadores que la respuesta a una solicitud GET no ha cambiado desde la última solicitud y se puede obtener de forma segura desde la caché del navegador.
Funcionan utilizando los encabezados HTTP_IF_NONE_MATCH
y HTTP_IF_MODIFIED_SINCE
para enviar de ida y vuelta un identificador de contenido único y la marca de tiempo de cuándo se modificó por última vez el contenido. Si el navegador realiza una solicitud en la que el identificador de contenido (ETag) o la marca de tiempo de la última modificación coincide con la versión del servidor, entonces el servidor solo necesita enviar una respuesta vacía con un estado de no modificado.
Es responsabilidad del servidor (es decir, nuestro) buscar una marca de tiempo de última modificación y la cabecera if-none-match y determinar si enviar o no la respuesta completa. Con el soporte de GET condicional en Rails, esta es una tarea bastante sencilla:
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
# Si la solicitud no es actual según la marca de tiempo y el valor de etag dados
# (es decir, necesita procesarse nuevamente) entonces ejecute este bloque
if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key_with_version)
respond_to do |wants|
# ... procesamiento normal de la respuesta
end
end
# Si la solicitud es nueva (es decir, no se ha modificado) entonces no necesita hacer
# nada. La representación predeterminada verifica esto utilizando los parámetros
# utilizados en la llamada anterior a stale? y automáticamente enviará un
# :not_modified. Así que eso es todo, has terminado.
end
end
En lugar de un hash de opciones, también puedes simplemente pasar un modelo. Rails utilizará los métodos updated_at
y cache_key_with_version
para establecer last_modified
y etag
:
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
if stale?(@product)
respond_to do |wants|
# ... procesamiento normal de la respuesta
end
end
end
end
Si no tienes ningún procesamiento especial de la respuesta y estás utilizando el mecanismo de renderizado por defecto (es decir, no estás utilizando respond_to
o llamando a render
tú mismo), entonces tienes un ayudante fácil en fresh_when
:
class ProductsController < ApplicationController
# Esto enviará automáticamente un :not_modified si la solicitud es fresca,
# y renderizará la plantilla por defecto (product.*) si está obsoleta.
def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.published_at.utc, etag: @product
end
end
A veces queremos cachear una respuesta, por ejemplo una página estática, que nunca caduca. Para lograr esto, podemos usar el ayudante http_cache_forever
y de esta manera el navegador y los proxies la cachearán indefinidamente.
Por defecto, las respuestas cacheadas serán privadas, solo se cachearán en el navegador web del usuario. Para permitir que los proxies cacheen la respuesta, establece public: true
para indicar que pueden servir la respuesta caché a todos los usuarios.
Usando este ayudante, el encabezado last_modified
se establece en Time.new(2011, 1, 1).utc
y el encabezado expires
se establece en 100 años.
ADVERTENCIA: Utiliza este método con cuidado ya que el navegador/proxy no podrá invalidar la respuesta caché a menos que se borre la caché del navegador de forma forzada.
class HomeController < ApplicationController
def index
http_cache_forever(public: true) do
render
end
end
end
4.1 ETags Fuertes vs ETags Débiles
Rails genera ETags débiles de forma predeterminada. Las ETags débiles permiten que las respuestas semánticamente equivalentes tengan las mismas ETags, incluso si sus cuerpos no coinciden exactamente. Esto es útil cuando no queremos que la página se regenere por cambios menores en el cuerpo de la respuesta.
Las ETags débiles tienen un prefijo W/
para diferenciarlas de las ETags fuertes.
W/"618bbc92e2d35ea1945008b42799b0e7" → ETag Débil
"618bbc92e2d35ea1945008b42799b0e7" → ETag Fuerte
A diferencia de las ETags débiles, las ETags fuertes implican que la respuesta debe ser exactamente la misma y byte por byte idéntica. Es útil cuando se hacen solicitudes de rango dentro de un archivo de video o PDF grande. Algunas CDNs solo admiten ETags fuertes, como Akamai. Si necesitas generar una ETag fuerte, puedes hacerlo de la siguiente manera.
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.published_at.utc, strong_etag: @product
end
end
También puedes establecer la ETag fuerte directamente en la respuesta.
response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7"
5 Caché en Desarrollo
Es común querer probar la estrategia de caché de tu aplicación en modo de desarrollo. Rails proporciona el comando dev:cache
para alternar fácilmente la caché activada/desactivada.
$ bin/rails dev:cache
El modo de desarrollo ahora está en caché.
$ bin/rails dev:cache
El modo de desarrollo ya no está en caché.
Por defecto, cuando la caché del modo de desarrollo está desactivada, Rails utiliza :null_store
.
6 Referencias
- Artículo de DHH sobre la expiración basada en claves
- Railscast de Ryan Bates sobre los digestos de caché
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor, contribuye si encuentras algún error tipográfico o factual. Para empezar, puedes leer nuestra contribución a la documentación sección.
También puedes encontrar contenido incompleto o desactualizado. Por favor, añade cualquier documentación faltante para main. Asegúrate de revisar Edge Guides primero para verificar si los problemas ya están resueltos o no en la rama principal. Consulta las Directrices de las Guías de Ruby on Rails para el estilo y las convenciones.
Si por alguna razón encuentras algo que corregir pero no puedes solucionarlo tú mismo, por favor abre un problema.
Y por último, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.