Journal receivers, journal-based audit, recovery point-in-time, replicacion para alta disponibilidad y gestion avanzada de journals en IBM i.
El journaling es el mecanismo de registro transaccional nativo de IBM i. Captura cada modificacion realizada sobre los datos (INSERT, UPDATE, DELETE) en un log secuencial e inmutable que permite recovery, auditoria y replicacion.
x86 -- Write-Ahead Log (WAL)
IBM i -- Journaling
Journal (*JRN)
Objeto central que recibe y gestiona las entradas. Las tablas se asocian a un journal. Administra la cadena de receivers.
Journal Receiver (*JRNRCV)
Almacena fisicamente las entradas de journal. Se crean en cadena secuencial. Cuando uno se llena, se hace swap a uno nuevo.
Journal Entry
Cada registro individual de cambio. Contiene el tipo de operacion, datos antes/despues, usuario, programa, job y timestamp.
El setup de journaling requiere tres pasos: crear el receiver inicial, crear el journal y luego asociar las tablas al journal con STRJRNPF.
CRTJRNRCV JRNRCV(MILIB/RCVR0001) +
THRESHOLD(500000) +
TEXT('Receiver inicial para MILIB')
/* THRESHOLD: tamano en KB a partir del cual el sistema
genera un mensaje de warning. Con MNGRCV(*SYSTEM) en
el journal, se hace swap automatico al llegar. */CRTJRN JRN(MILIB/QSQJRN) +
JRNRCV(MILIB/RCVR0001) +
MNGRCV(*SYSTEM) +
DLTRCV(*YES) +
RCVSIZOPT(*MAXOPT2) +
MINENTDTA(*NONE) +
TEXT('Journal principal de MILIB')
/* Parametros clave:
MNGRCV(*SYSTEM) - swap automatico de receivers
DLTRCV(*YES) - elimina receivers viejos automaticamente
RCVSIZOPT(*MAXOPT2) - optimiza I/O de receivers grandes
MINENTDTA(*NONE) - datos completos en cada entrada *//* Asociar tablas criticas al journal */
STRJRNPF FILE(MILIB/CLIENTES) +
JRN(MILIB/QSQJRN) +
IMAGES(*BOTH) +
OMTJRNE(*OPNCLO)
STRJRNPF FILE(MILIB/PEDIDOS) +
JRN(MILIB/QSQJRN) +
IMAGES(*BOTH) +
OMTJRNE(*OPNCLO)
STRJRNPF FILE(MILIB/FACTURAS) +
JRN(MILIB/QSQJRN) +
IMAGES(*BOTH) +
OMTJRNE(*OPNCLO)
/* Detener journaling de una tabla */
ENDJRNPF FILE(MILIB/TABLA_TEMP) +
JRN(MILIB/QSQJRN)IMAGES(*BOTH)Graba imagen antes y despues del cambio. Necesario para RMVJRNCHG (reverse recovery).IMAGES(*AFTER)Solo imagen posterior. Menos espacio pero no permite reverse recovery.OMTJRNE(*OPNCLO)Omitir entradas de open/close. Reduce volumen significativamente.JRN(*FILE)Usar el journal por defecto de la tabla (si esta definido).CREATE TABLE en una biblioteca que tiene un journal QSQJRN se journalean automaticamente. No es necesario ejecutar STRJRNPF para tablas creadas via DDL si el journal por defecto esta configurado.Cada operacion sobre una tabla journaleada genera una o mas entradas en el journal. Los tipos mas importantes se identifican por un codigo de journal y un tipo de entrada.
R PTRecord Put (INSERT)Se inserto un registro nuevo en la tablaR UBUpdate Before ImageImagen ANTES del UPDATE (datos originales)R UPUpdate After ImageImagen DESPUES del UPDATE (datos nuevos)R DLRecord DeleteSe elimino un registro de la tablaR BRRead Before UpdateLectura antes de update (record-level access)F OPFile OpenSe abrio el archivo/tablaF CLFile CloseSe cerro el archivo/tablaC SCStart Commit CycleInicio de un ciclo de commitment controlC CMCommitSe confirmo (commit) la transaccionC RBRollbackSe deshizo (rollback) la transaccionJ PRReceiver SwapSe realizo un swap de journal receiver/* Forma clasica: volcar entradas a un outfile */
DSPJRN JRN(MILIB/QSQJRN) +
FILE(MILIB/CLIENTES) +
RCVRNG(*CURCHAIN) +
JRNCDE(R) +
ENTTYP(PT UB UP DL) +
OUTPUT(*OUTFILE) +
OUTFILFMT(*TYPE5) +
OUTFILE(QTEMP/JRNDATA)
/* Luego consultar el outfile */
SELECT * FROM QTEMP.JRNDATA
ORDER BY JOENTT DESC
FETCH FIRST 50 ROWS ONLYEl journal es la herramienta definitiva de auditoria en IBM i. Cada entrada registra no solo los datos modificados, sino el contexto completo: usuario, programa, job, direccion IP y timestamp exacto.
Quien hizo el cambio
USER_NAME identifica al perfil de usuario que ejecuto la operacion.
Desde donde
JOB_NAME muestra el job. En combinacion con la IP del job se puede rastrear el origen.
Con que programa
PROGRAM_NAME registra el *PGM o *SRVPGM que ejecuto la operacion.
Cuando exactamente
ENTRY_TIMESTAMP con precision de microsegundos para cada operacion.
-- Rastrear todos los cambios sobre el cliente 1001
SELECT
ENTRY_TIMESTAMP AS CUANDO,
USER_NAME AS QUIEN,
JOB_NAME AS DESDE_JOB,
PROGRAM_NAME AS PROGRAMA,
JOURNAL_ENTRY_TYPE AS OPERACION,
CASE JOURNAL_ENTRY_TYPE
WHEN 'PT' THEN 'INSERT'
WHEN 'UP' THEN 'UPDATE (after)'
WHEN 'UB' THEN 'UPDATE (before)'
WHEN 'DL' THEN 'DELETE'
ELSE JOURNAL_ENTRY_TYPE
END AS TIPO_LEGIBLE,
ENTRY_DATA
FROM TABLE(
QSYS2.DISPLAY_JOURNAL(
'MILIB', 'QSQJRN',
JOURNAL_CODES => 'R',
JOURNAL_ENTRY_TYPES => 'PT,UB,UP,DL',
OBJECT_NAME => 'CLIENTES',
OBJECT_LIBRARY => 'MILIB',
STARTING_TIMESTAMP => CURRENT_TIMESTAMP - 30 DAYS
)
) AS jrn
ORDER BY ENTRY_TIMESTAMP DESC;-- Dashboard de actividad: operaciones por usuario en la ultima semana
SELECT
USER_NAME AS USUARIO,
OBJECT_NAME AS TABLA,
SUM(CASE WHEN JOURNAL_ENTRY_TYPE = 'PT' THEN 1 ELSE 0 END) AS INSERTS,
SUM(CASE WHEN JOURNAL_ENTRY_TYPE = 'UP' THEN 1 ELSE 0 END) AS UPDATES,
SUM(CASE WHEN JOURNAL_ENTRY_TYPE = 'DL' THEN 1 ELSE 0 END) AS DELETES,
COUNT(*) AS TOTAL_OPS,
MIN(ENTRY_TIMESTAMP) AS PRIMERA_OP,
MAX(ENTRY_TIMESTAMP) AS ULTIMA_OP
FROM TABLE(
QSYS2.DISPLAY_JOURNAL(
'MILIB', 'QSQJRN',
JOURNAL_CODES => 'R',
STARTING_TIMESTAMP => CURRENT_DATE - 7 DAYS
)
) AS jrn
GROUP BY USER_NAME, OBJECT_NAME
ORDER BY TOTAL_OPS DESC;Los journals permiten dos estrategias fundamentales de recovery: forward recovery (aplicar cambios sobre un backup) y reverse recovery (deshacer cambios erroneos).
Forward Recovery (APYJRNCHG)
Reverse Recovery (RMVJRNCHG)
/* Escenario: la tabla CLIENTES se corrompio.
Tenes un backup de anoche y los journals del dia. */
/* Paso 1: Restaurar la tabla desde el backup */
RSTOBJ OBJ(CLIENTES) SAVLIB(MILIB) DEV(*SAVF) +
SAVF(MILIB/SAVNOCHE)
/* Paso 2: Aplicar los cambios del journal desde el ultimo save */
APYJRNCHG JRN(MILIB/QSQJRN) +
FILE(MILIB/CLIENTES) +
FROMENT(*LASTSAVE) +
TOENT(*LAST)
/* Resultado: la tabla queda con todos los datos
hasta la ultima entrada del journal *//* Escenario: a las 14:00 alguien ejecuto
DELETE FROM MILIB.CLIENTES (sin WHERE!) */
/* Deshacer todos los cambios desde las 14:00 */
RMVJRNCHG JRN(MILIB/QSQJRN) +
FILE(MILIB/CLIENTES) +
FROMENT(*LAST) +
TOTIME('2025-01-15-14.00.00')
/* Point-in-time recovery: aplicar hasta un momento especifico */
APYJRNCHG JRN(MILIB/QSQJRN) +
FILE(MILIB/CLIENTES) +
FROMENT(*LASTSAVE) +
TOTIME('2025-01-15-13.59.59')IMAGES(*BOTH). Con IMAGES(*AFTER) solamente se puede hacer forward recovery. Para tablas de produccion criticas, siempre usa *BOTH.El remote journaling permite enviar journal entries a otro sistema IBM i en tiempo real. Es la base de las soluciones de alta disponibilidad (HA) como Precisely MIMIX, iTERA, Quick-EDD y Robot HA.
Replicacion sincrona
La aplicacion espera hasta que la entrada se grabe en ambos sistemas. Maxima proteccion, mayor latencia. RPO = 0.
Replicacion asincrona
La entrada se graba localmente y se envia al remoto en background. Menor impacto en performance. RPO > 0.
/* Agregar remote journal al journal existente */
ADDRMTJRN JRN(MILIB/QSQJRN) +
RMTJRN((MILIB/QSQJRN *RMTSYSNAME SYS_DR)) +
DLTRCV(*YES) +
JRNSTATE(*ACTIVE) +
DELIVERY(*ASYNC)
/* Verificar estado del remote journal */
WRKRMTJRN JRN(MILIB/QSQJRN)-- Consultar estado de remote journals SELECT JOURNAL_NAME, JOURNAL_LIBRARY, REMOTE_JOURNAL_TYPE, REMOTE_JOURNAL_DELIVERY, RELATIONAL_DATABASE_NAME AS SISTEMA_REMOTO, REMOTE_JOURNAL_STATE AS ESTADO, CURRENT_RECEIVER_NAME, CURRENT_RECEIVER_LIBRARY FROM QSYS2.REMOTE_JOURNAL_INFO WHERE JOURNAL_NAME = 'QSQJRN' AND JOURNAL_LIBRARY = 'MILIB';
PostgreSQL -- Streaming Replication
IBM i -- Remote Journaling
Los journal receivers crecen continuamente con cada operacion registrada. Una buena gestion de receivers es esencial para evitar problemas de espacio en disco y mantener la cadena de recovery integra.
/* Swap manual: crear nuevo receiver y cambiar */
CRTJRNRCV JRNRCV(MILIB/RCVR0002) +
THRESHOLD(500000) +
TEXT('Receiver 2 para MILIB')
CHGJRN JRN(MILIB/QSQJRN) +
JRNRCV(MILIB/RCVR0002)
/* El receiver anterior queda "detached" */
/* Eliminar un receiver viejo (verificar que no sea necesario) */
DLTJRNRCV JRNRCV(MILIB/RCVR0001)
/* Solo se puede eliminar si esta detached y
no es necesario para recovery */
/* Ver informacion del journal */
WRKJRN JRN(MILIB/QSQJRN)
WRKJRNA JRN(MILIB/QSQJRN)-- Estado de todos los receivers de un journal
SELECT
JOURNAL_RECEIVER_NAME AS RECEIVER,
JOURNAL_RECEIVER_LIBRARY AS BIBLIOTECA,
CASE ATTACHED
WHEN 'YES' THEN 'ATTACHED (activo)'
ELSE 'DETACHED'
END AS ESTADO,
SIZE AS TAMANO_KB,
NUMBER_JOURNAL_ENTRIES AS ENTRADAS,
FIRST_ENTRY_TIMESTAMP AS PRIMERA_ENTRADA,
LAST_ENTRY_TIMESTAMP AS ULTIMA_ENTRADA
FROM QSYS2.JOURNAL_RECEIVER_INFO
WHERE JOURNAL_NAME = 'QSQJRN'
AND JOURNAL_LIBRARY = 'MILIB'
ORDER BY FIRST_ENTRY_TIMESTAMP;
-- Tamano total de receivers activos
SELECT
SUM(SIZE) / 1024 AS TOTAL_MB,
COUNT(*) AS CANTIDAD_RECEIVERS
FROM QSYS2.JOURNAL_RECEIVER_INFO
WHERE JOURNAL_NAME = 'QSQJRN'
AND JOURNAL_LIBRARY = 'MILIB';DLTRCV(*YES) en el journal, los receivers detached se eliminan automaticamente cuando ya no son necesarios para recovery. Si necesitas mantener receivers para auditoria a largo plazo, usa DLTRCV(*NO) y gestiona la eliminacion manualmente, idealmente despues de salvarlos a cinta o medio externo.El journaling tiene un costo de I/O adicional. Cada operacion de escritura genera al menos una entrada en el receiver. Sin embargo, este overhead es generalmente bajo (2-5%) y se puede optimizar con las configuraciones adecuadas.
Journal caching
El sistema agrupa multiples entradas en un solo write fisico al receiver. RCVSIZOPT(*MAXOPT2) mejora esto aun mas, reduciendo operaciones de I/O.
IMAGES(*AFTER) vs *BOTH
*AFTER usa la mitad del espacio en el receiver pero no permite reverse recovery. Para tablas de alto volumen no criticas, puede ser una opcion.
OMTJRNE(*OPNCLO)
Omitir entradas de open/close reduce significativamente el volumen. Las entradas F OP y F CL rara vez se necesitan para recovery.
MINENTDTA(*NONE)
No minimizar datos de entrada. *FILE reduce tamano pero complica la lectura del journal. Solo para escenarios extremos de volumen.
Receivers en ASP separado
Colocar los receivers en un ASP (disk pool) diferente al de los datos mejora el throughput al distribuir la carga de I/O.
MNGRCV(*SYSTEM)
El sistema maneja swaps automaticos. Evita la situacion de receiver full que bloquea operaciones de escritura.
MySQL -- Binary Log overhead
IBM i -- Journal overhead
A partir de IBM i 7.3, la table function QSYS2.DISPLAY_JOURNAL permite consultar journal entries directamente con SQL, eliminando la necesidad de usar DSPJRN con outfiles.
-- Consulta basica de journal entries
SELECT
ENTRY_TIMESTAMP,
JOURNAL_ENTRY_TYPE,
JOURNAL_CODE,
JOB_NAME,
USER_NAME,
PROGRAM_NAME,
OBJECT_NAME,
OBJECT_LIBRARY,
ENTRY_DATA
FROM TABLE(
QSYS2.DISPLAY_JOURNAL(
'MILIB', 'QSQJRN',
JOURNAL_ENTRY_TYPES => 'PT,UB,UP,DL',
JOURNAL_CODES => 'R',
OBJECT_NAME => 'CLIENTES',
OBJECT_LIBRARY => 'MILIB',
STARTING_TIMESTAMP => '2025-01-15 08:00:00',
ENDING_TIMESTAMP => '2025-01-15 17:00:00'
)
) AS jrn
ORDER BY ENTRY_TIMESTAMP DESC
FETCH FIRST 100 ROWS ONLY;-- Dashboard completo de actividad semanal
WITH actividad AS (
SELECT
DATE(ENTRY_TIMESTAMP) AS FECHA,
HOUR(ENTRY_TIMESTAMP) AS HORA,
USER_NAME,
OBJECT_NAME AS TABLA,
JOURNAL_ENTRY_TYPE AS TIPO,
COUNT(*) AS CANTIDAD
FROM TABLE(
QSYS2.DISPLAY_JOURNAL(
'MILIB', 'QSQJRN',
JOURNAL_CODES => 'R',
JOURNAL_ENTRY_TYPES => 'PT,UP,DL',
STARTING_TIMESTAMP => CURRENT_DATE - 7 DAYS
)
) AS jrn
GROUP BY DATE(ENTRY_TIMESTAMP), HOUR(ENTRY_TIMESTAMP),
USER_NAME, OBJECT_NAME, JOURNAL_ENTRY_TYPE
)
SELECT FECHA, HORA, USER_NAME, TABLA,
SUM(CASE WHEN TIPO = 'PT' THEN CANTIDAD ELSE 0 END) AS INSERTS,
SUM(CASE WHEN TIPO = 'UP' THEN CANTIDAD ELSE 0 END) AS UPDATES,
SUM(CASE WHEN TIPO = 'DL' THEN CANTIDAD ELSE 0 END) AS DELETES,
SUM(CANTIDAD) AS TOTAL
FROM actividad
GROUP BY FECHA, HORA, USER_NAME, TABLA
ORDER BY FECHA DESC, HORA, TOTAL DESC;1Siempre usar MNGRCV(*SYSTEM)
Permite que el sistema gestione automaticamente el swap de receivers. Evita la situacion critica de receiver lleno que bloquea las escrituras.
2IMAGES(*BOTH) para tablas criticas
Garantiza la posibilidad de reverse recovery. El costo extra en espacio se justifica con la capacidad de deshacer cambios erroneos.
3Un journal por biblioteca
Es la practica estandar. Usar el nombre QSQJRN para que las tablas DDL se journaleen automaticamente.
4Salvar receivers antes de eliminarlos
Si necesitas mantener historial para auditoria, salva los receivers detached a cinta o SAVF antes de hacer DLTJRNRCV.
5Monitorear tamano de receivers
Usa QSYS2.JOURNAL_RECEIVER_INFO para verificar que los receivers no crezcan sin control. Configura alertas de tamano.
6Probar recovery periodicamente
Valida que APYJRNCHG y RMVJRNCHG funcionan correctamente en un entorno de test. No esperes a una emergencia para probarlo.
/* Setup optimo de journaling para produccion */
CRTJRNRCV JRNRCV(PRODLIB/RCVR0001) +
THRESHOLD(1000000) +
TEXT('Receiver inicial produccion')
CRTJRN JRN(PRODLIB/QSQJRN) +
JRNRCV(PRODLIB/RCVR0001) +
MNGRCV(*SYSTEM) +
DLTRCV(*NO) +
RCVSIZOPT(*MAXOPT2) +
MINENTDTA(*NONE) +
TEXT('Journal produccion')
/* Journalear TODAS las tablas de la biblioteca */
STRJRNPF FILE(PRODLIB/*ALL) +
JRN(PRODLIB/QSQJRN) +
IMAGES(*BOTH) +
OMTJRNE(*OPNCLO)