jueves, 2 de septiembre de 2010

Arquitectura ligera y uso de cachés

Después de explicar un poco la arquitectura ligera que estoy empleando en un proyecto y dado que estoy usando una caché, tema sobre el cual ya desvarié un poco, he pensado que también podría ser interesante explicar cómo estoy usando la caché en este proyecto, basándome en las reflexiones del mensaje sobre uso de cachés.

Como ya expliqué, el proyecto consiste en mostrar los resultados de las partidas de un juego online, quien participó, cómo lo hizo cada piloto y estadísticas recopilatorias sobre el historial de cada piloto. Respondamos entonces a las preguntas clave:

¿Merecía la pena introducir una caché? La respuesta en este caso es que claramente sí, dado que vamos a mostrar resultados acumulados de varias tablas, especialmente en el caso de las estadísticas históricas. Incluso la “simple” lista de partidas jugadas es una consulta que cruza 4 tablas ( no por que esté mal el esquema de BDD si no por que ya muestra datos acumulados) y las primeras pruebas ya indican un incremento en el rendimiento de x30 en las consultas más simples.

¿En que capa he introducido la caché? Dejar que la BDD se encargue de ello implicaría que todavía tendríamos que viajar por la red hasta la BDD. La cache de MyBatis no es algo con lo que he experimentado y no se que control me da (creo que no suficiente por lo que he leído en las listas de distribución), así que seguimos subiendo. El control de caché del framework ligero lo conozco bien y se que control me da: más que suficiente. Podría intentar subir aun más y usar la caché a nivel de filtro de servlets en la salida final (HTML o JSON), pero dado que más tarde introduciremos personalizaciones para que los usuarios vean sus propias páginas y eso implica que puede que la salida final de cada usuario sea ligeramente diferente, me quedo justo en el nivel de abajo. De todas formas, el salto gordo en el rendimiento se produce en este caso, como en muchos, en la consulta a la BDD y la creación de los objetos correspondientes en el servidor, así que haciendo caché de eso ya obtengo un gran beneficio.

¿Como vamos a mantener actualizada la caché? En la aplicación hay 3 tipos de páginas, clasificándolas en relación de dependencia con datos actualizables:

  • Grupo 1: Las que dependen de todas las partidas jugadas: Lista de partidas, estadísticas globales, clasificaciones de todos los jugadores, etc.

  • Grupo 2: Las que dependen de un piloto en particular: Estadísticas generales de cada piloto, detalles sobre los elementos utilizados por el piloto en sus partidas...

  • Grupo 3: Las que no dependen de nada: Datos de una partida y detalles de participación de cada piloto en esa partida.
Una vez sabemos esto, podemos ver que cada vez que haya una o más partidas nuevas, tenemos que invalidar todos los elementos de la caché del grupo 1 e invalidar los elementos del grupo 2 de los pilotos que hayan participado en las partidas nuevas. Los elementos del grupo 3 pueden estar en caché “indefinidamente”. Para llevar a cabo esta tarea lo que hemos hecho es implementar una tarea periódica con Quartz que a intervalos regulares comprueba si ha habido partidas nuevas y en caso afirmativo, procede a marcar como caducados los elementos de la caché según las reglas definidas.

¿Cual es el perfil de nuestra aplicación? Se presupone una aplicación donde habrá muchas más lecturas que actualizaciones, y donde las actualizaciones de datos no son críticas en absoluto. Por tanto el tipo de funcionamiento definido cuadra perfectamente.

Puntos extra: Para controlar mejor el uso que se hace de la caché y verificar que todo funciona correctamente, tenemos varias ayudas:

  • Por un lado, el framework que utilizamos de caché nos puede decir en cada momento el grado de utilización que le estamos dando (Hit/miss ratio) por lo que podemos saber si realmente se está leyendo mucho más de lo que se está actualizando la cache.

  • Por otro lado, podemos controlar la periodicidad de la tarea de control de la cache y si vemos que la el grado de utilización es bajo y la caché no se usa lo suficiente, podemos aumentar el período y así aumentar el grado de utilización, protegiendo a la vez la BDD de una carga excesiva.

  • Ambos elementos se pueden retocar en tiempo de ejecución sin necesidad de reiniciar la aplicación, lo cual nos permite una gestión muy cómoda sin miedo a molestar a los usuarios.
Espero que este ejemplo sirva para ilustrar lo que quería decir en el mensaje de reflexiones sobre el uso de cachés, y si os da algunas ideas para vuestras aplicaciones, mejor que mejor.

Happy coding! EJ

2 comentarios:

  1. Hace muchos años en un libro de oreilly vi una cache utilizando el Last-Modified para algunos tipos de paginas como planteas es eficiente, ademas montaban un filtro de servlet para pregenerar la pagina.

    ResponderEliminar
  2. Aha, esa es otra de las opciones que se planteó, pero la cuestión es que si usas el Last-Modified y el filtro de servlet, dependes de que nada, absolutamente nada, en la página puede cambiar para poder reutilizarla. Un detalle "tonto" como poner el nombre del usuario de quien accede, como vamos a hacer nosotros, y te fastidia el chiringuito ya que tendrás una copia en cache por cada usuario.
    Es decir, sí es una técnica muy útil, yo la he usado y de hecho OSCache ya te viene con ese filtro implementado, sólo tienes que configurar unas clases para asignar ids etc., pero se tienen que dar unas circunstancias muy concretas para que te sirva.

    ResponderEliminar