Arquitectura Batch

¿Cómo ejecutar procesos Batch en una arquitectura open?

A continuación se describen los principales componentes de la arquitectura Batch de IBM, es importante entender las capacidades de cada uno de ellos para replicarlas sobre una arquitectura open basada en contenedores.

  • JCL
  • JES
  • Programas de aplicación (COBOL, PL/I, etc.)
  • Datos (Ficheros y Bases de Datos.)

JCL

Podemos pensar en un JCL como un antepasado lejano de un DAG (Directed Acrylic Graph), es un conjunto de sentencias, heredadas de la tecnología de las fichas perforadas, que definen el proceso y la secuencia de pasos a ejecutar.

En el JCL vamos a encontrar las características básicas del proceso o job (nombre, tipo, prioridad, recursos asignados, etc.), la secuencia de programas a ejecutar, las fuentes de información de entrada y qué hacer con los datos de salida del proceso.

Las principales sentencias que encontraremos en un JCL son las siguientes;

  • Una ficha JOB, donde se define el nombre del proceso y sus características
  • Una o varias fichas EXEC con cada programa que debe ser ejecutado
  • Una o varias fichas DD, con la definición de los ficheros (Data Sets) utilizados por los programas anteriores
//JOB1    JOB (123),CLASS=C,MSGCLASS=S,MSGLEVEL=(1,1),NOTIFY=&SYSUID
//*
//STEP01   EXEC PGM=PROGRAM1
//INPUT1   DD   DSN=DEV.APPL1.SAMPLE,DISP=SHR
//OUTPUT1  DD   DSN=DEV.APPL1.CUOTA,
//              DISP=(NEW,CATLG,DELETE),VOLUME=SER=SHARED,
//              SPACE=(CYL,(1,1),RLSE),UNIT=SYSDA,
//              DCB=(RECFM=FB,LRECL=80,BLKSIZE=800)
//*

JES

El JES es el componente (subsistema) del Z/OS que se encarga de procesar el Batch. Realiza dos tareas principales:

  • La planificación (scheduling) de los procesos Batch
    • Asignación del proceso a una clase o iniciador (los jobs pueden asignarse a colas específicas)
    • Definir la prioridad del proceso
    • Asignar/limitar los recursos asignados al proceso (memoria, tiempo, etc.)
    • Controlar el flujo de ejecución de los pasos (STEPs) del proceso
  • La ejecución de los programas
    • Validación de las sentencias del JCL
    • Cargar los programas (COBOL, PL/I) en memoria para su posterior ejecución
    • Asignación de los ficheros de entrada/salida a los nombres simbólicos definidos en los programas de aplicación COBOL PL/I
    • Logging

Programas de aplicación

Programas, generalmente codificados usando COBOL, que implementan la funcionalidad del proceso.

El ejecutable resultado de la compilación del código fuente se almacena como un miembro de una librería particionada (PDS). Las librerías desde donde cargar los programas se identifican mediante una ficha específica en el JCL (JOBLIB / STEPLIB).

El JES invocará al programa principal del proceso (definido en la ficha EXEC del JCL) y este a su vez podrá llamar a distintas subrutinas de manera estática o dinámica.

Datos

El acceso a los datos se realiza principalmente mediante la utilización de ficheros (Data Sets) y Bases de Datos relacionales (DB2).

Los ficheros de entrada/salida se definen en los programas con un nombre simbólico.

         SELECT LOAN ASSIGN TO "INPUT1"
         ORGANIZATION IS LINE SEQUENTIAL
         ACCESS IS SEQUENTIAL.

La asignación de los nombres simbólicos a ficheros de lectura/escritura se realiza en el JCL, mediante la ficha DD.

//*
//INPUT1   DD   DSN=DEV.APPL1.SAMPLE,DISP=SHR

Los ficheros generalmente son de tipo;

  • Secuencial, los registros deben ser accedidos de manera secuencial, es decir, para leer el registro 1.000 previamente han leerse los anteriores 999 registros
  • VSAM. Existen distintos tipos de ficheros VSAM, siendo posible acceder a los registros directamente mediante mediante una clave (KSDS) o número de registro (RRDS)

En el caso de que el programa necesite acceder a una Base de Datos (DB2), la información necesaria para la conexión (seguridad, nombre de la Base de Datos, etc.) se pasa como parámetros en el JCL.

Migración del Batch mainframe a una arquitectura open

Para la migración de los procesos Batch construidos sobre tecnología mainframe vamos a replicar la funcionalidad descrita anteriormente sobre un clúster Kubernetes.

Es necesario por tanto;

  1. Convertir los JCLs (JOBs) a una herramienta o framework que permita la ejecución de workflows sobre una plataforma Kubernetes
  2. Replicar las funcionalidad del JES para permitir el scheduling y ejecución de los programas COBOL PL/I sobre el cluster Kubernetes
  3. Recompilar los programas de aplicación
  4. Permitir el acceso a los datos (ficheros y Bases de Datos)

1 - Conversión JCLs

¿Cómo convertir un JCL mainframe en un DAG?

A continuación se muestra un sencillo ejemplo de cómo convertir un JCL a un workflow de Argo (yaml).

Pueden utilizarse otros frameworks o herramientas que permitan la definición de DAGs y tengan una integración nativa con la plataforma Kubernetes.


apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
 name: batch-job-example
spec:
 entrypoint: job
 templates:
   - name: job
     dag:
       tasks:
         - name: extracting-data-from-table-a
           template: extractor
           arguments:
         - name: extracting-data-from-table-b
           template: extractor
           arguments:
         - name: extracting-data-from-table-c
           template: extractor
           arguments:
         - name: program-transforming-table-c
           dependencies: [extracting-data-from-table-c]
           template: exec
           arguments:
         - name: program-aggregating-data
           dependencies:
             [
               extracting-data-from-table-a,
               extracting-data-from-table-b,
               program-transforming-table-c,
             ]
           template: exec
           arguments:
         - name: loading-data-into-table1
           dependencies: [program-aggregating-data]
           template: loader
           arguments:
         - name: loading-data-into-table2
           dependencies: [program-aggregating-data]
           template: loader
           arguments:
   - name: extractor
   - name: exec
   - name: loader

Proceso Batch de ETL dividido en tres fases:

  • La extracción de información desde un conjunto de tablas DB2 (template extractor)
  • La transformación y agregación de las mismas mediante programas de aplicación COBOL (template exec)
  • La carga de la información resultante (template loader)

Cada JOB se transforma en un DAG en el que se define la secuencia de pasos (STEPs) a ejecutar y sus dependencias

Es posible definir “templates” con los principales tipos de tareas Batch de la instalación (descarga datos DB2, ejecución de programas COBOL, transmisión de ficheros, conversión de datos, etc.) de manera equivalente a los PROCS en mainframe

Cada STEP dentro del DAG es ejecutado en un contenedor independiente en un cluster Kubernetes

Las dependencias se definen a nivel de tarea en el DAG, pudiendo establecer árboles de ejecución no lineales

Resultado de la ejecución del proceso representado de manera gráfica en Argo

2 - Replicar JES

¿Cómo replicar la funcionalidad del JES?

Si está familiarizado con The Twelve-Factor App, conocerá que uno de sus principios pasa por independizar el código de aplicación de cualquier elemento que pueda variar en el despliegue del mismo en distintos entornos (pruebas, calidad, producción, etc).

Guardar la configuración en el entorno

La configuración de una aplicación es todo lo que puede variar entre despliegues (entornos de preproducción, producción, desarrollo, etc)

The Twelve-Factor App. III Configuraciones

Podemos asimilar la información contenida en los JCLs a ficheros de configuración (config.yml) que contendrán la información necesaria para la ejecución del código en cada uno de los entornos definidos en la instalación (recursos asignados, conexión a la base de datos, nombre y localización de los ficheros de entrada/salida, nivel de detalle del log, etc).

Para entender qué funcionalidad debemos replicar vamos a dividir un JCLs en dos partes:

  • Ficha JOB
  • Ficha EXEC y DD

//JOB1    JOB (123),CLASS=C,MSGCLASS=S,MSGLEVEL=(1,1),NOTIFY=&SYSUID
//*
//STEP01   EXEC PGM=BCUOTA
//INPUT1   DD   DSN=DEV.APPL1.SAMPLE,DISP=SHR
//OUTPUT1  DD   DSN=DEV.APPL1.CUOTA,
//              DISP=(NEW,CATLG,DELETE),VOLUME=SER=SHARED,
//              SPACE=(CYL,(1,1),RLSE),UNIT=SYSDA,
//              DCB=(RECFM=FB,LRECL=80,BLKSIZE=800)
//*

Ficha JOB

En la ficha JOB, vamos a encontrar la información básica para el “scheduling” del proceso en Kubernetes:

  • Información para la clasificación del JOB (CLASS). Permite separar los tipos de JOBs en función de sus características y asignarles parámetros de ejecución distintos
  • Definir la salida por defecto (MSGCLASS)
  • El nivel de información que se enviará a la salida (MSGLEVEL)
  • Cantidad de memoria máxima asignada al proceso (REGION)
  • Tiempo máximo estimado para la ejecución del proceso (TIME)
  • Información de usuario (USER)
  • Etc.

En Kubernetes, el componente kube-scheduler será el encargado de realizar estas tareas, buscando un nodo con las características adecuadas para la ejecución de los pods recién creados. Existen distintas opciones;

  • Los procesos Batch pueden usar el Job controller de Kubernetes, este ejecutara un pod por cada paso (STEP) del workflow y lo detendrá una vez finalizada la tarea
  • En caso de necesitar funcionalidad más avanzada, por ejemplo para la definición y priorización de distintas colas de ejecución, pueden utilizarse “schedulers” especializados como Volcano
  • Por último, es posible desarrollar un controlador kubernetes que se adapte a las necesidades específicas de una instalación

Ficha EXEC y DD

En cada paso (STEP) del JCL podemos encontrar una ficha EXEC y varias fichas DD.

En estas se define el programa (COBOL) a ejecutar y los ficheros de entrada/salida asociados. A continuación se muestra un ejemplo de cómo transformar un paso (STEP) de un JCL.

---
stepname: "step01"
exec:
 pgm: "bcuota"
dd:
 - name: "input1"
   dsn: "dev/appl1/sample.txt"
   disp: "shr"
   normaldisp: "catlg"
   abnormaldisp: "catlg"
 - name: "output1"
   dsn: "dev/appl1/cuota.txt"
   disp: "new"
   normaldisp: "catlg"
   abnormaldisp: "delete"

Para la ejecución de programas, las sentencias EXEC y DD se convierten a YAML. Esta información se pasa al controlador d8parti, especializado en la ejecución de programas Batch.

El controlador d8parti actúa como el JES:

  • Se encarga de validar la sintaxis del fichero YAML
  • Asigna los nombres simbólicos en los programas COBOL a ficheros físicos de entrada/salida
  • Carga el programa COBOL en memoria para su ejecución
  • Escribe información de monitorización/logging

3 - Compilación programas

¿Cómo reutilizar los programas de aplicación del mainframe?

Los programas COBOL PL/I de la plataforma mainframe son directamente reutilizables sobre la plataforma técnica de destino (Linux).

Como hemos comentado anteriormente, el módulo d8parti será el encargado de:

  • Inicializar el “runtime” del lenguaje (i.e. COBOL)
  • Asignar los ficheros de entrada/salida a los nombres simbólicos del programa
  • Cargar y ejecutar el programa principal (definido en la ficha EXEC del JCL)

Este programa principal puede realizar distintas llamadas a otras subrutinas mediante una sentencia CALL, estas llamadas son gestionadas por el “runtime” del lenguaje utilizado

Podemos visualizar este funcionamiento como un árbol invertido

Los programas compilados pueden almacenarse en un directorio compartido y cargarse en tiempo de ejecución (CALL dinámico) replicando el funcionamiento del mainframe IBM (STEPLIB).

Sin embargo, existe la posibilidad de cambiar el comportamiento anterior e implementar un modelo de contenedores inmutables, que presenta ciertas ventajas respecto al modelo anterior. En este caso, el árbol de ejecución anterior debería descomponerse funcionalmente en uno o varios “repos”.

Las modificaciones en alguno de los componentes de estos “repos” generan una nueva versión del mismo y la consiguiente regeneración del contenedor o contenedores que lo utilicen.

Con esta estrategia conseguimos:

  • Facilitar el proceso de desarrollo y prueba de las aplicaciones
  • Permitir la introducción progresiva de cambios en el sistema, eliminando riesgos
  • Posibilitar la portabilidad de los procesos a distintas plataformas (On-prem, On-cloud)

Una vez aislada una función de negocio en un contenedor con una interfaz estándar, este puede modificarse o re-escribirse en cualquier otro lenguaje de programación y desplegarse de manera transparente sin afectar al resto del sistema.

4 - Acceso a los datos

¿Cómo acceder a los datos almacenados en ficheros y bases de datos SQL?

Ficheros

En la arquitectura mainframe un Data Set es un conjunto de registros relacionados que se almacenan en una UNIT / VOLUME.

Para entender estos conceptos hay que volver a retroceder en el tiempo, cuando los dispositivos de almacenamiento masivo estaban basados en cintas o cartuchos. Así, cuando un proceso necesitaba acceder a la información de un Data Set, la cinta o cartucho tenía que ser “montada” en una UNIT y era identificado con un nombre o VOLUME.

Hoy en día la información reside en disco y no necesita ser montada/desmontada para su acceso, podemos asimilar los VOLUMEs del mainframe a un “share” de un NFS.

Pueden definirse distintos “puntos de montaje” al contenedor aplicativo para aislar la información y proteger su acceso (por ejemplo por entorno, desarrollo y producción). El acceso se ofrece a los contenedores mediante SDS (Software Define Storage) con el objetivo de poder desacoplar el almacenamiento del proceso.

Por último, los ficheros del mainframe deben transmitirse y convertirse (EBCDIC) a ficheros “Linux” para su utilización en la plataforma destino. Este proceso puede automatizarse mediante herramientas/productos de mercado o usando procesos Spark de conversión de datos.

Base de Datos

El principal motor de Base de Datos del mainframe es el IBM DB2, aunque se siguen utilizando de manera residual otro tipo de productos (IMS DB, IDMS, Adabas).

En el caso de las aplicaciones DB2. Existen dos grandes estrategias bien diferenciadas para el acceso a los datos:

  • Réplica de los datos DB2 sobre una nueva Base de Datos SQL (por ejemplo, PostgreSQL)
  • Acceso al DB2 de la plataforma mainframe desde el cluster Kubernetes mediante el proxy de convivencia

En el primer caso, los datos de las tablas DB2 se replican sobre un nuevo gestor usando herramientas de replicación (i.e. IBM CDC) o procesos ETL (por ejemplo usando Spark).

Las sentencias SQL DB2 (EXEC SQL … END-EXEC.) se pre-compilan para poder acceder al nuevo gestor de base de datos, es necesario realizar pequeños cambios en el SQL para adaptarlo, sin embargo existe metodología y herramientas para la realización de este proceso de manera automática:

  • Réplica del DDL (Tablespaces, Tablas, Índices, Columnas, etc.)
  • Adaptación tipos de datos DATE/TIME
  • SQLCODEs
  • Utilidades de carga y descarga
  • Etc

El principal inconveniente de esta estrategia es la necesidad de mantener la integridad de datos del modelo, generalmente el modelo de integridad referencial de la base de datos no está definido en el gestor DB2, debe deducirse por la lógica de las aplicaciones.

Todos los procesos de lectura/actualización que accedan a las tablas afectadas (independientemente de si son Batch/Online) deben ser migrados a la nueva plataforma o definir un mecanismo de convivencia/réplica entre plataformas (Mainframe DB2 / Next-gen SQL) que mantenga la integridad de los datos en ambas hasta la finalización del proceso de migración.

Esta convivencia se hace especialmente crítica en el caso de tablas con datos maestros accedidos por un elevado número de aplicaciones.

En caso de optar por seguir accediendo al DB2 mainframe mediante el proxy de convivencia no es necesario mantener la integridad de los datos entre plataformas (Mainframe / Next-gen). Los procesos (Online o Batch) se pueden migrar individualmente y de manera progresiva (canary deployment)

Una vez finalizado el proceso de migración de los programas de aplicación (Online y Batch) puede realizarse una migración de datos hacia una nueva Base de Datos en la plataforma destino (Next-gen).