Estas usando filesystems 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 filesystems 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 los encuentra allí los 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
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 este 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 archivo 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
¿File buffer cache o Direct I/O?
$ cat 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 &
$ cat 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
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.
$ cat 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 &
$ cat 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
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
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 Boost application performance using asynchronous I/O, y de las notas:
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 |
432854.1 | Asynchronous I/O Support on OCFS/OCFS2 and Related Settings: filesystemio_options, disk_asynch_io |