Mostrando entradas con la etiqueta Java. Mostrar todas las entradas
Mostrando entradas con la etiqueta Java. Mostrar todas las entradas

viernes, 22 de febrero de 2013

Sesiones, Tomcat y consumo de memoria

Dado que en nuestro caso, además de desarrollar las aplicaciones, gestionamos los servidores de aplicaciones (la parte software), de vez en cuando hago revisiones periódicas del consumo de memoria, versiones de librerías… y en la última revisión encontré algunas cosas interesantes, pero una me llamó bastante la atención:
El consumidor #1 de memoria de los servidores de aplicaciones era… ¡El gestor de sesiones del Tomcat! Aunque parezca una perogrullada, el motivo de mi sorpresa es que nosotros no guardamos apenas datos en sesión, y en muchos casos ni siquiera usamos la sesión para nada puesto que muchas de nuestras aplicaciones son simplemente de consulta.
Así que revisando, revisando, encontré que el problema venía dado por varios factores:

  • Aunque nosotros no usáramos la sesión para nada, algunas llamadas nuestras para comprobar si existía un atributo en la sesión o no, causaban que se crease la sesión sí o sí. Así que un par de if(request.getSession().getAttribute(…)) que se había colado se cambiaron por if(request.getSession(false)!=null && …) para evitar crear sesiones innecesarias.
  • Debido a que ahora tenemos un cluster con “session failover” aunque a la sesión no le metas nada, ocupa un cierto tamaño que al multiplicarse en número empieza a ser significativo. Debido al mismo factor, las sesiones ocupan espacio en ambos nodos del cluster y no se con “fácilmente recoletables”.
  • Dado que las sesiones son creadas “sin querer”, nadie las cierra y por tanto caducan solas agotando el tiempo máximo de vida sin actividad (session-timeout). Así que durante ese tiempo ocupan espacio en ambos servidores del cluster, inútilmente y encima no se pueden recolectar (GC). 
  • Dado que nuestras aplicaciones son públicas, los buscadores las recorren a menudo y en algunos casos sin reutilizar las cookies entre peticiones, así que nos crean una sesión por petición. 
Afortunadamente, para lo bueno y lo malo usamos nuestro propio framework así que introducir cambios para evitar al máximo la creación de sesiones no fue nada complicado. Así mismo, configurar por web.xml que el time-out de las sesiones es de 1 minuto, por si alguna se escapa, y poner un filtro para las aplicaciones solo públicas que si se crea alguna sesión la cierre al acabar cada petición tampoco fue muy complicado (esto último es por qué algunas librerías usadas en algunas aplicaciones pueden crearte sesiones sin que puedas hacer mucho por evitarlo, o si usas JSP o alguna otra tecnología que las cree alegremente).
El resultado final, probado en un nodo antes que en otro, fue que para el mismo tráfico y uso, todo funcionaba igual pero con 1/3 menos de consumo de memoria. Además, ahora un pico de tráfico público, más difícil de controlar, no nos daría tantos problemas mientras las sesiones están esperando a expirar, como pasaba antes, ya que si se crean, el GC las puede liquidar sin problemas.

La moraleja es que aunque creas que no estas usando sesiones, puede que en realidad sí lo estés haciendo y te estén afectando más de lo que creas.

¡Un saludo y happy coding!
E.J.

viernes, 14 de septiembre de 2012

El problema de N+1 consultas: explicación, caso real y solución propuesta


La entrada de hoy trata sobre un problema recurrente al acceder a bases de datos y especialmente al usar abstracciones por encima, como JPA o Hibernate, sin leer en detalle el funcionamiento interno de estas herramientas.

El problema se da al querer acceder a un objeto y sus N objetos relacionados, normalmente a través de claves foráneas, ya que la estrategia simple implica hacer una consulta para obtener el objeto principal y las claves de los objetos relacionados, y luego hacer N consultas, cada una para obtener un objeto relacionado, usando las claves obtenidas en la primera consulta. De ahí el nombre “N+1 consultas”. La trampa de este problema está en que con pocos datos o en ejemplos simples, el deterioro del rendimiento puede no ser significativo, pero cuando el número de objetos relacionados crece y los objetos relacionados están relacionados a su vez con otros objetos, que a su vez... En fin, la cosa crece exponencialmente y es la causa principal de que la gente se queje de que los ORM son lentos, ya que por defecto éste es su comportamiento y la gente no suele ir más allá.

Para ilustrar el problema usaremos un caso real con el que tuve que lidiar hace relativamente poco: mostrar en detalle los exámenes de un plan de estudios (sí, ya sé que suena a problema de examen de la carrera :) ). La cuestión es que eso que a principio suena tan simple se transforma en:
  • Los planes de estudios se ofrecen en varios campus (es lo que tiene una universidad con varios campus).
  • Para cada plan/campus, hay unas asignaturas que se ofrecen.
  • Cada asignatura, se divide a su vez en grupos-asignatura (por turnos, por letras, por plan ya que una asignatura puede ofrecerse en varios planes).
  • Para cada grupo-asignatura, se definen una serie de exámenes con sus datos.
  • Además, cada examen puede tener lugar en varias localizaciones (normalmente aulas de un mismo edificio).
  • Para componer más la cosa, los grupos-asignatura se juntan en entidades llamadas grupo-horario para poder distribuir los alumnos sin problemas de forma ordenada sin que haya solapamientos de horarios/exámenes etc.
  • Todo esto, filtrado por año académico.
Si queremos obtener estos datos para poder mostrarlos y lo hacemos “al tun-tun”, lo que ocurre es que, partiendo del código de plan de estudios, campus y año académico:
  • Obtenemos los datos del plan de estudios a partir de su código.
  • Obtenemos los datos del campus a partir su código.
  • Obtenemos la lista de códigos de los grupos-horario que se ofrecen para ese año académico filtrando por plan, campus y año.
  • Obtenemos los datos del grupo-horario a partir de su código.
  • Obtenemos los códigos de los grupos-asignatura de cada grupo-horario según el código de grupo-horario y el año académico.
  • Obtenemos los datos del grupo-asignatura por código
  • Obtenemos los datos de la asignatura por la clave foránea que tiene grupo-asignatura.
  • Obtenemos los códigos de los exámenes de cada grupo-asignatura.
  • Obtenemos los datos de cada examen por código
  • Obtenemos los códigos de las localizaciones de cada examen.
  • Obtenemos los datos de cada localización por código.

Cada una de esas lineas implica una consulta de 1 o N elementos, con el retardo extra que ello implica. Y eso si solo nos traemos los elementos necesarios, si usamos un  ORM y no tenemos cuidado con las relaciones que tenemos marcadas como “eager fetching”, podemos hacer muchas más consultas y traernos además multitud de objetos que, encima, no vamos a usar. La solución eficiente en cuanto a minimizar las consultas es, en este caso, sencilla: podemos usar una sola consulta para obtener todos esos datos. Una señora consulta, sí, y con datos repetidos, también, pero en un único viaje por la red, que en muchas veces será lo que más nos importe. La consulta, sin entrar en detalle, sería algo así

  • Plan de estudios
  • ∟ Datos del plan de estudios
  • ∟ Datos del campus
  • ∟ Lista de grupos-horario
    • ∟ Datos de grupo-horario
    • ∟ Lista de grupos-asignatura
      • ∟ Datos de grupo-asignatura
      • ∟ Datos de asignatura
      • ∟ Lista de exámenes
        • ∟ Datos de examen
        • ∟ Lista de localizaciones
          • ∟ Datos de localización
Ahora bien, si tenemos los datos de plan de estudio y campus repetidos en cada fila del resultado, los de cada grupo-horario para n filas etc. ¿Cómo hacemos para reconstruir el árbol de objetos sin que sea una pesadilla? Ahí es donde entra en marcha algo como Ibatis/MyBatis, que no sólo nos permite definir libremente las consultas si no que se encarga de “separar” los datos adecuadamente una vez se lo indiquemos en el fichero de “mapeo”. En este caso, al definir la consulta le diríamos que el resultado es un objeto de la clase PlanEstudios que contiene los datos del plan, y una referencia a un objeto de la clase Campus, que contendrá los datos del campus. Además, la clase PlanEstudios contiene una lista de objetos GrupoHorario, que se puede discriminar con la columna correspondiente al código de grupo-horario, el cual contiene los datos del mismo y una lista de objetos GrupoAsignatura el cual...

Una vez hecho así, podemos ejecutar la consulta y ésta, suponiendo que los datos estén bien, devolverá un único objeto de la clase PlanEstudios, con la listade GrupoHorario rellenada, y para cada GrupoHorario sus datos rellenados y sus listas de GruposAsignatura rellenadas etc. Todo el árbol de objetos rellenado y con los datos en su lugar con una sola consulta, no sólo a nivel SQL si no también a nivel de lógica, lo cual es una ventaja importante.

Obviamente, todo tiene su precio y en este caso el precio es introducir el framework Ibatis/MyBatis, y definir el mapeo de las columnas a los campos de los objetos, pero el beneficio es claro en cuanto a claridad en el código y rendimiento. Por otro lado, mencionar que no siempre es posible convertir todas las consultas en una (por ejemplo cuando un objeto está relacionado con dos listas de objetos independientes), pero en este caso lo que se puede hacer es definir la consulta de forma que obtenga los mayores datos posibles de la forma más eficiente minimizando las, en este caso inevitables, “consultas N”.

Cabe también mencionar que las herramientas ORM decentes incluyen opciones (véase la especificación del tipo de fetch y la utilización de “inner join” en  JPA) para poder controlar este tipo de cosas y, si bien de forma más limitada, permiten evitar en algunos casos el problema de las N+1 consultas y/o de la carga de objetos innecesaria. Así que antes de culpar a la herramienta, hay que asegurarse de que una la está usando correctamente.

No incluyo en esta entrada detalles de implementación, para no hacerla aun más extensa, pero espero que sirva para ilustrar un problema que existe y os encontrareis en el mundo real, y una forma de solucionarlo. Si el tema interesa, se podría hacer otra entrada con detalles de ese tipo, pero esa es otra historia ;).


Happy coding! EJ

sábado, 20 de marzo de 2010

Ordenando listas de POJOs en Java por distintos criterios

Hoy un punto breve y simple para aquellos que siempre tienen dudas sobre como ordenar las colecciones que típicamente nos devuelven frameworks como Hibernate, JPA etc. Aunque muchas veces es mejor dejar que la BDD nos ordene directamente los registros en la consulta, a veces no es posible o por flexibilidad podemos desear hacerlo en el código.

Dicho esto, pasemos al código de ejemplo:

package test;

import java.text.Collator;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

public class App
{
  static class Item
  {
    private int codigo;
    private String nombre;

    public static Comparator COMPARADOR_POR_NOMBRE = new Comparator()
    {
      private Collator theC = Collator.getInstance();

      @Override
      public int compare(Object o1, Object o2)
      {
        Item item1 = (Item) o1;
        Item item2 = (Item) o2;
        return theC.compare(item1.getNombre(), item2.getNombre());
      }
    };

    public static Comparator COMPARADOR_POR_CODIGO = new Comparator()
    {

      @Override
      public int compare(Object o1, Object o2)
      {
        Item item1 = (Item) o1;
        Item item2 = (Item) o2;
        return item1.getCodigo() - item2.getCodigo();
      }
    };

    public Item(int codigo, String nombre)
    {
      this.codigo = codigo;
      this.nombre = nombre;
    }

    public int getCodigo()
    {
      return codigo;
    }

    public String getNombre()
    {
      return nombre;
    }

    @Override
    public String toString()
    {
      return this.codigo + ": " + this.nombre;
    }

    @Override
    public int hashCode()
    {
      final int prime = 31;
      int result = 1;
      result = prime * result + codigo;
      return result;
    }

    @Override
    public boolean equals(Object obj)
    {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (!(obj instanceof Item))
        return false;
      Item other = (Item) obj;
      if (codigo != other.codigo)
        return false;
      return true;
    }

  }

  public static void main(String[] args) throws InterruptedException
  {
    List listaDeItems = new LinkedList();
    listaDeItems.add(new Item(2, "Pedro"));
    listaDeItems.add(new Item(1, "Juan"));
    listaDeItems.add(new Item(3, "María"));
    listaDeItems.add(new Item(0, "Paco"));
    System.err.println("Lista tal cual\n-----------------");
    for (Item unItem : listaDeItems)
    {
      System.err.println(".- " + unItem);
    }
    Collections.sort(listaDeItems
                     ,Item.COMPARADOR_POR_NOMBRE);
    System.err.println("Lista ordenada por nombre\n-----------------");
    for (Item unItem : listaDeItems)
    {
      System.err.println(".- " + unItem);
    }
    Collections.sort(listaDeItems
                     ,Item.COMPARADOR_POR_CODIGO);
    System.err.println("Lista ordenada por codigo\n-----------------");
    for (Item unItem : listaDeItems)
    {
      System.err.println(".- " + unItem);
    }
  }
}

Y la respuesta al ejecutar la aplicación sería:

Lista tal cual
-----------------
.- 2: Pedro
.- 1: Juan
.- 3: María
.- 0: Paco
Lista ordenada por nombre
-----------------
.- 1: Juan
.- 3: María
.- 0: Paco
.- 2: Pedro
Lista ordenada por codigo
-----------------
.- 0: Paco
.- 1: Juan
.- 2: Pedro
.- 3: María

El código ya es suficientemente explicativo, y únicamente resaltar el cuidado que hay que tener al implementar los comparadores ya que han de ser coherentes con el método equals, que no hay que olvidar implementar al igual que el método hashCode. Así que si según equals, dos objetos son iguales, el comparador ha de devolver 0. Ojo que esto significa que si esos dos objetos los metemos en una estructura que no permita repetidos, como por ejemplo ordenándolos a través de un SortedSet ,no importa lo que valgan el resto de campos que no se usan en el equals/compare, el objeto anterior desaparecerá. En este caso como usamos una lista durante todo el proceso, podemos meter objetos “repetidos” que no desaparecerán.

¡Felices ordenaciones!

EJ
Happy Coding!

viernes, 19 de marzo de 2010

Gestión simple de módulos y dependencias con java.util.ServiceLoader

En la entrada de hoy vamos a tratar un problema un poco más complejo, y por lo tanto la solución requiere también un poco más de trabajo, pero el resultado final merece la pena.

En este caso el enunciado del problema sería algo tal que así:
  • Supongamos un proceso que puede tener varias implementaciones diferentes y queremos permitir añadir diferentes versiones y permitir escoger una implementación simplemente a través de una cadena de configuración.
La solución que proponemos usa los mecanismos proporcionados en el API básico de Java (se necesita Java 6), sin acudir a soluciones más complejas como OsGI. En caso de necesitar poder actualizar/cambiar las dependencias en tiempo de ejecución y/o tener que poder permitir varias versiones de la misma dependencia a la vez en una misma JVM, sí deberíamos usar algo más complejo, pero si no es necesario, quizá no nos merezca la pena entrar en soluciones tan complejas.

Volviendo a nuestra solución propuesta, la idea es utilizar la clase java.util.ServiceLoader que nos permite declarar y recorrer las clases que implementan una Interfaz dada, pudiendo añadir nuevas implementaciones en el classpath simplemente empaquetando las clases adecuadamente.

El primer punto a tener en cuenta es que podríamos cargar con el ServiceLoader directamente las implementaciones del proceso, sin embargo esta estrategia presenta un problema. ServiceLoader instancia las clases al recorrerlas buscando las implementaciones posibles, así que tenemos que tener toda las dependencias de todas las implementaciones posibles en el classpath o tendremos problemas. En algunos casos eso puede no ser problemático, pero imaginemos que tenemos varias soluciones y que unas usan Hibernate, otras JPA, otras JDO... al cargarlas todas tendríamos que tener todas las dependencias en el classpath, aunque sólo fuéramos a usar JDO, por ejemplo.

Así pues, lo que haremos será que la clase cargada a través de ServiceLoader sea únicamente una utilidad que nos diga el nombre (una cadena) de clase que realmente implemente el proceso. De esta forma podemos cargar la clase utilidad tranquilamente en el classpath sin necesitar cargar todas las dependencias de la implementación.
En la implementación necesitaremos:
  • La interfaz a implementar por todas las clases utilidad. Para hacerlo más flexible permitiremos que una sola clase de utilidad pueda especificar varias implementaciones, cada una con su “clave”:
package my.pckg;

public interface ProcessImplementationRegistration
{
  /*
   Las implementaciones han de devolver una lista de pares clave/implementacion del proceso.
   Por ejemplo: {{“jpa”,”com.my.JPAImplementation”},
                 {“jdo”,”com.my.JDOImplementation”}}
  */
  public String[][] getProcessImplementations();
}
  • En la clase que va a utilizar las implementaciones, cargar las clases que utilizando el mecanismo de la clase java.util.ServiceLoader, y registrar las implementaciones según su clave:
ServiceLoader theServiceLoader = ServiceLoader.load(ProcessImplementationRegistration.class);
    for(ProcessImplementationRegistration pirInstance: theServiceLoader)
    {
      // Usar pirInstance.getProcessImplementations() para registrar
      // las implementaciones por clave
      ...
    }
  • Una vez hecho esto, para añadir una implementación simplemente tenemos que crear la clase que implemente la interfaz my.pckg.ProcessImplementationRegistration, empaquetarla en un jar y añadir en el directorio META-INF del jar, un fichero llamado my.pckg.ProcessImplementationRegistration donde incluiremos el nombre de la clase que implemente la interfaz.
Por ejemplo, clase que implementará la interfaz será:

package com.my;

public class MyJPARegistration implements ProcessImplementationRegistration
{
  /*
   Devolvemos la implementacion que registra este .jar
  */
  @Override
  public String[][] getProcessImplementations()
  {
   return new String[][]{{“jpa”,”com.my.JPAImplementation”}}
  }
}

El fichero META-INF/my.pckg.ProcessImplementationRegistration contendrá únicamente la linea:
com.my.MyJPARegistration
y obviamente en el .jar incluiremos también la clase com.my.JPAImplementation y sus clases auxiliares necesarias si las hubiera.

Colocamos el .jar en el classpath, reiniciamos la maquina virtual Java y voilà: nuestra implementación será descubierta y clasificada según la clave “jpa”. Además, si no la usamos no hará falta que tengamos las clases de JPA en el classpath, por lo que podemos tener los .jar de registro de todas nuestras opciones y sólo necesitaremos tener en el classpath las dependencias de las que realmente usemos.

Este es un mecanismo que utilizan algunos frameworks para incluir plugins opcionales de forma sencilla, basta incluir el fichero .jar en el classpath, sin necesitar incluir las dependencias a no ser que realmente los utilicemos.

Eso es todo. No es una implementación completa pero espero haberos provisto de todos los detalles para que podáis experimentar vosotros mismos. Sólo hace falta tener Java 6, así que animaros a hacer alguna prueba.

Un saludo y hasta la próxima.