Consommation de données en live

… ou comment appeler des web services en quasi-temps réel et présenter nos données

En ces temps d’ultra-connectivité, de mise à disposition des données en temps réel, de consommation instantanée de l’information, il s’avère souvent qu’un des casse-têtes des DBA reste la présentation de ces données.
Prenons l’exemple d’un site permettant d’afficher la disponibilité d’un produit. N’y a-t-il rien de plus rageant lors de la validation d’une commande de recevoir un mail, nous indiquant, bien cordialement, qu’un remboursement va avoir lieu du fait de l’indisponibilité du produit ?
Pourquoi ? Tout simplement à cause de la fraîcheur des données. Beaucoup de systèmes sont des évolutions d’évolutions évoluant sur la base d’évolution.
Je ne pense pas (et ne le prétends pas) avoir l’unique solution, mais voici une des solutions envisageables : la Vue Matérialisée !
Vous allez me dire, rien d’innovant, rien de compliqué… Non, c’est sûr mais voyons-en l’implémentation :
Dans un premier temps, nous allons créer une table de test :

CREATE TABLE Test_PGR
(ID1       NUMBER(50),
ID2        VARCHAR2(50),
ID3        NUMBER(50),
C01        NUMBER(30),
C02        NUMBER(30),
C03        NUMBER(30),
C04        NUMBER(30),
C05        NUMBER(30),
C06        NUMBER(30));

Ajoutons maintenant une clé primaire :

ALTER TABLE Test_PGR ADD CONSTRAINT Test_PGR_PK PRIMARY KEY (ID1, ID2, ID3);

Afin d’être efficace, nous allons devoir utiliser un refresh de notre vue matérialisée en mode FAST.
Pour rappel, ce mode est applicable sur des MV simples, sous certaines conditions, et permet de rafraîchir les MV de façon synchrone et incrémentale.
Ainsi, il nous faut donc créer un log de vue matérialisée sur notre table :

CREATE MATERIALIZED VIEW LOG ON Test_PGR;

Maintenant que les conditions sont respectées pour le FAST refresh, nous allons pouvoir créer notre MV :

CREATE MATERIALIZED VIEW Test_MV
BUILD IMMEDIATE
REFRESH FAST
ON COMMIT
DISABLE QUERY REWRITE
AS
        SELECT ID1, ID2, ID3, C01, C02, C03, C04
        FROM Test_PGR;

Et voilà !
Mais pourquoi toutes ces clauses ?

  • BUILD IMMEDIATE : construction de la MV à la création
  • REFRESH FAST : mise en place du refresh incrémentiel
  • ON COMMIT : sur commit effectué sur la table de base
  • DISABLE QUERY REWRITE : Bridage d’Oracle pour ne pas avoir de régression. Si l’option n’est pas désactivée, Oracle risque de décider de passer par la vue matérialisée lors de SELECT pensant que l’action serait plus efficace.

Nous disposons maintenant de tout pour avoir nos données au fil de l’eau. En cas d’INSERT ou d’UPDATE dans la table de base (Test_PGR), la modification sera enregistrée dans le MVLog et suite au COMMIT les données seront présentes dans la MV.

Ainsi, si nous souhaitons prendre connaissance de ces changements toujours au fil de l’eau, nous allons créer un trigger basé sur les mouvements de la MV qui déclenchera nos actions.

Pour que l’exemple soit parlant, j’ai décidé d’écrire mes données dans un fichier sur le filesystem de mon serveur de base de données Oracle. Bien entendu, pour ce faire, l’utilisateur possédant le trigger doit avoir les droits d’écriture sur l’objet directory.

CREATE OR REPLACE TRIGGER MV_TRIG AFTER INSERT OR UPDATE
ON Test_MV
REFERENCING NEW AS NEW
FOR EACH ROW
DECLARE
        fileHandler UTL_FILE.FILE_TYPE;
        v_filename VARCHAR2(50);
        v_buffer   VARCHAR2(32767);
BEGIN
        v_filename  := 'TEST_' || TO_CHAR(sysdate,'YYYYMMDDHH24MISS') || '.lst';
        fileHandler := UTL_FILE.FOPEN('DATA_OUT', v_filename, 'A');
        v_buffer    := :new.ID1 ||';' || :new.ID2 ||';' || :new.ID3 ||';'
                        || :new.C01 ||';' || :new.C02 ||';' || :new.C03 ||';' || :new.C04 || '\n';
        UTL_FILE.PUTF(fileHandler, v_buffer);
        UTL_FILE.FCLOSE(fileHandler);
EXCEPTION
WHEN utl_file.invalid_path THEN
        raise_application_error(-20000, 'ERROR: Mauvais chemin pour le fichier de sortie.');
END;

 
Que se passe-t-il lors d’un INSERT dans la table de base ?
Tout simplement, Oracle enregistre les données dans les datafiles ainsi que dans le log de vue matérialisée. Une fois le COMMIT consommé par la base, Oracle rafraîchit les données dans la MV tout en déclenchant notre trigger.
A son tour, le trigger crée un fichier contenant les différentes lignes, pour chaque seconde du fait du nommage dans le fichier.

1;Produit1;1;10;10;10;10
2;Produit2;1;15;20;25;5
1;Produit2;2;10;42;12;4

Pour ce qui est de la consommation de nos fichiers et donc de l’appel à notre Web Service, rien de plus simple :

while IFS=\; read id1 id2 id3 col1 col2 col3 col4
do
        curl \
        --header "Content-type: application/json" \
        --resquest POST \
        --data '{"Site":"$id1","Produit":"$id2","Couleur":"$id3","dispo":"$col1","restant":"$col2","Pct1":"$col3","Pct2":"$col4"}' \
        http://monAS/stock/rest
done < TEST_20160324133318.lst

Et voilà, il est éventuellement possible d’englober cette consommation dans un script scannant le dossier d’entrées, de variabiliser le nom du fichier et ainsi d’appeler notre Web Service au fil de l’eau.
Comme je l’indiquais précédemment, ce n’est peut-être pas la meilleure solution (vous pouvez d’ailleurs en discuter dans les commentaires), mais dans l’absolu, l’utilisation de ces fonctionnalités Oracle est simple à mettre en œuvre et permet d’avoir un impact moindre qu’en utilisant un trigger directement sur la table de base.
Sur ce, bonne continuation !