Autor: Francisco

  • Historial de ejecuciones de jobs SSIS en SQL Server Agent

    SQL Server Agent guarda el historial completo de ejecuciones de todos sus jobs en la base de datos del sistema msdb, pero la información viene en un formato poco amigable — fechas y horas como enteros separados, duraciones codificadas como HHMMSS sin separadores, y estados representados por números. Esta consulta traduce todo eso a algo legible, enfocado específicamente en pasos de tipo SSIS.

    Tablas utilizadas

    TablaDescripción
    msdb.dbo.sysjobsDefinición de los jobs del Agente
    msdb.dbo.sysjobstepsPasos de cada job (tipo, comando, etc.)
    msdb.dbo.sysjobhistoryHistorial de ejecuciones (fecha, duración, estado)

    Las tres se relacionan por job_id (y step_id entre pasos e historial), ya que un job puede tener varios pasos, y cada paso acumula múltiples ejecuciones a lo largo del tiempo.

    La query

    USE msdb;
    GO
    
    SELECT
        j.name                                              AS JobName,
        js.step_id                                          AS StepNo,
        js.step_name                                        AS StepName,
        js.subsystem                                        AS StepType,       -- 'SSIS' para paquetes SSIS
        js.command                                          AS SSISCommand,     -- ruta del paquete o comando
    
        -- Fecha y hora de inicio legibles
        msdb.dbo.agent_datetime(h.run_date, h.run_time)     AS RunStart,
    
        -- Duración formateada HH:MM:SS
        STUFF(STUFF(
            RIGHT(REPLICATE('0', 6) + CAST(h.run_duration AS VARCHAR(6)), 6),
        3, 0, ':'), 6, 0, ':')                              AS Duration_HHMMSS,
    
        -- Duración en segundos (para cálculos / ORDER BY)
        (h.run_duration / 10000 * 3600)
        + ((h.run_duration / 100) % 100 * 60)
        + (h.run_duration % 100)                            AS Duration_Seconds,
    
        -- Fecha fin estimada
        DATEADD(
            SECOND,
            (h.run_duration / 10000 * 3600)
            + ((h.run_duration / 100) % 100 * 60)
            + (h.run_duration % 100),
            msdb.dbo.agent_datetime(h.run_date, h.run_time)
        )                                                   AS RunEnd,
    
        CASE h.run_status
            WHEN 0 THEN 'Failed'
            WHEN 1 THEN 'Succeeded'
            WHEN 2 THEN 'Retry'
            WHEN 3 THEN 'Cancelled'
            WHEN 4 THEN 'In Progress'
        END                                                 AS RunStatus,
    
        h.message                                           AS ResultMessage,
        j.enabled                                           AS JobEnabled
    
    FROM msdb.dbo.sysjobs        j
    JOIN msdb.dbo.sysjobsteps    js ON js.job_id = j.job_id
    JOIN msdb.dbo.sysjobhistory  h  ON h.job_id  = j.job_id
                                    AND h.step_id = js.step_id
    
    WHERE js.subsystem = 'SSIS'
      -- AND j.enabled  = 1          -- descomentar: solo jobs activos
      -- AND h.run_date >= 20250101  -- descomentar: filtrar por fecha (YYYYMMDD)
    
    ORDER BY
        j.name,
        msdb.dbo.agent_datetime(h.run_date, h.run_time) DESC;

    Cómo funciona cada parte

    Fecha y hora legibles

    SQL Server Agent guarda run_date y run_time como dos enteros separados (por ejemplo 20260622 y 143000). La función del sistema msdb.dbo.agent_datetime() los combina y convierte a un DATETIME normal, evitando tener que parsear los enteros a mano.

    Duración: de entero codificado a HH:MM:SS

    run_duration también viene como entero, pero en formato HHMMSS concatenado (por ejemplo, 13045 significa 1 hora, 30 minutos, 45 segundos). El doble STUFF() rellena con ceros a la izquierda hasta 6 dígitos y luego inserta los dos puntos en las posiciones correctas, para que se lea como una duración real.

    Duración en segundos

    Para poder ordenar o hacer cálculos con la duración (algo que el formato HH:MM:SS como texto no permite bien), la query descompone el mismo entero run_duration en horas, minutos y segundos por separado usando división entera y módulo, y los suma todos convertidos a segundos.

    Fecha de fin estimada

    Como sysjobhistory solo guarda la hora de inicio y la duración (no la hora de fin), RunEnd se calcula sumando la duración en segundos (ya calculada arriba) a la fecha de inicio con DATEADD.

    Estado de la ejecución

    El campo run_status es un entero sin significado evidente por sí solo. El CASE lo traduce a los cinco estados posibles que maneja SQL Server Agent: Failed, Succeeded, Retry, Cancelled e In Progress.

    Filtros opcionales

    La query trae dos filtros comentados, útiles según el caso:

    • AND j.enabled = 1 — descomentar para ver únicamente jobs que están activos actualmente (excluye jobs deshabilitados que quedaron en el historial).
    • AND h.run_date >= 20250101 — descomentar para acotar el historial a partir de una fecha específica, en formato YYYYMMDD. Útil en instancias con mucho tiempo de historial acumulado, donde la consulta completa puede ser lenta.

    Variante: solo las ejecuciones fallidas

    Para monitoreo o alertas, suele ser más útil filtrar directamente por las ejecuciones que fallaron, en vez de traer todo el historial:

    -- Agregar a la cláusula WHERE de la query original:
    AND h.run_status = 0  -- solo ejecuciones fallidas

    Notas

    • run_duration está en formato HHMMSS como entero — requiere conversión manual, no es un tipo de dato de duración nativo.
    • agent_datetime(run_date, run_time) convierte los campos enteros a DATETIME legible.
    • Los filtros opcionales comentados permiten activar jobs y/o rango de fechas según la necesidad puntual de cada consulta.
  • Dominando systemd: servicios, logs y herramientas ctl

    Systemd es el sistema de inicialización y gestor de servicios usado por la mayoría de distribuciones Linux modernas (Debian, Ubuntu, Fedora, RHEL, Arch). Además de arrancar el sistema, systemd centraliza el control de casi todos los aspectos de administración a través de un conjunto de herramientas con el sufijo ctl («control»):

    • Servicios (.service) — procesos que corren en segundo plano.
    • Timers (.timer) — el reemplazo moderno de cron.
    • Sockets (.socket) — activación de servicios bajo demanda al recibir conexiones.
    • Targets — agrupaciones de unidades, equivalentes a los «runlevels» de SysV.
    • Logs (journal) — el sistema centralizado de registro de eventos.
    • Sesiones (loginctl) — gestión de sesiones de usuario.
    • Fecha/hora (timedatectl), hostname (hostnamectl), locales (localectl), red (networkctl) y DNS (resolvectl).
    • Bootloader (bootctl) y contenedores (machinectl, systemd-nspawn).

    Crear servicios systemd

    Estructura de un servicio

    Un servicio personalizado se define en un archivo .service, típicamente dentro de /etc/systemd/system/:

    [Unit]
    Description=Mi servicio de ejemplo
    Documentation=https://example.com/docs
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=simple
    User=usuario
    Group=usuario
    WorkingDirectory=/opt/app
    ExecStart=/usr/bin/python3 /opt/app/app.py
    ExecReload=/bin/kill -HUP $MAINPID
    Restart=on-failure
    RestartSec=5
    Environment="ENV=produccion"
    EnvironmentFile=/etc/default/app
    
    [Install]
    WantedBy=multi-user.target

    Cada sección cumple un propósito distinto:

    • [Unit] — metadatos y dependencias. After controla el orden de arranque (espera a que ocurra el evento indicado), mientras Wants expresa una dependencia flexible: si la red no está disponible, el servicio arranca igual, solo que después.
    • [Service] — cómo se ejecuta el proceso. Type=simple asume que el proceso principal no hace fork; ExecStart es el comando real; Restart=on-failure hace que systemd reinicie el servicio automáticamente si termina con un código de error (no en un cierre limpio); RestartSec es la pausa antes de reintentar.
    • [Install] — define en qué target se activa el servicio al hacer enable. multi-user.target es el equivalente al runlevel 3 clásico (sistema multiusuario, sin entorno gráfico).

    Procedimiento universal

    Flujo estándar para crear, activar y verificar cualquier servicio nuevo:

    sudo nano /etc/systemd/system/mi-servicio.service
    sudo systemctl daemon-reload
    sudo systemctl start mi-servicio
    systemctl status mi-servicio
    sudo systemctl enable mi-servicio

    daemon-reload es el paso que más se olvida: systemd cachea las definiciones de las unidades en memoria, así que cualquier cambio al archivo .service no se aplica hasta ejecutar este comando. enable crea el enlace simbólico que hace que el servicio arranque automáticamente en el próximo boot — start solo lo arranca ahora, una vez.

    Logs con journalctl

    El journal es el sistema de logs binario centralizado de systemd — reemplaza (o complementa) a los archivos de texto tradicionales en /var/log/.

    journalctl -u mi-servicio       # logs de un servicio específico
    journalctl -u mi-servicio -f    # seguimiento en vivo (equivalente a tail -f)
    journalctl -u mi-servicio -p err # solo mensajes de nivel error o más grave
    journalctl -u mi-servicio -b    # logs desde el arranque actual del sistema
    journalctl -xe                  # últimas líneas con contexto extendido, útil tras un fallo

    Comandos *ctl del ecosistema systemd

    systemctl — control de unidades

    La herramienta principal para gestionar el estado de servicios y otras unidades:

    systemctl start nombre       # arrancar ahora
    systemctl stop nombre        # detener ahora
    systemctl restart nombre     # detener y volver a arrancar
    systemctl reload nombre      # recargar configuración sin reiniciar el proceso
    systemctl enable nombre      # activar en el próximo boot
    systemctl disable nombre     # desactivar en el próximo boot
    systemctl status nombre      # ver estado actual y últimos logs
    systemctl list-units         # listar unidades cargadas actualmente
    systemctl list-unit-files    # listar todas las unidades instaladas (activas o no)
    systemctl daemon-reload      # releer definiciones de unidades tras editarlas
    systemctl isolate multi-user.target  # cambiar de target activo (similar a cambiar runlevel)
    systemctl --failed           # listar únicamente las unidades que fallaron

    journalctl — consultas adicionales al journal

    journalctl                    # todo el log disponible
    journalctl -f                 # seguimiento en vivo de todo el sistema
    journalctl -xe                # contexto extendido de los eventos más recientes
    journalctl -p err             # filtrar por severidad (err, warning, crit, etc.)
    journalctl -u nombre          # logs de una unidad específica
    journalctl -b                 # logs solo del arranque actual
    journalctl -k                 # solo mensajes del kernel (equivalente a dmesg)
    journalctl --since "2026-01-01"  # filtrar por fecha

    loginctl — sesiones de usuario

    Gestiona sesiones activas, útil en servidores multiusuario o para depurar sesiones colgadas:

    loginctl list-sessions           # ver todas las sesiones activas
    loginctl session-status 3        # detalle de una sesión específica
    loginctl terminate-session 3     # cerrar una sesión a la fuerza
    loginctl list-users              # usuarios con sesión activa
    loginctl user-status usuario     # detalle de sesiones de un usuario
    loginctl enable-linger usuario   # permite que los servicios de usuario (systemd --user) sigan corriendo aunque cierre sesión

    timedatectl — fecha, hora y NTP

    timedatectl                                  # ver configuración actual
    timedatectl set-time "2026-06-30 10:00:00"   # fijar hora manualmente
    timedatectl set-timezone America/Guayaquil   # cambiar zona horaria
    timedatectl set-ntp true                     # activar sincronización automática por NTP

    hostnamectl — nombre del equipo

    hostnamectl                      # ver hostname y datos del sistema (kernel, arquitectura, etc.)
    hostnamectl set-hostname servidor  # cambiar el hostname de forma persistente

    localectl — idioma y teclado

    localectl                          # ver configuración de locale actual
    localectl set-locale LANG=es_EC.UTF-8  # cambiar idioma del sistema
    localectl set-keymap us            # cambiar distribución de teclado

    networkctl — estado de interfaces de red

    Solo aplica en sistemas que usan systemd-networkd como gestor de red (no todos — muchas distros usan NetworkManager o ifupdown en su lugar):

    networkctl              # listar interfaces y su estado
    networkctl status eth0  # detalle de una interfaz específica
    networkctl list         # listado resumido
    networkctl reload       # releer configuración de red

    resolvectl — resolución DNS

    resolvectl status              # ver servidores DNS configurados por interfaz
    resolvectl query google.com    # hacer una consulta DNS de prueba
    resolvectl flush-caches        # limpiar la caché de resolución DNS
    resolvectl dns eth0 8.8.8.8    # fijar un DNS específico para una interfaz

    machinectl — contenedores y máquinas virtuales ligeras

    Gestiona contenedores creados con systemd-nspawn:

    machinectl list          # ver contenedores registrados
    machinectl start debian  # arrancar un contenedor
    machinectl stop debian   # detenerlo
    machinectl shell debian  # abrir una shell dentro del contenedor

    bootctl — gestor de arranque (systemd-boot)

    bootctl status   # ver estado del bootloader
    bootctl install  # instalar systemd-boot en la partición EFI
    bootctl update   # actualizar los binarios del bootloader

    systemd-analyze — rendimiento de arranque

    Herramienta de diagnóstico para entender qué tarda en el proceso de boot:

    systemd-analyze                  # tiempo total de arranque, desglosado por fase
    systemd-analyze blame            # qué servicios tardaron más en iniciar
    systemd-analyze critical-chain   # cadena de dependencias que determinó el tiempo total
    systemd-analyze plot > boot.svg  # generar un gráfico visual del arranque

    systemd-run — ejecutar comandos como unidades temporales

    Permite lanzar cualquier comando como si fuera un servicio administrado por systemd, sin crear un archivo .service permanente — útil para pruebas o tareas puntuales que necesitan aislamiento de recursos:

    systemd-run --unit=mi-tarea echo "hola"
    systemd-run --scope sleep 10

    systemd-cgls y systemd-cgtop — jerarquía de cgroups

    systemd-cgls    # árbol de cgroups activos (qué proceso pertenece a qué unidad)
    systemd-cgtop   # uso de recursos en tiempo real, agrupado por cgroup

    systemd-escape — codificar nombres para unidades

    Convierte rutas o cadenas con caracteres especiales al formato seguro que systemd usa internamente para nombrar unidades:

    systemd-escape "ruta rara"
    systemd-escape --path "/home/usuario/archivo"

    systemd-inhibit — evitar suspensión/apagado durante una tarea

    systemd-inhibit sleep 30
    systemd-inhibit --why="Proceso crítico" comando

    systemd-notify — comunicación entre el proceso y systemd

    Usado dentro de scripts o servicios Type=notify para avisarle a systemd que el proceso ya está listo, o para reportar su estado actual:

    systemd-notify --ready
    systemd-notify --status="Inicializando..."

    systemd-sysusers — creación declarativa de usuarios del sistema

    systemd-sysusers
    systemd-sysusers /etc/sysusers.d/archivo.conf

    systemd-tmpfiles — gestión de archivos y directorios temporales

    systemd-tmpfiles --create   # crear archivos/directorios definidos en configuración
    systemd-tmpfiles --clean    # limpiar archivos temporales expirados
    systemd-tmpfiles --remove   # eliminar los definidos en configuración

    systemd-nspawn — contenedores ligeros

    systemd-nspawn -D /var/lib/machines/debian
    systemd-nspawn -M mi-maquina

    Timers systemd

    Los timers son la alternativa moderna a cron, con la ventaja de integrarse con el journal (logs unificados) y con dependencias de otras unidades. Un timer siempre va acompañado de un servicio con el mismo nombre base.

    Timer (backup.timer):

    [Unit]
    Description=Backup diario
    
    [Timer]
    OnCalendar=daily
    Persistent=true
    
    [Install]
    WantedBy=timers.target

    Persistent=true es clave: si el equipo estuvo apagado cuando debía ejecutarse el timer, lo corre en cuanto vuelve a encender, en vez de esperar al siguiente ciclo — evita perder ejecuciones por reinicios o cortes de energía.

    Servicio asociado (backup.service):

    [Service]
    Type=oneshot
    ExecStart=/usr/local/bin/backup.sh

    Type=oneshot le indica a systemd que el proceso termina por sí solo tras completar su tarea (no es un servicio de larga duración) — es el tipo correcto para scripts que corren, hacen su trabajo, y salen.

    Activación:

    systemctl enable --now backup.timer
    systemctl list-timers

    Depuración avanzada

    Secuencia típica para diagnosticar un servicio que no arranca o falla:

    systemctl status mi-servicio                          # estado y últimas líneas de log
    journalctl -u mi-servicio -p err -b                    # solo errores, desde el último boot
    journalctl -u mi-servicio -n 20                        # últimas 20 líneas de log
    journalctl _PID=$(pidof mi-servicio)                   # logs filtrados por PID específico
    systemctl list-dependencies mi-servicio                # árbol de dependencias
    systemd-analyze verify /etc/systemd/system/mi-servicio.service  # validar sintaxis del archivo
    sudo -u usuario /usr/bin/python3 /opt/app/app.py        # ejecutar manualmente como el mismo usuario, para ver el error real
    ls -l /opt/app/app.py                                   # confirmar que el archivo existe y tiene permisos correctos
    ls -l /usr/bin/python3                                  # confirmar que el binario existe en la ruta esperada
    cat /etc/default/app                                    # revisar variables de entorno cargadas
    systemctl daemon-reload                                 # por si el problema es una definición desactualizada

    Interpretación completa de systemctl status

    Ejemplo de salida real:

    ○ rory-indexer.service - Rory Indexer RAG
         Loaded: loaded (/etc/systemd/system/rory-indexer.service; static)
         Active: inactive (dead) since Tue 2026-06-30 03:24:53 -05; 5h 29min ago
    TriggeredBy: ● rory-indexer.timer
        Process: 2967487 ExecStart=/opt/rory-indexer/bin/python3 /opt/rory-indexer/scripts/indexer_v2.py (code=exited, status=0/SUCCESS)
       Main PID: 2967487 (code=exited, status=0/SUCCESS)
            CPU: 1min 18.622s

    Primera línea

    Muestra el símbolo de estado visual (● activo, ○ inactivo, ✗ falló, ▲ iniciando), el nombre técnico del servicio y su descripción legible.

    Loaded

    Indica si el archivo .service está cargado, su ruta completa, y el estado de instalación: enabled (arranca en boot), disabled (no arranca en boot pero se puede iniciar manualmente), static (no tiene sección [Install], solo se activa como dependencia de otra unidad), o masked (bloqueado explícitamente, no se puede iniciar ni siquiera manualmente).

    Active

    El estado real de ejecución: active (running), inactive (dead), failed, activating o deactivating (transiciones en curso), y reloading.

    TriggeredBy

    Indica qué otra unidad activó este servicio — típicamente un Timer, un Socket, o un Path (activación al detectar cambios en un archivo/directorio).

    Process

    Detalle del último proceso ejecutado: PID, comando exacto que se corrió, y el código de salida (exit code) — clave para saber si terminó bien (status=0/SUCCESS) o con error.

    Main PID

    El proceso principal identificado por el servicio, el que systemd supervisa para determinar si sigue vivo.

    CPU

    Tiempo total de CPU consumido por el servicio desde que arrancó.

    Logs

    Las últimas líneas del journal relacionadas con esta unidad, mostradas automáticamente al final del status.

    Buenas prácticas

    • Usar siempre rutas absolutas en ExecStart — systemd no hereda el $PATH de una shell de usuario.
    • Restart=on-failure como valor por defecto razonable, para que el servicio se recupere solo ante caídas inesperadas.
    • After=network-online.target si el servicio depende de conectividad de red al arrancar (evita condiciones de carrera con la red, como las que resuelven un resolv.conf vacío en contenedores Docker al bootear antes que la red).
    • EnvironmentFile= para mantener configuraciones y secretos fuera del archivo .service en sí, en un archivo separado con permisos restringidos.
    • Monitorear en vivo con journalctl -u servicio -f al depurar o al desplegar cambios.
    • Nunca olvidar systemctl daemon-reload después de editar cualquier archivo .service o .timer.