domingo, 29 de junio de 2008

Tamaño máximo de heap (y tamaño mínimo del heap)

En otro artículo desgranaré algunos posibles argumentos / parámetros de lanzamiento de la máquina virtual. Pero una nota muy rápida para que quede claro, porque es una pregunta muy usada: ¿cuál es el tamaño máximo de heap adecuado para establecer con -Xmx?

Muy sencillo: la regla general es que en un servidor donde se está ejecutando una máquina virtual JAMÁS debe agotarse la memoria física. Jamás debe llegar a utilizarse el swap a disco. Nunca. Never. Jamais. Nie. Mai. Nooit.

Esto significa que sumando los heaps máximos de todas las máquinas virtuales que pueden ejecutarse en un servidor, deberemos asignar como máximo entre 0,5 y 1,5 GBytes menos de tamaños máximos de heap (sumados) que la memoria física de que dispone nuestra máquina. Nota: esta regla es fruto de mi experiencia, quizás por ahí haya otras medidas... ummm... ¿qué tal un 80%-20% o un 90%-10%? :-)

Es decir, si tenemos 16 gigas, usar siempre como máximo 14,5-15,5, el resto debe ser sufiente para tareas de automantenimiento, backups, scripts de consulta o consolas o sorpresas.

¿Por qué? Muy sencillo: el recolector de basura debe recorrer todo el heap, eso implica que si parte de éste está swapeado, el proceso de recolección generará un montón de fallos de página, lo que puede derivar en un auténtico desastre por OutOfMemoryError's muy frecuentes a poca carga que tengamos. Es decir, el modelo de GC no se lleva nada bien con un sistema de swapping a disco.

Y por cierto, para evitar fragmentación del heap y redimensionamientos no deseados en entornos de producción el tamaño inicial (-Xms) debe igualarse al máximo (-Xmx). Las pruebas demuestran que esto último tiene más importancia en la máquina virtual de Bea que en la de Sun.

También hay otras consideraciones a tener en cuenta, como el establecimiento de -Xmn para el tamaño de la zona de young generation (zona "Eden" + survivors), así como las políticas de promoción entre zonas y recolección de basura. Pero eso será objeto de ese artículo y no suele ser necesario si ayudamos a la JVM con -server/-client.

P.D: Aunque no es mi caso actualmente, además, tengamos en cuenta que cuanto mayor sea el heap, más le costará al GC hacer su trabajo, por lo que debemos tener cuidado con heaps de más de 50 gigas por mucho maquinón que nos pongan. Tenía por ahí alúna otra referencia que ahora no encuentro, dejo un link a http://dev2dev.bea.com/blog/hstahl/archive/2007/03/the_biggest_jvm.html ¿3,5 TBytes de heap? Guau!!!!.

Lo cierto, como aparece en un comentario que leí por ahí, es que si necesitamos semejante heap es que tenemos un problema de diseño de cojones en nuestra aplicación...

lunes, 23 de junio de 2008

Out-of-Box Performance ("Out of the box")

Se trata, para mi, de uno de los asuntos más importantes en el mundo de las máquinas virtuales: que el rendimiento y escalabilidad de una plataforma Java "sin tunear" (o auto-tuneada) sea muy parecida a lo que podríamos conseguir tuneándolo específicamente para un tipo de sistema o para un benchmark concreto.

Y digo que es importantísimo por tres motivos:

  • No siempre tenemos el tiempo adecuado para tunear todos los sistemas en cualquier circunstancia, o no siempre tenemos el equipo de ingeniero de sistemas adecuado, o no siempre tienen el tiempo o disponibilidad necesaria para hacer esas labores de tuning fino.
  • Las posibilidades de parametrización en el arranque de la máquina virtual son casi infinitas, un infierno me atrevería a decir, y las probabilidades de empeorar alguno de los aspectos de una aplicación cuando mejoramos perceptiblemente otros son muy altas, en mi experiencia, y no siempre son inmediatas de cazar.
  • El mundo no es inmóvil ni inmutable, y las condiciones en que se ejecuta un sistema van evolucionando (en concurrencia, carga, usuarios, tamaños de ficheros intercambiados, sistemas con los que se integra, nuevos elementos de red, etc), por lo que con el tiempo pueden dejar de tener sentido decisiones iniciales. Este problema es serio: me he encontrado con situaciones en las que había opciones de lanzamiento "heredadas" o no cambiadas en 3 años y al cambiar de versión de JVM ha sido contraproducente y una penalización en escalabilidad respecto a no haber puesto casi nada.

En este aspecto, las implementaciones modernas tanto de Hotspot como de JRockit tienen mejoras sustanciales. Fue muy sonado el anuncio de Sun en cuanto al Out-of-box performance del Java SE 6, aunque Bea asegura que su equivalente tiene incluso mejores prestaciones "Out-of-box".

¿Qué significa esto? Que con especificar parámetros en cuanto a tamaños de heap y poco más (ayudando con "-server"/"-client" pero en JRockit ni eso, al igual que Hotspot en algunos S.O.), hoy en día es casi suficiente para el 80% de las necesidades de nuestras aplicaciones y servicios. En algunos benchmarks que yo he hecho en mis aplicaciones, contrastándolo con una configuración "supertuneada" específicamente, la diferencia es sólo de entre un 0% y un 10% tanto en rendimiento como en escalabilidad. Mola.

Y lo que digo para Java SE se aplica en casi todos los órdenes de esta vida tecnológica: sistemas operativos, BIOS, etc.

Unas pocas referencias:

viernes, 20 de junio de 2008

Buffers: por defecto y por exceso

Llevo 11 años en el mundo Java. A lo largo de ese tiempo ven viniendo como los maderos a la playa los mismos error repetidos en quizás 20 ocasiones por personas distintas, en momentos distintos.

Defecto

Por ejemplo este: cargar todo el contenido de un fichero o un blob en memoria, no para trabajar con sus datos (que sería hasta discutible) sino para derivarlo hacia otra colaboración/proceso/cliente.

Hace poco me he encontrado, de nuevo, con un código similar a este para enviarle un fichero al navegador (eliminadas las partes de content-disposition, content-type, excepciones, etc):

input = new java.io.FileInputStream(pdf);
int longitud = input.available();
byte [] datos = new byte [longitud];
input.read(datos);
response.getOutputStream().write(datos);
response.getOutputStream().flush();


Lo que podríamos catalogar de como mínimo "bestia" si no sabemos si puede tratarse de ficheros pequeños o grandes, o si vamos a tener un poquito de concurrencia. ¡Aparte de que vaya usted a saber qué valor devuelve el método available()!

Es cierto que cada vez la memoria es más "barata" y que tiende a pensarse que es infinita... pero también es cierto que el número de sesiones simultáneas, clientes concurrentes y tamaño de los ficheros / contenidos multimedia crece en una proporción similar. ¿Tanto cuesta utilizar un pequeño buffer?. Un código equivalente similar a este:

input = new java.io.FileInputStream(pdf);
byte [] buffer = new byte [16*1024]; // 16 KBytes
OutputStream output = response.getOutputStream();
int readed;
while ( (readed = input.read(buffer)) > 0 ) {
output.write(buffer, 0, readed);
}
out.flush();


Importantísimo: Sólo hay que escribir el tamaño real que el método de lectura ha metido en el buffer, lo que significaría que en la última escritura a la salida meteríamos basura detrás del final de lo que realmente teníamos que escribir, y eso puede dar problemas serios. Es decir me suelo encontrar con esta otra versión del bucle que es ABSOLUTAMENTE INCORRECTA (yo mismo la he cagao en alguna ocasión reciente, al loro):
while ( (fiPdf.read(buffer)) > 0 ) {
out.write(buffer);
}
Por cierto, en mi experiencia, en función de la arquitectura y sistema operativo, tamaños de entre 16 y 32 KBytes suelen ser los apropiados para este tipo de operaciones y para el uso de java.io.BufferedInputStream y java.io.BufferedOutputStream (por cierto esas clases usan por defecto un buffer 8KBytes si no especificas otra cosa en sus contructores).

Exceso

Por otro lado, también es común encontrarnos con el caso contrario: utilizar buffers cuando no hacen falta. ¿Cuántas veces hemos visto algo parecido a new GZIPOutputStream(new BufferedInputStream(new FileOutputStream(tempFile)))? ¿o ps.setBlob(1, new BufferedInputStream(new FileInputStream(tempFile),32*1024), tempFile.length()). Y el desarrollador, tan orgulloso de ser más listo que nadie.

Por regla general, si vamos a invocar a un método cuyo parámetro es un InputStream o un OutputStream, no hay que utilizar como intermediario un buffer porque por regla general el método invocado está optimizado. Es decir, debemos confiar en nuestros partners. Y por supuesto, si partimos de un array, no usemos un buffer como intermediario, ¡porque nuestro origen ya es un buffer!

En definitiva, tanto por exceso como por defecto, nos podemos encontrar con un sobre uso de la memoria con un efecto multiplicador más que curioso.

Muchas veces le doy vueltas a que deberían darle alguna que otra pensada a la implementación interna de los arrays en la máquina virtual; es decir que internamente la JVM hiciera swapping a disco de arrays grandes como uno de los pasos del recolector de basura, GC!!!

Hablando del tema... en su día yo hice una clase que era una especie de abstracción de todo esto: un "ByteArrayAdapter" que internamente decidía utilizar un byte array o un fichero temporal en función de tamaños y algunos que otros condicionantes, de forma transparente al desarrollador. El problema es el de siempre: difícil de explicar y difícil de recordar que existe si no eres el creador de la criatura, y una cosa más que mantener. Repito el autoconsejo de uno de los primeros posts: cuanto menos contenido tenga la biblioteca propia de clases de nuestra compañía / grupo de trabajo / producto, mucho mejor. (¿Por qué todos nos empeñamos en que tenemos que tener una biblioteca estándar en nuestra empresa? ¿por qué tenemos que creernos más listos que las comunidades opensource? ¿por qué terminamos teniendo cuatro versiones de lo mismo que encima los nuevos integrantes de los equipos terminan "olvidando"? Si siempre pasa lo mismo, no caigamos en lo mismo de siempre.

Bueno, que me he ido del asunto. Volvemos a lo de siempre: seguimos necesitando que el desarrollador sea un ser pensante y con experiencia. Afortunadamente, por cierto.

Nota: el tema de este post en realidad es la escalabilidad más que el rendimiento :-)

martes, 17 de junio de 2008

Java CAPS y Open ESB: Enlaces de interés y recursos

Una recopilación no exhaustiva de referencias sobre Java CAPS y en menor medida sobre Open ESB, y no sólo de temas relacionados con rendimiento o escalabilidad. Iré actualizándola...

[Actualizado el 25/8/2008: añadidos algunos enlaces de interés tomados del blog CamelCase, como la nueva wiki]

Soporte y Documentación oficial de Sun:

Foros:


Wikis:


Herramientas y otros:


Libros:


Videos:


Blogs:


del.icio.us

Technorati

Twitter

Facebook

Nota: Muchas de estas referencias son recopilación mías, pero otras muchas están copiadas de la gran recopilación hecha por Sebastian Krueger (http://jcapsblogger.blogspot.com/2008/04/some-tips-on-following-java-caps-news.html) y he tomado otras del blog CamelCase referenciado arriba).

sábado, 14 de junio de 2008

Sun Hotspot -vs- Bea JRockit

Actualización 19/9/2008: Nuevo blog corporativo de Henrik, ahora que les ha comprado Oracle...

Dejo enlaces a los weblogs de miembros de los equipos de rendimiento de Sun y de Bea (ahora Oracle), para sus respectivas máquinas virtuales, Hotspot y JRockit. Algunos artículos son más que interesantes:

Je, je, es la gran batalla que da "vidilla" al rendimiento y escalabilidad en Java sobre plantaformas x86 y x64. Una vez más se demuestra que la competencia fomenta la innovación y el avance (vaya, esto parece un alegato capitalista antimonopolio :-) Bueno, están IBM y HP por ahí también, sobre todo IBM tuvo ganada la partida en el mundo Intel en los tiempos del JDK 1.1 y 1.2... pero para mi hoy en día son convidados de piedra en este juego, especializados en sus arquitecturas respectivas.

¿Y tú de quién eres? No, no, no se puede ser agnóstico, hay que tomar partido. Esto es como las decisiones Rolling/Beatles, Intel/AMD, Barça/Madrid, Windows/Linux, Cocacola/Pepsi, Fanta/Kas, MortadeloFilemón/ZipiZape, Astérix/Tintín, Demócratas/Republicanos, Google/Yahoo, Java/C++, Redhat/SuSE. Hay que tomar partido. Y defender tu posición. :-)

Si tengo que ser sincero, le tengo especial cariño a JRockit por aquello de que me sacó de un gran apuro hace ya tiempo en un proyecto, sólo con cambiar de máquina virtual y tunear algunas cosillas... aquello fue como magia. Pero eran los tiempos en que acababa de salir el JDK 1.4.2 y ha llovido mucho desde entonces.

Y como cada uno cacarea los resultados que quiere en sus publicidades, y mi sensación es que a día de hoy ambas tecnologías son muy parejas, mi opinión es que hoy en día hay que ser pragmático: si vas a ejecutar un Weblogic / Aqualogic, móntalo sobre JRockit; y si vas a ejecutar un Glassfish / JES / Java CAPS, hazlo sobre la JVM de Sun.

Porque suelen venir ya "pretuneados", o al menos han sido muy probadas esas combinaciones, y porque no quiero tener problemas cuando reporto una incidencia a los equipos de soporte de Sun y de Bea respectivamente. Porque su primera respuesta es obvia ante un reporte de incidencia, esto es como cuando vas al médico por lo que sea: ya sabes que lo primero de todo te va a decir que dejes de fumar y que adelgaces. Aunque tengas una rotura de fibras.

Es decir, reconozco que hoy en día estoy utilizando Sun Java SE 6.0 sobre Linux x64. Pero algún día jugaremos un poco a ver qué pasa...

P.D: Por cierto, he mantenido una conversación por email muy interesante con Henrik Ståhl. Y he de reconocer que estoy gratamente sorprendido no sólo por su capacidad profesional (que se le presupone) sino por su amabilidad e interés acerca de mis necesidades / caso de uso.

Disclaimers (II)

Otra cosilla, aparte de otros disclaimers generales (por cierto, literalmente Disclaimer significa "Negación").

Lo que aquí voy dejando para la posteridad no tiene por qué ser la Verdad absoluta, incluso puede que esté muy equivocado en más de una aserción. Son mis opiniones y/o experiencias en unos entornos, aplicaciones y momentos concretos que pueden diferir de lo que algún googler eventualmente vaya buscando si termina parando en esta esquinita de la web.

Es curioso porque el ser humano tiende a poner en tela de juicio constantemente versiones oficiales, leyes, libros, dogmas de fe, teorías científicas, lo que nos dicen los consultores de las grades empresas, etc, pero por el motivo que sea tendemos a creer que lo que encontramos googleando o en la wikipedia es la verdad absoluta. Que el anonimato obliga. Por algo se habrá escrito. El autor no gana nada con mentir deliveradamente.

Pero la realidad no tiene por qué ser esa, y no por mala fe. En el 70% de los casos la gente escribe de oídas o porque lo ha leído en otro foro o blog, y en raras ocasiones alguien se ha molestado en hacer una prueba de carga en condiciones y de perfilar una ejecución. En cualquier caso siempre estamos influidos por los prejuicios, o por lo que nos infundió gente a la que respetamos, o de empresas/apuestas a las que tenemos má cariño que a otras.

Así que aviso para navegantes, estas son mis opiniones, esta es mi verdad, que puede cambiar con el tiempo :-)

miércoles, 11 de junio de 2008

Java SE 6u5p Performance Release

[Nota de octubre'08 ==>

Querido googler: si llegas a esta página -como muchos- buscando información de las performance releases del JRE 6, quizás te interesen los artículos posteriores Java SE 6u6p Performance Release,
Java SE 6u6-p Performance Release - YA DISPONIBLE EN OTRAS PLATAFORMAS y 6u6-p: Pues en Linux x64 peta!!!!!!!]

Aunque la noticia tiene más de un mes, yo lo acabo de descubrir: Sun lanza una versión especial de Java SE 6 (llamada 6u5p) incluyendo las últimas innovaciones en rendimiento que eventualmente se incorporarán a futuras versiones de la plataforma.

Copia literal de la web de Sun:

May 2008
Java SE 6u5p Performance Release

This special version of Java SE includes the very latest performance innovations from Sun on selected platforms. The software is free for download, application performance testing, and benchmark submissions. Performance releases may be used in production. If desired, support is available via a Sun Java support contract. These performance enhancements will be included in an upcoming standard release of the Java SE platform. Please share with us your experience using this special advance release.

Y veo este mini resumen en estas trasparencias de Java One - http://blogs.sun.com/dannycoward/resource/PS_TS-6271_SETrackTalk_Final.pdf:

Java SE 6u5p “Performance Release”

  • Performance tuned release of the JRE™64bit architectures only
  • Solaris, Windows, Linux
  • Large number of small optimizations
    • Crypto libraries, TreeMap, HashMap, XML Parsing
    • Escape analysis, depth-first copying, page sizes
  • Optimizations seep into future JRE updates

Un poco exagerada la promesa de mejora que se ve en el gráfico de abajo ¿no?

Sun no está dando mucha información de qué incorpora (o yo no lo he encontrado), pero la gráfica de arriba es prometedora, y parte de la información de mejoras también... ¿eso de optimizar TreeMap, HashMap y XML Parsing no recuerda sospechosamente a algunas de las técnicas utilizadas en la biblioteca de Javolution? (ver http://serverperformance.blogspot.com/2008/06/javolution.html).

Pero siempre que ocurre alguna promesa de "milagro" de estos en lo que a rendimiento se refiere me surgen siempre dos dudas: ¿cuánto hay de cierto? y, lo que me come por dentro... ¿es que ahora somos muy listos o es que hasta hace 6 meses éramos todos muy tontos?

Puede solicitarse su descarga desde http://java.sun.com/performance, donde por cierto hay también unos cuantos documentos y recursos sobre rendimiento, escalabilidad y tunning.

Buena noticia esta "iniciativa 2.0". Creo que es la primera versión de anticipos de rendimiento que liberan así, como se ve en alguna gráfica de temporalidad de versiones, pero seguro que no es la última... habrá que ir chequeándolo...

En general me gusta la política de semi-puertas abiertas que tanto Sun con Bea/Oracle están siguiendo...

P.D: Ojo con utilizar esta versión en producción sin probarla muy-muy bien, veo en algunos foros y bug parade que ciertas opciones de GC fallan o que en algún caso muy concreto se comporta distinto que su hermana la versión estándar... Como siempre, es mejor seguir la máxima "que prueben otros" salvo necesidades muy bien justificadas o actualizaciones menores de software (esta no lo es aunque lo parezca), aunque la cabra tira al monte y el cuerpo nos pide marcha...

lunes, 9 de junio de 2008

Rendimiento en transformaciones XSLT

Esta vez voy a referenciar una serie de tres grandiosos posts hechos en el blog "Algo nuevo que hacer..." (http://www.cmaj.es/). Se trata de un blog muy interesante salpimentado con una pizca de tecnología y dos pizcas de liderazgo de equipos de trabajo y motivación.

XSLT en Java: uso de Translets

Si estás -como yo- preocupado por el rendimiento y escalabilidad de las transformaciones XSLT en Java, ya habrás llegado a la conclusión de que la única forma de que el rendimiento y escalabilidad del cruce de un XML con su plantilla XSL sea decente es el uso de "translets", esto es clases precompiladas:

No obstante, sigue este gran artículo dividido en tres entregas, muy interesante por la evolución cronológica que marca y por las sorpresas y tunnings realizados:

El "gato encerrado" descubierto en el tercer post hace que cada vez que reafirme más en esa sentencia que es recurrente en mis posts y conversaciones: si un framework deja dos opciones para hacer lo mismo, en el 50% de los casos se utilizará la incorrecta. Y lo que es peor, Murphy dice que el 100% de esas veces siempre pasa en proyectos más críticos y en sus peores fases. Venga pongámosle nombre a esta ley... ¿qué tal la Ley del Chikilicuatre?

¿Por qué entonces, joder, nos empeñamos todos los arquitectos software en dejar para la posteriidad múltiples opciones de implementación o parametrización cuando rara vez tiene sentido algo distinto de la opción "lógica"? ¿para que los desarrolladores tengan más emoción en sus vidas????

P.D: [OFFTOPIC - Reflexión personal sobre el uso de XSLT]:

La tecnología XSLT es muy interesante desde el punto de vista teórico para independizar la capa de presentación de la tecnología concreta en que se desarrolla el resto de la aplicación. Es decir, en un mundo ideal podemos tener un equipo especializado 100% en XSLT + HTML + Javascript + AJAX, y que ese equipo intervenga tanto en proyectos Java, .NET, Python, RoR, PHP, o lo que sea. Yo mismo he apostado por esa estrategia en el pasado.

Pero esa es la teoría, la realidad dice que muy pocas Compañías pueden permitirse ese grado de equipos/perfiles especializados, más bien generalmente el 95% de tus proyectos son mono-tecnología (Java en mi caso) y en la Organización no existen los perfiles especializados sino genéricos o todoterreno. En ese caso una constante suele ser que el 100% de tus desarrolladores se sienten "javeros" (o lo que toque) y sorprendentemente les da "alergia" cualquier cosa distinta (y en ese saco meto tanto SQL como XSLT, HTML o Javascript)... y ahí tenemos un problema de cojones!.

Conclusión: es mejor dejarse llevar por las fuertes corrientes e implementar la capa de presentación con Struts, JSF o lo que toque. Porque es lo que conocen los técnicos tanto de dentro como de fuera, y cuanto más distinto o especialito seas, más te cuesta sustituir a una persona o ampliar un equipo.

Corolario: el uso de XSLT puede llegar a ser equivalente o superior en cuanto a rendimiento en tiempo de ejecución, pero siempre va a ser muy inferior en cuanto a rendimiento del equipo de trabajo. Al menos en un par de años vista...

viernes, 6 de junio de 2008

Mitos sobre rendimiento y escalabilidad


Una recopilación de buenos enlaces, ellos sí que saben. Y en la PPT va probablemente una de las mejores frases que he leído en meses: "clever code confuses clever JVMs". o lo que es lo mismo "no te pases de listo" :-)

Tengo que reconocer que alguno de los puntos de la PPT me ha sorprendido, como que actualmente la sincronización en sí misma no es excesivamente costosa, sino que lo que sigue siendo costoso es la contención de threads. En otras palabras: en teoría "casi" es lo mismo usar un StringBuilder en lugar de StringBuffer cuando sólo un thread simultáneamente pasa por las regiones de exclusión mutua (lo que choca con lo que dije precisamente en uno de los primeros posts). ¿Es una gran equivocación o es que me estoy volviendo viejo y sigo tomando como ciertas cosas que eran verdad en la época del JDK 1.1, 1.2 y hasta 1.3 pero ya no lo son??? Si fuera verdad... ¿para qué crear la nueva StringBuilder? Ummm... Habrá que hacer pruebas de rendimiento (lo cierto es que en muchos aspectos casi todo el mundo habla de oídas)... [Actualización 1/9/2008: puede verse información detallada acerca de benhmarking comparativo aplicando distintas opciones de lanzamiento de la JVM sobre esta y otras opciones de optimizaciones sobre el modelo de Threading en el post ¿Funcionan las optimizaciones sobre el modelo de threading?]

Y por cierto, como me han recordado hoy, rendimiento no es sinónimo de escalabilidad. Hay que tener claros los conceptos y los objetivos en cada caso. En muchos casos los pasos para conseguir ambos objetivos son los mismos pero muchas otras veces son términos casi incompatibles. La cuestión es saber cuál es el término medio como siempre... Y hablando de mitos y leyendas...

miércoles, 4 de junio de 2008

JDBC 4.0 y Oracle 11G: combinación ganadora

La versión 4.0 de JDBC, incorporado en Java SE 6.0, supone un salto importante en cuanto a rendimiento en muchos pequeños aspectos. Uno de ellos es la mejora en los pooles, y otro ejemplo -en el que me voy a centrar hoy- son los LOBs. Este artículo no pretende ser un estudio exhaustivo acerca de JDBC 4.0, para eso mejor visitar por ejemplo algunos de los enlaces de abajo (en concreto el de onjava.com o el de ibm-developerworks).

Estas mejoras pueden no ser significativas en algunos/muchos entornos, pero sí que lo son en servidores con mucho estrés o en temas muy concretos como el uso de LOBs, que por fin se pueden gestionar de una forma coherente y sencilla.

Por ejemplo, se han añadido a PreparedStatement los métodos:
/**
* Sets the designated parameter to a InputStream object. The inputstream must contain the number
* of characters specified by length otherwise a SQLException will be
* generated when the PreparedStatement is executed.
* This method differs from the setBinaryStream (int, InputStream, int)
* method because it informs the driver that the parameter value should be
* sent to the server as a BLOB. When the setBinaryStream method is used,
* the driver may have to do extra work to determine whether the parameter
* data should be sent to the server as a LONGVARBINARY or a BLOB
* @param parameterIndex index of the first parameter is 1,
* the second is 2, ...
* @param inputStream An object that contains the data to set the parameter
* value to.
* @param length the number of bytes in the parameter data.
* @throws SQLException if parameterIndex does not correspond to a parameter
* marker in the SQL statement; if a database access error occurs;
* this method is called on a closed PreparedStatement;
* if the length specified
* is less than zero or if the number of bytes in the inputstream does not match
* the specfied length.
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
*
* @since 1.6
*/
void setBlob(int parameterIndex, InputStream inputStream, long length)
throws SQLException;


/**
* Sets the designated parameter to a Reader object. The reader must contain the number
* of characters specified by length otherwise a SQLException will be
* generated when the PreparedStatement is executed.
* This method differs from the setCharacterStream (int, Reader, int) method
* because it informs the driver that the parameter value should be sent to
* the server as a CLOB. When the setCharacterStream method is used, the
* driver may have to do extra work to determine whether the parameter
* data should be sent to the server as a LONGVARCHAR or a CLOB
* @param parameterIndex index of the first parameter is 1, the second is 2, ...
* @param reader An object that contains the data to set the parameter value to.
* @param length the number of characters in the parameter data.
* @throws SQLException if parameterIndex does not correspond to a parameter
* marker in the SQL statement; if a database access error occurs; this method is called on
* a closed PreparedStatement or if the length specified is less than zero.
*
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
* @since 1.6
*/
void setClob(int parameterIndex, Reader reader, long length)
throws SQLException;

Esto implica que, con bases de datos Oracle, nunca más sea necesaria la creación de un blob temporal a través de la API propia de Oracle y posterior asignación a la nueva fila sin que de forma mucho más intuitiva y estándar podemos hacer lo siguiente para insertar datos en una columna de tipo BLOB:
File originalFile = new File(PATH);
Connection conn = ...;
PreparedStatement ps = conn.prepareStatement("...");
ps.setBlob(1, new FileInputStream(originalFile), originalFile.length());
ps.execute();
ps.close();
conn.close();

No sólo es más elegante, sino que en Oracle 11G supone un incremento de hasta un 25% en el rendimiento frente al tradicional método con "BLOB.createTemporary()" propietario de Oracle (son mis datos empíricos con ficheros de más de 10 megas). Lo cual sumado al uso de los nuevos SECUREFILEs para almacenar LOBS, significa que la combinación Java 6 + JDBC 4 + Oracle 11G suponga hasta casi un 50% más de rendimiento que Java SE 1.5 + JDBC 3 + Oracle 10G en la inserción de LOBs de gran tamaño.

Para más información acerca de los cambios que supone JDBC 4 sobre sus antecedores:

lunes, 2 de junio de 2008

Javolution

http://javolution.org/

¿Operaciones de inserción en strings de orden logarítmico en lugar de lineal?

¿Alguien que por fin se acuerda de optimizar específicamente los marshallings/unmarshailling de XML?

Eso mola...

Se trata de una biblioteca de clases básicas, muchas de ellas "sustitutivas" de las que vienen en la biblioteca estándar del JDK, cuyo principal objetivo es el alto rendimiento y la predictibilidad (determinismo) del mismo. Principalmente pensada inicialmente para sistemas de tiempo real y empotrados, aunque como puede verse también puede ser de gran utilidad en sistemas servido con mucha concurrencia o necesidades de rendimiento y escalabilidad.

La biblioteca estándar del JDK tiene un problema general: está pensada para no dar problemas en ningún entorno, por lo que puede no ser la mejor solución siempre. Por ejemplo, la mayor parte de las clases son threadsafe, lo cual significa que por ejemplo la concatenación en un StringBuffer o el uso de una tabla de hash está lleno de regiones de exclusión mutua y uso de monitores. Siendo que en el 95% de los casos son completamente innecesarias debido a que hoy en día un desarrollador trabaja dentro de un framework, o genera código para un EJB, etc, y ese objeto sólo va a ser ejecutado por un thread en cada momento.

Por supuesto, con el tiempo Java ha evolucionado en dos líneas:

  • Por un lado cada revisión de la máquina virtual / Hotspot VM hace que se reduzca la penalización por el uso de monitores allá donde no hacían falta.
  • Por otro lado, poco a poco en el JDK se han ido introduciendo versiones "no sincronizadas". Por la propia filosofía de compatibilidad hacia atrás, el tema va quedando al menos "raro", pero es lo que hay. Por ejemplo:
    • java.uti.HashMap (que ofrece la misma funcionalidad que la java.util.Hashtable de toda la vida).
    • java.lang.StringBuilder por fin desde el JDK 1.6 supone una alternativa "no sincronizada" al infame java.lang.StringBuffer. (¿De verdad nadie se había dado cuenta hasta ahora que en el 99,999999% de las veces la concatenación de cadenas se usa en un contexto threadsafe?????)

El problema de separar así la API es que estás delegando en la capacidad, memoria, experiencia y entrenamiento del desarrollador. Mi experiencia dicta que "si quieres que algo se haga de una cierta forma, no des dos opciones para ello; en un 50% de los casos se utilizará la incorrecta". No sé si hay una ley para ello, o si no le ponemos un nombre. Claro que para que tenga éxito la postulación tenemos que poner mejor las cantidades 80-20 por algún lado ;-)

En esta línea llega Javolution. Me gusta, y mucho. Probablemente porque es lo que yo siempre he querido hacer, probablemente porque me he enamorado de su código fuente.

Pero tiene una pega, probablemente insalvable: en el fondo lo mismo que comento arriba: se trata de algo nuevo que aprender, no estándar y que los desarrolladores que hay en el mercado y entran nuevos a tu equipo no están habituados a utilizar. Es decir, mal rollo salvo que se utilice en un contexto muy concreto dentro de un producto o exclusivamente en las partes críticas de un proyecto.

También tiene otra pega que he sufrido yo en intentos similares anteriores: las siguientes revisiones de la JVM o del JDK pueden hacer inservibles parte de las optimizaciones, o que se optimicen las clases estándar más allá de la línea que ha podido seguir Javolution.

Y finalmente: hay que saber lo que está haciendo. Hay que "ayudar" a las optimizaciones de forma declarativa indicando contextos de trabajo... Y no me gusta tener la obligación de que todo desarrollador de un equipo sea un auténtico experto en rendimiento o cuncurrencia.

Más que reconmendable, sobre todo en sistemas de tiempo real, pero teniendo siempre presente que no existen los milagros (sino que lo importante es la suma de muchos pequeños detalles) y también en cuenta las reflexiones de arriba ("gato escaldado del agua fría huye").

Como muestra un botón: benchmarking de parsers XML --http://www.xml.com/pub/a/2007/05/09/xml-parser-benchmarks-part-1.html?page=2

En cualquier caso, mi conclusión es:

  • Utilizarlo sólo en los proyectos y contextos en que sea inevitable, o crítico
  • Muy interesante para conocerlo y aprender de por qué y cómo está hecho
  • Muy interesante para que los señores que desarrollan el JDK (ahora es Open Source así que debería estar más abierto a ideas frescas), así como implementadores de las APIs más comunes y equipos de desarrollo de servidores de aplicaciones varios
  • Muchas veces de nada serviría utilizarlo en nuestro proyecto, si no lo aplica el servidor de aplicaciones que hay por debajo, y que también habría que meterle mano para que realmente se note un cambio en la tendencia de respuesta o rendimiento.

Por cierto, que en la documentación de la API puede verse unos gráficos comparativos de tiempos de resupuesta de cada clase frente a su "competidora" en el JDK...