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...

12 comentarios:

angelborroy dijo...

Lo cierto es que el artículo es muy interesante e incluso divertido. Pero al final me da por pensar que has montado un pollo muy extraño solo con tal de no usar SQL Loader. Java no sirve para todo. Y menos todavía Java en un contenedor JEE.

Como digo, tan solo mi impresión después de una lectura rápida.

Martín dijo...

Un artículo muy interesante. Además creo que comparto contigo el "cariño" por J2EE, y el sano escepticismo sobre O/R mappers, depencency injection frameworks y demás etc.

Durante los últimos meses he estado trabajando en un proyecto realmente importante de procesamiento batch donde la arquitetura está basada en Spring+Hibernate, y bueno, me llevaría todo el día comentar la cantidad de "peculiaridades"...

En fin, en el pasado he trabajado también en algún otro sistema que comparte similitudes con lo que explica. En mi caso se trataba de un sistema que aunque no era un batch, funcionaba de manera similar, y se trataba de dividir la carga de procesado de carteras de inversión entre todos los nodos del cluster. Nosotros lo que hacíamos era ligeramente diferente, ya que sólo utilizabamos un único servidor de aplicaciones. Sobre el, creamos un framework master-slave propio para la distribución de trabajos dentro del cluster, y para ello, bueno, lo típico, invocaciones directas a los EJBs través de RMI y después esos "slaves" ya se encargaban de reinvocar a todos los servicios de manera "collocated".

No muy ortodoxo pero funcionaba realmente bien, proceso rapidísimo y administradores contentos por no tener que mantener otro cluster separado.

serverperformance dijo...

Tampoco te pongas así ;-) SQL Loader tampoco sirve para todo...

Aunque me hacho pensar tu comentario (hay veces que todos llevamos orejeras y sólo enfocamos las soluciones de una manera).

Algunos casos típicos que se me ocurren que no pueden hacerse online ni tampoco con un SQLLoader:

- Consolidación de sistemas de información, dispersos y heterogéneos, accedidos mediante JDBC, CICS, MQSeries y otros mecanismos distintos.

- Precálculos estadísticos de cuadro de mandos e informes, o pregeneración de PDFs.

- Pregeneración de ficheros para ser descargados por todos tus clientes al día siguiente (por ejemplo los ficheros de extractos de movimientos de cuenta en norma AEB 43 para empresas).

- Transformación masiva de información (eTL) en escenarios heterogéneos (el origen es un fichero XML y el destino una BDD relacional, o el origen una BDD relacional y el destino un sistema accedido mediante Tuxedo, etc).

- Generación de contenidos multimedia (video, etc)

...

serverperformance dijo...

@Martin: gracias por tu comentario!

Una pregunta, cuando comentas "framework master-slave propio para la distribución de trabajos dentro del cluster"... ¿el master estaba en la misma instancia del servidor de aplicaciones? ¿y las invocaciones a EJB se balanceaban?

Te lo pregunto porque, o te he entendido mal, o entoces sí que os funcionó el balanceo de cargas y eso da al traste con mi explicación :-) (¿qué servidor de aplicaciones utilizábais por cierto?

Por cierto, también hay otra forma de hacer todo esto sin necesidad de una instancia administrativa, y que es no tener un master (muerto el perro se acabó la rabia), sino seguir una estrategia "process and mark" de modo que cada nodo del clúster coge N registros de entrada, los marca como "en proceso" y los procesa y los marca como procesados, y así sucesiamente hasta que no queden registros sin marca. De modo que no hay un orquestador, sino unos procesos lanzados por ejb timer por startup classes que van haciendo su trabajo en paralelo...

Martín dijo...

No, no hacía balanceo de carga, es lo que tú comentas, por defecto todo viene collocated. Nosotros utilizabamos WebLogic y Jboss.

El caso es que esa parte del sistema era un poco fuera de la ley de JEE. Todo funcionaba en el mismo cluster, pero al arrancarlo uno de los servidores se hacía con el rol de maestro. A partir de ese momento, pues nada, los otros servidores se registraban como esclavos, y el maestro simplemente consultaba el trabajo a hacer, y lo repartía entre los diferentes esclavos.

Por lo tanto, el balanceo no existía como tal, si no más bien un map and reduce casero. Y nada, lo dicho, el maestro invocaba a los EJBs de los servidores esclavos directamente usando ya las particularidades de WebLogic y Jboss.

angelborroy dijo...

Lo cierto es que tampoco era mi intención descartar de un plumazo tus teorías, que insisto me parecen muy enfocadas.

Como dices, en ocasiones nos ponemos las orejeras de burro y tiramos por caminos escabrosos. Para muchos de los ejemplos que has incluido existen herramientas especializadas que pueden servir mejor que SQL Loader o Java: Data Mining, Distiller...

El caso es no empeñarse en hacer las cosas como uno sabe hacerlas, sino hacerlas como deben hacerse.

serverperformance dijo...

@angelborroy:

"El caso es no empeñarse en hacer las cosas como uno sabe hacerlas, sino hacerlas como deben hacerse."

Muy interesante comentario, pero como siempre el tema está en balancearlo. Es decir, lo óptimo es enemigo de lo bueno, y también conozco compañeros con la tendencia contraria: estar siempre aprendiendo o aplicando cosas nuevas, a veces me da la sensación de que por aburrimiento o por una tendencia natural a pensar que los que vinieron antes de mi no tienen ni p.i.

Pero, siempre hay que balancear si puede que eso tenga más costes que hacerlo "como sabemos", tanto en la ejecución como en su mantenibilidad.

¿Cómo sabe hacer las cosas el equipo? ¿y el arquitecto/diseñador? ¿optamos por la solución que "sabemos que funciona" o por la que "hemos leído que funciona"?

Sin ir más lejos, el producto en el que llevo metido más de un año tiene una parte que es un portal transaccional. El equipo que lo desarrolla estaba formado en YUI+Struts+Spring+Hibernate (Y otros 70 compañeros que potencialmente en algún momento pueden entrar a echar una mano al proyecto también) y no en ICEFaces+JSF+EJB+JDO que es como a mi me hubiera gustado... así que la montaña fue a Mahoma y el portal es YUI+Struts+Spring+Hibernate.

Me cago en la p*** madre de Hibernate de vez en cuando al ver algunas barbaridades en los accesos a la base de datos, y hemos perdido más tiempo del que me hubiera gustado en optimizar algunos hotspots, pero el coste de formar al equipo de nuevas o de introducir una persona nueva al equipo sería mayor. Por no hablar de la resistencia al cambio...

En fin, me he ido del tema, pero me parece algo muy ineresante.

Equipo dijo...

Me parece excelente el artículo. Comparto plenamente lo dicho. Yo también soy un viejo zorro de la programación y me gusta tener el control en el caso del mapeo a la base de datos. Me he llevado unos "trancazos" con los Wizards y Hibernate. Por eso, prefiero tener el control. Además, va a llegar un momento en que nos vamos a convertir en configuradores y no programadores. Siempre habrá que programar. Por lo menos en los próximos 50 años. Creo que para ese entonces, ya seremos historia.

Jaime Soto (Venezuela) dijo...

Me parece excelente el artículo. Comparto plenamente lo dicho. Yo también soy un viejo zorro de la programación y me gusta tener el control en el caso del mapeo a la base de datos. Me he llevado unos "trancazos" con los Wizards y Hibernate. Por eso, prefiero tener el control. Además, va a llegar un momento en que nos vamos a convertir en configuradores y no programadores. Siempre habrá que programar. Por lo menos en los próximos 50 años. Creo que para ese entonces, ya seremos historia.

serverperformance dijo...

Gracias Jaime por tus palabras. Tengo bastante abandonado el blog :-(

Blogger dijo...

Get daily ideas and methods for making $1,000s per day FROM HOME for FREE.
CLICK HERE TO FIND OUT

Blogger dijo...

SwagBucks is a very popular get-paid-to site.