Mostrando entradas con la etiqueta Diagnóstico. Mostrar todas las entradas
Mostrando entradas con la etiqueta Diagnóstico. Mostrar todas las entradas

viernes, 14 de enero de 2011

Análisis de consumo de espacio REDO global, por cada sesión y por cada sentencia

El consumo de espacio de redo (redo consumption) es algo inevitable, aunque es posible minimizarlo en ciertos casos y con ciertas operaciones, no se puede cancelar por completo. El redo es necesario para asegurar que ante una caida imprevista de la base, los datos en los bloques modificados, commiteados y todavia no persistidos en disco (dirty blocks), puedan recuperarse al levantar nuevamente la base con un proceso automatico denominado "rolling forward".

Si la base esta en modo archivelog, cada redo log se copia aparte para que no se sobreescriba y asi permitir, cuando se lo requiera, por ejemplo, poder realizar backup con la base online, ir a un estado anterior de la base, recuperar una base usando el ultimo backup full y aplicando los archives, etc.

Si el consumo de redo es importante, la I/O se va a ver comprometida y podría afectar el rendimiento general de la base. Recordar que la escritura en redo es secuencial, distinta a la escritura en datafiles. Siempre alojar los redo sobre raid 1 o similares y separados de los datafiles. Además, tambien tener en cuenta que con cada commit se debe escribir en redo en forma sincronica. Esto significa serializar, y por lo tanto debe ser lo mas eficiente posible.

Para que puedan medir el consumo de redo, y en consecuencia cantidad de archives generados, en sus bases, les paso una serie de metodos y consultas que uso habitualmente. Las consultas permiten obtener lo siguiente:

  • Consumo de Redo de la ultima hora
  • Consumo de Redo por sesión
  • Consumo de Redo por Consultas (*)

(*) El consumo por sentencia no se puede obtener en forma directa, entonces armé un procedimiento para ir guardando espacio por sqlid. Esta forma puede no ser muy precisa en ciertas ocasiones. Tiene que usarse teniendo ciertos requisitos y consideraciones. Puede ser muy util para testear el consumo de redo de una aplicación antes de la puesta en producción.

Para obtener consumo de redo global de la ultima hora (desde el ultimo snapshot de AWR) (redo consumption by DB)

Ejecutar la siguiente consulta que da el consumo de la base de datos desde el ultimo snapshot, es decir desde la ultima hora exacta. Es decir si lo ejecutamos a las 16:30, nos dará el consumo desde las 16hs para toda la base


select round((t1.value-t2.value)/1024/1024,2) "Consumo_Redo(MB)"
from
(select value from v$sysstat where name = 'redo size') t1,
(select value from dba_hist_sysstat
where stat_name = 'redo size'
and snap_id = (select max(snap_id) from dba_hist_sysstat)) t2


Para obtener consumo de redo por sesión (redo consumption by session)

Ejecutar la siguiente consulta, que da el consumo de redo por sesión. Considerar que el acumulado es desde que la sesión se abre

select se.INST_ID,
se.SID,
se.USERNAME,
se.OSUSER,
se.TERMINAL,
se.PROGRAM,
round(ss.value/1024/1024,2) "Redo Size(Mb)"
from gv$session se,
gv$sesstat ss,
v$statname sn
where se.SID = ss.SID
and ss.STATISTIC# = sn.STATISTIC#
and sn.NAME = 'redo size'
and username is not null
order by "Redo Size(Mb)" desc

Para obtener consumo de redo por sentencia (redo consumption by sqlid/sentence)

Primero realizar el setup copiado a continuación (copiar el contenido en un archivo .sql y ejecutarlo desde sqlplus en el usuario elegido como repositorio) :


Rem
Rem setup_redo_usage.sql (Redo Usage by Sentence/SQLID)
Rem
Rem NOMBRE
Rem setup_redo_usage.sql
Rem
Rem DESCRIPCION
Rem Configura la programacion de un job para recolectar
Rem información de uso de espacio de redo por sqlid.
Rem
Rem NOTAS - REQUISITOS DE INSTALACION
Rem El usuario que ejecute el script debera tener quota
Rem suficiente sobre un tablespace auxiliar (ej:TOOLS)
Rem para poder almacenar la informacion de monitoreo generada
Rem cada 5".
Rem Es necesario otorgar privilegios de SELECT sobre las
Rem vistas dinamicas:
Rem
Rem Conectado con sys hacer:
Rem
Rem grant select on gv_$sesstat to ;
Rem grant select on v_$statname to ;
Rem grant select on gv_$session to ;
Rem grant select on gv_$sqlarea to ;
Rem
Rem
Rem MODIFICADO (DD/MM/YY)
Rem
Rem
Rem Pablo A. Rovedo 06/01/11 -- Creado v 1.0
Rem

-- Borra la tabla TBL_REDO_USAGE si existe

drop table tmp$redo_usage
/

-- Crea la tabla de repositorio TBL_REDO_USAGE

create table tmp$redo_usage
(
USERNAME VARCHAR2(30),
OSUSER VARCHAR2(30),
TERMINAL VARCHAR2(30),
PROGRAM VARCHAR2(48),
SQL_ID VARCHAR2(13),
SQL_FULLTEXT CLOB,
REDO_SIZE NUMBER,
SNAP DATE,
INST_ID NUMBER(1)
)
pctfree 0
nologging
/


-- Crea el procedimiento que recolecta la información de Alocación de
-- espacio de redo

create or replace procedure p_get_redo_usage
is
begin
insert /*+ append */ into tmp$redo_usage
select s.USERNAME,
s.OSUSER,
s.terminal,
s.PROGRAM,
s.SQL_ID,
sa.SQL_FULLTEXT,
round(ss.VALUE/1024/1024) redo_size,
sysdate,
s.inst_id
from gv$sesstat ss,
v$statname sn,
gv$session s,
gv$sqlarea sa
where s.sid = ss.sid
and s.inst_id = ss.inst_id
and sn.STATISTIC# = ss.STATISTIC#
and s.sql_id = sa.SQL_ID
and s.inst_id = sa.inst_id
and sn.name = 'redo size'
and s.username not in ('SYS','SYSTEM');
commit;
end;
/

-- Borra el job si ya existe

begin
dbms_scheduler.drop_job(job_name => 'J_SAVE_REDO_USAGE',
force => true);
end;
/

-- Crea un nuevo job

begin
dbms_scheduler.create_job(
job_name => 'J_SAVE_REDO_USAGE'
,job_type => 'PLSQL_BLOCK'
,job_action => 'begin p_get_redo_usage; end;'
,start_date => sysdate
,end_date => sysdate+1/24 -- Recolecta durante 1 hora
,repeat_interval => 'FREQ=SECONDLY;BYSECOND=0,5,10,15,20,25,30,35,40,45,50,55'
,enabled => TRUE
,comments => 'Almacena Informacion de Alocación de espacio de redo');
end;
/

Luego de terminada la recolección (en el script se definió un intervalo
de monitoreo de 1 hora) se puede ejecutar la siguiente consulta para ver
los resultados:

select sql_id,sum(redo_size) "redo_size(Mb)"
from (select unique sql_id,
redo_size-lead(redo_size) over (partition by sql_id order by snap desc) redo_size
from tmp$redo_usage)
where redo_size is not null
group by sql_id
order by "redo_size(Mb)" desc

miércoles, 15 de diciembre de 2010

Cuando una consulta utiliza un indice, pero no el mejor indice posible

Muchas veces me preguntaron por que una consulta no responde en un tiempo adecuado cuando el plan de ejecución muestra que se esta utilizando un indice. La respuesta en ciertos casos es muy sencilla y se debea a que el indice no es lo mas selectivo posible, no filtra todo lo que podria filtrar. Decir que una consulta usa un plan que accede por un indice no es suficiente para asegurar que se haya encontrado el camino mas eficiente. Para mostrarles un poco de que estoy hablando voy a armar un caso, un tanto trivial pero no por eso menos ilustrativo, para que se entienda la idea.

Voy a crear una tabla T con 3 columnas. La columna COL1 va a tener 1000 valores distintos y la columna COL2 va a tener 10 valores posibles. Además agrego la columna COL3 de relleno



create table t
as
select mod(rownum,1000) col1,
mod(rownum,100000) col2,
dbms_random.string('a',50) col3
from dual
connect by rownum <= 1000000


Una vez creada la tabla voy a crear un indice por COL1 y recolecto las estadisticas para la tabla y para el indice:

create index t_idx on t (col1)

begin
dbms_stats.gather_table_stats(ownname => user,tabname => 'T',cascade => true);
end;


Ejecuto la siguiente consulta y extraigo el plan usando DBMS_XPLAN.DISPLAY_CURSOR para que el plan sea mas detallado (en una nota futura voy a usar el mismo método para mostrar como ver como se "confunde" el optimizador cuando no se dispone de las estadisticas adecuadas):


select /*+ gather_plan_statistics */ * from t
where col1 = 9
and col2 = 9

select * from table (dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
SQL_ID 25p2md7bszhj6, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ * from t where col1 = 9 and col2
= 9

Plan hash value: 1020776977

-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 1006 |
|* 1 | TABLE ACCESS BY INDEX ROWID| T | 1 | 1 | 10 |00:00:00.01 | 1006 |
|* 2 | INDEX RANGE SCAN | T_IDX | 1 | 1000 | 1000 |00:00:00.01 | 6 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("COL2"=9)
2 - access("COL1"=9)

En el paso 2 del plan se observa que la estimación fue buena (E-Rows=A-Rows) pero la cantidad filtrada fue 1ooo filas, cuando realmente se deberian haber filtrado 10, como se ve en el paso 1 (A-Rows)

(Aclaración: A-Rows es la cantidad de filas reales y E-Rows es la cantidad de filas estimada)

Ahora voy a eliminar el indice T_IDX y voy a crear otro con el mismo nombre pero indexando por COL1 y COL2. Veamos el nuevo plan:

drop index t_idx

create index t_idx on t (col1,col2)

select /*+ gather_plan_statistics 2 */ * from t
where col1 = 9
and col2 = 9

select * from table (dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
SQL_ID 25p2md7bszhj6, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ * from t where col1 = 9 and col2
= 9

Plan hash value: 1020776977

-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 14 |
| 1 | TABLE ACCESS BY INDEX ROWID| T | 1 | 10 | 10 |00:00:00.01 | 14 |
|* 2 | INDEX RANGE SCAN | T_IDX | 1 | 10 | 10 |00:00:00.01 | 4 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("COL1"=9 AND "COL2"=9)

Ahora el paso 2 retorna al paso 1 solo 10 filas, que es la cantidad de filas total retornada por la consulta. Observar que la cantidad de buffers utilizado es bastante inferior al caso anterior.
Esto demuestra que el nuevo indice fue 100 veces mas selectivo y por lo tanto se necesitaron menos recursos, en consecuencia menos tiempo de procesamiento, para obtener la misma salida.

La moraleja es que nunca hay que conformarse con solo verificar que la sentencia accede por un indice y profundizar en el análisis sobre el esquema actual de indexación comprobando si es el mejor indice posible o si se puede buscar alguna otra combinación mas eficiente.

viernes, 30 de abril de 2010

Herramienta para diagnosticar problemas de performance (Oracle Performance Viewer Freeware)

(Ahora podes bajar la ultima versión del utilitario desde http://www.oramdq.com/oracle-performance-viewer/)


Hace ya varios años que trabajo con bases de datos Oracle y en todo ese tiempo fui acumulando scripts que me facilitaron diversas tareas de administración, mantenimiento, monitoreo, etc. Ya que ultimamente me vengo dedicando a temas de performance, me di cuenta que en ese tipo de actividad es importante analizar la mayor cantidad de información en el menor tiempo posible, recordemos que Oracle es una base altamente instrumentada y que versión tras versión se van agregando mas métricas (Oracle Wait Interface). A partir de 10g tenemos el repositorio AWR que almacena historial detallado de la actividad de la base.

El AWR resulta de mucha utilidad como baseline o punto de comparación cuando nos encontramos con una base que esta con problemas de rendimiento, detectados o bien por la activación de alarmas o peor aún, cuando el usuario final persive la demora y realiza el reclamo. Cualquiera que trabaje como DBA de bases productivas, ni hablar si son muy criticas, tendrá que responder rapidamente a su superior evaluando el escenario, diagnosticando y proponiendo o activando cursos de acción en forma inmediata. A veces los problemas no se detectan con facilidad y empezar a correr scripts por separado puede resultar un tanto lento.

Para facilitarme la tarea, programé un pequeño utilitario en C# donde agregué varias de las consultas que utilizo diariamente, sumado a todo el poder gráfico que me permite entre otras cosas analizar la historia, filtrar convenientemente, exportar a excel la grilla, explotar la información con doble click sobre la celda, etc. Abajo copié algunas pantallas para mostrarles como esta pensada la aplicación y obviamente me interesa compartirla en forma gratuita con quien le interesa y asi poder mejorarla.


La pantalla principal es una MDI con 4 paneles, el de arriba a la izquierda tiene una estructura de árbol con todos los reportes disponibles hasta el momento, clasificados según cierto criterio. el panel de abajo a la izquierda tiene un resumen de la base de datos donde esta conectada la app. el panel de arriba a la derecha tiene la grilla con los resultados, y el panel de abajo a la derecha tiene la consulta que se ejecutó para llenar la grilla.



La pantalla 2 muestra los criterios de filtrado para llenar la grilla de acuerdo al tipo de reporte que se este ejecutando:



La pantalla 3 muestra la grilla resultado. En el ejemplo se ejecutó el reporte de historial de DB Time:



La pantalla 4 muestra la salida del reporte de historial de los 5 eventos de espera mas importantes:



La pantalla 5 muestra las sesiones actuales de la base (información actual)



Con doble click sobre una fila de la grilla se abre otro form con detalle de la sesion seleccionada. En el panel donde aparece el texto de la sentencia que esta ejecutando la sesión se puede clickear el boton derecho y ver y seleccionar "Detalle.." para ver el historial de ejecución de dicha sentencia.



La siguiente pantalla muestra todos los forms abiertos puestos en mosaico horizontal (menu Ventanas)



La pantalla 8 es el resultado de la ejecucion del reporte de Tablespaces



Con doble click sobre la fila de la grilla se abre un nuevo form con detalle del tablespace:



La pantalla 10 muestra los posibles filtros que se pueden realizar para analizar el historial de sentencias TOP.



La pantalla 11 muesta las sentencias TOP que cumplen los filtros definidos anteriormente:



Con un click sobre la celda que tiene el SQLID se copia el valor y luego presionando el boton de REFRESCAR se "pastea" el valor copiado para ver historial de la sentencia:



Todas las grillas de resultado se pueden exportar a excel, se pueden filtrar y todas las consultas se puede copiar (botón derecho sobre el panel donde esta el texto de la consulta y seleccionar COPIAR).

Mi idea fue mostrarles algunas pantallas para que vean la funcionalidad que intenté darle a mi aplicación. Obviamente hay varios reportes mas que no estan detallados en el blog, pero invito a quien quiera evaluar mi programita (se llama OraPerfViewer.exe y pesa alrededor de 150kb) que solo me escriba a: rovedop@gmail.com y les enviaré el ejecutable que solo requiere windows y un framework .NET instalado (creo que la mayoria de las windows lo traen instalado por default). Por el momento no es RAC-Aware, pero en el futuro lo será y corre sobre versiones 10g en adelante.

Es una primera versión, y seguramente tenga varios bugs y cosas que mejorar, pero estoy seguro que compartiendo y discutiendo ideas se mejoran las cosas.

viernes, 26 de febrero de 2010

Reporte para obtener métricas de SO desde AWR

Como podemos hace para detectar problemas de cpu o i/o sin tener que pedir ayuda al grupo de Sistemas Operativos?, suena complicado, no?. Bueno... a partir de 10g una opción, por lo menos para ir teniendo un primer panorama, es ver si esta pasando algo con la caja donde reside nuestra base de datos consultando en el repositorio AWR. Para exteriorizar estas métricas existe la vista historica: DBA_HIST_OSSTAT, que en 10g contiene lo siguiente:

STAT_NAME
-------------------------------------------
BUSY_TIME
AVG_IDLE_TIME
NUM_CPUS
AVG_BUSY_TIME
OS_CPU_WAIT_TIME
VM_IN_BYTES
AVG_USER_TIME
AVG_SYS_TIME
LOAD
SYS_TIME
RSRC_MGR_CPU_WAIT_TIME
IDLE_TIME
USER_TIME
PHYSICAL_MEMORY_BYTES
IOWAIT_TIME
AVG_IOWAIT_TIME
VM_OUT_BYTES

y en 11g R1 se agregan las siguientes:

TCP_SEND_SIZE_DEFAULT
TCP_RECEIVE_SIZE_DEFAULT
TCP_RECEIVE_SIZE_MAX
NUM_CPU_SOCKETS
TCP_SEND_SIZE_MAX
NUM_CPU_CORES


Abajo, les copio un script que armé para ver algunas métricas importantes. El reporte toma como unico parametro la cantidad de horas hacia atras que quiero analizar.


set line 120
set pagesize 9999
set verify off
accept horas prompt "Ingrese cantidad de horas hacia atras que desea reportar: "

col snap format a20

select unique to_char(snap,'DD-MON-YYYY HH24') snap,
avg_idle_time-lead(avg_idle_time) over (partition by st order by snap desc) avg_idle_time,
avg_user_time-lead(avg_user_time) over (partition by st order by snap desc) avg_user_time,
avg_sys_time-lead(avg_sys_time) over (partition by st order by snap desc) avg_sys_time,
avg_iowait_time-lead(avg_iowait_time) over (partition by st order by snap desc) avg_iowait_time,
os_cpu_wait_time-lead(os_cpu_wait_time) over (partition by st order by snap desc) os_cpu_wait_time
from
(select s.end_interval_time snap,
s.startup_time st,
max(decode(stat_name,'AVG_IDLE_TIME',value,null)) AVG_IDLE_TIME,
max(decode(stat_name,'AVG_USER_TIME',value,null)) AVG_USER_TIME,
max(decode(stat_name,'AVG_SYS_TIME',value,null)) AVG_SYS_TIME,
max(decode(stat_name,'AVG_IOWAIT_TIME',value,null)) AVG_IOWAIT_TIME,
max(decode(stat_name,'OS_CPU_WAIT_TIME',value,null)) OS_CPU_WAIT_TIME
from dba_hist_osstat os,
dba_hist_snapshot s
where s.snap_id = os.snap_id
group by s.end_interval_time,s.startup_time)
where snap > sysdate-&horas/24
order by snap desc
/


SNAP AVG_IDLE_TIME AVG_USER_TIME AVG_SYS_TIME AVG_IOWAIT_TIME OS_CPU_WAIT_TIME
25-FEB-10 09 211854 108399 40156 99439 2087400
25-FEB-10 08 271995 61131 27296 97923 1199800
25-FEB-10 07 236760 85951 31938 84592 1489500
25-FEB-10 06 180172 130112 50168 90475 2281500
25-FEB-10 05 182995 123247 54623 105254 2274500
25-FEB-10 04 193166 116204 51902 127028 2197000
25-FEB-10 03 195871 119849 45241 130806 2126400
25-FEB-10 02 236610 85916 37782 160617 1574500


Obviamente, para tener un diagnostico preciso de uso de recursos de SO lo ideal es pedir a los grupos encargados de la administración del SO, networking o storage reportes detallados historicos de actividad, pero si queremos tener una primera foto en forma rapida de lo que esta pasando con solo tener acceso al repositorio de la base, podemos usar la consulta de arriba o cualquier variación (se puede agregar columnas para reportar uso de memoria virtual, tráfico de red, etc) de esta para tener una primera impresión.

viernes, 5 de febrero de 2010

Colorear una sentencia sql (Colored SQL)

Muchos se preguntaran que significa "colorear un sentencia sql", verdad?. Lo que implica colorear es ni mas ni menos que marcar una sentencia identificandola por su sqlid, que es la identificación unica de una sentencia en la base da datos, para que los snapshots de AWR la incluyan en el repositorio y luego ser analizada. El repositorio de AWR almacena, entre otras cosas, las sentencias TOP, es decir las que mas consumieron entre dos snapshots, por default los snapshots se sacan cada hora, por lo que permite analizar por periodos de una hora. Si yo quisiera ver como se fue comportando una sentencia a lo largo de cierto tiempo pero dicha sentencia no esta dentro de las mas consumidoras no quedará registro y por lo tanto no se podrá analizar su actividad a posteriori. Para asegurar que se le siga el rastro a las ejecuciones de un sentencia, sin importar cuanto consume, a partir de 11g se la puede "colorear", veamos como es esto:

Armo una consulta bien sencilla, que obviamente no consumirá mucho y no quedará registrada en una base con una minima actividad:

rop@DESA11G> select 'TEST COLORED SQL' from dual;

'TESTCOLOREDSQL'
----------------
TEST COLORED SQL

Buscamos el sqlid asociado:

rop@DESA11G> set line 120
rop@DESA11G> select sql_text,sql_id from v$sqlstats where sql_text like '%TEST COLORED SQL%';

SQL_TEXT
------------------------------------------------------------------------------------------------------------------------
SQL_ID
-------------
select sql_text,sql_id from v$sqlstats where sql_text like '%TEST COLORED SQL%'
f2c39t3uct6vp

select 'TEST COLORED SQL' from dual
0fm46pj2s9vux


Una vez obtenido el sqlid voy a marcarla o colorearla de la siguiente forma:

rop@DESA11G> ed
Escrito file afiedt.buf

1 begin
2 dbms_workload_repository.add_colored_sql(sql_id => '0fm46pj2s9vux'
3 );
4* end;
rop@DESA11G> /

Procedimiento PL/SQL terminado correctamente.

Ahora tomo un snapshot y luego ejecuto 3 veces la sentencia coloreada:

rop@DESA11G> begin
2 dbms_workload_repository.create_snapshot;
3 end;
4 /

Procedimiento PL/SQL terminado correctamente.

rop@DESA11G> select 'TEST COLORED SQL' from dual;

'TESTCOLOREDSQL'
----------------
TEST COLORED SQL

rop@DESA11G> /

'TESTCOLOREDSQL'
----------------
TEST COLORED SQL

rop@DESA11G> /

'TESTCOLOREDSQL'
----------------
TEST COLORED SQL

Tomo otro snapshot luego de aproximadamente 15' y veo si aparece:

rop@DESA11G> begin
2 dbms_workload_repository.create_snapshot;
3 end;
4 /

Procedimiento PL/SQL terminado correctamente.

rop@DESA11G> select executions_delta,cpu_time_delta,elapsed_time_delta from dba_hist_sqlstat
2 where snap_id = (select max(snap_id) from dba_hist_sqlstat)
3 and sql_id = '0fm46pj2s9vux';

EXECUTIONS_DELTA CPU_TIME_DELTA ELAPSED_TIME_DELTA
---------------- -------------- ------------------
3 0 0

La sentencia apareció y se ve que consumió tan poco que no se llegó a registrar tiempo
Para desmarcarla hago lo siguiente:

rop@DESA11G> ed
Escrito file afiedt.buf

1 begin
2 dbms_workload_repository.remove_colored_sql(sql_id => '0fm46pj2s9vux');
3* end;
rop@DESA11G> /

Procedimiento PL/SQL terminado correctamente.

Repito el proceso pero sin la consulta "coloreada":

rop@DESA11G> ed
Escrito file afiedt.buf

1 begin
2 dbms_workload_repository.create_snapshot;
3* end;
rop@DESA11G> /

Procedimiento PL/SQL terminado correctamente.

rop@DESA11G> select 'TEST COLORED SQL' from dual;

'TESTCOLOREDSQL'
----------------
TEST COLORED SQL

rop@DESA11G> /

'TESTCOLOREDSQL'
----------------
TEST COLORED SQL

rop@DESA11G> /

'TESTCOLOREDSQL'
----------------
TEST COLORED SQL

rop@DESA11G> begin
2 dbms_workload_repository.create_snapshot;
3 end;
4 /

Procedimiento PL/SQL terminado correctamente.

rop@DESA11G> ed
Escrito file afiedt.buf

1 select executions_delta,cpu_time_delta,elapsed_time_delta from dba_hist_sqlstat
2 where snap_id = (select max(snap_id) from dba_hist_sqlstat)
3* and sql_id = '0fm46pj2s9vux'
rop@DESA11G> /

ninguna fila seleccionada

rop@DESA11G>

Como se observa ahora no se registró en AWR, ya que como se vió es una sentencia con consumo nulo y que obviamente no califica entre las Top para ser persistida en el repositorio.

viernes, 29 de enero de 2010

Enfoque para medir la disponibilidad efectiva de una base de datos Oracle

La tarea de los dba's es poco conocida y a veces mal entendida por la mayoria de la gente que no esta especificamente en el tema de base de datos. Es por eso que muchas veces es dificil estimar o medir nuestra eficiencia si no se conoce nada o poco del tema. Dado que la gestión y mantenimiento de bases de datos es un tema muy técnico, resulta ser todo un misterio, incluso para algunos gerentes de sistemas con perfil más de gestión que técnico, que se conforman con no tener sobresaltos y que las bases productivas esten disponibles y con un tiempo de respuesta decente, obviamente esto es deseable en cualquier ambito, pero no permite medir eficiencia con detalle.

Una forma de medir nuestra eficiencia con una métrica entendible por el público en general, podría ser mostrar en numeros la disponibilidad de las bases de datos que gestionamos. Con disponibilidad (availability) me refiero al tiempo total que las bases están arriba, es decir que se pueden usar. Esto en alguna medida (al margen de temas de rendimiento) podría mostrar o cuantificar cuan bien administramos ya que si la base esta arriba las aplicaciones que usan dicha base están operativas. Quien no escucho alguna vez en la cola de un banco o realizando algún trámite municipal la frase: "el sistema esta caido, vuelva mas tarde..."?, en la gran mayoria de los casos es por algun problema en la base de datos o bien porque se tuvieron que realizar tareas de mantenimiento de urgencia, por mala administración, por errores de los operadores o dba's, etc. Es cierto que las bases de datos, y sobre todo Oracle, hoy en día son muy robustas y se han minimizado muchos de los potenciales problemas. Tambien el hardware es mas confiable y, si se tiene presupuesto, se puede redundar en componentes lo cual hace mucho mas improbable una indisponibilidad.

Para poder medir el tiempo de disponibilidad total de una base de datos armé un script que reporta la disponibilidad de los ultimos n dias. En el ejemplo lo hice para los ultimos 4 meses (120 dias). Hay que tomar en cuenta que el reporte se basa de ciertas premisas:

  • Debe estar disponible la data a procesar en el alert de la base a analizar. Si se realiza alguna tarea de depuracion diaria, semanal, etc, se deberá concatenar de antemano en un archivo toda la info grabada en un archivo de alert que contenga la totalidad de la información necesaria.
  • Si no existen registros con información de fecha y hora entre dos startups se toma como que la base estuvo arriba todo ese tiempo.
  • Cuando la base se baja abruptamente, ya sea por un shutdown abort, por un error grave de HW o bien porque se desconectó el cable de alimentación no se registra el shutdown en el alert por lo cual se pierde esa referencia y se usará el último registro disponible en el alert.


Para armar poder correr el reporte primero hay que generar una tabla externa (recordemos que esto existe a partir de 9i por lo cual este metodo no funcionara en versiones anteriores) que permite leer con la sentencia sql el archivo alert.


create directory BDUMP as '/u01/app/oracle/admin/ROP102/bdump'

CREATE TABLE "ALERT_LOG"
( "TEXT" VARCHAR2(400)
)
ORGANIZATION EXTERNAL
( TYPE ORACLE_LOADER
DEFAULT DIRECTORY "BDUMP"
ACCESS PARAMETERS
( records delimited by newline
nobadfile
nodiscardfile
nologfile
)
LOCATION
( 'alert_ROP102.log'
)
)
REJECT LIMIT UNLIMITED;


Una vez creada la tabla externa ya puedo ejecutar la sentencia. La lógica que tomé se basa en buscar el string en el alert que registra el startup de la base y luego (usando funciones análiticas) obtener el registro próximo anterior. Con esto infiero que el tiempo entre el ultimo registro anterior al startup y el registro de startup es el tiempo en el que estuvo baja la base. Esto es una aproximación y puede tener cierta imprecisión pero creo que se acerca bastante a la realidad en la mayoria de los casos. Existen registros en el catalogo de Oracle de cuando una base levanta, pero no se tiene información de cuando se baja, incluso consideremos que si existiera esto, seria complicado registrar los shutdown abort. Por ese motivo recalco que el script que realicé es una aproximación, dado que no se puede saber con exactitud el momento preciso del en el que la base se bajó. Considerando todo lo expuesto anteriormente ahora si vemos como funciona el reporte:


select min_date,
max_date,
round((max_date-min_date)*24,2) total,
down,
round(100-((down*100)/((max_date-min_date)*24)),3) pct_up
from
(select min(to_date(last_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english')) min_date,
max(to_date(start_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english')) max_date,
sum(round((to_date(start_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english')-
to_date(last_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english'))*24,2)) down
from
(select start_time,
last_time
from
(select text,
lag(text,1) over (order by r) start_time,
lag(text,2) over (order by r) last_time
from ( select rownum r, text
from alert_log
where text like '___ ___ __ __:__:__ 20__'
or text like 'Starting ORACLE instance (normal)'))
where text like 'Starting ORACLE instance (normal)'
and last_time like '___ ___ __ __:__:__ 20__'
and to_date(start_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english') > sysdate-120))


MIN_DATE MAX_DATE TOTAL DOWN PCT_UP
-------- -------- ------ ---- ------
02/10/2009 29/01/2010 2858.22 22.45 99,214


Como se ve en la salida se muestra el intervalo de fechas analizadas, el tiempo total transcurrido entre las dos fechas (medido en horas), el tiempo que estuvo baja (medido en horas) y el porcentaje que estuvo arriba la base. Como vemos esta en valores esperados ya que la disponibilidad esta por arriba del 99%.

viernes, 11 de diciembre de 2009

Reporte para analizar planes que referencien a una tabla dada

Cada tanto me consultan desde el area de desarrollo sobre si agregando tal o cual indice a una tabla mejoraria el rendimiento de la aplicación. Como en muchas ocasiones yo no conozco el negocio me resulta dificil saber como se usa la tabla, es decir, que sentencias la referencian, si se hacen updates, deletes, inserts o solo se consulta. Para poder analizar esto a veces uso el script que copio abajo, que da el plan de ejecución y la ultima vez que se ejecutaron las sentencias que referencian a una cierta tabla. A esto se le puede sumar la generación de sugerencias usando advisor tales como SQL Tuning y SQL Access Advisors.


set serverout on
set line 120
set pagesize 9999
set verify off
set feed off

ACCEPT tabla PROMPT "Ingrese Tabla a Analizar: "
PROMPT
PROMPT

begin
for i in (select sp.sql_id,max(sh.begin_interval_time) ufecha
from dba_hist_sql_plan sp,
dba_hist_sqlstat ss,
dba_hist_snapshot sh
where sp.sql_id = ss.sql_id
and ss.snap_id = sh.snap_id
and sp.object_name = upper('&tabla')
group by sp.sql_id)
loop
dbms_output.put_line('Ultima Ejecución: '||to_char(i.ufecha,'DD/MM/YYYY HH24:MI'));
for j in (select * from table(dbms_xplan.display_awr(i.sql_id)))
loop
dbms_output.put_line (j.plan_table_output);
end loop;
dbms_output.put_line(chr(10)||rpad('*',100,'*')||chr(10));
end loop;
end;
/

set verify on
set feed on

viernes, 14 de agosto de 2009

Verificando el uso de "Buenas Practicas" en código PL/SQL

Para verificar si se aplican "Buenas Practicas" de programación y para contribuir a realizar código mas robusto y menos propenso a que se generen errores en tiempo de ejecución, a partir de 10g R1, se introdujo un nuevo mecanismo que permite advertir en tiempo de compilación sobre potenciales problemas (WARNINGS), que si bien dejan compilada la unidad de código, pueden darnos dolores de cabeza y conducir a que las aplicaciones que utilizan dicho código generen errores imprevistos o peor aún, que no se obtengan los datos correctos alterando la semántica pretendida y siendo, en muchas ocasiones, muy complicados de detectar.

Existe 4 categorias de WARNINGS:

SEVERE : Pueden causar acciones inesperadas, errores que hagan
cancelar una operatoria o resultados erroneos.

PERFORMANCE : Pueden causar problemas de rendimiento

INFORMATIONAL: No afectan el rendimiento ni altera los resultados pero
advierte sobre complicaciones en el mantenimiento del
codigo a futuro.

ALL : Contempla todos los casos anteriores.


Para activar los mensajes de warning se puede usar el parametro PLSQL_WARNINGS a nivel sesion o a nivel instancia (cosa que no recomiendo), tambien se puede usar el paquete DBMS_WARNING para setear el nivel de warning deseado a nivel de código PL en procedures, packages, triggers, etc. Consultando la vista [USER | ALL | DBA]_WARNING_SETTINGS se puede saber que objetos tienen activado el warning y consultando la vista [USER | ALL | DBA]_ERRORS, filtrando por el campo ATTRIBUTE='WARNING' se ven todos los warnings generados.

Ahora que ya hice una introduccion rapida al tema, vayamos a los ejemplos:

Habilito para detectar todas las categorias:

rop@DESA10G> alter session set plsql_warnings='ENABLE:ALL';

Sesión modificada.

Creo una tabla sencila

rop@DESA10G> create table t (x int,y varchar(5));

Tabla creada.

rop@DESA10G> insert into t
2 select rownum,
3 trunc(dbms_random.value(1,99999))
4 from dual
5 connect by rownum <= 100000;

100000 filas creadas.

rop@DESA10G> commit;

Confirmación terminada.

Voy a crear un procedimiento P_PRUEBA1 de forma tal de que se detecte un warning:


rop@DESA10G>ed
1 create or replace procedure p_prueba1 (p_val int)
2 is
3 l_cnt int;
4 begin
5 select count(1) into l_cnt
6 from t
7 where y = p_val;
8 if (l_cnt > 0) then
9 dbms_output.put_line ('El valor existe en la tabla');
10 else
11 dbms_output.put_line ('El valor NO existe en la tabla');
12 end if;
13* end;
rop@DESA10G> /

SP2-0804: Procedimiento creado con advertencias de compilación

rop@DESA10G> select text from user_errors where name = 'P_PRUEBA1';

TEXT
----------------------------------------------------------------------------------------------------
PLW-07204: puede que la conversión que no sea de tipo de columna dé como resultado un plan de consulta subóptimo


En el caso de arriba detectó un potencial problema de performance, ya que al comparar la columna "y" de tipo varchar2 con el parametro "p_val" de tipo number se hace una conversión implicita TO_NUMBER() de la columna "y". Oracle siempre pasa a number cuando se comparan los tipos number y char/varchar2.
Veamos otros ejemplos:

rop@DESA10G> ed
Escrito file afiedt.buf

1 create or replace procedure p_prueba2 (p_val int)
2 is
3 l_cnt int;
4 begin
5 select count(1) into l_cnt
6 from t
7 where y = to_char(p_val);
8 if ( 0 = 0) then
9 if (l_cnt > 0) then
10 dbms_output.put_line ('El valor existe en la tabla');
11 else
12 dbms_output.put_line ('El valor NO existe en la tabla');
13 end if;
14 else
15 null;
16 end if;
17* end;
rop@DESA10G> /

SP2-0804: Procedimiento creado con advertencias de compilación

rop@DESA10G> select text from user_errors where name = 'P_PRUEBA2';

TEXT
----------------------------------------------------------------------------------------------------
PLW-06002: Código inaccesible

Este es una advertencia informativa. Ahora voy a crear una función:


rop@DESA10G> ed
Escrito file afiedt.buf

1 create or replace function f_prueba1
2 return int
3 is
4 l_val int;
5 begin
6 l_val := dbms_random.value(1,10);
7* end;
rop@DESA10G> /

SP2-0806: Función creada con advertencias de compilación

rop@DESA10G> select text from user_errors where name = 'F_PRUEBA1';

TEXT
----------------------------------------------------------------------------------------------------
PLW-05005: la función F_PRUEBA1 se devuelve sin valor en la línea 7

al no retornar valor se podria generar un problema mas grave


rop@DESA10G> ed
Escrito file afiedt.buf

1 create or replace procedure p_prueba3 (p_val varchar2)
2 is
3 begin
4 insert into t (x) values (p_val);
5* end;
rop@DESA10G> /

SP2-0804: Procedimiento creado con advertencias de compilación

rop@DESA10G> select text from user_errors where name = 'P_PRUEBA3';

TEXT
----------------------------------------------------------------------------------------------------
PLW-07202: el tipo de enlace daría como resultado una conversión lejos del tipo de columna

rop@DESA10G>


El warning para el procedimiento P_PRUEBA3, aunque la traducción al español no es muy clara, da un posible problema en la conversión de tipos. Asi podriamos seguir probando otros tantos casos.
La idea fue mostrarles que con esta herramienta se puede mejorar la calidad del software pl/sql y detectar en forma automatica y proactiva posibles problemas en tiempo de ejecución.

lunes, 2 de marzo de 2009

Herramienta para Testeo de Rendimiento de I/O (Orion)

Orion es una herramienta de calibración de I/O que permite predicir el rendimiento que tendrá una base de datos Oracle sin tener creada una base de datos, incluso sin tener el software de Oracle instalado. A diferencia de otras herramientas de calibración de I/O, Orion esta diseñado para simular el acceso a disco tal cual lo hace Oracle ya que usa la misma capa de acceso.
Los tipos de carga que pueden ser simulados son los siguientes:

Small Random I/O: Los sistemas OLTP se caracterizan por tener este tipo de acceso. En este contexto las métricas que importan son I/O por segundo (IOPS) y latencia promedio por acceso

Large Sequential I/O: Simula carga típica en aplicaciones de DataWarehouse, carga masiva, backups, restore, etc. Tales aplicaciones procesan gran cantidad de datos y lo importante es medir Megabytes por segundo (MBPS).

Large Random I/O: Cuando se usa striping las lecturas secuenciales se realizan como una cantidad concurrente de lecturas secuenciales aleatorias de 1Mb, conocido como I/O secuencial multiusuario.

Mixed Workload: Se simulan dos cargas de trabajo simultaneas: Small Random I/O y Large Sequential I/O o Large Random I/O. Esto permite simular por ejemplo el comportamiento de una base OLTP (Small Random I/O) mientras se esta efectuando un backup completo de la base de datos en caliente (Large Sequential I/O).

Orion puede utilizarse para testear cualquier tipo de discos que soporte I/O asincrónica. Se pueden testear:

• DAS (direct-attached storage)
• SAN (storage-area network)
• NAS (network-attached storage)

Y se encuentra disponible para las siguientes plataformas:

• AIX
• Solaris 64_sparc
• Solaris 64_x86
• Linux 32
• Linux 64
• Windows

y recientemente se agregaron: zlinux,HP itanium y PA RISC, linux sobre sobre itanium y power.

Instalación y Configuración


Orion es una herramienta totalmente gratuita, que puede descargarse desde otn.roacle.com. La instalación solo requiere de la descompresión del archivo .gz bajado. Los siguientes pasos describen una configuración básica:

1. Una vez descomprimido, asignar permisos de ejecución al usuario desde el que se va a ejecutar el test.

2. Crear un archivo (ej: mytest.lun) con la lista de los volumen o raw devices a calibrar. El archivo debe contener un nombre del volumen o filesystem por linea, por ejemplo:
/dev/vx/dsk/sgu/vol01
/dev/vx/dsk/sgu/vol02

3. Verificar que los volúmenes sean accesibles con herramientas de copiado, tipo dd, por ejemplo:
dd if=/dev/raw/raw1 of=/dev/null bs=32k count=1

4. Verificar que la plataforma tenga instaladas y accesibles las librerias de acceso asincrónico. Orion funciona solo con I/O asincrónico.

5. Es conveniente comenzar con una prueba simple, por ejemplo, el siguiente comando:
$./orion –run simple –testname mytest –num_disks 4

Ejecuta un test simple con small random reads y large random reads con diferentes cargas que permitiran tener una primera noción de cómo se comporta la I/O para distintos tipos de accesos y de carga.


Archivos de Resultado


El resultado del Test se encuentra detallado en los siguientes archivos:

1. mytest_summary.txt: Este archivo contiene:
a. Parámetros de Entrada.
b. Throughput maximo para carga Large Random/Sequential.
c. Tasa Maxima de I/O para carga Small Random.
d. Minima Latencia para carga Small Random.

2. mytest_mbps.txt: Archivo separado por comas, que contiene detalle de la tasa de transferencia para tipo de acceso Large Sequential/Random.

3. mytest_iops.txt: Archivo separado por comas, que detalla el I/O throughput medido en IOPS para carga de trabajo tipo Small Random.

4. mytest_lat.csv: Archivo separado por comas con los resultados de latencia según distintos tipos de carga para tipo de carga Small Random.

5. mytest_trace.txt: Archivo con información “cruda” producto de la traza del test.

Parametrización de Entrada

La ejecución de Orion puede ser parametrizada por distintas opciones. Los parámetros obligatorios son:

-run: Puede ser simple, normal o advanced. Para comenzar conviene usar simple y luego utilizar advanced para tener mas control de la calibración.
-testname: Aca va el nombre del archivo definido en el paso 2 de la configuración (mytest).
-num_disks: Define la cantidad de volúmenes a evaluar, generalmente este valor es igual a la cantidad de filas que contenga el archivo mytest.
Los parámetros opcionales son:
-help: Información de ayuda. Descripción de parametros
-size_small: Tamaño de I/O (en Kb) para carga Small Random I/O (el default es 8)
-size_large: Tamaño de I/O (en kb) para carga Large Sequential/Random I/O (el default es 1Mb)
-type: Tipo de carga de trabajo Large (default rand)
-num_streamIO: Numero de I/O’s por cada stream
-simulate: Permite similar como estan los datos dispuestos en los discos (el default es concat)
-write: Porcentaje de I/O que será:n writes.
-cache_size: Tamaño de cache del storage array
-duration: Duración del test por cada punto de datos (el default es 60 segundos)
-matriz: Tipo de carga (el default de detailed)
-num_small: Máximo numero de I/O para tipo de carga Small Random.
-num_large: Máximo numero de I/O para tipo de carga Large Random o numero de Large I/O por stream.
-verbose: Muestra información de estado y progreso en la salida estándar.

Ejemplos de Uso

A continuación se detallan algunos ejemplos de uso:
Ejemplo1: Ejecución básica
$./orion –run simple –testname mytest –num_disks 1

Ejemplo 2: Ejecución avanzada de tipo Large Sequential con nivel de carga Small fijado en 2
$ ./orion -run advanced -testname mytest -num_disks 2 -type seq -num_streamIO 10 -matrix col -num_small 2

Ejemplo 3: Ejecución para generar multiples escrituras de 1mb simulado RAID0 con stripes de 1Mb
$./orion –run advanced –testname mytest –num_disks 8 –simulate raid 0 –stripe 1024 –write 100 –type seq –matrix col –num_small 0


Si bien en la documentación sigue diciendo que esta herramienta es beta y no es soportada por Oracle Corporation ya tiene varios años y se ha portado a casi todas las plataformas por lo cual daría la impresión que ya ha madurado lo suficiente. Orion permite evaluar Storage y ver como se comporta con los requerimientos de rendimiento esperado sin necesidad de instalar ni el motor ni una base de datos Oracle. Los administradores podrán comparar distintos arreglos de storage de acuerdo a la carga de trabajo esperada y poder optar por la configuración mas conveniente.

Para mayor información ver:
Oracle Orion Users Guide
Para bajar Orion (Oracle I/O Calibration Test):
Oracle Orion Downloads