Oracle Database 12c - In-Memory

Pessoal,

Bom dia.

Agora no Oracle Database 12c Release 1 (12.1.0.2) temos uma nova feature denominada: In-Memory. Pensando em otimizar buscar e manter blocos em memória para acesso mais rápido e por mais tempo a Oracle cria esta feature Oracle In-Memory que possuí uma nova área de memória que armazena dados de forma colunar, utilizando apenas a memória como armazenamento.


Relatórios analíticos com consultas complexas que trabalham em grandes tabelas com alto(milhões) volume de dados são beneficiadas por esta feature.

O Oracle In-Memory acelera consultas analíticas, permitindo decisões de negócios em tempo real. Usando banco de dados In-Memory, as empresas podem instantaneamente executar análises e relatórios que antes levavam horas ou dias. Isso é possível devido a uma arquitetura única "dual-format" que permite que dados sejam mantidos, tanto no formato de linha existente no Oracle, para operações OLTP, e também um formato novo de coluna puramente na memória, otimizados para o processamento analítico, conforme imagem abaixo:


Oracle Database tem tradicionalmente os dados armazenados em um formato de linha. Em um banco de dados formato de linha, cada nova transação ou registro armazenado no banco de dados é representado como uma nova linha em uma tabela. Essa linha é composta de colunas.

Um formato de linha é ideal para sistemas com transações on-line, uma vez que permite o acesso rápido a todas as colunas em um record desde que todos os dados para um determinado registro são mantidos juntos na memória. Um banco de dados em formato de coluna armazena cada um dos atributos sobre uma transação em uma coluna separada. Um formato de coluna é ideal para análise, uma vez que permite uma recuperação de dados mais rápida quando apenas algumas colunas são selecionadas, mesmo que a consulta acesse um grande conjunto de dados.

Vale dizer que, este formato de coluna não é tão eficiente no processamento de linha a linha para operações DML.

Banco de dados In-Memory usa um novo componente da System Global Area (SGA), chamada de área de In-Memory. Esta área não substitui o cache de buffer, mas atua como um complemento, para que os dados possam ser agora armazenados em memória em ambos uma linha e um formato de coluna.
A área de In-Memory é um conjunto estático dentro do SGA, cujo tamanho é controlado pelo parâmetro de inicialização INMEMORY_SIZE (padrão 0). A área de In-Memory deve ter um tamanho mínimo de 100MB e o formato de coluna em memória deve ser dimensionado para acomodar os objectos que devem ser armazenados na memória.

Vamos ver alguns parâmetros de inicialização:

SQL> show parameter inmemory

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
inmemory_clause_default              string
inmemory_force                       string      DEFAULT
inmemory_max_populate_servers        integer     0
inmemory_query                       string      ENABLE
inmemory_size                        big integer 0
inmemory_trickle_repopulate_servers_ integer     1
percent
optimizer_inmemory_aware             boolean     TRUE

Esta nova feature possui alguns parâmetros de inicialização, dentro alguns quero destacar o: INMEMORY_SIZE, que define o tamanho da In-Memory ou controla a quantidade de memória alocada. O valor padrão é 0 bytes, que diz que não estamos utilizando o recurso In-Memory. O tamanho mínimo requerido para o parâmetro INMEMORY_SIZE é de 100MB.
O parâmetro de inicialização OPTIMIZER_INMEMORY_AWARE recebe TRUE/FALSE. Quando o parâmetro está como FALSE, quer dizer que o otimizador passa a ignorar a propriedade in-memory das tabelas durante a otimização de instruções SQL.

Vamos habilitar o In-Memory:

Vemos que não temos ativa a nossa área de memória In-Memory na SGA:

SQL> select * from v$sga;

NAME                      VALUE     CON_ID
-------------------- ---------- ----------
Fixed Size              2928968          0
Variable Size         440405688          0
Database Buffers      306184192          0
Redo Buffers            5455872          0

Definimos então uma quantidade de 200M para nossa In-Memory:

SQL> alter system set inmemory_size=200M scope=spfile;

System altered.

SQL> shutdown immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.

Vemos agora após a inicialização do banco que temos nossa In-Memory reservada na SGA:

SQL> startup
ORACLE instance started.

Total System Global Area 1073741824 bytes
Fixed Size                  2932632 bytes
Variable Size             754974824 bytes
Database Buffers          100663296 bytes
Redo Buffers                5455872 bytes
In-Memory Area            209715200 bytes
Database mounted.
Database opened.


SQL> select * from v$sga;
NAME                      VALUE     CON_ID
-------------------- ---------- ----------
Fixed Size              2932632          0
Variable Size         775946344          0
Database Buffers       79691776          0
Redo Buffers            5455872          0
In-Memory Area        209715200          0

Utilização básica:

A área In-Memory deve ser preenchida com os dados de desempenho mais críticos no banco de dados. Apenas objetos com o atributo InMemory serão preenchidas para o armazenamento de coluna na nova área da SGA. O atributo InMemory pode ser especificada em uma tabela, partição de tabela, ou visão materializada. É possível também preencher apenas um subconjunto das colunas que desejamos em memória. Veja abaixo que ignoramos a coluna prod_id, ou seja, esta coluna não será carregada em memória:

ALTER TABLE sales INMEMORY NO INMEMORY(prod_id);

Vamos a um exemplo de criação de tabela com o atributo In-Memory:

SQL> create table TB_TESTE (data date) INMEMORY;

Table created.

SQL> create table TB01 as select level as id from dual connect by level<=100;

Table created.

Vamos a um exemplo de criação de uma view materializada com o atributo In-Memory:


SQL> create materialized view MV_TB1 INMEMORY as
select * from tb01;

Materialized view created.

Veja na consulta abaixo que temos uma tabela(TB01) que não foi definida como candidata a ser populada na área In-Memory:

SQL> select table_name, inmemory from user_tables;

TABLE_NAME                 INMEMORY
-------------------------- --------
MV_TB1                     ENABLED
TB01                       DISABLED
TB_TESTE                   ENABLED

3 rows selected.

Podemos alterar uma tabela criada, e definí-la como In-Memory, abaixo exemplo:

SQL> alter table TB01 INMEMORY;

Table altered.

SQL> select table_name, inmemory from user_tables;

TABLE_NAME                        INMEMORY
--------------------------------- --------
TB_TESTE                          ENABLED
TB01                              ENABLED
MV_TB1                            ENABLED

Verificação de Desempenho: 

Vamos a um teste, primeiramente vamos criar uma tabela que não está com a opção In-Memory ativada:

SQL> alter session set statistics_level='ALL';

Session altered.

SQL> create table teste.tb_obj as select * from all_objects;

Table created.

SQL> Insert into teste.tb_obj Select * from teste.tb_obj;

90102 rows created.

SQL> Insert into teste.tb_obj Select * from teste.tb_obj;

180204 rows created.

SQL> Insert into teste.tb_obj Select * from teste.tb_obj;

360408 rows created.

SQL> Insert into teste.tb_obj Select * from teste.tb_obj;

720816 rows created.

SQL> commit;

Commit complete.

Bom temos agora uma tabela(TB_OBJ) criada com aproximadamente 720 mil registros e com um tamanho de 190M conforme vemos na consulta abaixo:

SQL> select owner, segment_name, segment_type, sum(bytes)/1024/1024 mb
from dba_extents
where owner='TESTE'
and segment_name='TB_OBJ'
group by owner, segment_name, segment_type; 

OWNER           SEGMENT_NAME            SEGMENT_TYPE       MB
--------------- ----------------------- ------------------ ----------
TESTE           TB_OBJ                  TABLE              190

Veja abaixo que a tabela está com o InMemory desabilitado:

SQL> select table_name, inmemory from user_tables;

TABLE_NAME      INMEMORY
--------------- --------
TB_OBJ          DISABLED

Vamos colher as estatísticas da tabela:

SQL> ANALYZE TABLE teste.tb_obj COMPUTE STATISTICS;

Table analyzed.

Vamos fazer um select básico com nossa tabela já populada, realizando apenas um count na tabela que ainda não foi definida com o atributo In-Memory. Forçamos a monitoração que vamos testar rodando em real-time e vamos verificar o máximo de estatísticas para análise do plano de execução, comparando a otimização obtida posteriormente:

SQL> SELECT /*+ monitor gather_plan_statistics*/
COUNT(OBJECT_NAME)
FROM teste.tb_obj
Where OBJECT_NAME <> 'I_USER2'; 

COUNT(OBJECT_NAME)
------------------
1441616

Legal, vamos agora obter a identificação de meu select executado no banco:

SQL> SELECT sql_id,
hash_value,
sql_text
FROM v$sql
WHERE upper(sql_text) LIKE upper('%' || 'SELECT /*+ MONITOR GATHER_PLAN_STATISTICS*/' || '%')
AND upper(sql_text) NOT LIKE '%LIKE%'; 

SQL_ID        HASH_VALUE  SQL_TEXT    
------------- ----------  ------------ 
au1ff3xdwkbt7  702425610  SELECT /*+ monitor gather_plan_statistics */  COUNT(OBJECT_NAME)   FROM teste.tb_obj

Vamos analisar agora, a consulta au1ff3xdwkbt7:

SET LONG 100000 LONGCHUNKSIZE 100000 LINESIZE 130 
SELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(sql_id => 'au1ff3xdwkbt7', report_level=>'ALL', type => 'TEXT') as report FROM dual;

SQL Monitoring Report

SQL Text
------------------------------
SELECT /*+ monitor gather_plan_statistics*/ COUNT(OBJECT_NAME) FROM teste.tb_obj

Global Information
------------------------------
Status              :  DONE (ALL ROWS)
Instance ID         :  1
Session             :  SYS (1:14873)
SQL ID              :  au1ff3xdwkbt7
SQL Execution ID    :  16777218
Execution Started   :  07/04/2016 22:59:00
First Refresh Time  :  07/04/2016 22:59:00
Last Refresh Time   :  07/04/2016 22:59:00
Duration            :  .008799s
Module/Action       :  sqlplus@serv01.localdomain (TNS V1-V3)/-
Service             :  SYS$USERS
Program             :  sqlplus@serv01.localdomain (TNS V1-V3)
Fetch Calls         :  1

Global Stats
===================================================================
| Elapsed |   Cpu   |    IO    | Fetch | Buffer | Read | Read     |
| Time(s) | Time(s) | Waits(s) | Calls |  Gets  | Reqs | Bytes    |
===================================================================
|    0.13 |    0.00 |     0.00 |     1 |   24126 |   765 |  188MB |
===================================================================

SQL Plan Monitoring Details (Plan Hash Value=2584588655)
==================================================================================================================================

| Id |      Operation       |  Name  |  Rows   | Cost |   Time    | Start  | Execs |   Rows   | Read | Read  | Activity | Activity
     
|    |                      |        | (Estim) |      | Active(s) | Active |       | (Actual) | Reqs | Bytes |   (%)    |(#samples)
==================================================================================================================================
|  0 | SELECT STATEMENT     |        |         |      |         1 |     +0 |     1 |        1 |      |       |          |
|
|  1 |   SORT AGGREGATE     |        |       1 |      |         1 |     +0 |     1 |        1 |      |       |          |
|
|  2 |    TABLE ACCESS FULL | TB_OBJ |      1M | 6556 |         1 |     +0 |     1 |       1M |  765 | 188MB |          |
|
==================================================================================================================================

Bom vimos acima que está sendo um FULL TABLE SCAN na tabela e que o tempo foi de aproximadamente: 0.13s e foram lidos 188M. Ok, agora vamos colocar a nossa tabela tb_obj em modo In-Memory e realizar novamente a consulta e realizar uma verificação.

SQL> ALTER TABLE teste.tb_obj INMEMORY;

Table altered.

Vamos popular nossa area de memória:

SQL> ALTER SESSION SET "_inmemory_populate_wait"=TRUE;

Session altered.

SQL> EXEC DBMS_INMEMORY.POPULATE('TESTE','TB_OBJ');

PL/SQL procedure successfully completed.

Vamos conferir e ver que a memória foi populada, COMPLETED:

SQL> 
SELECT v.owner,
       v.segment_name sgt,
       v.populate_status status,
       v.inmemory_size / 1024 / 1024 size,
       v.bytes / (1024 * 1024) size_megs,
       v.bytes_not_populated / (1024 * 1024) not_populated npop,
       v.inmemory_priority p,
       v.inmemory_compression c
  FROM v$im_segments v;

OWNER  SGT    STATUS     SIZE        SIZE_MEGS     NPOP  P     C
-----  ------ ---------  -------     ---------     ----- ----  ----------------
TESTE  TB_OBJ COMPLETED   27.25      190.0           0.0 NONE     FOR QUERY LOW

Vamos desconsideramos nossos testes anteriores:

SQL> ALTER SYSTEM FLUSH SHARED_POOL;

System altered.

SQL> ALTER SYSTEM FLUSH BUFFER_CACHE;

System altered.

Agora sim realizamos nossa consulta básica que executamos anteriormente realizando um count na tabela:

SQL> SELECT /*+ monitor gather_plan_statistics*/
COUNT(OBJECT_NAME)
FROM teste.tb_obj
Where OBJECT_NAME <> 'I_USER2'; 

COUNT(OBJECT_NAME)
------------------
1441616

Obtemos o ID de nossa instrução novamente e verificamos o que aconteceu:

SQL>SELECT sql_id,
hash_value,
sql_text
FROM v$sql
WHERE upper(sql_text) LIKE upper('%' || 'SELECT /*+ MONITOR GATHER_PLAN_STATISTICS*/' || '%')
AND upper(sql_text) NOT LIKE '%LIKE%';

SQL_ID        HASH_VALUE SQL_TEXT    
------------- ----------  ------------ 
au1ff3xdwkbt7  702425610  SELECT /*+ monitor gather_plan_statistics */  COUNT(OBJECT_NAME)   FROM teste.tb_obj

SQL>SELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(sql_id => 'au1ff3xdwkbt7', report_level=>'ALL', type => 'TEXT') as report FROM dual;

SQL Monitoring Report

SQL Text
------------------------------
SELECT /*+ monitor gather_plan_statistics*/ COUNT(OBJECT_NAME) FROM teste.tb_obj

Global Information
------------------------------
Status              :  DONE (ALL ROWS)
Instance ID         :  1
Session             :  SYS (1:14873)
SQL ID              :  au1ff3xdwkbt7
SQL Execution ID    :  16777216
Execution Started   :  07/04/2016 23:16:46
First Refresh Time  :  07/04/2016 23:16:46
Last Refresh Time   :  07/04/2016 23:16:46
Duration            :  .002186s
Module/Action       :  sqlplus@serv01.localdomain (TNS V1-V3)/-
Service             :  SYS$USERS
Program             :  sqlplus@serv01.localdomain (TNS V1-V3)
Fetch Calls         :  1

Global Stats
===========================================================================
| Elapsed |   Cpu   |    IO    |  Other   | Fetch | Buffer | Read | Read  |
| Time(s) | Time(s) | Waits(s) | Waits(s) | Calls |  Gets  | Reqs | Bytes |
===========================================================================
|    0.01 |    0.00 |     0.00 |     0.00 |     1 |      3 |    1 |  8192 |
===========================================================================

SQL Plan Monitoring Details (Plan Hash Value=2584588655)
==================================================================================================================================

| Id |           Operation           |  Name  |  Rows   | Cost |   Time    | Start  | Execs |   Rows   | Read | Read  | Activity |
Activity Detail |
|    |                               |        | (Estim) |      | Active(s) | Active |       | (Actual) | Reqs | Bytes |   (%)    |
(# samples)   |
==================================================================================================================================
==================
|  0 | SELECT STATEMENT              |        |         |      |         1 |     +0 |     1 |        1 |      |       |          |
|
|  1 |   SORT AGGREGATE              |        |       1 |      |         1 |     +0 |     1 |        1 |      |       |          |
|
|  2 |    TABLE ACCESS INMEMORY FULL | TB_OBJ |      1M |  263 |         1 |     +0 |     1 |       1M |    1 |  8192 |          |
|
==================================================================================================================================

Bom vimos agora que o tempo foi de apenas 0.01s e que os bytes lidos foram apenas 8.192. O tempo caiu de 0.13s para 0.01s. Importante: Para que o otimizador do Oracle decida ir na In-Memory, um acesso “FULL SCAN” deve ser feito na tabela Veja no plano de execução que a operação de consulta foi via TABLE ACCESS INMEMORY FULL.

Algumas Restrições do In-Memory:

Quase todos os objetos no banco de dados são elegíveis para ser preenchido na coluna IM mas há um pequeno número de exceções. Os seguintes objetos de banco de dados e tipos não podem ser preenchido coluna In-Memory:


  • Qualquer objeto de propriedade do usuário SYS e armazenados no sistema ou tabela SYSAUX;
  • Index Organized Tables (IOTs) e Clustered Tables;
  • LONGS (deprecated desde o Oracle Database 8i) e Out of line LOBs data types;
  • Objetos que são menores do que 64KB não são preenchidos na memória, , como eles vão perder uma quantidade considerável de espaço dentro da coluna IM sendo a memória alocada em pedaços de 1MB;
  • IM não pode ser usado em uma instância Active Data Guard, mas, ele pode ser usado em uma instância mantida usando o Oracle Golden Gate.

Apenas para informação, qualquer consulta que requer dados de colunas com um tipo não suportado será executado através do cache de buffer.

Pessoal, este artigo foi apenas uma introdução ao tão falado Oracle 12c – In-Memory, e é apenas uma gota no oceano de tantos recursos existentes, visto que não comentei no artigo sobre outros recursos do Oracle In-Memory como: Níveis de prioridade, Níveis de compressão de dados, In-Memory em Oracle RAC, Optimizer Hints etc.

Mais informações no site da Oracle: https://www.oracle.com/database/database-in-memory/index.html

Abraço,

Ronaldo.

Um comentário: