Mostrando entradas con la etiqueta Caché. Mostrar todas las entradas
Mostrando entradas con la etiqueta Caché. Mostrar todas las entradas

jueves, 25 de agosto de 2011

PHP Cache

Una estrategia de cache es siempre una de las primeras opciones a tomar cuando se requiere mejorar el desempeño de una aplicación. Recientemente tuve que buscar una opción disponible para una aplicación web que estabamos trabajando en PHP, y de las que consulté por Internet, me quedé con la que ofrece este amigo en este artículo.

Esta clase PHPCache utiliza una tabla en base de datos con tres columnas como mecanismo de almacenamiento del cache:
  • PHPCache_key: la llave del objeto a guardar
  • PHPCache_value: el valor a guardar
  • PHPCache_expires: cuando expira el objeto en cache
Nuestro amigo ofrece un código que ejemplifica muy bien el uso de la clase:

require_once 'phpcache.class.php'; // Se incluye la clase de PHPCache.

// Parametros de conexión de la Base de datos.
$database = array(
'type' => 'mysql',
'name' => 'YOUR DATABASE NAME',
'table' => 'PHPCache',
'user' => 'YOUR DATABASE USERNAME',
'pass' => 'YOUR DATABASE USERNAME\'S PASSWORD',
'host' => 'localhost' /* Or maybe IP address of your database server */
);

// Llamado de configuración de la base de datos.
PHPCache::configure($database);
// Se obtiene una única instancia (patrón Singleton).
$cache = PHPCache::instance();

// Se pregunta primero si los resultados ya están en cache.
if( ($results = $cache->get('result_of_some_nasty_code')) !== false ) {
$tpl->assign($results);
/* Or maybe return $results or whatever depending on where you use this */
} else {
// Los datos todavía no estan en cache. Hay que obtenerlos de la manera lenta.
/***********************
* Your slow code here
***********************/
// Inmediatamente se guardan en cache. La siguiente vez no se hará de la forma lenta.
$cache->store('result_of_some_nasty_code', $results_of_your_slow_code, PHPCACHE_1_HOUR * 5); /* Cache for 5 hours */
}

jueves, 17 de septiembre de 2009

Ciclo de Vida de JCS

En esta última parte acerca de JCS voy a explicar el ciclo de vida básico de un objeto guardado en cache y las diferentes clases que se ocupan para poder cumplir con cada tarea básica.

En el post anterior describí como establecer la configuración básica para tener nuestro cache listo para ser llenado con nuestros preciosos objetos.Un punto más que necesitamos dejar listo antes de comenzar nuestro ahorro de recursos es tomar en cuenta los manejadores de expiración o en inglés “Expiration Handlers”.

Un manejador de expiración es una clase encargada de ejecutar las respectivas acciones cuando un objeto termina su largo o corto periodo de vida (R.I.P.). Como creo que un código dice más que mil palabras, veamos un ejemplo.

Vamos a crear primero nuestra clase AbstractExpirationHandler la cual va ser extendida por nuestras clases de expiración “concreto”.

import java.util.HashSet;
import java.util.Set;

import org.apache.jcs.engine.CacheElement;
import org.apache.jcs.engine.control.event.ElementEvent;
import org.apache.jcs.engine.control.event.behavior.IElementEvent;
import org.apache.jcs.engine.control.event.behavior.IElementEventHandler;

public abstract class AbstractExpirationHandler implements IElementEventHandler {

private static final Set events = new HashSet();

{
events.add(ELEMENT_EVENT_EXCEEDED_IDLETIME_BACKGROUND);
events.add(ELEMENT_EVENT_EXCEEDED_IDLETIME_ONREQUEST);
events.add(ELEMENT_EVENT_EXCEEDED_MAXLIFE_BACKGROUND);
events.add(ELEMENT_EVENT_EXCEEDED_MAXLIFE_ONREQUEST);
}

public void handleElementEvent(IElementEvent elementEvent) {
ElementEvent event;
CacheElement element;

if (events.contains(elementEvent.getElementEvent())) {
event = (ElementEvent) elementEvent;
element = (CacheElement) event.getSource();
handleEvent(element);
}
}
/*To be implemented by concrete classes.*/
protected abstract void handleEvent(CacheElement element);
}

Nuestra clase concreto simplemente invocará las funciones necesarias para actualizar nuestro cache.

import org.apache.jcs.engine.CacheElement;

public class ConcreteExpirationHandler extends
AbstractExpirationHandler {


protected void handleEvent(CacheElement element) {
ConcreteKey key = (ConcreteKey) element.getKey();
/**
TODO: Update cache object identified by its key.
*/
}
}

La clase manejador concreto necesita ser asociada a una region especifica. Podemos hacer esto agregando el siguiente método a la clase CacheManager.

public static boolean addEventHandler(String region,
IElementEventHandler handler) {
boolean result = true;
JCS cache;
IElementAttributes attributes;
try {
cache = JCS.getInstance(region);
attributes = cache.getDefaultElementAttributes();
attributes.addElementEventHandler(handler);
cache.setDefaultElementAttributes(attributes);
} catch (CacheException ce) {
result = false;
}
return result;
}

Y usarla en neutro método init():

CacheManager.addEventHandler(“default”, new ConcreteExpirationHandler());

Si el lector está poniendo atención, habrá notado que se introdujo un nuevo concepto en el último pedazo de código, el elemento llave de cache (“Cache Key” en inglés). Como expliqué antes en la segunda parte de esta serie, un objeto de cache necesita ser identificado con una llave única. En un mundo simple, un objeto puede ser identificaso con una sola variable, probablemente un String, pero como vivimos en un mundo complejo, las llaves para nuestros objetos pueden ser un conjunto entero de pequeñas llaves, similar a una tabla de una base de datos que tiene una llave compuesta.

Así que para tener una llave necesitamos tener una clase con tres características básicas:
  1. Implemente la interface Serializabe
  2. Implemente equals()
  3. Implemente hascode()

import java.io.Serializable;


public class ConcreteKey implements Serializable {
private static final long serialVersionUID = 3599612490238955657L;

private String keyParam1;
private String keyParam2;

public String getKeyParam1() {
return keyParam1;
}

public void setKeyParam1(String keyParam1) {
this. keyParam1 = keyParam1;
}

public String getKeyParam2() {
return keyParam2;
}

public void setKeyParam1(String keyParam1) {
this. keyParam1 = keyParam1;
}

public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (this == obj) {
return true;
}
if (!(obj instanceof ConcreteKey)) {
return false;
}
ConcreteKey other = (ConcreteKey) obj;
boolean equals;
equals = (this.keyParam1.equals(other.keyParam1))
&& (this.keyParam2.equals(other.keyParam2));
return equals;
}

public int hashCode() {
StringBuilder builder = new StringBuilder();
builder.append(this.keyParam1).append(this.keyParam1);
String objectStr = builder.toString();
return objectStr.hashCode();
}
}

Ahora con nuestra llave podemos consultar y actualizar nuestro cache:

public class FooService {

public FooObject getFooObject(String keyParam1, String keyParam2) {
FooObject foo = null;
ConcreteKey key = new ConcreteKey();
key.setKeyParam1(keyParam1);
key.setKeyParam1(keyParam2);

JCS cache = JCS.getInstance("default");
foo = cache.get(key); // Query cache.

if (foo == null) { // Not available in cache right now?
// Get object from normal source.
foo = FooFactory.getFoo(keyParam1, keyParam2);
// Put it in cache to be available next time.
cache.put(key, foo);
}
}
}
Hasta aquí llegamos, terminé en este punto explicando los pasos básicos para configurar el cache, pero hay un montón de opciones más a explorar como auxiliares de cache, la consola de administración, etc. Si tienen alguna pregunta no duden en enviarme un correo o escribir un comentario en este blog.

lunes, 24 de agosto de 2009

Almacenamiento en caché de objetos Java: JCS y la inicialización de las regiones de cache

En este post voy a explicar cómo inicializar las regiones de JCS en el contexto de una aplicación web. Cualquier otro contexto como una aplicación de escritorio debería ser un caso más simple con el cual trabajar. (Para más detalles o documentación oficial es preferible visitar directamente el sition de Jakarta's)

Una vez que se han incluido todos los jars necesarion para correr JCS, necesitamos defininir cómo vamos a cargar las regiones del cache. Una region en JCS es un grupo de configuraciones que son aplicadas a cualquier objeto que guardado bajo esa región de cache. Por ejemplo en una región está definido el tiempo de expiración de un objeto, la cantidad máxima de objetos que se permite guardar, uso de un auxiliar de cache para reducir la carga de memoria, etc.
Podemos definir tantas regiones como requiramos, sin embargo es necesario pensar en un buen diseño de las regiones para establecer una buena categorarización de manera que en el futuro se nos facilite la monotirización de nuestro cache.

Por ejemplo, si definimos solamente una región para nuestra implementación de cache y decidimos guardar 5 clases distintas de objetos bajo una región, un tiempo después cuando estemos monitoreando la “proporción de éxito de cache” (“cache hit rate”), se nos resultará dificultoso determinar cuáles clases están contribuyendo en buen o mal “hit rate”.

En caso de que alguién no esté familiarizado con el concepto de CACHE HIT. Se refiere al evento en el cual hacemos una consulta al cache por un objeto en especifico y resulta que satisfactoriamente el objeto estaba guardado en cache. El caso contrario se le conoce como un “CACHE MISS” o “desacierto de cache”. Es siempre deseable para un buen rendimiento de la aplicación mantener una buena proporción de cache hits. El uso de cache implica un nuevo consumo de recursos como RAM (almacenamiento de objetos) y tiempo de CPU (cálculos para encontrar los objetos en los arreglos del cache) así que si solo vamos a obtener unos cuantos hits, podríamos estar gastando nuestros recursos innecesariamente.

Mi opinion personal, dada la experiencia que he tenifo usando JCS en la aplicación web con la que trabajo, es que es mejor definir las regiones acorde con las clases de objetos usados porque en nuestro caso guardamos objetos de cache provenientes de distintos servicios web. Es mejor saber cuáles clases “cachiables” están contribuyendo al mejoramiento del rendimiento total de la aplicación.

Un colega una vez sugerió que deberíamos definif las regiones acorde con los periodos de vida, por ejemplo:

* ShortLifeRegion
* MediumLifeRegion
* LongLifeRegion
* EternalLifeRegion

Este es otro tipo de categorización y realmente no hay una receta para determinar cuál es la mejor estratefia. Es una decisión técnica que tiene que ser tomada en cada caso particular.

El caso más comun para definir las regiones es trabajar con un archivo de propiedades y cargarlas una única vez.


import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.InvalidPropertiesFormatException;
import java.util.List;
import java.util.Properties;
import org.apache.jcs.JCS;
import org.apache.jcs.access.exception.CacheException;
import org.apache.jcs.engine.behavior.IElementAttributes;
import org.apache.jcs.engine.control.CompositeCacheManager;
import org.apache.jcs.engine.control.event.behavior.IElementEventHandler;

public class CacheManager {

public static void init(String cacheConfigPath) {

InputStream in = null;
CacheInitializerThread initializerThread =
new CacheInitializerThread();
try {
in = new FileInputStream(cacheConfigPath);
Properties prop = new Properties();
prop.load(in);
CompositeCacheManager ccm = CompositeCacheManager
.getUnconfiguredInstance();
ccm.configure(prop);

} catch (FileNotFoundException e) {
logger.logError(e);
} catch (InvalidPropertiesFormatException e) {
logger.logError(e);
} catch (IOException e) {
logger.logError(e);
}
finally {
try {
if (in != null) {
in.close();
in = null;
}
}
catch (Exception e) {}
}
}
...
}

Así que en nuestra aplicación podríamos llamar es métofo init en una clase Plugin.


public class CachePlugin extends Plugin {

private void init(ActionServlet servlet) {
String cacheConfigPath=
servlet.getServletContext().getRealPath(“cache.cff”);
CacheManager.init(cacheConfigPath);

}

}

Las siguientes propiedades definen una región llamada “medium”:

# medium live time refresh objects
jcs.region.medium=
jcs.region.medium.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.region.medium.cacheattributes.MaxObjects=10000
jcs.region.medium.cacheattributes.MemoryCacheName=org.apache.jcs.engine.memory.lru.LRUMemoryCache
jcs.region.medium.cacheattributes.UseMemoryShrinker=true
jcs.region.medium.cacheattributes.ShrinkerIntervalSeconds=300
jcs.region.medium.cacheattributes.MaxMemoryIdleTimeSeconds=18000
jcs.region.medium.cacheattributes.MaxSpoolPerRun=100
jcs.region.medium.elementattributes=org.apache.jcs.engine.ElementAttributes
jcs.region.medium.elementattributes.IsEternal=false
jcs.region.medium.elementattributes.MaxLifeSeconds=18000


Cualquier objeto guardado en esta región expirará en 5 horas (MaxLifeSeconds=18000) y permitirá guardar solamente un máximo de 10000 objetos.

jueves, 30 de julio de 2009

Almacenamiento en caché de objetos Java: Cache vs Pool

Abriendo un paréntesis, algunos pueden confundirse con un concepto similar pero a su vez diferente: Pool de Objetos. Ambos, "cacheo" y "pooleo" son usados para un mismo propósito general, mejorar el rendimiento al reducir el costo de crear ciertos objetos que consumen recursos vitales (tiempo, ancho de banda, memoria, etc).
La diferencia principal es que el pool de objectos no requiere "unicidad" de los objetos que son almacenados. En otras palabras, para cierta necesidad, cualquier objeto guardado en el pool puede hacer el trabajo. De esta manera el pool puede balancear las solicitudes para cierto objeto al mantener varias instancias para devolverlos de manera inmediata. Cache por contraste requiere que un objeto sea mapeable, esto es que pueda ser identificado por una llave única (esta llave puede ser una llave compuesta como veremos después en los siguientes posts). Para este caso, cualquier objeto no puede hacer el trabajo, se necesita un objeto especifico y debemos poder tener la facultad de contruit la llave para el almacenamiento y recuperación del objeto.

Para ejemplificar esto podemos pensar en una aplicación hipotética que trabaje con instancias de una clase llamada "Persona". Supongamos que un proceso de negocio requiere obtener una persona, cualquier persona de manera aleatoria. Debido a que el proceso para construir a la persona requiere de varios llamados a una base de datos, un pool es implementado de manera que cierto número de instancias de la clase Persona son mantenidas in memoria local. La aplicación por tanto haría un llamado similar a esto:



PersonaPool.solicitarPersona();



Ahora si otro proceso requiere obtener una persona en especifico identificable por un ID, requieriendo tambi[en hacer una serie de llamados a la base de datos para la construcci[on del objeto, un diseño de cache seria necesario para evitar el alto costo de realizar todos estos llamados a la base de datos. La aplicación por tanto haría un llamado como este:



PersonaCache.obtenerPersona('125677');



El pool de objetos es una técnica usada ampliamente en aplicaciones que requieren abrir conexiones con una base de datos a traves de objetos de conexión. No obstante debemos tener siempre la mente abierta para aprovechar cualquier buena oportunidad para usarla en nuestras aplicaciones.

martes, 30 de junio de 2009

Almacenamiento en caché de objetos Java: Introducción


Si usted va a una tienda en busca de algo de ropa, digamos un par de pantalones y tres camisetas, probablemente al igual que el resto de nosotros usted hará lo siguiente: camina alrededor, toma algunas piezas que le gusta y las lleva al cambiador. Usted no puede tomar todo el departamento consigo a esta pequeña habitación de modo que tiene que elegir cuidadosamente las prendas que más llaman su atención. Una vez que pruebe algunas piezas, se quedará con algunas y descartará otras, así que tendrá que repetir el proceso el número de veces que sea necesario para conseguir todo lo que anda buscando.

Y ahora, ¿qué ocurre si en lugar de tomar todas las piezas que se permite al cambiador, usted toma sólo una pieza a la vez. Es probable que dure tres o cuatro veces más, sin mencionar la curiosidad de la persona que administra el cambiador. Esta actividad de sentido común: aumentar al máximo la cantidad de ropa al alcance de la mano a la hore de probarse la ropa, es a veces no tan evidente para algunos desarrolladores de software . Tengo que admitir que no era tan evidente para mi tampco.

El concepto es bien concocido no obstante, CACHE. La primera vez que escuché la palabra cache fue cuando tuve mi primer PC. El vendedor muy orgulloso con los tecnisismos de su vocabulario, mencionaba que el micro procesador tenía ~ ~ k bytes de memoria caché. Siendo yo uno adolescente todo eso me sonaba a chino, aunque me dio la sensación de que caché era algo super importante.

Unos pocos años pasaron y aterricé en un curso de Arquitectura de Computadores en la universidad en el que se me explicó la importancia de una buena estrategia de caché para el buen desempeño de un micro-procesador. Incluso tuvimos que programar un emulador de caché así como un administrador de la jerarquía de memoria (cómo gestionar los datos que se asignan a los diferentes niveles de memoria: registros, caché, RAM, disco). Sin embargo, parece que mis neuronas no estaban muy productivas ese semestre porque no hicieron una conexión con mis neuronas de arquitectura de software.

No es como si hubiera un problema de rendimiento antes en mis aplicaciones, sólo fue un tipo de nuevo descubrimiento al averiguar acerca de la existencia de sistemas Java especializados en almacenamiento de objetos en caché En el próximo post voy a seguir describiendo el sistema que he estado usando por un tiempo: Jakarta: JCS (“Java Caching System”) y por qué considero que es una necesidad en aplicaciones distribuidas.