<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Carla Muñoz López]]></title><description><![CDATA[Soy DBA senior de bases de datos Oracle y me defino como una persona alegre y creativa. Apasionada por conocer, compartir ideas, divertirme y seguir aprendiendo]]></description><link>https://selectfromdual.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 06:24:30 GMT</lastBuildDate><atom:link href="https://selectfromdual.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Optimización de Índices: De Reverse a B-Tree en Entornos Particionados]]></title><description><![CDATA[En administración de bases de datos Oracle, no todos los índices se comportan igual ante un cambio de estructura. Recientemente me enfrenté a la necesidad de transformar un índice Reverse Key a un índ]]></description><link>https://selectfromdual.com/optimizaci-n-de-ndices-de-reverse-a-b-tree-en-entornos-particionados</link><guid isPermaLink="true">https://selectfromdual.com/optimizaci-n-de-ndices-de-reverse-a-b-tree-en-entornos-particionados</guid><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Wed, 25 Mar 2026 09:38:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/64254682d76bf2adc099aaf2/79a552fb-3f38-4faa-a80e-07dc3b6116dc.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En administración de bases de datos Oracle, no todos los índices se comportan igual ante un cambio de estructura. Recientemente me enfrenté a la necesidad de transformar un índice Reverse Key a un índice Normal (B-Tree).</p>
<h2>El matiz: ¿Global o Local?</h2>
<p>Si estuviéramos ante un índice <strong>Global</strong>, la solución sería inmediata y elegante: <code>ALTER INDEX IDX_GLOBAL REBUILD ONLINE NOREVERSE;</code></p>
<p>Pero, <strong>¿qué pasa si el índice es LOCAL y la tabla está particionada?</strong> Aquí la sintaxis de Oracle nos limita, y no podemos usar la cláusula <code>NOREVERSE</code> en un rebuild de este tipo. Por ello, debemos recurrir a una estrategia de reconstrucción completa.</p>
<h2>La Estrategia: Creación online e Invisible</h2>
<p>Para evitar el error ORA-01408 (columna ya indexada) y no degradar rendimiento, el flujo de trabajo ideal es el siguiente:</p>
<h3>Preparación del terreno (TEMP y Monitorización)</h3>
<p>Dado que moveremos grandes volúmenes de datos (en mi caso, más de 100 GB), es vital asegurar que el tablespace temporal aguante el "sort". SQL</p>
<pre><code class="language-plaintext">-- Monitorización del consumo de TEMP durante la creación en entorno de prueba
SELECT ROUND(SUM(blocks)*8/1024, 2) MB_REAL FROM v$sort_usage;
</code></pre>
<h3>Creación del nuevo índice "en la sombra"</h3>
<p>Utilizamos las cláusulas ONLINE para no bloquear DML e INVISIBLE ya que si no nos encontraríamos el error: ORA-01408: such column list already indexed.</p>
<p>Oracle permite tener dos índices sobre las mismas columnas siempre y cuando <strong>solo uno de ellos sea visible</strong> para el optimizador y una estructura física distinta,</p>
<pre><code class="language-plaintext">CREATE INDEX XXX.IDX_LOG_ENV_PERF 
ON XXX.LOG_ENTREGAS (ID_DISPOSITIVO, FECHA_LOG) 
LOCAL 
ONLINE 
INITRANS 50 
PCTFREE 20 
TABLESPACE TBS_INDEXES 
PARALLEL 8 -- Aprovechamos la potencia del server 
INVISIBLE;

ALTER INDEX XXX.IDX_LOG_ENV_PERF NOPARALLEL;
</code></pre>
<p>Compruebo que ambos conviven:</p>
<img src="https://cdn.hashnode.com/uploads/covers/64254682d76bf2adc099aaf2/42d5fb4b-c7aa-411d-ace2-a56d4624188d.png" alt="" style="display:block;margin:0 auto" />

<blockquote>
<p><strong>Nota importante sobre el rendimiento:</strong> Ten en cuenta que mientras ambos índices convivan (el antiguo Reverse y el nuevo Normal), cada operación de <code>INSERT</code>, <code>UPDATE</code> o <code>DELETE</code> tendrá que actualizar ambos. Esto supone una <strong>penalización temporal en las operaciones DML</strong>, por lo que se recomienda realizar el cambio en horas de menor carga.</p>
</blockquote>
<h3>El Switch de Visibilidad</h3>
<p>Una vez creado, el cambio es instantáneo a ojos de la aplicación. Pasamos el índice antiguo a modo invisible y activamos el nuevo. Esto nos da un plan de rollback inmediato: si algo falla, solo hay que revertir la visibilidad.</p>
<pre><code class="language-plaintext">ALTER INDEX XXX.IDX_ORIGINAL_REVERSE INVISIBLE; 
ALTER INDEX XXX.IDX_LOG_ENV_PERF VISIBLE;
</code></pre>
<p>Va todo bien? Liberamos espacio una vez validado el cambio .</p>
<pre><code class="language-plaintext">DROP INDEX XXX.IDX_ORIGINAL_REVERSE;
</code></pre>
<p>Cuando trabajamos con <strong>índices locales</strong>, perdemos la facilidad del <code>REBUILD NOREVERSE</code>.</p>
<p>Utilizar índices invisibles y creación online es la forma más transparente de realizar este cambio en entornos críticos de 24/7.</p>
]]></content:encoded></item><item><title><![CDATA[El lado oscuro del DRM en Oracle RAC: Micro-cortes y el evento 'gcs drm freeze']]></title><description><![CDATA[En un entorno Oracle RAC, la "magia" de la coherencia de caché tiene un precio.
Hoy analizaré un caso real (los que me gustan!) donde el mecanismo de Dynamic Remastering (DRM) causó un micro-delay de ]]></description><link>https://selectfromdual.com/el-lado-oscuro-del-drm-en-oracle-rac-micro-cortes-y-el-evento-gcs-drm-freeze</link><guid isPermaLink="true">https://selectfromdual.com/el-lado-oscuro-del-drm-en-oracle-rac-micro-cortes-y-el-evento-gcs-drm-freeze</guid><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Tue, 24 Feb 2026 11:45:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/64254682d76bf2adc099aaf2/7dd6e71e-f31d-4008-919a-ad8c0557caa9.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En un entorno Oracle RAC, la "magia" de la coherencia de caché tiene un precio.</p>
<p>Hoy analizaré un caso real (los que me gustan!) donde el mecanismo de Dynamic Remastering (DRM) causó un micro-delay de casi un segundo, demorando transacciones críticas.</p>
<h2><strong>El síntoma: El evento de espera "fantasma"</strong></h2>
<p>Al revisar la ASH (dba_hist_active_sess_history) debido a una demora puntual de menos de 1 segundo, detecto algo que me llama la atención.</p>
<pre><code class="language-plaintext">SYS@cdbxxx1&gt; SELECT
    event,
    count(*) as total_capturas,
    round(avg(wait_time+time_waited)/1000, 2) as avg_wait_ms
FROM dba_hist_active_sess_history
WHERE sample_time BETWEEN TO_TIMESTAMP('22/02/26 06:13:00', 'DD/MM/YY HH24:MI:SS')
                      AND TO_TIMESTAMP('22/02/26 06:14:00', 'DD/MM/YY HH24:MI:SS')
GROUP BY event
ORDER BY 3 DESC;


EVENT								 TOTAL_CAPTURAS AVG_WAIT_MS
---------------------------------------------------------------- -------------- -----------
gcs drm freeze in enter server mode					      1      919.47
LGWR worker group ordering						      1       33.48
log file sync								     31       26.52
LGWR any worker group							      2       17.67
									      4 	.75
db file sequential read 						      1 	 .7
buffer busy waits							      1 	.39
log file parallel write 						      2 	.38
rdbms ipc reply 							      1 	  0


Evento: gcs drm freeze in enter server mode

Tiempo de espera: 919.47 ms (¡Casi 1 segundo!)
</code></pre>
<h2><strong>¿Qué estaba pasando internamente? (La traza de LMON)</strong></h2>
<p>La traza del proceso LMON nos da la respuesta. El sistema detectó que ciertos objetos tenían una carga de lectura/escritura que no era óptima para su ubicación actual y decidió "remasterizarlos" (mover la gestión del objeto de un nodo a otro).</p>
<pre><code class="language-plaintext">*** 2026-02-22T06:13:24.101236+01:00 (CDB$ROOT(1))

* DRM RCFG called (swin 0)
* kjfcdrmrfg: cap sync timeout at 3 * default (752 -&gt; 489 secs, res limit 7518552)
Begin DRM(29029) (swin 0)
 AFFINITY pdb 3 tsn 13 object id 720963, * -&gt; 2 obj size:7333
 AFFINITY pdb 3 tsn 12 object id 720969, 1 -&gt; * obj size:449893
 AFFINITY pdb 3 tsn 11 object id 720996, 1 -&gt; * obj size:217838
 AFFINITY pdb 3 tsn 52 object id 731023, * -&gt; 2 obj size:92115
 AFFINITY pdb 3 tsn 13 object id 731026, * -&gt; 2 obj size:6331
 AFFINITY pdb 3 tsn 13 object id 731027, * -&gt; 2 obj size:12668
 AFFINITY pdb 3 tsn 13 object id 731028, * -&gt; 2 obj size:5780
 AFFINITY pdb 3 tsn 13 object id 731029, * -&gt; 2 obj size:6685
 AFFINITY pdb 3 tsn 13 object id 731030, * -&gt; 2 obj size:6077
 AFFINITY pdb 3 tsn 13 object id 731031, * -&gt; 2 obj size:5386
 AFFINITY pdb 3 tsn 13 object id 731032, * -&gt; 2 obj size:7677
 AFFINITY pdb 3 tsn 13 object id 731033, * -&gt; 2 obj size:5352
 AFFINITY pdb 3 tsn 13 object id 731034, * -&gt; 2 obj size:3425
 AFFINITY pdb 3 tsn 13 object id 731035, * -&gt; 2 obj size:4113
 AFFINITY pdb 3 tsn 13 object id 731036, * -&gt; 2 obj size:3602
 AFFINITY pdb 3 tsn 13 object id 731037, * -&gt; 2 obj size:7983
 AFFINITY pdb 3 tsn 13 object id 731038, * -&gt; 2 obj size:5376
 AFFINITY pdb 3 tsn 13 object id 731039, * -&gt; 2 obj size:3714
 AFFINITY pdb 3 tsn 14 object id 329632, * -&gt; 2 obj size:295
 AFFINITY pdb 3 tsn 13 object id 731040, * -&gt; 2 obj size:5576
 AFFINITY pdb 3 tsn 13 object id 731041, * -&gt; 2 obj size:7776
 AFFINITY pdb 3 tsn 13 object id 731042, * -&gt; 2 obj size:2757
 AFFINITY pdb 3 tsn 13 object id 706860, 1 -&gt; * obj size:131057
 AFFINITY pdb 3 tsn 13 object id 690745, * -&gt; 2 obj size:634
 AFFINITY pdb 3 tsn 51 object id 733055, * -&gt; 2 obj size:4095
 AFFINITY pdb 3 tsn 14 object id 447506, * -&gt; 2 obj size:257
 AFFINITY pdb 3 tsn 13 object id 718971, * -&gt; 2 obj size:1709
Max cumultv sz 2624716 sz limit  4294967295 pk cnt 27 total objsz 1005504
 individual max sizes [ 1005504 2624716 ]
 Estimated time 6.2 secs  (avgtime 0.9 secs, avgsize 384055)
* drm freeze
 drm (29029) freeze step (window 1) - posted lmses/lmds for mtobe
 drm (29029) freeze step (window 1) - apifrz = true
 drm (29029) freeze step (window 1) - all lms procs frozen
* drm sync 1
* drm cleanup
* kjfclmsync: lmses complete parallel work in step 0.33.0
* drm sync 2
* drm replay
* kjfclmsync: lmses complete parallel work in step 0.35.0
send ftd(35) to 2:
* drm sync 3
* drm fix writes
* kjfclmsync: lmses complete parallel work in step 0.37.0
* drm sync 4
* drm end
* window 1 time 0.9 secs)

* DRM RCFG called (swin 0)
* kjfcdrmrfg: cap sync timeout at 3 * default (768 -&gt; 489 secs, res limit 7673832)
* drm freeze
 drm (29029) freeze step (window 2) - posted lmses/lmds for mtobe
 drm (29029) freeze step (window 2) - apifrz = true
 drm (29029) freeze step (window 2) - all lms procs frozen
* drm sync 1
* drm cleanup

*** 2026-02-22T06:13:25.234977+01:00 (CDB$ROOT(1))
* kjfclmsync: lmses complete parallel work in step 0.33.0
* drm sync 2
* drm replay
* kjfclmsync: lmses complete parallel work in step 0.35.0
send ftd(35) to 2:
* drm sync 3
* drm fix writes
* kjfclmsync: lmses complete parallel work in step 0.37.0
* drm sync 4
* drm end
* window 2 time 1.0 secs)
</code></pre>
<p><code>window 1 time 0.9 secs</code>: Exactamente los <strong>919 ms</strong> que capturó el ASH</p>
<p><strong>El momento del "Freeze"</strong></p>
<p>Lo más crítico de la traza es cuando Oracle congela los procesos LMS (los encargados de mover los bloques de datos por el interconnect):</p>
<pre><code class="language-plaintext">* drm freeze drm (29029) freeze step (window 1)
* apifrz = true 
* all lms procs frozen
</code></pre>
<p><strong>¿Por qué es esto un problema?</strong> Cuando los procesos LMS se congelan para reorganizar las afinidades de los objetos, toda la comunicación de bloques entre nodos se detiene. Si tu aplicación está intentando leer un dato que está en el otro nodo en ese instante, la sesión se queda colgada en el evento gcs drm freeze.</p>
<h2>Los objetos afectados: Identificando a los culpables</h2>
<p>Cruzando los datos de gv$policy_history y dba_objects, veo que el sistema estuvo moviendo afinidades de particiones críticas:</p>
<pre><code class="language-plaintext">select h.*,o.object_name, o.SUBOBJECT_NAME 
from gv$Policy_History h, dba_objects o
where h.data_object_id = o.data_object_id 
and o.object_id IN (564682, 391009, 504609, 626248, 716966, 720969, 720996) 
and  TO_DATE(EVENT_DATE, 'MM/DD/YYYY HH24:MI:SS') BETWEEN TO_DATE('02/22/2026 06:00:00', 'MM/DD/YYYY HH24:MI:SS') AND TO_DATE('02/22/2026 07:00:00', 'MM/DD/YYYY HH24:MI:SS')
order by TO_DATE(EVENT_DATE, 'MM/DD/YYYY HH24:MI:SS') desc;

   INST_ID POLICY_EVENT           DATABASE_ID TABLESPACE_ID DATA_OBJECT_ID TARGET_INSTANCE_NUMBER EVENT_DATE               CON_ID OBJECT_NAME                                                                                                                      SUBOBJECT_NAME                                                                                                                  
---------- ---------------------- ----------- ------------- -------------- ---------------------- -------------------- ---------- -------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------
         1 initiate_affinity                3            12         720969                      2 02/22/2026 06:15:37           0 TABLA1                                                                                                                    SYS_P16222                                                                                                                      
         1 initiate_affinity                3            51         716966                      2 02/22/2026 06:15:37           0 TABLA2                                                                                                                     SYS_P16177                                                                                                                      
         1 initiate_affinity                3            11         720996                      2 02/22/2026 06:15:37           0 TABLA3                                                                                                        SYS_P16226                                                                                                                      
         1 push_affinity                    3            14         564682                      2 02/22/2026 06:05:37           0 TABLA4                                                                                                                        SYS_P15690                                                                                                                      
         1 push_affinity                    3            14         504609                      2 02/22/2026 06:05:37           0 TABLA4                                                                                                                        SYS_P15471                                                                                                                      
         1 push_affinity                    3            14         391009                      2 02/22/2026 06:05:37           0 TABLA4                                                                                                                        SYS_P15123                                                                                                                      
         1 push_affinity                    3            14         626248                      2 02/22/2026 06:05:37           0 TABLA4                                                                                                                        SYS_P15914                                                                                                                      
</code></pre>
<p>El sistema "empujó" (push_affinity) la gestión de los objetos hacia la instancia 2.</p>
<p><strong>El "Culpable" Inesperado: El Job de Estadísticas</strong></p>
<p>En nuestro caso real, tras investigar los horarios y el module de las sesiones, descubrimos al responsable. No era un error de la aplicación, sino el Job automático de estadísticas que entró por el nodo 2. La tormenta perfecta</p>
<ul>
<li><p>La Aplicación está trabajando intensamente en el Nodo 1, leyendo y escribiendo. Oracle ha establecido la afinidad de esos objetos en el Nodo 2 para optimizar el rendimiento.</p>
</li>
<li><p>El Job de Estadísticas se lanza desde el Nodo 2. Para calcular las estadísticas, el Nodo 12empieza a pedir masivamente bloques .</p>
</li>
<li><p>El Conflicto: El DRM de Oracle ve que el Nodo 2 ahora tiene una actividad brutal sobre esos objetos. Consulta su parámetro <em>gc</em>affinity_ratio y dice: "Oye, el Nodo 2 está pidiendo esto mucho más que antes, voy a mover la maestría del objeto del Nodo 1 al Nodo 2".</p>
</li>
<li><p>El Resultado: Durante ese cambio de "dueño", se produce el GCS Freeze. La aplicación en el Nodo 1 se detiene en seco durante 919 ms esperando a que el Nodo 2 termine de reclamar la propiedad de los datos.</p>
</li>
</ul>
<h2>El "Secuestro" de la Afinidad</h2>
<p>Lo que ocurrió en este caso es un secuestro de afinidad. Las estadísticas engañaron al DRM haciéndole creer que el nodo donde se ejecutaban era el más importante. El freeze de 919 ms fue el precio que pagó la aplicación mientras Oracle le "quitaba" las llaves de la tabla para dárselas al proceso de estadísticas.</p>
<h2>Opciones de Ajuste Fino (Tuning) del DRM</h2>
<p>Si decides mantener el DRM activo para aprovechar la optimización del Interconnect, pero quieres evitar los freezes causados por procesos como las estadísticas, tienes estos tres niveles de configuración:</p>
<h3>El "Filtro de Ruido" (_gc_affinity_ratio)</h3>
<p>Este es el parámetro que determina cuánta "presión" debe ejercer un nodo remoto para robarle la maestría a otro.</p>
<pre><code class="language-plaintext">Valor por defecto: 50.

Ajuste recomendado: 1000 o superior.

Efecto: Al subirlo a 1000, el proceso de estadísticas tendría que realizar muchísimas más solicitudes que la aplicación para que el DRM considere que vale la pena mover el objeto. Esto hace que el sistema ignore "picos" de trabajo de mantenimiento y se mantenga fiel a la aplicación OLTP.
</code></pre>
<h3>La "Ventana de Paciencia" (_gc_policy_time)</h3>
<p>Controla cada cuánto tiempo (en minutos) Oracle evalúa si debe remasterizar objetos.</p>
<pre><code class="language-plaintext">Valor por defecto: 10.

Ajuste recomendado: 60.

Efecto: Si tu job de estadísticas dura 30 minutos, pero la ventana de evaluación es de 60, el job terminará antes de que el DRM decida que el cambio de maestría es necesario. Evitas el freeze simplemente siendo más paciente.
</code></pre>
<h3>El "Límite de Agresión" (_gc_affinity_limit)</h3>
<p>Define el número mínimo de accesos necesarios para que un objeto sea siquiera candidato a DRM.</p>
<pre><code class="language-plaintext">Ajuste recomendado: Incrementar el valor actual (puedes ver el actual en V$GC_POLICY_MODIFICATIONS).

Efecto: Evita que tablas pequeñas o medianas sean movidas de nodo constantemente por consultas aleatorias o jobs rápidos, reservando el DRM solo para objetos donde el ahorro de tráfico sea masivo y justificado.
</code></pre>
<h3>Segmentación y Afinidad de Servicios</h3>
<p>Si tu entorno tiene múltiples aplicaciones, la solución no es solo mover las estadísticas, sino separar las cargas de trabajo por nodos. De esta forma, el DRM deja de ser un problema para convertirse en un aliado silencioso.</p>
<p><strong>Segmentación de Aplicaciones</strong></p>
<p>Imagina que tienes la App A y la App B. Si ambas conectan a ambos nodos, el Interconnect será un caos de tráfico cruzado.</p>
<pre><code class="language-plaintext">Nodo 1: Configurado como PREFERRED para el servicio de la App A.

Nodo 2: Configurado como PREFERRED para el servicio de la App B.
</code></pre>
<p><strong>El lugar de las Estadísticas en un entorno segmentado</strong></p>
<p>En este escenario, tienes dos opciones inteligentes para el mantenimiento:</p>
<ul>
<li><p>Alineación Total: Lanzas las estadísticas de las tablas de la App A en el Nodo 1, y las de la App B en el Nodo 2 Resultado: El DRM no se inmuta. Los bloques ya están donde deben estar. Es la opción más eficiente en red.</p>
</li>
<li><p>Nodo de Mantenimiento Dedicado: Si prefieres no cargar los nodos donde están las Apps, puedes usar un tercer nodo (si lo tienes) o un nodo con menos carga. Pero aquí debes usar el tuning de parámetros (_gc_affinity_ratio) para que el DRM no intente "robar" la maestría de las tablas hacia el nodo de mantenimiento.</p>
</li>
</ul>
<h2>Conclusión</h2>
<p>El DRM es una herramienta de optimización, pero es ciega. No sabe qué proceso es más importante; solo ve volumen de tráfico. Si dejas que las estadísticas corran en el nodo donde menos impacto tendrá, el DRM castigará a tu aplicación para favorecer a un proceso de mantenimiento.</p>
]]></content:encoded></item><item><title><![CDATA[Cómo Crear un Recurso de Clúster en Oracle para Ejecutar SQL de forma persistente]]></title><description><![CDATA[En entornos Oracle RAC (Real Application Clusters), a veces necesitamos ejecutar procesos de monitorización o mantenimiento que estén vinculados a la base de datos, que puedan moverse entre nodos, par]]></description><link>https://selectfromdual.com/c-mo-crear-un-recurso-de-cl-ster-en-oracle-para-ejecutar-sql-de-forma-persistente</link><guid isPermaLink="true">https://selectfromdual.com/c-mo-crear-un-recurso-de-cl-ster-en-oracle-para-ejecutar-sql-de-forma-persistente</guid><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Fri, 20 Feb 2026 13:25:29 GMT</pubDate><enclosure url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/64254682d76bf2adc099aaf2/6351301e-ec91-47ab-ab11-69810be4f669.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En entornos Oracle RAC (Real Application Clusters), a veces necesitamos ejecutar procesos de monitorización o mantenimiento que estén vinculados a la base de datos, que puedan moverse entre nodos, pararse y arrancarse automáticamente. Esto se puede lograr mediante recursos de clúster gestionados por Oracle Clusterware, con scripts que interactúan con la base de datos a través de SQL.</p>
<p>En este artículo veremos un ejemplo práctico, donde implementaremos un recurso llamado monitorizacion_app que lanza un sql que se ejecuta de forma persistente en la BBDD.</p>
<h2><strong>Concepto de Recurso de Clúster</strong></h2>
<p>Un recurso de clúster en Oracle es cualquier elemento que se puede gestionar mediante Clusterware y tiene:</p>
<ul>
<li><p>Un script de acción que Clusterware ejecuta (start, stop, check, status).</p>
</li>
<li><p>Dependencias de otros recursos o servicios.</p>
</li>
<li><p>Posibilidad de moverse entre nodos (relocate).</p>
</li>
<li><p>Parámetros de reinicio automático y timeout.</p>
</li>
<li><p>Control de permisos de usuario y ejecución segura.</p>
</li>
</ul>
<p>En este ejemplo, el recurso ejecutará un script que lanza un procedimiento PL/SQL en la base de datos de forma persistente. La finalidad es que va informando una tabla con datos de consultas muy recurrentes de monitorización (métricas de negocio, sesiones conectadas, etc ) a modo de caché (evitamos llamadas recurrentes mismos usuarios pidiendo la misma información) y guardando un histórico en el tiempo para análisis de tendencias.</p>
<h2><strong>Preparación del Script de Monitorización</strong></h2>
<p>Nuestro script /scripts/monitorizacion_resource.sh tiene las siguientes características:</p>
<p>Ejecutado por el usuario grid, pero con permisos sudo para ejecutar como oracle.</p>
<ul>
<li><p>Controla la inicio, detención, check y status del monitor.</p>
</li>
<li><p>Interactúa con la base de datos a través de SQLPlus.</p>
</li>
<li><p>Guarda logs separados por nodo (monitor_app_.log).</p>
</li>
<li><p>Puede matar sesiones activas si el recurso se detiene.</p>
</li>
</ul>
<p>Ejemplo de invocación manual:</p>
<pre><code class="language-plaintext">/scripts/monitorizacion_resource.sh start
/scripts/monitorizacion_resource.sh stop
/scripts/monitorizacion_resource.sh status
/scripts/monitorizacion_resource.sh check
</code></pre>
<h2>Crear el Servicio Oracle</h2>
<p>Antes de crear el recurso de clúster, crearemos un servicio asociado a la base de datos, que será la dependencia de nuestro monitor</p>
<pre><code class="language-plaintext">srvctl add service -db BASEDEDATOS -service MONITORIZACION \
    -role primary -preferred node1 -available node2 \
    -pdb PDB_APP -stopoption IMMEDIATE

srvctl start service -db BASEDEDATOS -service MONITORIZACION
</code></pre>
<p>Este servicio será usado por el monitor para conectarse a la base de datos.</p>
<h2>Crear el Recurso de Clúster</h2>
<p>Lógica Principal: El script redirige errores al log y luego gestiona la acción solicitada:</p>
<p><strong>Start</strong></p>
<ul>
<li><p>Limpia el log.</p>
</li>
<li><p>Verifica si el monitor ya corre en BD.</p>
</li>
<li><p>Lanza el procedimiento PL/SQL USUARIO_MONITORIZACION.pkg_monitor.monitor_main_loop en background con nohup.</p>
</li>
<li><p>Espera a que la sesión aparezca en la BD, confirmando que el monitor está activo.</p>
</li>
</ul>
<p><strong>Stop</strong></p>
<ul>
<li><p>Lista todas las sesiones activas.</p>
</li>
<li><p>Mata cada sesión con kill_session_db.</p>
</li>
<li><p>Verifica que no queden sesiones residuales.</p>
</li>
</ul>
<p><strong>Check</strong></p>
<ul>
<li><p>Realiza hasta 3 intentos para comprobar que el monitor está corriendo.</p>
</li>
<li><p>Retorna 0 si está activo y 1 si no.</p>
</li>
</ul>
<p><strong>Status</strong></p>
<ul>
<li><p>Lista todas las sesiones activas.</p>
</li>
<li><p>Retorna 0 si hay sesiones y 1 si no.</p>
</li>
</ul>
<blockquote>
<p>Dentro del paquete de monitorización indentificamos el módulo para ser capaces de ubicar exactamente las sesiones mediante (ejemplo): DBMS_APPLICATION_INFO.SET_MODULE('MODULO_MONITOR', 'capture_metrics');</p>
<p>Además, para que sea persistente, el paquete ejecuta en bucle el sql<br />LOOP<br />-- Ejecuta la captura de métricas<br />.....................<br />-- Espera de X segundo<br />DBMS_SESSION.SLEEP(C_SLEEP_CAPTURE);</p>
<p>END LOOP;</p>
</blockquote>
<p>Código:</p>
<pre><code class="language-plaintext">#!/bin/bash
# Script para Clusterware monitorizacion_app
# Ejecutado por usuario grid con sudo a oracle

ORACLE_HOME="/u01/app/oracle/product/19.xx"

# Ruta de log local al nodo
HOSTNAME=$(hostname -s)
LOG_FILE="/scripts/logs/monitor_app_${HOSTNAME}.log"

touch "$LOG_FILE" 2&gt;/dev/null || true
chmod 664 "$LOG_FILE" 2&gt;/dev/null || true

# Identificadores únicos para nuestro monitor
MODULE_NAME="MODULO_MONITOR"

# Credenciales
DB_USER="USUARIO_MONITORIZACION"
DB_PASSWORD="xxxxxxxxxxx"
DB_TNS_SERVICE="MONITORIZACION"

ACTION="$1"

# --- FUNCIONES ---

# Ejecutar SQL y devolver resultado
run_sql() {
    local sql="$1"
    sudo -u oracle /bin/bash -c "
        export ORACLE_HOME='$ORACLE_HOME'
        export PATH=\"\\(ORACLE_HOME/bin:\\)PATH\"

        sqlplus -S \(DB_USER/\)DB_PASSWORD@$DB_TNS_SERVICE &lt;&lt;'EOF'
SET FEEDBACK OFF
SET HEADING OFF
SET PAGESIZE 0
SET VERIFY OFF
SET TRIMSPOOL ON
SET LINESIZE 32767
$sql
EOF
" 2&gt;/dev/null
}

# Verificar si el monitor está corriendo en BD
is_monitor_running_db() {
    local sql="
SELECT 'RUNNING:'||s.inst_id||':'||s.sid||':'||s.serial#||':'||s.process
FROM gv\$session s
WHERE s.username = UPPER('$DB_USER')
  AND s.module = '$MODULE_NAME'
  AND ROWNUM = 1
;
"

    local result
    result=\((run_sql "\)sql")

    if echo "$result" | grep -q "^RUNNING:"; then
        echo "$result" | sed 's/^RUNNING://'
        return 0
    else
        return 1
    fi
}

# Detener sesión en BD
kill_session_db() {
    local inst_id="$1"
    local sid="$2"
    local serial="$3"

    local sql="ALTER SYSTEM KILL SESSION '\(sid,\)serial,@$inst_id' IMMEDIATE;"

    run_sql "$sql" &gt;/dev/null
    return $?
}

# Obtener todas las sesiones del monitor
get_all_monitor_sessions() {
    local sql="
SELECT
    s.inst_id||':'||s.sid||':'||s.serial#||':'||s.process
FROM gv\$session s
WHERE s.username = UPPER('$DB_USER')
  AND s.module = '$MODULE_NAME'
;
"

    run_sql "$sql"
}

# --- LÓGICA PRINCIPAL ---

exec 2&gt;&gt;"$LOG_FILE"

case "$ACTION" in
start)
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Iniciando monitor..." &gt;&amp;2

    # Limpiar log solo al inicio
    echo "=== MONITOR INICIADO \((date '+%Y-%m-%d %H:%M:%S') ===" &gt; "\)LOG_FILE"

    # Verificar si ya está corriendo en BD
    if session_info=$(is_monitor_running_db); then
        IFS=':' read -r inst_id sid serial process &lt;&lt;&lt; "$session_info"
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Monitor ya está corriendo en BD:" &gt;&amp;2
        echo "  Instancia: \(inst_id, SID: \)sid, Serial#: $serial" &gt;&amp;2
        exit 0
    fi

    # Iniciar el proceso
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Ejecutando procedimiento de monitorización..." &gt;&amp;2

    # Lanzar proceso en background usando nohup
    nohup sudo -u oracle /bin/bash -c "
        export ORACLE_HOME='$ORACLE_HOME'
        export PATH=\"\\(ORACLE_HOME/bin:\\)PATH\"
        export LD_LIBRARY_PATH=\"\\(ORACLE_HOME/lib:\\)LD_LIBRARY_PATH\"

        sqlplus -S \(DB_USER/\)DB_PASSWORD@\(DB_TNS_SERVICE &lt;&lt;'SQL_EOF' &gt;&gt; \"\)LOG_FILE\" 2&gt;&amp;1
SET SERVEROUTPUT ON SIZE UNLIMITED

BEGIN
    USUARIO_MONITORIZACION.pkg_monitor.monitor_main_loop;
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('[' || TO_CHAR(SYSTIMESTAMP, 'YYYY-MM-DD HH24:MI:SS') || '] ERROR: ' || SQLERRM);
        RAISE;
END;
/
SQL_EOF
    " &gt;/dev/null 2&gt;&amp;1 &amp;

    # Esperar y verificar registro en BD
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Esperando registro en BD..." &gt;&amp;2

    for i in {1..10}; do
        if session_info=$(is_monitor_running_db); then
            IFS=':' read -r inst_id sid serial process &lt;&lt;&lt; "$session_info"
            echo "[$(date '+%Y-%m-%d %H:%M:%S')] Monitor funcionando:" &gt;&amp;2
            echo "  Instancia: \(inst_id, SID: \)sid, Serial#: $serial" &gt;&amp;2
            exit 0
        fi
        sleep 3
    done

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: No se registró en BD después de 30 segundos" &gt;&amp;2
    exit 1
    ;;

stop)
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Deteniendo monitor..." &gt;&amp;2

    # Obtener todas las sesiones
    sessions=$(get_all_monitor_sessions)

    if [ -z "$sessions" ]; then
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] No hay sesiones activas" &gt;&amp;2
        exit 0
    fi

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Sesiones encontradas:" &gt;&amp;2
    while IFS= read -r session_line; do
        [ -z "$session_line" ] &amp;&amp; continue
        IFS=':' read -r inst_id sid serial process &lt;&lt;&lt; "$session_line"
        echo "  Instancia: \(inst_id, SID: \)sid, Serial#: \(serial, Process: \)process" &gt;&amp;2
    done &lt;&lt;&lt; "$sessions"

    # Matar cada sesión 
    while IFS= read -r session_line; do
        [ -z "$session_line" ] &amp;&amp; continue
        IFS=':' read -r inst_id sid serial process &lt;&lt;&lt; "$session_line"
        echo "  Matando: Instancia \(inst_id, SID \)sid, Serial# \(serial", Process: \)process &gt;&amp;2
        kill_session_db "\(inst_id" "\)sid" "$serial" 
    done &lt;&lt;&lt; "$sessions"

    sleep 2
    if remaining_sessions=\((get_all_monitor_sessions); [ -n "\)remaining_sessions" ]; then
        echo "[$(date '+%Y-%m-%d %H:%M:%S')]  Aún hay sesiones después de KILL SESSION:" &gt;&amp;2
        while IFS= read -r line; do
            IFS=':' read -r i s se p &lt;&lt;&lt; "$line"
            echo "  Instancia: \(i, SID: \)s, Serial#: \(se, Process: \)p" &gt;&amp;2
        done &lt;&lt;&lt; "$remaining_sessions"
    else
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Todas las sesiones eliminadas" &gt;&amp;2
    fi

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Comando stop completado" &gt;&amp;2
    exit 0
    ;;

check)
    # Clusterware: exit 0 si está corriendo, 1 si no
    # Intentar varias veces con pausas cortas
    max_attempts=3
    wait_seconds=5

    for attempt in \((seq 1 \)max_attempts); do
        if session_info=$(is_monitor_running_db); then
            IFS=':' read -r inst_id sid serial process &lt;&lt;&lt; "$session_info"
            echo "[\((date '+%Y-%m-%d %H:%M:%S')] Monitor corriendo (Intento \)attempt/\(max_attempts) - Instancia: \)inst_id, SID: $sid" &gt;&amp;2
            exit 0
        fi

        if [ \(attempt -lt \)max_attempts ]; then
            echo "[\((date '+%Y-%m-%d %H:%M:%S')]  Check falló (Intento \)attempt/\(max_attempts), reintentando en \){wait_seconds}s..." &gt;&amp;2
            sleep $wait_seconds
        fi
    done

    echo "[\((date '+%Y-%m-%d %H:%M:%S')] Monitor no corriendo después de \)max_attempts intentos" &gt;&amp;2
    exit 1
    ;;

status)
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Estado del monitor:" &gt;&amp;2

    sessions=$(get_all_monitor_sessions)

    if [ -n "$sessions" ]; then
        echo "Sesiones activas:" &gt;&amp;2
        while IFS= read -r line; do
            IFS=':' read -r inst_id sid serial process &lt;&lt;&lt; "$line"
            echo "  Instancia: \(inst_id, SID: \)sid, Serial#: \(serial, Process: \)process" &gt;&amp;2
        done &lt;&lt;&lt; "$sessions"
        exit 0
    else
        echo "No hay sesiones activas" &gt;&amp;2
        exit 1
    fi
    ;;

*)
    echo "Uso: $0 {start|stop|check|status}" &gt;&amp;2
    exit 1
    ;;
esac
</code></pre>
<h2>Crear el Recurso de Clúster</h2>
<p>El recurso se crea mediante crsctl:</p>
<pre><code class="language-plaintext">crsctl add resource monitorizacion_app \
-type cluster_resource \
-attr "ACTION_SCRIPT='/scripts/monitorizacion_resource.sh', \
DESCRIPTION='Proceso persistente de monitorización', \
START_DEPENDENCIES='hard(ora.BASEDEDATOS.MONITORIZACION.svc)', \
STOP_DEPENDENCIES='hard(ora.BASEDEDATOS.MONITORIZACION.svc)', \
RESTART_ATTEMPTS=5, \
CHECK_INTERVAL=30, \
START_TIMEOUT=60, \
STOP_TIMEOUT=60, \
HOSTING_MEMBERS='node1 node2', \
PLACEMENT='restricted', \
CARDINALITY=1, \
ACTIONS='START,user:oracle STOP,user:oracle CHECK'"
</code></pre>
<h3>Explicación de los parámetros:</h3>
<table style="min-width:50px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><th><p>Parámetro</p></th><th><p>Descripción</p></th></tr><tr><td><p><code>ACTION_SCRIPT</code></p></td><td><p>Script que se ejecuta para start/stop/check/status</p></td></tr><tr><td><p><code>DESCRIPTION</code></p></td><td><p>Descripción del recurso</p></td></tr><tr><td><p><code>START_DEPENDENCIES</code></p></td><td><p>Recurso o servicio del que depende para iniciar</p></td></tr><tr><td><p><code>STOP_DEPENDENCIES</code></p></td><td><p>Recurso o servicio del que depende para parar</p></td></tr><tr><td><p><code>RESTART_ATTEMPTS</code></p></td><td><p>Número de reintentos si falla</p></td></tr><tr><td><p><code>CHECK_INTERVAL</code></p></td><td><p>Intervalo de chequeo en segundos</p></td></tr><tr><td><p><code>START_TIMEOUT</code></p></td><td><p>Tiempo máximo para iniciar</p></td></tr><tr><td><p><code>STOP_TIMEOUT</code></p></td><td><p>Tiempo máximo para parar</p></td></tr><tr><td><p><code>HOSTING_MEMBERS</code></p></td><td><p>Nodos en los que puede correr el recurso</p></td></tr><tr><td><p><code>PLACEMENT</code></p></td><td><p>Restricciones de colocación (<code>restricted</code> significa que solo se ejecuta en nodos listados)</p></td></tr><tr><td><p><code>CARDINALITY</code></p></td><td><p>Número de instancias del recurso (1 en nuestro caso)</p></td></tr><tr><td><p><code>ACTIONS</code></p></td><td><p>Qué acciones puede realizar el recurso y con qué usuario</p></td></tr></tbody></table>

<h2>Asignar Permisos de Ejecución al Recurso</h2>
<pre><code class="language-plaintext">crsctl setperm resource monitorizacion_app -u user:oracle:r-x
</code></pre>
<p>Esto permite que Clusterware ejecute el script como el usuario oracle de manera segura.</p>
<h2>Gestión del Recurso</h2>
<p>Una vez creado, puedes:</p>
<pre><code class="language-plaintext"># Iniciar
crsctl start resource monitorizacion_app

# Parar
crsctl stop resource monitorizacion_app

# Verificar estado
crsctl status resource monitorizacion_app
</code></pre>
<h2>Pruebas Reales de Dependencias y Relocalización del Recurso</h2>
<p>Una vez creado el recurso monitorizacion_app y vinculado mediante dependencia hard a un servicio de base de datos, realizamos pruebas para validar:</p>
<ul>
<li><p>Que el recurso bloquea el stop del servicio si está activo</p>
</li>
<li><p>Que las dependencias se respetan</p>
</li>
<li><p>Que el recurso puede moverse correctamente entre nodos</p>
</li>
<li><p>Que el kill de sesión se ejecuta correctamente</p>
</li>
<li><p>Que el estado final queda consistente</p>
</li>
</ul>
<p><strong>Validación de Dependencia Hard</strong></p>
<p>Intentamos parar el servicio asociado:</p>
<pre><code class="language-plaintext">srvctl stop service -d BASEDEDATOS -s monitorizacion -i BASEDEDATOS1
PRCD-1316 : fallo al parar los servicios monitorizacion para la base de datos BASEDEDATOS
PRCR-1132 : Fallo al parar los recursos mediante un filtro
CRS-2974: no se ha podido actuar en el recurso 'ora.BASEDEDATOS.MONITORIZACION.svc' en el servidor 'node1' porque sería necesario parar o reubicar el recurso 'monitorizacion_app', pero no se ha especificado el indicador de forzado apropiado


crsctl status resource monitorizacion_app
NAME=monitorizacion_app
TYPE=cluster_resource
TARGET=ONLINE
STATE=ONLINE on node1
</code></pre>
<p><strong>Relocate Manual del Recurso</strong></p>
<p>Mueve dependencias:</p>
<pre><code class="language-plaintext">crsctl relocate resource monitorizacion_app -n nodo2

Attempting to start 'ora.BASEDEDATOS.MONITORIZACION.svc' on 'nodo2'
Start of 'ora.BASEDEDATOS.MONITORIZACION.svc' succeeded
Attempting to stop 'monitorizacion_app' on 'nodo1'
Stop of 'monitorizacion_app' on 'nodo1' succeeded
Attempting to start 'monitorizacion_app' on 'nodo2'
Start of 'monitorizacion_app' on 'nodo2' succeeded

-- Secuencia de eventos
crsctl status resource monitorizacion_app
NAME=monitorizacion_app
TYPE=cluster_resource
TARGET=ONLINE
STATE=ONLINE on nodo1

crsctl status resource monitorizacion_app
NAME=monitorizacion_app
TYPE=cluster_resource
TARGET=ONLINE
STATE=OFFLINE

crsctl status resource monitorizacion_app
NAME=monitorizacion_app
TYPE=cluster_resource
TARGET=ONLINE
STATE=ONLINE on nodo2

-- Veremos en el alertlog
(3):KILL SESSION for sid=(2855, 32280):
(3):  Reason = alter system kill session
(3):  Mode = KILL HARD SAFE -/-/-
(3):  Requestor = USER (orapid = 309, ospid = 810697, inst = 1)
(3):  Owner = Process: USER (orapid = 150, ospid = 3169885)
(3):  Result = ORA-31
</code></pre>
<p><strong>Parada y arranque</strong></p>
<pre><code class="language-plaintext">crsctl stop resource monitorizacion_app

-- Veremos en el alertlog
(3):KILL SESSION for sid=(2855, 32280):
(3):  Reason = alter system kill session
(3):  Mode = KILL HARD SAFE -/-/-
(3):  Requestor = USER (orapid = 309, ospid = 810697, inst = 1)
(3):  Owner = Process: USER (orapid = 150, ospid = 3169885)
(3):  Result = ORA-31

crsctl start resource monitorizacion_app
</code></pre>
<h2>Ejemplo registro log</h2>
<pre><code class="language-plaintext">-- Ejemplo arranque
more /scripts/logs/monitor_app_nodo1.log
=== MONITOR INICIADO 2026-02-03 22:02:08 ===
[2026-02-03 22:02:08] Ejecutando procedimiento de monitorización...
[2026-02-03 22:02:08] Esperando registro en BD...
[2026-02-03 22:02:12] Monitor funcionando:
  Instancia: 1, SID: 3320, Serial#: 54019
[2026-02-03 22:02:12] Monitor corriendo (Intento 1/3) - Instancia: 1, SID: 3320
[2026-02-03 22:02:42] Monitor corriendo (Intento 1/3) - Instancia: 1, SID: 3320
[2026-02-03 22:03:12] Monitor corriendo (Intento 1/3) - Instancia: 1, SID: 3320
[2026-02-03 22:03:42] Monitor corriendo (Intento 1/3) - Instancia: 1, SID: 3320
[2026-02-03 22:04:12] Monitor corriendo (Intento 1/3) - Instancia: 1, SID: 3320
[2026-02-03 22:04:42] Monitor corriendo (Intento 1/3) - Instancia: 1, SID: 3320
[2026-02-03 22:05:12] Monitor corriendo (Intento 1/3) - Instancia: 1, SID: 3320

-- Ejemplo parada


[2025-12-10 12:51:19] Deteniendo monitor...
[2025-12-10 12:51:19] Sesiones encontradas:
  Instancia: 1, SID: 5197, Serial#: 59722, Process: 3928045
  Matando: Instancia 1, SID 5197, Serial# 59722, Process: 3928045
ERROR:
ORA-03114: not connected to ORACLE


BEGIN
*
ERROR at line 1:
ORA-00028: your session has been killed
ORA-06512: at "SYS.DBMS_SESSION", line 435
ORA-06512: at "USUARIO_MONITORIZACION.PKG_MONITOR", line 236
ORA-06512: at line 2


[2025-12-10 12:51:21] Todas las sesiones eliminadas
[2025-12-10 12:51:21] Comando stop completado
[2025-12-10 12:51:21]  Check falló (Intento 1/3), reintentando en 5s...
[2025-12-10 12:51:26]  Check falló (Intento 2/3), reintentando en 5s...
[2025-12-10 12:51:31] Monitor no corriendo después de 3 intentos
</code></pre>
<h2>Ventajas de Esta Configuración</h2>
<ul>
<li><p>Persistencia: El monitor se asegura de que siempre haya una sesión activa.</p>
</li>
<li><p>Alta disponibilidad: Puede moverse a otro nodo si falla el nodo actual.</p>
</li>
<li><p>Integración con servicios: Solo arranca si el servicio de base de datos está disponible.</p>
</li>
<li><p>Control total: Inicio/parada manual, chequeo de estado, reinicio automático.</p>
</li>
</ul>
<h2>Conclusión</h2>
<p>En nuestro caso necesitábamos que el proceso sobreviviese a relocalizaciones del servicio y que no dependiese de un cron externo. Después de probar varias alternativas ( job scheduler, etc.), la opción de recurso gestionado por Clusterware resultó ser la más limpia operativamente.  </p>
<p>Permite:</p>
<ul>
<li><p>Ejecutar scripts críticos vinculados a servicios.</p>
</li>
<li><p>Gestionar fallos y reinicios automáticos.</p>
</li>
<li><p>Mantener alta disponibilidad y control operativo.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Fragmentación por splits en índices B-tree: análisis práctico en Oracle 19c]]></title><description><![CDATA[Analizando un índice B-tree en una tabla que recibe únicamente INSERTs encuentro que la volumetría de éste es similar a la volumetría de la tabla.
select sum(bytes/1024/1024/1024) Gb from dba_segments where segment_name='TABLA';

        GB
---------...]]></description><link>https://selectfromdual.com/fragmentacion-por-splits-en-indices-b-tree-analisis-practico-en-oracle-19c</link><guid isPermaLink="true">https://selectfromdual.com/fragmentacion-por-splits-en-indices-b-tree-analisis-practico-en-oracle-19c</guid><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Wed, 18 Feb 2026 09:06:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/UK78i6vK3sc/upload/686ff5de3f82dddc0b2dc0b0e5f9a60f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Analizando un índice B-tree en una tabla que recibe únicamente INSERTs encuentro que la volumetría de éste es similar a la volumetría de la tabla.</p>
<pre><code class="lang-plaintext">select sum(bytes/1024/1024/1024) Gb from dba_segments where segment_name='TABLA';

        GB
----------
94,0537109


select sum(bytes/1024/1024/1024) Gb from dba_segments where segment_name='INDICE_TABLA'

        GB
----------
95,1396484
</code></pre>
<p>La tabla está particionada y tiene volumetrías similares en cada partición.</p>
<p>En entorno replicado comprobamos si recuperamos el espacio después de un rebuild:</p>
<pre><code class="lang-plaintext">SET SERVEROUTPUT ON
DECLARE
    v_sql VARCHAR2(1000);
BEGIN
    FOR rec IN (
        SELECT index_name, partition_name
        FROM dba_ind_partitions
        WHERE index_name = 'INDICE_TABLA'
          AND index_owner = 'PROPIETARIO'
        ORDER BY partition_name
    ) LOOP
        v_sql := 'ALTER INDEX PROPIETARIO.' || rec.index_name || 
                 ' REBUILD PARTITION ' || rec.partition_name || 
                 ' ONLINE';
        DBMS_OUTPUT.PUT_LINE('Ejecutando: ' || v_sql);
        EXECUTE IMMEDIATE v_sql;
    END LOOP;
END;
/

Ejecutando: ALTER INDEX PROPIETARIO.INDICE_TABLA REBUILD PARTITION SYS_P15950 ONLINE
Ejecutando: ALTER INDEX PROPIETARIO.INDICE_TABLA REBUILD PARTITION SYS_P16003 ONLINE
Ejecutando: ALTER INDEX PROPIETARIO.INDICE_TABLA REBUILD PARTITION SYS_P16051 ONLINE
Ejecutando: ALTER INDEX PROPIETARIO.INDICE_TABLA REBUILD PARTITION SYS_P16091 ONLINE
Ejecutando: ALTER INDEX PROPIETARIO.INDICE_TABLA REBUILD PARTITION SYS_P16160 ONLINE
Ejecutando: ALTER INDEX PROPIETARIO.INDICE_TABLA REBUILD PARTITION SYS_P16173 ONLINE


PL/SQL procedure successfully completed.
Elapsed: 00:52:08.117

select sum(bytes/1024/1024/1024) Gb from dba_segments where segment_name='INDICE_TABLA'

        GB
----------
57,2919922
</code></pre>
<p>Vemos que recuperamos ~38 GB. <strong>Investigo para ponerle cara</strong>.</p>
<h2 id="heading-1-indice-y-estructura-de-la-tabla">1. Índice y estructura de la tabla</h2>
<p>Tabla con tipos de datos</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Columna</td><td>Tipo</td></tr>
</thead>
<tbody>
<tr>
<td>ID</td><td>NUMBER</td></tr>
<tr>
<td>ITEM</td><td>NUMBER</td></tr>
<tr>
<td>EVENT_DATE</td><td>DATE</td></tr>
<tr>
<td>TIMESTAMP</td><td>DATE</td></tr>
<tr>
<td>ATRIBUTO1</td><td>NUMBER</td></tr>
<tr>
<td>ATRIBUTO2</td><td>NUMBER</td></tr>
</tbody>
</table>
</div><p>Índice B-tree particionado denominado INDICE_TABLA sobre (ITEM, EVENT_DATE).</p>
<p>En primera instancia compruebo si hay bloques no ocupados. Nos vale con una partición, todas son similares.</p>
<pre><code class="lang-plaintext">SET SERVEROUTPUT ON
DECLARE
    v_total_blocks      NUMBER;
    v_total_bytes       NUMBER;
    v_unused_blocks     NUMBER;
    v_unused_bytes      NUMBER;
    v_last_file_id      NUMBER;
    v_last_extent_block NUMBER;
    v_last_used_block   NUMBER;
BEGIN
    DBMS_SPACE.UNUSED_SPACE(
        segment_owner             =&gt; 'PROPIETARIO',
        segment_name              =&gt; 'INDICE_TABLA',
        segment_type              =&gt; 'INDEX PARTITION',
        total_blocks              =&gt; v_total_blocks,
        total_bytes               =&gt; v_total_bytes,
        unused_blocks             =&gt; v_unused_blocks,
        unused_bytes              =&gt; v_unused_bytes,
        last_used_extent_file_id  =&gt; v_last_file_id,
        last_used_extent_block_id =&gt; v_last_extent_block,
        last_used_block           =&gt; v_last_used_block,
        partition_name            =&gt; 'SYS_P16051'
    );

    DBMS_OUTPUT.PUT_LINE('Partición SYS_P16003:');
    DBMS_OUTPUT.PUT_LINE('  Total Blocks : ' || v_total_blocks);
    DBMS_OUTPUT.PUT_LINE('  Unused Blocks: ' || v_unused_blocks);
    DBMS_OUTPUT.PUT_LINE('  Unused Bytes : ' || ROUND(v_unused_bytes/1024/1024,2) || ' MB');
END;
/

Partición SYS_P16003:
  Total Blocks : 2247296
  Unused Blocks: 0
  Unused Bytes : 0 MB
</code></pre>
<h2 id="heading-observaciones-clave">Observaciones clave</h2>
<ol>
<li><p><strong>No hay espacio no utilizado:</strong></p>
<ul>
<li><p>DBMS_SPACE.UNUSED_SPACE devuelve 0 unused</p>
</li>
<li><p>La fragmentación <strong>no es PCTFREE ni filas borradas</strong>.</p>
</li>
</ul>
</li>
<li><p><strong>Splits de leaf nodes no se monitorizan directamente en Oracle:</strong></p>
<ul>
<li><p>No hay vista que liste “splits ocurridos”.</p>
</li>
<li><p>Solo se puede estimar a partir de diferencias entre <strong>bloques reales vs bloques teóricos</strong>.</p>
</li>
</ul>
</li>
<li><p><strong>Rebuild recupera el espacio:</strong></p>
<ul>
<li><p>Compacta los leaf nodes y reduce los 50/50 y 99/1 parcialmente llenos.</p>
</li>
<li><p>Reducción observada en este ejemplo: ~38 GB.</p>
</li>
</ul>
</li>
</ol>
<h1 id="heading-investigamos-splits">Investigamos splits</h1>
<h2 id="heading-tipos-de-split-en-oracle-19c">Tipos de split en Oracle 19c</h2>
<p>Antes de nada explico los distintos tipos de split en Oracle.</p>
<h3 id="heading-5050-split">50/50 Split</h3>
<ul>
<li><p>La nueva clave no es la mayor del bloque → bloque viejo y bloque nuevo ~50 % llenos.</p>
</li>
<li><p>Ocurre cuando varias filas tienen el mismo ITEM y se insertan fechas intermedias (EVENT_DATE).</p>
</li>
</ul>
<pre><code class="lang-plaintext">Bloque inicial (capacidad 5):

I1-2026-02-10
I1-2026-02-11
I2-2026-02-09
I2-2026-02-10
I3-2026-02-08

Insertamos I2-2026-02-09.5 → split 50/50

Bloque viejo        Bloque nuevo
I1-2026-02-10      I2-2026-02-09.5
I1-2026-02-11      I2-2026-02-10
I2-2026-02-09      I3-2026-02-08
</code></pre>
<h3 id="heading-991-split">99/1 Split</h3>
<ul>
<li><p>La nueva clave <strong>es la mayor</strong> → bloque viejo ~99 % lleno, bloque nuevo casi vacío.</p>
</li>
<li><p>Minimiza desperdicio y ocurre con fechas crecientes.</p>
</li>
</ul>
<pre><code class="lang-plaintext">Bloque viejo inicial (ITEM I1):

I1-2026-02-10
I1-2026-02-11

Insertamos I1-2026-02-12 → split 99/1

Bloque viejo        Bloque nuevo
I1-2026-02-10      I1-2026-02-12
I1-2026-02-11
</code></pre>
<h2 id="heading-conceptos-clave-de-ocupacion-de-bloques">Conceptos clave de ocupación de bloques</h2>
<ul>
<li><p><strong>ROWID:</strong> 6 bytes por fila, puntero físico.</p>
</li>
<li><p><strong>Overhead de entrada:</strong> ~3 bytes (flags, punteros, longitud de clave).</p>
</li>
<li><p><strong>Tamaño promedio de entrada de índice:</strong> <code>avg_index_entry_size = 20 bytes</code></p>
</li>
<li><p><strong>Ajuste para ramas internas:</strong> +10 % → <code>22 bytes/fila</code></p>
</li>
<li><p><strong>Factor de ocupación máximo post-rebuild:</strong> 95 %</p>
</li>
</ul>
<p>Para el tamaño promedio de entrada de índice calculo cúanto debería ocupar cada entrada:</p>
<pre><code class="lang-plaintext">SELECT ROUND(AVG(vsize(ITEM) + vsize(EVENT_DATE) + 6 + 3)) AS avg_index_entry_size
FROM PROPIETARIO.TABLA partition(SYS_P16160);

avg_index_entry_size
--------------------
20
</code></pre>
<p>Y con ésta fórmula calcularíamos los bloques teóricos:</p>
<pre><code class="lang-plaintext">blocks_teoricos = num_rows * avg_index_entry_size / (db_block_size * block_occupancy_factor)
</code></pre>
<p>Comparando con bloques reales obtenemos el espacio extra generado por splits.</p>
<h2 id="heading-analisis-de-mb-recuperables">Análisis de MB recuperables</h2>
<p>Tener en cuenta que es una tabla donde se inserta constantemente, los valores son superiores a la primera estimación de espacio puesto que se ha realizado posteriormente (pasamos de 95Gb de volumetría de tabla a 109Gb aprox)</p>
<pre><code class="lang-plaintext">-- ===============================================
-- Script para estimación de MB recuperables
-- ===============================================

-- Variables
VAR block_size NUMBER
VAR avg_index_entry_size NUMBER
VAR block_occupancy_factor NUMBER

EXEC :block_size := 8192;               -- tamaño del bloque en bytes
EXEC :avg_index_entry_size := 22;       -- bytes por fila (clave + ROWID + overhead de rama)
EXEC :block_occupancy_factor := 0.95;   -- factor de ocupación realista post-rebuild

-- Formato de salida
COLUMN partition_name FORMAT A15
COLUMN num_rows FORMAT 999,999,999
COLUMN blocks FORMAT 999,999,999
COLUMN blocks_teoricos FORMAT 999,999,999
COLUMN diff_blocks FORMAT 999,999,999
COLUMN size_mb FORMAT 999,999.99
COLUMN est_mb_recover FORMAT 999,999.99
COLUMN occupancy_pct FORMAT 999.99

-- Fragmentación por partición ajustada
SELECT 
    i.partition_name,
    i.num_rows,
    s.blocks,
    ROUND(i.num_rows * :avg_index_entry_size / (:block_size * :block_occupancy_factor)) AS blocks_teoricos,
    s.blocks - ROUND(i.num_rows * :avg_index_entry_size / (:block_size * :block_occupancy_factor)) AS diff_blocks,
    ROUND(s.bytes/1024/1024,2) AS size_mb,
    ROUND((s.blocks - ROUND(i.num_rows * :avg_index_entry_size / (:block_size * :block_occupancy_factor))) 
          * :block_size / 1024/1024,2) AS est_mb_recover,
    ROUND((i.num_rows * :avg_index_entry_size) / s.bytes * 100,2) AS occupancy_pct
FROM dba_ind_partitions i
JOIN dba_segments s
  ON i.index_name = s.segment_name
 AND i.partition_name = s.partition_name
 AND i.index_owner = s.owner
WHERE i.index_owner = 'PROPIETARIO'
  AND i.index_name = 'INDICE_TABLA'
ORDER BY i.partition_position;

-- Total del índice ajustado
SELECT 
    ROUND(SUM(s.bytes)/1024/1024,2) AS total_size_mb,
    ROUND(SUM(i.num_rows * :avg_index_entry_size)/1024/1024,2) AS total_used_mb,
    ROUND(SUM((s.blocks - ROUND(i.num_rows * :avg_index_entry_size / (:block_size * :block_occupancy_factor))) 
              * :block_size)/1024/1024,2) AS total_est_mb_recover,
    ROUND(SUM(i.num_rows * :avg_index_entry_size)/SUM(s.bytes)*100,2) AS total_occupancy_pct
FROM dba_ind_partitions i
JOIN dba_segments s
  ON i.index_name = s.segment_name
 AND i.partition_name = s.partition_name
 AND i.index_owner = s.owner
WHERE i.index_owner = 'PROPIETARIO'
  AND i.index_name = 'INDICE_TABLA';

PARTITION_NAME      NUM_ROWS       BLOCKS BLOCKS_TEORICOS  DIFF_BLOCKS     SIZE_MB EST_MB_RECOVER OCCUPANCY_PCT
--------------- ------------ ------------ --------------- ------------ ----------- -------------- -------------
SYS_P15950       435,119,799    2,328,320       1,230,036    1,098,284   18,190.00       8,580.34         50.19
SYS_P16003       418,392,746    2,390,144       1,182,751    1,207,393   18,673.00       9,432.76         47.01
SYS_P16051       409,281,521    2,247,296       1,156,994    1,090,302   17,557.00       8,517.98         48.91
SYS_P16091       414,177,258    2,487,936       1,170,834    1,317,102   19,437.00      10,289.86         44.71
SYS_P16160       435,751,783    2,511,744       1,231,823    1,279,921   19,623.00       9,999.38         46.59
SYS_P16173       330,935,423    2,032,512         935,519    1,096,993   15,879.00       8,570.26         43.73



TOTAL_SIZE_MB TOTAL_USED_MB TOTAL_EST_MB_RECOVER TOTAL_OCCUPANCY_PCT
------------- ------------- -------------------- -------------------
       109359         51270             55390,59               46,88
</code></pre>
<p>Teniendo en consideración los conceptos clave detallados anteriormente:</p>
<p><strong>Resultados resumidos por partición</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Partición</td><td>Filas</td><td>Bloques Reales</td><td>Bloques Teóricos</td><td>Diff Blocks</td><td>Tamaño Actual (MB)</td><td>Tamaño Recuperable (MB)</td><td>% Ocupación</td></tr>
</thead>
<tbody>
<tr>
<td>P1</td><td>435,119,799</td><td>2,328,320</td><td>1,230,036</td><td>1,098,284</td><td>18,190</td><td>8,580</td><td>50.2</td></tr>
<tr>
<td>P2</td><td>418,392,746</td><td>2,390,144</td><td>1,182,751</td><td>1,207,393</td><td>18,673</td><td>9,433</td><td>47.0</td></tr>
<tr>
<td>P3</td><td>409,281,521</td><td>2,247,296</td><td>1,156,994</td><td>1,090,302</td><td>17,557</td><td>8,518</td><td>48.9</td></tr>
<tr>
<td>P4</td><td>414,177,258</td><td>2,487,936</td><td>1,170,834</td><td>1,317,102</td><td>19,437</td><td>10,290</td><td>44.7</td></tr>
<tr>
<td>P5</td><td>435,751,783</td><td>2,511,744</td><td>1,231,823</td><td>1,279,921</td><td>19,623</td><td>9,999</td><td>46.6</td></tr>
<tr>
<td>P6</td><td>330,935,423</td><td>2,032,512</td><td>935,519</td><td>1,096,993</td><td>15,879</td><td>8,570</td><td>43.7</td></tr>
</tbody>
</table>
</div><p><strong>Totales:</strong></p>
<ul>
<li><p>Tamaño actual: ~109 GB</p>
</li>
<li><p>Tamaño usado: ~51 GB</p>
</li>
<li><p>Tamaño recuperable: ~55 GB</p>
</li>
<li><p>Ocupación global: 46 %</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusión</h2>
<p>Tras analizar el comportamiento del índice B-tree sobre (ITEM NUMBER, EVENT_DATE DATE), podemos relacionar todos los elementos observados y <strong>entender claramente el origen de la fragmentación</strong>.</p>
<p>En primer lugar, descartamos causas clásicas:</p>
<ul>
<li><p>No existen DELETEs ni actualizaciones que reduzcan cardinalidad.</p>
</li>
<li><p>DBMS_SPACE.UNUSED_SPACE devuelve 0 bloques sin uso por lo que no hay espacio sin formatear dentro del segmento.</p>
</li>
<li><p>El PCTFREE no explica una ocupación cercana al 46 %, ya que incluso tras un rebuild Oracle puede llenar los leaf nodes hoja hasta ~90–95 %.</p>
</li>
</ul>
<p>Por tanto, la diferencia entre:</p>
<ul>
<li><p>~109 GB de tamaño real</p>
</li>
<li><p>~51 GB de tamaño teórico necesario</p>
</li>
<li><p>~55 GB recuperables tras rebuild</p>
</li>
</ul>
<p>no puede atribuirse a espacio no utilizado, sino a leaf nodes parcialmente ocupados generados por splits.</p>
<p>El análisis del patrón de inserción explica el fenómeno:</p>
<ul>
<li><p>ITEM (NUMBER) no es consecutivo.</p>
</li>
<li><p>EVENT_DATE (DATE) es creciente dentro de cada ITEM.</p>
</li>
<li><p>El índice está ordenado por (ITEM, EVENT_DATE).</p>
</li>
</ul>
<p>Esto provoca una combinación de comportamientos:</p>
<ul>
<li><p>Cuando se insertan claves intermedias dentro del rango de un mismo ITEM, se producen splits 50/50, dejando dos bloques aproximadamente al 50 % de ocupación.</p>
</li>
<li><p>Cuando la nueva clave es la mayor dentro del bloque (fecha creciente al final), se producen splits 99/1, que también introducen bloques parcialmente vacíos.</p>
</li>
</ul>
<p>A gran escala (cientos de millones de filas por partición), la acumulación de estos splits explica matemáticamente la ocupación observada (~44–50 %).</p>
<p>Además, Oracle no proporciona una vista que permita monitorizar directamente cuántos splits han ocurrido. El único modo práctico de detectarlos es indirectamente, comparando:</p>
<ul>
<li><p>Bloques reales del segmento</p>
</li>
<li><p>Bloques teóricos necesarios según el tamaño medio de entrada</p>
</li>
</ul>
<p>Cuando la desviación es estructural, homogénea entre particiones y coherente con el patrón de inserción, la explicación más sólida es el comportamiento interno del B-tree ante splits de leaf nodes.</p>
<p><strong>En definitiva:</strong></p>
<p>La fragmentación observada no es un síntoma de corrupción ni de mala configuración, sino una <strong>consecuencia natural del algoritmo de mantenimiento del B-tree bajo un patrón específico de inserciones</strong>.</p>
<p>El rebuild no “arregla un problema lógico”, sino que simplemente recompone los leaf nodes eliminando el espacio generado por años de splits acumulados.</p>
]]></content:encoded></item><item><title><![CDATA[Clonado de endpoints en ORDS para versionado de APIs (cuando la consola no es suficiente)]]></title><description><![CDATA[Uno de los escenarios más habituales al trabajar con Oracle REST Data Services (ORDS) es el versionado de APIs.
Antes de entender qué hace el script de clonado, es importante comprender cómo modela ORDS una API internamente.
ORDS no trabaja con “endp...]]></description><link>https://selectfromdual.com/clonado-de-endpoints-en-ords-para-versionado-de-apis-cuando-la-consola-no-es-suficiente</link><guid isPermaLink="true">https://selectfromdual.com/clonado-de-endpoints-en-ords-para-versionado-de-apis-cuando-la-consola-no-es-suficiente</guid><category><![CDATA[Oracle]]></category><category><![CDATA[ords]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Mon, 26 Jan 2026 16:40:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/y7GlIdTUOvo/upload/922a5792157ffda8b52942d8cb5676bd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Uno de los escenarios más habituales al trabajar con Oracle REST Data Services (ORDS) es el versionado de APIs.</p>
<p>Antes de entender qué hace el script de clonado, es importante comprender <strong>cómo modela ORDS una API internamente</strong>.</p>
<p>ORDS no trabaja con “endpoints sueltos”, sino con una <strong>jerarquía bien definida</strong>.</p>
<p>El diagrama resume esta estructura:</p>
<pre><code class="lang-plaintext">Módulo
 └── Template (ruta)
      └── Handler (método HTTP)
           └── Parámetros
</code></pre>
<p>Por ejemplo:</p>
<pre><code class="lang-plaintext">/JsonConsulta/v1
/JsonConsulta/v2
</code></pre>
<p>El nombre del API y del recurso se mantiene, y lo único que queremos cambiar es la versión (v1, v2, etc.) en nuestro caso.</p>
<p>El problema es que <strong>ORDS no permite clonar un endpoint desde la consola web.</strong></p>
<p>Cuando un endpoint tiene:</p>
<ul>
<li><p>SQL complejo</p>
</li>
<li><p>Múltiples parámetros</p>
</li>
<li><p>Lógica probada en producción</p>
</li>
</ul>
<p>Copiarlo a mano para una nueva versión es lento y propenso a errores.</p>
<p>Para resolver esto, se puede clonar directamente el endpoint desde el diccionario de ORDS, usando PL/SQL.</p>
<h2 id="heading-objetivo-del-script">Objetivo del script</h2>
<p>Este procedimiento:</p>
<ul>
<li><p>Clona un endpoint ORDS existente (<code>v1</code>) a una nueva versión (<code>v2</code>) o simplemente copiarlo con un nombre nuevo.</p>
</li>
<li><p>Mantiene <strong>el mismo módulo</strong></p>
</li>
<li><p>Copia <strong>handlers, SQL y parámetros</strong></p>
</li>
<li><p>Evita duplicados (puede ejecutarse más de una vez sin romper nada)</p>
</li>
</ul>
<p>Está pensado especialmente para <strong>entornos</strong> <strong>donde se quiere evolucionar una API sin tocar la versión anterior.</strong></p>
<h2 id="heading-script-completo-de-clonado-de-endpoint-ords">Script completo de clonado de endpoint ORDS</h2>
<p>Para tener el proceso controlado conectamos con el propietario del esquema ORDS.</p>
<p>Variables principales</p>
<pre><code class="lang-plaintext">v_module_name := 'nombremodulo'; 
v_template_src := 'nombretemplateorigen/vx';
v_template_dst := 'nombretemplatedestino/vx';
</code></pre>
<ul>
<li><p>Módulo ORDS donde existe el API</p>
</li>
<li><p>Template origen (endpoint actual)</p>
</li>
<li><p>Template destino (nueva versión)</p>
</li>
</ul>
<pre><code class="lang-plaintext">SET SERVEROUTPUT ON

DECLARE
    v_module_id       NUMBER;
    v_template_exists NUMBER;
    v_handler_exists  NUMBER;
    v_param_exists    NUMBER;

    -- Nombre lógico del módulo
    v_module_name     VARCHAR2(100) := 'nombremodulo';

    -- Template origen y template destino (clonado)
    v_template_src    VARCHAR2(100) := 'JsonConsulta/v1';
    v_template_dst    VARCHAR2(100) := 'JsonConsulta/v2';

BEGIN
    -- Obtener el ID del módulo ORDS
    SELECT id
    INTO v_module_id
    FROM user_ords_modules
    WHERE name = v_module_name;

    -- Crear el template destino si no existe
    SELECT COUNT(*)
    INTO v_template_exists
    FROM user_ords_templates
    WHERE module_id = v_module_id
      AND uri_template = v_template_dst;

    IF v_template_exists = 0 THEN
        ORDS.DEFINE_TEMPLATE(
            p_module_name =&gt; v_module_name,
            p_pattern     =&gt; v_template_dst,
            p_priority    =&gt; 0,
            p_etag_type   =&gt; 'HASH',
            p_comments    =&gt; 'Clonado desde ' || v_template_src
        );
        DBMS_OUTPUT.PUT_LINE('Template ' || v_template_dst || ' creado.');
    ELSE
        DBMS_OUTPUT.PUT_LINE('Template ' || v_template_dst || ' ya existe.');
    END IF;

    -- Clonar todos los handlers (GET, POST, etc.)
    FOR src_handler IN (
        SELECT h.method,
               h.source_type,
               h.source,
               h.items_per_page,
               h.mimes_allowed,
               h.comments
        FROM user_ords_handlers h
        JOIN user_ords_templates t ON h.template_id = t.id
        WHERE t.module_id = v_module_id
          AND t.uri_template = v_template_src
        ORDER BY h.id
    ) LOOP
        SELECT COUNT(*)
        INTO v_handler_exists
        FROM user_ords_handlers h
        JOIN user_ords_templates t ON h.template_id = t.id
        WHERE t.module_id = v_module_id
          AND t.uri_template = v_template_dst
          AND h.method = src_handler.method;

        IF v_handler_exists = 0 THEN
            ORDS.DEFINE_HANDLER(
                p_module_name    =&gt; v_module_name,
                p_pattern        =&gt; v_template_dst,
                p_method         =&gt; src_handler.method,
                p_source_type    =&gt; src_handler.source_type,
                p_items_per_page =&gt; src_handler.items_per_page,
                p_mimes_allowed  =&gt; src_handler.mimes_allowed,
                p_comments       =&gt; 'Clonado desde ' || v_template_src,
                p_source         =&gt; src_handler.source
            );
            DBMS_OUTPUT.PUT_LINE(
                'Handler ' || src_handler.method || ' creado.'
            );
        ELSE
            DBMS_OUTPUT.PUT_LINE(
                'Handler ' || src_handler.method || ' ya existe.'
            );
        END IF;
    END LOOP;

    -- Clonar los parámetros de cada handler
    FOR src_param IN (
        SELECT p.name,
               p.bind_variable_name,
               p.source_type,
               p.param_type,
               p.access_method,
               p.comments,
               h.method
        FROM user_ords_parameters p
        JOIN user_ords_handlers h ON p.handler_id = h.id
        JOIN user_ords_templates t ON h.template_id = t.id
        WHERE t.module_id = v_module_id
          AND t.uri_template = v_template_src
        ORDER BY h.method, p.name
    ) LOOP
        SELECT COUNT(*)
        INTO v_param_exists
        FROM user_ords_parameters p
        JOIN user_ords_handlers h ON p.handler_id = h.id
        JOIN user_ords_templates t ON h.template_id = t.id
        WHERE t.module_id = v_module_id
          AND t.uri_template = v_template_dst
          AND p.name = src_param.name
          AND h.method = src_param.method;

        IF v_param_exists = 0 THEN
            ORDS.DEFINE_PARAMETER(
                p_module_name        =&gt; v_module_name,
                p_pattern            =&gt; v_template_dst,
                p_method             =&gt; src_param.method,
                p_name               =&gt; src_param.name,
                p_bind_variable_name =&gt; src_param.bind_variable_name,
                p_source_type        =&gt; src_param.source_type,
                p_param_type         =&gt; src_param.param_type,
                p_access_method      =&gt; src_param.access_method,
                p_comments           =&gt; src_param.comments
            );
            DBMS_OUTPUT.PUT_LINE(
                'Parámetro ' || src_param.name ||
                ' (' || src_param.method || ') creado.'
            );
        ELSE
            DBMS_OUTPUT.PUT_LINE(
                'Parámetro ' || src_param.name ||
                ' (' || src_param.method || ') ya existe.'
            );
        END IF;
    END LOOP;

    COMMIT;
    DBMS_OUTPUT.PUT_LINE('Clonación del endpoint completada.');

END;
/
</code></pre>
<p>Ejemplo salida:</p>
<pre><code class="lang-plaintext">Template JsonConsulta/v2 creado. 
Handler JsonConsulta/v2 [GET] creado. 
Parametro parm1 para método GET creado. 
Parametro parm2 para método GET creado. 
Parametro parm3 para método GET creado. 
Parametro parm4 para método GET creado. 
Parametro parm5 para método GET creado.
Parametro parm6 para método GET creado. 
Parametro parm7 para método GET creado. 
Parametro parm8 para método GET creado. 
Parametro parm9 para método GET creado. 
Parametro parm10 para método GET creado. 
Parametro parm11 para método GET creado. 
Clonación completada exitosamente. 
PL/SQL procedure successfully completed.
</code></pre>
<h2 id="heading-que-hace-realmente-este-script">¿Qué hace realmente este script?</h2>
<p>A alto nivel, el procedimiento <strong>reproduce internamente lo que haríamos a mano en ORDS</strong>, pero de forma segura:</p>
<ul>
<li><p>Usa las vistas <code>USER_ORDS_*</code> para <strong>leer la definición actual del API</strong></p>
</li>
<li><p>Replica exactamente:</p>
<ul>
<li><p>La ruta del endpoint (solo cambia la versión/nombre)</p>
</li>
<li><p>Los métodos HTTP</p>
</li>
<li><p>El SQL asociado</p>
</li>
<li><p>Todos los parámetros y binds</p>
</li>
</ul>
</li>
<li><p>Comprueba siempre si algo ya existe antes de crearlo</p>
</li>
</ul>
<p>Esto permite ejecutar el script varias veces sin riesgo y mantener un <strong>versionado limpio y controlado</strong>.</p>
<h2 id="heading-por-que-hacerlo-asi-y-no-a-mano">¿Por qué hacerlo así y no a mano?</h2>
<p>✔ Evita errores copiando SQL<br />✔ Permite versionado limpio de APIs<br />✔ Es repetible y controlado<br />✔ Suple una limitación real de la consola ORDS</p>
<p>Especialmente útil en:</p>
<ul>
<li><p>Entornos de <strong>pruebas</strong></p>
</li>
<li><p>Versionado de APIs</p>
</li>
<li><p>Cambios controlados antes de producción</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusión</h2>
<p>Aunque ORDS es una herramienta potente, <strong>no todo se puede hacer desde la consola web</strong>.</p>
<p>Conociendo su diccionario interno y las APIs PL/SQL (<code>ORDS.DEFINE_*</code>), es posible automatizar tareas avanzadas como el clonado completo de endpoints.</p>
<p>Este procedimiento convierte una tarea manual y propensa a errores en un <strong>proceso seguro y limpio</strong>.</p>
<p>Y como refrexión personal, poner botón para clonar un endpoint desde la consola web por lo que vemos no debería ser tan complejo implementarlo.</p>
]]></content:encoded></item><item><title><![CDATA[Tuning Oracle AHF: Resolviendo picos de CPU causados por "Rediscovery"]]></title><description><![CDATA[En la administración de entornos Oracle, el Oracle Autonomous Health Framework (AHF) es una pieza fundamental para el diagnóstico proactivo. Sin embargo, como cualquier herramienta robusta, sus procesos internos pueden impactar en el rendimiento si n...]]></description><link>https://selectfromdual.com/tuning-oracle-ahf-resolviendo-picos-de-cpu-causados-por-rediscovery</link><guid isPermaLink="true">https://selectfromdual.com/tuning-oracle-ahf-resolviendo-picos-de-cpu-causados-por-rediscovery</guid><category><![CDATA[Oracle]]></category><category><![CDATA[ahf]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Wed, 21 Jan 2026 15:42:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Nv-vx3kUR2A/upload/1f12ba967cace08eeb99c7bb94380cf1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En la administración de entornos Oracle, el <strong>Oracle Autonomous Health Framework (AHF)</strong> es una pieza fundamental para el diagnóstico proactivo. Sin embargo, como cualquier herramienta robusta, sus procesos internos pueden impactar en el rendimiento si no están bien ajustados.</p>
<p>Recientemente, investigamos un caso donde el sistema presentaba picos de CPU cíclicos (cada media hora exacta). En esta entrada, detallo cómo identificamos al responsable y el procedimiento para estabilizar el entorno.</p>
<h2 id="heading-1-el-hallazgo-investigando-con-oswatcher-osw">1. El hallazgo: Investigando con OSWatcher (OSW)</h2>
<p>Todo comenzó analizando los datos históricos de <strong>OSWatcher</strong>. Al revisar los archivos de procesos (<code>ps</code>), detectamos un patrón recurrente. Varios procesos de Perl ejecutados como <code>root</code> realizaban modificaciones de inventario con el flag <code>-collectall</code> de forma persistente:</p>
<pre><code class="lang-plaintext">[grid@rac-node-001 oswps]$ grep -i collectall *.dat
rac-node-001.internal_ps_25.11.30.2200.dat:root   13603   9521  19 13.6  S 22:09:28 00:00:00 /bin/perl /opt/oracle.ahf/tfa/bin/tfactl.pl directory modify /u01/app/oracle/agent/... -collectall
rac-node-001.internal_ps_25.11.30.2200.dat:root   16113   9521  19 36.2  S 22:10:00 00:00:00 /bin/perl /opt/oracle.ahf/tfa/bin/tfactl.pl directory modify /u01/app/oracle/product/19.26/... -collectall
rac-node-001.internal_ps_25.11.30.2200.dat:root  138557 134853  19  7.2  S 22:39:27 00:00:00 /bin/perl /opt/oracle.ahf/tfa/bin/tfactl.pl directory modify /u01/app/oracle/agent/... -collectall
rac-node-001.internal_ps_25.11.30.2200.dat:root  140654 134853  19  8.7  S 22:39:58 00:00:00 /bin/perl /opt/oracle.ahf/tfa/bin/tfactl.pl directory modify /u01/app/oracle/product/19.26/... -collectall
rac-node-001.internal_ps_25.12.01.1500.dat:root   69691  65697  19  6.1  S 15:09:29 00:00:00 /bin/perl /opt/oracle.ahf/tfa/bin/tfactl.pl directory modify /u01/app/oracle/agent/... -collectall
rac-node-001.internal_ps_25.12.01.1500.dat:root   72082  65697  19  7.3  S 15:10:00 00:00:00 /bin/perl /opt/oracle.ahf/tfa/bin/tfactl.pl directory modify /u01/app/oracle/product/19.26/... -collectall
</code></pre>
<h2 id="heading-2-anatomia-del-problema-el-arbol-de-procesos">2. Anatomía del problema: El árbol de procesos</h2>
<p>Para entender el origen, mapeamos la jerarquía del demonio de <strong>TFA (Trace File Analyzer)</strong>. Identificamos que el disparador era una tarea de "redescubrimiento" automática:</p>
<p><strong>Estructura del proceso:</strong></p>
<ul>
<li><p><strong>TFA Daemon (Java TFAMain)</strong>: El proceso padre persistente.</p>
<ul>
<li><p><code>tfactl rediscover -mode lite -auto</code>: El comando encargado de buscar cambios en el entorno.</p>
<ul>
<li><p><a target="_blank" href="http://tfactl.pl"><code>tfactl.pl</code></a> <code>rediscover</code>: El script de ejecución.</p>
<ul>
<li><a target="_blank" href="http://tfactl.pl"><code>tfactl.pl</code></a> <code>directory modify ... -collectall</code>: Las tareas finales que impactaban en la CPU al escanear directorios de inventario (DB, Agentes, etc).</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="heading-revelando-la-jerarquia-quien-consume-mi-cpu">Revelando la Jerarquía: ¿Quién consume mi CPU?</h3>
<p>Para entender por qué el servidor presentaba picos de carga, no basta con ver un proceso suelto; es necesario entender la relación padre-hijo. Utilizando los datos de <strong>OSWatcher (OSW)</strong> y rastreando los PIDs (Process IDs) mediante <code>ps -ef</code>, logramos reconstruir el árbol genealógico del culpable.</p>
<h4 id="heading-la-raiz-el-demonio-tfa">La Raíz: El Demonio TFA</h4>
<p>Todo nace del <strong>TFA Daemon (PID 2616695 en nuestro caso)</strong>, con el proceso Java (<code>oracle.rat.tfa.TFAMain</code>) que corre como <code>root</code>. Este demonio mantiene varios hilos vivos para monitorizar eventos , pero el "ruido" venía de su rama de descubrimiento.</p>
<h4 id="heading-el-descenso-de-java-a-perl">El Descenso: De Java a Perl</h4>
<p>Al investigar el PID padre, vimos cómo el demonio invoca una cadena de comandos para realizar el mantenimiento del inventario:</p>
<ol>
<li><p><strong>El Iniciador:</strong> El demonio lanza un script shell <code>tfactl rediscover</code>.</p>
</li>
<li><p><strong>La Capa Python:</strong> Este a su vez llama a un componente empaquetado (<code>tfactl.egg</code>) que gestiona la lógica de AHF.</p>
</li>
<li><p><strong>El Motor Perl:</strong> Finalmente, la ejecución recae en scripts de Perl (<a target="_blank" href="http://tfactl.pl"><code>tfactl.pl</code></a>), que es donde se realiza el trabajo pesado de interacción con el sistema de archivos.</p>
</li>
</ol>
<h4 id="heading-el-fumigador-el-proceso-collectall">El "Fumigador": El proceso <code>collectall</code></h4>
<p>El punto crítico aparece en los procesos hoja (los últimos de la rama). Identificamos múltiples procesos Perl ejecutando: <a target="_blank" href="http://tfactl.pl"><code>tfactl.pl</code></a> <code>directory modify &lt;path&gt; -collectall -private</code></p>
<p>Estos procesos son los que escanean recursivamente directorios críticos como:</p>
<ul>
<li><p>El inventario del Agente de Enterprise Manager (<code>/u01/app/oracle/agent/...</code>).</p>
</li>
<li><p>El inventario del Home de la Base de Datos (<code>/u01/app/oracle/product/19.26/...</code>).</p>
</li>
<li><p>Etc.</p>
</li>
</ul>
<h4 id="heading-representacion-del-arbol-de-ejecucion">Representación del Árbol de Ejecución</h4>
<p>Así es como se ve la cascada de ejecución que capturamos durante el pico de consumo:</p>
<pre><code class="lang-plaintext">TFA Daemon (PID 2616695) – TFAMain (Java)
└─ sh tfactl rediscover (PID 9325)
   └─ python tfactl.egg rediscover (PID 9370)
      └─ sh tfactl.tfa rediscover (PID 9485)
         └─ tfactl.pl rediscover -mode lite -auto (PID 9521)  &lt;-- El "Scheduler"
            ├─ tfactl.pl directory modify ... -collectall (PID 13603)
            ├─ tfactl.pl directory modify ... -collectall (PID 16113)
            └─ java CommandLine rac-node-001:listtfausers (PID 9617)
</code></pre>
<h2 id="heading-3-comandos-utiles-de-diagnostico">3. Comandos útiles de diagnóstico</h2>
<p>Durante la resolución, estos fueron los comandos clave para entender qué estaba pasando "bajo el capó":</p>
<ul>
<li><p><strong>Consultar la frecuencia actual:</strong> <code>tfactl get rediscoveryInterval</code> <em>(En nuestro caso, devolvió</em> <code>30m</code>, confirmando ejecuciones cada media hora).</p>
<pre><code class="lang-plaintext">  [root@rac-node-001 ~]# tfactl get rediscoveryInterval
  .------------------------------------------------------.
  |                      rac-node-001                    |
  +----------------------------------------------+-------+
  | Configuration Parameter                      | Value |
  +----------------------------------------------+-------+
  | Rediscovery Interval ( rediscoveryInterval ) | 30m   |
  '----------------------------------------------+-------'
</code></pre>
</li>
<li><p><strong>Revisar logs de actividad:</strong> Los logs de AHF son vitales. Se encuentran en nuestro caso (separación de roles): <code>/u01/app/grid/oracle.ahf/data/&lt;hostname&gt;/diag/ahf/tfactl.log</code>.</p>
</li>
<li><p><strong>Verificar ayuda y límites:</strong> En la documentación oficial vemos que el intervalo acepta minutos (<code>m</code>), horas (<code>h</code>) o días (<code>d</code>), con un <strong>máximo de 1 día</strong>. <a target="_blank" href="https://docs.oracle.com/en/engineered-systems/health-diagnostics/autonomous-health-framework/ahfug/tfactl-set.html">Documentación</a></p>
</li>
</ul>
<p><strong>Conclusión del análisis:</strong> Este árbol nos confirmó que no estábamos ante un error o un proceso "zombie", sino ante una tarea de mantenimiento programada de AHF (<code>rediscover -mode lite</code>) que, por configuración por defecto, era demasiado agresiva para nuestro entorno.</p>
<h2 id="heading-4-el-desafio-de-la-programacion-scheduler">4. El desafío de la programación (Scheduler)</h2>
<p>Al no existir un comando para deshabilitar el proceso o para fijar una hora exacta (ej. las 03:00 AM), tuvimos que entender el funcionamiento del <em>timer</em> de TFA. El contador es <strong>relativo</strong>: <strong>comienza a contar desde que el servicio se inicia o desde que se modifica el parámetro.</strong></p>
<h2 id="heading-5-la-solucion-sincronizando-el-intervalo-al-maximo">5. La Solución: Sincronizando el intervalo al máximo</h2>
<p>Decidimos reducir la frecuencia al mínimo posible estableciendo el intervalo en <strong>24 horas</strong> para todo el cluster:</p>
<pre><code class="lang-plaintext">[root@rac-node-001 ~]# tfactl set rediscoveryInterval=24h -c
Successfully set rediscoveryInterval=24h

[root@rac-node-001 ~]# tfactl get rediscoveryInterval 
.------------------------------------------------------.
|                      rac-node-001                    |
+----------------------------------------------+-------+
| Configuration Parameter                      | Value |
+----------------------------------------------+-------+
| Rediscovery Interval ( rediscoveryInterval ) | 24h   |
'----------------------------------------------+-------'
</code></pre>
<p><strong>Nota importante:</strong> Al aplicar el cambio con el parámetro <code>-c</code> (cluster-wide), TFA lanza inmediatamente un <code>rediscover</code> sincronizado en todos los nodos. Esto es ideal porque nos permite "resetear" el contador de 24 horas en el momento exacto en que ejecutamos el comando.</p>
<h2 id="heading-6-verificacion-en-los-logs">6. Verificación en los Logs</h2>
<p>Confirmamos la ejecución en los logs de ambos nodos justo tras el cambio: <code>[2026-01-21 10:04:40 CET] INFO - Executing command ... rediscover -mode lite -auto</code> <code>[2026-01-21 10:04:48 CET] INFO - Process completed execution with exit code 0</code></p>
<h2 id="heading-conclusion">Conclusión</h2>
<p>Si sufres picos de CPU causados por AHF, no intentes deshabilitarlo, busca el ajuste para la métrica concreta.</p>
<p>La estrategia ganadora es:</p>
<ol>
<li><p>Elegir una ventana de baja carga.</p>
</li>
<li><p>Configurar <code>rediscoveryInterval=24h -c</code>.</p>
</li>
<li><p>Si necesitas una hora fija, reinicia el servicio TFA (<code>tfactl restart</code>) en ese horario.</p>
</li>
</ol>
<p>Con esto, hemos pasado de 48 picos de CPU diarios a solo 1, manteniendo el sistema monitorizado pero estable.</p>
]]></content:encoded></item><item><title><![CDATA[Afinando el Job interno MGMT_CAW_EXTRACT_JOB en Oracle OEM]]></title><description><![CDATA[En entornos con Oracle Enterprise Manager (OEM) y AWR Warehouse , gran parte de la magia ocurre gracias a jobs internos gestionados por el propio repositorio.
El problema aparece cuando necesitamos entender su comportamiento o afinar su ejecución (po...]]></description><link>https://selectfromdual.com/afinando-el-job-interno-mgmtcawextractjob-en-oracle-oem</link><guid isPermaLink="true">https://selectfromdual.com/afinando-el-job-interno-mgmtcawextractjob-en-oracle-oem</guid><category><![CDATA[Oracle]]></category><category><![CDATA[awr]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Fri, 19 Dec 2025 12:12:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/TamMbr4okv4/upload/53ac2b7947d1ba55ea55d5c0fef362e7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En entornos con Oracle Enterprise Manager (OEM) y AWR Warehouse , gran parte de la magia ocurre gracias a jobs internos gestionados por el propio repositorio.</p>
<p>El problema aparece cuando necesitamos entender su comportamiento o afinar su ejecución (por ejemplo, en entornos RAC) y descubrimos que no existe opción alguna para modificarlo desde la consola de OEM.</p>
<h2 id="heading-que-es-awr-warehouse">Qué es AWR Warehouse?</h2>
<p><strong>AWR Warehouse</strong> es un componente de Oracle Enterprise Manager que permite <strong>centralizar la información histórica de rendimiento</strong> (AWR) de múltiples bases de datos en un único repositorio.</p>
<p>En lugar de consultar los datos AWR directamente en cada base de datos (con la retención limitada que tenga configurada):</p>
<ul>
<li><p><strong>Extrae periódicamente los snapshots AWR</strong> de las bases de datos monitorizadas.</p>
</li>
<li><p>Los <strong>almacena en un esquema dedicado dentro del repositorio de OEM</strong>.</p>
</li>
<li><p>Permite <strong>análisis históricos de largo plazo</strong>, comparativas entre sistemas y reporting avanzado.</p>
</li>
</ul>
<p>Esto es especialmente útil cuando:</p>
<ul>
<li><p>Necesitamos análisis de rendimiento de <strong>meses o años atrás</strong>.</p>
</li>
<li><p>Queremos <strong>comparar varias bases de datos</strong> o entornos.</p>
</li>
<li><p>La retención local de AWR es reducida por motivos de espacio.</p>
</li>
</ul>
<h2 id="heading-como-se-alimenta-awr-warehouse"><strong>¿Cómo se alimenta AWR Warehouse?</strong></h2>
<p>La información no llega sola. CAW depende de una serie de jobs internos del repositorio OEM que se encargan de:</p>
<ul>
<li><p>Lanzar procesos de extracción.</p>
</li>
<li><p>Conectarse a las bases de datos monitorizadas.</p>
</li>
<li><p>Recoger los datos AWR necesarios.</p>
</li>
<li><p>Cargarlos en el warehouse de forma centralizada.</p>
</li>
</ul>
<p><strong>En entornos RAC, el lugar donde se ejecuta este job sí importa:</strong></p>
<ul>
<li><p>Puede afectar a la carga de una instancia concreta.</p>
</li>
<li><p>Puede condicionar la conectividad hacia determinadas bases.</p>
</li>
<li><p>Puede influir en el rendimiento general del repositorio OEM.</p>
</li>
</ul>
<p>El objetivo es:</p>
<p>Controlar en qué instancia del RAC se ejecuta el proceso de extracción de AWR Warehouse, asegurando un <strong>comportamiento más predecible</strong> y alineado con nuestra arquitectura.</p>
<p>Aquí es donde entra en juego la necesidad de modificar el INSTANCE_ID del job, algo que OEM no permite hacer desde interfaz, pero que sí es posible a nivel de base de datos.</p>
<h2 id="heading-el-job-interno-identificado">El Job interno identificado</h2>
<p>Tras investigar, identificamos el job interno implicado:</p>
<pre><code class="lang-plaintext">MGMT_CAW_EXTRACT_JOB
</code></pre>
<p>Se trata de un job propiedad de SYS, gestionado por DBMS_SCHEDULER, y completamente oculto a nivel de interfaz gráfica.</p>
<p>Este job actúa como orquestador de la extracción, y su correcta ejecución es crítica para que los datos de rendimiento lleguen al warehouse.</p>
<h2 id="heading-se-puede-modificar-desde-oem">¿Se puede modificar desde OEM?</h2>
<p><strong>No.</strong></p>
<p>OEM no permite modificar este job desde la interfaz. No hay opción para cambiar atributos como la instancia en la que se ejecuta, ni su planificación.</p>
<p>Así que la única vía posible es <strong>actuar directamente contra la base de datos</strong>.</p>
<h2 id="heading-modificando-el-job-directamente-en-base-de-datos">Modificando el Job directamente en base de datos</h2>
<p>En nuestro caso, el objetivo era <strong>forzar la ejecución del job en una instancia concreta del RAC</strong>. Para ello, utilizamos el paquete <code>DBMS_SCHEDULER</code> y modificamos el atributo <code>instance_id</code>:</p>
<pre><code class="lang-plaintext">BEGIN
 DBMS_SCHEDULER.set_attribute(
 name =&gt; '"SYS"."MGMT_CAW_EXTRACT_JOB"',
 attribute =&gt; 'instance_id',
value =&gt; '2'
 );
END;
/
</code></pre>
<p><strong>Puntos importantes a tener en cuenta:</strong></p>
<ul>
<li><p>El job pertenece al esquema <strong>SYS</strong>, por lo que es necesario tener privilegios adecuados.</p>
</li>
<li><p>El nombre debe ir completamente cualificado y entrecomillado.</p>
</li>
<li><p>Este tipo de cambios <strong>no están documentados oficialmente</strong>, así que conviene aplicarlos con criterio y siempre en entornos controlados.</p>
</li>
</ul>
<h2 id="heading-monitorizacion-de-la-ejecucion">Monitorización de la ejecución</h2>
<p>Una vez aplicado el cambio, pasamos a <strong>monitorizar el comportamiento real del job</strong>. Para ello, consultamos la vista <code>DBA_SCHEDULER_JOB_RUN_DETAILS</code>:</p>
<pre><code class="lang-plaintext">SELECT instance_id,
 log_date,
 owner,
 job_name,
 status,
 run_duration,
 output
FROM DBA_SCHEDULER_JOB_RUN_DETAILS
WHERE job_name = 'MGMT_CAW_EXTRACT_JOB'
ORDER BY log_date DESC;
</code></pre>
<p>Esta consulta nos permite ver:</p>
<ul>
<li><p><strong>En qué instancia se ejecuta realmente</strong> (<code>INSTANCE_ID</code>)</p>
</li>
<li><p><strong>Cuándo se lanza</strong> (<code>LOG_DATE</code>)</p>
</li>
<li><p><strong>Duración y estado</strong> de cada ejecución</p>
</li>
<li><p><strong>Salida del job</strong>, muy útil para confirmar si hay generación efectiva de datos</p>
</li>
</ul>
<h2 id="heading-comportamiento-observado"><strong>Comportamiento observado</strong></h2>
<p>Aquí viene una de las claves más interesantes:</p>
<p>El job se ejecuta cada 3 horas.</p>
<p>Sin embargo, la extracción real de datos solo se produce cada 24 horas (configurado desde la interfaz)</p>
<p>Es decir:</p>
<p>No todas las ejecuciones implican una carga efectiva de información en el AWR Warehouse.</p>
<p>Esto explica por qué, aun viendo ejecuciones frecuentes, no siempre observamos actividad de generación de datos.</p>
]]></content:encoded></item><item><title><![CDATA[Cómo ejecutar SQL Tuning Advisor desde un Standby en Oracle Active Data Guard desde OEM]]></title><description><![CDATA[En entornos Oracle con Active Data Guard, es posible ejecutar SQL Tuning Advisor desde la base standby y generar reportes de la primaria. Esto permite analizar SQL de alta carga sin afectar la producción.

1. Preparar un Database Link
Para que la sta...]]></description><link>https://selectfromdual.com/como-ejecutar-sql-tuning-advisor-desde-un-standby-en-oracle-active-data-guard-desde-oem</link><guid isPermaLink="true">https://selectfromdual.com/como-ejecutar-sql-tuning-advisor-desde-un-standby-en-oracle-active-data-guard-desde-oem</guid><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Mon, 17 Nov 2025 18:46:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/FHnnjk1Yj7Y/upload/3e0873c57c8a1b7e265fca32c6e4bfd3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En entornos Oracle con Active Data Guard, es posible ejecutar SQL Tuning Advisor desde la base standby y generar reportes de la primaria. Esto permite analizar SQL de alta carga sin afectar la producción.</p>
<hr />
<h2 id="heading-1-preparar-un-database-link">1. Preparar un Database Link</h2>
<p>Para que la standby pueda consultar la primaria, necesitamos un database link privado:</p>
<ol>
<li>Crear entradas TNS :</li>
</ol>
<pre><code class="lang-plaintext">PDB_XXXXXX_PRI=
    (DESCRIPTION_LIST=
      (LOAD_BALANCE=off) (FAILOVER=on)
          (DESCRIPTION= (CONNECT_TIMEOUT=15)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)
             (ADDRESS_LIST= (LOAD_BALANCE=on)
                (ADDRESS=(PROTOCOL=TCP)(HOST=racscan-pri.xxxxx.xxx)(PORT=1521)))
                  (CONNECT_DATA=(SERVICE_NAME=mant.xxxxx.xxx)))
           (DESCRIPTION= (CONNECT_TIMEOUT=15)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)
                (ADDRESS_LIST= (LOAD_BALANCE=on)
                    (ADDRESS=(PROTOCOL=TCP)(HOST= racscan-stb.xxxxx.xxx)(PORT=1521)))
                         (CONNECT_DATA=(SERVICE_NAME=mant.xxxxx.xxx))))
</code></pre>
<ol start="2">
<li>Desbloquear el usuario SYS$UMF:</li>
</ol>
<pre><code class="lang-plaintext">ALTER USER "SYS$UMF" IDENTIFIED BY sysumf ACCOUNT UNLOCK;
</code></pre>
<ol start="3">
<li>Crear el database link desde la primaria:</li>
</ol>
<pre><code class="lang-plaintext">CREATE DATABASE LINK lnk_to_pri
CONNECT TO SYS$UMF IDENTIFIED BY "sysumf"
USING 'PDB_PRIMARY';
</code></pre>
<ol start="4">
<li>Verificar la conexión desde Standby:</li>
</ol>
<pre><code class="lang-plaintext">SQL&gt;  select * from dual@lnk_to_pri;

D
-
X
</code></pre>
<hr />
<h2 id="heading-2-generar-la-tarea-sql-tuning">2. Generar la tarea SQL Tuning</h2>
<p>Desde OEM la tarea automática se llamará como:</p>
<p>SQL_TUNING_STDBY_xxxx</p>
<ul>
<li><p>Pertenecen a SYSTEM.</p>
</li>
<li><p>Desde la standby, se pueden consultar mediante DBMS_SQLTUNE.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763405141652/a044688a-66a3-4696-b749-6c5112b634cc.png" alt class="image--center mx-auto" /></p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763405156474/9cb8dcdc-3c90-4678-b0b0-ebb22c59a020.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<hr />
<h2 id="heading-3-generar-el-reporte-desde-la-standby">3. Generar el reporte desde la standby</h2>
<p>Podremos ver resultados desde OEM o consultar desde BBDD:</p>
<pre><code class="lang-plaintext">SET SERVEROUTPUT ON SIZE UNLIMITED

DECLARE
l_report CLOB;
BEGIN
l_report := DBMS_SQLTUNE.report_tuning_task(
task_name =&gt; 'SQL_TUNING_STDBY_xxxx,
type =&gt; 'TEXT',
level =&gt; 'TYPICAL',
section =&gt; 'ALL',
owner_name =&gt; 'SYSTEM'
);
DBMS_OUTPUT.put_line(l_report);
END;
/
</code></pre>
<ul>
<li><p>type =&gt; 'TEXT' devuelve un reporte legible en consola.</p>
</li>
<li><p>section =&gt; 'ALL' incluye todo el detalle del tuning.</p>
</li>
<li><p>owner_name =&gt; 'SYSTEM' permite acceder a tareas de otro usuario.</p>
</li>
</ul>
<hr />
<h2 id="heading-4-guardar-el-reporte-en-un-archivo">4. Guardar el reporte en un archivo</h2>
<pre><code class="lang-plaintext">SPOOL sql_tuning_report.txt

DECLARE
l_report CLOB;
BEGIN
l_report := DBMS_SQLTUNE.report_tuning_task(
task_name =&gt; 'SQL_TUNING_STDBY_xxxx',
type =&gt; 'TEXT',
level =&gt; 'ALL',
section =&gt; 'ALL',
owner_name =&gt; 'SYSTEM'
);
DBMS_OUTPUT.put_line(l_report);
END;
/

SPOOL OFF
</code></pre>
<p>Esto genera un archivo sql_tuning_report.txt con todas las recomendaciones, índices sugeridos y sugerencias de reescritura.</p>
<hr />
<h2 id="heading-beneficios-de-usar-sql-tuning-en-standby">Beneficios de usar SQL Tuning en Standby</h2>
<ul>
<li><p>Ejecutar tuning sin impactar la primaria</p>
</li>
<li><p>Compatible con Active Data Guard</p>
</li>
<li><p>Permite optimizaciones periódicas desde standby</p>
</li>
<li><p>Acceso a recomendaciones completas incluso si la tarea fue ejecutada en la primaria</p>
</li>
</ul>
<hr />
<p>Nota: Siempre usar database links privados y privilegios mínimos necesarios (SYS$UMF).</p>
]]></content:encoded></item><item><title><![CDATA[Cómo habilitar trazas en el autostats job de Oracle]]></title><description><![CDATA[En ocasiones, necesitamos analizar con detalle el comportamiento del autostats job en Oracle, especialmente cuando detectamos problemas en la recogida de estadísticas o un comportamiento anómalo en la ejecución del Automatic Statistics Gathering.Orac...]]></description><link>https://selectfromdual.com/como-habilitar-trazas-en-el-autostats-job-de-oracle</link><guid isPermaLink="true">https://selectfromdual.com/como-habilitar-trazas-en-el-autostats-job-de-oracle</guid><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Fri, 31 Oct 2025 07:45:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/cckf4TsHAuw/upload/7e86fa3486f5f7877735e092508b9c4a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En ocasiones, necesitamos analizar con detalle el comportamiento del <strong>autostats job</strong> en Oracle, especialmente cuando detectamos problemas en la recogida de estadísticas o un comportamiento anómalo en la ejecución del <em>Automatic Statistics Gathering</em>.<br />Oracle ofrece la posibilidad de habilitar trazas a nivel de <code>DBMS_STATS</code> para entender qué está ocurriendo internamente durante estos procesos.</p>
<p>A continuación se muestra el procedimiento completo para activar y revisar las trazas del autostats job de manera controlada y segura.</p>
<hr />
<h2 id="heading-1-crear-la-tabla-de-log-temporal">1. Crear la tabla de log temporal</h2>
<p>Primero, creamos una tabla en el esquema <code>SYS</code> que almacenará la información de trazas.<br />La estructura se basa en la tabla interna <code>SYS.STATS_TARGET$</code>:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> sys.stats_target$_log
<span class="hljs-keyword">as</span>
<span class="hljs-keyword">select</span> t.*, rpad(<span class="hljs-string">'X'</span>,<span class="hljs-number">30</span>,<span class="hljs-string">'X'</span>) session_id, <span class="hljs-number">1</span> state
<span class="hljs-keyword">from</span> sys.stats_target$ t
<span class="hljs-keyword">where</span> <span class="hljs-number">1</span>=<span class="hljs-number">0</span>;
</code></pre>
<p>Esta tabla se crea vacía (<code>where 1=0</code>) y contendrá los registros generados por el proceso de autostats durante la traza.<br />El campo <code>session_id</code> se rellena con una cadena de prueba (<code>rpad('X',30,'X')</code>) y el campo <code>state</code> con valor <code>1</code> para simular la estructura de datos.</p>
<hr />
<h2 id="heading-2-habilitar-la-traza-para-dbmsstats">2. Habilitar la traza para DBMS_STATS</h2>
<p>A continuación, habilitamos el tracing a nivel global.<br />El parámetro <code>'trace'</code> de <code>DBMS_STATS.SET_GLOBAL_PREFS</code> permite activar diferentes niveles de detalle mediante la suma de bits.<br />En este caso, se activan múltiples niveles para obtener información completa, incluyendo la del <strong>autostats job</strong>:</p>
<pre><code class="lang-sql">exec dbms_stats.set_GLOBAL_PREFS('trace', 4+8+16+32+128+512+1024+2048+32768);
</code></pre>
<p>Esto incluye trazas para:</p>
<ul>
<li><p>Operaciones internas de <code>DBMS_STATS</code></p>
</li>
<li><p>Ejecución del autostats scheduler job</p>
</li>
<li><p>Seguimiento detallado de las decisiones de estadísticas sobre objetos</p>
</li>
<li><p>Procesamiento de targets y resultados de cálculo</p>
</li>
</ul>
<hr />
<h2 id="heading-3-permitir-la-ejecucion-del-autostats-job">3. Permitir la ejecución del autostats job</h2>
<p>Una vez activada la traza, dejamos que el job de autostats se ejecute normalmente.<br />Es importante tener en cuenta que <strong>no es necesario ejecutar el job desde la misma sesión</strong> en la que se activó la traza.<br />El tracing se aplica de forma global mientras el parámetro esté activo.</p>
<hr />
<h2 id="heading-4-desactivar-la-traza">4. Desactivar la traza</h2>
<p>Cuando hayamos recopilado suficiente información, debemos <strong>desactivar las trazas inmediatamente</strong> para evitar un consumo excesivo de recursos o un log innecesariamente grande:</p>
<pre><code class="lang-sql">exec DBMS_STATS.SET_GLOBAL_PREFS('trace',0);
</code></pre>
<p>Y finalmente, eliminamos la tabla temporal que creamos al inicio:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">drop</span> <span class="hljs-keyword">table</span> sys.stats_target$_log;
</code></pre>
<hr />
<h2 id="heading-5-localizar-los-archivos-de-traza">5. Localizar los archivos de traza</h2>
<p>Podemos buscar los archivos generados por <code>DBMS_STATS</code> en el sistema de archivos con un comando similar a:</p>
<pre><code class="lang-bash">find /ruta_trace -<span class="hljs-built_in">type</span> f | xargs grep <span class="hljs-string">'DBMS_STATS'</span>
</code></pre>
<p>Esto nos ayudará a identificar los ficheros de traza donde Oracle ha registrado la información de depuración.<br />Generalmente, los archivos se encuentran bajo el directorio de diagnóstico (<code>ADR</code>) del <em>Oracle Base</em>.</p>
<hr />
<h2 id="heading-6-analizar-las-tareas-mas-lentas-del-autostats-job">6. Analizar las tareas más lentas del autostats job</h2>
<p>Además de las trazas, es posible identificar <strong>qué fases o tareas dentro del autostats job consumen más tiempo</strong> utilizando las vistas <code>DBA_OPTSTAT_OPERATIONS</code> y <code>DBA_OPTSTAT_OPERATION_TASKS</code>.<br />La siguiente consulta permite desglosar el detalle de cada tarea, su duración y tipo de objeto afectado:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> 
    ta.opid,
    ta.TARGET_TYPE,
    ta.TARGET,
    ta.STATUS,
    TO_CHAR(ta.START_TIME, <span class="hljs-string">'YYYY-MM-DD HH24:MI:SS'</span>) <span class="hljs-keyword">AS</span> START_TIME,
    TO_CHAR(ta.END_TIME, <span class="hljs-string">'YYYY-MM-DD HH24:MI:SS'</span>) <span class="hljs-keyword">AS</span> END_TIME,
    <span class="hljs-keyword">ROUND</span>((<span class="hljs-keyword">CAST</span>(ta.END_TIME <span class="hljs-keyword">AS</span> <span class="hljs-built_in">DATE</span>) - <span class="hljs-keyword">CAST</span>(ta.START_TIME <span class="hljs-keyword">AS</span> <span class="hljs-built_in">DATE</span>)) * <span class="hljs-number">24</span> * <span class="hljs-number">60</span>, <span class="hljs-number">2</span>) <span class="hljs-keyword">AS</span> DURATION_MINUTES,
    ta.ESTIMATED_COST,
    ta.notes
<span class="hljs-keyword">FROM</span> 
    DBA_OPTSTAT_OPERATION_TASKS ta,
    DBA_OPTSTAT_OPERATIONS op
<span class="hljs-keyword">WHERE</span>
    ta.start_time &gt; (<span class="hljs-keyword">SYSDATE</span> - <span class="hljs-number">15</span>)
    <span class="hljs-keyword">AND</span> ta.opid = op.id
    <span class="hljs-keyword">AND</span> op.operation = <span class="hljs-string">'gather_database_stats (auto)'</span>
<span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> 
    ta.START_TIME <span class="hljs-keyword">DESC</span>;
</code></pre>
<p>Con esta consulta podemos identificar:</p>
<ul>
<li><p>Qué objetos están tardando más en el proceso de <em>gather stats</em>.</p>
</li>
<li><p>Cuánto tiempo dura cada tarea.</p>
</li>
<li><p>El tipo de objeto (tabla, índice, esquema, etc.).</p>
</li>
<li><p>El coste estimado y notas del optimizador.</p>
</li>
</ul>
<p>Esto resulta especialmente útil para detectar <strong>cuellos de botella</strong> o <strong>objetos que bloquean la ejecución del autostats job</strong>.</p>
<hr />
<h2 id="heading-7-recomendaciones">7. Recomendaciones</h2>
<ul>
<li><p>Realiza este procedimiento <strong>solo bajo supervisión o en entornos controlados</strong> si se trata de producción.</p>
</li>
<li><p>Desactiva siempre el tracing al finalizar para evitar logs innecesarios.</p>
</li>
<li><p>Conserva los archivos de traza para su análisis conjunto con Oracle Support si estás trabajando en un SR.</p>
</li>
<li><p>Usa la consulta de tareas para priorizar el ajuste de estadísticas en los objetos más costosos.</p>
</li>
</ul>
<hr />
<h2 id="heading-conclusion">Conclusión</h2>
<p>El uso del tracing en <code>DBMS_STATS</code> combinado con el análisis de las vistas de operaciones permite comprender en profundidad el comportamiento del autostats job en Oracle.<br />Gracias a estas herramientas, puedes detectar las fases más lentas del proceso, identificar qué objetos requieren atención y mejorar el rendimiento del mantenimiento de estadísticas en entornos críticos.</p>
]]></content:encoded></item><item><title><![CDATA[Consultas lentas a vistas AWR y del diccionario en PDB: el caso de EXTENDED DATA LINK FULL y cómo resolverlo]]></title><description><![CDATA[En entornos multitenant de Oracle Database (CDB/PDB), es habitual que ciertas consultas sobre vistas del diccionario o del AWR se comporten de forma inesperadamente lenta, incluso con filtros adecuados y bajo volumen de datos.
En este artículo te cue...]]></description><link>https://selectfromdual.com/consultas-lentas-a-vistas-awr-y-del-diccionario-en-pdb-el-caso-de-extended-data-link-full-y-como-resolverlo</link><guid isPermaLink="true">https://selectfromdual.com/consultas-lentas-a-vistas-awr-y-del-diccionario-en-pdb-el-caso-de-extended-data-link-full-y-como-resolverlo</guid><category><![CDATA[Oracle]]></category><category><![CDATA[awr]]></category><category><![CDATA[pdb]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Thu, 25 Sep 2025 07:30:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/cckf4TsHAuw/upload/bbc05a5ec42fc6633e2cad71e92c81e0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En entornos <strong>multitenant</strong> de Oracle Database (CDB/PDB), es habitual que ciertas consultas sobre vistas del diccionario o del AWR se comporten de forma inesperadamente lenta, incluso con filtros adecuados y bajo volumen de datos.</p>
<p>En este artículo te cuento un caso real que me encontré al trabajar con la vista <code>DBA_HIST_SYSSTAT</code> y cómo lo resolví gracias al parámetro oculto <code>CONTAINER_DATA</code> introducido a partir del <strong>bug 31142749</strong>.</p>
<hr />
<h2 id="heading-el-sintoma-consultas-awr-lentas-sin-motivo-aparente">El síntoma: consultas AWR lentas sin motivo aparente</h2>
<p>Estaba trabajando en una consulta para calcular el número de transacciones por segundo a partir de las estadísticas AWR.<br />El SQL utilizaba <code>DBA_HIST_SYSSTAT</code> y <code>DBA_HIST_SNAPSHOT</code> con filtros muy concretos por <code>SNAP_ID</code>, <code>DBID</code> e <code>INSTANCE_NUMBER</code>. A pesar de ello, la ejecución era muy lenta:</p>
<p>A pesar de que esta consulta debería completarse en segundos, la ejecución tardaba <strong>varios minutos</strong>.<br />El plan de ejecución reveló la causa:</p>
<pre><code class="lang-plaintext">|* 17 | EXTENDED DATA LINK FULL | AWR_CDB_SYSSTAT |
</code></pre>
<p>Este paso era el responsable del mayor coste y retraso.</p>
<h2 id="heading-la-causa-el-bug-31142749-y-las-vistas-con-extended-data-link">La causa: el bug 31142749 y las vistas con <em>extended data link</em></h2>
<p>El problema está relacionado con el <strong>bug 31142749 – QUERY ON ALL_ARGUMENTS SLOW IN 19.6 PDB</strong></p>
<p>Para identificar los objetos que son <strong>susceptibles de provocar planes de ejecución con operaciones costosas</strong> como <code>EXTENDED DATA LINK FULL</code>, especialmente cuando se consultan desde una PDB sin ajustar el parámetro <code>CONTAINER_DATA</code> :</p>
<pre><code class="lang-plaintext">SELECT object_name, object_type, sharing
FROM dba_objects
WHERE sharing = 'EXTENDED DATA LINK';
</code></pre>
<h2 id="heading-la-solucion-usar-el-parametro-containerdata">La solución: usar el parámetro <code>CONTAINER_DATA</code></h2>
<p>Oracle introdujo el parámetro oculto <code>CONTAINER_DATA</code> a partir del parche <strong>31142749</strong> (incluido desde <strong>19.10 RU - Enero 2021</strong>).</p>
<p>El parámetro <code>CONTAINER_DATA</code> controla el comportamiento de las consultas sobre objetos con <em>extended data links</em> tanto en aplicaciones comunes como en el diccionario de datos. Su valor determina qué datos devuelve una consulta cuando se ejecuta desde una PDB (pluggable database):</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Valor</td><td>Extended data-linked application common objects</td><td>Extended data-linked Oracle-supplied dictionary objects</td></tr>
</thead>
<tbody>
<tr>
<td><strong>ALL</strong></td><td>Devuelve datos compartidos (application root) + datos locales de <strong>todas las PDBs relacionadas</strong>.</td><td>Devuelve datos del CDB root + datos de <strong>todas las PDBs</strong> relacionadas</td></tr>
<tr>
<td><strong>CURRENT</strong></td><td>Solo datos locales de la PDB actual.</td><td>Solo datos de la PDB actual.</td></tr>
<tr>
<td><strong>CURRENT_DICTIONARY</strong></td><td>Igual que ALL (root + local), pero limitado al <strong>contexto actual de aplicación</strong>.</td><td>Igual que ALL (root + PDB actual), pero <strong>sin incluir datos</strong> de otras PDBs.</td></tr>
</tbody>
</table>
</div><h2 id="heading-formas-de-aplicarlo">Formas de aplicarlo</h2>
<h3 id="heading-usar-un-hint-en-la-consulta-solucion-mas-rapida">Usar un <strong>hint</strong> en la consulta (solución más rápida)</h3>
<p>En mi caso, el rendimiento se solucionó inmediatamente añadiendo este hint:</p>
<pre><code class="lang-plaintext">/*+ OPT_PARAM('container_data' 'current_dictionary') */
</code></pre>
<p>El plan de ejecución cambió y el paso <code>EXTENDED DATA LINK FULL</code> desapareció.</p>
<h3 id="heading-cambiarlo-a-nivel-de-sesion">Cambiarlo a nivel de sesión</h3>
<p>Para no modificar cada consulta individualmente:</p>
<pre><code class="lang-plaintext">ALTER SESSION SET CONTAINER_DATA=CURRENT_DICTIONARY;
</code></pre>
<p>También puedes usar un trigger de logon para aplicarlo automáticamente:</p>
<pre><code class="lang-plaintext">CREATE OR REPLACE TRIGGER set_container_data
AFTER LOGON ON DATABASE
BEGIN
  EXECUTE IMMEDIATE 'ALTER SESSION SET CONTAINER_DATA=CURRENT_DICTIONARY';
END;
/
</code></pre>
<h3 id="heading-cambiarlo-a-nivel-de-instancia-permanente">Cambiarlo a nivel de instancia (permanente)</h3>
<p>Puedes configurarlo en el <code>spfile</code> y reiniciar la base de datos:</p>
<pre><code class="lang-plaintext">ALTER SYSTEM SET CONTAINER_DATA=CURRENT_DICTIONARY SCOPE=SPFILE;
</code></pre>
<h2 id="heading-resultado-mejora-inmediata-del-rendimiento">Resultado: mejora inmediata del rendimiento</h2>
<p>Tras aplicar el hint <code>OPT_PARAM</code> o ajustar la sesión, las consultas sobre <code>DBA_HIST_SYSSTAT</code> pasaron de tardar <strong>minutos</strong> a ejecutarse en <strong>segundos</strong>.</p>
<p>El plan de ejecución dejó de mostrar <code>EXTENDED DATA LINK FULL</code>, eliminando el acceso innecesario a datos del root.</p>
<h2 id="heading-conclusion">Conclusión</h2>
<p>Si trabajas con vistas AWR o del diccionario en una <strong>PDB</strong> y notas un rendimiento inesperadamente bajo, revisa el plan de ejecución.</p>
<p>Si ves un paso <code>EXTENDED DATA LINK FULL</code>, probablemente estés afectado por el bug <strong>31142749</strong>.</p>
<p>La solución es aplicar el parámetro <code>CONTAINER_DATA</code> con el valor <code>CURRENT_DICTIONARY</code>, lo que restringe el acceso a datos del contenedor actual y evita un coste de ejecución innecesario.</p>
]]></content:encoded></item><item><title><![CDATA[Cómo calcular transacciones por segundo (TPS) a partir de AWR en Oracle]]></title><description><![CDATA[En ocasiones necesitamos analizar la carga transaccional real de una base de datos Oracle, más allá de los informes AWR estándar. Un buen enfoque es calcular las transacciones por segundo (TPS) directamente a partir de las métricas históricas que gua...]]></description><link>https://selectfromdual.com/como-calcular-transacciones-por-segundo-tps-a-partir-de-awr-en-oracle</link><guid isPermaLink="true">https://selectfromdual.com/como-calcular-transacciones-por-segundo-tps-a-partir-de-awr-en-oracle</guid><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Tue, 23 Sep 2025 08:37:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/vZJdYl5JVXY/upload/ababe76f497d90a6975407c873deebbd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En ocasiones necesitamos analizar la carga transaccional real de una base de datos Oracle, más allá de los informes AWR estándar. Un buen enfoque es calcular las transacciones por segundo (TPS) directamente a partir de las métricas históricas que guarda Oracle en sus vistas de AWR.</p>
<p>En esta entrada te muestro cómo hacerlo paso a paso.</p>
<h3 id="heading-de-donde-salen-los-datos">¿De dónde salen los datos?</h3>
<p>Oracle guarda en la vista DBA_HIST_SYSSTAT distintos contadores acumulados. Para el caso que nos interesa:</p>
<p>user commits → número de commits ejecutados por los usuarios.</p>
<p>user rollbacks → número de rollbacks ejecutados.</p>
<p>Estos valores son acumulativos desde que se inició la instancia. Para obtener la actividad en un intervalo debemos calcular el delta (diferencia entre snapshots consecutivos).</p>
<h3 id="heading-la-consulta">La consulta</h3>
<pre><code class="lang-plaintext">/* ---------------------------------------------------------
   Calcular transacciones por segundo (TPS) desde AWR
   usando las métricas de DBA_HIST_SYSSTAT y DBA_HIST_SNAPSHOT
   --------------------------------------------------------- */

WITH snaps AS (
    SELECT s.dbid,
           s.instance_number,
           s.snap_id,
           -- Redondeamos el inicio del intervalo al minuto
           TRUNC(s.begin_interval_time, 'MI') AS snap_time,
           -- Diferencia de tiempo entre snapshots (en segundos)
           (EXTRACT(DAY FROM (s.begin_interval_time - LAG(s.begin_interval_time) 
                               OVER (PARTITION BY s.dbid, s.instance_number ORDER BY s.snap_id))) * 86400) +
           (EXTRACT(HOUR FROM (s.begin_interval_time - LAG(s.begin_interval_time) 
                               OVER (PARTITION BY s.dbid, s.instance_number ORDER BY s.snap_id))) * 3600) +
           (EXTRACT(MINUTE FROM (s.begin_interval_time - LAG(s.begin_interval_time) 
                               OVER (PARTITION BY s.dbid, s.instance_number ORDER BY s.snap_id))) * 60) +
           (EXTRACT(SECOND FROM (s.begin_interval_time - LAG(s.begin_interval_time) 
                               OVER (PARTITION BY s.dbid, s.instance_number ORDER BY s.snap_id)))) 
             AS interval_sec
      FROM dba_hist_snapshot s
),
stats AS (
    SELECT t.dbid,
           t.instance_number,
           t.snap_id,
           -- Calculamos delta para commits y rollbacks
           GREATEST(NVL(t.value - LAG(t.value) 
                           OVER (PARTITION BY t.dbid, t.instance_number, t.stat_name ORDER BY t.snap_id),0),0) 
               AS delta_tx
      FROM dba_hist_sysstat t
     WHERE t.stat_name IN ('user commits', 'user rollbacks')
),
calc AS (
    SELECT sn.snap_time,
           sn.instance_number,
           SUM(st.delta_tx) / NULLIF(sn.interval_sec,0) AS tx_per_sec
      FROM snaps sn
           JOIN stats st
             ON st.dbid = sn.dbid
            AND st.instance_number = sn.instance_number
            AND st.snap_id = sn.snap_id
     WHERE sn.interval_sec IS NOT NULL
     GROUP BY sn.snap_time, sn.instance_number, sn.interval_sec
)
SELECT TO_CHAR(c.snap_time,'YYYY-MM-DD HH24:MI') AS minuto,
       c.instance_number,
       ROUND(c.tx_per_sec,2) AS transactions_per_sec
  FROM calc c
 ORDER BY minuto DESC, instance_number;
</code></pre>
<h3 id="heading-interpretacion">Interpretación</h3>
<p>Cada fila representa la tasa de transacciones por segundo de una instancia en un minuto concreto.</p>
<p>Si tu base de datos es RAC, verás cada instancia por separado.</p>
<p>Puedes sumar las TPS de todas las instancias para tener una visión global de la base de datos.</p>
<h3 id="heading-conclusion">Conclusión</h3>
<p>Esta consulta es muy útil cuando queremos:</p>
<ul>
<li><p>Ver la evolución de la actividad transaccional en detalle.</p>
</li>
<li><p>Comparar instancias de un RAC.</p>
</li>
<li><p>Correlacionar picos de carga con eventos específicos (deploys, cierres contables, etc.).</p>
</li>
</ul>
<p>Con AWR ya tienes todos los datos, solo hace falta un poco de SQL para explotarlos</p>
]]></content:encoded></item><item><title><![CDATA[Moviendo la ventana de ejecución de jobs en Oracle Enterprise Manager Cloud Control]]></title><description><![CDATA[Durante unas semanas notábamos picos puntuales de CPU en nuestros servidores Oracle Grid Infrastructure y bases de datos, sin actividad aparente de los usuarios. La carga aparecía siempre a la misma hora: 15:18, y duraba unos segundos, pero disparaba...]]></description><link>https://selectfromdual.com/moviendo-la-ventana-de-ejecucion-de-jobs-en-oracle-enterprise-manager-cloud-control</link><guid isPermaLink="true">https://selectfromdual.com/moviendo-la-ventana-de-ejecucion-de-jobs-en-oracle-enterprise-manager-cloud-control</guid><category><![CDATA[Oracle]]></category><category><![CDATA[oem]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Wed, 17 Sep 2025 14:03:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/G7nGwvmhd1Q/upload/38dfcd55e4614f63b622f498dc4b7c72.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Durante unas semanas notábamos picos puntuales de CPU en nuestros servidores Oracle Grid Infrastructure y bases de datos, sin actividad aparente de los usuarios. La carga aparecía siempre a la misma hora: 15:18, y duraba unos segundos, pero disparaba el consumo de CPU de varios procesos perl y java del agente de Oracle Enterprise Manager Cloud Control (OEM).</p>
<h2 id="heading-esto-nos-llevo-a-una-pequena-investigacion-para-entender-que-ocurria-y-finalmente-a-mover-la-ventana-de-ejecucion-de-los-jobs-de-inventario-de-parches-del-agente-a-una-hora-menos-conflictiva">Esto nos llevó a una pequeña investigación para entender qué ocurría y, finalmente, a mover la ventana de ejecución de los jobs de inventario de parches del agente a una hora menos conflictiva.</h2>
<p><strong>Introducción</strong></p>
<p>En los últimos días detectamos un <strong>pico recurrente de carga de CPU alrededor de las 15:18</strong> en nuestros nodos de base de datos. Este patrón se repetía casi a diario y afectaba al rendimiento de las transacciones durante unos minutos.</p>
<p>El objetivo de este artículo es explicar:</p>
<ul>
<li><p>Cómo detectamos el patrón.</p>
</li>
<li><p>Cómo lo diagnosticamos hasta identificar la causa raíz.</p>
</li>
<li><p>Cómo reprogramamos la tarea para evitar que se ejecute en horario crítico de negocio.</p>
</li>
</ul>
<h2 id="heading-deteccion-inicial-patron-recurrente-en-transacciones">Detección inicial: patrón recurrente en transacciones</h2>
<p>Todo comenzó revisando métricas de nuestra base de datos de negocio. Vimos un patrón repetido de demora de rendimiento en torno a las <strong>15:18.</strong></p>
<h2 id="heading-correlacion-con-el-agente-de-cloud-control">Correlación con el agente de Cloud Control</h2>
<p>Al revisar el log del agente en:</p>
<pre><code class="lang-plaintext">$AGENT_HOME/agent_inst/sysman/log/gcagent.log
</code></pre>
<p>Vimos entradas exactamente a esa hora:</p>
<pre><code class="lang-plaintext">2025-08-26 15:18:44,807 [GC.Executor... oracle_home_config:PatchFixedBug] xxxxxxxxxxxxxxxxxx
</code></pre>
<p>También lo comprobamos en los dos nodos del RAC con:</p>
<pre><code class="lang-plaintext">$AGENT_HOME/bin/emctl status agent scheduler | grep -i oracle_home_config
</code></pre>
<p>Resultado:</p>
<pre><code class="lang-plaintext">2025-08-27 15:18:41.788 : oracle_home:OraHomeXX_rac-xxx-001:oracle_home_config
2025-08-27 15:18:42.857 : oracle_home:OraGIXXHome1_1_rac-xxx-001:oracle_home_config
</code></pre>
<p>Y justo en ese momento, <code>top</code> mostraba varios procesos <code>perl</code> y <code>java</code> consumiendo &gt;190% de CPU:</p>
<pre><code class="lang-plaintext">PID    USER      %CPU  COMMAND
51242  agent13   190   java
51273  agent13    84   perl
51522  agent13    81   perl
</code></pre>
<h2 id="heading-diagnostico">Diagnóstico</h2>
<p>Confirmamos así que:</p>
<ul>
<li><p>A las 15:18, el agente de Oracle Enterprise Manager Cloud Control lanza tareas de tipo <code>oracle_home_config:PatchFixedBug</code>, que forman parte de la <strong>Configuration Collection</strong>.</p>
</li>
<li><p>Estas tareas ejecutan internamente herramientas como OPatch para <strong>recoger el inventario de parches aplicados</strong> en cada Oracle Home y en la Grid Infrastructure.</p>
</li>
<li><p>Esto provoca un <strong>pico puntual de CPU</strong>, suficiente para afectar a otras cargas sensibles en ese instante.</p>
</li>
</ul>
<h2 id="heading-cambiando-la-ventana-de-ejecucion">Cambiando la ventana de ejecución</h2>
<p>Para evitar el impacto en horario de negocio, decidimos <strong>mover la ventana de ejecución de esos jobs a la madrugada</strong>.</p>
<p>Pasos:</p>
<ol>
<li><p>Listar las tareas programadas del agente:  </p>
<p> $AGENT_HOME/bin/emctl status agent scheduler  </p>
</li>
<li><p>Identificar las tareas <code>oracle_home_config</code><br /> oracle_home:OraGIXXHome1_1_rac-xxx-001:oracle_home_config oracle_home:OraHomeXX_rac-xxx-001:oracle_home_config</p>
</li>
<li><p>Reprogramarlas con una nueva hora de inicio donde haya menos afección (ejemplo: 04:45 AM):</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758117431771/576ad67c-b42d-4d05-aea3-8bb4de90a9c7.png" alt class="image--center mx-auto" /></p>
<p> Repetir para cada Oracle Home.</p>
</li>
<li><p>Confirmar el cambio:  </p>
<p> $AGENT_HOME/bin/emctl status agent scheduler | grep oracle_home_config  </p>
</li>
</ol>
<pre><code class="lang-plaintext">[agent13@rac-xxx-002 ~]$ $AGENT_HOME/bin/emctl status agent scheduler | grep -i oracle_home_config
2025-08-27 21:42:55.879 : oracle_home:agent13c1_3_rac-xxx-002.paytef.local_8730:oracle_home_config
2025-08-28 04:30:00.000 : oracle_home:OraGIXXHome1_1_rac-xxx-002.paytef.local_8611:oracle_home_config
2025-08-28 04:45:00.000 : oracle_home:OraHomeXX_rac-xxx-002.paytef.local_6495:oracle_home_config
</code></pre>
<h2 id="heading-resultado">Resultado</h2>
<p>Desde que movimos las tareas, <strong>desaparecieron los picos de CPU en horario laboral</strong> y no hemos vuelto a ver demoras en tiempos a las 15:18.<br />La tarea sigue ejecutándose correctamente, pero <strong>en una ventana sin carga de usuarios</strong>.</p>
<h2 id="heading-conclusion">Conclusión</h2>
<p>Aunque el agente de Oracle Enterprise Manager Cloud Control trabaja de forma desatendida, <strong>sus tareas de inventario pueden ser muy pesadas</strong>.</p>
<p>Monitorizar patrones horarios y correlacionarlos con los logs del agente permite <strong>anticiparse a estos cuellos de botella invisibles</strong> y recolocar su ventana de ejecución para que no interfieran con el negocio.</p>
]]></content:encoded></item><item><title><![CDATA[Cursor Sharing y Binds Grandes: Cómo Diagnosticamos y Solucionamos un Caso Real con el Evento 10503 en Oracle]]></title><description><![CDATA[En entornos Oracle de alto rendimiento, la aparición de múltiples child cursors debido a BIND_MISMATCH es un problema común que puede degradar el rendimiento significativamente.
Nuestra aplicación, que utiliza extended strings, comenzó a presentar un...]]></description><link>https://selectfromdual.com/cursor-sharing-y-binds-grandes-como-diagnosticamos-y-solucionamos-un-caso-real-con-el-evento-10503-en-oracle</link><guid isPermaLink="true">https://selectfromdual.com/cursor-sharing-y-binds-grandes-como-diagnosticamos-y-solucionamos-un-caso-real-con-el-evento-10503-en-oracle</guid><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Fri, 29 Aug 2025 09:53:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/FHnnjk1Yj7Y/upload/ee6ba61a4800380c4aa46ddf6d22d735.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En entornos Oracle de alto rendimiento, la aparición de múltiples child cursors debido a BIND_MISMATCH es un problema común que puede degradar el rendimiento significativamente.</p>
<p>Nuestra aplicación, que utiliza extended strings, comenzó a presentar una elevada cantidad de child cursors en dos sentencias INSERT críticas.</p>
<p><strong>La Investigación y el Diagnóstico</strong></p>
<p>El primer paso fue identificar el origen exacto del BIND_MISMATCH. Para ello, seguimos un proceso meticuloso:</p>
<p>Identificación de los SQL_ID problemáticos: A través de V$SQL y V$SQL_SHARED_CURSOR, localizamos los dos SQL_ID involucrados (xxxxxxxxxxxxx1 y xxxxxxxxxxxxx2).</p>
<p>Para encontrar el motivo de un cursor no compartido:</p>
<pre><code class="lang-plaintext">-- Script Code
set serveroutput on

DECLARE
  v_count number;
  v_sql varchar2(500);
  v_sql_id varchar2(30) := '&amp;sql_id';
BEGIN
  v_sql_id := lower(v_sql_id);
  dbms_output.put_line(chr(13)||chr(10));
  dbms_output.put_line('sql_id: '||v_sql_id);
  dbms_output.put_line('------------------------');
  FOR c1 in
    (select column_name 
       from dba_tab_columns
      where table_name ='V_$SQL_SHARED_CURSOR'
        and column_name not in ('SQL_ID', 'ADDRESS', 'CHILD_ADDRESS', 'CHILD_NUMBER', 'REASON', 'CON_ID')
      order by column_id)
  LOOP
    v_sql := 'select count(*) from V_$SQL_SHARED_CURSOR
              where sql_id='||''''||v_sql_id||''''||'
              and '||c1.column_name||'='||''''||'Y'||'''';
    execute immediate v_sql into v_count;
    IF v_count &gt; 0
    THEN
      dbms_output.put_line(' - '||rpad(c1.column_name,30)||' count: '||v_count);
    END IF;
  END LOOP;
END;

Versions Summary
----------------
BIND_MISMATCH :121 &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;-----------------
ROLL_INVALID_MISMATCH :2
BIND_LENGTH_UPGRADEABLE :9
</code></pre>
<p>Análisis de los binds: La clave fue consultar la vista V$SQL_BIND_CAPTURE para entender la variabilidad en el tamaño de los binds para cada una de las sentencias.</p>
<p>Para el primer SQL_ID: sql</p>
<pre><code class="lang-plaintext">SELECT COUNT(*) AS count,
       position,
       MIN(max_length) AS min_length,
       MAX(max_length) AS max_length,
       datatype,
       CASE 
         WHEN MAX(value_string) IS NOT NULL THEN 'Yes' 
         ELSE 'No' 
       END AS bind_captured,
       '('||MAX(precision)||','||MAX(scale)||')' AS graduation
  FROM gv$sql_bind_capture
  WHERE  sql_id = 'xxxxxxxxxxxxx1'
 GROUP BY position, datatype
 ORDER BY max_length desc;

COUNT(*) POSITION MIN(MAX_LENGTH) MAX(MAX_LENGTH) DATATYPE BIND GRADUATION (PRECISION,SCALE)
======== ======== =============== =============== ======== =============== =================
21 1 22 22 2 No (,)
21 2 32 32 1 No (,)
21 3 2000 2000 1 No (,)
21 4 2000 2000 1 No (,)
21 5 2000 2000 1 No (,)
21 6 7 7 12 No (,)
21 7 22 22 2 No (,)
12 8 2000 2000 1 No (,)
9 8 22 22 2 No (,)
21 9 32 2000 1 Yes (,)
21 10 128 128 1 No (,)
21 11 128 128 1 No (,)
21 12 22 22 2 No (,)
21 13 2000 2000 1 No (,)
21 14 2000 2000 1 No (,)
21 15 32 2000 1 Yes (,)
21 16 2000 2000 1 No (,)
21 17 128 2000 1 Yes (,)
</code></pre>
<p>Los resultados mostraron que los binds de tipo VARCHAR2 en varias posiciones variaban entre 32 y 2000 bytes. ¡Este era el culpable del BIND_MISMATCH para la primera INSERT.</p>
<blockquote>
<p>High Version Count Due To BIND_MISMATCH ( <a target="_blank" href="https://support.oracle.com/epmos/faces/DocumentDisplay?parent=SrDetailText&amp;sourceId=3-40676398581&amp;id=336268.1">Doc ID 336268.1</a> <a target="_blank" href="https://support.oracle.com/epmos/faces/DocumentDisplay?parent=SrDetailText&amp;sourceId=3-40676398581&amp;id=336268.1">)</a></p>
</blockquote>
<p><a target="_blank" href="https://support.oracle.com/epmos/faces/DocumentDisplay?parent=SrDetailText&amp;sourceId=3-40676398581&amp;id=336268.1">Aplicación</a> de la solución: Basándonos en este análisis, aplicamos el evento 10503 con el nivel específico que necesitábamos:</p>
<pre><code class="lang-plaintext">ALTER SYSTEM SET EVENTS '10503 trace name context forever, level 2000';
</code></pre>
<p>Resultado: El problema de los child cursors para la primera sentencia INSERT se resolvió por completo.</p>
<p>El segundo problema: Tras el primer éxito, nos enfocamos en el segundo SQL_ID (xxxxxxxxxxxxx2). Repetimos el análisis y descubrimos que sus binds llegaban hasta 16,386 bytes.</p>
<p>Aplicamos el evento con un nivel superior:</p>
<pre><code class="lang-plaintext">ALTER SYSTEM SET EVENTS '10503 trace name context forever, level 16386';
</code></pre>
<p><strong>La Limitación</strong></p>
<p>Para nuestra sorpresa, el segundo problema no se resolvió. Al contactar con Soporte de Oracle, confirmaron nuestra sospecha: <strong>el evento 10503 tiene una limitación interna y no funciona para binds mayores a 4000 bytes</strong>, incluso si se establece un nivel superior.</p>
<p><strong>Conclusión y Lecciones Aprendidas</strong></p>
<p>El diagnóstico es clave: La solución no es aplicar el evento 10503 a ciegas. La consulta a V$SQL_BIND_CAPTURE para obtener el MAX(MAX_LENGTH) de los binds problemáticos es un paso fundamental y no negociable para determinar el nivel correcto.</p>
<p>Solución para binds &lt;= 4000 bytes: Para la primera INSERT, el evento 10503 configurado al nivel preciso (2000) fue la solución perfecta y eliminó el BIND_MISMATCH.</p>
<p>Limitación actual para binds &gt; 4000 bytes: Para la segunda INSERT, chocamos con un muro. Oracle confirmó que no hay solución nativa actualmente y que no hay planes a corto plazo para extender el evento más allá de 4000 bytes.</p>
<p><strong>Reflexión Final</strong></p>
<p>Este caso es un ejemplo perfecto de cómo un enfoque metódico—identificar, diagnosticar y aplicar—nos permitió resolver el 50% del problema. Para el otro 50%, nos topamos con una limitación actual de la base de datos.</p>
<p><strong>Cambio persistente en spfile</strong></p>
<p>Para que el cambio aplique tras reinicios</p>
<pre><code class="lang-plaintext">ALTER SYSTEM SET EVENT= '10503 trace name context forever, level 2000'  COMMENT='High Version Count Due To BIND_MISMATCH' SCOPE=SPFILE SID='*';
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Gestión de mensajes en trace frecuentes en Standby con ADG Redirect DML]]></title><description><![CDATA[Recientemente, en PAYTEF Sistemas, nos encontramos con un comportamiento curioso en nuestras bases de datos Oracle 19c que decidimos documentar, tanto para registrar la incidencia como para compartir aprendizajes con otros DBA que trabajen con Data G...]]></description><link>https://selectfromdual.com/gestion-de-mensajes-en-trace-frecuentes-en-standby-con-adg-redirect-dml</link><guid isPermaLink="true">https://selectfromdual.com/gestion-de-mensajes-en-trace-frecuentes-en-standby-con-adg-redirect-dml</guid><category><![CDATA[Oracle]]></category><category><![CDATA[adg]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Wed, 13 Aug 2025 10:11:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/xG8IQMqMITM/upload/9809436b8b62624dd75cdabaf98bdad6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recientemente, en PAYTEF Sistemas, nos encontramos con un comportamiento curioso en nuestras bases de datos Oracle 19c que decidimos documentar, tanto para registrar la incidencia como para compartir aprendizajes con otros DBA que trabajen con Data Guard y Standby con ADG Redirect DML.</p>
<p><strong>Contexto</strong></p>
<p>En nuestro entorno RAC, tenemos habilitado ADG Redirect DML en la base de datos standby. Esta funcionalidad permite que operaciones DML ejecutadas en la standby se redirijan automáticamente al primary.</p>
<p>Es un comportamiento esperado, <strong>pero genera un efecto colateral</strong>: decenas de archivos de trace por día, cada uno indicando que se ha conectado al primary, por ejemplo:</p>
<p>Connected to primary database target CDBXXX</p>
<p>Este volumen de trazas no representaba un error, pero sí un ruido significativo en nuestros logs, lo que dificulta la monitorización y la identificación de incidencias reales.</p>
<p><strong>Análisis</strong></p>
<p>La base de datos no tenía activado log_archive_trace, que podría controlar algunas trazas relacionadas con archivado.  </p>
<p><strong>Datos Relevantes</strong></p>
<p>Durante la fase de análisis, contabilizamos los trace generados por día en la standby:</p>
<pre><code class="lang-plaintext">Fecha        Trace files
2025-06-29    54
2025-07-01    96
2025-07-04    125
2025-07-10    66
2025-07-15    96
</code></pre>
<p>Como se puede ver, la cantidad variaba de manera considerable, llegando hasta 125 archivos en un solo día.</p>
<p><strong>Solución Adoptada</strong></p>
<p><strong>No existe un parámetro directo para desactivar estas trazas en el nivel RDBMS</strong> mientras ADG Redirect DML esté activo.</p>
<p>La solución práctica fue:</p>
<p>Crear un script de mantenimiento periódico que elimine o archive los trace files en intervalos regulares, utilizando cron en Linux.</p>
<p>Monitorizar los logs para asegurar que no se pierdan trazas relevantes para diagnósticos futuros.</p>
<p><strong>Conclusión</strong></p>
<p>Este caso nos recuerda que ciertas funcionalidades avanzadas de Oracle, como ADG Redirect DML, traen consigo comportamientos secundarios que no siempre están documentados como “configurables”.</p>
<p>En nuestro caso, el sistema siguió totalmente operativo, y la medida adoptada nos permitió mantener la claridad en nuestros logs sin afectar la disponibilidad ni la integridad de la standby.</p>
]]></content:encoded></item><item><title><![CDATA[Solucionando errores de memoria en Oracle Enterprise Manager 13c (OMS_HEAP_MAX y BEA-310003)]]></title><description><![CDATA[En uno de nuestros entornos de Oracle Enterprise Manager (OEM) 13c (Release 5), detectamos que el OMS (Oracle Management Service) se reiniciaba automáticamente sin intervención.
Tras revisar los logs, nos encontramos con el siguiente aviso preocupant...]]></description><link>https://selectfromdual.com/solucionando-errores-de-memoria-en-oracle-enterprise-manager-13c-omsheapmax-y-bea-310003</link><guid isPermaLink="true">https://selectfromdual.com/solucionando-errores-de-memoria-en-oracle-enterprise-manager-13c-omsheapmax-y-bea-310003</guid><category><![CDATA[cloudcontrol]]></category><category><![CDATA[oem]]></category><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Fri, 08 Aug 2025 16:39:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/rfNLa1HL7eY/upload/ad47ae1464d32c73ad542f1a6b6f4e35.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En uno de nuestros entornos de Oracle Enterprise Manager (OEM) 13c (Release 5), detectamos que el OMS (Oracle Management Service) se reiniciaba automáticamente sin intervención.</p>
<p>Tras revisar los logs, nos encontramos con el siguiente aviso preocupante:</p>
<pre><code class="lang-plaintext">&lt;Free memory in the server is 52,928 bytes. There is danger of receiving an OutOfMemoryError.&gt;
</code></pre>
<p>Aunque la máquina tenía memoria física suficiente, el proceso OMS estaba alcanzando su límite de heap Java, configurado con un valor bajo.</p>
<p><strong>Diagnóstico</strong></p>
<p>El proceso de OMS se ejecuta como una JVM (Java Virtual Machine), por lo tanto, su uso de memoria está limitado por los parámetros de arranque, típicamente:</p>
<p>-Xms: heap mínimo</p>
<p>-Xmx: heap máximo</p>
<p>Al revisar los procesos activos:</p>
<p>ps -ef | grep oms | grep Xmx</p>
<p>Detectamos que estaba usando un heap máximo de apenas 1740M, lo cual es insuficiente para un entorno de producción con cierta carga.</p>
<p><strong>Solución</strong></p>
<p>Aumentamos el parámetro OMS_HEAP_MAX a 4096M (4 GB), ejecutando lo siguiente:</p>
<pre><code class="lang-plaintext">$OMS_HOME/bin/emctl set property -name OMS_HEAP_MAX -value 4096M
</code></pre>
<p>Y aplicamos reinicio.</p>
<pre><code class="lang-plaintext">$OMS_HOME/bin/emctl stop oms -all -force 
$OMS_HOME/bin/emctl start oms
</code></pre>
<p>Después del reinicio, confirmamos que el nuevo valor se aplicó correctamente:</p>
<p>ps -ef | grep oms | grep Xmx</p>
<p>Debería mostrar algo como: -Xmx4096M</p>
<p><strong>Recursos del sistema</strong></p>
<p>También confirmamos que la máquina tenía recursos suficientes:</p>
<p>free -m</p>
<p>Resultado:</p>
<p>Mem: 23744 total</p>
<p>Suficiente RAM para aumentar la memoria heap del OMS sin riesgos.</p>
<p><strong>Conclusión</strong></p>
<p>Este caso nos recuerda que:</p>
<p>OEM 13c puede consumir bastante heap según la carga.</p>
<p>Un valor bajo de OMS_HEAP_MAX puede causar errores BEA-310003 y reinicios del OMS.</p>
<p>Es importante revisar regularmente los logs de WebLogic (server.log, EMGC_OMS1.out) y ajustar parámetros JVM según el crecimiento del entorno.</p>
<p><strong>¿Dónde buscar estos logs?</strong></p>
<p>Los logs relevantes del OMS están aquí:</p>
<p>$OMS_HOME/gc_inst/user_projects/domains/GCDomain/servers/EMGC_OMS1/logs/</p>
<p>EMGC_OMS1.out</p>
<p>server.log</p>
<p>Y los específicos de OEM:</p>
<p>$OMS_HOME/gc_inst/em/EMGC_OMS1/sysman/log/emoms.log</p>
<p>¿Has tenido problemas similares con OEM? ¿Cuánto heap tenéis configurado en producción?</p>
<h3 id="heading-referencias-y-notas-de-soporte">Referencias y Notas de Soporte</h3>
<ul>
<li><p>[MOS 2674369.1] <strong>How to Increase the Java Heap Size for the Oracle Management Service (OMS) in OEM 13c</strong></p>
</li>
<li><p>[MOS 1955854.1] <strong>OMS Fails with BEA-310003: Free memory in the server is ... There is danger of receiving an OutOfMemoryError</strong></p>
</li>
<li><p>Documentación oficial OEM 13c – sección <em>Tuning the OMS Heap Size</em></p>
</li>
<li><p>[MOS 1589136.1] <strong>How to Modify the OMS Java Heap Size Parameters in Enterprise Manager</strong></p>
</li>
<li><p>BEA-310003 Error Codes Reference</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Anticipa Problemas: Monitorización Inteligente de Tablespaces Oracle con AWR Warehouse]]></title><description><![CDATA[Como DBAs, sabemos que uno de los dolores de cabeza más comunes son los tablespaces que se quedan sin espacio. Un tablespace lleno puede detener aplicaciones críticas, generar errores y, en el peor de los casos, causar una interrupción total del serv...]]></description><link>https://selectfromdual.com/anticipa-problemas-monitorizacion-inteligente-de-tablespaces-oracle-con-awr-warehouse</link><guid isPermaLink="true">https://selectfromdual.com/anticipa-problemas-monitorizacion-inteligente-de-tablespaces-oracle-con-awr-warehouse</guid><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Thu, 31 Jul 2025 09:43:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/yTFy8GE9y5g/upload/b88f0fbea8b7dd084b42c64e9fffcb1d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Como DBAs, sabemos que uno de los dolores de cabeza más comunes son los tablespaces que se quedan sin espacio. Un tablespace lleno puede detener aplicaciones críticas, generar errores y, en el peor de los casos, <strong>causar una interrupción total del servicio.</strong> ¿No sería genial si pudieras predecir cuándo va a ocurrir esto y actuar antes de que sea un problema?</p>
<p>¡Hoy te presento una solución inteligente! He desarrollado un script que combina el poder de los datos históricos de Oracle AWR Warehouse con la sencillez de un script de shell para monitorizar el crecimiento de los tablespaces y alertarte proactivamente cuando necesiten más espacio.</p>
<p><strong>El Desafío de los Tablespaces y Por Qué Necesitamos Ser Proactivos</strong></p>
<p>Imagina esto: un lunes por la mañana, suena tu teléfono. "¡La aplicación está caída! ¡Error de tablespace lleno!". Es una pesadilla. Los tablespaces, que son las estructuras lógicas de almacenamiento en Oracle donde residen tus datos, crecen constantemente. Las transacciones, la inserción de nuevos datos, la creación de índices, todo consume espacio.</p>
<p>Si no monitorizas este crecimiento, te expones a:</p>
<ul>
<li><p>Caídas de aplicaciones: Cuando un tablespace crítico se llena, las operaciones de escritura fallan.</p>
</li>
<li><p>Pérdida de datos (temporal)</p>
</li>
<li><p>Estrés y trabajo reactivo: En lugar de planificar, te ves apagando fuegos urgentes.</p>
</li>
</ul>
<p><strong>La Magia Detrás de Escena: AWR Warehouse de Oracle</strong></p>
<p>Aquí es donde entra en juego el AWR Warehouse (Automatic Workload Repository Warehouse).</p>
<p><strong>¿Qué es AWR?</strong></p>
<p>Es una característica de Oracle que recopila automáticamente estadísticas de rendimiento y uso de tu base de datos en intervalos regulares (snapshots). Guarda datos sobre el uso de CPU, memoria, I/O, SQL más costosos... ¡y también el uso de tablespaces!</p>
<p><strong>¿Qué es AWR Warehouse?</strong></p>
<p>Piensa en ello como un "gran almacén" donde puedes guardar los datos AWR de múltiples bases de datos de tu entorno. En lugar de consultar directamente tu base de datos de producción (que podría tener un impacto en el rendimiento), puedes consultar este almacén centralizado.</p>
<p><strong>¿Por qué es clave para nuestra solución?</strong></p>
<p>Porque nos permite acceder al historial de crecimiento de los tablespaces sin afectar el rendimiento de tu base de datos de producción. Podemos ver cuánto creció un tablespace en el último mes, y esa es la métrica que usaremos para nuestra lógica predictiva.</p>
<h2 id="heading-os-presento-mi-oraculo-de-tablespaces">Os presento mi "Oráculo de Tablespaces"</h2>
<p>El script es un pequeño pero poderoso aliado que ejecuta la siguiente lógica:</p>
<ol>
<li><p><strong>Conexión Segura:</strong> Se conecta a una base de datos <strong>standby</strong> (para obtener la información actual de los tablespaces sin impactar la producción) y a tu <strong>AWR Warehouse</strong>.</p>
</li>
<li><p><strong>Lista los Tablespaces:</strong> Obtiene todos los tablespaces que no sean del sistema o temporales.</p>
</li>
<li><p><strong>Para cada tablespace, hace magia</strong>:</p>
<ul>
<li><p>Obtiene el <strong>espacio actualmente utilizado</strong> y <strong>asignado</strong> (reservado) en MB.</p>
</li>
<li><p>Consulta el <strong>AWR Warehouse</strong> para determinar el <strong>crecimiento total en MB del último mes completo</strong>.</p>
</li>
<li><p>Calcula el <strong>espacio libre disponible</strong>.</p>
</li>
<li><p><strong>¡La regla de oro!</strong> Compara: <strong>¿Es el espacio libre disponible menor que el crecimiento del último mes?</strong></p>
</li>
</ul>
</li>
<li><p><strong>Genera Alerta y Sugerencia:</strong> Si la respuesta es SÍ, se dispara una alerta. El script no solo te avisa, sino que te propone un comando <code>ALTER TABLESPACE RESIZE</code> para ampliarlo (atención que se trata de bigfile), sugiriendo un tamaño basado en su uso actual más el crecimiento del último mes.</p>
</li>
<li><p><strong>Envía el Informe:</strong> Consolida toda la información en un correo electrónico detallado.</p>
</li>
</ol>
<p>La belleza de este enfoque es que no usamos un umbral fijo de porcentaje. En su lugar, usamos el <strong>patrón de crecimiento histórico</strong> del tablespace para predecir si se quedará sin espacio en el futuro cercano, basándose en su ritmo de consumo. <strong>Esto es ser verdaderamente proactivo.</strong></p>
<p><strong>Desglose del Script (monitor_tbs_</strong><a target="_blank" href="http://growth.sh"><strong>growth.sh</strong></a><strong>)</strong></p>
<pre><code class="lang-plaintext">#!/bin/bash

#-----------------------------------------
# Configuración
#-----------------------------------------
ORACLE_SID_STANDBY="MY_PDB_STBY" # TNS de tu PDB Standby o base de datos de lectura
ORACLE_SID_AWR="MY_AWR_DB" # TNS de tu AWR Warehouse

USUARIO_STANDBY="system"
CLAVE_STANDBY="******" # ¡Ofuscada! Cambiar por tu contraseña real
USUARIO_AWR="system"
CLAVE_AWR="******" # ¡Ofuscada! Cambiar por tu contraseña real

MAIL_DESTINO="dba@mycompany.com" # Cambiar por tu dirección de correo
DEBUG=true # true para debug, false cuando esté implementado en producción

# --- AWR Warehouse Specifics ---
# !!! IMPORTANTE: DEBES CONFIGURAR ESTOS VALORES PARA TU ENTORNO !!!
# DB_TARGET_NAME TNS del target OEM .
# PDB_NAME_TO_MONITOR TNS de la Pluggable Database que quieres monitorizar.
DB_TARGET_NAME="my_cdb_prod"         # Ejemplo: 'my_cdb_prod' o 'oracle_db_name'
PDB_NAME_TO_MONITOR="MY_APP_PDB"     # Ejemplo: 'MY_APP_PDB'


# Variables entorno de BBDD
PATH=/home/nagios/.local/bin:/home/nagios/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin
ORACLE_SID=cdboem # El SID del Listener/Instancia que atiende tu AWR Warehouse
ORAENV_ASK=NO
. oraenv -s

export TNS_ADMIN=/home/nagios/tns_admin # Ruta a tu tnsnames.ora

#-----------------------------------------
# Función para depuración
#-----------------------------------------
log_debug() {
    # Redirige los mensajes de depuración a stderr para evitar contaminar stdout
    $DEBUG &amp;&amp; echo "[DEBUG] $(date +'%Y-%m-%d %H:%M:%S') $1" &gt;&amp;2
}

#-----------------------------------------
# Verifica conexión SQL
#-----------------------------------------
verifica_sqlplus() {
    local conn_str=$1
    local test_query="SELECT 'OK' FROM dual;"
    log_debug "Intentando conectar a: $conn_str"
    echo "$test_query" | sqlplus -s "$conn_str" | grep -q "OK" &gt; /dev/null
    if [ $? -ne 0 ]; then
        echo "❌ ERROR: Fallo de conexión con $conn_str" &gt;&amp;2
        exit 1
    else
        log_debug "Conexión exitosa a: $conn_str"
    fi
}

#-----------------------------------------
# Obtener tablespaces excluyendo los sistema/temp
# Nota: Esto obtiene tablespaces de la standby
#-----------------------------------------
get_tablespaces() {
    local conn_str="$1"
    log_debug "Obteniendo listado de tablespaces de $conn_str"
    sqlplus -s "$conn_str" &lt;&lt;EOF
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF
SELECT DISTINCT tablespace_name
FROM dba_data_files
WHERE tablespace_name NOT IN ('SYSTEM','SYSAUX','UNDOTBS1','TEMP')
ORDER BY tablespace_name;
EOF
}

#-----------------------------------------
# Obtener crecimiento total del último mes completo desde el AWR Warehouse
# (Usando TRUNC(SYSDATE, 'MM') en SQL para fechas )
#-----------------------------------------
get_last_month_total_growth() {
    local conn_awr="$1"
    local tbs_name="$2"
    local pdb_name_filter="$3"
    local target_name_filter="$4"

    log_debug "Calculando crecimiento TOTAL para '$tbs_name' en PDB '$pdb_name_filter' (Target: '$target_name_filter') para el último mes completo."

    sqlplus -s "$conn_awr" &lt;&lt;EOF
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF
WHENEVER SQLERROR EXIT FAILURE;
WITH
pdbs AS (
    SELECT DISTINCT dbid, pdb_name, con_dbid
    FROM DBA_HIST_PDB_INSTANCE
    WHERE pdb_name = '$pdb_name_filter'
),
all_monthly_snapshots AS (
    SELECT
        s.end_interval_time,
        (tsu.tablespace_usedsize * df.block_size) / 1024 / 1024 AS used_mb_raw,
        df.tsname AS tablespace_name,
        s.dbid,
        p.pdb_name
    FROM
        dwa_hist_tbspc_space_usage tsu -- Usar dwa_hist_tbspc_space_usage si viene de DWA
        JOIN dba_hist_snapshot s ON tsu.snap_id = s.snap_id AND tsu.dbid = s.dbid
        JOIN dba_hist_datafile df ON tsu.dbid = df.dbid AND tsu.tablespace_id = df.ts#
        JOIN dbsnmp.caw_dbid_mapping m ON s.dbid = m.new_dbid
        JOIN pdbs p ON tsu.con_dbid = p.con_dbid
    WHERE
        s.END_INTERVAL_TIME &gt;= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -1) -- Inicio del último mes completo
        AND s.END_INTERVAL_TIME &lt; TRUNC(SYSDATE, 'MM')              -- Fin del último mes completo (exclusivo)
        AND df.tsname = '$tbs_name'
        AND m.target_name = '$target_name_filter'
        AND df.tsname NOT IN ('UNDO1', 'UNDO2', 'TEMP', 'PDB$SEED')
),
ranked_monthly_usage AS (
    SELECT
        used_mb_raw,
        ROW_NUMBER() OVER (PARTITION BY tablespace_name, dbid, pdb_name ORDER BY end_interval_time ASC) AS rn_asc,
        ROW_NUMBER() OVER (PARTITION BY tablespace_name, dbid, pdb_name ORDER BY end_interval_time DESC) AS rn_desc
    FROM
        all_monthly_snapshots
)
SELECT
    NVL(
        ROUND(
            MAX(CASE WHEN rn_desc = 1 THEN used_mb_raw END) -
            MAX(CASE WHEN rn_asc = 1 THEN used_mb_raw END)
        , 2)
    , 0)
FROM
    ranked_monthly_usage;
EOF
}

#-----------------------------------------
# Obtener resumen de crecimiento anual por mes (para resumen histórico detallado)
# (Usando TRUNC(SYSDATE, 'MM') en SQL para fechas)
#-----------------------------------------
get_monthly_growth_summary() {
    local conn_awr="$1"
    local tbs_name="$2"
    local pdb_name_filter="$3"
    local target_name_filter="$4"

    log_debug "Generando resumen mensual de crecimiento para '$tbs_name' en PDB '$pdb_name_filter' (Target: '$target_name_filter') para los últimos 12 meses."

    sqlplus -s "$conn_awr" &lt;&lt;EOF
SET LINESIZE 300 PAGESIZE 100 FEEDBACK OFF
WHENEVER SQLERROR EXIT FAILURE;
WITH
pdbs AS (
    SELECT DISTINCT dbid, pdb_name, con_dbid
    FROM DBA_HIST_PDB_INSTANCE
    WHERE pdb_name = '$pdb_name_filter'
),
monthly_last_snapshot AS (
    SELECT
        TRUNC(s.END_INTERVAL_TIME, 'MM') AS month_start_date,
        df.tsname AS tablespace_name,
        (tsu.tablespace_usedsize * df.block_size) / 1024 / 1024 AS tablespace_used_mb,
        ROW_NUMBER() OVER (PARTITION BY TRUNC(s.END_INTERVAL_TIME, 'MM'), df.tsname, s.dbid, p.pdb_name ORDER BY s.END_INTERVAL_TIME DESC) AS rn
    FROM
        dwa_hist_tbspc_space_usage tsu -- Usar dwa_hist_tbspc_space_usage si viene de DWA
        JOIN dba_hist_snapshot s ON tsu.snap_id = s.snap_id AND tsu.dbid = s.dbid
        JOIN dba_hist_datafile df ON tsu.dbid = df.dbid AND tsu.tablespace_id = df.ts#
        JOIN dbsnmp.caw_dbid_mapping m ON s.dbid = m.new_dbid
        JOIN pdbs p ON tsu.con_dbid = p.con_dbid
    WHERE
        s.END_INTERVAL_TIME &gt;= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -12) -- Inicio de los últimos 12 meses completos
        AND s.END_INTERVAL_TIME &lt; TRUNC(SYSDATE, 'MM')               -- Fin de los últimos 12 meses completos (exclusivo)
        AND df.tsname = '$tbs_name'
        AND m.target_name = '$target_name_filter'
        AND df.tsname NOT IN ('UNDO1', 'UNDO2', 'TEMP', 'PDB$SEED')
),
calculated_growth AS (
    SELECT
        month_start_date,
        tablespace_name,
        tablespace_used_mb,
        LAG(tablespace_used_mb) OVER (PARTITION BY tablespace_name ORDER BY month_start_date) AS previous_month_used_mb
    FROM
        monthly_last_snapshot
    WHERE rn = 1
)
SELECT
    TO_CHAR(month_start_date, 'YYYY-MM') AS mes,
    NVL(ROUND(tablespace_used_mb - previous_month_used_mb, 2), 0) AS crecimiento_mb
FROM
    calculated_growth
WHERE previous_month_used_mb IS NOT NULL
ORDER BY
    month_start_date;
EOF
}


#-----------------------------------------
# Obtener el tamaño ACTUALMENTE UTILIZADO del tablespace en MB desde dba_segments.
# Se conecta a la BBDD de standby.
#-----------------------------------------
get_current_tbs_used_size_mb() {
    local conn_str="$1" # Conexión a la BBDD que tiene los segmentos (ej. standby o prod)
    local tbs_name="$2"

    log_debug "Obteniendo tamaño ACTUALMENTE UTILIZADO para tablespace: $tbs_name de $conn_str"
    sqlplus -s "$conn_str" &lt;&lt;EOF
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF
SELECT ROUND(SUM(bytes)/1024/1024, 2)
FROM dba_segments
WHERE tablespace_name = '$tbs_name'
GROUP BY tablespace_name;
EOF
}

#-----------------------------------------
# Obtener el tamaño ACTUALMENTE ASIGNADO (ALLOCATED) del tablespace en MB
# desde dba_data_files. Esto representa el tamaño físico del datafile.
# Se conecta a la BBDD de standby .
#-----------------------------------------
get_current_tbs_allocated_size_mb_from_db() {
    local conn_str="$1" # Conexión a la BBDD (ej. standby o prod)
    local tbs_name="$2"

    log_debug "Obteniendo tamaño ACTUALMENTE ASIGNADO para tablespace: $tbs_name de $conn_str"
    sqlplus -s "$conn_str" &lt;&lt;EOF
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF
SELECT ROUND(SUM(bytes)/1024/1024, 2)
FROM dba_data_files
WHERE tablespace_name = '$tbs_name'
GROUP BY tablespace_name;
EOF
}


#-----------------------------------------
# Proceso principal
#-----------------------------------------
main() {
    local conn_stby="${USUARIO_STANDBY}/${CLAVE_STANDBY}@${ORACLE_SID_STANDBY}"
    local conn_awr="${USUARIO_AWR}/${CLAVE_AWR}@${ORACLE_SID_AWR}"

    verifica_sqlplus "$conn_stby"
    verifica_sqlplus "$conn_awr"

    log_debug "Obteniendo listado de tablespaces desde $ORACLE_SID_STANDBY..."
    mapfile -t tablespaces &lt; &lt;(get_tablespaces "$conn_stby" | tr -d '\r')

    if [ ${#tablespaces[@]} -eq 0 ]; then
        log_debug "No se encontraron tablespaces para monitorear."
        echo "OK: No se encontraron tablespaces para monitorear (excluyendo sistema/temp)."
        exit 0
    fi

    local informe=""
    local alter_commands_generated=""
    local tablespaces_to_alert=()

    for tbs_name in "${tablespaces[@]}"; do
        if [[ -z "$tbs_name" ]]; then
            continue
        fi

        log_debug "Analizando tablespace: $tbs_name"

        current_tbs_used_size_mb=$(get_current_tbs_used_size_mb "$conn_stby" "$tbs_name" | tr -d '[:space:]\r')
        current_tbs_allocated_size_mb=$(get_current_tbs_allocated_size_mb_from_db "$conn_stby" "$tbs_name" | tr -d '[:space:]\r')
        # last_month_growth will now be obtained correctly from the SQL query itself
        last_month_growth=$(get_last_month_total_growth "$conn_awr" "$tbs_name" "$PDB_NAME_TO_MONITOR" "$DB_TARGET_NAME" | tr -d '[:space:]\r')

        log_debug "$tbs_name: Used=${current_tbs_used_size_mb}MB, Allocated=${current_tbs_allocated_size_mb}MB, Last_Month_Growth=${last_month_growth}MB"

        if [[ "$current_tbs_used_size_mb" =~ ^[0-9]+([.][0-9]+)?$ ]] &amp;&amp; \
           [[ "$current_tbs_allocated_size_mb" =~ ^[0-9]+([.][0-9]+)?$ ]] &amp;&amp; \
           [[ "$last_month_growth" =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then

            # Calculate free space
            free_space_mb=$(echo "$current_tbs_allocated_size_mb - $current_tbs_used_size_mb" | bc)
            log_debug "Free Space for $tbs_name: ${free_space_mb}MB"

            # Check if free space is less than last month's growth
            comp=$(echo "$free_space_mb &lt; $last_month_growth" | bc)

            if [[ $comp -eq 1 ]]; then
                tablespaces_to_alert+=("$tbs_name")

                informe+=$'\n'"--- Tablespace: ${tbs_name} ---"
                informe+=$'\n'"  &gt; Tamaño Utilizado: ${current_tbs_used_size_mb} MB"
                informe+=$'\n'"  &gt; Tamaño Asignado (Reservado): ${current_tbs_allocated_size_mb} MB"
                informe+=$'\n'"  &gt; Espacio Libre Disponible: ${free_space_mb} MB"
                informe+=$'\n'"  &gt; Crecimiento último mes: ${last_month_growth} MB"
                informe+=$'\n'"  &gt; Alerta: El espacio libre (${free_space_mb} MB) es menor que el crecimiento del último mes (${last_month_growth} MB)."

                informe+=$'\n'
                informe+=$'\n'"------------------------------------------------------------"

                informe+=$'\n'"Resumen de crecimiento mensual para $tbs_name (últimos 12 meses):"
                # get_monthly_growth_summary now also uses SQL dates
                informe+=$'\n'"$(get_monthly_growth_summary "$conn_awr" "$tbs_name" "$PDB_NAME_TO_MONITOR" "$DB_TARGET_NAME" | tr -d '\r')"

                # Calculate the suggested resize command
                target_size_calculated=$(echo "$current_tbs_used_size_mb + $last_month_growth" | bc)

                new_size_mb=$(echo "scale=0; if ($current_tbs_allocated_size_mb &gt; $target_size_calculated) $current_tbs_allocated_size_mb else $target_size_calculated" | bc)

                new_size_mb=$(echo "$new_size_mb" | awk '{print int($1)}')

                informe+=$'\n'
                alter_cmd="ALTER TABLESPACE \"$tbs_name\" RESIZE ${new_size_mb}M;"
                informe+=$'\n'"COMANDO SUGERIDO (ejecutar en PDB de producción):"
                informe+=$'\n'"${alter_cmd}"
                alter_commands_generated+=$'\n'"${alter_cmd}"
                informe+=$'\n'
            else
                log_debug "Tablespace $tbs_name tiene suficiente espacio libre (${free_space_mb} MB) para el crecimiento del último mes (${last_month_growth} MB)."
            fi
        else
            informe+=$'\n'"ADVERTENCIA: No se pudieron obtener todos los datos (USADO, ASIGNADO o CRECIMIENTO) para el tablespace $tbs_name."
            log_debug "ADVERTENCIA: Datos incompletos para $tbs_name. Used: '$current_tbs_used_size_mb', Allocated: '$current_tbs_allocated_size_mb', Growth: '$last_month_growth'"
        fi
    done

    if [ ${#tablespaces_to_alert[@]} -gt 0 ]; then
        log_debug "Enviando informe por correo..."
        (
            echo -e "📈 Alerta de Espacio en Tablespaces - ¡Necesitan Ampliación Pronto! 📈\n"
            echo -e "Los siguientes tablespaces tienen menos espacio libre disponible que su crecimiento del último mes:\n"
            echo -e "$informe"
            if [ -n "$alter_commands_generated" ]; then
                echo -e "\n--- Resumen de Comandos ALTER TABLESPACE Generados (para ejecutar en PDB de producción) ---"
                echo -e "$alter_commands_generated"
            fi
        ) | mail -s "🚨 Alerta de espacio en Tablespaces - ${PDB_NAME_TO_MONITOR}" "$MAIL_DESTINO"
        echo "CRITICAL: Informe de alerta de espacio en tablespaces enviado."
    else
        log_debug "No hay tablespaces que necesiten ampliación urgente."
        echo "OK: No hay tablespaces con espacio libre menor que su crecimiento del último mes."
    fi
}

main
</code></pre>
<p><strong>Un Ejemplo de su Ejecución (Log Obfuscado)</strong></p>
<p>Aquí puedes ver cómo se vería la salida del script en un entorno de depuración (DEBUG=true).</p>
<pre><code class="lang-plaintext">[xxxxxx@host-monitor-001 plugins]$ /usr/local/ncpa/plugins/monitor_tbs_growth.sh
[DEBUG] 2025-07-31 10:21:08 Intentando conectar a: system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:08 Conexión exitosa a: system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:08 Intentando conectar a: system/******@MY_AWR_DB
[DEBUG] 2025-07-31 10:21:08 Conexión exitosa a: system/******@MY_AWR_DB
[DEBUG] 2025-07-31 10:21:08 Obteniendo listado de tablespaces desde MY_PDB_STBY...
[DEBUG] 2025-07-31 10:21:08 Obteniendo listado de tablespaces de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:08 Analizando tablespace: AUDIT_DATA
[DEBUG] 2025-07-31 10:21:08 Obteniendo tamaño ACTUALMENTE UTILIZADO para tablespace: AUDIT_DATA de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:08 Obteniendo tamaño ACTUALMENTE ASIGNADO para tablespace: AUDIT_DATA de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:09 Calculando crecimiento TOTAL para 'AUDIT_DATA' en PDB 'MY_APP_PDB' (Target: 'my_cdb_prod') para el último mes completo.
[DEBUG] 2025-07-31 10:21:11 AUDIT_DATA: Used=80.38MB, Allocated=600MB, Last_Month_Growth=-8MB
[DEBUG] 2025-07-31 10:21:11 Free Space for AUDIT_DATA: 519.62MB
[DEBUG] 2025-07-31 10:21:11 Tablespace AUDIT_DATA tiene suficiente espacio libre (519.62 MB) para el crecimiento del último mes (-8 MB).
[DEBUG] 2025-07-31 10:21:11 Analizando tablespace: LOG_ARCHIVE
[DEBUG] 2025-07-31 10:21:11 Obteniendo tamaño ACTUALMENTE UTILIZADO para tablespace: LOG_ARCHIVE de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:11 Obteniendo tamaño ACTUALMENTE ASIGNADO para tablespace: LOG_ARCHIVE de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:11 Calculando crecimiento TOTAL para 'LOG_ARCHIVE' en PDB 'MY_APP_PDB' (Target: 'my_cdb_prod') para el último mes completo.
[DEBUG] 2025-07-31 10:21:14 LOG_ARCHIVE: Used=8.63MB, Allocated=1024MB, Last_Month_Growth=71.75MB
[DEBUG] 2025-07-31 10:21:14 Free Space for LOG_ARCHIVE: 1015.37MB
[DEBUG] 2025-07-31 10:21:14 Tablespace LOG_ARCHIVE tiene suficiente espacio libre (1015.37 MB) para el crecimiento del último mes (71.75 MB).
[DEBUG] 2025-07-31 10:21:14 Analizando tablespace: APP_CONFIG
[DEBUG] 2025-07-31 10:21:14 Obteniendo tamaño ACTUALMENTE UTILIZADO para tablespace: APP_CONFIG de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:14 Obteniendo tamaño ACTUALMENTE ASIGNADO para tablespace: APP_CONFIG de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:14 Calculando crecimiento TOTAL para 'APP_CONFIG' en PDB 'MY_APP_PDB' (Target: 'my_cdb_prod') para el último mes completo.
[DEBUG] 2025-07-31 10:21:16 APP_CONFIG: Used=75.81MB, Allocated=100MB, Last_Month_Growth=0MB
[DEBUG] 2025-07-31 10:21:16 Free Space for APP_CONFIG: 24.19MB
[DEBUG] 2025-07-31 10:21:16 Tablespace APP_CONFIG tiene suficiente espacio libre (24.19 MB) para el crecimiento del último mes (0 MB).
... (salida omitida para brevedad, pero seguiría para todos los tablespaces) ...
[DEBUG] 2025-07-31 10:21:36 Analizando tablespace: SALES_DATA_2025
[DEBUG] 2025-07-31 10:21:36 Obteniendo tamaño ACTUALMENTE UTILIZADO para tablespace: SALES_DATA_2025 de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:36 Obteniendo tamaño ACTUALMENTE ASIGNADO para tablespace: SALES_DATA_2025 de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:36 Calculando crecimiento TOTAL para 'SALES_DATA_2025' en PDB 'MY_APP_PDB' (Target: 'my_cdb_prod') para el último mes completo.
[DEBUG] 2025-07-31 10:21:39 SALES_DATA_2025: Used=120324.75MB, Allocated=126700MB, Last_Month_Growth=20241.5MB
[DEBUG] 2025-07-31 10:21:39 Free Space for SALES_DATA_2025: 6375.25MB
[DEBUG] 2025-07-31 10:21:39 Generando resumen mensual de crecimiento para 'SALES_DATA_2025' en PDB 'MY_APP_PDB' (Target: 'my_cdb_prod') para los últimos 12 meses.
... (más output de debug) ...
[DEBUG] 2025-07-31 10:21:47 Analizando tablespace: FILE_STORAGE
[DEBUG] 2025-07-31 10:21:47 Obteniendo tamaño ACTUALMENTE UTILIZADO para tablespace: FILE_STORAGE de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:47 Obteniendo tamaño ACTUALMENTE ASIGNADO para tablespace: FILE_STORAGE de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:21:47 Calculando crecimiento TOTAL para 'FILE_STORAGE' en PDB 'MY_APP_PDB' (Target: 'my_cdb_prod') para el último mes completo.
[DEBUG] 2025-07-31 10:21:47 FILE_STORAGE: Used=110707MB, Allocated=116736MB, Last_Month_Growth=20989MB
[DEBUG] 2025-07-31 10:21:47 Free Space for FILE_STORAGE: 6029MB
[DEBUG] 2025-07-31 10:21:47 Generando resumen mensual de crecimiento para 'FILE_STORAGE' en PDB 'MY_APP_PDB' (Target: 'my_cdb_prod') para los últimos 12 meses.
... (más output de debug) ...
[DEBUG] 2025-07-31 10:22:39 Analizando tablespace: TXN_DATA_2025
[DEBUG] 2025-07-31 10:22:39 Obteniendo tamaño ACTUALMENTE UTILIZADO para tablespace: TXN_DATA_2025 de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:22:39 Obteniendo tamaño ACTUALMENTE ASIGNADO para tablespace: TXN_DATA_2025 de system/******@MY_PDB_STBY
[DEBUG] 2025-07-31 10:22:39 Calculando crecimiento TOTAL para 'TXN_DATA_2025' en PDB 'MY_APP_PDB' (Target: 'my_cdb_prod') para el último mes completo.
[DEBUG] 2025-07-31 10:22:42 TXN_DATA_2025: Used=85135.88MB, Allocated=89700MB, Last_Month_Growth=13935.13MB
[DEBUG] 2025-07-31 10:22:42 Free Space for TXN_DATA_2025: 4564.12MB
[DEBUG] 2025-07-31 10:22:42 Generando resumen mensual de crecimiento para 'TXN_DATA_2025' en PDB 'MY_APP_PDB' (Target: 'my_cdb_prod') para los últimos 12 meses.
[DEBUG] 2025-07-31 10:22:50 Enviando informe por correo...
CRITICAL: Informe de alerta de espacio en tablespaces enviado.
</code></pre>
<p>En este log, vemos cómo el script analiza cada tablespace. Por ejemplo:</p>
<ul>
<li><p><code>AUDIT_DATA</code>: Tuvo un crecimiento negativo (<code>-8MB</code>), lo que significa que el espacio libre actual (<code>519.62MB</code>) es más que suficiente. ¡No hay alerta!</p>
</li>
<li><p><code>SALES_DATA_2025</code>: Mostró un crecimiento significativo de <code>20241.5MB</code> en el último mes, pero solo tiene <code>6375.25MB</code> de espacio libre. Esto dispara una alerta, ya que el espacio libre es menor que el crecimiento mensual. El script te enviaría un comando <code>ALTER TABLESPACE "SALES_DATA_2025" RESIZE &lt;nuevo_tamaño&gt;M;</code> sugerido.</p>
</li>
<li><p><code>FILE_STORAGE</code>: Un patrón similar al anterior, con <code>6029MB</code> de espacio libre y un crecimiento de <code>20989MB</code>. También generaría una alerta.</p>
</li>
<li><p><code>TXN_DATA_2025</code>: Otro tablespace que necesita atención, con <code>4564.12MB</code> libres frente a un crecimiento de <code>13935.13MB</code>.</p>
</li>
</ul>
<h2 id="heading-el-correo-electronico-de-alerta">El Correo Electrónico de Alerta</h2>
<p>Cuando se detecta un tablespace que necesita atención, <strong>el script genera un correo electrónico con un resumen detallado</strong>. Así es como se vería un correo de alerta , similar al que recibirías en tu bandeja de entrada:</p>
<hr />
<p><strong>Asunto:</strong> 🚨 Alerta de espacio en Tablespaces - [Nombre de tu PDB/Base de Datos]</p>
<p><strong>Cuerpo del Correo:</strong></p>
<pre><code class="lang-plaintext">📈 Alerta de Espacio en Tablespaces - ¡Necesitan Ampliación Pronto! 📈

Estimados administradores de bases de datos,

Se ha detectado que los siguientes tablespaces en la base de datos '[Nombre de tu PDB/Base de Datos]' tienen menos espacio libre disponible que su crecimiento registrado en el último mes. Esto indica una necesidad inminente de ampliación para evitar posibles interrupciones en el servicio.

--- Tablespace: SALES_DATA_2025 ---
  &gt; Tamaño Utilizado: 120324.75 MB
  &gt; Tamaño Asignado (Reservado): 126700 MB
  &gt; Espacio Libre Disponible: 6375.25 MB
  &gt; Crecimiento último mes: 20241.5 MB
  &gt; Alerta: El espacio libre (6375.25 MB) es menor que el crecimiento del último mes (20241.5 MB).

------------------------------------------------------------
Resumen de crecimiento mensual para SALES_DATA_2025 (últimos 12 meses):

MES CRECIMIENTO_MB
------- --------------
2025-02        14223.5
2025-03        15952.5
2025-04        17006.5
2025-05       18854.38
2025-06        20241.5

COMANDO SUGERIDO (ejecutar en PDB de producción):
ALTER TABLESPACE "SALES_DATA_2025" RESIZE 140566M;

--- Tablespace: FILE_STORAGE ---
  &gt; Tamaño Utilizado: 110707 MB
  &gt; Tamaño Asignado (Reservado): 116736 MB
  &gt; Espacio Libre Disponible: 6029 MB
  &gt; Crecimiento último mes: 20989 MB
  &gt; Alerta: El espacio libre (6029 MB) es menor que el crecimiento del último mes (20989 MB).

------------------------------------------------------------
Resumen de crecimiento mensual para FILE_STORAGE (últimos 12 meses):

MES CRECIMIENTO_MB
------- --------------
2024-09          0
2024-10          0
2024-11          0
2024-12          0
2025-01          0
2025-02          0
2025-03        384
2025-04       8512
2025-05       20802.88
2025-06      21053

COMANDO SUGERIDO (ejecutar en PDB de producción):
ALTER TABLESPACE "FILE_STORAGE" RESIZE 131696M;

--- Tablespace: TXN_DATA_2025 ---
  &gt; Tamaño Utilizado: 85135.88 MB
  &gt; Tamaño Asignado (Reservado): 89700 MB
  &gt; Espacio Libre Disponible: 4564.12 MB
  &gt; Crecimiento último mes: 13935.13 MB
  &gt; Alerta: El espacio libre (4564.12 MB) es menor que el crecimiento del último mes (13935.13 MB).

------------------------------------------------------------
Resumen de crecimiento mensual para TXN_DATA_2025 (últimos 12 meses):

MES CRECIMIENTO_MB
------- --------------
2025-02       10391.25
2025-03       11487.31
2025-04       12062.13
2025-05       13312.31
2025-06       13935.13

COMANDO SUGERIDO (ejecutar en PDB de producción):
ALTER TABLESPACE "TXN_DATA_2025" RESIZE 99071M;


--- Resumen de Comandos ALTER TABLESPACE Generados (para ejecutar en PDB de producción) ---

ALTER TABLESPACE "SALES_DATA_2025_OFS" RESIZE 140566M;
ALTER TABLESPACE "FILE_STORAGE_OFS" RESIZE 131696M;
ALTER TABLESPACE "TXN_DATA_2025_OFS" RESIZE 99071M;

Por favor, revisen estos tablespaces y consideren aplicar las acciones sugeridas lo antes posible.

Atentamente,

El equipo de Monitorización de Bases de Datos
[Tu Empresa/Organización]
</code></pre>
<p><strong>¡Lleva la Gestión de DB al Siguiente Nivel!</strong></p>
<p>Este script no solo te ayuda a evitar interrupciones, sino que te permite planificar tus ampliaciones de tablespaces con antelación, basándote en datos reales y no en suposiciones.</p>
]]></content:encoded></item><item><title><![CDATA[DML Redirection y proxy user: un choque inesperado]]></title><description><![CDATA[Desde Oracle 19c, Active Data Guard permite ejecutar declaraciones DML (INSERT, UPDATE, DELETE) en una base de datos standby abierta en modo lectura (READ ONLY WITH APPLY), redirigiendo DML automáticamente a la primaria, activando el parámetro ADG_RE...]]></description><link>https://selectfromdual.com/dml-redirection-y-proxy-user-un-choque-inesperado</link><guid isPermaLink="true">https://selectfromdual.com/dml-redirection-y-proxy-user-un-choque-inesperado</guid><category><![CDATA[Oracle]]></category><category><![CDATA[dataguard]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Wed, 23 Jul 2025 19:31:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/IUY_3DvM__w/upload/c3ed45dd2d95ed0dc4a250e80ad1435d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Desde Oracle 19c, Active Data Guard permite ejecutar declaraciones DML (INSERT, UPDATE, DELETE) en una base de datos standby abierta en modo lectura (READ ONLY WITH APPLY), redirigiendo DML automáticamente a la primaria, activando el parámetro ADG_REDIRECT_DML</p>
<p>Sin embargo, en entornos donde se utilizan proxy users, he identificado un fallo poco documentado que provoca que la redirección del DML falle aunque esté configurado correctamente.</p>
<p><strong>Contexto: ¿qué es DML Redirect?</strong></p>
<p>Desde Oracle 19c, si activas:</p>
<pre><code class="lang-plaintext">ALTER SYSTEM SET ADG_REDIRECT_DML = TRUE SCOPE = BOTH;
</code></pre>
<p>y la standby está en modo <code>READ ONLY WITH APPLY</code>, cualquier DML que ejecutes en la standby se redirige automáticamente al primario. Luego el cambio se aplica en la standby vía redo logs.</p>
<p><strong>Cual es el problema?</strong></p>
<p>Cuando te conectas a la standby mediante un proxy user—por ejemplo, proxy_user[target_user]—y se ejecuta un UPDATE, aparece:</p>
<p>ORA-16397: statement redirection from Oracle Active Data Guard standby database to primary database failed</p>
<p>Pero si te conectas directamente como target_user, el mismo UPDATE funciona sin problemas, redirigiéndose correctamente a la primaria.</p>
<p>Esto sucede a pesar de tener:</p>
<p>ADG_REDIRECT_DML=TRUE en ambas bases (primaria y standby)</p>
<p><strong>Causa del comportamiento</strong></p>
<p>Oracle, en la doc (error ORA-16397), indica que la redirección falla si "el usuario actual y el usuario conectado no son iguales"</p>
<p>Al usar proxy users, la sesión incorpora ambas identidades (proxy + target), por lo que Oracle detecta una discrepancia entre el usuario registrado y el usuario del esquema, bloqueando la redirección DML que debería activar ADG_REDIRECT_DML.</p>
<p><strong>Recomendaciones prácticas</strong></p>
<p>Para operaciones DML que se redirigirán desde la standby, no utilices proxy users.</p>
<p>Conéctate directamente como usuario “al uso“:</p>
<p>sqlplus target_user/password@standby</p>
<p>Asegúrate de que:</p>
<p>ADG_REDIRECT_DML=TRUE.</p>
<p>La standby está en READ ONLY WITH APPLY y con real-time apply activo.</p>
<p>Si el uso de proxy user es imprescindible, redirige estas conexiones al primario o crea un método alternativo (DB link).</p>
<p>El parámetro ADG_REDIRECT_DML activa la capacidad de redirigir DML desde la standby hacia el primario en Active Data Guard, pero no puede operar cuando se usa un proxy user, ya que <strong>Oracle detecta una inconsisténcia de identidad y cancela el redireccionamiento</strong>.</p>
<h3 id="heading-que-dice-la-documentacion">¿Qué dice la documentación?</h3>
<p>Si buscamos ORA‑16397 puede deberse a varias causas:</p>
<ol>
<li><p>cadena de conexión al primario no establecida ←No es el caso</p>
</li>
<li><p>primario no disponible←No es el caso</p>
</li>
<li><p><strong>usuario actual ≠ usuario logado</strong></p>
</li>
<li><p>redirección DML no soportada para PL/SQL con binds←No es el caso</p>
</li>
<li><p>real-time apply no activo ←No es el caso</p>
</li>
</ol>
<p>El único punto que encaja con mi caso es “usuario actual y usuario logado no son el mismo”. Sin embargo, <strong>no hay ninguna referencia a proxy users</strong>, aunque encajan perfectamente en esa descripción. por lo que es recomendable evitar proxy users en sesiones que requieran redirección DML, usando conexiones directas en su lugar. Un matiz que no está documentado y puede generar confusión, pero que esta implementación ha sacado a la luz.</p>
]]></content:encoded></item><item><title><![CDATA[Cómo analizar las conexiones Oracle Listener por minuto y por servicio con un script bash]]></title><description><![CDATA[Como DBA, entender el comportamiento del listener de Oracle es clave para detectar posibles cuellos de botella, picos inesperados o problemas de conexión que afectan el rendimiento y la estabilidad de]]></description><link>https://selectfromdual.com/como-analizar-las-conexiones-oracle-listener-por-minuto-y-por-servicio-con-un-script-bash</link><guid isPermaLink="true">https://selectfromdual.com/como-analizar-las-conexiones-oracle-listener-por-minuto-y-por-servicio-con-un-script-bash</guid><category><![CDATA[acepro]]></category><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Fri, 20 Jun 2025 10:15:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/FHnnjk1Yj7Y/upload/944bb53aee7b9c01d62aaf401c68bd24.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Como DBA, entender el comportamiento del listener de Oracle es clave para detectar posibles cuellos de botella, picos inesperados o problemas de conexión que afectan el rendimiento y la estabilidad de las bases de datos. Una forma sencilla y eficaz de obtener esta información es analizando el archivo listener.log.</p>
<p>Hoy quiero compartir un script bash que he desarrollado para procesar esos logs, obtener estadísticas de conexiones por minuto y desglosarlas por servicio. Te explico cómo funciona y por qué puede ser una herramienta útil en tu día a día.</p>
<p><strong>¿Qué hace este script?</strong></p>
<ul>
<li><p>Lee uno o varios archivos listener.log de Oracle, incluyendo posibles rotaciones.</p>
</li>
<li><p>Filtra las líneas correspondientes a una fecha y rango horario definidos (ejemplo: entre las 08:00 y las 12:00 de un día).</p>
</li>
<li><p>Extrae la hora exacta (hasta minuto) y el nombre del servicio (SERVICE_NAME) usado en cada conexión establecida.</p>
</li>
</ul>
<p>Calcula y muestra:</p>
<ul>
<li><p>El número total de conexiones por minuto.</p>
</li>
<li><p>El número total de conexiones por servicio.</p>
</li>
<li><p>Presenta el resultado ordenado cronológicamente y alfabéticamente para facilitar su interpretación.</p>
</li>
</ul>
<p>¿Cómo funciona el script?</p>
<p><strong>Variables configurables:</strong></p>
<ul>
<li><p>LISTENER_LOG_PATH y LISTENER_LOG_PATTERN para definir dónde están los logs y qué archivos leer.</p>
</li>
<li><p>FECHA, HORA_INICIO y HORA_FIN para definir el intervalo temporal a analizar.</p>
</li>
</ul>
<p><strong>¿Qué aporta a un DBA?</strong></p>
<ul>
<li><p>Visibilidad precisa: Podrás identificar en qué minutos se producen picos de conexión y si estos corresponden a servicios específicos, permitiendo detectar patrones anómalos.</p>
</li>
<li><p>Diagnóstico rápido: Al saber qué servicios están más activos, puedes priorizar análisis o acciones sobre aplicaciones concretas.</p>
</li>
<li><p>Soporte a troubleshooting: Ayuda a relacionar problemas de rendimiento o caídas con la carga real sobre el listener.</p>
</li>
<li><p>Automatización sencilla: El script se puede integrar en tareas cron para generar informes periódicos sin intervención manual.</p>
</li>
</ul>
<pre><code class="language-plaintext">#!/bin/bash

# Ruta a listener.log (puedes usar comodines)
LISTENER_LOG_PATH="/path_a_ruta_listener/listener/trace"
LISTENER_LOG_PATTERN="listener*.log"

# Rango horario a analizar
FECHA="20-JUN-2025"
HORA_INICIO="08:00"
HORA_FIN="12:00"

echo "Conexiones por minuto en listener.log entre \(HORA_INICIO y \)HORA_FIN del $FECHA"
echo

# Leer y procesar los logs
cat \(LISTENER_LOG_PATH/\)LISTENER_LOG_PATTERN 2&gt;/dev/null | grep "$FECHA" | grep "CONNECT_DATA" | \
awk -v start="\(HORA_INICIO" -v end="\)HORA_FIN" '
{
  fecha = $1
  hora = $2
  minuto = substr(hora, 1, 5)

  if (minuto &gt;= start &amp;&amp; minuto &lt;= end) {
    conexiones[minuto]++

    match($0, /SERVICE_NAME=[^)]+/, svc)
    if (svc[0] != "") {
      split(svc[0], tmp, "=")
      servicio = tmp[2]
      servicio_conexiones[servicio]++
    }
  }
}

END {
  print "Conexiones por minuto:"
  PROCINFO["sorted_in"] = "@ind_str_asc"
  for (t in conexiones) {
    printf "  %s → %d conexiones\n", t, conexiones[t]
  }

  print "\n Conexiones por SERVICE_NAME:"
  PROCINFO["sorted_in"] = "@ind_str_asc"
  for (s in servicio_conexiones) {
    printf "  %s → %d conexiones\n", s, servicio_conexiones[s]
  }
}'
</code></pre>
<p>Ejemplo de salida:</p>
<pre><code class="language-plaintext">[xxxxxxxx@xxx-xxx-xxx ~]$ ./analisis_listener.sh
Conexiones por minuto en listener.log entre 08:00 y 12:00 del 20-JUN-2025

Conexiones por minuto:
  08:00 → 1274 conexiones
  08:01 → 1319 conexiones
  08:02 → 1326 conexiones
  08:03 → 1335 conexiones
  08:04 → 1254 conexiones
  08:05 → 1195 conexiones
  08:06 → 1196 conexiones
  08:07 → 1193 conexiones
  08:08 → 1194 conexiones
  08:09 → 1217 conexiones
  08:10 → 1192 conexiones
  08:11 → 1194 conexiones
  08:12 → 1211 conexiones
  08:13 → 1187 conexiones
  08:14 → 1199 conexiones
  08:15 → 1178 conexiones
  08:16 → 1209 conexiones
  08:17 → 1178 conexiones
  08:18 → 1177 conexiones
  08:19 → 1192 conexiones
  08:20 → 1213 conexiones
  08:21 → 1194 conexiones
  08:22 → 1177 conexiones
  08:23 → 1184 conexiones
  08:24 → 1183 conexiones
  08:25 → 1192 conexiones
  08:26 → 1220 conexiones
  08:27 → 1200 conexiones
  08:28 → 1220 conexiones
  08:29 → 1184 conexiones
  08:30 → 1196 conexiones
  08:31 → 1214 conexiones
  08:32 → 1233 conexiones
  08:33 → 1201 conexiones
  08:34 → 1223 conexiones
  08:35 → 1209 conexiones
  08:36 → 1219 conexiones
  08:37 → 1212 conexiones
  08:38 → 1194 conexiones
  08:39 → 1196 conexiones
  08:40 → 1219 conexiones
  08:41 → 1227 conexiones
  08:42 → 1207 conexiones
  08:43 → 1194 conexiones
  08:44 → 1204 conexiones
  08:45 → 1236 conexiones
  08:46 → 1212 conexiones
  08:47 → 1209 conexiones
  08:48 → 1215 conexiones
  08:49 → 1213 conexiones
  08:50 → 1183 conexiones
  08:51 → 1165 conexiones
  08:52 → 1204 conexiones
  08:53 → 1224 conexiones
  08:54 → 1182 conexiones
  08:55 → 1180 conexiones
  08:56 → 1187 conexiones
  08:57 → 1218 conexiones
  08:58 → 1214 conexiones
  08:59 → 1211 conexiones
  09:00 → 1240 conexiones
  09:01 → 1253 conexiones
  09:02 → 1239 conexiones
  09:03 → 1256 conexiones
  09:04 → 1225 conexiones
  09:05 → 1253 conexiones
  09:06 → 1230 conexiones
  09:07 → 1236 conexiones
  09:08 → 1200 conexiones
  09:09 → 1210 conexiones
  09:10 → 1240 conexiones
  09:11 → 1235 conexiones
  09:12 → 1209 conexiones
  09:13 → 1221 conexiones
  09:14 → 1195 conexiones
  09:15 → 1194 conexiones
  09:16 → 1221 conexiones
  09:17 → 1219 conexiones
  09:18 → 1178 conexiones
  09:19 → 1189 conexiones
  09:20 → 1212 conexiones
  09:21 → 1201 conexiones
  09:22 → 1228 conexiones
  09:23 → 1230 conexiones
  09:24 → 1238 conexiones
  09:25 → 1229 conexiones
  09:26 → 1219 conexiones
  09:27 → 1208 conexiones
  09:28 → 1240 conexiones
  09:29 → 1222 conexiones
  09:30 → 1222 conexiones
  09:31 → 1215 conexiones
  09:32 → 1219 conexiones
  09:33 → 1224 conexiones
  09:34 → 1213 conexiones
  09:35 → 1223 conexiones
  09:36 → 1208 conexiones
  09:37 → 1187 conexiones
  09:38 → 1236 conexiones
  09:39 → 1205 conexiones
  09:40 → 1202 conexiones
  09:41 → 1187 conexiones
  09:42 → 1214 conexiones
  09:43 → 1204 conexiones
  09:44 → 1194 conexiones
  09:45 → 1198 conexiones
  09:46 → 1209 conexiones
  09:47 → 1240 conexiones
  09:48 → 1221 conexiones
  09:49 → 1212 conexiones
  09:50 → 1237 conexiones
  09:51 → 1223 conexiones
  09:52 → 1221 conexiones
  09:53 → 1222 conexiones
  09:54 → 1238 conexiones
  09:55 → 1227 conexiones
  09:56 → 1234 conexiones
  09:57 → 1238 conexiones
  09:58 → 1221 conexiones
  09:59 → 1233 conexiones
  10:00 → 1279 conexiones
  10:01 → 1227 conexiones
  10:02 → 1236 conexiones
  10:03 → 1195 conexiones
  10:04 → 1203 conexiones
  10:05 → 1218 conexiones
  10:06 → 1186 conexiones
  10:07 → 1221 conexiones
  10:08 → 1232 conexiones
  10:09 → 1176 conexiones
  10:10 → 1178 conexiones
  10:11 → 1222 conexiones
  10:12 → 1206 conexiones
  10:13 → 1218 conexiones
  10:14 → 1205 conexiones
  10:15 → 1218 conexiones
  10:16 → 1251 conexiones
  10:17 → 1247 conexiones
  10:18 → 1288 conexiones
  10:19 → 1263 conexiones
  10:20 → 1290 conexiones
  10:21 → 1223 conexiones
  10:22 → 1241 conexiones
  10:23 → 1208 conexiones
  10:24 → 1220 conexiones
  10:25 → 1267 conexiones
  10:26 → 1239 conexiones
  10:27 → 1207 conexiones
  10:28 → 1231 conexiones
  10:29 → 1245 conexiones
  10:30 → 1247 conexiones
  10:31 → 1197 conexiones
  10:32 → 1205 conexiones
  10:33 → 1207 conexiones
  10:34 → 1231 conexiones
  10:35 → 1197 conexiones
  10:36 → 1218 conexiones
  10:37 → 1234 conexiones
  10:38 → 1184 conexiones
  10:39 → 1222 conexiones
  10:40 → 1237 conexiones
  10:41 → 1221 conexiones
  10:42 → 1232 conexiones
  10:43 → 1243 conexiones
  10:44 → 1233 conexiones
  10:45 → 1268 conexiones
  10:46 → 1204 conexiones
  10:47 → 1225 conexiones
  10:48 → 1257 conexiones
  10:49 → 1235 conexiones
  10:50 → 1228 conexiones
  10:51 → 1217 conexiones
  10:52 → 1258 conexiones
  10:53 → 1246 conexiones
  10:54 → 1243 conexiones
  10:55 → 1229 conexiones
  10:56 → 1222 conexiones
  10:57 → 1220 conexiones
  10:58 → 1197 conexiones
  10:59 → 1220 conexiones
  11:00 → 1220 conexiones
  11:01 → 1216 conexiones
  11:02 → 1246 conexiones
  11:03 → 1254 conexiones
  11:04 → 1231 conexiones
  11:05 → 1226 conexiones
  11:06 → 1238 conexiones
  11:07 → 1219 conexiones
  11:08 → 1211 conexiones
  11:09 → 1231 conexiones
  11:10 → 1227 conexiones
  11:11 → 1242 conexiones
  11:12 → 1210 conexiones
  11:13 → 1225 conexiones
  11:14 → 1245 conexiones
  11:15 → 1231 conexiones
  11:16 → 1246 conexiones
  11:17 → 1253 conexiones
  11:18 → 1253 conexiones
  11:19 → 1228 conexiones
  11:20 → 1248 conexiones
  11:21 → 1225 conexiones
  11:22 → 1202 conexiones
  11:23 → 1198 conexiones
  11:24 → 1207 conexiones
  11:25 → 1244 conexiones
  11:26 → 1222 conexiones
  11:27 → 1201 conexiones
  11:28 → 1229 conexiones
  11:29 → 1223 conexiones
  11:30 → 1240 conexiones
  11:31 → 1212 conexiones
  11:32 → 1230 conexiones
  11:33 → 1248 conexiones
  11:34 → 1237 conexiones
  11:35 → 1226 conexiones
  11:36 → 1248 conexiones
  11:37 → 1232 conexiones
  11:38 → 1238 conexiones
  11:39 → 1245 conexiones
  11:40 → 1250 conexiones
  11:41 → 1261 conexiones
  11:42 → 1252 conexiones
  11:43 → 1252 conexiones
  11:44 → 1228 conexiones
  11:45 → 1241 conexiones
  11:46 → 1213 conexiones
  11:47 → 1210 conexiones
  11:48 → 1202 conexiones
  11:49 → 1238 conexiones
  11:50 → 1232 conexiones
  11:51 → 1235 conexiones
  11:52 → 1228 conexiones
  11:53 → 1222 conexiones
  11:54 → 1201 conexiones
  11:55 → 1261 conexiones
  11:56 → 1218 conexiones
  11:57 → 1247 conexiones
  11:58 → 1223 conexiones
  11:59 → 702 conexiones

Conexiones por SERVICE_NAME:
  servicio1.xxxxx.xxx → 18104 conexiones
  cdbxxx.xxxxx.xxx → 309 conexiones
  servicio2.xxxxxx.xxx → 715 conexiones
  servicio3.xxxxxx.xxx → 1847 conexiones
  servicio4.xxxxx.xxx → 55783 conexiones
  pdb_xxx.xxxxx.xxx → 6 conexiones
  servicio5.xxxxx.xxx → 15 conexiones
  servicio6.xxxxx.xxx → 215613 conexiones
</code></pre>
<p><strong>Mejoras y personalizaciones</strong></p>
<ul>
<li><p>Añadir un parámetro para filtrar por usuario o IP origen.</p>
</li>
<li><p>Generar gráficos con herramientas como gnuplot o exportar a CSV.</p>
</li>
<li><p><strong>Integrar alertas si se detectan picos fuera de lo normal.</strong></p>
</li>
</ul>
<p>Dando rienda suelta a la imaginación y necesidades puede ser tu gran aliado.</p>
]]></content:encoded></item><item><title><![CDATA[Excluir tablas en expdp sin perder la metadata: la opción inteligente en Oracle Data Pump]]></title><description><![CDATA[Cuando realizamos un export full en Oracle con expdp, suele surgir la necesidad de excluir una o varias tablas, ya sea porque son demasiado grandes, temporales, o simplemente no son necesarias en el entorno de destino.
La solución más inmediata que m...]]></description><link>https://selectfromdual.com/excluir-tablas-en-expdp-sin-perder-la-metadata-la-opcion-inteligente-en-oracle-data-pump</link><guid isPermaLink="true">https://selectfromdual.com/excluir-tablas-en-expdp-sin-perder-la-metadata-la-opcion-inteligente-en-oracle-data-pump</guid><category><![CDATA[Oracle]]></category><category><![CDATA[expdp]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Wed, 30 Apr 2025 09:48:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/NtxkQvKikWs/upload/187828609d9978d393dc8929acd6228f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Cuando realizamos un export full en Oracle con expdp, suele surgir la necesidad de excluir una o varias tablas, ya sea porque son demasiado grandes, temporales, o simplemente no son necesarias en el entorno de destino.</p>
<p>La solución más inmediata que muchos aplican es usar la cláusula EXCLUDE=TABLE:"IN ('TABLA_X')" para omitirlas completamente. Pero, <strong>¿es realmente la mejor opción?</strong></p>
<p>En muchos casos, no lo es. Excluir una tabla completamente con EXCLUDE=TABLE implica que ni los datos ni la estructura de la tabla (metadata) estarán disponibles en el dump. Y eso puede traer problemas más adelante, sobre todo si se espera que esa tabla exista en el entorno de destino por dependencias o esquemas consistentes.</p>
<p>La alternativa: <strong>exportar solo la metadata</strong></p>
<p>En lugar de excluir por completo una tabla, podemos indicarle a expdp que exporte la estructura pero no los datos. ¿Cómo? Utilizando la cláusula QUERY con una condición que nunca se cumpla, como WHERE 1 &lt;&gt; 1.</p>
<p>Ejemplo práctico</p>
<p>Supón que tenemos un export full como este:</p>
<pre><code class="lang-plaintext">cat expdp_parameters.par

exclude=statistics 
LOGTIME=ALL 
METRICS=Y 
flashback_time=systimestamp 
full=y 
parallel=4 
job_name=EXP_FULL_XXXX 
cluster=N 
QUERY=XXXX.TABLA_1:"where 1 &lt;&gt; 1" 
QUERY=XXXX.TABLA_2:"where 1 &lt;&gt; 1" 
QUERY=XXXX.TABLA_3:"where 1 &lt;&gt; 1"
</code></pre>
<p>En este ejemplo estamos diciendo:</p>
<p>"Quiero exportar toda la base de datos (full=y), pero para las tablas TABLA_1, TABLA_2 y TABLA_3, solo exporta la metadata.</p>
<p>Los datos no se exportarán porque la cláusula WHERE 1 &lt;&gt; 1 nunca se cumple."</p>
<p><strong>¿Qué ventajas tiene esto?</strong></p>
<ul>
<li><p>Preservas la estructura de las tablas: columnas, tipos de datos, constraints, etc.</p>
</li>
<li><p>Mantienes integridad del esquema, especialmente si otras tablas, vistas o procedimientos dependen de la existencia de esas tablas.</p>
</li>
<li><p>Evitas errores en la importación (impdp) por objetos faltantes.</p>
</li>
<li><p>Reduces el tamaño del dump al no incluir datos innecesarios.</p>
</li>
<li><p>Puedes recrear esas tablas vacías automáticamente en el entorno destino, lo cual puede ser muy útil en pruebas, clones o entornos de desarrollo.</p>
</li>
</ul>
<p><strong>¿Cuándo conviene excluir completamente con EXCLUDE=TABLE?</strong></p>
<p>Solo si estás completamente seguro de que:</p>
<ul>
<li><p>No necesitas la estructura de la tabla en el entorno destino.</p>
</li>
<li><p>No hay dependencias lógicas ni referenciales hacia esa tabla.</p>
</li>
<li><p>Quieres que no exista en absoluto tras el impdp.</p>
</li>
</ul>
<p>De lo contrario, usar QUERY=... WHERE 1&lt;&gt;1 es la mejor práctica.</p>
<p><strong>Conclusión</strong></p>
<p>A veces, las soluciones más simples no son las más adecuadas. Si necesitas excluir datos de una tabla en expdp pero deseas mantener su definición, usa la técnica de QUERY=... WHERE 1&lt;&gt;1. Evitarás muchos problemas de consistencia y podrás seguir trabajando con estructuras completas sin cargar datos innecesarios.</p>
]]></content:encoded></item><item><title><![CDATA[Parse errors por OMC_ASH_VIEWER en Oracle]]></title><description><![CDATA[Introducción
En algunos entornos Oracle gestionados desde Enterprise Manager Cloud Control (OEM), puede aparecer un error curioso y molesto en el alert log de la base de datos:
WARNING: too many parse errors, count=4390 SQL hash=0xb6a91e8f PARSE ERRO...]]></description><link>https://selectfromdual.com/parse-errors-por-omcashviewer-en-oracle</link><guid isPermaLink="true">https://selectfromdual.com/parse-errors-por-omcashviewer-en-oracle</guid><category><![CDATA[oem]]></category><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Carla Muñoz López]]></dc:creator><pubDate>Sat, 19 Apr 2025 19:31:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/npxXWgQ33ZQ/upload/d1496456c5afbfb6011522a6a38b379a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Introducción</strong></p>
<p>En algunos entornos Oracle gestionados desde Enterprise Manager Cloud Control (OEM), puede aparecer un error curioso y molesto en el alert log de la base de datos:</p>
<p><code>WARNING: too many parse errors, count=4390 SQL hash=0xb6a91e8f PARSE ERROR: ospid=3152219, error=904: Additional information: ... sqlid=fa9vmfqvak7ng Current username=SYS</code></p>
<p>Este tipo de error suele estar relacionado con llamadas automáticas que hace OEM a paquetes PL/SQL internos. En este caso concreto, se trataba del paquete OMC_ASH_VIEWER, que no estaba correctamente desplegado en la base de datos. Ups!!</p>
<p><strong>Diagnóstico</strong></p>
<p>Para confirmar que el error estaba relacionado con el paquete OMC_ASH_VIEWER ejecutamos:</p>
<pre><code class="lang-plaintext">SELECT dbsnmp.OMC_ASH_VIEWER.getVersion() FROM dual;
ORA-00904: "DBSNMP"."OMC_ASH_VIEWER"."GETVERSION": invalid identifier
</code></pre>
<p><strong>¿Qué es OMC_ASH_VIEWER?</strong></p>
<p>Es un paquete PL/SQL creado por OEM dentro del esquema DBSNMP, que se utiliza para recolectar datos de:</p>
<ul>
<li><p>Active Session History (ASH)</p>
</li>
<li><p>AWR y CPU</p>
</li>
<li><p>Sesiones bloqueadas</p>
</li>
<li><p>Información de rendimiento en tiempo real o histórico</p>
</li>
</ul>
<p>Este paquete forma parte del plugin oracle.sysman.db.oms.plugin, y se crea automáticamente mediante el job de OEM llamado:</p>
<p><em>Deploy Database Management PL/SQL Packages</em></p>
<p><strong>Solución</strong></p>
<p>Oracle ofrece una solución oficial (Doc ID 2769758.1) para este problema:</p>
<ol>
<li>Ejecutar el job desde OEM:</li>
</ol>
<p>Desde la consola de Enterprise Manager:</p>
<ul>
<li><p>Ir a Enterprise → Job → Activity</p>
</li>
<li><p>Hacer clic en Create Job</p>
</li>
<li><p>Buscar y seleccionar el tipo de job: Deploy Database Management PL/SQL Packages</p>
</li>
<li><p>Seleccionar el target afectado (base de datos o PDB)</p>
</li>
<li><p>Proporcionar credenciales SYSDBA válidas</p>
</li>
<li><p>Ejecutar inmediatamente (o programar)</p>
</li>
</ul>
<ol start="2">
<li>Verificación</li>
</ol>
<p>Una vez desplegado correctamente, la siguiente consulta debería funcionar:</p>
<pre><code class="lang-plaintext">
SYS@CDB$ROOT&gt; SELECT dbsnmp.OMC_ASH_VIEWER.getVersion() FROM dual;

   DBSNMP.OMC_ASH_VIEWER.GETVERSION()
_____________________________________
60
</code></pre>
<p>Y el alert log ya no mostrará errores de parse.</p>
<p><strong>¿Qué hace internamente este job?</strong></p>
<p>Este job ejecuta scripts como:</p>
<ul>
<li><p>omc_ashv_pkg.sql y omc_ashv_pkg_body.sql: Definen el paquete OMC_ASH_VIEWER en DBSNMP</p>
</li>
<li><p>priv_grant_omc_ash.sql: Asigna los permisos necesarios al paquete</p>
</li>
</ul>
<p>Esto permite a OEM acceder a vistas como GV$SESSION, DBA_HIST_*, V$SQL, etc., y generar informes y gráficos en el panel de rendimiento.</p>
<p><strong>Conclusión</strong></p>
<p>Este tipo de error puede pasar desapercibido si no revisamos el alert log con frecuencia, pero puede generar carga innecesaria y confusión.</p>
<p>Afortunadamente, la solución es sencilla si sabemos lo que buscamos: verificar y desplegar correctamente los paquetes PL/SQL de gestión desde OEM.</p>
<p>¿Te ha pasado algo similar en tus entornos? ¡Cuéntamelo en los comentarios!</p>
]]></content:encoded></item></channel></rss>