Archive for the ‘Performance’ Category

enq: US – contention, buffer busy waits y automatic tuning de undo_retention

October 12, 2007

Cuando aparecen estos sintomas en Oracle 10gR2 y tienes desactivado el autoextend en los datafiles de los tablespace de undo:

1. enq: US – contention, contencion de Undo Segments

2. aumento constante de v$waitstat.file header block

3. buffer busy waits sobre los bloques de la cabecera de los datafiles de UNDO, esto se ve en p1 y p2 de v$session

4. v$undostat.tuned_undoretention tiene un valor exagerado

5. el valor de dc_rollback_segments v$rowcache es exagerado (incluso llegando a negativo)

6. El UNDO esta muy “lleno”

7. Hay muchos segmentos de Rollback en estado Offline, cientos

Activa el autoextend de los datafiles de UNDO con un maxsize (el mismo que el tamaño actual es suficiente). La combinacion de estos sintomas podria generar un aumento de consumo de CPU del 5% a 10% (principalmente por los buffer busy waits)

Esto paso en un RAC de 4 nodos donde solo dos nodos mostraban estos sintomas sin embargo creo que un mono-instancia tambien podria pasar. Tiene que haber bastantes modificaciones como inserciones constantes.

El problema parece que esta relacionado con el tuning automatico de retencion de undo…. Esta reportado como bug 5749075 pero el workaround que ofrece no es el unico, poner autoextend on es mucho mas simple y menos intrusivo (no tienes que tocar parametros ocultos)

JDBC 9.x y las variables de Timestamp de Java

October 12, 2007

Yo personalmente no programo en Java, lo hice hace casi 10 años y desde hace 8 no lo he vuelto a hacer asi que la verdad es que ni se exactamente como se declara una variable de fechas en Java.

Hace poco en un cliente donde tenian bastantes problemas de rendimiento en una de sus aplicaciones el cual es de una muy elevada criticidad (afecta a los que viajamos ;-) ). La aplicacion esta hecha por J2EE sobre Oracle 9iR2 y como servidor de aplicaciones Bea Weblogic. Se veian queries muy simples contra tablas correctamente indexados realizando Full Table Scan, no era trivial porque lanzaba las mismas queries desde SQLPLUS y me iba de fabula asi que como casi siempre me acorde del bind peeking (los malditos!) pero despues de revisar unas 6 docenas de queries donde algunas no usaban bind variables y tenian el mismo problema de ir rapido en SQLPLUS y lento desde la aplicacion.

Despues de estar unos dias traceando y analizando las 200 trazas que genere me di cuenta que habia muchas ocurrencias de “Dump of memory” en las variables bind. Hablando con los desarrolladores los cuales me comentan que pasan las fechas de java como variables de timestamp a la base de datos. En las trazas muestar algo parecido a esto:

bind 5: dty=180 mxl=11(11) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0 offset=120
bfp=40759cbc bln=11 avl=07 flg=01
value=
Dump of memory from 0×40759CBC to 0×40759CC3
40759CB0 0C0A6B78 [xk..]
40759CC0 00010101 [....]

Cuando value deberia de aparecer algo asi: value=”9/20/2007 9:20:0″ (dty=180 indica que la variable es del tipo Timestamp)

Cual es el problema? Es la version de JDBC, a partir de la version 9.x el JDBC deja de convertir las variables Timestamp de Java a Date, envia directamente a la base de datos tal cual porque Oracle 9i ya soporte este tipo de datos. La consecuencia es que el motor de la base de datos recibe la variable Timestamp y ve que el campo es un Date por lo tanto le aplica implicitamente un TO_DATE evitando el uso del indice.

La solucion es añadir v8compatible en las propiedades del JDBC. Y este problema ya he lo he visto en mas de un cliente.

Adaptive Cursor Sharing II

August 31, 2007

Siguiendo con el post anterior he probado con otra distribucion de datos y pasando las variables de STATE del 0 al 10:


     STATE CNT_PER_STATE STATE_PERCENT
---------- ------------- -------------
         0         10000            10
         1            10           .01
         2         15000            15
         3            10           .01
         4            10           .01
         5         22000            22
         6            10           .01
         7            10           .01
         8            10           .01
         9         15000            15
        10         37940         37.94  

Planes generados   

SQL_FULLTEXT                                                                     SQL_ID        PARSE_CALLS EXECUTIONS VERSION_COUNT INVALIDATIONS
-------------------------------------------------------------------------------- ------------- ----------- ---------- ------------- -------------
select  /* ACS-LSCC */                                                           1pq13qpqfza6x          11         11             7             0
        id, state, count(*)
from t1
where state = :state   

Y en vez de 6 planes ahora genera 7 asi que parece que la sospecha es correcta. El CBO hace bind peeking hasta que encuentre un punto estable, que puede ser despues de varias consultas y ve que la selectividad es my similar y para ahi.

Adaptive Cursor Sharing I (ACS)

August 31, 2007

Esta semana que he estado probando lo de Adaptive Cursor Sharing mencionado en Mas alla de Bind Peeking. Hace cosas raras, hay que estudiarlo mas a fondo pero funciona, ahora lo veremos.

Generamos datos para tener una distribucion de datos no-uniforme:


SQL> @create
SQL> drop table t1;   

Table dropped.   

SQL>
SQL> create table t1
  2  (
  3          id      number,
  4          text1   varchar2(20),
  5          text2   varchar2(20),
  6          state   number
  7  );   

Table created.   

SQL>
SQL> insert into t1
  2     with tdata as
  3          (select rownum id
  4             from all_objects
  5            where rownum <= 1000)
  6     select rownum,
  7            dbms_random.string ('u', 20),
  8            dbms_random.string ('u', 20),
  9            case
 10                   when rownum/100000  create index t1_i1
  2  on t1(state);   

Index created.   

SQL>
SQL> exec dbms_stats.gather_table_stats('LSC', 'T1', ESTIMATE_PERCENT => 20, -
>                                    METHOD_OPT => 'FOR COLUMNS STATE SIZE 10', CASCADE => TRUE); -- PASAR HISTOGRAMS PARA QUE PODAMOS USAR ESTA FUNCIONALIDAD   

PL/SQL procedure successfully completed.   

SQL>
SQL> select state, count(*) cnt_per_state, (count(*)*100)/nr state_percent
  2  from t1, (select max(id) nr
  3              from t1)
  4  group by state, nr;   

     STATE CNT_PER_STATE STATE_PERCENT
---------- ------------- -------------
         2            10           .01
         0            10           .01
         3            10           .01
         8            10           .01
         1            10           .01
        10         99900          99.9
         5            10           .01
         4            10           .01
         7            10           .01
         9            10           .01
         6            10           .01   

11 rows selected.  

Hemos generado unos datos que teoricamente utilizaremos indice para esos STATE que no sean el 10. Pues bien a principio esto era muy confuso porque no importaba el valor el caso es que nunca dejaba de usar el indice, incluso para el STATE 10 (deberia hacer un FTS). La prueba era usando esta query y pasando los valores del 0 a 10 a la bind variable :STATE, hasta llegar las 11 ejecuciones.


set autotrace trace exp stat
var state number   

exec :state := 0
select  /* ACS-LSCC */
        id, state, count(*)
from t1
where state = :state
group by id, state;   

pause Press enter to continue   

exec :state := 1
select  /* ACS-LSCC */
        id, state, count(*)
from t1
where state = :state
group by id, state;   

pause Press enter to continue   

exec :state := 2
select  /* ACS-LSCC */
        id, state, count(*)
from t1
where state = :state
group by id, state;   

pause Press enter to continue
etc etc ....................... hasta 10   

y durante cada ejecucion en otra sesion monitorizando v$sqlstats con esta query   

select /* ACS-QRY */
sql_fulltext, sql_id, parse_calls, executions, version_count, invalidations
from v$sqlstats
where sql_fulltext like '%ACS-LSC%'
  and sql_fulltext not like '%ACS-QRY%'   

El caso es que el VERSION_COUNT siempre se quedaba en uno, es decir que no generaba mas que un plan de ejecucion, y me pregunto pero esto funciona? Hasta que le di la vuelta a las variables, en vez de pasar del 0 a 10 del 10 a 0, y magicamente a partir del valor 8 (el 9 ni se inmutaba) empieza a generar otro plan, el 7 generaba otro, el 6 otro pero el 5, 4 no han generado ningunplan nuevo hasta el STATE 3. Curioso para 11 valores donde 10 son iguales me ha generado 6 planes de ejecucion.


Estado final usando los valores del 10 al 0   

SQL_FULLTEXT                                                     SQL_ID        PARSE_CALLS EXECUTIONS VERSION_COUNT INVALIDATIONS
---------------------------------------------------------------- ------------- ----------- ---------- ------------- -------------
select  /* ACS-LSCC */                                           1pq13qpqfza6x          11         11             6             0
        id, state, count(*)
from t1
where state = :state  

Y los 6 planes de ejecucion donde solo voy a demostrar 2 porque el resto son iguales pero con diferente coste en Ilas operaciones de NDEX RANGE SCAN y TABLE ACCESS BY INDEX ROWID:


SQL> @XPLAN
SQL> select *
  2  from table(dbms_xplan.display_cursor('1pq13qpqfza6x', 0));   

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
SQL_ID  1pq13qpqfza6x, child number 0
-------------------------------------
select  /* ACS-LSCC */         id, state, count(*) from t1 where state
= :state group by id, state   

Plan hash value: 136660032   

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   245 (100)|          |
|   1 |  HASH GROUP BY     |      |     1 |    16 |   245   (3)| 00:00:03 |
|*  2 |   TABLE ACCESS FULL| T1   | 99998 |  1562K|   240   (1)| 00:00:03 |
---------------------------------------------------------------------------   

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

   2 - filter("STATE"=:STATE)   

SQL> select *
  2  from table(dbms_xplan.display_cursor('1pq13qpqfza6x', 1));   

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
SQL_ID  1pq13qpqfza6x, child number 1
-------------------------------------
select  /* ACS-LSCC */         id, state, count(*) from t1 where state
= :state group by id, state   

Plan hash value: 2629508439   

--------------------------------------------------------------------------------------
| Id  | Operation                    | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |       |       |       |     3 (100)|          |
|   1 |  HASH GROUP BY               |       |     1 |    16 |     3  (34)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1    |     5 |    80 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | T1_I1 |     5 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------   

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

   3 - access("STATE"=:STATE)

La verdad es que no entiendo muy bien lo de 6 planes, pero pensando bien puede ser asi es ACS, se adapta a los diferentes valores y logicamente no va a parar con dos planes porque podria tener perfectamente 2,3 o 4 valores que necesiten FTS y no ir por el indice. Por ejemplo si tuviesemos esta distribucion de datos es logico hacer bind peeking cada dos por tres.

      STATE CNT_PER_STATE STATE_PERCENT
---------- ------------- -------------
         0         10000            10
         1            10           .01
         2         15000            15
         3            10           .01
         4            10           .01
         5         22000            22
         6            10           .01
         7            10           .01
         8            10           .01
         9         15000            15
        10         37940         37.94

El caso es que esto parece un arma de doble filo ya que dependiendo de los valores que hay en el campo podriamos terminar con muchos cursores hijos. Habra que subir el shared pool cuando upgradeamos a 11g ó aumentar MEMORY_TARGET, MEMORY_MAX_TARGET si vamos a usar el Automatic Memory Management (AMM).
De todos modos si no generamos ningun tipo de histogramas esto no supondria ningun problema.

Y como dije en otro post que pasaria con PL/SQL? Usamos el siguien Procedure para ver lo que pasa.


create or replace procedure LSC$ASC
as
        l_id    number;
        l_state number;
        l_cnt   number;
begin
        for i in reverse 1..10
        loop
                select /* ACS-LSC */
                       id, state, count(*)
                into   l_id, l_state, l_cnt
                from   t1
                where state = i
                  and rownum < 2
                group by id, state;
        end loop;
end;
/   

resultado   

SQL_FULLTEXT                                                                     SQL_ID        PARSE_CALLS EXECUTIONS VERSION_COUNT INVALIDATIONS
-------------------------------------------------------------------------------- ------------- ----------- ---------- ------------- -------------
SELECT ID, STATE, COUNT(*) FROM T1 WHERE STATE = :B1 AND ROWNUM < 2 GROUP BY ID, 52r38wma52qbp           1         10             1             0   

Como sospechaba, como PL/SQL no parsea mas que una vez (por eso nos gusta PL/SQL) solamente genera un plan de ejecucion.

Finalmente lo de que no genera planes diferentes si pasamos del 1 a 10 sospecho que es porque despues de varios intentos el CBO ve que el plan sigue siendo el mismo y asume que no es necesario generar planes diferentes. Lo tendre que probarlo.

Mas alla de Bind Peeking

August 17, 2007

Estoy seguro que muchos de nosotros hemos tenido algun problema de mayor o menor escala con Bind Peeking introducido en Oracle 9i, especialmente los que migramos hace años de 8i a 9i.

Lo que hace bind peeking es si se utiliza las variables bind el primer parsing de un cursor (sentencia SQL) la variable se sustituye por el valor real y con ese valor se genera el plan de ejecucion, esto esta bien si todas las consultas usan ese valor que se ha generado el plan. Por ejemplo cuando consultas contra una tabla (con 500000 resgirtos) de facturas y quieres consultar las pendientes (estado), que podria representar solamente el 1% de la tabla, lo ideal es que el optimizador utilice un indice sobre el campo de estado, imaginense el primer parsing de la query es una consulta que en vez de consultar por el estado pendiente va por el estado cobrado, el plan de ejecucion seria un Full Table Scan sin embargo el resto de la gente que van a consultar por el estado pendiente seran las victimas de ese plan, una consulta que tarda millisegundos ppdria llegar a tardar hasta 30 segundos. (siempre partiendo con la idea de que la tabla tiene histogramas sobre el campo estado) Lo que es peor es que la consulta depende de los dias funciona bien ó mal y genera inestabilidades.

El bind peeking introduce de alguna manera alternativas e inteligencia que no existia en las versiones anteriores sin embargo se ha comprobado en numerosos casos que son los causantes de los problemas de rendimiento. De hecho aplicaciones como SAP, Siebel recomiendan deshabilitar bind peeking. En Oracle E-Business Suite me ha dado algun problemillas tambien.

Se ha visto que un plan de ejecucion no es optimo para todo los valores posibles de bind por lo tanto es necesario algun tipo de mecanismo mas inteligente que sea capaz de generar un plan de ejecucion por cada tipo de bind. En Oracle 11g se ha introducido el concepto de cursores inteligentes (Adapative Cursor Sharing) y esto ayudara a generar los posibles planes de ejecucion para los diferentes valores de bind. Con esto supongo que se pretende eliminar los problemas que hemos sufrido por el bind peeking. Pero habria que estudiarlo muy bien porque parece que necesita varios parsing de un cursor para llegar a un punto estable y tener todos los planes de ejecucion, y parece ser que es necesario Soft Parsing para que esto funcione (que pasaria con Packages de PL/SQL?).

En cuando pueda pondre un ejemplo demostrando la funcionalidad.