Mejora el rendimiento de tu aplicación web usando compresión

Una de las características que tienen los navegadores de hoy en día, es poder aceptar contenido comprimido con gzip o con zip. Esto supone una enorme reducción en el peso de nuestras páginas, ya que el código HTML tiene muchas repeticiones en su contenido. Estamos hablando que en general (sin contar imágenes) nuestras páginas y código javaScript obtienen una reducción de mucho más del 50% (estamos hablando de alrededor de un 70%). Eso se traduce directamente en que nuestras páginas cargan más rápido.

Ya los grandes como Google o Yahoo están utilizando esta capacidad para mejorar el rendimiento de sus sitios. Ahora, ¿Por qué los servidores web (Apache, tomcat, Jboss, etc) no tienen esa opción habilitada por omisión? La razón es que los browsers antiguos contenían errores y soporte muy precario para el contenido comprimido.

Yendo al grano. ¿Cómo habilitar la compresión?

Tomcat / JBoss:
  • Abrir el archivo server.xml, localizar los conectores:

  • <connector port="8080" maxhttpheadersize="8192" maxthreads="150" minsparethreads="25" maxsparethreads="75" enablelookups="false" redirectport="8443" acceptcount="100" connectiontimeout="20000" disableuploadtimeout="true" emptysessionpath="true" >
  • Agregar los siguientes atributos:
    • compression = "on"
    • compressableMimeType = "text/html,text/css,text/javascript,text/xml"


    Estos atributos le dicen al tomcat que comprima todo el contenido de los tipos especificados.

Se deben colocar en todos los conectores, para así habilitar la compresión para SSL y las conexiones que vienen a través de un servidor Apache o IIS.

Si estamos sirviendo contenido para internet, lo más probable es que tengamos un Apache que sirve el contenido estático y le pase a tomcat/jboss el control para procesar el contenido dinámico. En este caso debemos también configurar Apache para que sirva el contenido estático comprimido.

Otra de las razones por las que no se habilita por omisión la compresión, es por el impacto en rendimiento que puede causar en los servidores. Con los servidores que hay ahorita pienso que es seguro habilitarla. Sin embargo, no está demás considerarlo y hacer las pruebas respectivas. Existe la manera de utilizar contenido precomprimido, pero eso aún no lo he investigado. Cuando lo tenga claro escribo otro post.

Si se tiene sólo al tomcat/jboss atendiendo las peticiones, recomiendo habilitar las librerías nativas APR. Las instrucciones están en otro post (Librerías nativas Tomcat 5.5 y 6.0).

Apache:

Hay dos formas equivalentes, o se crean archivos .htaccess o se edita el archivo httpd.conf, /etc/apache2/default-server.conf o sus equivalentes. Pero el contenido que se coloca es el mismo:

# compress all text & html:
AddOutputFilterByType DEFLATE text/html text/css text/xml text/javascript application/xhtml+xml application/x-javascript text/x-js

Esto se pone tal cual en los .htaccess y dentro de los tags <Directory> en los otros archivos como el httpd.conf o el /etc/apache2/default-server.conf. Esta instrucción le dice al servidor que todos los tipos de archivo ahí especificados los mande comprimidos. Ejemplo:

<Directory "/srv/www/htdocs">
# Possible values for the Options directive are "None", "All",
# or any combination of:
# Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
#
# Note that "MultiViews" must be named *explicitly* --- "Options All"
# doesn't give it to you.
#
# The Options directive is both complicated and important. Please see
# http://httpd.apache.org/docs-2.2/mod/core.html#options
# for more information.
Options All
# AllowOverride controls what directives may be placed in .htaccess files.
# It can be "All", "None", or any combination of the keywords:
# Options FileInfo AuthConfig Limit
AllowOverride None
# Controls who can get stuff from this server.
Order allow,deny
Allow from all


AddOutputFilterByType DEFLATE text/html text/css text/xml text/javascript application/xhtml+xml application/x-javascript text/x-js

</Directory>

Hay que asegurarse que el módulo mod_deflate está instalado. Eso en Linux se verifica en la variable de /etc/sysconfig/apache2 APACHE_MODULES. Ejemplo:
APACHE_MODULES="unique_id  ... mod_jk jk mod_deflate"
¿Alguna pregunta? Bienvenida

Hasta la próxima...

Nota: Si esta información te es de utilidad o piensas que se puede mejorar, por favor deja un comentario con tus observaciones.

Java 1.5 Usar autoboxing o métodos valueOf para mejorar el rendimiento (Ventajas)

Java 1.5 vino con varias mejoras en el lenguaje. Entre ellas autoboxing y autounboxing. ¿Y para que sirve? si tienes que usar un objeto "wrapper" para meter enteros en una lista por ejemplo, en vez de usar new Integer(numero), usas directamente número. Entonces imaginemos que queremos meter 3 números en una lista, en Java 1.4 se haría así:

List lista = new ArrayList();

lista.add( new Integer( 1));
lista.add( new Integer( 2));
lista.add( new Integer( 3));

En Java 1.5 se haría así:

List <Integer> lista = new ArrayList<Integer>();

lista.add( 1);
lista.add( 2);
lista.add( 3);

El compilador se encarga de crear los objetos wrapper por uno.

Esta característica pareciera sólo cosmética, es decir, que sólo serviría para poner el código más bonito. Pero resulta que por dentro tiene una optimización.

Las clases wrapper en Java 1.5 ahora tienen unos métodos llamados valueOf. Que simplemente reciben un número y devuelven la clase wrapper correspondiente. Bueno resulta que cuando java hace autoboxing no usa los constructores (new Integer) sino los métodos valueOf. La ventaja de estos métodos es que guardan un caché de instancias de números más usados (-127..128). en general rara vez los números pasan de ese rengo. Como las clases wrapper son inmutables no importa tener la misma instancia para todos los Integer que representen el número 1. Así en vez de tener innumerables instancias de Integer que embasuren la memoria y enlentezcan el Garbage Collector(ya que en su mayoría son objetos que tienen muy corta vida) y que además todos representen el mismo número, se tenga una única instancia por cada número distinto.

En resumen, usar el autoboxing no sólo hace más sencillo el código, sino que además es más eficiente en cuanto al consumo de memoria se refiere.

Si no se quiere usar el autoboxing porque puede ser confuso, al menos deberían usarse los métodos valueOf. El código anterior quedaría así:

List <Integer> lista = new ArrayList<Integer>();

lista.add( Integer.valueOf( 1));
lista.add( Integer.valueOf( 2));
lista.add( Integer.valueOf( 3));
Hasta la próxima...

Nota: Si esta información te es de utilidad o piensas que se puede mejorar, por favor deja un comentario con tus observaciones.

Acelerar la compilación de GWT durante la fase de desarrollo

GWT es una tecnología de google que permite escribir páginas web como si fueran aplicaciones desktop en Java. Para lograr ésto, GWT se vale de un compilador que lee código fuente escrito en Java y lo transforma o lo traduce en código JavaScript.

Aunque GWT trae una herramienta para depurar el código (llamado Hosted Mode) usando la característica de debugger que tienen los IDE de Java. Es necesario de tanto en tanto, ver la aplicación montada en el entorno real, es decir, dentro de la aplicación web en un navegador. Este proceso de compilación de código Java a JavaScript es bastante lento, midiéndose en minutos cuando se trata de aplicaciones web grandes. Este factor se agrava cuando se usa la internacionalización y la aplicación web se tiene que ver en varios idiomas.

Resulta que el compilador genera una versión distinta del código para cada idioma y para cada navegador de los soportados. Entre los navegadores está (ie, firefox 1, firefox 2 en adelante, opera y safari). Cuando se está en pleno desarrollo, normalmente las pruebas se hacen en un sólo navegador y en un sólo idioma.

Lo bueno es que GWT permite configurar ésto, reduciendo dramáticamente el tiempo de compilación y por ende el tiempo de espera hasta que la aplicación está completamente desplegada. Para hacer ésto se debe agregar la siguiente línea en el archivo xml que define cada módulo que tiene definido un punto de entrada:
<module>
 <inherits name="com.google.gwt.user.User"></inherits>

 <set-property name="user.agent" value="gecko1_8"></set-property>
</module>

Esto hace que sólo se compile para Firefox 2 en adelante. Los valores permitidos para esta propiedad son:
ie8, gecko1_8, safari, ie9, ie10

Pero ojo sólo se puede poner un valor a la vez. Es decir todo o uno.

Si se tienen definidos varios idiomas se debe dejar uno sólo:
<module>
  <inherits name="com.google.gwt.user.User"></inherits>
  <inherits name="com.google.gwt.i18n.I18N"></inherits>

  <!-- Spanish -->
  <extend-property name="locale" values="es"></extend-property>

  <!-- English language, independent of country -->
  <!--<extend-property name="locale" values="en">-->
</module>

En mi aplicación el tiempo de compilación se redujo a la mitad.

Es importante recordar arreglar estos valores para el deploy final de la aplicación, especialmente si se publicará en internet.

Hasta la próxima...

Nota: Si esta información te es de utilidad o piensas que se puede mejorar, por favor deja un comentario con tus observaciones.

Uso correcto del PreparedStatement

Actualización: Esta entrada se revisó y actualizó el 29 de Diciembre de 2015

El PreparedStatement nos permite definir una sentencia SQL base, que nos sirve para modificar/insertar/buscar uno o varios registros con sólo cambiar los valores de los parámetros que especifiquemos.

Un ejemplo típico es:
PreparedStatement pstmt = con.prepareStatement(
"update empleado set sueldo = ? where id_empleado = ?");
pstmt.setDouble(1, 153833.00);
pstmt.setInt(2, 110592);
Esto nos permite tener una sentencia SQL de fácil lectura y sin tener que concatenar cadenas o hacer conversiones de tipos. De la documentación de java:
An object that represents a precompiled SQL statement.
A SQL statement is precompiled and stored in a PreparedStatement object. This object can then be used to efficiently execute this statement multiple times.
Supongamos que tenemos un mapa de empleados y sueldos:
Map<Integer, Double> empleadoSueldo = new HashMap<>();
empleadoSueldo.put(1, 1000.0);
empleadoSueldo.put(2, 2000.0);
empleadoSueldo.put(3, 3000.0);
Es muy común ver un código como el siguiente:
for(Map.Entry<Integer, Double> empleado : empleadoSueldo.entrySet()) {
      PreparedStatement pstmt = con.prepareStatement(
   "update empleado set sueldo = ? where id_empleado = ?");

      pstmt.setDouble(1, empleado.getValue());
      pstmt.setInt(2, empleado.getKey());
      pstmt.executeUpdate();
  }
La definición de PreparedStatement dice que es una sentencia SQL precompilada para ser ejecutada muchas veces. Pero al crear el PreparedStatement cada vez, se está precompilando y ejecutando. Perdiendo completamente la ventaja en rendimiento que supone el precompilado de una sentencia SQL. Hay algunos manejadores que inclusive crean un procedimiento almacenado temporal, cada vez que se crea un PreparedStatement.

Para aprovechar el precompilado, el código debe cambiar a:
PreparedStatement pstmt = con.prepareStatement(
   "update empleado set sueldo = ? where id_empleado = ?");

  for(Map.Entry<Integer, Double> empleado : empleadoSueldo
          .entrySet()) {

      pstmt.setDouble(1, empleado.getValue());
      pstmt.setInt(2, empleado.getKey());
      pstmt.executeUpdate();
}
Así el SQL es interpretado una sola vez por el manejador de base de datos, y de ahí en adelante sólo se ocupa de colocar parámetros y ejecutar.

Hasta la próxima...

Nota: Si esta información te es de utilidad o piensas que se puede mejorar, por favor deja un comentario con tus observaciones.

ClipDiary y ATnotes sácale provecho al portapapeles

Cuando se está echando código, a menudo se necesita copiar varios fragmentos consecutivamente de un fuente a otro. O desde otro programa como Excel. Es tedioso tener que pasar al sitio desde donde se quiere copiar un pedazo, copiarlo, pasar al destino, pegar y luego repetir la operación. Sin hablar cuando se quiere copiar algo y uno se acuerda que algo importante ya está en el portapapeles. Bueno combinando dos herramientas (ClipDiary y ATnotes) se puede hacer el trabajo super sencillo.

Clipdiary es simplemente un almacenador del portapapeles. Todo lo que entra en el portapapeles, este programa lo almacena. Entonces lo que uno puede hacer es pasar al sitio desde donde se quiere copiar, y copiar todos los fragmentos que se necesitan dándole repetidamente Copiar. Luego en el sitio de destino abrir clipdiary e ir pegando todo lo que se copió. Recomiendo aprenderse o asignar un acceso directo al clipdiary. Ya que el pegado se reduce a presionar el acceso directo y decir pegar. El contenido de clipdiary además persiste después de apagar el PC.

Cuando se quiere ver a la vez dos piezas de código, o ver y copiar datos a un fuente mientras se están viendo ambos, tanto el fuente como los datos e ir copiando poco a poco, ahí es donde entra ATnotes. Éste es un programa de notas de recordatorio y esas cosas, que se pegan en el escritorio. Pero la idea no es usarlo de esa manera, sino la capacidad que tiene de quedarse flotando sobre las ventanas y de crear una nota proveniente del portapapeles.

Para usarlo lo primero que hay que hacer es seleccionar la opción "Always on top". Luego copiar todo el texto que se quiere ver y presionar el acceso directo Win+C. Ésto crea una nota flotante con el contenido del portapapeles. Lo que falta por hacer es ir al programa destino y poder trabajar con ambos textos, e inclusive copiar porciones de la nota.

Aquí están los enlaces para descargarlos:

  • Clipdiary
  • ATnotes (Ellos dicen que el programa está descontinuado, pero funciona perfectamente inclusive en Windows Vista SP1)

Yo descubrí éstos programas después de haber probado sus equivalentes que vienen en KDE (LINUX) Klipper y Knotes. Sólo que Klipper tiene además una fantástica opción de búsqueda y una característica llamada acciones que permite abrir programas dependiendo del contenido en el portapapeles. Así por ejemplo si un copia http://direccion/, klipper permite con un comando abrir el navegador con esa dirección.

Hasta la próxima...

Nota: Si esta información te es de utilidad o piensas que se puede mejorar, por favor deja un comentario con tus observaciones.

Búsqueda

Síguenos