lunes, 10 de noviembre de 2008

¿Cómo acelerar el acceso a los file systems?

Estas usando file systems para tu base de datos y todo opera en aparente normalidad, pero ¿sabías que por defecto Oracle no aprovecha todas las mejoras que los file systems modernos tienen implementados? Pues esa es la cruda realidad, si te interesa saber cómo puedes sacarle el jugo al I/O de tus discos con solo cambiar un parámetro, entonces debemos empezar por algo de teoría no Oracle.


File Buffer Cache
Un archivo no es más que una colección de bits almacenados en un medio persistente. Cuando un proceso requiere acceder a los datos de un archivo, el sistema operativo los lleva a la memoria principal, donde el proceso puede hacer uso del mismo, modificarlo, y luego solicitar que sea nuevamente guardado en disco. Pero teniendo en mente que los discos son mucho más lentos que la memoria principal, los sistemas operativos hacen normalmente uso de un buffer en memoria, llamado file buffer cache. Como resultado, el sistema operativo primero intenta obtener los datos del buffer cache, si no la encuentra allí la lee de disco y la coloca en el buffer cache. De forma similar, las escrituras también pasan por el buffer cache de forma que las futuras lecturas puedan ser satisfechas sin necesidad de acceder a los discos.

Direct I/O
Hasta acá el uso del file buffer cache parecería beneficioso, pero no olvidemos que Oracle ya tiene su propia implementación: el database buffer cache. Al ser el propio Oracle quien controla qué bloques requieren de permanecer o no en el cache, bajo un algoritmo LRU con el que no cuenta el sistema operativo, la existencia del file buffer cache puede resultar indeseable e innecesario al tener que viajar los datos primero al file buffer cache y luego al database buffer cache, conllevando a un consumo adicional de CPU y también de memoria principal, la cual ya no está disponible para Oracle. Tomando esto en cuenta, hace su aparición el Direct I/O, como una forma de evitar el uso del file buffer cache, de hecho es la forma con que se interactúa con los raw devices (porciones de disco no formateados); en la actualidad prácticamente todos los sistemas operativos soportan direct I/O y en algunos casos incluso versiones más sofisticadas como concurrent i/o provisto por IBM con JFS2.

Habilitando Direct I/O
En Oracle existe una forma de controlar la forma en que se interactúa con los file systems, se trata de filesystemio_options, que acepta como valores:
  • none. La grabación será síncrona con bloqueo, es decir la aplicación espera a que la llamada al sistema se complete antes de poder hacer una nueva llamada. Es universalmente soportada pero es la más forma más lenta.
  • asynch. La aplicación no espera a que la llamada al sistema se complete, puede realizar otras tareas mientras espera la confirmación de la llamada previa.
  • directio. La grabación es síncrona sin pasar por el file buffer cache (las dos modalidades previas hacen uso del file buffer cache).
  • setall. La grabación es asíncrona y sin pasar por el file buffer cache. Presenta la posibilidad de máximo desempeño.

Dependiendo de la plataforma Oracle le asigna a éste parámetro el valor por defecto none (ejm. Linux) o asynch (ejm. Solaris), para habilitar direct I/O los valores a escoger han de ser directio o mejor aún setall (asynch + directio). Este parámetro no se puede cambiar online por lo que luego de aplicar el cambio en el spfile.ora debes reiniciar la base de datos para que entre en efecto.

SYS@orcl > show parameter filesystemio_options

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
filesystemio_options                 string      none

SYS@orcl > alter system set filesystemio_options=setall scope=spfile;

System altered.

SYS@orcl > shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.

SYS@orcl > startup
ORACLE instance started.

Total System Global Area  209715200 bytes
Fixed Size                  1279360 bytes
Variable Size             109054592 bytes
Database Buffers           96468992 bytes
Redo Buffers                2912256 bytes
Database mounted.
Database opened.

Estamos probando con Linux, de allí el valor none, ahora para verificar que estamos trabajando con asynch y directio en simultáneo, podemos valernos de algunos utilitarios propios de Linux, pero que tienen sus similares en otros sistemas operativos. Primero veamos la situación original, es decir cuando filesystemio_options=none.

SYS@orcl> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
SYS@orcl> startup mount;
ORACLE instance started.

Total System Global Area  205520896 bytes
Fixed Size                  1266608 bytes
Variable Size             142609488 bytes
Database Buffers           58720256 bytes
Redo Buffers                2924544 bytes
Database mounted.

$ ps -ef | grep dbw
oracle   28532     1  0 16:56 ?        00:00:00 ora_dbw0_orcl
SYS@orcl> alter database open;

Database altered.
$ more /tmp/trace_dbwr.out
. . .
open("/u02/oradata/ORCL/datafile/o1_mf_system_3wqn5ypm_.dbf", O_RDWR|O_SYNC|O_LARGEFILE) = 17
. . .

$ cat /proc/slabinfo | grep kio
kioctx   18  30  256  15  1 : tunables  120  60  0 : slabdata  2  2  0
kiocb     0   0  128  31  1 : tunables  120  60  0 : slabdata  0  0  0

Ahora con filesystemio_options=setall.

$ more /tmp/trace_dbwr.out
. . .
open("/u02/oradata/ORCL/datafile/o1_mf_system_3wqn5ypm_.dbf", O_RDONLY|O_DIRECT|O_LARGEFILE) = 17
. . .
$ cat /proc/slabinfo | grep kio
kioctx   17  30  256  15  1 : tunables  120  60  0 : slabdata  2  2 0
kiocb     7  31  128  31  1 : tunables  120  60  0 : slabdata  1  1 0

Que DBWR abra los datafiles con O_DIRECT es señal del uso de Direct I/O, mientras que la presencia de valores distintos de cero para kiocb lo es del uso de Asynch I/O.

¿File buffer cache o Direct I/O?
La decisión no es simple, antes de optar por uno u otro debemos tener en cuenta cómo puede afectar a nuestras aplicaciones, para empezar vemos el caso de una aplicación simulada que es intensiva en lecturas aleatorias, lo cual es típico de las aplicaciones OLTP.

Random.sh
sqlplus / as sysdba << EOF
alter system flush buffer_cache;
exit;
EOF
sqlplus /nolog @Random.sql &
sqlplus /nolog @Random.sql &
sqlplus /nolog @Random.sql &
sqlplus /nolog @Random.sql &

Random.sql
connect test/test
set timing on
alter session set events '10046 trace name context forever, level 8';
begin
  for i in 1..200000 loop
     execute immediate
        'select data from testio where id = :id'
     using ROUND(dbms_random.value(1,200000));
  end loop;
end;
/
exit

Analizando los tiempos con tkprof:

filesystemio_options=none

select data
from
 testio where id = :id

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.00       1.38          0          0          0           0
Execute 800000     51.29     172.13          1          1          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   800004     51.30     173.51          1          1          0           0

===
filesystemio_options=setall

select data
from
 testio where id = :id

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.00       0.00          0          0          0           0
Execute 800000     38.84     128.18          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   800004     38.85     128.18          0          0          0           
0

Ahora con una aplicación intensiva en Full Table Scans, típico de las aplicaciones DSS.

Full.sh
sqlplus / as sysdba << EOF
alter system flush buffer_cache;
exit;
EOF
sqlplus /nolog @Full.sql &
sqlplus /nolog @Full.sql &
sqlplus /nolog @Full.sql &
sqlplus /nolog @Full.sql &

Full.sql
connect test/test
set timing on
alter session set events '10046 trace name context forever, level 8';
declare v_data testio.data%type;
begin
for i in 1..25 loop
select max(data) into v_data from testio;
end loop;
end;
/
exit

Resumen de traces analizados con tkprof.

filesystemio_options=none

SELECT MAX(DATA)
FROM
 TESTIO

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.00       0.00          0          0          0           0
Execute    100      0.01       0.01          0          0          0           0
Fetch      100     61.89     232.97     503813    1446600          0         100
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      204     61.91     232.99     503813    1446600          0         100

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file sequential read                       146        0.00          0.00
  db file scattered read                      13771        0.67         19.53 

====
filesystemio_options=setall

SELECT MAX(DATA)
FROM
 TESTIO

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.01       1.22          1          1          0           0
Execute    100      0.02       0.06          0          0          0           0
Fetch      100     70.63     366.62     259438    1446600          0         100
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      204     70.66     367.91     259439    1446601          0         100

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  read by other session                       20820        0.24        133.64
  db file scattered read                       8942        0.99        100.00 
  latch: cache buffers chains                   193        0.02          0.73
  db file sequential read                       302        0.07          1.39

Con filesystemio_options=setall las lecturas aleatorias mejoraron en 26%, mientras que los Full Table Scans empeoraron en casi 58%!

Conclusiones
Revisando los resultados de los escenarios simulados, podemos concluir que, ante la activación de direct I/O, las aplicaciones intensivas en acceso aleatorio a los discos se ven beneficiadas mientras que las intensivas en Full Table Scans se ven perjudicadas, de allí que no debemos saltar a la conclusión de que con solo habilitar el direct I/O nuestra base de datos será mágicamente más rápida, es posible que así sea, como también es posible que el impacto sea nulo e incluso adverso, primero hagan las pruebas del caso durante un periodo de carga típica antes de decidir dejarlo permanentemente. Lo que sí es seguro que mejorará el desempeño es el uso de filesystemio_options=asynch, por lo que es lo mínimo con lo que deberías configurar tu base de datos.

Cada plataforma tiene sus particularidades en cuanto a la aplicabilidad de los diversos valores para filesystemio_options e incluso hay algunos bugs específicos para ciertas combinaciones de versión de Oracle + versión de Sistema Operativo, por ello es conveniente que te informes más del tema. Te recomiendo la lectura de los Notes 462072.1 File System's Buffer Cache versus Direct I/O, 555601.1 How To Verify Whether DIRECTIO is Being Used, 237299.1 How To Check if Asynchronous I/O is Working On Linux y 432854.1 Asynchronous I/O Support on OCFS/OCFS2 and Related Settings: filesystemio_options, disk_asynch_io, finalmente Boost application performance using asynchronous I/O.

Post Relacionados:

¿Te pareció interesante este artículo?, ¿te quedaron algunas dudas?, ¿quieres sugerirme un tema a tratar?, pues déjame tus comentarios o envíame un email y para que NO te pierdas ningún Post, suscríbete por email ahora mismo!

5 comentarios, agrega el tuyo!

uliseS_klein dijo...

hola Kike;
se tendra algun tip para base sobre ASM... u OCFS

Enrique Orbegozo dijo...

Hola Ulises, te recomiendo darle una lectura al Note 432854.1 Asynchronous I/O Support on OCFS/OCFS2 and Related Settings: filesystemio_options, disk_asynch_io. Allí se indica que Oracle usa por default Direct I/O cuando se trata de OCFS, OCFS2 y ASM, solamente te queda la opción de usar o no Asynch I/O, si se desea usarlo entonces hay que establecer filesystemio_options=setall.

baraka dijo...

hola Kike, disculpa la ignorancia. Como puedo hacer para un proceso que aparece 'activo' pero está en espera del evento db file scattered read. Y no me bloquee mas las tablas

Enrique Orbegozo dijo...

Hola Baraka, que la sesión esté en espera del evento db file scattered read es una señal de que la tabla en cuestión está siendo leída por un Full Table Scan, es decir en su totalidad, si esto no es correcto y debería estar usando algún índice entonces tienes que revisar la sintaxis de la sentencia SQL para que haga uso del mismo o si falta un índice entonces debes crearlo.

Dan dijo...

Gracias por la explicación Enrique.
Consulta para el caso de las bases de datos sobre Windows, este parámetro seteado a setall podría darnos alguna mejora?