Há vários meses foi lançada a versão do banco de dados Oracle 12.1.0.2, que contém diversas novas características relativas ao tratamento de dados em memória. Com o passar do tempo fomos observando as alterações sofridas pelo banco de dados Oracle em relação à gestão de dados e como estes são armazenados no Buffer Cache. Em versões anteriores, um algoritmo chamado LRU ou Last Recent Used, era apenas uma lista, que quando estava cheia desalocava os blocos que estavam a mais tempo sem serem utilizados. Logo, começamos a ver mudanças ao longo das novas versões que surgiram e sabemos que este algoritmo mudou um pouco, foi melhorado e agora desalocar os blocos no Buffer Cache é baseado em um contador e no tempo. O problema com o velho LRU é que cada vez que um bloco que já estava no Buffer Cache era usado, ele se movia para o topo da lista (nos referimos ao topo da lista os blocos mais recentemente usados). Quando era necessário desalocar blocos para acomodar outros, o que o Oracle fazia era remover os últimos blocos da lista (último recentemente usado) e acomodar os novos na parte mais alta. Como você pode ver a parte “alta“ se torna popular e muito solicitada.
O problema com isto é que, em um ambiente OLTP onde os blocos de diferentes objetos são utilizados ao mesmo tempo, é gerada uma contenção nesta lista. Isto ocorre porque o Oracle tenta colocar muitos blocos no topo e ao mesmo tempo. Para evitar esta contenção na parte superior da lista, o Oracle adicionou um contador e também um campo onde o tempo é salvo quando o bloco é usado. Sempre que um bloco é usado o contador é incrementado (o tempo também é atualizado). Em seguida, o contador nos dirá quanto tempo se passou desde que ele foi usado. Diz-se que o bloco é muito utilizado se o contador é alto e pouco usado se o contador é baixo, mas não apenas o contador é levado em conta quando se desloca blocos para o início da lista, também o momento em que o último bloco foi utilizado é levado em consideração.
Você pode ter um bloco que foi lido milhares de vezes que nos dará como resultado um contador alto, mas foi utilizado pela última vez a uma hora atrás ou mais. Deveríamos move-lo para o topo da lista? Devemos catalogar como um bloco muito usado? É aí que não só o contador é levado em conta, mas também a data que acompanha este contador. Tais perguntas são as que os algoritmos do Oracle tentam responder para tomar a melhor decisão possível. É bom esclarecer que não devemos basear 100% sobre o contador, pois foi demostrado que um bloco lido milhares de vezes ao longo de um período de tempo muito pequeno pode mostrar um contador menor que um bloco que foi lido muitas vezes (não milhares), mas em um tempo muito mais longo (Poderíamos filosofar sobre isso, mas temos as páginas limitadas neste artigo).
Então agora você sabe brevemente como os objetos são desalocados, e isso deve ser levado em conta já que não possuímos memória ilimitada. Nós temos um limite de GBs e devemos administrar bem.
O gerenciamento de tabelas muito grandes é um problema para essa quantidade de GBs que estão alocados para o Buffer Cache, pois se efetuarmos a leitura de um objeto grande poderíamos desalocar quase todos os blocos dos objetos mais usados que já estão no Buffer Cache. Todos os blocos da tabela grande seriam alojados e seriam marcados como usados mais recentemente. Isso estaria certo? Poderíamos pensar que sim, mas agora, suponha que esta tabela grande seja lida uma vez por semana, então por que deixar os blocos que são verdadeiramente utilizados no dia a dia pelas nossas aplicações substituídos por blocos que são lidos uma vez por semana? Não vale a pena e possivelmente iríamos obter um baixo desempenho, porque os blocos que foram realmente utilizados e que agora não existem mais na memória devem ser lidos novamente do disco. Para resolver este problema o Oracle aplica os seguintes critérios para tabelas grandes:
Agora vamos falar sobre o outro lado da moeda, onde temos uma grande quantidade de GBs para o nosso Buffer Cache e esse montante é muito maior que o tamanho do nosso banco de dados. Agora que temos memória suficiente para armazenar todos os objetos no banco de dados no Buffer Cache, por que deveríamos continuar utilizando os algoritmos para desalocar blocos do Buffer Cache? Se tudo cabe na memória, não há razão para desalocar. A Oracle pensou nisto e introduziu um novo recurso na versão 12.1.0.2 chamado Full Database Caching.
O Full Database Caching trata de colocar dentro do Buffer Cache todos os blocos de todas as tabelas que forem lidos, o Oracle assume que o tamanho do Buffer Cache é grande o suficiente para acomodar toda a base de dados. Os critérios que já foram explicados para as tabelas pequenas, médias e grandes já não se aplicam ao banco de dados e já não interessa mais esta informação, pois ele irá colocar essas tabelas diretamente na memória.
Quando este recurso estiver ativado, o Oracle não irá carregar imediatamente todos os blocos de todos os objetos na memória, mas sim durante a leitura dos blocos. Então ative e permita que os aplicativos leiam os dados e façam a “mágica” acontecer.
Se o Full Database Caching estiver ativado em uma base de dados do tipo CDB, então esta característica é aplicada a nível do CDB e todos os objetos de todas as bases de dados Pluggable (PDB) que se encontram no CDB serão colocados em memória.
Com esse recurso, todos os blocos são colocados na memória, mesmo aqueles objetos que têm a propriedade NOCACHE, como por exemplo objetos LOB.
Para um banco de dados não-RAC, o tamanho lógico do nosso banco de dados deve ser menor do que o tamanho total do Buffer Cache.
Para os ambientes do tipo Real Application Cluster (RAC), o tamanho lógico do banco de dados deve ser menor do que o tamanho do Buffer Cache de cada instância que compõem a configuração do RAC e deve ser menor do que 80% do tamanho total do Buffer Cache de cada uma das instâncias. Para uma configuração RAC não é possível ter esse recurso ativado em alguns nós e em outros não, este recurso é aplicado ao banco de dados e consequentemente a todas as instâncias.
Para saber o tamanho do Buffer Cache para uma instância específica, podemos executar a seguinte consulta:
SQL> SELECT NAME, BYTES/1024/1024/1024 GB
FROM V$SGAINFO WHERE NAME = 'Buffer Cache Size';
NAME GB
-------------------- ----------
Buffer Cache Size 3.21875
O tamanho lógico de nosso banco de dados deve ser inferior a este montante.
Para descobrir se o Full Database Caching está ativo, devemos executar a seguinte consulta:
SQL> SELECT FORCE_FULL_DB_CACHING FROM V$DATABASE;
FORCE_FULL_DB_CACHING
---------------------
NO
Agora vamos ver o comportamento de um banco de dados quando o recurso Full Database Caching não está habilitado. Para isso, serão utilizadas três tabelas e faremos a leitura utilizando o 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
Limpando a SGA:
SQL> shutdown immediate;
SQL> startup;
Nota: Você também pode fazer um Flush.
Agora vamos ver quantos blocos de nossas tabelas estão no 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: A partir de agora vamos nos referir a esta consulta como: dgomez_cache_blocks.sql
Como se pode ver anteriormente, não há blocos de dados no Buffer Cache de nenhuma das nossa três tabelas. Agora vamos fazer uma varredura completa da tabela:
SQL> select count(*) from dgomez.big_table;
COUNT(*) ---------- 10000000
Vamos verificar quantos blocos foram colocados na memória:
SQL> @dgomez_cache_blocks.sql
OBJECT_NAME BLOCKS SIZE_MB
--------------- -------- ----------
BIG_TABLE 1 0.007
A tabela não foi carregada no Buffer Cache, você sabe por quê? Se agora você está dizendo que a razão é porque a tabela é muito grande (> 10% Buffer Cache), então você está entendendo o conceito.
Agora vamos tentar colocar outra tabela grande no 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
Esta tabela não foi colocada no Buffer Cache, porque ela também é classificada como "grande". Vamos tentar colocar uma tabela menor do que 2% do Buffer Cache, utilizando a nossa tabela LITTLE_TABLE que tem apenas 1 MB de tamanho:
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 a tabela é classificada como “pequena”, todos os blocos foram colocados no Buffer Cache. Parece que os conceitos estão sendo cumpridos.
Agora iremos mostrar o comportamento do banco de dados com o Full Database Caching ativado.
Ativamos o recurso 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.
Podemos verificar que o recurso foi ativado corretamente:
SQL> SELECT FORCE_FULL_DB_CACHING FROM V$DATABASE;
FORCE_FULL_DB_CACHING
---------------------
YES
Podemos verificar os blocos de nossas tabelas que estão em memória:
SQL> @dgomez_cache_blocks.sql
no rows selected
Vamos realizar uma varredura completa em uma tabela 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 você pode ver, o Oracle não está mais interessado se a tabela é pequena, média ou grande, simples e facilmente acomoda todos os blocos em memória.
Faremos a leitura de outra tabela grande para verificar se alguns dos blocos são desalocados. Em outras palavras confirmar se o algoritmo LRU é usado:
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
Podemos ver que a segunda tabela também foi alojada na memória em sua totalidade e os blocos da primeira tabela não foram tocados, ou seja, eles não foram desalocados. O que o Oracle está fazendo é simplesmente acumular todos os blocos que foram lidos até agora e continuará até que todo o banco de dados esteja na memória.
Finalmente faremos a leitura da nossa tabela pequena:
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
Novos blocos foram adicionados e nenhum foi desalocado.
Se você é um DBA curioso como nós, neste momento você está pensando sobre qual é o comportamento do banco de dados quando o Full Database Caching está ativo, mas o Buffer Cache é menor do que o tamanho total do banco de dados. Bem, “lamentos” informar que você não receberá um ORA-600. Para manter o bom hábito de apoiar a curiosidade, vamos mostrar com exemplos esse comportamento:
Verificando se o Full Database Caching está ativado:
SQL> SELECT FORCE_FULL_DB_CACHING FROM V$DATABASE;
FORCE_FULL_DB_CACHING
--------------------
YES
Comprovamos que o nosso Buffer Cache só comportaria uma de nossas tabelas grandes:
SQL> SELECT NAME, BYTES/1024/1024/1024 GB
FROM V$SGAINFO WHERE NAME = 'Buffer Cache Size';
NAME GB
------------------------ ----------
Buffer Cache Size 1.53125
Podemos verificar os blocos de nossas tabelas que estão em memória:
SQL> @dgomez_cache_blocks.sql
no rows selected
Vamos realizar uma varredura completa na primeira tabela grande:
SQL> select count(*) from dgomez.big_table;
COUNT(*)
----------
10000000
Podemos verificar os blocos de nossas tabelas que estão em memória:
SQL> @dgomez_cache_blocks.sql
OBJECT_NAME BLOCKS SIZE_MB
------------- -------- ----------
BIG_TABLE 146838 1147.171
Vamos realizar uma varredura completa na outra tabela grande:
SQL> select count(*) from dgomez.other_big_table;
COUNT(*)
----------
10000000
Podemos verificar os blocos de nossas tabelas que estão em memória:
SQL> @dgomez_cache_blocks.sql
OBJECT_NAME BLOCKS SIZE_MB
------------------ ---------- ----------
BIG_TABLE 42794 334.328
OTHER_BIG_TABLE 146836 1147.156
Aparentemente o Oracle continua utilizando do Full Database Caching, pois a segunda tabela grande foi colocada na memória completamente e não foi utilizado qualquer um dos critérios de carregamento em memória de tabelas grandes ([0%,<2%],[>=2%,<=10%+tiempo],[>10%,100%]), mas como é óbvio alguns blocos da primeira tabela grande (BIG_TABLE) foram desalocados, ou seja, os blocos que foram necessários para colocar na memória a segunda maior tabela (OTHER_BIG_TABLE).
Vamos ver o que acontece ao ler novamente a primeira tabela 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
Agora alguns blocos da tabela OTHER_BIG_TABLE foram desalocados, ou seja, os blocos que foram necessários para colocar na memória a tabela BIG_TABLE.
Nossa recomendação é que se o Buffer Cache não tem memória suficiente para acomodar o banco de dados como um todo, você não deve ativar esse recurso, porque ao ativar você irá observar um grande número de blocos sendo desalocados, o que não é o ideal. Nós recomendamos que você deixe o Oracle decidir e implementar os seus critérios e algoritmos para alocar e desalocar os blocos.
Como último ponto vamos lhe mostrar como desativar o Full Database Caching, para isso você só precisa seguir estes passos:
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.
Deiby Gomez é "Oracle ACE Director", "A Oracle Certified Master 11g" e "A Oracle 12c Certified Master". Ele tem sido um orador na Oracle Open World nos EUA e no Brasil; em Colaborar, Las Vegas e OTN Tour em vários países da América Latina. Vencedor do "SELECT Choice Award da revista Editor de 2016". Ele é um membro da "OraWorld-Team" foi "Beta Tester" da versão 12cR2. Deiby é Presidente o Grupo usuários Oracle de Guatemala (GOUG) e atualmente é Consultor bancos de dados Oracle em Nuvola Consulting Group (www.nuvolacg.com). Twitter @hdeiby.
Alex Zaballa, formado em Análise de Sistemas, é especialista em Banco de Dados Oracle com sólidos conhecimentos em Servidores de Aplicação e Sistemas Operacionais; trabalha com Oracle há 15 anos, é Oracle ACE Director, certificado OCM Database 11G/Cloud e conta com mais de 140 outras certificações em produtos da Oracle. Alex também é fundador do Grupo de Usuários Oracle de Angola (GUOA) e membro do time OraWorld.
Este artigo foi revisto pela equipe de produtos Oracle e está em conformidade com as normas e práticas para o uso de produtos Oracle.