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.

viernes, 14 de agosto de 2009

Muestra tu progreso a la hora de leer tus archivos

Perl nos permite muchas facilidades para procesar archivos y precisamente por eso lo he usado para examinarciertos logs de servidor cuando necesito saber
la causa de un problema. En una ocasión tuve que procesar varios cientos de megas de un solo archivo y dado que usaba varias expresiones regulares, tardaba un buen rato en terminar de leerlo todo.
Así que tuve la necesidad de saber cuánto llevaba procesado por el momento del archivo porque en ocasiones pensaba que se había quedado pegado el script, así que me puse la tarea de investigar como podía hacer esto y aquí dejo mi solución por si a alguien le sirve.


#!/usr/local/bin/perl

my $dir = $ARGV[0];

print "\n\n\t\tReading directory: " . $dir . " ...\n\n";
opendir(DIR, $dir);
my @files = readdir(DIR);
closedir(DIR);

foreach $file( @files){
unless ( ($file eq ".") || ($file eq "..") ) {
my $total_size_read = 0;
my $file_path = $dir . '\\' . $file;
my $filesize;
{
use strict;
use warnings;
$filesize = -s $file_path ;
$filesize = $filesize / 1000; #Kbytes
}

print "\nReading file " .$file_path . "(" . $filesize . " kb)\n";
open (CURRENT_FILE, $file_path) || die "couldn't open the file!";

while ($record = ) {

$line_number++;
if ($line_number % 100 eq 0) {
my $progress = sprintf("%.2f",(($total_size_read /1000) / $filesize) * 100);
print "\rTotal lines read: " . $line_number . " | Progress:" . $progress . "%";

}
{
use bytes;
my $byte_size = length($record);
$total_size_read = $total_size_read + $byte_size;
}
}

close(CURRENT_FILE);
}
}