Mécanismes internes du multitenant: liens objects et métadonnées

ceci est la traduction de mon article sur le blog dbi-services

Attention: il ne s’agit ici que de jouer avec les mécanismes internes non documentés. A ne faire que sur une base de test prête à être jetée. Jouer avec ces mécanisme est le meilleur moyen de planter la base avec des ORA-600 et de corrompre le dictionnaire.

Sur une base multitenant (CDB) nous savons que chaque pluggable database est isolée afin d’être vue comme une seule base lorsqu’on est connecté à son service. Mais elles partagent des ressource communes puisque le but est de consolider plusieurs bases en une seule. Pas de problème pour la CPU, la mémoire, les journaux redo et undo: il ne sont gérés qu’au niveau CDB. Le multitenant n’a pas changé grand chose là dessus, à part qu’on stocke l’identificateur de conteneur dans différentes structures, par exemple dans les vecteurs de redo pour identifier les objets (object id peuvent être les mêmes entre plusieurs CDB). Au niveau des fichiers, pas de gros changements par rapport à ce qui avait été introduit avec les tablespaces transportables.

Mais ce qui a été un plus gros challenge pour les développeurs d’Oracle 12c, c’est surtout la manière d’implémenter le dictionnaire. Il a été séparé:

  • Tout ce qui est interne au fonctionnement d’Oracle, les métadonnées du dictionnaire, les packages dbms_…, tout ce qui est stocké pour toute l’instance: tables de références, repository AWR, etc. C’est le but: consolider, ne pas répéter plusieurs fois ce qui est identique du moment qu’on est sur la même version d’Oracle
  • Toutes les métadonnées qui concernent l’application: les tables et packages que vous avez créé. Celles-ci sont propres à chaque pluggable tablespace, et sont transportées, clonées avec les oprérations de plug/unplug

et finalement, comme le but principal d’Oracle étant de pouvoir faire dans une PDB tout ce qu’on peut faire sur base, il faut pouvoir foir au niveau PDB les informations stockées au niveau CDB.

Tout ça semble un peu magique par rapport à ce qu’on connait d’Oracle qui n’a jamais fait cette séparation. Par exemple depuis le début d’Oracle on a dans la table SYS.TAB$ les informations de nos tables, ainsi que les informations des tables du dictionnaire – incluant les métadonnées de la table SYS.TAB$ elle-même. La documentation ne va pas très loin sur la manière dont ça a été implémenté. Heureusement on a quelques scripts dans ?/rdbms/admin qui donnes des idées là dessus. ils utilisent une syntaxe un peu particulière, non documentée, et qui n’est disponible que lorsque les scripts internes mettent le paramètre non documenté « _ORACLE_SCRIPT »=true.

Maintenant, ce sont des trucs de geek qui arrivent. Rien à voir avec ce qu^’on fait en production. Nous allons créer nos propres liens objet et métadonnée. Ce qui est fait dans la suite est fait dans une session avec « _ORACLE_SCRIPT »=true. On va voir des nouveaux éléments de syntaxe: cdb$view(), sharing=metadata, sharing=object, common_data

Container data objects

D’abord nous allons voir comment au niveau CDB la racine CDB$ROOT peut voir les informations des autres conteneurs (PDB)

Je suis au niveau CDB:

SQL> alter session set container=cdb$root;
Session altered.

SQL> show con_name
CON_NAME
------------------------------
CDB$ROOT

SQL> show con_id
CON_ID
------------------------------
1

et je créé une table normale:

SQL> create table DEMO_REG_TABLE sharing=none as select 111 dummy from dual;
Table created.

SQL> select * from DEMO_REG_TABLE;
     DUMMY
----------
       111

Puis je fais exactement la même chose dans la PDB:

SQL> alter session set container=pdb1;
Session altered.

SQL> show con_name
CON_NAME
------------------------------
PDB1

SQL> show con_id
CON_ID
------------------------------
3

SQL> create table DEMO_REG_TABLE sharing=none as select 999 dummy from dual;
Table created.

SQL> select * from DEMO_REG_TABLE;
     DUMMY
----------
       999

et de retour dans la racine, j’utilse la fonction de table CDB$VIEW pour voir ce que j’ai dans chacune de mes PDB:

SQL> select * from  cdb$view(DEMO_REG_TABLE) where con_id in (1,3);
     DUMMY     CON_ID
---------- ----------
       999          3
       111          1

C’est comme ça que les ‘container objects’ sont définis: ils utilisent la fonction CDB$VIEW pour exécuter une requête sur chaque PDB, consolider le résultat et rajouter l’identifiant de PDB: CON_ID pour montrer d’où viennent les lignes.

Vous voulez savoir comment c’est implémenté? Il semble que ce soit le mécanisme de parallel query qui soit utilisé. Voici la preuve:

SQL> select * from  cdb$view(DEMO_REG_TABLE);
select * from  cdb$view(DEMO_REG_TABLE)
*
ERROR at line 1:
ORA-12801: error signaled in parallel query server P002
ORA-00942: table or view does not exist

Lorsque la table n’est pas trouvée (j’ai volontairement créé une table sur quelques conteneurs seulement) c’est un process parallel query qui renvoit l’erreur.

Metadata links

Maintenant, je vais créer une fonction PL/SQ dans la racine et dans une PDB, mais je n’ai pas envie de dupliquer le code dans les deux tablespaces SYSTEM. Je vais utiliser SHARING=METADATA pour créer un metadata link.

SQL> alter session set container=cdb$root;
Session altered.

SQL> show con_name
CON_NAME
------------------------------
CDB$ROOT

SQL> show con_id

CON_ID
------------------------------
1

SQL> create function DEMO_MDL_FUNCTION sharing=metadata
  2  return varchar2 as dummy varchar2(100); begin select max(dummy) into dummy from DEMO_REG_TABLE; return dummy; end;
  3  /
Function created.

SQL> select DEMO_MDL_FUNCTION from dual;
DEMO_MDL_FUNCTION
------------------------------
111
SQL> alter session set container=pdb1;
Session altered.

SQL> show con_name
CON_NAME
------------------------------
PDB1

SQL> show con_id
CON_ID
------------------------------
3

SQL> create function DEMO_MDL_FUNCTION sharing=metadata
  2  return varchar2 as dummy varchar2(100); begin select max(dummy) into dummy from DEMO_REG_TABLE; return dummy; end;
  3  /
Function created.

SQL> select DEMO_MDL_FUNCTION from dual;
DEMO_MDL_FUNCTION
------------------------------
999

Je peux donc voir le code dans SYS.SOURCE$ de la racine:

SQL> alter session set container=cdb$root;
Session altered.

SQL> select * from source$ where obj# in (select obj# from obj$ where name like 'DEMO%');

      OBJ#       LINE SOURCE
---------- ---------- ------------------------------
     95789          1 function DEMO_MDL_FUNCTION

Mais rien dans ma PDB:

 
SQL> alter session set container=pdb1;
Session altered.

SQL> select * from source$ where obj# in (select obj# from obj$ where name like 'DEMO%');

no rows selected

Object links

Ceci était pour ne stocker les métadonnées qu’à un seul endroit, dans la racine, en ayant seulement un lien dessus dans les PDB. On peut aussi faire la même chose pour des données qu’on ne veut stocker que dans la racine, en utilisant SHARING=METADATA pour créer la table et SHARING=OBJECT pour créer une vue dessus

SQL> alter session set container=cdb$root;
Session altered.

SQL> show con_name
CON_NAME
------------------------------
CDB$ROOT

SQL> show con_id
CON_ID
------------------------------
1

SQL> create table DEMO_MDL_TABLE sharing=metadata as select 111 dummy from dual;
Table created.

SQL> alter session set container=pdb1;
Session altered.

SQL> show con_name
CON_NAME
------------------------------
PDB1

SQL> show con_id

CON_ID
------------------------------
3

SQL> create table DEMO_MDL_TABLE sharing=metadata as select 999 dummy  from dual;
Table created.
 
SQL> alter session set container=cdb$root;
Session altered.

SQL> select * from  cdb$view(DEMO_MDL_TABLE) where con_id in (1,3);

     DUMMY     CON_ID
---------- ----------
       999          3
       111          1
SQL> alter session set container=cdb$root;
Session altered.

SQL> show con_name
CON_NAME
------------------------------
CDB$ROOT

SQL> show con_id
CON_ID
------------------------------
1

SQL> create view DEMO_OBL_VIEW sharing=object as select * from DEMO_MDL_TABLE;
View created.

SQL> select * from DEMO_OBL_VIEW;
     DUMMY
----------
       111
 
SQL> alter session set container=pdb1;
Session altered.

SQL> show con_name
CON_NAME
------------------------------
PDB1

SQL> show con_id
CON_ID
------------------------------
3

SQL> create view DEMO_OBL_VIEW sharing=object as select * from DEMO_MDL_TABLE;
View created.

SQL> select * from DEMO_OBL_VIEW;
     DUMMY
----------
       111

La vue montre les données stockées dans CDB$ROOT en suivant le ‘object link’ au lieu de lire la table du container courant.

On ne peut pas insérer directement dans un ‘object link':

SQL> insert into DEMO_OBL_VIEW select 9999 dummy from dual;
insert into DEMO_OBL_VIEW select 9999 dummy from dual
            *
ERROR at line 1:
ORA-02030: can only select from fixed tables/views

et on a une idée de l’implémentation en regardant le plan d’exécution:

---------------------------------------------
| Id  | Operation        | Name             |
---------------------------------------------
|   0 | SELECT STATEMENT |                  |
|   1 |  FIXED TABLE FULL| X$OBLNK$aed0818c |
---------------------------------------------

C’est une table X$ qui va faire le lien

Common data views

finalement, comment une PDB peut voir les données de la racine? Nous avons vu au dessus que la fonction que j’ai créé ci-dessus est stockée dans le SYS.SOURCE$ de la racine seulement. Par contre on peut la voir à partir du DBA_SOURCE de la PDB

SQL> alter session set container=cdb$root;
Session altered.

SQL> show con_name
CON_NAME
------------------------------
CDB$ROOT

SQL> show con_id
CON_ID
------------------------------
1

SQL> create or replace view DEMO_INT_VIEW common_data (dummy,sharing) as select dummy,case when dummy='222' then 0 else 1 end from DEMO_MDL_TABLE;
View created.

SQL> select * from DEMO_INT_VIEW;

     DUMMY    SHARING
---------- ----------
       111          1
       222          0

J’ai créé une ‘common data view’ en rajoutant une colonne ‘sharing’ pour montrer d’où viennent les lignes.

SQL> alter session set container=pdb1;
Session altered.

SQL> show con_name
CON_NAME
------------------------------
PDB1

SQL> show con_id
CON_ID
------------------------------
3

SQL> create or replace view DEMO_INT_VIEW common_data (dummy,sharing) as select dummy,case when dummy='222' then 0 else 1 end from DEMO_MDL_TABLE;
View created.

SQL> select * from DEMO_INT_VIEW;
     DUMMY    SHARING ORIGIN_CON_ID
---------- ---------- -------------
       999          1             3
       111          1             1
SQL> set autotrace on
SQL> select * from DEMO_INT_VIEW;

     DUMMY    SHARING ORIGIN_CON_ID
---------- ---------- -------------
       111          1             1
       999          1             3

Execution Plan
----------------------------------------------------------
Plan hash value: 3158883863

--------------------------------------------------------------------------------------------
|Id  | Operation               | Name            |Pstart|Pstop |   TQ  |IN-OUT| PQ Distrib |
--------------------------------------------------------------------------------------------
|  0 | SELECT STATEMENT        |                 |      |      |       |      |            |
|  1 |  PX COORDINATOR         |                 |      |      |       |      |            |
|  2 |   PX SEND QC (RANDOM)   | :TQ10000        |      |      | Q1,00 | P->S | QC (RAND)  |
|  3 |    PX PARTITION LIST ALL|                 |    1 |    2 | Q1,00 | PCWC |            |
|  4 |     FIXED TABLE FULL    | X$COMVW$e40eb386|      |      | Q1,00 | PCWP |            |
--------------------------------------------------------------------------------------------

On voit que les données de chaque conteneur est retourné comme une partition.

Laisser un commentaire