lunes, 23 de septiembre de 2013

Histogramas de tiempos de ejecución SQL con AWK

Hace unos días tuve que analizar la performance de una aplicación java que usa Oracle y evaluar si el problema de lentitud estaba en las sentencias SQL ejecutadas. El escenario es:
 - el código no se puede modificar
 - la base de datos usada recibe cientos de sentencias por segundo de varias aplicaciones que vienen del mismo servidor.
 - en el mismo servidor de aplicaciones se ejecutan varias aplicaciones java similares a la que tiene problemas.

Como el problema es en producción, no había posibilidad de bajar el resto de las aplicaciones para dejar solo el tráfico de la problemática.

Así que la alternativa disponible fue habilitar un log de ejecución de sentencias en la aplicación.
Esto genera una entrada por cada ejecución en el archivo con este formato:
2013-09-09 14:38:44,066 [org.hibernate.SQL] Sentencia

No sabemos en qué momento se escribe la entrada en el log. Puede ser antes o después de ejecutar la sentencia. Pero lo que sí sabemos es que el tiempo de ejecución es como máximo el tiempo entre dos entradas consecutivas. Así que como una aproximación sirve para tener una idea de por donde puede venir el problema.

Obtener datos útiles de archivos de log con sentencias SQL es una alternativa muy conocida y bien aprovechada en MySQL con utilitarios como pt-query-digest. Sin intentar imitarlo, me alcanza con ver los tiempos de ejecución de las sentencias, cosa que se puede obtener con un simple script AWK tomando el tiempo transcurrido entre dos entradas consecutivas. Por ejemplo, para ver las que demoraron más de un segundo:
oracle@oraculo:~> awk '{split($3,a,":"); sec=60*(60*a[1]+a[2])+a[3]; if(b!=0 && (sec-b)>1){ print $3 " - " sec - b; }; b=sec}' /tmp/pp.txt
    14:38:49,899 - 5,015
    14:38:51,995 - 2,094
    14:43:46,037 - 2,067
    14:43:51,054 - 5,015
    14:43:56,072 - 5,015

Así puedo ir al log y ver cuales fueron esas sentencias para después analizar si realmente tienen buenos planes de ejecución.

Limpié del archivo entradas iniciales antes de empezar la ejecución de sentencias, y algunas entradas finales, mirando con un editor los números de línea (vi y g es lo más simple). Así nos quedamos con las líneas 6821 a 74572:

oracle@oraculo:~> head -n 74572 myjavaapp.2013091112.log | tail -n +6821 > /tmp/pp.txt

Las sentencias SQL incluidas en este log son:
oracle@oraculo:~> grep -ic insert /tmp/pp.txt
    507
oracle@oraculo:~> grep -ic delete /tmp/pp.txt
    0
oracle@oraculo:~> grep -ic update /tmp/pp.txt
    524
oracle@oraculo:~> grep -ic select /tmp/pp.txt
    2254

Para tener una visión más clara del tiempo de ejecución de las sentencias, armo histogramas de tiempos de ejecución, redondeando a 0,1 segundo (tiempos más chicos no me interesa optimizar). El formato de esta salida es: tiempo de ejecución en segundos y cantidad de sentencias.
oracle@oraculo:~> awk 'BEGIN {b=0;} { split($3,a,":"); sec=60*(60*a[1]+a[2])+a[3]; if(b!=0){ n[int((sec-b)*10+.5)/10]++}; b=sec} END {for (i in n) print i,n[i]}' /tmp/pp2.txt | sort -n
   
    0 2268
    0,1 161
    0,2 421
    0,3 207
    0,4 90
    0,5 68
    0,6 50
    0,7 11
    0,8 1
    0,9 2
    2,1 2
    5 3

Se puede ver con claridad que la mayoría de las sentencias se resuelven en menos de 0,4 segundos, lo que indica con claridad que la base de datos no está teniendo problemas, y se debe analizar el código de la aplicación en busca de más pistas de la lentitud observada.


Espero les sea útil.
Un saludo.

lunes, 2 de septiembre de 2013

Automatizar recover UNTIL CANCEL

Relacionado a mi post anterior, otra de las cosas que ocurre frecuentemente al actualizar una base Oracle de test con un respaldo de producción, es la necesidad de realizar recuperación incompleta UNTIL CANCEL. En este caso la sentencia RECOVER nos va a preguntar qué archivelogs se debe aplicar para dejar la base en un estado consistente.
08:41:07 SYS@test>recover database until cancel using backup controlfile;
ORA-00279: change 2691063472 generated at 03/12/2013 01:30:02 needed for thread 1
ORA-00289: suggestion : /u01/app/oracle/flash_recovery_area/test/archivelog/2013_03_13/o1_mf_1_58643_%u_.arc
ORA-00280: change 2691063472 for thread 1 is in sequence #58643


08:41:29 Specify log: {=suggested | filename | AUTO | CANCEL}

Si estamos recuperando al momento en que se tomó el respaldo, éste incluye archivelogs y ya los copiamos desde el respaldo al directorio sugerido (/u01/app/oracle/flash_recovery_area/test/archivelog/2013_03_13), entonces podemos indicar AUTO para que los aplique sin mayores problemas.
Como referencia, la restauración de archivelogs con RMAN se hace con el comando 'restore archivelog ...'.

Cuando es necesario ir a un momento en el tiempo posterior, usando respaldos adicionales de archivelogs, y sin tener definido con exactitud el tiempo al que queremos llegar, debemos ir aceptando manualmente cada archivelog para luego ver si es hasta ahí donde queremos llegar (por ejemplo si interesa recuperar hasta el momento previo en que se borraron datos en una tabla, y no contamos con la funcionalidad FLASHBACK).

Al presionar enter y aceptar la sugerencia, si el archivo existe en el destino se aplica:
08:41:04 Specify log: {=suggested | filename | AUTO | CANCEL}
 

ORA-00279: change 2691076177 generated at 03/12/2013 02:02:53 needed for thread 1
ORA-00289: suggestion : /u01/app/oracle/flash_recovery_area/TEST/archivelog/2013_03_13/o1_mf_1_58644_%u_.arc
ORA-00280: change 2691076177 for thread 1 is in sequence #58644
ORA-00278: log file '/u01/app/oracle/flash_recovery_area/test/archivelog/2013_03_13/o1_mf_1_58643_8n0rj3kr_.arc' no
longer needed for this recovery

Si todavía no trajimos este archivelog desde el respaldo, o lo dejamos en otro directorio, va a dar este error:
08:41:04 Specify log: {=suggested | filename | AUTO | CANCEL}
 

ORA-00308: cannot open archived log
'/u01/app/oracle/flash_recovery_area/test/archivelog/2013_03_13/o1_mf_1_58645_%u_.arc'
ORA-27037: unable to obtain file status
Linux-x86_64 Error: 2: No such file or directory
Additional information: 3

Luego que aplicamos todos los archivelogs que nos parecen necesarios para llegar al tiempo que nos interesa, ingresamos CANCEL. Y aquí es donde puede aparecer el problema: la base todavía no está en un punto consistente.
08:41:04 Specify log: {=suggested | filename | AUTO | CANCEL}
cancel

ORA-01547: warning: RECOVER succeeded but OPEN RESETLOGS would get error below
ORA-01194: file 1 needs more recovery to be consistent
ORA-01110: data file 1: '/u02/oradata/test/system01.dbf'

Esto es un aviso de que nos faltó aplicar archivelogs, o que nos pasamos del punto que nos interesaba (si tenemos varios respaldos de archivelogs y los trajimos todos, podemos estar aplicando ya el siguiente grupo de archivos).

Cuando cancelamos la recuperación y llegamos a un punto consistente, el mensaje es claro:
09:07:53 Specify log: {=suggested | filename | AUTO | CANCEL}
cancel
Media recovery cancelled.

Ahora sí queda la base lista para ser usada después de abrirla con resetlogs.
09:08:00 SYS@test>alter database open resetlogs;

Database altered.

Si tenemos muchos archivelogs para aplicar, estar pendiente de la pregunta para presionar enter es algo tedioso, y se puede automatizar fácilmente con un script en bash, que continúa la aplicación mientras el resultado de cancelar el recovery no sea exitoso:
#/bin/bash
###############################
# reco.sh
#
# ncalero 5/2010 - recupera aplicando
# archivelogs hasta que CANCEL sea exitoso
#
###############################

RESULT=/tmp/reco.txt
LOG=/tmp/reco-full.txt

# flag que cuenta si aparecen estos errores. Termina cuando sea cero
SIGO=1
while [ $SIGO -gt 0 ]; do
echo "..."
sqlplus / as sysdba <$RESULT 2>&1
recover database until cancel using backup controlfile;
CANCEL
exit;
EOF


# buscamos mensaje "ORA-01194: file 1 needs more recovery to be consistent"
SIGO=`grep -c 01194 $RESULT`
echo "ora-1194 = $SIGO"

if [[ $SIGO -gt 0 ]] ; then
   # se dio el error, hacemos doble validacion con el otro error
   # ORA-01547: warning: RECOVER succeeded but OPEN RESETLOGS would get error below
   SIGO=`grep -c 01547 $RESULT`
   echo "ora-1547 = $SIGO"
fi
cat $RESULT >>$LOG
done

echo " ### Se puede recuperar!!! ### "

Al ejecutar este script, se muestran dos líneas por errores de intentar cancelar, y termina cuando la base queda en un estado consistente:
oracle@oraculo:/local/work/scripts> ./reco.sh
...
ora-1194 = 1
ora-1547 = 1
...
ora-1194 = 1
ora-1547 = 1
...
ora-1194 = 1
ora-1547 = 1
...
ora-1194 = 1
ora-1547 = 1
...
ora-1194 = 1
ora-1547 = 1
### Se puede recuperar!!! ###

Una última consideración: si cuando ingresamos cancel nos dice que necesita más recovery e intentamos resolver el problema dejando la base en un punto anterior en el tiempo al último archivelog aplicado, vamos a obtener el siguiente error:
RMAN> recover database until scn 2691031554;
Starting recover at 12/MAR/2013 09:14:47
using channel ORA_DISK_1
RMAN-00571: ===========================================================
RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============
RMAN-00571: ===========================================================
RMAN-03002: failure of recover command at 03/12/2013 09:14:48
RMAN-06556: datafile 1 must be restored from backup older than scn 2691031554

Esto se debe a que no podemos deshacer la aplicación de archivelogs que hicimos antes con el comando RECOVER. Tenemos que volver a traer los datafile desde el respaldo con el que empezamos esta maniobra y aplicar los archivelogs hasta ese punto, sin volver a pasarnos.

Espero que les sea útil.
Un saludo.

martes, 27 de agosto de 2013

Automatizar recuperación de respaldos RMAN

Algo que tengo que hacer seguido y es tarea habitual de un DBA, es restaurar un respaldo RMAN en un servidor distinto al que fue tomado. Si estamos en 10g no podemos usar el comando DUPLICATE ... FROM LOCATION que haría todo más fácil (por detalles ver mi post anterior).

Este procedimiento es bien conocido como Database Point in Time Recovery.

Se debe restaurar el respaldo usando la cláusula "UNTIL TIME .." del comando RESTORE (o SCN / LOG). ¿Qué valor debemos usar? Mirando el contenido del respaldo podemos identificar el último archivelog incluido a continuación del respaldo completo.

Ahora, si queremos poner estos pasos en un script, ¿cómo obtenemos este valor?.
Podemos consultar los datos que almacena RMAN en la base destino después de catalogar el respaldo. Vamos a ver cómo hacerlo sin usar una base de catálogo central, obteniendo estos datos directamente del controlfile (vistas V$BACKUP_*). Usando catálogo habría que usar las vistas RC_*.

Hay varios detalles a tener en cuenta. Lo más importante es poder identificar el respaldo completo que vamos a usar. En este ejemplo, lo identificamos porque tenemos catalogado solamente un respaldo completo (ejecutamos crosscheck después de recuperar el controlfile, y catalogamos solo este respaldo), con esta consulta:
select 'set until sequence '||max_seq||';'
from (
 select min(sequence#) min_seq, max(sequence#) max_seq
 from  v$backup_set s, v$BACKUP_ARCHIVELOG_DETAILS r
 where s.set_count = r.id2
   and s.set_stamp = r.id1
   and s.backup_Type='L'
   and r.first_time>=(
       select start_time from v$backup_set s
       where s.CONTROLFILE_INCLUDED='NO' and s.backup_type='D'
        and (set_stamp, set_count) in (
        select set_stamp, set_count
        from v$backup_piece
        where deleted='NO')
   )
);

Por más información sobre el contenido de estas vistas se puede ver el manual de cada una: v$backup_piece, v$backup_set y v$BACKUP_ARCHIVELOG_DETAILS.

¿Esto es lo mismo que se obtiene con comandos RMAN?. Vemos un ejemplo que lo confirma:
oracle@oraculo:> rman target /

Recovery Manager: Release 10.2.0.3.0 - Production on Tue Aug 27 10:27:08 2013

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

connected to target database: PROD (DBID=3195287633)

RMAN> list backup summary;

using target database control file instead of recovery catalog

List of Backups                                                                                                                
===============
Key     TY LV S Device Type Completion Time      #Pieces #Copies Compressed Tag
------- -- -- - ----------- -------------------- ------- ------- ---------- ---
19056   B  F  A DISK        08/AUG/2013 02:36:18 17      1       YES        TAG20130807T233030                                 
19057   B  F  A DISK        08/AUG/2013 02:36:24 1       1       NO         TAG20130808T023623                 
19058   B  A  A DISK        08/AUG/2013 02:41:36 1       1       YES        TAG20130808T023635                                 
19059   B  F  A DISK        08/AUG/2013 03:01:53 1       1       NO         TAG20130808T030153

Estos datos los podemos ver en SQLPLUS:
11:27:15 SYS@PROD> select *
from v$backup_set
where set_count in (19056,19057,19058,19059);

     RECID      STAMP  SET_STAMP  SET_COUNT BAC CONTROLFI INCREMENTAL_LEVEL     PIECES START_TIME
---------- ---------- ---------- ---------- --- --------- ----------------- ---------- -----------------------------
COMPLETION_TIME               ELAPSED_SECONDS BLOCK_SIZE INPUT_FIL KEEP      KEEP_UNTIL
----------------------------- --------------- ---------- --------- --------- -----------------------------
KEEP_OPTIONS
------------------------------
     19017  822452177  822451892      19056 L   NO                                   1 03/AUG/2013 02:51:32
03/AUG/2013 02:56:17                      285        512 NO        NO


     19018  822452458  822452178      19057 L   NO                                   1 03/AUG/2013 02:56:18
03/AUG/2013 03:00:58                      280        512 NO        NO


     19019  822452470  822452470      19058 D   YES                                  1 03/AUG/2013 03:01:10
03/AUG/2013 03:01:10                        0      16384 NO        NO

     19020  822798680  822798381      19059 L   NO                                   1 07/AUG/2013 03:06:21
07/AUG/2013 03:11:20                      299        512 NO        NO

Para validar que tenemos un respaldo completo, dos del controlfile y uno de archivelogs, vemos sus contenidos:
oracle@oraculo:> rman target /

Recovery Manager: Release 10.2.0.3.0 - Production on Tue Aug 27 10:36:00 2013

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

connected to target database: PROD (DBID=3195287633)

RMAN> list backup;

using target database control file instead of recovery catalog

List of Backup Sets
===================

BS Key  Type LV Size       Device Type Elapsed Time Completion Time   
------- ---- -- ---------- ----------- ------------ --------------------
19056   Full    33.87G     DISK        03:05:47     08/AUG/2013 02:36:18
  List of Datafiles in backup set 19056
  File LV Type Ckp SCN    Ckp Time             Name
  ---- -- ---- ---------- -------------------- ----
  1       Full 3048062425 07/AUG/2013 23:30:32 /u02/oradata/PROD/system01.dbf
  2       Full 3048062425 07/AUG/2013 23:30:32 /u03/oradata/PROD/undotbs01.dbf
  3       Full 3048062425 07/AUG/2013 23:30:32 /u02/oradata/PROD/sysaux01.dbf
  4       Full 3048062425 07/AUG/2013 23:30:32 /u02/oradata/PROD/users_01.dbf
  5       Full 3048062425 07/AUG/2013 23:30:32 /u02/oradata/PROD/datos_prod.dbf
  6       Full 3048062425 07/AUG/2013 23:30:32 /u03/oradata/PROD/indices_prod.dbf
  7       Full 3048062425 07/AUG/2013 23:30:32 /u02/oradata/PROD/cwmlite01.dbf
  8       Full 3048062425 07/AUG/2013 23:30:32 /u02/oradata/PROD/drsys01.dbf
  9       Full 3048062425 07/AUG/2013 23:30:32 /u02/oradata/PROD/example01.dbf
  10      Full 3048062425 07/AUG/2013 23:30:32 /u02/oradata/PROD/indx01.dbf
  11      Full 3048062425 07/AUG/2013 23:30:32 /u02/oradata/PROD/tools01.dbf

  Backup Set Copy #1 of backup set 19056
  Device Type Elapsed Time Completion Time      Compressed Tag
  ----------- ------------ -------------------- ---------- ---
  DISK        03:05:47     09/AUG/2013 04:55:28 YES        TAG20130807T233030

    List of Backup Pieces for backup set 19056 Copy #1
    BP Key  Pc# Status      Piece Name
    ------- --- ----------- ----------
    31790   1   AVAILABLE   /backup/database_PROD_20130807_p1_s19095_1
    31795   2   AVAILABLE   /backup/database_PROD_20130807_p2_s19095_1
    31766   3   AVAILABLE   /backup/database_PROD_20130807_p3_s19095_1
    31789   4   AVAILABLE   /backup/database_PROD_20130808_p4_s19095_1
    31794   5   AVAILABLE   /backup/database_PROD_20130808_p5_s19095_1
    31765   6   AVAILABLE   /backup/database_PROD_20130808_p6_s19095_1
    31775   7   AVAILABLE   /backup/database_PROD_20130808_p7_s19095_1
    31788   8   AVAILABLE   /backup/database_PROD_20130808_p8_s19095_1
    31793   9   AVAILABLE   /backup/database_PROD_20130808_p9_s19095_1
    31792   10  AVAILABLE   /backup/database_PROD_20130808_p10_s19095_1
    31764   11  AVAILABLE   /backup/database_PROD_20130808_p11_s19095_1
    31768   12  AVAILABLE   /backup/database_PROD_20130808_p12_s19095_1
    31787   13  AVAILABLE   /backup/database_PROD_20130808_p13_s19095_1
    31791   14  AVAILABLE   /backup/database_PROD_20130808_p14_s19095_1
    31763   15  AVAILABLE   /backup/database_PROD_20130808_p15_s19095_1
    31767   16  AVAILABLE   /backup/database_PROD_20130808_p16_s19095_1
    31786   17  AVAILABLE   /backup/database_PROD_20130808_p17_s19095_1

BS Key  Type LV Size       Device Type Elapsed Time Completion Time   
------- ---- -- ---------- ----------- ------------ --------------------
19057   Full    11.58M     DISK        00:00:01     08/AUG/2013 02:36:24
        BP Key: 31753   Status: AVAILABLE  Compressed: NO  Tag: TAG20130808T023623
        Piece Name: /backup/c-3195287633-20130808-00
  Control File Included: Ckp SCN: 3048178773   Ckp time: 08/AUG/2013 02:36:23
  SPFILE Included: Modification time: 15/MAY/2013 11:17:45

BS Key  Size       Device Type Elapsed Time Completion Time   
------- ---------- ----------- ------------ --------------------
19058   742.70M    DISK        00:04:59     08/AUG/2013 02:41:36
        BP Key: 31758   Status: AVAILABLE  Compressed: YES  Tag: TAG20130808T023635
        Piece Name: /backup/archivedlogs_PROD_20130808_p1_s19097_1

  List of Archived Logs in backup set 19058
  Thrd Seq     Low SCN    Low Time             Next SCN   Next Time
  ---- ------- ---------- -------------------- ---------- ---------
  1    70088   3048053610 07/AUG/2013 23:18:01 3048062243 07/AUG/2013 23:30:27
  1    70089   3048062243 07/AUG/2013 23:30:27 3048086304 08/AUG/2013 00:01:51
  1    70090   3048086304 08/AUG/2013 00:01:51 3048117667 08/AUG/2013 00:50:04
  1    70091   3048117667 08/AUG/2013 00:50:04 3048156318 08/AUG/2013 01:24:49
  1    70092   3048156318 08/AUG/2013 01:24:49 3048178827 08/AUG/2013 02:36:32
  1    70093   3048178827 08/AUG/2013 02:36:32 3048178838 08/AUG/2013 02:36:34

BS Key  Type LV Size       Device Type Elapsed Time Completion Time   
------- ---- -- ---------- ----------- ------------ --------------------
19059   Full    11.58M     DISK        00:00:00     08/AUG/2013 03:01:53
        BP Key: 31754   Status: AVAILABLE  Compressed: NO  Tag: TAG20130808T030153
        Piece Name: /backup/c-3195287633-20130808-01
  Control File Included: Ckp SCN: 3048186759   Ckp time: 08/AUG/2013 03:01:53
  SPFILE Included: Modification time: 08/AUG/2013 01:00:58

Y vemos lo mismo en SQLPLUS:

11:13:12 SYS@PROD> col handle for a45
11:13:12 SYS@PROD> col tag for a25
11:13:12 SYS@PROD> set linesize 120

11:13:12 SYS@PROD> select p.tag, piece#, handle
from v$backup_set s, v$backup_piece p
where s.CONTROLFILE_INCLUDED='NO' and s.backup_type='D' and p.deleted='NO'
and s.set_stamp=p.set_stamp and s.set_count=p.set_count
order by piece#;

TAG                           PIECE# HANDLE
------------------------- ---------- ---------------------------------------------
TAG20130807T233030                 1 /backup/database_PROD_20130807_p1_s19095_1
TAG20130807T233030                 2 /backup/database_PROD_20130807_p2_s19095_1
TAG20130807T233030                 3 /backup/database_PROD_20130807_p3_s19095_1
TAG20130807T233030                 4 /backup/database_PROD_20130808_p4_s19095_1
TAG20130807T233030                 5 /backup/database_PROD_20130808_p5_s19095_1
TAG20130807T233030                 6 /backup/database_PROD_20130808_p6_s19095_1
TAG20130807T233030                 7 /backup/database_PROD_20130808_p7_s19095_1
TAG20130807T233030                 8 /backup/database_PROD_20130808_p8_s19095_1
TAG20130807T233030                 9 /backup/database_PROD_20130808_p9_s19095_1
TAG20130807T233030                10 /backup/database_PROD_20130808_p10_s19095_1
TAG20130807T233030                11 /backup/database_PROD_20130808_p11_s19095_1
TAG20130807T233030                12 /backup/database_PROD_20130808_p12_s19095_1
TAG20130807T233030                13 /backup/database_PROD_20130808_p13_s19095_1
TAG20130807T233030                14 /backup/database_PROD_20130808_p14_s19095_1
TAG20130807T233030                15 /backup/database_PROD_20130808_p15_s19095_1
TAG20130807T233030                16 /backup/database_PROD_20130808_p16_s19095_1
TAG20130807T233030                17 /backup/database_PROD_20130808_p17_s19095_1

17 rows selected.

11:14:08 SYS@PROD> select 'set until sequence '||max_seq||';' texto
from (
 select min(sequence#) min_seq, max(sequence#) max_seq
 from  v$backup_set s, v$BACKUP_ARCHIVELOG_DETAILS r
 where s.set_count = r.id2
   and s.set_stamp = r.id1
   and s.backup_Type='L'
   and r.first_time>=(
       select start_time from v$backup_set s
       where s.CONTROLFILE_INCLUDED='NO' and s.backup_type='D'
        and (set_stamp, set_count) in (
        select set_stamp, set_count
        from v$backup_piece
        where deleted='NO')
   )
);

TEXTO
-------------------------
set until sequence 70093;

1 rows selected.

Con estas consultas y un poco de scripting se puede armar un buen script de recuperación.

Un saludo.