Performance Tuning

Mostrando las entradas con la etiqueta Performance Tuning. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Performance Tuning. Mostrar todas las entradas

jueves, 21 de mayo de 2026

Corregir la fragmentación y reducir el High Water Mark (HWM) con SHRINK SPACE


En la administración de bases de datos Oracle, un escenario común de degradación de rendimiento ocurre cuando una tabla que solía albergar millones de registros sufre un proceso masivo de depuración (DELETE). A pesar de que los datos ya no residen en el disco, las consultas tipo Full Table Scan (FTS) sobre dicha tabla continúan demorando el mismo tiempo o consumiendo una cantidad idéntica de operaciones de E/S (I/O).

¿Por qué sucede esto? La respuesta se encuentra en el High Water Mark (HWM) o la "marca de agua" del segmento. En este artículo analizaremos conceptualmente este comportamiento, construiremos un laboratorio en la versión 19c para simular la fragmentación y demostraremos cómo diagnosticar y compactar el espacio desperdiciado utilizando herramientas nativas.

¿Qué es el High Water Mark?

El HWM es un puntero dentro de la estructura de almacenamiento de Oracle que define el límite hasta donde se han escrito bloques de datos en un segmento.

  • Cuando se realizan operaciones de INSERT, el HWM sube de manera dinámica para asignar nuevos bloques.

  • Cuando se realiza una operación de DELETE, los registros se borran lógicamente y los bloques quedan vacíos, pero el HWM no se reduce.

Dado que un Full Table Scan lee obligatoriamente todos los bloques por debajo del HWM (asumiendo que contienen datos válidos), Oracle terminará leyendo bloques vacíos de manera innecesaria, degradando el rendimiento de los queries y desperdiciando memoria en el Buffer Cache.

Simulación de Fragmentación


Para demostrar este impacto, crearemos una tabla de pruebas, la poblaremos con un volumen considerable de datos, realizaremos una depuración masiva y mediremos el comportamiento de los bloques.
-- Creamos una tabla basada en los objetos del diccionario para simular volumen
CREATE TABLE t_hwm_test AS SELECT rownum as id, a.* FROM all_objects a, (SELECT 1 FROM dual CONNECT BY level <= 10); 

-- Contamos la cantidad de registros creados en la tabla
SELECT COUNT(*) FROM t_hwm_test;

-- Verificamos el tamaño inicial del segmento y bloques asignados 
SELECT blocks, bytes/1024/1024 AS MB FROM user_segments WHERE segment_name = 'T_HWM_TEST';


Procederemos a eliminar el 90% de los registros de la tabla para inducir una fragmentación severa del segmento.

-- Eliminamos la gran mayoría de los registros 
DELETE FROM t_hwm_test WHERE MOD(id, 10) != 0;
COMMIT;

-- Ejecutamos estadísticas para actualizar el diccionario de datos

EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T_HWM_TEST');


Si consultamos las vistas USER_TABLES (datos lógicos) y USER_SEGMENTS (datos físicos), notaremos la discrepancia:

SELECT t.table_name, t.num_rows, t.blocks AS blocks_with_data, s.blocks AS total_allocated_blocks FROM user_tables t JOIN user_segments s ON t.table_name = s.segment_name WHERE t.table_name = 'T_HWM_TEST';

Para no depender exclusivamente de la ejecución de estadísticas de las tablas, podemos utilizar el paquete nativo DBMS_SPACE.UNUSED_SPACE para obtener el estado del HWM en tiempo real.

Ejecutaremos el siguiente bloque de código en nuestra sesión:

DECLARE
    v_total_blocks       NUMBER;
    v_total_bytes        NUMBER;
    v_unused_blocks      NUMBER;
    v_unused_bytes       NUMBER;
    v_last_used_extent_file_id NUMBER;
    v_last_used_extent_block_id NUMBER;
    v_last_used_block    NUMBER;
BEGIN
    DBMS_SPACE.UNUSED_SPACE(
        segment_owner             => USER,
        segment_name              => 'T_HWM_TEST',
        segment_type              => 'TABLE',
        total_blocks              => v_total_blocks,
        total_bytes               => v_total_bytes,
        unused_blocks             => v_unused_blocks,
        unused_bytes              => v_unused_bytes,
        last_used_extent_file_id  => v_last_used_extent_file_id,
        last_used_extent_block_id => v_last_used_extent_block_id,
        last_used_block           => v_last_used_block
    );
    DBMS_OUTPUT.PUT_LINE('Bloques Totales Asignados: ' || v_total_blocks);
    DBMS_OUTPUT.PUT_LINE('Bloques sobre el HWM (Sin usar): ' || v_unused_blocks);
    DBMS_OUTPUT.PUT_LINE('Bloques por debajo del HWM (HWM Actual): ' || (v_total_blocks - v_unused_blocks));
END;
/

Compactación del Segmento con SHRINK SPACE 


A partir de la introducción de los tablespaces con administración automática de espacio de segmentos (ASSM), Oracle provee una instrucción en línea (ONLINE) extremadamente eficiente para reorganizar las filas, liberar el espacio desperdiciado y reajustar el HWM hacia abajo: el comando SHRINK.

El proceso consta de dos fases lógicas ejecutadas internamente por el motor:

  1. Fase de movimiento de filas (Row Movement): Desplaza las filas de la parte superior del segmento hacia los huecos libres en la parte inferior.

  2. Fase de ajuste del HWM: Bloquea brevemente la tabla para bajar el puntero del HWM y liberar el espacio sobrante al tablespace.

Para poder reubicar físicamente los registros y alterar sus ROWID, es obligatorio activar la propiedad ROW MOVEMENT en la tabla:
ALTER TABLE t_hwm_test ENABLE ROW MOVEMENT;

Procedemos a ejecutar el shrink. Este comando liberará el espacio de vuelta al tablespace de forma inmediata.

ALTER TABLE t_hwm_test SHRINK SPACE;


Nota: Si se tratara de una tabla crítica en un entorno productivo de alta concurrencia, podemos ejecutar ALTER TABLE t_hwm_test SHRINK SPACE COMPACT; para realizar solo la fase 1 sin bloquear el HWM, y posteriormente ejecutar el comando completo en una ventana de mantenimiento corta.

Volvemos a recopilar estadísticas y evaluamos nuevamente el estado físico de la tabla en el diccionario de datos.

-- Ejecutamos nuevamente estadísticas sobre la tabla
EXEC
DBMS_STATS.GATHER_TABLE_STATS(USER, 'T_HWM_TEST');

-- Verificamos el total de bloques de la tabla nuevamente 
SELECT t.table_name, t.num_rows, s.blocks AS total_allocated_blocks FROM user_tables t JOIN user_segments s ON t.table_name = s.segment_name WHERE t.table_name = 'T_HWM_TEST';

 


El total_allocated_blocks se redujo drásticamente de 20480 que tenia inicialmente a 2048, alineándose al volumen real actual de la tabla.

Consideraciones

El comando SHRINK SPACE es la herramienta ideal para combatir la fragmentación en bases de datos modernas, sin embargo, como DBAs debemos considerar las siguientes pautas antes de su aplicación en producción:

  1. Índices: A diferencia de un ALTER TABLE MOVE, el comando SHRINK SPACE mantiene los índices de la tabla en estado VALID, ya que actualiza de manera interna las modificaciones de los ROWID. No es necesario un rebuild posterior.

  2. Restricciones: No se puede aplicar SHRINK sobre tablas con índices basados en funciones, vistas materiales con logs de refresco o tablas que contengan columnas de tipo LONG.

  3. Alternativa Tradicional: Si el tablespace no cuenta con ASSM (lo cual es raro en versiones 19c/23ai), la alternativa obligada sigue siendo el clásico ALTER TABLE MOVE seguido de un ALTER INDEX REBUILD.

Comprender el comportamiento del High Water Mark en Oracle 19c es fundamental para evitar diagnósticos erróneos ante caídas de rendimiento repentinas tras procesos de purga masiva. Como DBAs, nuestro rol no solo consiste en liberar espacio en disco, sino en garantizar la eficiencia de las operaciones de E/S.

Saludos.

lunes, 18 de mayo de 2026

Cómo generar y analizar trazas en Oracle con DBMS_MONITOR y TKPROF


En muchas ocasiones nos encontramos con problemas de rendimiento en nuestras aplicaciones y necesitamos saber con exactitud qué consultas SQL están consumiendo la mayor cantidad de recursos en nuestra base de datos Oracle.

Una de las herramientas más potentes y clásicas para resolver este rompecabezas es la combinación de DBMS_MONITOR (para capturar la actividad) y TKPROF (para formatear y entender el archivo de traza resultante).

A partir de las versiones modernas de Oracle, el uso de DBMS_SUPPORT o ALTER SESSION SET SQL_TRACE ha quedado en desuso en favor del paquete DBMS_MONITOR, el cual nos da un control mucho más granular.

Identificar y activar la traza

Para este ejemplo, identificaremos el SID y el SERIAL# de la sesión que queremos auditar desde la vista V$SESSION. Una vez obtenidos los valores, ejecutamos el siguiente bloque PL/SQL desde una cuenta con privilegios de Administrador (como SYS o SYSTEM):

BEGIN
  DBMS_MONITOR.session_trace_enable(
    session_id => &target_sid,
    serial_num => &target_serial,
    waits      => TRUE,   -- Captura de eventos en v$session_wait / Extended Trace
    binds      => TRUE    -- Captura el volcado de variables bind en el dump
  );
END;
/

A partir de este momento, toda la actividad de esa sesión se registrará en un archivo de traza (.trc) en el servidor.

Localizar el archivo generado (.trc)


Para encontrar el archivo generado en el servidor, podemos lanzar la siguiente consulta en SQL*Plus o SQL Developer mientras la sesión siga activa para conocer la ruta exacta del directorio de diagnóstico:

SELECT
    p.tracefile
FROM v$session s
JOIN v$process p ON s.paddr = p.addr
WHERE s.sid = &target_sid;


Activar la traza con binds => TRUE y waits => TRUE genera un impacto en el rendimiento de la sesión afectada y puede llenar rápidamente el sistema de archivos si el proceso procesa millones de filas. Úsalo con moderación en entornos de producción.

Desactivar el rastreo

Cuando la sesión termine de ejecutar los procesos lentos, debemos proceder a deshabilitar el rastreo para no saturar el almacenamiento:

BEGIN
  DBMS_MONITOR.session_trace_disable(session_id => &target_sid, serial_num => &target_serial);
END;
/


Formatear la traza con TKPROF

El archivo .trc original es difícil de leer directamente de forma manual. Aquí es donde entra TKPROF, una utilidad de línea de comandos del sistema operativo que convierte el archivo plano en un reporte legible.

Abrimos la terminal de nuestro servidor (Linux/Windows) y ejecutamos la sintaxis básica:

tkprof ora_12345_pdb.trc user_report.txt sys=no sort=prsela,exeela,fchela


  • sys=no: Filtra y descarta las consultas recursivas del diccionario de datos ejecutadas por el usuario SYS, aislando únicamente el SQL emitido por la aplicación.

  • sort=prsela,exeela,fchela: Ordena el archivo de salida priorizando las sentencias SQL que acumularon el mayor tiempo transcurrido (Elapsed Time) durante las fases de Parsing, Ejecución y Fetch.

Interpretación de los resultados


Al abrir nuestro archivo user_report.txt, veremos bloques de información para cada sentencia ejecutada, organizados en una tabla de estadísticas como esta:


  • Elapsed: Es el tiempo total que tardó la consulta de cara al usuario. Si es muy alto comparado con el tiempo de CPU, significa que la consulta estuvo esperando por recursos (bloqueos, lectura de disco, etc.).
  • Query: Representa las lecturas lógicas en memoria (Buffers). Un número extremadamente alto aquí suele indicar que a la consulta le falta un índice adecuado y está realizando un Full Table Scan.
  • Disk: Lecturas físicas en el disco duro. Las lecturas en disco siempre penalizan el rendimiento.

El uso combinado de DBMS_MONITOR y TKPROF sigue siendo una de las metodologías más certeras y recomendadas por los expertos para realizar Tuning de SQL, ya que nos muestra la realidad de lo que ocurre sin suposiciones.

Saludos.

miércoles, 13 de mayo de 2026

Evento enq: JQ - contention ¿Por qué se detienen mis Jobs?


Recientemente, me encontré con un escenario donde los procesos de Job Queue de una base de datos Oracle parecían no avanzar. Al revisar las vistas de rendimiento, identifiqué una espera prolongada en el evento enq: JQ - contention

Para entender el problema, primero debemos recordar cómo gestiona Oracle los jobs. A partir de Oracle 11g y 12c, la arquitectura de Oracle Scheduler separa la programación de la ejecución real mediante varios procesos de fondo:

  • CJQ0 (Job Queue Coordinator): Es el director de orquesta. Lee la tabla de jobs, determina cuál debe ejecutarse y levanta procesos esclavos para realizar el trabajo.

  • Jnnn / Wnnn (Job/Worker Slaves): Son los procesos que ejecutan el código PL/SQL del job.

El Enqueue JQ es un bloqueo de nivel de instancia utilizado para serializar el control de los trabajos. Oracle lo usa para asegurar que un job no sea ejecutado por dos procesos a la vez y para gestionar la creación/terminación de los procesos esclavos.

Cuando vemos el evento enq: JQ - contention, significa que hay una fila de procesos esperando por ese "permiso". Esto suele ocurrir por saturación del coordinador, límites bajos en JOB_QUEUE_PROCESSES o jobs "zombies" que mantienen el bloqueo sin liberar el recurso.

Al ejecutar una consulta a v$session para monitorear los procesos tipo Job (J001), observamos lo siguiente:

SELECT sid, serial#, event, p1, p2, p3, seconds_in_wait FROM v$session WHERE program LIKE '%(J001)%';


Observen la columna SECONDS_IN_WAIT. El proceso lleva más de 12 horas esperando. El valor de P1 (1246822406) traducido a ASCII corresponde precisamente a 'JQ'.

Podemos obtener el valor de P1 traducido a ASCII, con el siguiente query:

SELECT chr(bitand(1246822406,-16777216)/16777216)|| chr(bitand(1246822406,16711680)/65536) AS Enqueue_Type FROM dual;

Para identificar el SPID a nivel de sistema operativo:

SELECT s.sid, p.spid, s.program, s.event FROM v$session s JOIN v$process p ON s.paddr = p.addr WHERE s.program LIKE '%(J001)%';


 En este escenario, al revisar otros procesos esclavos (Workers W...), detectamos sesiones con un LAST_CALL_ET muy alto, lo que indica que están ejecutando tareas que se quedaron colgadas o son ineficientes:

 SELECT p.spid, s.username, s.program, s.last_call_et FROM v$session s JOIN v$process p ON s.paddr = p.addr WHERE s.sid = 4170;



Si la contención es crítica y los jobs están paralizados, la solución más efectiva es "resetear" el coordinador de jobs:

  1. Verificamos el valor actual del parámetro job_queue_processes.

    sqlplus / as sysdba
    SHOW PARAMETER "job_queue_processes";

     

  2. Establecemos el parámetro a cero.

    ALTER SYSTEM SET job_queue_processes=0;

  3. Si existen procesos a nivel de OS que no mueren, identificarlos por su SPID y terminarlos.

  4. Volvemos al valor original, en este caso teníamos lo establecido en 2000.

    ALTER SYSTEM SET job_queue_processes=2000;

Esto obliga al CJQ0 a reiniciarse y liberar los enqueues persistentes.

Como hemos visto, el evento enq: JQ - contention es una señal clara de que el motor de jobs está saturado o bloqueado por un proceso ineficiente. En entornos críticos, especialmente en RAC, no basta con aumentar el número de procesos; es fundamental monitorear el LAST_CALL_ET de los workers para identificar tareas "zombies" que secuestran al coordinador. Realizar un reseteo controlado de la cola de trabajos suele ser la solución más efectiva para normalizar el servicio sin afectar al resto de las instancias del cluster.

lunes, 27 de abril de 2026

Uso de Hints de Paralelismo en grandes volúmenes de datos


Bajo el ecosistema de Oracle Database, es habitual lidiar con procesos que manejan millones de registros (Batch, ETL o reportes pesados). Cuando el tiempo de respuesta es fundamental, el procesamiento en paralelo se convierte en nuestra mejor herramienta.

Sin embargo, el Optimizador de Oracle (CBO) no siempre elige el paralelismo de forma automática. En este post, veremos cómo forzarlo correctamente y qué consideraciones debemos tener.

Cuando usamos un hint de paralelismo, Oracle divide una tarea grande en piezas más pequeñas. Un Query Coordinator (QC) coordina a varios procesos esclavos (Parallel Execution Servers) que trabajan simultáneamente.

La forma más común de implementarlo es a través del hint /*+ PARALLEL(alias, degree) */. El degree o grado define cuántos procesos esclavos vamos a solicitar.

SELECT /*+ PARALLEL(ventas, 4) */ 
       producto_id, SUM(monto)
FROM fact_ventas ventas
GROUP BY producto_id;

En este caso, estamos solicitando 4 procesos esclavos para escanear la tabla y realizar la agregación.

Este es un punto donde muchos fallan. Para que un INSERT funcione realmente rápido, debemos combinar el paralelismo con el Direct Path Load mediante el hint APPEND. Esto hace que Oracle escriba directamente al final de la tabla, saltándose el Buffer Cache y generando mucho menos Redo Log.

ALTER SESSION ENABLE PARALLEL DML;
INSERT /*+ APPEND PARALLEL(destino, 8) */ INTO table_final destino
SELECT /*+ PARALLEL(origen, 8) */ * FROM table_staging origen;
COMMIT;

Para confirmar que el hint está funcionando, debemos generar el plan de ejecución (EXPLAIN PLAN). Debemos identificar las siguientes operaciones:

  • PX COORDINATOR: El proceso principal que une los resultados.
  • PX SEND / PX RECEIVE: Indica que los datos se están moviendo entre procesos esclavos.
  • PX BLOCK ITERATOR: Oracle ha dividido la tabla en bloques para los esclavos.
  • LOAD AS SELECT (HIGH WATER MARK): Indica que el APPEND y el paralelismo de escritura están activos.

Si el plan de ejecución muestra estas líneas (comenzando con "PX"), el paralelismo se está aplicando correctamente.


Este plan de ejecución representa una carga de datos de alto rendimiento (Full Parallel Stack) que utiliza el paralelismo tanto para leer como para escribir. En lugar de procesar los datos fila por fila, el (PX Coordinator) divide la tarea entre 4 procesos esclavos que filtran los duplicados simultáneamente y realizan una inserción directa al disco (Direct Path Load), saltándose la memoria intermedia y escribiendo los datos al final de la tabla de forma masiva. Además, el plan incluye el mantenimiento de índices en paralelo, lo que evita que la base de datos se ralentice al actualizar los índices después de la carga, garantizando la máxima velocidad posible y un uso mínimo de los logs de transacciones.

IMPORTANTE:

No se debe abusar del paralelismo. Aquí mis recomendaciones:

  1. Costo de CPU: Cada proceso paralelo consume CPU. Si el servidor tiene 8 núcleos y lanzas un PARALLEL 32, causarás una degradación general del sistema.
  2. Uso de Memoria: El paralelismo utiliza la PGA de forma intensiva para los ordenamientos y joins. Asegúrate de tener suficiente PGA_AGGREGATE_TARGET.
  3. Tablas pequeñas: No utilices este hint en tablas de pocos registros. El tiempo que le toma a Oracle gestionar los procesos esclavos será mayor que la ganancia de velocidad.

El uso de hints de paralelo es fundamental para grandes volúmenes, pero un grado de paralelismo mal calculado puede afectar a todos los usuarios. ¡Úsalo con cautela y siempre verifica tu plan de ejecución!

jueves, 9 de abril de 2026

Resolviendo el error ORA-1652: unable to extend temp segment by 128 in tablespace TEMP


El error ORA-1652 es uno de los incidentes más temidos por los DBAs. La reacción más común suele ser añadir más archivos de datos (Tempfiles), pero la experiencia demuestra que el aumento en el consumo de espacio temporal es debido a un proceso o consulta ineficiente.

Cuando el error ocurre, el espacio en TEMP se libera automáticamente al fallar la sesión, borrando los registros en las vistas de tiempo real. Para investigar, debemos consultar el historial de sesiones activas (ASH) para encontrar el pico máximo de consumo de cada consulta.

SELECT 
    sql_id,
    user_id,
    program,
    round(MAX(temp_space_allocated)/1024/1024/1024,2) AS max_temp_gb,
    MIN(sample_time) AS inicio,
    MAX(sample_time) AS fin
FROM v$active_session_history
WHERE temp_space_allocated > 0
  AND sql_id IS NOT NULL
--Aqui colocamos la fecha y hora relacionada al error ORA-1652  
  AND sample_time BETWEEN TO_TIMESTAMP('08/04/2026 08:00', 'DD/MM/YYYY HH24:MI') 
                      AND TO_TIMESTAMP('08/04/2026 08:30', 'DD/MM/YYYY HH24:MI')
GROUP BY sql_id, user_id, program
ORDER BY max_temp_gb DESC;


En nuestro caso, encontramos que el SQL_ID 85v0ju71vfphg alcanzó un pico de 370 GB. Al revisar el reporte AWR (Automatic Workload Repository), la sección "Top SQL with Top Events" reveló lo siguiente:


  • Evento: direct path write temp
  • Wait Class: User I/O
  • Top Row Source: HASH JOIN / HASH GROUP BY
¿Por qué una consulta necesitaría tanto espacio? Al obtener el plan con DBMS_XPLAN.DISPLAY_AWR, detectamos dos problemas críticos:
  1. MERGE JOIN CARTESIAN: El motor intentó realizar un producto cartesiano (unir cada fila con todas las filas de otra tabla) debido a una condición de unión mal definida con una tabla. Esto multiplicó exponencialmente el set de datos intermedio.

  2. SORT UNIQUE: El uso de UNION (en lugar de UNION ALL) obligó a un ordenamiento masivo para eliminar duplicados de un set de datos que ya estaba inflado por el cartesiano.


Para solucionar esto, el primer paso es auditar la lógica de las uniones (JOINs): asegurar que todas las tablas tengan su predicado de unión correspondiente en el WHERE o en la cláusula ON.

Si la lógica es correcta pero el optimizador sigue eligiendo un cartesiano por un error de estimación en las estadísticas, podemos intervenir usando el hint /*+ NO_CARTESIAN */. Asimismo, evaluamos cambiar UNION por UNION ALL si sabemos que los resultados de ambas consultas son disjuntos o si los duplicados son aceptables.


El ORA-1652 no se resuelve simplemente asignando más disco. Antes de ampliar, identifica el pico (ASH), confirma el evento (AWR) y audita la lógica (Plan de Ejecución). Optimizar el código es, en la gran mayoría de los casos, la forma más eficiente y profesional de resolver este error.

martes, 31 de marzo de 2026

Dimensionamiento de Memoria en Oracle


El rendimiento de una base de datos Oracle no depende de cuánta RAM tengas, sino de cómo la distribuyes. En un entorno de producción, la memoria es un recurso finito y costoso; asignarla en exceso es tan peligroso como quedarse corto, ya que puede inducir problemas de paginación a nivel de OS o enmascarar consultas ineficientes que deberían ser optimizadas en código.

En esta guía aprenderemos a interpretar los Advisors para ajustar la SGA y la PGA utilizando los componentes internos del motor.

Antes de tocar parámetros, debemos entender que es SGA y PGA:

  • SGA (System Global Area): Memoria compartida. Su objetivo es minimizar la I/O física (lectura de disco).

  • PGA (Program Global Area): Memoria privada por proceso. Su objetivo es realizar ordenamientos y uniones "In-Memory".




  • db file sequential read (11.4% del tiempo): Es el tiempo que la base de datos pasa esperando a que el disco le entregue un bloque de datos que no encontró en la memoria. La SGA es demasiado pequeña para el volumen de datos que consultamos. El motor está "trabajando de más" yendo al disco por información que debería estar ya en la RAM.
  • acknowledge over PGA limit: Este es un evento "semáforo". Aparece cuando Oracle detiene un proceso porque ya se alcanzó el límite máximo de memoria RAM permitido. El sistema está asfixiado. Literalmente está "poniendo en cola" a los usuarios porque no hay más memoria disponible para procesar sus consultas. Es la confirmación de que nuestra PGA necesita crecer urgentemente.

Diagnostico SGA (V$SGA_TARGET_ADVICE):
Ejecuta la siguiente consulta:

SELECT sga_size, sga_size_factor, estd_db_time, estd_physical_reads
FROM v$sga_target_advice
ORDER BY sga_size_factor;

Si aumentamos la SGA de 30 GB a 45 GB (Factor 1.5):

  • Lecturas Físicas: Bajan de 170B a 149B (Una reducción del ~12%).
  • Tiempo de DB: Baja de 118M a 114M (Una mejora de apenas el 3.4%).
Necesitamos invertir 15 GB adicionales de RAM para obtener una mejora de velocidad que el usuario final probablemente ni siquiera notará.

Diagnostico PGA(V$PGA_TARGET_ADVICE):
Ejecuta la siguiente consulta:

SELECT pga_target_for_estimate/1024/1024 AS target_mb, 
       pga_target_factor, 
       estd_extra_bytes_rw,
       estd_pga_cache_hit_percentage
FROM v$pga_target_advice;

Si aumentamos la PGA de 10 GB a 18 GB (Factor 1.8):

  • Tráfico en Disco (Extra Bytes RW): Baja de 186 TB a 182 TB. Ahorramos 4 Terabytes de escritura innecesaria en el TEMP.

  • Eficiencia (Cache Hit %): Sube del 91% al 92%.

Es una inversión inteligente. Con solo 8 GB adicionales de RAM, eliminamos el cuello de botella que causa el evento acknowledge over PGA limit y estabilizamos las operaciones pesadas.

Una vez identificado el tamaño óptimo, aplica los cambios. Recuerda que si usas ASMM (Automatic Shared Memory Management), solo ajustas los targets.

Puedes usar esta consulta para verificar:

SELECT 
    name, 
    value/1024/1024 AS mbytes, 
    CASE 
        WHEN name = 'sga_target' AND value > 0 THEN 'ASMM: Gestión Automática de SGA'
        WHEN name = 'pga_aggregate_target' AND value > 0 THEN 'PGA: Gestión Automática de Procesos'
        WHEN name = 'memory_target' AND value > 0 THEN 'AMM: Gestión Total (SGA + PGA)'
        ELSE 'Gestión Manual / Límite Estricto'
    END AS modo_gestion
FROM v$parameter 
WHERE name IN ('sga_target', 'pga_aggregate_target', 'memory_target', 'pga_aggregate_limit');


En este caso solo redimensionaremos la PGA, dado que tenemos mayor beneficio por un bajo costo (8GB de RAM).

-- 1. Subir el Target
ALTER SYSTEM SET pga_aggregate_target = 18G SCOPE=BOTH;
-- 2.  Lo ideal es que el limit sea al menos 2x el target
ALTER SYSTEM SET pga_aggregate_limit = 36G SCOPE=BOTH; 

La PGA es mucho más flexible. Puedes aplicar los cambios y tendrán efecto inmediato, pero cuidado con el límite (limit), debe ser siempre mayor al target.

IMPORTANTE:

No redimensiones sin validar estos tres puntos:

  1. Evitar el Paging: La suma de SGA + PGA + SO nunca debe superar el 80% de la RAM física. Si el SO empieza a usar Swap, el rendimiento de Oracle colapsará.

  2. HugePages (Solo Linux): Si tu SGA es mayor a 8GB, asegúrate de configurar HugePages a nivel de kernel para evitar que el proceso vktm consuma demasiada CPU gestionando tablas de páginas.

  3. Monitoreo Post-Cambio: Tras el ajuste, monitorea V$SQL_WORKAREA_ACTIVE para confirmar que las operaciones pasaron de MULTI-PASS a OPTIMAL.

La mejor optimización de memoria no es comprar más módulos de RAM, sino reducir la necesidad de ella mediante la indexación correcta y el tuning de sentencias SQL.

viernes, 20 de marzo de 2026

Resolviendo las limitaciones del Auto Optimizer Stats Collection en Oracle


En el mundo de la administración de bases de datos, las estadísticas son la ruta de acceso que el optimizador (CBO) elegirá para ejecutar una sentencia SQL. Sin ellas, hasta la consulta más simple puede convertirse en una pesadilla de rendimiento. Oracle nos ofrece por defecto el Auto Optimizer Stats Collection, un job diseñado gestionar la recolección de estadísticas de forma integral y desatendida. Sin embargo, en entornos de Data Warehouse con volúmenes masivos de datos esto suele ser insuficiente.

Recientemente, en una de las bases de datos que administro, me enfrenté a un escenario crítico: el mantenimiento nativo de Oracle no lograba concluir dentro de la ventana programada de 4 horas. Esto derivaba en la presencia de estadísticas obsoletas (STALE) y el bloqueo de estadísticas en objetos específicos, provocando una degradación progresiva en el rendimiento global de la base de datos.

  • El proceso se interrumpía dejando gran parte de los objetos sin procesar.

  • Las estadísticas quedaban en un estado inconsistente, impidiendo actualizaciones manuales y disparando errores como el ORA-20005.

  • Sin datos frescos, el optimizador elegía rutas de acceso ineficientes, aumentando drásticamente la latencia global del sistema.

Para romper este ciclo, decidimos tomar el control total mediante una automatización personalizada. Nuestra lógica aplica reglas de ingeniería de datos claras:

  • El procedimiento identifica si el esquema contiene tablas particionadas o normales para aplicar el método de recolección correcto.
  • Priorizamos la última partición vigente, asegurando que el optimizador (CBO) siempre tenga datos frescos de la carga reciente sin desperdiciar recursos en datos históricos estáticos.
  • Implementamos una tabla de control para registrar cada inicio, fin y duración, permitiendo una auditoría que el job nativo no ofrece de forma tan detallada.

Importante: Antes de implementar esta solución es necesario desactivar la recolección automática de estadísticas de Oracle. 

Pero antes verificamos el estado actual de la tarea de recolección de estadísticas.

select client_name,status from dba_autotask_client where client_name='auto optimizer stats collection';

Procedemos a desactivar la tarea de recolección automática de Oracle.

BEGIN
  DBMS_AUTO_TASK_ADMIN.DISABLE(
    client_name => 'auto optimizer stats collection',
    operation   => NULL,
    window_name => NULL
  );
END;
/

Verificamos nuevamente el estado actual de la tarea de recolección de estadísticas.

He compartido la solución completa (DDL de bitácora, procedimiento PL/SQL y configuración del Job) en mi repositorio de GitHub para que la comunidad pueda adaptarlo:

🔗Oracle-Stats-Automation

Puedes consultar el estado de tus estadísticas y el rendimiento del job mediante la tabla de bitácora:

select * from sys.gbd_bitacora_estat where grupo=5 order by 1,2 asc;

Esta solución fue desarrollada y validada con éxito en un entorno Oracle Database 12c, demostrando una estabilidad total tanto en la gestión de objetos particionados como en la integración con el Oracle Scheduler. Aunque el enfoque es estándar, se recomienda realizar pruebas previas en entornos de desarrollo antes de pasar a producción.

lunes, 16 de febrero de 2026

Resolviendo el evento de espera: Streams AQ: enqueue blocked on low memory


Era uno de esos días en los que me encontraba realizando tareas de respaldo de información de las particiones de tablas de una de las muchas bases de datos que administro.
Noté algo que me llamó la atención: ¿Cómo podían exportarse más de 1,735 particiones de otra base de datos en horas, mientras que las 762 particiones de esta base de datos demoraban días? Sí, leyeron bien: 2 a 3 días para completar la exportación.

Claramente esto no era normal, había algo que estaba afectando el proceso de exportación (EXPDP). Me puse manos a la obra para descubrir cuál era el problema. Inicialmente pensé que era un tema de tamaño de las particiones, por lo que revisé la cantidad de registros y el peso total de cada una, comparándolas con otra base de datos. Sorprendentemente, estas particiones eran más pequeñas, por lo que descarté esa hipótesis.

Generé un reporte AWR para analizar el rendimiento de la base de datos mientras se ejecutaba el proceso de exportación. Grande fue mi sorpresa al encontrar este tipo de espera:

Queueing
(Encolamiento: tiempo que una sesión pasa esperando recursos antes de poder ejecutarse)


Al revisar el top de eventos, identifiqué un evento peculiar que nunca antes había visto: Streams AQ: enqueue blocked on low memory.


Buscando información sobre este evento, encontré la nota de Oracle Support: EXPDP And IMPDP Slow Performance In 11gR2 and 12cR1 And Waits On Streams AQ: Enqueue Blocked On Low Memory (Doc ID 1596645.1)

El documento describía exactamente los síntomas que estaba experimentando:

  • Oracle Data Pump Export (expdp) y Import (impdp) funcionan muy lentamente en versiones como 11gR2 y 12cR1


Solución:

Modificar el parámetro STREAMS_POOL_SIZE si no contaba con un valor asignado y luego reiniciar la base de datos para que el cambio surta efecto.

La pregunta siguiente era: ¿Cuánto asignarle? ¿8 MB? ¿16 MB?

En mi búsqueda encontré una consulta dinámica que calcula automáticamente un valor seguro, tomando el valor máximo entre los parámetros streams_pool_size y __streams_pool_size y sumándole 64 MB:

select 'alter system set streams_pool_size='||(max(to_number(trim(c.ksppstvl)))+67108864)||' SCOPE=SPFILE;' from sys.x$ksppi a, sys.x$ksppcv b, sys.x$ksppsv c where a.indx = b.indx and a.indx = c.indx and lower(a.ksppinm) in ('__streams_pool_size','streams_pool_size');

Con esto obtuve el valor recomendado para asignar al parámetro.

1. Conectarse como SYSDBA
sqlplus / as sysdba

2. Modificar el parámetro 


SQL> alter system set streams_pool_size=384m scope=both;


3. Reiniciar la base de datos

SQL> shutdown immediate;

SQL> startup;


4. Verificar el cambio

SQL> show parameter "streams_pool_size";






Luego del cambio:

  • La espera Streams AQ: enqueue blocked on low memory desapareció.

  • Las exportaciones con Data Pump progresaron con normalidad.

  • El rendimiento general mejoró notablemente.




Si alguna vez experimentan exportaciones lentas con Data Pump y detectan el evento de espera: Streams AQ: enqueue blocked on low memory aumentar el parámetro STREAMS_POOL_SIZE siguiendo el procedimiento descrito es la solución más efectiva.


Saludos,
Luis Felipe.