Uso de DBMS_SCHEDULER en lugar de cron en RAC y entornos HA
Como eliminar puntos únicos de fallo para external jobs en entornos HA
En un entorno de alta disponibilidad como RAC o clusters HA, no podemos delegar la ejecución de trabajos nocturnos en el cron de una única máquina ya que en caso de pérdida de servicio de ese host, el trabajo no se ejecutará aunque la base de datos y sus servicios sigan disponibles en el resto de hosts que forman la arquitectura de alta disponibilidad.
Se propone por tanto, usar la base de datos para programar Jobs que dependan o tengan que ejecutarse contra la propia base de datos.
Como prueba de concepto, configuraremos un external job con el paquete dbms_scheduler y que además corra contra un servicio de base de datos. Así nos desvincularemos de la dependencia de un nodo del cluster y además le añadiremos la potencia derivada del uso de servicios en bases de datos.
Esta POC está ejecutada en un entorno rac en oracle 19c.
Configuración en el Sistema operativo
Para poder lanzar un ejecutable del sistema operativo desde un scheduler de base de datos. Hay que indicar en contenido del fichero "$ORACLE_HOME/dbms/admin/externaljob.ora" el usuario y grupo del sistema operativo al que se le permite ejecutarlo, en nuestro caso usaremos oracle y dba. Esto hay que hacerlo en todos los nodos donde se ejecute o pueda ejecutarse la base de datos. Es necesario privilegios de root para ejecutar esta configuración.

El fichero externaljob.ora debe ser propiedad de root y el grupo debe ser oinstall y tener privilegios de lectura y ejecución para el propietario y de lectura para el grupo. Se necesitan privilegios de root para configurarlo correctamente, ejecutarlo con cuidado.
Mostramos captura de los privilegios que debe tener el fichero en todos los nodos de la base de datos.

La edición del fichero externaljob.ora y el cambio de propietario y permisos, pueden hacerse con la base de datos levantada, no es necesario reinicio para que entren en vigor.
El Shell script a ejecutar y sus logs los colocaremos en un nfs compartido por todos los nodos, así estará disponible el mismo fichero frente a switcheos de base de datos en entornos HA o frente a paradas y caídas de instancia.
En nuestra POC, ejecutaremos el script visible y compartido por todos los nodos:
/nfs/admin/scripts/oracle/test.sh
#!/bin/bash
. /home/oracle/oraenv.sh
MY_HOST=$(hostname -a)
NOW=$(date '+%Y%m%d %H:%M:%S')
DIR_LOG=/nfs/admin/scripts/oracle/logs/
echo \(MY_HOST >>\)DIR_LOG/ejemplo_scheduler_os.log
echo \(NOW>>\)DIR_LOG/ejemplo_scheduler_os.log
Es conveniente que el script tenga tambien su propio logs, así nos ayudará en caso de depuración.
El script a ejecutar (test.sh) debe tener permiso de ejecución y lectura para el usuario oracle del sistema operativo. Por seguridad no se le darán permisos para others
Configuración en base de datos
Para poder crear schedulers que llamen a programas externos, el usuario de base de datos debe tener el grant CREATE JOB y el CREATE EXTERNAL JOB.
Si el ejecutable del sistema operativo corre con el usuario del sistema operativo propietario de la base de datos, no hay que definir credenciales con el dbms_scheduler. En nuestro caso usaremos oracle, por lo que no necesitamos credentials.
Job Class, Los scheduler jobs pueden agruparse en clases lo que permite que puedan se asociadas a servicios de base de datos y que se les pueda aplicar el resource manager para la gestión de recursos asignados. Nosotros crearemos una clase para vincular nuestros Jobs al servicio asociado a nuestro aplicativo.
Creación de la job class
Se muestra la creación de una job class asociada a un servicio existente.
BEGIN
DBMS_SCHEDULER.CREATE_JOB_CLASS (
job_class_name => 'class_lab_appls_pro',
service => 'srv_lab_appls_pro',
comments => 'Class for service srv_lab_appls_pro'
);
END;
/
Creacion del Job
El usuario de base de datos que cree el job, debe tener el privilegio de create job y create external job.
Acontinuación se muestra la creación de un job tipo "executable" que va a lanzar el script externo test.sh
begin
dbms_scheduler.create_job(
job_name => 'JOB_LAB_EXTERNAL_TEST',
job_type => 'EXECUTABLE',
job_action => '/ruta al ejecutable aquí/test.sh',
job_class => 'lab_appls_class',
start_date => TO_TIMESTAMP_TZ('2026/02/20 00:43:00 Europe/Madrid','yyyy/mm/dd hh24:mi:ss TZR'),
repeat_interval => 'FREQ=HOURLY;BYHOUR=1;BYMINUTE=0,10,20,30,40,50',
auto_drop => false,
enabled => true);
end;
/
Así creamos un job que ejecuta el script de test cada 10 minutos, asociado a una job_class que a su vez se asocia a un servicio de oracle, en Rac nos permite entre otras cosas, detener el servicio en un nodo de base de datos y que corra por tanto el job en los nodos que tengan el servicio levantado.
Monitorización
A parte de ver el log que deje el script en el sistema operativo, tenemos vistas que monitorizan los Jobs en base de datos.
DBA_SCHEDULER_JOB,
DBA_SCHEDULER_JOB_LOG,
DBA_SCHEDULER_JOB_LOG_DETAILS,
DBA_SCHEDULER_JOB_CLASSES
Podemos usar las siguientes queries para ver los jobs externos programados
set linesize 300
col job_name format a30
col job_type format a10
col job_action format a40
col start_date format a20
col repeat_interval format a30
col job_class format a19
col state format a15
col owner format a12
SELECT owner, job_name,job_type,job_action,start_date,repeat_interval,job_class,state,run_count FROM DBA_SCHEDULER_JOBS where job_type='EXECUTABLE' ORDER BY OWNER,start_date,repeat_interval;

Para ver que se ha ejecutado
set linesize 300
col log_id format 999999
col log_date format a36
col owner format a8
col job_name format a30
col job_class format a20
col operation format a5
col status format a9
SELECT log_id,log_date,owner,job_name,job_class,operation,status FROM DBA_SCHEDULER_JOB_LOG WHERE job_name=UPPER('&NOMBRE_JOB') ORDER BY LOG_ID DESC;
Si es posible, es aconsejable ejecutar el job manualmente para confirmar que no hay problemas de código o privilegios:

Y verificamos que se ha ejecutado bien

y vemos que despues de la ejecución manual, vuelve a ejecutarse cuando corresponde

Con la dba_scheduler_job_run_details vemos que una vez se ha ejecutado en la instancia 1 y otra vez en la instancia 2
col run_duration format a15
col instance_id format 99
col session_id format a15
col slave_pid format a7
col cpu_used format a16
col errors format a20
col additional_info format a60
SELECT log_id,log_date,owner,job_name,status,run_duration,instance_id,session_id,slave_pid,cpu_used,additional_info,errors FROM DBA_SCHEDULER_JOB_RUN_DETAILS WHERE job_name=UPPER('&NOMBRE_JOB') ORDER BY LOG_ID DESC;

Conclusión
Transformar los tradicionales crons de linux en jobs de base de datos, nos permite que se ejecuten siempre que la base de datos esté activa, incluso frente a switchover o failovers de data guard, sin tener que preocuparnos que en qué host está el cron programado.
En un entorno con dos Racs en configuración data guard y el ejecutable en un nfs (o quizá un dbfs, así se replica entre primaria y standbs) compartido por todos los nodos, comprobamos que el job se ejecuta siempre en uno de los nodos de la base de datos que esté como primaria y que tenga el servicio asociado a la clase levantado.
Su monitorización puede hacerse consultando vistas, no siendo generalmente necesario la consulta del log que hubiera sido configurado en el script.
Podemos decir que el uso de dbms_scheduler frente a crons aporta resilencia a nuestro sistema de alta disponibilidad así como coherncia en la programación al estar centralizada en la base de datos.
Documentación Oficial
Para oracle 19c
Scripts usados
Añado el link para los tres scripts de monitorizacion
Para ver los jobs externos programados:
https://github.com/sysassysdba/database-scripts/blob/main/chk_scheduler_jobs.sql
Para ver las ejecuciones de los jobs:
https://github.com/sysassysdba/database-scripts/blob/main/chk_job_run.sql
Para ver los detalles de las ejecuciones:
https://github.com/sysassysdba/database-scripts/blob/main/chk_job_run_details.sql