lundi 2 septembre 2019

Updates et Compression HCC (1)



Dans cet article je souhaiterais vous montrer ce qu'il se passe concrètement quand un update est effectué sur des lignes compressées en HCC (compression disponible sur Exadata uniquement) grâce à l'utilisation du package DBMS_COMPRESSION.

Je vais commencer par créer une table en mode compression HCC et y insérer des lignes en mode conventionnel (sans utiliser le hint APPEND):
DROP TABLE T1 PURGE;
create table T1 as select * from all_objects where 1=2;
alter table T1 compress for query high;
-- insert without using DIRECT PATH WRITE (data non compressed)
insert into T1 select * from all_objects;
commit;


Imaginons que je ne sache pas comment ces lignes aient été insérées. En regardant les propriétés de la table indiquant qu'il s'agit d'une table compressée en HCC je pourrais être amené à croire que cette table contient des lignes compressées alors que non puisque pour avoir des lignes compressées en HCC dans Exadata il ne suffit pas de créer une table en mode HCC, il faut également que les lignes soient insérées en mode direct path write (en mode APPEND). Faut vraiment bien garder cela à l'esprit.

La fonction GET_COMPRESSION_TYPE du package DBMS_COMPRESSION permet de savoir quel type de compression est appliqué pour chacune des lignes d'une table:
select  decode (dbms_compression.get_compression_type(user,'T1',rowid),
1, 'NOCOMPRESS',
2, 'COMP_FOR_OLTP',
4, 'COMP_FOR_QUERY_HIGH',
8, 'COMP_FOR_QUERY_LOW',
16,'COMP_FOR_ARCHIVE_HIGH',
32,'COMP_FOR_ARCHIVE_LOW',
64,'COMP_BLOCK',
'OTHER') comp_type, count(*)
from T1
where rownum < 100
group by decode (dbms_compression.get_compression_type(user,'T1',rowid),
1, 'NOCOMPRESS',
2, 'COMP_FOR_OLTP',
4, 'COMP_FOR_QUERY_HIGH',
8, 'COMP_FOR_QUERY_LOW',
16,'COMP_FOR_ARCHIVE_HIGH',
32,'COMP_FOR_ARCHIVE_LOW',
64,'COMP_BLOCK',
'OTHER');

COMP_TYPE          COUNT(*)
---------------- ----------
NOCOMPRESS               99

select trunc(bytes/power(1024,2)) m
from user_segments where segment_name ='T1'; 

         M
----------
       258


La fonction GET_COMPRESSION_TYPE appliquée sur les lignes de ma table indiquent un type de compression à 1 c'est à dire non compressées.

Je vais maintenant supprimer les lignes de la table et y insérer les mêmes lignes en utilisant cette fois le mode DIRECT PATH WRITE:
truncate table T1;
insert /*+ APPEND */ into T1 select * from all_objects;
commit;

select  decode (dbms_compression.get_compression_type(user,'T1',rowid),
1, 'NOCOMPRESS',
2, 'COMP_FOR_OLTP',
4, 'COMP_FOR_QUERY_HIGH',
8, 'COMP_FOR_QUERY_LOW',
16,'COMP_FOR_ARCHIVE_HIGH',
32,'COMP_FOR_ARCHIVE_LOW',
64,'COMP_BLOCK',
'OTHER') comp_type, count(*)
from T1
where rownum < 100
group by decode (dbms_compression.get_compression_type(user,'T1',rowid),
1, 'NOCOMPRESS',
2, 'COMP_FOR_OLTP',
4, 'COMP_FOR_QUERY_HIGH',
8, 'COMP_FOR_QUERY_LOW',
16,'COMP_FOR_ARCHIVE_HIGH',
32,'COMP_FOR_ARCHIVE_LOW',
64,'COMP_BLOCK',
'OTHER');

COMP_TYPE          COUNT(*)
---------------- ----------
FOR_QUERY_HIGH           99

select trunc(bytes/power(1024,2)) m
from user_segments where segment_name ='T1';

         M
----------
        11


Le package DBMS_COMPRESSION nous indique que la compression appliquée aux lignes de la table est bien de type HCC FOR QUERY HIGH, qui est le type de compression qu'on a définit pour notre table lors de sa création.

Notez comment la taille de la table est passée de 258MB à 11MB seulement. C'est assez impressionnant.
Maintenant, je vais exécuter un update sur toutes les lignes de la table:
update T1 set CREATED=SYSDATE;
commit;

select  decode (dbms_compression.get_compression_type(user,'T1',rowid),
1, 'NOCOMPRESS',
2, 'COMP_FOR_OLTP',
4, 'COMP_FOR_QUERY_HIGH',
8, 'COMP_FOR_QUERY_LOW',
16,'COMP_FOR_ARCHIVE_HIGH',
32,'COMP_FOR_ARCHIVE_LOW',
64,'COMP_BLOCK',
'OTHER') comp_type, count(*)
from T1
where rownum <100 br="">group by decode (dbms_compression.get_compression_type(user,'T1',rowid),
1, 'NOCOMPRESS',
2, 'COMP_FOR_OLTP',
4, 'COMP_FOR_QUERY_HIGH',
8, 'COMP_FOR_QUERY_LOW',
16,'COMP_FOR_ARCHIVE_HIGH',
32,'COMP_FOR_ARCHIVE_LOW',
64,'COMP_BLOCK',
'OTHER');

COMP_TYPE               COUNT(*)
--------------------- ----------
COMP_BLOCK                    99

select trunc(bytes/power(1024,2)) m
from user_segments where segment_name ='T1';

         M
----------
        96


Vous remarquez qu'après l'update le type de compression est passé au type 64 qui est en fait un type de compression OLTP.
Que s'est-il passé exactement?
En fait, pour pouvoir modifier des lignes compressées en HCC, Oracle migre ces lignes dans un nouveau block défini en mode OLTP compression. Un pointeur a été laissé dans le block d'origine. On est là devant un cas de ligne migrée c'est à dire qu'il faut lire 2 blocks pour lire une ligne. La taille de la table est passé de 11MB à 96MB ce qui est toujours mieux que la taille en mode non compressée (258MB) mais moins bien que le taux de compression obtenu en HCC.

De plus, lorsqu'un full scan est effectué sur une table et que le moteur Oracle tombe sur une ligne migrée, il bascule automatiquement en mode Block Shipping inhibant ainsi le smart scan.

En résumé, en modifiant des lignes compressées en HCC on casse en quelque sorte cette compression (les lignes sont migrées dans des blocks en mode compression OLTP) et on empêche le offloading de pouvoir s'effectuer sur ces lignes lors d'un SELECT. En gros, les 2 fonctionnalités phares de l'Exadata sont mis à mal à cause de ces updates. Voilà pourquoi les lignes compressées en HCC ne devraient jamais être modifiées. Si un update est necessaire il vaut mieux supprimer et réinsérer ces lignes.

Il existe d'autres contraintes liées aux updates de lignes compressées en HCC (Lock au niveau compression unit, entrées ITL limitées à 1 etc. ) mais j'en parlerai dans un autre post.