Oracle Database 12c In-Memory: Configuration/Découverte de l’option

Dans le contexte actuel d’accroissement rapide du volume des informations stockées dans nos bases de données de prédilection (je fais évidemment ici référence à ORACLE), il peut devenir très difficile d’extraire certaines informations dans les temps imposés pour des besoins particuliers.
Certaines décisions ne peuvent pourtant pas toujours attendre plusieurs heures voire plusieurs jours dans certains cas, lorsque des requêtes décisionnelles doivent manipuler plusieurs « terabytes » de données.
Lorsque les possibilités d’optimisations des traitements métiers ou d’adaptations architecturales (utilisation de l’option « partitioning » par exemple) ne suffisent plus à répondre à cette problématique de temps de réponse, plusieurs solutions matérielles (solutions de stockage à haute performance / utilisation d’Engineered Systems / etc …) peuvent être envisagées mais plusieurs facteurs (coûts / choix imposés par l’hébergeur / etc …) ne permettent pas toujours de s’y orienter aisément.
ORACLE propose une solution logicielle, sous la forme d’une option utilisable avec la version 12c de son SGBD : Oracle 12c In-Memory (Option « In Memory Database Cache » dans le catalogue des prix).
Oracle l’a présenté à ses partenaires lors des animations Techn’Oracle et Easyteam vous en a fait le compte-rendu ici.
Dans cet article, nous allons voir comment configurer l’utilisation de cette option, en travaillant dans une base de données mono-instance ORACLE 12.1.0.2 EE.
1) Dans un premier temps, nous allons activer la possibilité technique d’utiliser le « In Memory Column Store » :
La configuration de l’option se fait simplement en positionnant la valeur du paramètre d’initialisation « inmemory_size », qui par défaut est égale à « 0 ».
Ce paramètre agit sur la taille à attribuer au buffer statique (= Non gérable dynamiquement via « AMM » ou « ASMM ») nommé « In Memory » ou plus simplement « IM » dans la suite.

SQL> show parameter inmemory_size;
NAME                                 TYPE        VALUE
------------------------------------ ----------- -----
inmemory_size                        big integer 0
SQL> alter system set inmemory_size=5G scope=spfile;
SQL> shutdown immediate;
(...)
SQL>startup;
(...)

Suite au redémarrage de notre base de données, le buffer « In Memory » est actif et dispose de 5Go d’espace :

SQL> show parameter inmemory;
NAME                                 TYPE        VALUE
------------------------------------ ----------- --------
inmemory_clause_default              string
inmemory_force                       string      DEFAULT
inmemory_max_populate_servers        integer     12
inmemory_query                       string      ENABLE
inmemory_size                        big integer 5G
inmemory_trickle_repopulate_servers_ integer     1
percent
optimizer_inmemory_aware             boolean     TRUE

Attardons-nous un instant sur les 6 autres paramètres mentionnés ci-dessus :
« inmemory_clause_default » agit sur le comportement par défaut lors des créations des nouveaux objets (dans un souci de simplification, nous parlerons ci-après uniquement du cas des tables).
Par exemple, on pourrait souhaiter que toute nouvelle table créée soit automatiquement placée dans le buffer « IM », il faudrait alors positionner « inmemory_clause_default » à la valeur « INMEMORY ».
Par défaut la valeur de « inmemory_clause_default » est égale à « NULL », ce qui signifie que seules les tables sur lesquelles on agira explicitement dans ce sens (via une commande « ALTER » ou avec l’attribut positionné à la création) seront placées dans le buffer « IM ».
« inmemory_force »
Par défaut, tout objet incluant l’attribut « INMEMORY » est candidat à l’intégration dans le buffer « IM ».
Cependant, si ce paramètre « inmemory_force » était positionné à la valeur « OFF », aucun objet ne serait placé dans le buffer « IM ». Autrement dit, cela reviendrait à désactiver techniquement l’utilisation de l’option.
« optimizer_inmemory_aware »
Ce paramètre agit directement sur le comportement de l’optimiseur de sélection de plans. Si la valeur « FALSE » était positionnée, l’optimiseur ne tiendrait plus compte du fait qu’un objet se trouve dans le buffer « IM », lors de ses calculs de plans d’exécutions.
Les paramètres « inmemory_max_populate_servers » et « inmemory_trickle_repopulate_servers_percent » agissent sur le comportement des processus invoqués lors des phases de migrations des informations des segments vers le buffer « IM ».
Le paramètre « inmemory_query » permet d’autoriser ou non l’utilisation de « IM », aussi bien au niveau système qu’au niveau « sessions ».
2) Maintenant que le contexte nous permet l’utilisation de l’option, nous allons illustrer son fonctionnement par l’exemple :
A) Créons une table avec un peu de données dans un des schémas de notre base de données :

SQL> CREATE TABLE "MATABLE"
   ("MACOLONNE1" VARCHAR2(10),
    "MACOLONNE2" VARCHAR2(10),
    "MACOLONNE3" VARCHAR2(10),
    CONSTRAINT "MATABLE_PK" PRIMARY KEY ("MACOLONNE1")
    )
TABLESPACE "USERS";
Table created.
SQL> insert into MATABLE values ('A','B','C');
1 row created.

B) Interrogeons les champs de la vue « user_tables » relatifs à l’option « IN MEMORY » et concernant la table qui vient d’être créée :

SQL> select cache, inmemory, inmemory_priority, inmemory_distribute, inmemory_compression from user_tables where table_name like 'MATABLE';
CACHE            INMEMORY INMEMORY INMEMORY_DISTRI INMEMORY_COMPRESS
-----            -------- -------- --------------- -----------------
N                DISABLED

Nous constatons que notre table n’est pour l’instant pas candidate au stockage en mémoire (« INMEMORY » = « DISABLED »).
Cela est logique, dans le sens où la valeur du paramètre « inmemory_clause_default » précédemment mentionné est restée égale à sa valeur par défaut (« NULL »).
Une interrogation de cette table va donc pour l’instant engendrer la génération d’un plan d’exécution classique de parcours complet de la table dans notre contexte :

SQL> select * from MATABLE;
MACOLONNE1 MACOLONNE2 MACOLONNE3
---------- ---------- ----------
A          B          C
| Id  | Operation                 | Name    | Rows  | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |         |     1 |    21 |     2   (0)| 00:00
:01 |
|   1 |  TABLE ACCESS FULL| MATABLE |     1 |    21 |     2   (0)| 00:00
:01 |
--------------------------------------------------------------------------------

C) Rendons désormais cette table candidate au stockage en mémoire :
Cela se fait tout simplement en exécutant cet ordre SQL :

SQL> alter table MATABLE CACHE INMEMORY;
Table altered.

En faisant cela, nous autorisons ORACLE à placer la table dans le buffer « IM », au moment opportun (= La table ne résidera pas forcément en permanence en mémoire).
Nous pouvons confirmer la bonne prise en compte de cette modification, en exécutant de nouveau la requête de tout à l’heure :

SQL> select cache, inmemory, inmemory_priority, inmemory_distribute, inmemory_compression from user_tables where table_name like 'MATABLE';
CACHE                INMEMORY INMEMORY INMEMORY_DISTRI INMEMORY_COMPRESS
-------------------- -------- -------- --------------- -----------------
    Y                ENABLED  NONE     AUTO            FOR QUERY LOW

Par défaut, « INMEMORY_PRIORITY » est positionné à « NONE » ce qui signifie que typiquement, ORACLE placera la table dans le buffer « IM » suite à son premier accès.
Il est possible de modifier la valeur de ce paramètre afin que la table soit, par exemple, directement placée dans le buffer « IM » au démarrage de la ou des instances.
Le paramètre « INMEMORY_DISTRIBUTE » est quant à lui utile dans un environnement de type « RAC ». Dans ce contexte, chaque instance dispose de son propre buffer « IM » et il est possible que chacun d’eux héberge des objets complètement différents (c’est d’ailleurs le comportement par défaut).
Grâce à ce paramètre, on pourrait très bien choisir de répartir équitablement les « large objects » dans l’ensemble des buffers « IM » d’un cluster, voir de disposer de l’ensemble des objets dans chacun d’eux (cette dernière possibilité n’étant néanmoins envisageable que dans le cadre de l’utilisation d’un « Engineered System » (type « EXADATA » par exemple)).
Dans le buffer « IM », chaque objet est compressé. Le niveau de compression est configurable via le paramètre « INMEMORY_COMPRESSION » et a par défaut la valeur « FOR QUERY LOW », le meilleur compromis entre taux de compression et performance.
D) Interrogeons une nouvelle fois notre table, maintenant qu’elle est candidate au stockage en mémoire :

SQL> select * from MATABLE;
MACOLONNE1 MACOLONNE2 MACOLONNE3
---------- ---------- ----------
A          B          C
| Id  | Operation                  | Name    | Rows  | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |         |     1 |    21 |     1   (0)| 00:0
0:01 |
|   1 |  TABLE ACCESS INMEMORY FULL| MATABLE |     1 |    21 |     1   (0)| 00:0
0:01 |
--------------------------------------------------------------------------------

Nous constatons d’ores et déjà (du fait de la présence de l’opération « TABLE ACCESS INMEMORY FULL » dans le plan d’exécution généré par l’optimiseur de sélections) le fait que la table a été placée en mémoire lors de ce premier accès.
Cela a été effectué par les process d’arrière-plan nommés « worker proceses », dont le comportement est contrôlable, entre autres possibilités, via les paramètres « inmemory_max_populate_servers » et « inmemory_trickle_repopulate_servers_percent » évoqués plus haut.
E) Contrôlons désormais le status de la migration de notre table vers le buffer « IM », via la vue v$IM_SEGMENTS :

SQL> select v.segment_name name, v.populate_status status, v.bytes_not_populated from v$im_segments v where v.segment_name like 'MATABLE' :
NAME
--------------------------------------------------------------------------------
STATUS    BYTES_NOT_POPULATED
--------- -------------------
MATABLE
COMPLETED                   0

La valeur du champ « BYTES_NOT_POPULATED » est égale à « 0 ». Cela signifie que la totatilité des données de notre table est actuellement présente en mémoire.
La description de cette vue « v$im_segments« , nous fait entrevoir les possibilités qu’elle offre :

SQL> desc v$im_segments;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 OWNER                                              VARCHAR2(128)
 SEGMENT_NAME                                       VARCHAR2(128)
 PARTITION_NAME                                     VARCHAR2(128)
 SEGMENT_TYPE                                       VARCHAR2(18)
 TABLESPACE_NAME                                    VARCHAR2(30)
 INMEMORY_SIZE                                      NUMBER
 BYTES                                              NUMBER
 BYTES_NOT_POPULATED                                NUMBER
 POPULATE_STATUS                                    VARCHAR2(9)
 INMEMORY_PRIORITY                                  VARCHAR2(8)
 INMEMORY_DISTRIBUTE                                VARCHAR2(15)
 INMEMORY_DUPLICATE                                 VARCHAR2(13)
 INMEMORY_COMPRESSION                               VARCHAR2(17)
 CON_ID                                             NUMBER

F) Qu’en est-il de la compression des données relatives à notre table dans le buffer « IM » :

select v.segment_name, v.bytes orig_size, v.inmemory_size in_mem_size, round(v.bytes / v.inmemory_size, 2) comp_ration from v$im_segments v where v.segment_name like 'MATABLE';
SEGMENT_NAME
----------------------------------
 ORIG_SIZE IN_MEM_SIZE COMP_RATION
---------- ----------- -----------
MATABLE
    21     11         48

Nous remarquons que, sans avoir agi sur le paramètre « INMEMORY_COMPRESSION » évoqué plus haut, notre table prend par défaut deux fois moins de place en mémoire que sur disque (la compression par défaut est bien utilisée).
3) En conclusion, nous avons pu remarquer que la configuration du « In Memory Column Store » se fait très simplement et rapidement, en positionnant à minima le paramètre d’initialisation « INMEMORY_SIZE« .
Une fois ce dernier positionné, il est également aisé de rendre une table (existante ou nouvelle) candidate au stockage en mémoire.
Une fois cela fait, et si tant est que la paramètre d’initialisation nommé « optimizer_inmemory_aware » soit resté positionné à sa valeur par défaut (« TRUE »), des gains très importants en performance se feront alors directement ressentir lors de certaines interrogations faites sur une telle table (voir l’opération « TABLE ACCESS INMEMORY FULL » du plan d’exécution mentionné au chapitre « 2-D » de cet article), si tant est qu’elle soit bien entendu volumineuse.
Il faut savoir que la fonctionnalité « In-Memory » va être améliorée en 12.2 (ces améliorations étant d’ailleurs une des principales attentes de cette version, non disponible à l’heure à laquelle est écrit cet article).
Mes prochains articles pourraient traiter plus particulièrement des gains en performance apportés par cette nouvelle fonctionnalité, avec des exemples plus concrets de requêtes type décisionnelles sollicitant des tables de plusieurs millions d’enregistrements.
D’autres sujets qui gravitent autours de cette nouvelle fonctionnalité pourraient également être abordés plus tard (« In-Memory Advisor », « In-Memory Transaction Manager », etc …).

Vous souhaitez monter en compétences sur la version 12c de la base de données Oracle ? Alors découvrez nos prochaines dates de formation sur Paris et en région

2 réflexions sur “Oracle Database 12c In-Memory: Configuration/Découverte de l’option”

  1. Merci bcp pour vos articles sur oracle.
    C’est rare de trouver de la documentation écrite en français.
    Cela m’aide dans mon travail.
    Merci

Les commentaires sont fermés.