Recomendaciones para Optimizar una Aplicación

A diferencia de la optimización de una infraestructura, optimizar una aplicación consiste en revisar los detalles de implementación y aplicar un montón de pequeños cambios donde generalmente cada uno tiene un pequeño impacto sobre el consumo de recursos y/o tiempo de respuesta. En este artículo no se trata el tema forma genérica, ni es una introducción sobre el proceso de búsqueda y corrección de bugs, para lo anterior es más útil el Taller de Pruebas de Carga y Optimización publicado en este mismo sitio.

Este artículo realmente está compuesto por un listado de recomendaciones específicas que deben ser tenidas en cuenta en el desarrollo de una aplicación. Las recomendaciones están divididas entre recomendaciones genéricas para Java, recomendaciones para JavaServer Faces, recomendaciones para bases de datos, entre otras.


Recomendaciones Genéricas para Java

1. No usar System.out.println()

Recomendación
Evitar el uso de System.out.println(), este es un recurso serializado y requiere el uso de I/O en cada llamado.
Parámetros de configuración
Si eliminar el código del proyecto resulta costoso, puede enviar la salida estándar del servidor a una salida nula (por ejemplo /dev/null en ambientes UNIX).
Síntomas y Excepciones que caracterizan el problema
* Tiempos de respuesta elevados.
Bibliografía de Referencia
* Performance Análisis for Java Web Sites. Capítulo 2.

2. Métodos Transaccionales en EJBs.

Recomendación
En EJBs marcar como transaccionales únicamente los métodos necesarios.
Parámetros de configuración
Modificar esta opción en el archivo ejb-jar.xml de cada EJB.
Por ejemplo:
<container-transaction>
    <method>
        <ejb-name>EJBEjemploTransaccional</ejb-name>
        <method-name>metodoDeConsulta</method-name>
    </method>
    <trans-attribute>NotSupported</trans-attribute>
</container-transaction>
Síntomas y Excepciones que caracterizan el problema
  • Baja reutilización de conexiones JDBC.
  • Tiempos de respuesta elevados ante llamados concurrentes.

3. Sentencias Precompiladas.

Recomendación
Usar al máximo sentencias de base de datos precompiladas a través de la clase PreparedStatement cuando ejecute sentencias SQL. Esto evita que la base de datos compile varias veces la misma consulta y agiliza le ejecución de la sentencia.
Síntomas y Excepciones que caracterizan el problema
* Tiempos de respuesta de base de datos elevados.
  • En el caso de Oracle el mismo motor indica las sentencias que se usan sin precompilar.

4. Paginar Resultados Grandes.

Recomendación
Paginar los resultados extensos (más de 100 registros) de bases de datos.
Parámetros de configuración
Utilice alguna utilidad o limite los resultados de las consultas SQL.
Síntomas y Excepciones que caracterizan el problema
* Consumo de memoria de máquina virtual elevado.
  • Demoras en al cargar una página específica.

5. Evitar el uso de colecciones sincronizadas.

Recomendación
Evite el uso de colecciones sincronizadas como Hashtable o Vector, estas implementaciones tienen todos sus métodos sincronizados lo que implica que generan bloqueos incluso para las operaciones de lectura. En vez de ello utilice implementaciones más sofisticadas como ConcurrentHashMap o CopyOnWriteArrayList que sólo bloquean las operaciones que modifiquen el contenido del objeto.
Parámetros de configuración
A partir de la versión 5 del JDK se incluyen implementaciones para el manejo de la concurrencia, busque las equivalencias para los objetos usados antes de esta versión.
Síntomas y Excepciones que caracterizan el problema
* Incremento de los tiempos de respuesta ante peticiones concurrentes.
Bibliografía de Referencia
* Java Concurrency in Practice, sección 5.2.

6. Maneje Adecuadamente las Conexiones con Bases de Datos.

Recomendación
Las conexiones hacia base de datos son recursos finitos y costosos en términos de tiempos de respuesta; para causar el menor impacto en la aplicación haga caso de las siguientes recomendaciones:
  • Siempre que abra una conexión, hágalo en el momento exacto de su uso y ciérrela de forma incondicional.
  • Cierre la conexión en un bloque finally para que las excepciones no provoquen referencias perdidas.
  • Evite que las conexiones o clases que hagan referencia a conexiones sean atributos de instancia o estáticos.
  • NUNCA pase como referencia el mismo método que devuelve la conexión. Esto conexiones sin referencia e imposibles de recuperar de forma adecuada.
Código de Ejemplo
Manejo adecuado de una conexión:
Connection conn = null;
try {
    // Obténgala dentro del bloque try
    conn = ConnectionFactory.getConnection();
    … //usando la conexión
} catch () { 
     // si es necesario.
} finally { //Cierre siempre la conexión en un bloque de este tipo
   if ((conn != null) && (!conn.isClose)) {
      conn.close();
   }
}

NUNCA pase como referencia el método que devuelve la conexión:

consultarElementos(ConnectionFactory.getConnection()); //NUNCA haga esto
Síntomas y Excepciones que caracterizan el problema
* Aparece contínuamente un mensaje indicando que el servidor de aplicaciones cerró una conexión sin finalizar.
  • El pool de conexiones crece y no reutiliza las conexiones abiertas previamente.
  • Degradación del tiempo de respuesta después de varias horas o incluso minutos de uso de la aplicación.

7. Use Cachés para Evitar Operaciones de Lectura en Disco.

Recomendación
Si repetidamente necesita leer un archivo almacenado en disco, procure usar un caché para cargarlo en memoria y realizar las operaciones de lectura de esta forma. El acceso a disco es una operación costosa y si sólo necesita leer el archivo, el caché es una manera optimizada de hacerlo.
Use cachés manuales como variables estáticas o librerías más avanzadas como Tangosol Coherence.
Síntomas y Excepciones que caracterizan el problema
* Problemas de escalabilidad.
  • Tiempos de respuesta elevados para ciertas operaciones.

8. Lectura de Parámetros desde Un Archivo.

Recomendación
Abrir y cerrar un archivo es una operación sumamente costosa en términos de tiempo, evite ejecutar este tipo de operaciones de forma masiva.
Si la aplicación lee parámetros desde un archivo, asegúrese que este se cargue una única vez en algún mecanismo de caché (puede ser una variable estática) y que sea desde ahí de donde se lean los valores.
Código de Ejemplo
  public CacheConfiguration() {
    initBundle();
  }
 
  private ResourceBundle cacheBundle = null;
  private synchronized void initBundle() {
      if (cacheBundle == null) {
          cacheBundle = ResourceBundle.getBundle(CONFIG_FILE_NAME);
      }
  }
 
  public String getLoaderClass(String cacheName) {
    try {
         return cacheBundle.getString(cacheName.concat(SUFFIX_LOADER));
    }catch (Exception e){
        return null;    
    }
 
  }
 
  public String getOnDemandLoader(String cacheName) {
   try {
    return cacheBundle.getString(cacheName.concat(SUFFIX_ON_DEMAND_LOADER));
   } catch (Exception e) {
    return null;
   }
  }
Síntomas y Excepciones que caracterizan el problema
* Tiempos de respuesta elevados.
  • Incremento exponencial en los tiempos de respuesta para peticiones concurrentes.

9. Verifique el Uso Correcto de Recursos Compartidos.

Recomendación
Cuando use un recurso compartido ya sea a través de un patrón singleton o cargándolo desde algún caché verifique que este recurso no conserve algún estado por especialmente con el uso de atributos clase.

En caso de tener algún conflicto con este recursos cree una copia usando el método clone().

Síntomas y Excepciones que caracterizan el problema
* Errores inesperados ante peticiones concurrentes.
  • Errores del tipo ConcurrentModificationException.


Recomendaciones para Aplicaciones Web

1. Evite al Máximo Almacenar Objetos en Sesión.

Recomendación
Evite al máximo el almacenamiento de objetos en sesión.
La memoria consumida por estos objetos no se libera rápidamente y puede generar OutOfMemoryError o tiempos elevados en recolección de basura.
Parámetros de configuración
* Hacer un uso adecuado de variables de instancia y de la sesión en los Backing Beans.
  • Usar EJBs de sesión stateless.
  • Evitar el uso de EJBs Sateful.
Síntomas y Excepciones que caracterizan el problema
* Patrones de consumo y liberación de memoria que indican fugas.
  • Tiempos de respuesta elevados de forma intermitente.

2. Elimine la Sesión tan Rápido como sea Posible.

Recomendación
Elimine la sesión tan rápido como sea posible para liberar la memoria consumida.
Parámetros de configuración
* En lo posible configure un timeout de sesión corto.
  • Elimínela programáticamente cuando el usuario realice ciertas acciones (presionar un botón para finalizar la sesión, abandonar una página, etc).
Síntomas y Excepciones que caracterizan el problema
* OutOfMemoryError.
  • Poca memoria liberada por el recolector de basura.

3. Optimice el Contenido de las Páginas para Disminuir el Consumo de Ancho de Banda.

Recomendación
* Eliminar los tags ALT, DIR, AccessKey y Rich:tooltip y las etiquetas que no sean absolutamente necesarias en las páginas Web.
  • Marque el contenido estático para usar caché a nivel del navegador.
  • Transporte el contenido usando compresión gzip.
Parámetros de configuración
Asegúrese de usar los encabezados HTTP como se indica continuación:
  • Cache-Control = public, max-age=86400. Defina un valor adecuado para max-age.
  • Expires = 30 días después de la última instalación u otro valor más adecuado.
  • Last-Modified = Fecha de la última instalación.
  • Adicionalmente se debe asegurar que no esté presente el encabezado http “Pragma: no-cache”, éste impide que un recurso sea guardado en el cache.
  • Asegúrese que cuando el navegador solicite un recurso estático, lo haga usando el encabezado If-Modified-Since para que traiga el recurso únicamente si este cambió desde la última actualización local.
Síntomas y Excepciones que caracterizan el problema
* Alto consumo de ancho de banda.
  • Tiempos de respuesta elevados.

4. Use el tag <object> para los applets y configure los parámetros de caché.

Recomendación
El tag <applet> está obsoleto, use <object> para insertar applets en las páginas Web y configure los parámetros para hacer caché de los recursos que no cambian frecuentemente.
Parámetros de configuración
Un ejemplo del uso del tag es:
<OBJECT  classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" 
         width="640" height="500" id="applet">
    <PARAM name="code"                            
value=
   "com.heinsohn.runt.common.impresion.applets.ImpresionReportesApplet.class" 
   />
    <PARAM name="cache_option" value="plugin" />
    <PARAM name="cache_archive" value="../../AppletsImpresionDocumentos.jar,
       ../../lib/jasperreports-2.0.5.jar,
       ../../lib/jasperreports-2.0.5-applet.jar" />
    <PARAM name="cache_version" value="1.0.0.1,1.0.0.0,1.0.0.0" />
    <PARAM name="document"
           value="#{EstadisticaComportamientoVolumenSoatBean.reporte}" />
 <COMMENT>
    <EMBED code=
"com.heinsohn.runt.common.impresion.applets.ImpresionReportesApplet.class"
 width="640" height="500" id="applet" type="application/x-java-applet" 
 cache_option="plugin" 
 cache_archive= "../../AppletsImpresionDocumentos.jar,
                 ../../lib/jasperreports-2.0.5.jar,
                 ../../lib/jasperreports-2.0.5-applet.jar"
 cache_version="1.0.0.1,1.0.0.0,1.0.0.0"
 document="#{EstadisticaComportamientoVolumenSoatBean.reporte}"   
 MAYSCRIPT="true">
    <NOEMBED> 
       Este navegador no tiene soporte para aplicaciones Java. </NOEMBED>  
    </EMBED> 
 </COMMENT> 
</OBJECT>

Note especialmente los parámetros en rojo que indican al manejador de applets que haga caché de los archivos pasados en la opción "cache-archive".

Síntomas y Excepciones que caracterizan el problema
* Alto consumo de ancho de banda.
  • Tiempos elevados en la carga de applets.


Recomendaciones para JavaServer Faces

1. Evite el uso de Bindings en páginas Web.

Recomendación
No use bindings.
Los objetos usados para hacer enlaces entre controles Web y clases de Java son objetos bastante pesados y dado que su uso es extensivo, estos elevan considerablemente el consumo de memoria.
Síntomas y Excepciones que caracterizan el problema
* Consumo excesivo de memoria.
  • OutOfMemoryError.

2. Optimice el Consumo de Recursos de JavaServer Faces a través del Archivo web.xml.

Recomendación
La optimización de JavaServer Faces a través de web.xml puede mejorar el consumo de ancho de banda y de memoria.
Personalice los siguientes parámetros:
  • javax.faces.STATE_SAVING_METHOD.
  • org.apache.myfaces.COMPRESS_STATE_IN_SESSION.
  • org.apache.myfaces.SERIALIZE_STATE_IN_SESSION.
  • org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION.
  • facelets.DEVELOPMENT.
Síntomas y Excepciones que caracterizan el problema
* Alto consumo de ancho de banda.
  • Tiempos de respuesta elevados.
Referencias
* http://www.waltercedric.com/java-j2ee-mainmenu-53/283-java-server-faces/1215-recommended-webxml-for-jsf.html


Recomendaciones para Bases de Datos Oracle

1. Secuencias con Caché.

Recomendación
Crear la secuencias con caché por defecto.
Parámetros de configuración
En la creación de la secuencia utilizar un caché con el número de peticiones concurrentes esperados. Usar la instrucción:
create sequence <NOMBRE_SECUENCIA> incremental by 1 start with 1 noclycle cache <CACHE>.

Dónde CACHE debe ser un valor aproximado al número de usuarios concurrentes esperados usando la secuencia.

Síntomas y Excepciones que caracterizan el problema
* Tiempos de respuesta elevados cuando aumenta el número de usuarios concurrentes.

2. Evite el Uso de LIKE.

Recomendación
Las consultas que se filtran con el operador LIKE pueden tener tiempos de respuestas muy elevados en tablas medianas/grandes. Evite el uso de este operador, procure usar el igual (=) y en cuanto sea posible use índices para las búsquedas en campos de texto.
Síntomas y Excepciones que caracterizan el problema
Tiempos de respuesta elevados en consultas a la base de datos.

3. Evite el Uso de DISTINCT.

Recomendación
Las consultas que usan el operador DISTINCT para eliminar resultados duplicados son muy costosas en términos de tiempo y CPU para grupos grandes de datos. Evite este operador en lo posible y procure mejor realizar filtros y joins entre tablas de forma que las consultas no devuelvan valores duplicados.
Síntomas y Excepciones que caracterizan el problema
* Tiempos de respuesta elevados en consultas a la base de datos.

4. Cree Índices con los Campos Usados como Filtros en los y con los Campos usados para Joins entre Tablas.

Recomendación
Los índices mejoran considerablemente el desempeño de las sentencias SQL. Cree índices para los campos que se usen como filtros en las condiciones WHERE y créelos también para los campos que se usan en los join entre tablas.
Cuando no esté seguro si una consulta está usando un índice use la sentencia set autotrace traceonly explain para hacer un análisis detallado. En SQL Plus únicamente ejecute la consulta y verá el plan de ejecución, en SQL Developer ingrese la consulta y presione F10 para hacer el análisis respectivo.
Síntomas y Excepciones que caracterizan el problema
* Tiempos de respuesta elevados en consultas a base de datos.
  • Sentencias que se ejecutan si usar índices.

5. Limitar Valores para la Condición IN.

Recomendación
Al usar una condición del tipo IN (a, b, c, …) tenga cuidado en no sobrepasar los mil valores pasados como parámetros a la condición porque el motor devolverá un mensaje de error. Esto es especialmente útil cuando los valores que se usarán como parámetros en la condición provienen de una consulta realizada previamente.
Síntomas y Excepciones que caracterizan el problema
* Error: "ORA-01795: el número máximo de expresiones en una lista es 1000".
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License