Paralelizando procesos batch.

En los últimos tiempos me ha tocado optimizar algunos procesos batch, para hacerlos mas rápidos.

En general los proceso que he visto, son de la forma.

INICIALIZACION
PARA UN CONJUNTO GRANDE DE REGITSROS
..PROCESO UN REGISTRO
FIN PARA
AJUSTES FINALES

Una forma "fácil" de acelerar el procesamiento de muchos registros, es aprovechar a ejecutar varias tareas en paralelo, lo cual en los procesadores actuales que tienen muchos Cores o computadoras con varios procesadores.
Si bien esto en principio parece fácil, aparecen nuevos problemas que hay que tener en cuenta.

El hacer paralelos nuestros programas es algo que vamos a tener que aprender a hacer pues en los próximos años va a ser cada vez mas común.

En forma general, el programa anterior puede quedar de la siguiente forma

INICIALIZACION
PARA UN CONJUNTO GRANDE DE REGISTROS
..SI CANTIDAD DE PROCESOS SOMETIDOS >= MAX_CANTIDAD_PROCESOS_SIMULTANEOS ==> ESPER
..PROCESO REGISTRO (EN BACKGROUND O SOMETIDO).
FINALIZACION

Algunas de las cosas que hay que tener en cuenta para el proceso que va a someterse son:

EL PROCESO DEBE DEJAR TODO COMO LO ENCONTRÓ (a menos de las modificaciones propias del proceso).
Si crea directorios o archivos y no va a usarlos mas, debe borrarlos. Si graba alguna tabla temporal, debe borrarlo.

CONCURRENCIA.
El proceso debe trabajar con recursos separados a los demas procesos. Esto es bastante difícil de lograr, pues el programa que corre es idéntico, por lo que lograr que no usen los mismos recursos se debe lograr a través de utilización de claves o agregando Identificadores ficticios. Por ejemplo, si se utiliza el FileSystem, cada proceso debe trabajar en un directorio diferente, diferenciada por algun numero.
Si va a utilizar archivos, también debe hacer lo mismo, generando archivos con diferentes nombres.

De la inicializacion puede ser necesario pasar algo de la misma al proceso sometido.

El motivo de esto fue que teníamos un programa principal, que era el que llamaba a los demás y cuando terminaba

LOCKS
Por lo dicho anteriormente, puede ser necesaria la utilización de numeradores, haciendo que el proceso pida un numero. Esto puede realizarse por la base de datos (con autonumber) o teniendo una tabla para ese fin.
Es conveniente que el proceso solicite un numero y rápidamente realice commit (si es que pueden perderse números de la secuencia). Esto es porque sino esa tabla se va a convertir en un cuello de botella de todos los procesos.
Si fuera necesario, es conveniente la utilización del EXECUTE IN A NEW LUW, propiedad muy útil para este tipo de procesos.
También en conveniente minimizar el update de grupos de registros. Por como esta generado el código Genexus, muchas veces mas conveniente hacer

for each
..
pActualizoRegistro(Clave, &valor)
..
endfor

que

for each
..
AttSecundario=&Valor
..
endfor

DEADLOCKS.
Los deadlocks se dan cuando dos procesos pelean por recursos y cada uno de ellos tienen algo que el otro necesita. Para evitar este tipo de problemas, no hay soluciones mágicas, pero algunas reglas básicas, son:
* tratar de bloquear siempre en el mismo orden
Si el proceso tiene que actualizar 2 tablas (por ejemplo TABLA1 y TABLA2) en varios lugares del codigo, asegurarse que siempre bloquee primero TABLA1 y luego TABLA2, de forma que no producir deadlocks
* tratar de identificar alguna forma de que los procesos trabajen con conjunto disjuntos de recursos
en caso de una tabla, hacer que los procesos actualicen los registros de una clave especifica, solo usada por dicho proceso.
en caso de archivos, trabajar con nombres de archivos diferentes o con directorios diferentes por proceso.
* Liberar los recursos tan pronto se pueda.
En el casos de archivos, hay que cerrarlos lo antes posible. Borrarlos cuando ya no se necesiten mas.
El el caso de registros modificados, hacer commit lo antes posible (respetando la logica transaccional, no agregar commit en todos lados!!).

EVITAR EL CONSUMO INNECESARIO.
Al ser procesos que van a ser ejecutados muchas veces y en paralelo, hay que pensar que pequenos detalles, como por ejemplo mensajes innecesarios, pueden perjudicar la performance de la solucion.

REVISAR LA LÓGICA DEL PROCESO EN FORMA DETALLADA.
Chequear en el código que se ejecuta, cuando puede estar dependiendo de otros registros. Por ejemplo si el proceso chequea que no existan otros registros en un estado X, y el proceso cambia el estado, van a aparecer problemas cuando se realice dicho control en dos procesos en forma simultanea.

Seria bueno contar con alguna herramienta que ayudara a detectar posibles anomalias en el codigo, pero por el momento no es muy fácil.

Puede interesar ver tambien : Paralelizando Procesos

Comentarios

  1. Excelente Enrique.
    En donde trabajo manejamos procesos paralelos con GeneXus hace más de 10 años (Puede que sean 20 años o más).
    Los apuntes mencionados son todo correctos.
    Otro tema también que hay que tener en cuenta son los IO, en el momento que comience a correr las cosas en paralelo, no solo tendrás problemas con la lógica de la base de datos, sino que comenzarás a encontrarte que tendrás que escalar hasta detalles mucho más pequeños.
    Por ejemplo, tener en consideración el IO de placas de red, de memoria, de disco duro, cómo está particionada la base de datos, el tema de cómo se maneja o administra y "tunnea" el motor interno de la base de datos.
    Sobre herramientas, por un lado nosotros usamos JMX y WMI con GeneXus y algunas adaptaciones para tener más información para analizar, luego usamos las herramientas de monitoreo de sistema operativo (en donde puedes encontrar también contadores de hardware y software) y las proporcionadas por hardware especializado.

    Ahora bien, volviendo al "mundano GeneXus".
    Algo que faltó es que en los procesos se tiene que cambiar la forma de resolver algunas cosas.

    1 - Partición de la información a procesar
    Lo que se hace es analizar el "TODO" y dividir qué cosas procesar sobre cada proceso paralelo, la forma en que esto se hace en ocasionen no es trivial, porque el que reparte las cartas tiene que tener en consideración posibles lockeos que puedan ocasionarse, si existe posibilidad de lockeo debe de buscar repartir el trabajo para un unico hilo para que sea secuencial, aquellos que tengan menos posibilidades de lockeo dejarlo correr en paralelo.
    Cuando se tiene una tabla base que luego deriva en N procesamientos, se puede tomar la idea de partir registros a procesar, dividiendo los rangos de registros a procesar para cada paralelo (Y buscar balancear de alguna forma la ejecución), el analizador tiene que tener en cuenta y "precalcular o estimar" cada registro cuanto puede llevar para buscar balancearlo, si tengo que 90% de los registros procesan muy rapido y un 10% de registros son muy lentos, y corro 10 hilos, tengo que buscar de ese 10% esté repartido en esos 10 hilos y que no queden todos colgados de un mismo hilo.

    ResponderBorrar
  2. 2) Intenta hacer las lecturas al inicio del proceso, intenta manejar en memoria la información (mientras no sea mucha) y aprovecha el sistema de Cache de GeneXus para lecturas de registros que son estáticos.
    Principalmente los DBMS tienen un sistema de cache interno (Planes de ejecución y ultimos resultados por sentencia), o sea, si cada "hilo/proceso" lee los mismos registros al mismo tiempo, el DBMS tiene un poco de ayuda interna, si manejas luego esa misma info en memoria minimizando las lecturas al dbms, lo ayudas.

    3) También ojo con las lecturas dentro de los For Each's, es preferible leer en ocasiones toda una tabla y dejarla en memoria buscando la info en vector/sdt que ir golpeando al DBMs dentro de un For Each. Usar el sistema de Cache de GeneXus puede ayudar también en estos casos, termina siendo memoria y se reutiliza más la información en memoria no almacenando redundancia por proceso, sino por instancia del runtime genexus.

    4) Conoce bien lo que estás haciendo.
    Si sabes que el proceso lo unico que va a hacer es insert de datos a una tabla y que no va a dar duplicados, ve directo al insert, no hagas un For Each para validar que exista para hacer un Update, esto genera un costo extra y en ocasiones el Lock para update lockea más de lo que uno quiere.
    Si conoces que es probable que el registro exista, primero prueba hacer un For Each (sin update dentro), de esta forma no estás bloqueando de más, si realmente existe el registro, entonces si ejecuta el update y si no existe el insert.

    5) Cuando son muchos los cambios, no hay mas remedio, tienes que hacer commit.
    Sucede que internamente los DBMS tienen un "buffer" y van almacenando los cambios que vamos realizando, si no vamos haciendo commit vamos saturando el DBMS, ya que tienen que manejar un log transaccional que va creciendo y los dbms tienen un limite aceptado antes de comenzar a paginar de memoria a disco. Se recomienda manejar contadores internos a los procesos y forzar commit cada X cantidad (Depende el DBMS, y qué tanta carga en paralelo estés ejecutando).
    Esto fuerza en ocasiones de pasar de una lógica de rollback transaccional a una de rollback por estados (tengo que saber que registros en qué tablas depurar en caso que quiera hacer rollback), por lo que es un tema delicado.

    6 ) aprovechemos lo nuevo de GeneXus, Blocking en Insert
    http://wiki.gxtechnical.com/commwiki/servlet/hwiki?Blocking+Clause+in+%27New%27+Command,

    http://wiki.gxtechnical.com/commwiki/servlet/hwiki?Cl%C3%A1usula+Blocking+en+comando+%27New%27,

    http://wiki.gxtechnical.com/commwiki/servlet/hwiki?Block+Insert+Performance,

    Lo mejor es que lean la documentación, depende mucho de los casos, pero es algo muy bueno para el DBMS el enviar la información en bloques.

    Seguramente tenga muchos más tips que ahora no los recuerdo, pero les dejo estos para que si alguien algún día quiere entrar en éste mágico mundo tenga algo de información por donde ir picando.

    ResponderBorrar
  3. David:
    Gracias por los comentarios. Muy buenas las recomendaciones.

    ResponderBorrar
  4. Ya de paso vuelvo a reflotar una vieja pregunta

    ¿Existe algún Framework para Procesamiento?
    http://genexus.shapado.com/questions/existe-algun-framework-para-procesamiento

    Yo se que existe algo así, porque es lo que usamos todos los días en mi empresa, y conozco implementaciones similares según la necesidad de cada empresa.
    Hoy no tenemos a disposición de la comunidad (open source, "freeware" o comercial) que pueda brindar toda la infraestructura básica para hacer éste tipo de procesamiento.

    ResponderBorrar

Publicar un comentario

1) Lee el post
2) Poné tu opinión sobre el mismo.
Todos los comentarios serán leidos y la mayoría son publicados.

Entradas más populares de este blog

Aplicación monolítica o distribuida?

La nefasta influencia del golero de Cacho Bochinche en el fútbol uruguayo

Funcionalidades de GeneXus que vale la pena conocer: DATE Constants.