La memoria caché es un tipo de almacenamiento, menor que la unidad de almacenamiento principal, pero con un acceso más rápido, que se utiliza en ingeniería de la computación para aportar un mayor rendimiento al sistema. La caché no es un concepto nuevo dentro de la informática, es una memoria que se lleva utilizando años y desde el punto de más bajo nivel de una computadora (procesador), hasta el más alto (software).
Físicamente podemos encontrar 3 tipos de memorias caché diferenciadas por su cercanía o lejanía del procesador:
Caché L1
Esta la podemos encontrar dentro del procesador, muy rápida, pero pequeña. Es la memoria donde el procesador almacena los registros con los que hace operaciones.
Caché L2
Algo más separada encontramos la L2, al lado de procesador, más grande que la L1 pero más lenta. Aunque no obstante es muy rápida. Es la típica caché que anuncian en los procesadores: i7 con 4kb de cache. Esta se usa como memoria intermedia, para almacenar más datos y luego volcarlos a la L1 para operar.
Caché L3
Es la equivalente a la memoria RAM o de la del disco duro (la RAM es más rápida que el disco). Este es el nivel donde los vamos a mover a lo largo del resto de este artículo. Es la caché que como programadores de lenguajes manejados, estamos capacitados para gestionar.
También cabe mencionar que la memoria RAM a su vez, tiene también una caché de acceso más rápido que la propia RAM, la SRAM, donde se almacenan los datos que más se usan. Y además puede usar la memoria de disco de la máquina si es que se ha llenado. Y los discos usan como caché el típico buffer que se usa al leer un archivo. Pero quizá nos estamos desviando de la temática principal.
El concepto que es importante que trascienda de aquí es que todo en los ordenadores usa siempre algún tipo de memoria caché :).
Cache no-memory vs. in-memory
En nivel de L3 se suelen diferenciar dos tipos de caché según donde se almacenarán los datos físicamente.Y este almacén otorgará unas características y escenarios aplicables diferentes.
No-memory
Una caché persistida (no-memory) es aquella caché que queda guardada en una unidad de almacenamiento. Físicamente quedará almacenada hasta que caduque. Un ejemplo es el output-cache de una página web. Al usar estos parámetros, los navegadores entienden que esos datos no se van a modificar. Así que los guarda y no los vuelve a pedir al servidor, usa la copia almacenada en su caché. Aunque apaguemos el ordenador, estos datos no se borran, la próxima vez que iniciemos, estarán allí.
Existen varias formas diferentes de usar la caché output dentro de una página web:
Si nos basáramos en el estándar HTML5 deberíamos crear un archivo de manifiesto donde indicaríamos que archivos van a ser almacenados en la caché del navegador y cuales no. Un ejemplo de este archivo sería este:
CACHE MANIFEST # v1.0.0 /bootstrap.min.css /site.css /logo.gif /main.js NETWORK: /Auth/Login # * FALLBACK: /Auth/Login /Home/offline.html
Cuando iniciamos el archivo siempre hay que declarar la directiva “CACHE MANIFEST”, acto seguido irán los recursos que se almacenarán en la caché. Dentro de la directiva “NETWORK” irán aquellos recursos que siempre deben ser solicitados al servidor. Y en el caso de “FALLBACK”, indicaremos el recurso alternativo a mostrar si es que no se encuentra una solicitud.
Y por último necesitaremos referenciar ese archivo en nuestra página web:
<!DOCTYPE html> <html manifest="demo.appcache"> <head> <title>Demo</title> <link rel="../Content/CACHE_MANIFEST" type="text/cache-manifest" /> </head> <body> </body> </html>
Aquí podemos observar dos apuntes. El primero es la declaración de un atributo “manifest” en el tag “html”. Esto es necesario para que el navegador sepa que la página web actual va a usar un manifiesto. Y por convenio se suele nombrar como “[nombre de aplicación].appcache”. Y el segundo es la etiqueta “link” que apunta a nuestro archivo de manifiesto.
Cualquier recurso referenciado para ser almacenado en caché de esta forma, será actualizado solo si:
- El usuario borra la caché del navegador
- Cuando el archivo de manifiesto sea modificado (por eso es recomendable añadir un comentario con la versión actual del archivo)
- Cuando se actualice la caché de la aplicación programáticamente </ul>
Por otro lado Asp.Net nos provee de una forma bastante sencilla de almacenar y configurar una página concreta en la caché del navegador.
Si nos encontramos utilizando WebForms, deberíamos añadir al inicio de nuestro archivo .aspx una línea semejante a esta:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="...Default" %> <%@ OutputCache Duration="3000" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> ...
Como podemos observar en el ejemplo, hemos añadido una directiva "OutputCache" en la que indicamos que esta página será almacenada en la caché durante 3000 segundos.
Y si estuviéramos usando Asp.Net MVC, sería algo parecido, aunque en este caso tendríamos que añadirlo a nivel de acción del controlador:
public class HomeController : Controller { [OutputCache(Duration = 3000)] public ActionResult Index() { return View(); } }
Donde añadiremos un atributo "OutputCache" indicando también la duración de 3000 segundos.
In-memory
La caché in-memory por otro lado, se almacena en la memoria RAM de la máquina. De forma que si reiniciamos la máquina, estos datos se borran y habrá que volverlos a cargar en la caché. Un ejemplo de esto sería el HttpRuntimeCache de Asp.Net: una caché que perdura en memoria y que gestiona por nosotros IIS/Asp.Net. Se le puede dar muchos usos, pero uno de los más típicos sería el ejemplo de un combo de países o ciudades. Datos que no suelen cambiar y que no queremos estar cargando constantemente de la base de datos. Los almacenamos en una memoria caché y accedemos a ellos de forma muy rápida.
Implementar este ejemplo en una página web podría ser algo parecido a esto:
protected void Page_Load(object sender, EventArgs e) { if (!this.IsPostBack) { var cities = HttpRuntime.Cache.Get("cities") as string[]; if (cities == null) { // cargar las ciudades en la variable cities HttpRuntime.Cache.Insert("cities", cities, null, DateTime.Now.AddYears(1), TimeSpan.FromSeconds(3000)); } // cargar el combo con el listado de ciudades } }
Aquí intentaríamos recoger de HttpRuntime.Cache un objeto que almacena las ciudades. Si no lo encontramos en la caché, entonces lo cargaremos y lo almacenaremos dentro de la misma. Como detalle podemos observar dos parámetros TimeSpan a la hora de insertar algo en la caché. El primero sería el tiempo de caducidad absoluto, es decir que cuando se cumpla ese periodo de tiempo, el objeto se borrará. Y el segundo es el tiempo de refresco. Este valor es el periodo de tiempo que pasa desde el último acceso a ese objeto de la caché, para que caduque. Es decir, que cada vez que usamos ese valor de caché se empieza a contar desde cero hasta llegar a ese tiempo.
HttpRuntime.Cache es de por sí thread-safe. Esto significa que puede ser accedido desde diferentes threads (hilos de ejecución) a la vez, sin problemas. Pero las operaciones de lectura de datos no tienen por qué ser thread-safe. Si en nuestro escenario ocurriera este problema, entonces tendríamos que añadir un bloqueo a estas operaciones:
private static readonly object locker = new object(); protected void Page_Load(object sender, EventArgs e) { if (!this.IsPostBack) { lock (locker) { var cities = HttpRuntime.Cache.Get("cities") as string[]; if (cities == null) { // cargar las ciudades en la variable cities HttpRuntime.Cache.Insert("cities", cities, null, DateTime.Now.AddYears(1), TimeSpan.FromSeconds(3000)); } } // cargar el combo con el listado de ciudades } }
Y si en lugar de tratarse de una página web y quisiéramos tener una caché desde por ejemplo, una aplicación de Windows, .Net nos provee del objeto MemoryCache, que se encuentra en el ensamblado “System.Runtime.Caching.dll”:
using System.Runtime.Caching; public void LoadData() { var cache = MemoryCache.Default; var cities = cache.Get("cities") as string[]; if (cities == null) { // cargar cities del almacen de datos var item = new CacheItem("cities", cities); var policy = new CacheItemPolicy(); policy.AbsoluteExpiration = DateTime.Now.AddYears(1); policy.SlidingExpiration = TimeSpan.FromSeconds(3000); cache.Set(item, policy); } }
En este ejemplo podemos encontrarnos el mismo escenario que el anterior, pero esta vez usando MemoryCache. La mayor diferencia es la necesidad de usar un objeto de tipo “CacheItem” para almacenar la información, y otro “CacheItemPolicy” para la gestión de la caducidad.
Tipos de datos que manejamos en una caché
Cuando hablamos de almacenar información dentro de una memoria caché, podemos diferenciar el tipo de información en dependencia de: cómo de volátil es esa información y quien puede acceder a la misma. Y encontramos tres grandes bloques de información:
- Datos de referencia: prácticamente de solo lectura. No se modifican demasiado y son comunes a todos los usuarios. Por ejemplo el listado de productos de una tienda virtual o los sprites/imágenes que luego se dibujan en pantalla en un videojuego.
- Datos de actividad: son datos de lectura y escritura fundamentalmente, cuyo ciclo de vida es el mismo que el de la sesión de un usuario. Por ejemplo el carrito de la compra de una tienda virtual, o la puntuación de un jugador en un videojuego.
- Datos de recursos: Son datos de lectura y escritura, compartidos por todos los usuarios, pero que pueden cambiar con frecuencia. Estos datos tienen la peculiaridad de poder ser accedidos por muchos usuarios. Un ejemplo sería el stockage de los productos de una tienda virtual, o las puntuaciones globales de todos los usuarios en un videojuego. </ul>
Está en manos del desarrollador saber en cada momento y aplicación, qué datos sería mejor almacenar en qué tipo de caché. No hay una fórmula mágica para decidirlo, aunque sí algunos truquillos. Y también es importante saber que tampoco sirve la opción de almacenar todo dentro de la caché. No es un martillo dorado que sirva para todo.
Lo que sí hay que tener en cuenta es que una caché puede perder los datos en algún momento, por lo que no tenemos la seguridad de que estén ahí siempre, aunque los hayamos almacenado ahí al iniciar la aplicación...
Cache-aside Pattern
Un concepto que tenemos que tener muy claro a la hora de utilizar memorias caché, es que los datos que se almacenan en una caché no tienen por qué estar ahí. Existen muchas razones por las cuales podrían no estar, desde que no han sido cargados esos datos hasta que hayan caducado o incluso que el sistema haya decidido que necesita más espacio y se haya deshecho de ellos.
Por estas razones existe el patrón cache-aside. Una forma muy simple de recoger/cargar datos de una memoria caché sin tener que tener miedo a que no existan. Su forma de funcionar es muy simple:
Y como muchos habrán deducido ya, hemos realizado una implementación del mismo en los ejemplos anteriores. Aunque esa implementación del patrón era específica, no genérica. Una implementación genérica del patrón podría ser esta:
public abstract class CacheBase { public T GetOrSet<T>(string key, Func<T> getter, TimeSpan? expiration = null) { try { if (this.Exists(key)) { return this.Get<T>(key); } } catch { Trace.Write("El objeto buscado en la caché no existe", "CachingInfo"); } var value = getter(); try { this.Set<T>(key, value, expiration); } catch { Trace.Write("No se ha podido almacenar un objeto en la caché", "CachingError"); } return value; } public abstract bool Exists(string key); public abstract T Get<T>(string key); public abstract void Set<T>(string key, T value, TimeSpan? expiration = null); }
Aunque también hay algunos proveedores de caché que no tienen la instrucción “Exists” y que si no encuentran un objeto, simplemente devuelven “null”. Esto se basa en la teoría de que una caché no debe devolver errores ya que perderíamos más tiempo gestionándolos que lo que ganamos usando la caché en sí. Para esos proveedores una implementación más cercana podría ser la siguiente:
public abstract class CacheBase { public T GetOrSet<T>(string key, Func<T> getter, TimeSpan? expiration = null) { try { var data = this.Get<T>(key); if (data != null) { return data; } } catch { Trace.Write("El objeto buscado en la caché no existe", "CachingInfo"); } var value = getter(); try { this.Set<T>(key, value, expiration); } catch { Trace.Write("No se ha podido almacenar un objeto en la caché", "CachingError"); } return value; } public abstract T Get<T>(string key); public abstract void Set<T>(string key, T value, TimeSpan? expiration = null); }
Limitaciones de una caché
Es tan importante conocer las virtudes de usar caché como sus posibles limitaciones. Dentro de estas podríamos enumerar:
Tiempo de vida de objetos en caché limitado:
una gran parte de las memorias caché que existen implementan una política de expiración de los objetos que almacenamos en ella. Esto significa que un objeto tiene un tiempo de vida concreto. Si introducimos un valor de expiración muy alto, es posible que terminemos teniendo un objeto ya no válido. El uso de la caché está recomendado sobre todo para datos prácticamente estáticos o que se leen con mucha frecuencia.
Desalojo de datos:
también la mayoría de las cachés implementan un sistema automático de desalojo de objetos. Como la memoria de la caché es limitada, cuando esta se llena se opta por borrar los objetos más antiguos. Aunque podríamos implementar un modo de desalojo global, con un tiempo determinado para cualquier objeto, podría ser que haya objetos que sea recomendable saltarse estas limitaciones por razones de performance y de que el dato no varía con facilidad.
Preparación inicial de caché:
Muchos programas a la hora de arrancar realizan un llenado inicial de varios objetos en la memoria caché y alguno de ellos pueden ser usados en el proceso de inicio. Si usamos el patrón cache-aside, podemos garantizar que leemos correctamente estos datos aunque hayan caducado.
Consistencia de la información:
Lo primero que perdemos al utilizar memorias caché es la consistencia de los datos. No tiene por qué coincidir el objeto que está almacenado en caché con el que está en el almacén de datos. Y este problema se acentúa en la medida de la frecuencia de modificación de un dato que tenemos almacenado en la memoria caché.
Cachés locales:
Si usamos una aplicación con varios servidores balanceados y cada uno usa una caché local, además de tener un dato replicado en diferentes sitios, podría ser que existiera inconsistencia entre datos de la caché de un servidor y de otro. Esto tiene una fácil solución gestionando cachés distribuidas, como veremos más adelante.
Alternativas a usar una caché
Como ya hemos mencionado no siempre usar una caché será la solución a nuestros problemas. Existen alternativas para disminuir la latencia de un sistema. Algunas de las más comunes serían:
- Escalar el sistema: lo que vulgarmente conocemos como añadir más hierro. Cuando escalamos horizontalmente o verticalmente un sistema mejoramos la performance porque las funciones que antes se realizaban usando una serie de recursos, ahora pueden utiliza muchos más. Pero el problema del escalado es que por lo general es caro. Además podemos estar escondiendo problemas más serios en nuestra aplicación a base de hacer que funciones en máquinas más potentes.
- Podríamos mejorar la performance de los procesos. Que puede resultar más caro que añadir más hierro. Pero esto nos asegura tener una mejor aplicación. Aunque hay que entender que los procesos están limitados por la tecnología y los sistemas.
- Almacenar y cargar agregados y no pequeñas entidades de una en una. Almacenar agregados, tampoco nos va a aportar una velocidad tan grande, aunque es muy recomendable.
- Usar variables de sesión o la caché output. Pero las variables de sesión están solo disponibles para una instancia de una aplicación concreta, no para todas. A no ser que la persistamos con lo que perdemos ese plus de velocidad. Y la caché output, la gestiona el cliente bajo unas directrices del servidor, por lo que no podemos borrarla bajo demanda. Solo el propio cliente es el que la puede manejar. </ul>
- Hay que pensar en el nuevo hierro.
- Debemos gestionar estos sistemas, mantenerlos actualizados, configurarlos…
- Tenemos que ser capaces de integrar esa cache distribuida con el resto de los sistemas.
- Hay que tener un plan de escalabilidad, si es que en un momento determinado se nos quedan cortos los recursos.
- Todo servicio en producción debe de constar de un plan de contingencia para tratar los problemas.
- … </ul>
- Azure Cache InRole
- Azure Cache Services
- Máquina virtual con memcached
- Azure Redis Caché </ul>
Probablemente si nuestro sistema tiene problemas de performance, la solución no será solo implementar cachés en algunos puntos concretos de la aplicación. Es muy posible que tengamos que aplicar estas técnicas que mencionamos, y otras tantas para conseguir subsanar todos los problemas actuales y futuros que nos encontremos.
Cache distribuida
En los entornos actuales, una aplicación puede estar alojada en diferentes máquinas. Puede escalar en diferentes instancias. Hoy en día nos encontramos con sistemas híbridos cloud/on-premises o sistemas totalmente en la nube. Para todos estos escenarios las cachés locales in-memory se nos antojan algo desfasadas, porque nuestras arquitecturas requieren de un solo componente que no tenga que estar replicado por cada una de las instancias de una aplicación. Y es aquí cuando aparece una forma nueva de usar la caché: caché distribuida.
Un sistema de Caché distribuido, no consiste más que en tratar la memoria caché como un nuevo servicio que pueden consumir el resto de aplicaciones de un datacenter.
Pero gestionar y asegurar una alta disponibilidad en un sistema de caché distribuido puede ser un problema debido a que surgen nuevos retos:
Todos estos problemas nos llevan a utilizar herramientas de terceros para tener una solución fiable y robusta. Y dentro de las soluciones de terceros, tenemos las cachés distribuidas en las nubes de las grandes empresas, como son los diferentes servicios de caché de Microsoft Azure.
Caché en azure
Dentro de la plataforma Microsoft Azure tenemos varios servicios de caché en los que no tendremos que preocuparnos más que de decidir su tamaño. Y si estos sistemas no cubren nuestras necesidades, siempre estará posibilidad de crear una máquina virtual donde podemos instalar el servidor que más nos guste de cachés. En este artículo hemos tratado las siguientes opciones:
Azure Cache InRole
El Azure Cache InRole es un servicio disponible en principio para los Web Roles y los Worker Roles de los Cloud Services. Nos permite utilizar una cantidad de memoria de uno de estos roles o bien Worker Roles dedicados, como memoria caché. La caché InRole funciona como un cluster, podemos crear varias instancias de los diferentes roles y todos estos sumarán su memoria a la memoria caché del Cloud Service. Además no tendremos que preocuparnos de difíciles configuraciones ya que es un proceso automático.
Para poder desplegar esta memoria de una forma sencilla, la primera opción pasa por crear un nuevo proyecto Cloud usando Visual Studio:
Para después en el dialogo de configuración del Cloud Service añadir un rol de caché dedicado:
Con esto ya tendríamos nuestro proyecto preparado para usar la caché InRole. Para configurar una cantidad de memoria u otra, tendremos que jugar con el tamaño de la máquina que lo hospedará y el numero de instancias (recordemos que todas suman). Esto lo podemos configurar en la página de propiedades del rol en el proyecto de Cloud Service:
Y para poder consumir esta caché en otro de los roles que tengamos dentro del mismo Cloud Service tendremos que añadir una referencia al paquete de nuget “Microsoft.WindowsAzure.Caching”. Entonces veremos que en el archivo de configuración de nuestro rol ha añadido una nueva sección, donde tan solo tendremos que introducir el nombre que le hemos puesto al rol que actuará como caché:
<dataCacheClients> <dataCacheClient name="default"> <autoDiscover isEnabled="true" identifier="[nombre del rol]" /> </dataCacheClient> </dataCacheClients>
Y para poder operar desde c# podremos heredar de nuestra clase CacheBase e implementar las funciones necesarias:
public class AzureCache : CacheBase { private readonly DataCacheFactory factory = new DataCacheFactory(); public MicrosoftCache() { factory = new DataCacheFactory(); } protected override T OnGet<T>(string key) { var cache = this.GetCurrentCache(); return (T)cache.Get(key); } protected override void OnSet<T>(string key, T value, TimeSpan? expiration = null) { var cache = this.GetCurrentCache(); if (expiration.HasValue) cache.Put(key, value, expiration.Value); else cache.Put(key, value); } protected override void OnRemove(string key) { var cache = this.GetCurrentCache(); cache.Remove(key); } private DataCache GetCurrentCache() { return factory.GetDefaultCache(); } }
Hay que tener en cuenta que la caché InRole, para comprobar que un objeto no existe devuelve “null” al solicitarlo, así que no hará falta implementar la función “Exists” ya que sería una reiteración recoger el valor dos veces.
Pero la caché InRole esconde un huevo de pascua, y es que contiene un gateway que nos permite utilizarla como un servidor memcached. Esto puede resultar verdaderamente útil cuando queremos hospedar páginas que ya usaban este tipo de servidores.
Para poderlo usar de esta forma tendremos que abrir el puerto 11211 y ponerle el nombre al endPoint de “memcache_default”:
No se recomienda que este endPoint sea público por cuestiones de seguridad, es decir que carece de cualquier medida de protección. Por lo que es más recomendable dejarlo como un endPoint interno para su uso exclusivo dentro del Cloud Service.
Para poder consumir esta caché a traves del gateway tendremos que usar un proveedor de Memcached. En este caso lo que suelo usar es el paquete de nuget “Enyim.Caching.Memcached”.
Y su implementación partiendo de la clase CacheBase sería algo parecido a esto:
public class MemcachedCache : CacheBase { private readonly IMemcachedClient client; public MemcachedCache() { this.client = new MemcachedClient(); } protected override T OnGet<T>(string key) { return client.Get<T>(key); } protected override void OnSet<T>(string key, T value, TimeSpan? expiration = null) { client.Store(StoreMode.Set, key, value); } protected override void OnRemove(string key) { client.Remove(key); } }
Azure Cache Services
Es un servicio distribuido dedicado solo a gestionar cachés.
Su creación se ha de realizar desde PowerShell:
PS C:\> Add-AzureAccount VERBOSE: Account "user@domain.com" has been added. VERBOSE: Subscription "MySubscription" is selected as the default subscription. VERBOSE: To view all the subscriptions, please use Get-AzureSubscription. VERBOSE: To switch to a different subscription, please use Select-AzureSubscription. PS C:\> New-AzureManagedCache -Name mycache -Location "West Europe" -Sku Basic -Memory 128MB VERBOSE: Intializing parameters... VERBOSE: Creating prerequisites... VERBOSE: Verify cache service name... VERBOSE: Creating cache service... VERBOSE: Waiting for cache service to be in ready state... Name : mycache Location : West Europe State : Active Sku : Basic Memory : 128MB PS C:\>
Y si queremos conectarnos a este servicio bastará con usar el código que hemos mostrado anteriormente para conectar con Azure Cache InRole, pero modificando en el web.config:
<dataCacheClients> <dataCacheClient name="default"> <autoDiscover isEnabled="true" identifier="[url del servicio]" /> <securityProperties mode="Message" sslEnabled="true"> <messageSecurity authorizationInfo="[contraseña del servicio]" /> </securityProperties> </dataCacheClient> </dataCacheClients>
Máquina virtual con memcached
Memcached es un servidor muy extendido para cachés distribuidas. Se usa en una gran cantidad de aplicaciones y resulta muy rápido. Actualmente no existe ningún servicio PaaS que nos provea de esta caché, así que tendremos que instalarlo en una máquina virtual.
Para crear una máquina virtual con el servidor de memcached, basta con ir al portal antiguo de azure y crear una máquina desde la galería:
Una vez ahí elegiremos una imagen de linux. En este caso hemos seleccionado un Ubuntu Server porque se basa en debian, que es la distro de linux que siempre hemos manejado y con la que nos sentimos más cómodos. Pero en realidad se podría elegir cualquiera.
No hay que olvidar configurar un usuario para el acceso remoto por SSH. Que es lo que necesitaremos para acceder al terminal de la máquina de forma remota y poder ejecutar comandos.
Con este simple comando ya tendremos un servidor básico instalado:
sudo apt-get install memcached
Si quisiéramos hacer que fuera accesible desde una máquina diferente a la que estamos, tendremos que editar el archivo “/etc/memcached.conf” y sustituir esta línea:
# Specify which IP address to listen on. The default is to listen on all IP addresses # This parameter is one of the only security measures that memcached has, so make sure # it's listening on a firewalled interface. -l 127.0.0.1
Por esta otra:
-l 0.0.0.0
Y después deberíamos reiniciar el servicio.
Para acceder a esta memoria caché podríamos usar el conector que hemos creado anteriormente (para Azure Cache InRole Gateway) configurando correctamente el servidor.
Azure Redis Caché
Aunque dentro de los servicios PaaS de Microsoft Azure se vende redis con un servidor de caché distribuida, en realidad redis es mucho más que esto. Es un servidor de base de datos NoSQL que almacena la información en forma de clave/valor. Además es capaz de trabajar con 5 estructuras de datos diferentes, implementa colas con bloqueo, pub/sub, transacciones, y muchas más cosas. Mucha gente define redis como un memcached con esteroides.
Y por esta razón se nos antoja escaso el espacio que ha quedado dentro de este artículo para hablar de redis, tan solo comentaremos que existen varios conectores muy simples de utilizar (ServiceStack.Redis, StackExchange.Redis y uno que estoy desarrollando actualmente con el nombre de Tokiota.Redis). Y que espero escribir a no mucho tardar un artículo mucho más extenso sobre cómo utilizar redis desde plataformas .Net y sacarle todo el partido posible, así que estad atentos .
Comparativas de caché en azure
Ya que nos hemos puesto a analizar muchos servicios de caché distribuidos sobre la plataforma azure, no podríamos despedirnos sin mostrar una tabla comparativa de la performance en forma de media de tiempos de cada uno. Para realizar esta prueba, se ha montado un Worker Role en azure desde donde se han ido realizando llamadas a los siguientes servicios y utilizando los proveedores señalados:
La primera deducción que podemos sacar de aquí es que el proveedor de memcached de “Enyim” es posiblemente una implementación muy buena, que además se ve favorecida por el protocolo tan ligero que manejan este tipo de servidores.
Pero aun así vemos bastante diferencia del servidor de memcached con respecto la segunda envelocidad: Azure Cache InRole a través del gateway de memcached. Esta diferencia se basa en que el segundo se trata de un cluster donde se garantiza una alta disponibilidad y una gestión de la caché entre varias máquinas virtuales, mientras que memcached es una instalación stand-alone en una máquina virtual linux.
Las diferencias que vemos con las dos formas de acceder al Azure Cache Service responden en exclusiva al proveedor y protocolo, que son mucho más ligeros para memcached. Pero no hay que olvidar que El proveedor de caché de Azure de Microsoft, nos provee de varias opciones y configuraciones más que en memcached no encontramos (como por ejemplo el expiration sliding time).
Sobre redis podemos comentar que a los drivers les queda aún camino por recorrer en cuanto a performace se refiere. La implementación Tokiota.Redis (en la que me encuentro trabajando a la hora de escribir esta artículo) todavía está incompleta, aunque ya juega con tiempos mejores. Pero hay que entender que la empezamos a desarrollar con ese objetivo .
Y Azure Cache Service termina como el termino medio. No es el más rápido, pero tienes tantas posibilidades en cuanto servicios de caché (aunque redis tiene más de otro tipo). Gestiona clusters muy grandes de caché y puede ser consumido desde casi cualquier lenguaje de programación conocido.
De cualquier forma, todas estas cifras que mostramos son milisegundos, por lo que podemos determinar que ninguno de los sistemas es especialmente lento y en nuestras aplicaciones notaremos muy poca diferencia de uno a otro. A no ser que abusemos del uso de la memoria caché.
Y hasta aquí este artículo que empezaba como una pequeña nota acerca de unas charlas en las que he tenido el placer de participar y donde he podido hablar de cachés. Para terminar os publicamos algunas referencias que a muchos os resultarán interesantes para seguir investigando.
Referencias
- http://www.wikipedia.org
- http://www.stackoverflow.com
- http://www.msdn.com
- Microsoft Azure Documentation
- http://redis.io/
- http://www.memcached.org
- https://github.com/enyim/EnyimMemcached
- https://github.com/ServiceStack
- https://github.com/StackExchange
- https://github.com/fernandoescolar/Tokiota.Redis </ul>