Patrones
Vs Categorías
Como la gran
mayoría de buenos desarrolladores saben, los patrones de diseño son paradigmas
que aportan soluciones a problemas
típicos y recurrentes que nos podemos encontrar a la hora de
desarrollar una aplicación. Seguramente
que la aplicación que estamos desarrollando sea exclusiva, pero tendrá partes
comunes con otras aplicaciones: acceso a datos, creación de objetos,
operaciones entre sistemas etc. En lugar de reinventar la rueda, podemos
solucionar problemas utilizando algún patrón, ya que son soluciones probadas y
documentadas por multitud de programadores.
Como comentaba al
principio, las 8 categorías que se han definido para tratar de englobar las áreas
en la nube en las que podemos encontrar problemas comunes son:
Para cada una de
estas categorías, veremos patrones comunes diseñados para ayudar a los
desarrolladores a resolver los problemas a los que se enfrentan con
regularidad.
Nosotros veremos,
a lo largo de varios artículos, en que categorías se pueden utilizar, el
contexto y problema que resuelve, la solución, consideraciones, cuando usarlo y
un ejemplo de su uso. En este primer artículo veremos el primer patrón, para
seguir en los siguientes artículos con los varios patrones más.
Patrón
Cache-Aside
Categorías donde
aplicarlo: Gestión de Datos y Rendimiento y Escalabilidad
Cargar los datos
mayoritariamente demandados en una memoria caché de un almacén de datos. Este
patrón puede mejorar el rendimiento y también ayuda a mantener la coherencia
entre los datos almacenados en la memoria caché y los datos en el almacén de
datos subyacente.
Contexto y Problema
Las aplicaciones
utilizan una caché para optimizar el acceso repetido a la información en un
almacén de datos. Sin embargo, para ello hemos de estar seguros que los datos
almacenados en caché tienen que ser consistentes con los datos en el almacén de
datos. Las aplicaciones deben implementar una estrategia que ayuda a asegurar
que los datos en la caché estén actualizados en la manera de lo posible, pero
también pueden detectar y manejar situaciones que surjan cuando los datos de la
caché estén obsoletos.
Solución
Muchos de los
sistemas de almacenamiento en caché comerciales proporcionan lectura y
escritura a través de las operaciones de lectura / escritura de la base de
datos subyacente. En estos sistemas, una aplicación recupera los datos de la
memoria caché. Si los datos no están en la caché, se recuperan de forma
transparente del almacén de datos subyacente y se agrega a la caché. Cualquier
modificación de los datos almacenados en la memoria caché se escriben
automáticamente al almacén de datos también.
Para cachés que
no proporcionan esta funcionalidad, es responsabilidad de las aplicaciones que
utilizan la memoria caché para mantener los datos en la caché.
Una aplicación
puede emular esta funcionalidad implementando el patrón Cache-Aside. Este
patrón carga de forma efectiva datos en la caché bajo demanda.
Consideraciones
Hay que tener en
cuenta los siguientes puntos en el momento de decidir la forma de aplicar este
patrón:
•
Vida útil de los
datos almacenados en caché. Muchos cachés implementan una directiva de caducidad que
hace que los datos sean invalidados y se eliminan de la memoria caché si no se
accede por un período determinado. Para que este patrón sea efectivo, asegúrate
de que la política de vencimiento coincide con el patrón de acceso para las
aplicaciones que utilizan los datos. Recuerda que el almacenamiento en caché es
más eficaz para los datos relativamente estáticos o datos que se leen con
frecuencia.
•
El vaciado de Datos. La mayoría de los
cachés tienen un tamaño limitado en comparación con el almacén de datos de
donde se originan los datos, y van a vaciar a los datos si es necesario. La
mayoría de los cachés adoptan una política de uso de utilización más reciente
para seleccionar los elementos a vaciar, pero esto se puede personalizar.
Configure la propiedad global de vencimiento y otras propiedades de la memoria
caché, y la propiedad de vencimiento de cada elemento almacenado en caché, para
ayudar a asegurar que el caché es rentable. No siempre puede ser adecuado
aplicar una política de vaciado global para todos los elementos de la caché.
Por ejemplo, si un elemento de la caché es muy costoso de recuperar del almacén
de datos, puede ser beneficioso mantenerlo en la memoria caché a expensas de
otros elementos a los que son accedidos con mayor frecuencia, pero son menos
costosos.
•
Precargar la Caché. Muchas soluciones
rellenan previamente la memoria caché con los datos que es probable que
necesite como parte del proceso de inicio de una aplicación. Este patrón puede
ser útil si algunos de estos datos expiran o son eliminados.
•
Consistencia. Implementar
este patrón no garantiza la coherencia entre el almacén de datos
y la memoria caché. Un elemento en el almacén de datos se puede cambiar en cualquier momento por
un proceso externo, y este cambio
no se refleja en la memoria caché
hasta la próxima vez que el elemento se
cargue en la memoria caché. En un sistema que replica datos a través de almacenes de datos,
este problema puede llegar a ser especialmente
grave si se produce la
sincronización con mucha frecuencia.
•
Almacenamiento en
caché local (In-Memory). Una caché podría ser local a una
instancia de la aplicación y ser almacenada en memoria. El patrón puede ser
útil en este entorno si una aplicación accede repetidamente a los mismos datos.
Sin embargo, si una caché local es privada y las diferentes instancias de la
aplicación tienen una copia de los mismos datos en la caché, los datos podrían
convertirse rápidamente inconsistentes entre cachés, por lo que puede ser
necesario revisar las políticas de cuando expiran los datos almacenados en una
memoria caché privada y actualizarlos con mayor frecuencia. En estos escenarios
puede ser apropiado el uso de un mecanismo de caché distribuida.
Cuando usar este patrón:
• La caché no facilita operaciones nativas de lectura y escritura.
•
La demanda de recursos es impredecible. Este modelo permite
a las aplicaciones cargar datos bajo demanda. Se
predicen qué datos de una
aplicación requerirá de antemano.
Este patrón podría no ser adecuado:
•
Cuando el conjunto de datos en
caché es estático
•
Para el
almacenamiento en caché de la
información de estado de sesión en una aplicación web
alojada en unagranja de servidores web. En
este entorno, se debe evitar la
introducción de dependencias basadas en
la afinidad de
cliente-servidor.
Ejemplo:
En Windows Azure se puede usar Windows Azure Caché para crear una memoria caché distribuida que puede ser compartida por varias instancias de una aplicación. El método GetMyEntityAsync en el siguiente ejemplo de código muestra una implementación de este patrón basado en Windows Azure caché. Un objeto se identifica mediante el uso de un ID con valor de número entero como clave. El método GetMyEntityAsync genera un valor de cadena con base en esta clave (la API de Windows Azure caché utiliza cadenas de valores de clave) y trata de recuperar un elemento con esta clave de la caché. Si se encuentra un elemento coincidente, lo devuelve. Si no hay ninguna coincidencia en la caché, el método GetMyEntityAsync recupera el objeto de un almacén de datos, lo agrega a la caché, y luego la devuelve (el código que realmente recupera los datos del almacén de datos se ha omitido debido a que es el almacén de datos dependiente). Hay que tener en cuenta que el elemento en caché está configurado para expirar a fin de evitar de que se quede obsoleto si se actualiza en cualquier otro lugar.
1private DataCache cache;2...3public4async Task<MyEntity> GetMyEntityAsync(int id) {5
1// Define una2clave única para este método y sus parámetros.3
1var key = string.Format("StoreWithCache_GetAsync_{0}", id);2
1var expiration = TimeSpan.FromMinutes(3);2
1bool cacheException = false;2
1try2
1{2
1// Intentamos2obtener la clave de la caché.3
1var cacheItem = cache.GetCacheItem(key);2
1if (cacheItem != null)2
1{2
1return cacheItem.Value as MyEntity;2
1}2
1}2
1catch (DataCacheException)2
1{2
1// si hay2algún problema lanzará una excepción3
1// y evitará el uso de la caché para el resto de2la llamada.3
1cacheException2= true;3
1}2
1// Si hay un2error de caché, obtenemos el id del almacén original y // lo almacenaremos en3la caché.4
1// el código2ha sido omitido por ser de un almacén dependiente este // ejemplo.3
1var entity = ...;2
1if (!cacheException)2
1{2
1try2
1{2
1// Evitamos2el almacenamiento en caché de un valor nulo3
1if (entity != null)2
1{2
1// Ponemos el2elemento con tiempo de caducidad dependiendo de la3
1// criticidad2que pueda ser tener datos obsoletos3
1cache.Put(key, entity, timeout: expiration);2
1}2
1}2
1catch (DataCacheException)2
1{2
1// If there is a cache related issue, ignore it2
1// and just return the entity.2
1}2
1}2
1return entity;2
1 }2
El método UpdateEntityAsync
que voy a mostrar a continuación muestra
cómo invalida un objeto en la caché cuando el valor es modificado por la aplicación. Este es un ejemplo de un enfoque de escritura simultánea. El código actualiza el almacén de datos original y luego elimina el elemento de la caché de la caché mediante una llamada al método Remove, especificando la clave (el
código para esta parte de la
funcionalidad se ha omitido, ya que será el almacén de datos dependiente).
1public async Task UpdateEntityAsync(MyEntity entity) {2
1// Actualizar2el objeto en el almacén de datos original.3
1await this.store.UpdateEntityAsync(entity).ConfigureAwait(false);2
1// Obtener la2clave correcta para el objeto en caché.3
1var key = this.GetAsyncCacheKey(entity.Id);2
1// Entonces,2Eliminamos el actual objeto de la caché3
1this.cache.Remove(key); }2
1private string GetAsyncCacheKey(int objectId) {2
1return string.Format("StoreWithCache_GetAsync_{0}", objectId); }2
En el siguiente artículo continuaremos viendo patrones de diseño, que a buen seguro nos serán de gran utilidad a la hora de diseñar e implementar soluciones en la nube.
Francisco
Ricardo Gil González
MVP CLUSTER | Especialista en SharePoint &
Office 365
francisco.gil@fiveshareit.es
Linkedin
http://www.mvpcluster.es