martes, 2 de agosto de 2016

Oracle Database 12.1.0.2: Introducción a "Full Database Caching"

Oracle Database 12.1.0.2: Introducción a "Full Database Caching"

Por Deiby Gómez Oracle ACE Director
Publicado en Octubre 2015
Hace ya varios meses fue liberada la versión de la base de datos 12.1.0.2 la cual contiene nuevas y muy buenas características relacionadas con el manejo de datos en memoria. Con el pasar del tiempo hemos ido viendo los cambios que ha sufrido la base de datos Oracle con respecto al manejo de los datos y cómo son alojados estos en el "Buffer Cache". En las primeras versiones se utilizaba un algoritmo llamado "LRU" o "Last Recent Used", que era meramente una lista, la cual cuando se llenaba desalojaba los bloques que llevaban más tiempo sin ser usados. Luego, empezamos a ver modificaciones a lo largo de las nuevas versiones que fueron surgiendo y ahora sabemos que este algoritmo ha cambiado un poco, fue mejorado y ahora el desalojamiento de los bloques en el Buffer Cache está dado más bien basándose en un contador y una fecha. El problema con el viejo LRU es que cada vez que un bloque que ya estaba dentro del Buffer Cache era usado, este se movía hasta la parte más alta de la lista (me refiero con “parte alta” a los bloques más recientemente usados). Cuando se necesitaba desalojar bloques para alojar a otros, lo que Oracle hacia era remover los últimos bloques de la lista (los últimos recientemente usados) y alojar a los nuevos en la parte mas alta. Cómo puedes ver la parte “alta” se vuelve popular y solicitada.
El problema de esto es que en un ambiente OLTP donde bloques de diferentes objetos son usados al mismo tiempo, se genera contención en esta lista pues se intenta poner muchos bloques en la parte más alta y al mismo tiempo. Para evitar esta contención en la parte alta de la lista, Oracle agregó un contador y también un campo donde  se guarda el tiempo en que dicho bloque se utilizó. Cada vez que un bloque es usado el contador es aumentado (también el tiempo es actualizado). Entonces, el contador nos dirá qué tan usado puede llegar a ser un bloque y que tanto tiempo ha pasado desde que fue usado. Se dice que el bloque es muy usado si el contador es alto, y poco usado si el contador es bajo, pero no solo el contador es tomado en cuenta a la hora de mover bloques hacia la parte alta de la lista, también el tiempo en que el bloque fue usado por ultima vez es tomado en cuenta.
Es posible tener un bloque que se leyó miles de veces lo cual nos dará como resultado un contador alto pero que fue usado por ultima vez hace 1 hora o más, ¿deberíamos de moverlo hasta arriba de la lista?  ¿Deberíamos catalogarlo como un bloque muy usado? es ahí donde no solo el contador es tomado en cuenta sino también la fecha que acompaña a dicho contador. Este tipo de preguntas son las que los algoritmos de Oracle tratan de resolver para tomar la mejor decisión posible. Es bueno aclarar también que no nos debemos basar al 100% en el contador, pues se ha demostrado que un bloque leído miles de veces en un lapso de tiempo muy pequeño puede llegar a mostrar un contador menor a un bloque se ha leído muchas veces (no miles) pero en un tiempo mucho más largo (Podríamos filosofar más al respecto pero tenemos las páginas limitadas en este artículo).
Hasta ahora ya saben brevemente cómo los objetos son desalojados, y esto lo debemos de tomar en cuenta ya que no tenemos memoria ilimitada. Tenemos un límite de GBs y debemos manejarlos muy bien.
El manejo de tablas muy grandes es un problema para esta cantidad de GBs con los que cuenta el Buffer Cache, pues si leemos un objeto grande podríamos desalojar a casi todos los bloques de los objetos más utilizados que ya están en el Buffer Cache, pues  todos los bloques de la tabla grande serían alojados y se marcarían como los más recientemente usados. ¿Estaría esto correcto? Podrían llegar a pensar que sí, pero ahora, supongamos que esta tabla grande la leemos una sola vez por semana, entonces ¿por qué desalojar los bloques que verdaderamente son muy utilizados en el día a día por nuestras aplicaciones por bloques que leemos una vez por semana? No vale la pena, e incluso podríamos recibir un bajo performance pues los bloques que en realidad eran muy utilizados y que ahora ya no existen en memoria, deberán ser leídos nuevamente desde disco.  Para resolver este problema Oracle aplica los siguientes criterios para las tablas grandes:
  • Las tablas pequeñas son alojadas en memoria  solamente si el tamaño total de la tabla es menor del 2% del tamaño total del Buffer Cache.
  • Para las tablas medianas (Algunos dicen que mediano es entre el 2% y el 10% del tamaño del Buffer Cache, pero no está confirmado), Oracle analiza la fecha en que la tabla fue escaneada por última vez, la fecha de la última utilización de los bloques que ya están en el Buffer cache, el tamaño de la tabla y el espacio libre en el Buffer Cache; en base a esta información Oracle decide si poner los bloques de esta tabla en el Buffer Cache o no. 
  • Las tablas grandes No son puestas en el Buffer Cache a menos que se indique explícitamente que estos bloques deberían ser puestos en el Buffer cache mediante la clausula "KEEP". Podríamos llegar a ver algunos bloques de estas tablas pero más bien son de metadatos.
Ahora vamos a hablar del otro lado de la moneda, en dónde tenemos una cantidad de GBs para nuestro Buffer Cache pero que dicha cantidad es aún mucho más grande del tamaño de nuestra base de datos. Ahora tenemos suficiente memoria para mantener todos los objetos de la base de datos en  el Buffer Cache, ¿Por qué debería seguir soportando los algoritmos de desalojo de bloques en el Buffer Cache? Si todo cabe en memoria no habría razón para desalojarlos. Oracle pensó en esto y es lo que nos viene a solucionar con su nueva característica presentada en la versión 12.1.0.2 llamada "Full Database Caching".
El "Full Database Caching" trata de poner dentro del Buffer Cache todos los bloques de todas las tablas que han sido leídos, Oracle asume que el tamaño del Buffer Cache es lo suficientemente grande como para alojar a la base de datos entera. Los criterios que anteriormente hemos explicado para las tablas pequeñas, medianas y grandes ya no aplicarían, a la base de datos ya no le interesa esta información y decide directamente poner estas tablas en memoria.
Cuando esta característica es activada, Oracle no carga inmediatamente todos los bloques de todos los objetos en memoria, sino más bien conforme se vayan leyendo los bloques se irán alojando en memoria. Así que actívala y deja que las aplicaciones lean datos y hagan la magia.
Si el "Full Database Caching" está activado en una base de datos de tipo Contenedor (CDB), entonces esta característica es aplicada a nivel del CDB, por lo tanto, todos los objetos de todas las bases de datos tipo "Pluggable" (PDB) que se encuentran en el CDB serán puestos en memoria.
Con esta característica todos los bloques son puestos en memoria incluso aquellos objetos que tienen la propiedad "NOCACHE", por ejemplo objetos LOB.
Para una base de datos no-RAC el tamaño lógico de nuestra base de datos debe de ser menor al tamaño total del Buffer Cache.
Para los ambientes de tipo Real Application Cluster (RAC), el tamaño lógico de la base de datos debe ser más pequeño que el tamaño del Buffer Cache de cada Instancia en particular que conforman la configuración de RAC y debe ser más pequeño que el 80% de la suma total del tamaño del Buffer Cache de cada una de las instancias. Para una configuración de RAC no se puede tener esta característica habilitada en unas instancias y en otras no, la característica aplica a la base de datos completa y a todas sus instancias por igual.
Para saber el tamaño del Buffer Cache para una instancia en particular se puede ejecutar la siguiente consulta:
SQL> SELECT NAME, BYTES/1024/1024/1024 GB  
     FROM V$SGAINFO WHERE NAME = 'Buffer Cache Size';
NAME                 GB
-------------------- ----------
Buffer  Cache Size   3.21875

El tamaño lógico de nuestra base de datos debería de ser menor a esta cantidad.
Para saber si una base de datos tiene activado el "Full Database Caching" se debe ejecutar la siguiente consulta:
SQL>  SELECT FORCE_FULL_DB_CACHING FROM V$DATABASE;
FORCE_FULL_DB_CACHING
---------------------
NO

Ahora veremos el comportamiento de una base de datos cuando no se tiene activada la característica "Full Database Caching", para ello se utilizarán las siguientes 3 tablas sobre las cuales se realizaran escaneos completos de cada una de ellas (Full Table Scan):
SQL> select table_name, blocks,  trunc((blocks*8192)/1024/1024,3) MB 
     from dba_tables where table_name in  ('BIG_TABLE','OTHER_BIG_TABLE','LITTLE_TABLE') order by 1;
TABLE_NAME           BLOCKS     MB
-------------------- ---------- ----------
BIG_TABLE            147473     1152.132
LITTLE_TABLE         143        1.117
OTHER_BIG_TABLE      147471     1152.117

"Full Database Caching" no activado
Limpiamos el SGA:
SQL>  shutdown immediate;
SQL> startup; 

Nota:
También puedes hacer un Flush.
Ahora vemos cuantos bloques de nuestras tablas se encuentran en el Buffer Cache:
SELECT o.object_name, COUNT(*) blocks,  trunc((COUNT(*)*8192)/1024/1024,3) size_MB 
FROM DBA_OBJECTS o, V$BH bh
WHERE o.data_object_id = bh.OBJD
AND o.owner ='DGOMEZ'
GROUP BY o.object_Name
ORDER BY COUNT(*);   
no rows selected 

Nota:
De ahora en adelante nos referiremos a esta consulta como "dgomez_cache_blocks.sql"
Como se pudo ver anteriormente no hay bloques de datos en el Buffer Cache de ninguna de nuestras tres tablas. Ahora voy a realizar un escaneo completo de la tabla:
SQL> select count(*)  from dgomez.big_table;
COUNT(*)
----------
10000000

Y verificamos cuantos Bloques fueron puestos en memoria:
SQL>  @dgomez_cache_blocks.sql
OBJECT_NAME     BLOCKS    SIZE_MB
--------------- --------  ----------
BIG_TABLE       1         0.007

La tabla no fue puesta en el Buffer Cache, ¿sabes por qué? Si en estos momentos estas diciendo que la razón es porque la tabla es muy grande (~>10% del Buffer Cache) entonces haz entendido todos los conceptos muy bien.
Intentemos poner otra tabla grande en el Buffer Cache:
SQL>  select count(*) from dgomez.other_big_table;
COUNT(*)
----------
10000000
SQL>  @dgomez_cache_blocks.sql
OBJECT_NAME         BLOCKS    SIZE_MB
------------------  --------- ----------
BIG_TABLE           1         0.007
OTHER_BIG_TABLE     1         0.007 

Tampoco esta tabla fue puesta en el Buffer Cache, porque también es catalogada como "grande". Ahora bien, qué pasaría si intentamos poner una tabla menor al 2% del Buffer Cache, que te parece si usamos nuestra tabla "LITTLE_TABLE" que tan solo tiene 1MB de tamaño:
SQL>  select count(*) from dgomez.little_table;
COUNT(*)
----------
10000
SQL>  @dgomez_cache_blocks.sql
OBJECT_NAME         BLOCKS    SIZE_MB
------------------  --------- ----------
BIG_TABLE           1         0.007
OTHER_BIG_TABLE     1         0.007
LITTLE_TABLE        132       1.031 
Como la tabla está catalogada como pequeña ahora todos sus bloques fueron puestos en el Buffer Cache. Tal parece que los conceptos se están cumpliendo.
Ahora les mostraré el comportamiento de la base datos cuando se tiene activada la característica "Full Database Caching".
"Full Database Caching" activado
Activamos la característica "Full Database Caching":
SQL>  shutdown immediate;
      Database  closed.
      Database  dismounted.

ORACLE  instance shut down.

SQL>  startup mount;

ORACLE  instance started.
Total  System Global Area   4294967296 bytes
       Fixed  Size             2932632 bytes
       Variable  Size        939524200 bytes
       Database  Buffers    3338665984 bytes
       Redo  Buffers          13844480 bytes
       Database  mounted.
SQL>  ALTER DATABASE FORCE FULL DATABASE CACHING;
Database  altered.
SQL>  alter database open;
Database altered.
     
Comprobamos que la característica fue activada correctamente:
SQL>  SELECT FORCE_FULL_DB_CACHING FROM V$DATABASE;  
FORCE_FULL_DB_CACHING
---------------------
YES

Verificamos los bloques de nuestras tablas que actualmente están en memoria:
SQL> @dgomez_cache_blocks.sql
no rows selected

Hacemos un escaneo completo de una tabla grande:
SQL>  select count(*) from dgomez.big_table;
COUNT(*)
----------
10000000
SQL> @dgomez_cache_blocks.sql
OBJECT_NAME     BLOCKS     SIZE_MB
--------------- ---------- ----------
BIG_TABLE       146838     1147.171

Como pueden ver, a Oracle ya no le interesa si la tabla es pequeña, mediana o grande, simple y sencillamente aloja todos sus bloques en memoria.
Leemos otra tabla grande para verificar si alguno de los bloques son desalojados.  En otras palabras confirmaremos si el algoritmo LRU+contador+fecha es utilizado:
SQL>  select count(*) from dgomez.other_big_table;
COUNT(*)
----------
10000000  
   

OBJECT_NAME                BLOCKS     SIZE_MB
-------------------------  ---------- ----------
OTHER_BIG_TABLE            146836     1147.156
BIG_TABLE                  146838     1147.171

Ahora vemos que la segunda tabla también fue alojada en memoria en su totalidad y los bloques de la primer tabla no fueron tocados, no fueron desalojados. Lo que esta haciendo Oracle en estos momentos es sencillamente ir acumulando todos los bloques que se han leído hasta el momento, y lo seguirá haciendo hasta que la base de datos completa esté en memoria. Ningún bloque es desalojado.
Leemos finalmente nuestra tabla pequeña:
SQL> @dgomez_cache_blocks.sql
SQL> select count(*) from dgomez.little_table;
COUNT(*)
----------
10000
   

OBJECT_NAME         BLOCKS     SIZE_MB
------------------  ---------- ----------
LITTLE_TABLE        132        1.031
OTHER_BIG_TABLE     146836     1147.156
BIG_TABLE           146838     1147.171

Nuevos bloques se han agregado y ninguno ha sido desalojado.

Si eres un DBA curioso como yo, estarás pensando a estas alturas sobre cuál es el comportamiento de la base de datos cuando "Full Database Caching" está activado pero el Buffer Cache es más pequeño que el tamaño total de la base de datos. Bueno, lamento profundamente decirte que no recibirás un ORA-600. Para no perder la buena costumbre de apoyar a la curiosidad te mostraré con ejemplos dicho comportamiento:
Comprobamos si  "Full Database Caching" está activado:
SQL> SELECT FORCE_FULL_DB_CACHING FROM V$DATABASE;  
FORCE_FULL_DB_CACHING
--------------------
YES

Comprobamos que nuestro Buffer Cache solo soportaría una de nuestras tablas "grandes":
SQL> SELECT  NAME, BYTES/1024/1024/1024 GB 
     FROM V$SGAINFO WHERE NAME = 'Buffer Cache Size';
NAME                      GB
------------------------  ----------
Buffer Cache Size         1.53125

Vemos los bloques que están en memoria de nuestras tablas:
SQL>  @dgomez_cache_blocks.sql
no rows selected

Realizamos un escaneo completo de la primer tabla "grande":
SQL>  select count(*) from dgomez.big_table;
COUNT(*)
----------
10000000

Verificamos que la tabla haya sido puesta en memoria:
SQL>  @dgomez_cache_blocks.sql
OBJECT_NAME   BLOCKS   SIZE_MB
------------- -------- ----------
BIG_TABLE     146838   1147.171

Realizamos el escaneo completo de la segunda tabla:
SQL> select  count(*) from dgomez.other_big_table;
COUNT(*)
----------
10000000

Vemos el comportamiento:
SQL>  @dgomez_cache_blocks.sql
OBJECT_NAME         BLOCKS     SIZE_MB
------------------  ---------- ----------
BIG_TABLE           42794      334.328
OTHER_BIG_TABLE     146836     1147.156

Al parecer Oracle sigue usando un poco del "Full Database Caching" pues la segunda tabla grande fue puesta en memoria completamente y no fue usado ninguno de los criterios de carga en memoria de "tablas grandes" ([0%,<2>=2%,<=10%+tiempo],[>10%,100%]), pero como es obvio algunos bloques de la primer tabla grande (BIG_TABLE) fueron desalojados, tantos bloques como se necesitaron para poner en memoria la segunda tabla grande (OTHER_BIG_TABLE).
Veamos qué pasaría si escaneamos nuevamente la primer tabla grande (BIG_TABLE):
SQL> SQL>  select count(*) from dgomez.big_table;
COUNT(*)
----------
10000000
SQL>  @dgomez_cache_blocks.sql
OBJECT_NAME           BLOCKS     SIZE_MB
--------------------  ---------- ----------
OTHER_BIG_TABLE       41916      327.468
BIG_TABLE             146838     1147.171

Ahora se desalojaron bloques de la tabla "OTHER_BIG_TABLE", todos los que se necesitaron para poder alojar en memoria la tabla "BIG_TABLE" en memoria.
Mi recomendación es que si el Buffer Cache no tiene la suficiente cantidad de memoria para alojar la base de datos en su totalidad no actives esta características, pues si la activas observarás grandes cantidades de bloques siendo desalojados lo cual no es optimo. Te recomiendo que dejes a Oracle decidir y aplicar sus criterios y algoritmos para alojar y desalojar bloques.
Como ultimo punto te mostraré como desactivar el  "Full Database Caching", para esto únicamente debes seguir los siguientes pasos:
SQL>  shutdown immediate;.
SQL>  startup mount;
ORACLE  instance started.
SQL>  ALTER DATABASE NO FORCE FULL DATABASE CACHING;
Database  altered.
SQL> alter database open;
Database altered.

No hay comentarios: