viernes, 30 de enero de 2009

Balanceo de cargas de EJB en un clúster Java EE. "Collocation Optimization" y cómo afecta al diseño de procesamiento BATCH de alto rendimiento



Me gusta Java EE. Y me gustan los EJB. Siempre en su justa medida, me explico y no me fustiguéis los amantes de Spring Framework, que seguramente llevo más tiempo que vosotros en esto y las viejas costumbres son imposibles de cambiar...

To EJB or not to EJB

El concepto de componente empresarial (EJB) no es algo por lo que rasgarse las vestiduras, siempre que estemos hablando de Stateless Session Beans o Message-Driven Beans y no de Stateful Session Beans, o peor aún, de los infames Entity Beans (por muchas vueltas que le hubieran dado en la especificación 3.0 y lo que queda por dante con la 3.1 que se liberará este año...).

Lo de los Entity Beans es lo de siempre, mapeo O/R y por mucho que haya otras aproximaciones más potentes como Hibernate y similares, a mi lo que me gusta es tener el control. JDBC y SQL, tío. Que trabajar con SQL también es programar, y te aseguro que vas a tener menos problemas, la "magia" se termina pagando, en un proyecto es más el tiempo que se pierde en depurar las cosas raras de los mapeos O/R o conseguir optimizar una consulta o que haga un puto join de 8 tablas en lugar de 300 consultas a base de datos.

Pero me estoy yendo del asunto principal. Sigamos...

La historia de Java EE no empieza en J2EE sino en JPE, allá por 1998 (sorpresa, su primer nombre no fue J2EE). Y antes de eso, en 1997 salió la especificación EJB 1.0 y Servlet 1.0 allá porfinales de 1997:


Sí hay que agradecerle a Spring que haya conseguido poner las pilas a los chicos del JCP, ya que la especificación 3.0 importó algunos de los conceptos de Spring Framework, en concreto lo que respecta al uso de Anotaciones para eliminar los deployment descriptors y simplificar el carajal de desarrollar y mantener un componente, que los servidores de aplicaciones inyecten código en tiempo de despliegue/ejecución. También, desde algo antes, la 2.1, se permite definir interfaz local además de (o en lugar de) la remota, por lo que en tiempo de ejecución cada vez el tema es también más ligero, amén de que los fabricantes de servidores de aplicaciones compatibles cada vez hacen las cosas un poquito mejor...

No quiero hacer un alegato a favor de Java EE o en contra de Spring. Basta con decir que Spring, igual que Struts o Hibernate son lo que son, y estamos en manos de un sólo proveedor (o una sola comunidad pero visto lo visto con JBoss o con el propio Spring, tarde o temprano la gente quiere llevar las judías a casa, lógico). Y Java EE es una especificación con múltiples implementaciones, y no está en manos de lo que a Sun le apetezca hacer con sus cosas, sino que son "estándares" (JSRs) del Java Community Process (JCP), que por mucho que esté liderado por Sun (faltaría plus) no es lo mismo, amigo...
Por cierto, aparte de todo el miedo que ha habido en 2008 con el cambio de política de licencia y releases de Spring (ver artículo de Cmaj), también he oído voces que las nuevas versiones del susodicho cada vez son más complejas y pesadas. Oh, oh, ¿no era la comlejidad de Java EE y la simplicidad que deberían mantener las aplicaciones empresariales lo que hizo a Rod Johnson tirarse a crearlo allá por 2002?

Qué aporta el Contenedor de EJB

El concepto de EJB está ya maduro, pues, y el servidor de aplicaciones / contenedor de EJB provee dos servicios/características fundamentales que los hace muy atractivos:
  • Demarcación transaccional. Pudiendo definir el comportamiento transaccional de cada método de negocio de forma declarativa mediante anotaciones. Vamos, para los de antes, el contenedor de EJBs actúa como MONITOR TRANSACCIONAL.
  • Distribución. La interfaz remota de un EJB es un punto de potencial separación en múltiples instancias o servidores de aplicaciones. Es decir, además de otros mecanismos en la capa de Contenedor HTTP / Motor de servlets, ahí tenemos un punto de potencial balanceo de cargas y failover.

En realidad es lo único que me gusta del modelo. Pero no es poco.

Como decía, como todo, las cosas con mesura. Las dos penúltimas arquitecturas que diseñe para clientes se basaban en un único EJB fachada de toda la lógica distribuida en POJOs normales (o unos pocos EJB, para no mentir). Granularidad muy gruesa, amigo. Me gustan las ventajas pero la sobrecarga excesiva.


Balanceo de cargas y optimizaciones que dificultan las cosas

Pero hay una trampa, siempre hay truco. El balanceo de cargas / failover que nos proporciona un clúster de servidores de aplicaciones a nivel de EJB no es tan automático como parece, sino que puede llevar alguna sorpresa.

Y esto es debido a que está clarito-clarito que la arquitectura Java EE está pensado en el mundo online, orientado a peticiones remotas (de un navegador o de una aplicación remota) que requieren un procesamiento extrarápido y extraescalable. Lo cual hace que por defecto los servidores de aplicaciones se pasen por el forro de los cojones la distribución de cargas entre invocaciones de EJB. Es decir, si ya hemos balanceado en la capa http / servlet, ¿para qué volver a perder el tiempo en balancear la ejecución de cada componente involucrado en esa operativa si se da la premisa de que la sobrecarga de cada invocación remota (calcular_round-robin->serializar->enviar_por_la_red->deserializar->ejecutar->serializar->respuesta_por_la_red->deserializar) seguramente es más costosa que los milisegundos de ejecución de ese método de negocio?

Pues mejor no hacerlo. Y así es cómo actúan casi todos los servidores de aplicaciones cuando están en Clúster. al menos Weblogic, Glassfish y Websphere. E infiero que también el resto: si se invoca a un EJB desde dentro de un servidor de aplicaciones que está en clúster, el servidor de aplicaciones SIEMPRE invocará una instancia local del componente (que está desplegado en esa isntancia) y nunca a una de otro nodo del clúster. Es lo que se llama "optimización por colocación" que no es exactamente lo mismo que "afinidad de servidor".

A modo de ejemplo, pego un extracto de la documentación de Weblogic 10, que tiene su toque de ironía por cierto:

http://download.oracle.com/docs/cd/E11035_01/wls100/cluster/load_balancing.html

WebLogic Server always uses the local, collocated copy of Object A, rather than distributing the client’s calls to other replicas of Object A in the cluster. It is more efficient to use the local copy, because doing so avoids the network overhead of establishing peer connections to other servers in the cluster.

This optimization is often overlooked when planning WebLogic Server clusters. The collocation optimization is also frequently confusing for administrators or developers who expect or require load balancing on each method call. If your Web application is deployed to a single cluster, the collocation optimization overrides any load balancing logic inherent in the replica-aware stub.

If you require load balancing on each method call to a clustered object, see Recommended Multi-Tier Architecture for information about how to plan your WebLogic Server cluster accordingly.


Y, todo esto, son conclusiones para generalizar el comportamiento de los servidoresd e aplicaciones líderes del mercado, pero podría no ser así con el servidor de aplicaciones que hayas elegido...

Pero... ¿y la especificación EJB / Java EE no deja clarito estos comportamientos?

Pues lamentablemente no, curiosamente la especificación no dice absolutamente nada de cómo debe comportarse un clúster de servidores de aplicaciones. Bueno, miento, en realidad lo dan por sentado: los "contratos" que establece la especificación que deben cumplir los fabricantes de servidores de aplicaciones indican cómo debe esperar un diseñador/desarrollador que se comporte su aplicación y sus componentes. Pero en el alto nivel, nada de balanceo. Volvemos a lo mismo que decía antes con la magia del mapeo O/R o con las implicaciones del RMI/IIOP o con las curiosidades del protocolo HTTP: los pollos de las especificaciones piensan que un "desarrollador" no debe preocuparse por esas cosas. Pues mira por dónde, yo digo que sí.

Y también hay "zonas muertas" como si el EJB Timer Service debe disparar un trigger sólo en una instancia del clúster o en todas ellas (esto lo digo como curiosidad)...

En cualquier caso, créeme, lo que cuento arriba no es sólo válido para Weblogic, también para el resto, me juego una caña.


Procesamiento por lotes (en batch) de enormes volúmenes de datos

Leches, os diréis, ¿y esta chuminada en qué me afecta a mi? Si parece que es lo correcto ¿no?

Sí, en el 90% de los casos. Es decir, como digo en un procesamiento online normal así es. Pero ¿y qué hay de un peazo de proceso batch para tratar en chuncks millones de registros o hacer procesamientos computacionalmente complejos? ¿cómo beneficiarnos de las N máquinas del clúster en este tipo de procesamiento?

Pues, salvo que utilicemos aproximaciones menos transparentes (que desaconsejo), la única forma de hacerlo es que la clase que invoca al EJB esté fuera del clúster, en otro servidor de aplicaciones, o clúster administrativo separado.

Incluso en este caso, hay que tener cuidado porque en la misma URL de Weblogic que referencio, hablan de la "Transactional Collocation", es decir dentro de una misma transacción, si se invoca a métodos de negocio del mismo EJB aunque nos hayamos asegurado de que sea remoto... el muy cabrito invoca siempre a la misma instancia del clúster elegida en el round-robin para la primera invocación.

O sea, que la única forma de balanceo real y efectiva es que cada invocación a EJB esté en una transacción distinta (o que el método esté demarcado como REQUIRESNEW). Para un procesamiento batch basado en tratamientos de lotes (o chuncks) de información, esto nos sirve. Es decir, tenemos que procesar 20.000.000 de filas, y -por si lo estábais pensando- es una gilipollez intentar hacerlo en una sola transacción, de modo que la estrategia más ampliamente utilizada es hacerlo en "pequeñas" transacciones, cada una de ellas procesando por ejemplo 10.000 registros. Bien, en este escenario es perfectamente viable puesto que los requerimientos "de negocio" casualmente coinciden con las capacidades del clústering descrito. Siempre que el "master" o "controller" del proceso batch esté fuera del clúster donde se ejecutan los componentes de tratamiento de cada lote.

Últimamente me he hecho experto en procesamiento batch en Java EE. Que por cierto es casi un mundo virgen, qué curioso. Salvo por Quartz como único planificador medianamente serio (ejem si lo comparamos con TWS, anda que no nos queda...), y Spring Batch como único intento de framework para batches de alto rendimiento (aunque no lo recomiendo, sinceramente, aparte de que está muy-muy verde, tiene toda la pinta de que evoluciona hacia el Spring Integrator) con perdón del fantástico artículo High-volume Transaction Processing in J2EE, cuyas bases son impecables pero si tenemos cuidado con la paja mental que acabo de soltar y cuyo framework planteado pego a continuación:

Aunque, ya que estamos, si de verdad estamos hablando de procesamiento batch de eTL de grandes volúmenes de información, dejémosnos de Java EE y vayamos al siguiente nivel de abstracción, los Servidores de Integración (como Java CAPS, que incorpora un bonito módulo de eTL) o herramientas de eTL independientes. Aunque por cierto, en el caso de Java CAPS sospecho que el procesamiento "distribuido" de esas Collaborations de transformación no es tan distribuido, al fin y al cabo una Colaboración se implementa por debajo como un EJB, así que...


¿Y todo esto me afecta para algo o no?

Pues tú sabrás, tú eres el arquitecto :-)

En el 90% de las situaciones no, estáte tranquilo. Tú o tus compañeros de Sistemas Medios han diseñado seguro-seguro una arquitectura que balancea a nivel de motor de servlets, o mejor aún, pode delante de él, con balanceadors de cargas delante de los Apaches para las peticiones de los navegadores.

Pero en situaciones "no tan online" o en las que tu sistema hace procesamiento "de verdad" (y no las puñeteras aplicaciones de gestión que en realidad son las bancas online, comercios electrónicos y otras cosas que nos creemos que son muy avanzadas). O si tienes que precalcular informes estadísticos del último mes, o generar ficheros de extractos para ser descargados por todos tus clientes al día siguiente, no pretenderás hacerlo online ¿verdad? Y aunque la respuesta sea "sí", probablemente te gustaría no infrautilizar los flamantes N servidores que te han puesto ahí y cuyo balanceo "por delante" presupone que hay una homogeneidad en la duración y recursos consumidos por cada operativa o transacción...

También es cierto, que un buen particionamiento de los chunks es la base de un buen proceso batch, y que sólo son paralelizables tareas en las que hay más carga de CPU de tu servidor que de la base de datos que tienes por detrás. Porque como sea ese último caso, ya puedes paralelizar en Java, que el problema no lo tienes ahí... :-)

Pero el rollo de los particionamientos, chunks y otras aves sería motivo de otro artículo bien gordote... (para una introducción, lee el enlace que he referencio arriba, lo pego otra vez: http://www.devx.com/Java/Article/20791/0/page/1 )


P.D: Cada vez me cuesta más encontrar tiempo y fuerzas para escribir...

viernes, 2 de enero de 2009

Historia de versiones del JDK y conceptos de rendimiento en Java

Esta vez simplemente apunto unos enlaces a la Wikipedia.

Son muy-muy buenas recopilaciones aunque la entrada sobre "Java performance" está algo desactualizada y algunas de las optiomizaciones que marca como "futuras" ya están disponibles en Java 6u10 (y otras fueron introducidas en la 6u6-p.

Y no sólo son buenas referencias por su contenido en si mismo, sino porque la bibliografía a la que asimismo referencian es tremenda. Enhorabuena a sus autores: