El almacenamiento en caché es esencial para mejorar el rendimiento y la escalabilidad de las aplicaciones web, y el almacenamiento en caché en Ruby on Rails no es una excepción. Al almacenar y reutilizar los resultados de cálculos costosos o consultas a bases de datos, el almacenamiento en caché reduce significativamente el tiempo y los recursos necesarios para atender las peticiones de los usuarios.

Aquí repasamos cómo implementar distintos tipos de almacenamiento en caché en Rails, como el almacenamiento en caché de fragmentos y el almacenamiento en caché de muñecas rusas. También te mostramos cómo gestionar las dependencias de la caché y elegir los almacenes de caché, y detallamos las mejores prácticas para utilizar la caché de forma eficaz en una aplicación Rails.

Este artículo asume que estás familiarizado con Ruby on Rails, que utilizas la versión 6 o superior de Rails y que te sientes cómodo utilizando las vistas de Rails. Los ejemplos de código demuestran cómo utilizar el almacenamiento en caché dentro de plantillas de vistas nuevas o existentes.

Tipos de Almacenamiento en Caché de Ruby on Rails

Existen varios tipos de almacenamiento en caché en las aplicaciones Ruby on Rails, dependiendo del nivel y la granularidad del contenido a almacenar en caché. Los principales tipos utilizados en las aplicaciones Rails modernas son:

  • Almacenamiento en caché de fragmentos: Almacena en caché partes de una página web que no cambian con frecuencia, como cabeceras, pies de página, barras laterales o contenido estático. La caché de fragmentos reduce el número de parciales o componentes renderizados en cada petición.
  • Caché de muñecas rusas: Almacena en caché fragmentos anidados de una página web que dependen unos de otros, como colecciones y asociaciones. La caché de muñecas rusas evita consultas innecesarias a la base de datos y facilita la reutilización de fragmentos almacenados en caché sin modificar.

Anteriormente, Ruby on Rails incluía dos tipos adicionales de caché, pero ahora están disponibles como objetos independientes:

  • Almacenamiento en caché de páginas: Almacena en caché páginas web enteras como archivos estáticos en el servidor, evitando todo el ciclo de vida de renderización de la página
  • Almacenamiento en caché de acciones: Almacena en caché la salida de acciones de controlador completas. Es similar a la caché de página, pero te permite aplicar filtros como la autenticación.

El almacenamiento en caché de páginas y acciones se utiliza con poca frecuencia y ya no se recomienda para la mayoría de los casos de uso en las aplicaciones Rails modernas.

Caché de Fragmentos en Ruby on Rails

El almacenamiento en caché de fragmentos te permite almacenar en caché partes de una página que cambian con poca frecuencia. Por ejemplo, una página que muestre una lista de productos con sus precios y valoraciones asociados podría almacenar en caché detalles que es poco probable que cambien.

Mientras tanto, podría permitir a Rails volver a renderizar partes dinámicas de la página — como comentarios o valoraciones — en cada carga de la página. La caché de fragmentos es menos útil cuando los datos subyacentes de una vista cambian con frecuencia, debido a la sobrecarga que supone actualizar frecuentemente la caché.

Al ser el tipo de caché más sencillo incluido en Rails, la caché de fragmentos debería ser tu primera opción cuando añadas caché a tu aplicación para mejorar el rendimiento.

Para utilizar la caché de fragmentos en Rails, utiliza el método auxiliar de cache en tus vistas. Por ejemplo, escribe el siguiente código para almacenar en caché un parcial de producto en tu vista:

<% @products.each do |product| %>
  <% cache product do %>
    <%= render partial: "product", locals: { product: product } %>
  <% end %>
<% end %>

El método auxiliar de cache genera una clave de caché basada en el nombre de clase de cada elemento, id, y la marca de tiempo updated_at (por ejemplo, products/1-20230501000000). La próxima vez que un usuario solicite el mismo producto, el método auxiliar de cache recuperará el fragmento en caché del almacén de caché y lo mostrará sin leer el producto de la base de datos.

También puedes personalizar la clave de caché pasando opciones al método auxiliar de cache. Por ejemplo, para incluir un número de versión o una marca de tiempo en tu clave de caché, escribe algo como esto

<% @products.each do |product| %>
  <% cache [product, "v1"] do %>
    <%= render partial: "product", locals: { product: product } %>
  <% end %>
<% end %>

Alternativamente, puedes establecer un tiempo de caducidad:

<% @products.each do |product| %>
  <% cache product, expires_in: 1.hour do %>
    <%= render partial: "product", locals: { product: product } %>
  <% end %>
<% end %>

El primer ejemplo añadirá v1 a la clave de la caché (por ejemplo, products/1-v1). Esto es útil para invalidar la caché cuando cambies la plantilla parcial o el diseño. El segundo ejemplo establece un tiempo de caducidad para la entrada de la caché (1 hora), lo que ayuda a caducar los datos obsoletos.

Caché de Muñecas Rusas en Ruby on Rails

La caché de muñecas rusas es una potente estrategia de caché en Ruby on Rails que optimiza el rendimiento de tu aplicación anidando cachés unas dentro de otras. Utiliza la caché de fragmentos de Rails y las dependencias de caché para minimizar el trabajo redundante y mejorar los tiempos de carga.

En una aplicación Rails típica, a menudo renderizas una colección de elementos, cada uno con múltiples componentes hijos. Al actualizar un único elemento, evita volver a renderizar toda la colección o cualquier elemento no afectado. Utiliza la caché de Muñecas Rusas cuando trabajes con estructuras de datos jerárquicas o anidadas, especialmente cuando los componentes anidados tengan sus propios datos asociados que podrían cambiar independientemente.

El inconveniente de la caché de Muñecas Rusas es que añade complejidad. Debes comprender las relaciones entre los niveles anidados de elementos que estás almacenando en caché para asegurarte de que almacenas en caché los elementos correctos. En algunos casos, tendrás que añadir asociaciones a tus modelos Active Record para que Rails pueda inferir las relaciones entre los elementos de datos almacenados en caché.

Al igual que con la caché de fragmentos normal, la caché de muñecas rusas utiliza el método auxiliar de cache. Por ejemplo, para almacenar en caché una categoría con sus subcategorías y productos en tu vista, escribe algo como esto

<% @categories.each do |category| %>
  <% cache category do %>
    <h2><%= category.name %></h2>
    <% category.subcategories.each do |subcategory| %>
    <% cache subcategory do %>
    <h3><%= subcategory.name %></h3>
    <% subcategory.products.each do |product| %>
    <% cache product do %>
        <%= render partial: "product", locals: { product: product } %>
        <% end %>
    <% end %>
    <% end %>
    <% end %>
  <% end %>
<% end %>

El método auxiliar de cache almacenará cada nivel anidado por separado en el almacén de caché. La próxima vez que se solicite la misma categoría, recuperará su fragmento en caché del almacén de caché y lo mostrará sin renderizarlo de nuevo.

Sin embargo, si cambian los detalles de alguna subcategoría o producto  — como su nombre o descripción — se invalida su fragmento en caché, que se vuelve a renderizar con los datos actualizados. El almacenamiento en caché de muñecas rusas garantiza que no tengas que invalidar toda una categoría si cambia una sola subcategoría o producto.

Gestión de Dependencias de Caché en Ruby on Rails

Las dependencias de caché son relaciones entre los datos almacenados en caché y sus fuentes subyacentes, y gestionarlas puede ser complicado. Si los datos de origen cambian, cualquier dato almacenado en caché asociado debe caducar.

Rails puede utilizar marcas de tiempo para gestionar automáticamente la mayoría de las dependencias de caché. Cada modelo de Registro Activo tiene atributos created_at y updated_at que indican cuándo la caché creó o actualizó por última vez el registro. Para asegurarte de que Rails puede gestionar automáticamente el almacenamiento en caché, define las relaciones de tus modelos de Registro Activo como sigue:

class Product < ApplicationRecord
  belongs_to :category
end
class Category < ApplicationRecord
  has_many :products
end

En este ejemplo

  • Si actualizas un registro de producto (por ejemplo, cambiando su precio), su marca de tiempo updated_at cambia automáticamente.
  • Si utilizas esta marca de tiempo como parte de tu clave de caché (como products/1-20230504000000), también se invalida automáticamente su fragmento en caché.
  • Para invalidar el fragmento almacenado en caché de tu categoría cuando actualices un registro de producto — quizás porque muestra algunos datos agregados como el precio medio — utiliza el método touch en tu controlador (@product.category.touch) o añade una opción touch en tu asociación modelo (belongs_to :category touch: true).

Otro mecanismo para gestionar las dependencias de la caché es utilizar métodos de caché de bajo nivel -como fetch y write — directamente en tus modelos o controladores. Estos métodos te permiten almacenar datos o contenidos arbitrarios en tu almacén de caché con claves y opciones personalizadas. Por ejemplo

class Product < ApplicationRecord
  def self.average_price
    Rails.cache.fetch("products/average_price", expires_in: 1.hour) do
    average(:price)
    end
  end
end

Este ejemplo muestra cómo almacenar en caché datos calculados — como el precio medio de todos los productos — durante una hora utilizando el método fetch con una clave personalizada (products/average_price) y una opción de caducidad (expires_in: 1.hour).

El método fetch intentará leer primero los datos del almacén caché. Si no encuentra los datos o éstos han caducado, ejecuta el bloque y almacena el resultado en el almacén caché.

Para invalidar manualmente una entrada de la caché antes de su tiempo de caducidad, utiliza el método write con la opción force:

Rails.cache.write("products/average_price", Product.average(:price), force: true))

Almacenes de Caché y Backends en Ruby on Rails

Rails te permite elegir diferentes almacenes de caché o backends para guardar tus datos y contenidos en caché. El almacén de caché de Rails es una capa de abstracción que proporciona una interfaz común para interactuar con diferentes sistemas de almacenamiento. Un backend de caché implementa la interfaz del almacén de caché para un sistema de almacenamiento específico.

Rails admite varios tipos de almacenes de caché o backends, que se detallan a continuación.

Almacén de Memoria

El almacén de memoria utiliza un hash en memoria como almacenamiento caché. Es rápido y sencillo, pero tiene capacidad y persistencia limitadas. Este almacén de caché es adecuado para entornos de desarrollo y pruebas o para aplicaciones pequeñas y sencillas.

Almacenamiento en Disco

El almacenamiento en disco utiliza archivos del disco como almacenamiento en caché. Es la opción de almacenamiento en caché más lenta de Rails, pero tiene una gran capacidad y persistencia. El almacenamiento en disco es adecuado para aplicaciones que deben almacenar en caché grandes cantidades de datos y no necesitan el máximo rendimiento.

Redis

El almacén Redis utiliza una instancia de Redis para el almacenamiento en caché. Redis es un almacén de datos en memoria que admite varios tipos de datos. Aunque es rápido y flexible, requiere un servidor y una configuración independientes. Es adecuado para aplicaciones que deben almacenar en caché datos complejos o dinámicos que cambian con frecuencia. Redis es una opción ideal cuando se ejecutan aplicaciones Rails en la nube porque algunos proveedores de alojamiento, incluido Kinsta, ofrecen Redis como caché de objetos persistente.

Memcached

El almacén Memcached utiliza una instancia de Memcached para el almacenamiento en caché. Memcached es un almacén clave-valor en memoria que admite tipos de datos y funciones sencillas. Es rápido y escalable, pero al igual que Redis, requiere un servidor y una configuración independientes. Este almacén es adecuado para aplicaciones que necesitan almacenar en caché datos simples o estáticos que se actualizan con frecuencia.

Puedes configurar tu almacén caché en tus archivos de entornos Rails (por ejemplo, config/environments/development.rb) utilizando la opción config.cache_store. A continuación te explicamos cómo utilizar cada uno de los métodos de almacenamiento en caché incorporados en Rails:

# Use memory store
config.cache_store = :memory_store
# Use disk store
config.cache_store = :file_store, "tmp/cache"
# Use Redis
config.cache_store = :redis_cache_store, { url: "redis://localhost:6379/0" }
# Use Memcached
config.cache_store = :mem_cache_store, "localhost"

Sólo debes tener una llamada a config.cache_store por archivo de entorno. Si tienes más de una, el almacén de caché sólo utiliza la última.

Cada almacén de caché tiene ventajas y desventajas únicas en función de las necesidades y preferencias de tu aplicación. Elige el que mejor se adapte a tu caso de uso y a tu nivel de experiencia.

Buenas Prácticas para el Almacenamiento en Caché de Ruby on Rails

Utilizar el almacenamiento en caché en tu aplicación Rails puede aumentar significativamente su rendimiento y escalabilidad, especialmente cuando implementas las siguientes buenas prácticas:

  • Almacena en caché de forma selectiva: Almacena en caché sólo los datos a los que se accede con frecuencia, los que son caros de generar o los que se actualizan con poca frecuencia. Evita el exceso de caché para evitar el uso excesivo de memoria, los riesgos de datos obsoletos y la degradación del rendimiento.
  • Expirar entradas de caché: Evita que los datos caduquen expirando las entradas inválidas o irrelevantes. Utiliza marcas de tiempo, opciones de caducidad o invalidación manual.
  • Optimiza el rendimiento de la caché: Elige el almacén de caché que se ajuste a las necesidades de tu aplicación, y ajusta sus parámetros — como el tamaño, la compresión o la serialización — para obtener un rendimiento óptimo.
  • Supervisa y comprueba el impacto de la caché: Evalúa el comportamiento de la caché — como la tasa de aciertos, la tasa de fallos y la latencia — y valora sus respectivos impactos en el rendimiento (tiempo de respuesta, rendimiento, uso de recursos). Utiliza herramientas como New Relic, Rails logsActiveSupport notifications o Rack mini profiler

Resumen

El almacenamiento en caché de Ruby on Rails mejora el rendimiento y la escalabilidad de las aplicaciones al almacenar y reutilizar de forma eficiente los datos o contenidos a los que se accede con frecuencia. Con un conocimiento más profundo de las técnicas de almacenamiento en caché, estarás mejor equipado para ofrecer aplicaciones Rails más rápidas a tus usuarios.

Cuando despliegues tu aplicación Rails optimizada, puedes recurrir a la plataforma de Alojamiento de Aplicaciones de Kinsta. Empieza gratis con una cuenta Hobby Tier y explora la plataforma con este ejemplo de inicio rápido de Ruby on Rails.

Steve Bonisteel Kinsta

Steve Bonisteel es un Editor Técnico de Kinsta que comenzó su carrera de redactor como periodista de prensa escrita, persiguiendo ambulancias y camiones de bomberos. Lleva tratando temas relacionados con la tecnología de Internet desde finales de la década de 1990.