Monitorizando Dataguard en Bash

Photo by Carlos Muza on Unsplash

Monitorizando Dataguard en Bash

Cuántas veces nos hemos encontrado con una integración de monitorización cuando entra en juego DG?

Pues lo voy a poner fácil. Lo hacemos en un script con las recomendaciones de Oracle. La versión con la que trabajo es 19c, importante.

En este caso está planificado en nagios, pero sería perfectamente válido para otras plataformas como zabbix. Al final lo que importa es la salida ( Ok, Warning, Critical) y/o el exit code.

La idea es que pasándole el SID pueda distinguir si es Primaria o Standby y poder planificar el script para Standby y Primaria y que solo salte cuando se cumpla el rol para el chequeo.

Las alertas query_apply_lag y query_transport_lag están parametrizadas para saltar a los 5 minutos, es ajustable al entorno.

Os presento uno de los Carli-scripts que os facilitará la vida. Empezamos?

check_dg.sh
check_dg.sh <ORACLE_SID> <query_archive_dest|query_gaps|query_errors|query_loging|query_apply_lag|query_transport_lag|query_datafiles_offline|query_mrp

Tiene buena pinta verdad? Espera que invoquemos con el SID y el chequeo concreto.

En qué me baso?

Monitoring Oracle Data Guard Configuration Health Using SQL 19c

Para ello he desarollado el siguiente script:

[nagios@rac-prueba-001 ~]$ cat /home/oracle/check_dg.sh
#!/bin/bash
# Verificar que se haya pasado ORACLE_SID como argumento
if [ "$#" -ne 2 ]; then
    echo "Uso: $0 <ORACLE_SID> <query_archive_dest|query_gaps|query_errors|query_loging|query_apply_lag|query_transport_lag|query_datafiles_offline|query_mrp>"
    exit 1
fi


#Variables entorno de BBDD
ORACLE_SID=$1
ORAENV_ASK=NO
. oraenv -s

#Variable chequeo
OPCION_CHECK=$2

# Configuración de conexión
dbname=$ORACLE_SID  # El nombre de la base de datos será el mismo que ORACLE_SID
dbuser='system'
dbpass='Temporal01'

# Función para ejecutar un comando SQL y devolver los resultados
ejecutar_sqlplus() {
    local sql=$1
    $ORACLE_HOME/bin/sqlplus -s "$dbuser/$dbpass@$dbname" <<EOF
    SET PAGESIZE 0
    SET FEEDBACK OFF
    SET VERIFY OFF
    SET HEADING OFF
    SET ECHO OFF
    $sql
    EXIT;
EOF
}


# Verificar si la base de datos es primaria
query_role=$(cat <<EOF
SELECT DATABASE_ROLE FROM v\$database;
EOF
)

#################### STANDBY ##########################

# Consulta de chequeo lag apply
query_apply_lag=$(cat <<EOF
        SELECT name || ': ' || max(value)
        FROM gv\$dataguard_stats
        WHERE   name = 'apply lag'
            AND value > '+00 00:05:00'
        GROUP BY NAME;
EOF
    )

# Consulta de chequeo lag transport
query_transport_lag=$(cat <<EOF
        SELECT name || ': ' || max(value)
        FROM gv\$dataguard_stats
        WHERE   name = 'transport lag'
                AND value > '+00 00:05:00'
        GROUP BY NAME;
EOF
    )

# Data file check (offline files or files that are not accessible)
query_datafiles_offline=$(cat <<EOF
    select 'Hay ' || count(*) || ' datafiles en estado offline'
     from v\$datafile_header
     where     status ='OFFLINE'
         or ERROR is not null
        having count(*)>0;
EOF
    )

# Verify that the Media Recovery Process is currently running. Tiene que devolver fila
query_mrp=$(cat <<EOF
    select count(*)
     from gv\$managed_standby
     where process like 'MRP%'
        having count(*)>0;
EOF
    )

#################### PRIMARIA ########################

# Consulta de chequeo errores arhive dest
query_archive_dest=$(cat <<EOF
    WITH AggregatedData AS (
            SELECT inst_id, substr(error,1,9) error
            from gv\$archive_dest_status
            where type='PHYSICAL'
        and status!='VALID'
    )
    SELECT 'Instancia: ' || LISTAGG(inst_id, ',') WITHIN GROUP (ORDER BY inst_id) || ' Errores: ' || LISTAGG(error, ',') WITHIN GROUP (ORDER BY error) AS result
    FROM AggregatedData
        HAVING COUNT(*) > 0;
EOF
    )

# Consulta de chequeo gaps
query_gaps=$(cat <<EOF
    select LISTAGG(inst_id, ',') WITHIN GROUP (ORDER BY inst_id) || ' ' ||  database_mode || ' ' || recovery_mode || ' ' || gap_status
     from gv\$archive_dest_status
     where     type='PHYSICAL'
         and gap_status !='NO GAP'
    group by database_mode,recovery_mode, gap_status;

EOF
    )

# severe Data Guard event occurred in the last day    
query_errors=$(cat <<EOF
    WITH AggregatedData AS (
      SELECT inst_id, COUNT(*) AS count
      FROM gv\$dataguard_status
      WHERE     severity IN ('Error', 'Fatal')
            AND timestamp > (sysdate - 1)
      GROUP BY inst_id
    )
    SELECT 'Instancia: ' || LISTAGG(inst_id, ',') WITHIN GROUP (ORDER BY inst_id) || ' Numero errores: ' || LISTAGG(count, ',') WITHIN GROUP (ORDER BY inst_id) AS result
    FROM AggregatedData
        HAVING COUNT(*) > 0;
EOF
    )

# Check if any NOLOGGING activity occurred on the primary database in the last day    
query_loging=$(cat <<EOF
    SELECT CASE
         WHEN COUNT(*) > 0 THEN 'Hay datafiles con NOLOGGING activity en el último día. File ID: ' ||
                                  LISTAGG(file#, ',') WITHIN GROUP (ORDER BY file#)
         ELSE NULL
           END AS result
    FROM v\$datafile
    WHERE unrecoverable_time > (sysdate - 1);
EOF
    )

role_output=$(ejecutar_sqlplus "$query_role")

if [ "$role_output" = "PRIMARY" ]; then

    case $OPCION_CHECK in
        query_archive_dest)
            check_output=$(ejecutar_sqlplus "$query_archive_dest")
            if [ -n "$check_output" ]; then
                echo "[CRITICAL] $check_output"
                        exit 2
            fi

            ;;
        query_gaps)
                check_output=$(ejecutar_sqlplus "$query_gaps")
                if [ -n "$check_output" ]; then
                        echo "[CRITICAL] $check_output"
                        exit 2
                fi

            ;;
        query_errors)
                check_output=$(ejecutar_sqlplus "$query_errors")
                if [ -n "$check_output" ]; then
                        echo "[CRITICAL] $check_output"
             exit 2
                fi

            ;;
        query_loging)
                check_output=$(ejecutar_sqlplus "$query_loging")
                if [ -n "$check_output" ]; then
                        echo "[WARNING] $check_output"
            exit 1
                fi

            ;;
        query_datafiles_offline)
                check_output=$(ejecutar_sqlplus "$query_datafiles_offline")
                if [ -n "$check_output" ]; then
                        echo "[WARNING] $check_output"
                        exit 1
                fi

            ;;
    esac
fi

if [ "$role_output" = "PHYSICAL STANDBY" ]; then
    case $OPCION_CHECK in
        query_apply_lag)
                check_output=$(ejecutar_sqlplus "$query_apply_lag")
                if [ -n "$check_output" ]; then
                        echo "[CRITICAL] $check_output"
            exit 2
                fi

            ;;
        query_transport_lag)
                check_output=$(ejecutar_sqlplus "$query_transport_lag")
                if [ -n "$check_output" ]; then
                        echo "[CRITICAL] $check_output"
            exit 2
                fi

            ;;
        query_datafiles_offline)
                check_output=$(ejecutar_sqlplus "$query_datafiles_offline")
                if [ -n "$check_output" ]; then
                        echo "[WARNING] $check_output"
            exit 1
                fi

            ;;
        query_mrp)
                check_output=$(ejecutar_sqlplus "$query_mrp")
                if [ -n "$check_output" ]; then
             echo "[OK]"
                        exit 0
                else
            echo "[CRITICAL]. No hay procesos activos de replicación."
            exit 2
                fi

            ;;

    esac

fi

echo "[OK]"
exit 0

Qué esperamos? Devolverá un OK si todo está en orden, WARNING o CRITICAL junto con la información asociada en una sola línea cuando no es así. Es totalmente customizable.

Lo pruebas y me cuentas?

[usuario_monitorizacion@rac-prueba-001 ~]$ /home/oracle/check_dg.sh cdbpro query_archive_dest
[OK]