Je viens de me taper l'excellent "Java persistence with Hibernate"
(Bauer, King) qui permet d'embrasser toute la puissance d'Hibernate.
C'est un peu fouillis mais l'information est là, sans délayage, avec
une vision très claire de l'intégration des différentes couches
applicatives. Bon bouquin, donc.
Pourtant je n'arrive pas à me défaire de l'impression qu'Hibernate
repose sur une erreur fondamentale : des effets de bord, qui induisent
une complexité que le produit s'attache à régler partiellement,
donnant cette impression de puissance toujours à la limite de la perte
de contrôle. On a des objets qui représentent des choses complètement
fugaces, comme l'état de la base de donnée, puis rien du tout dès lors
qu'ils sont "détachés" de la session. On peut ensuite les rattacher,
cooool ! Evidemment tout ce genre d'acrobaties génère de la prestation
et l'impression d'utiliser "what's between your ears" (formule de
Goldratt).
On peut dire que c'est la faute de la JVM qui ne permet pas de
détruire des objets explicitement. Ou que c'est la faute des SGBDR
dont la raison d'être est de capturer un état muable. Oh mais
blasphème que tout cela, les SGBDR sont teeellement irremplaçables...
Offrons-nous mainenant un petit détour par git, le gestionnaire de
version distribué qui :
# Met tous ses concurrents à plat en termes de fonctionnalités.
# Est bâti sur des structures de données immuables.
C'est de cette dernière particularité architecturale que découlent
toutes ses fonctionnalités. Les deltas se prêtent à toutes sortes de
manipulations hors de la structure rigide d'un référentiel
(littéralement les empiler dans un coin avec git-stash). Il sont
signés cryptographiquement, ce qui sécurise les échange sur le réseau
et permet de récupérer du code sur un repo inconnu sans qu'une
altération passe inaperçue. Pour la synchro des repos il suffit de
savoir à quelle version s'est arrêté le repo esclave : il est
structurellement garanti que la base commune est la même.
Ainsi d'une seule propriété architecturale, les données immuables,
découlent assez de simplifications pour permettre un produit induisant
une rupture qualitative radicale. Imaginons maintenant un SGBDR qui
applique les formules de git. Je parle bien d'une approche
relationnelle. On voit fleurir sur le marché différentes solutions de
stockage pour des volumes colossaux (`>= Teraoctet`) : BigTable,
Mnesia... Mais la sémantique relationnelle a quand même de beaux jours
devant elle, notamment parce qu'elle donne aux pointeurs une forme
aisément compréhensible.
Concrètement, ça ressemblerait à quoi ? Disons que nous avons une
table A qui contient des références vers une table B :
Version 1
¨¨¨¨¨¨¨¨¨
| A | | B |
|----------------------| |---------------|
| A_ID | B_ID | A_NAME | | B_ID | B_NAME |
|------|------|--------| |------|--------|
| 10 | 20 | aah | | 20 | bar |
(Comme d'habitude, ce sera plus clair avec une police
non-proportionnelle. L'historique de Google Groups permet ça.)
Appliquons un changement sur NAME dans la ligne 10 de A. Un SGBDR basé
sur des données muables représenterait les données comme ceci :
Version 2
¨¨¨¨¨¨¨¨¨
| A | | B |
|--------------------| |---------------|
| A_ID | B_ID | NAME | | B_ID | B_NAME |
|------|------|------| |------|--------|
| 10 | 20 | ooh | | 20 | bar |
Mais si on veut que la version 0 existe toujours il faut ajouter le
numéro de version. Disons qu'il apparaît dans une colonne sur la
gauche de chaque table et indique pour chaque ligne la version de la
base dans laquelle apparaît la ligne dans son état. Le numéro de
version au niveau du nom de la table indique la version dans laquelle
la table a été modifiée pour la dernière fois. On a maintenant :
Version 2
¨¨¨¨¨¨¨¨¨
| 2 | A | | 1 | B |
|---|--------------------| |---|------|--------|
| | A_ID | B_ID | NAME | | | B_ID | B_NAME |
|---|------|------|------| |---|------|--------|
| 1 | 10 | 20 | aah | | 1 | 20 | bar |
| 2 | 10 | 20 | ooh |
Que se passe-t-il si on modifie la ligne de B référencée par A ?
Normalement on retombe sur nos pattes parce que si on souhaite revenir
en version 2, comme il n'y a pas de version 2 dans B, c'est parce
qu'au moment où la base était dans sa version 2 la version de B
correspond à son plus haut numéro de version strictement inférieur à
3. Voilà ce qu'on aurait après modification de B :
Version 3
¨¨¨¨¨¨¨¨¨
| 2 | A | | 3 | B |
|---|--------------------| |---|------|--------|
| | A_ID | B_ID | NAME | | | B_ID | B_NAME |
|---|------|------|------| |---|------|--------|
| 1 | 10 | 20 | aah | | 1 | 20 | bar |
| 2 | 10 | 20 | ooh | | 3 | 20 | baz |
C'est bien joli, on voit s'aligner les versions, mais que se
passe-t-il si on a plusieurs lignes et qu'on change les clés primaires
? Réponse : on ne peut pas !C'est généralement considéré comme une
mauvaise pratique, le support de cette fonctionnalité doit ajouter
beaucoup de code bizarre, donc on dégage et la corrélation se fait
toute seule.
A l'évidence, on gagne :
* Une isolation parfaite des transactions.
* Utiliser un même état de référence à travers plusieurs transactions.
Hibernate s'approche de cela avec le flush "en fin de conversation"
sans y arriver parfaitement.
* Une piste d'audit dont l'intégrité est garantie par la base
elle-même. Un produit comme Envers se contente d'alimenter des tables.
D'une façon moins évidente (mes connaissances restreintes en SGBDR
m'obligent à me parer de ces précautions oratoires) :
* Des branches, comme dans git. Si on sait gérer les versions, l'ajout
des branches ne nécessite pas un travail démesuré (voir comment fait
git). On peut alors entasser des modifications dans une branche, et
regarder ce que donnerait un merge. Il y a même là l'opportunité de
redéfinir la notion de transaction : on crée une branche, quand on a
mis toutes les données souhaitées on essaye de la promouvoir en tant
que dernière version. Le MultiVersion Concurrency Control d'Oracle,
Firebird et compagnie n'en est d'ailleurs pas très éloigné. La notion
de branche est encore plus explicite dans un produit comme
EBX.Platform qui sait coordonner différents référentiels.
* On gagne aussi d'énormes facilités de réplication, comme avec git.
Si on n'a plus à se soucier de l'intégrité de données "live" parce
qu'elles sont comme noyées dans la glace (ou la carbonite), on peut
alors utiliser des mécanismes de réplication éprouvés tirés du
Peer-to-Peer qui sait transférer de gros volumes de données éclatées
sur plusieurs sites. Un SI d'entreprise avec pour middleware eMule, ça
le fait, non ?
Bien sûr un tel produit qui vise la montée en charge se retrouve face
à des problèmes colossaux qui ont mis vingt ans à être résolus. D'un
autre côté, la seule raison pour avoir des données muables dans un
SGBDR c'est d'économiser l'espace disque, ce qui n'est plus vraiment à
l'ordre du jour et on peut donc s'attendre à des simplifications
draconiennes de ce côté-là. Comme d'habitude en informatique, il ne
s'agit pas d'inventer l'outil qui résoudra tous les problèmes. Il
s'agit juste d'inventer les nouveaux problèmes qui remplaceront ceux
dont on a fini par se lasser (rires).
Tout cela m'amuse beaucoup je paye une pinte à celui qui veut bien
passer deux heures à discuter de la faisabilité d'un tel projet.
Enjoy !
c.
Annexe
Article précédent sur l'immuabilité, le Web et les toolkits graphiques :
http://groups.google.com/group/techos/browse_thread/thread/0873f76a617b7ed4
Article précédent sur git :
http://groups.google.com/group/techos/browse_thread/thread/c88b78c2347330b4
Survol des structures internes de git (très bien fait, plein d'images) :
http://eagain.net/articles/git-for-computer-scientists
Envers, versioning automatique d'entités JPA :
http://www.jboss.org/envers
EBX.Platform d'Orchestra Network,
http://www.orchestranetworks.com/product/features_lifecycle.cfm
Mnesia, la base de donnée distribuée basée sur Erlang :
http://www.infoq.com/news/2007/08/mnesia
La BigTable de Google :
http://labs.google.com/papers/bigtable.html
Pour ce qui est de la taille des données, on arrive à quelque chose
d'énorme, fatalement. Parlons de données "frontales" pour désigner la
toute dernière version de chaque ligne de la base, comme on parle de
l'avancée d'un "front nuageux". Parlons de "profondeur" pour
l'historique derrière ces données frontales. Il est possible de
calculer des "résumés" des versions antérieures et de déporter le
stockage des données "profondes" sur des serveurs d'historique. Comme
le résumé pour une version donnée ne changera jamais on s'affranchit
de divers problèmes de réplication. Dit autrement : le serveur de
données frontales est un cache d'historique. Pour les anciens d'AceTP
: imaginez que le Houskeeping ne nécessiterait //aucun// effort de
développement ! A son niveau, git utilise un mécanisme vaguement
analogue en compressant des agrégats de blobs dès qu'ils sont un peu
anciens. Par contre git n'est pas fait pour déporter une partie de son
historique sur un serveur distant. Pour limiter le volume de données
on peut aussi choisir de jeter toutes les versions en-dessous d'une
certaine profondeur et ne garder que le résumé, comme si on recréait
un référentiel git à partir d'une version de travail.
Pour l'historisation des relations il faut explorer les scénarios.
J'ai l'intuition qu'il faut ajouter une contrainte au niveau du DDL du
type : "si tu as deux données liées A et B, que tu as lu A en version
n et que tu l'écris en version n + m (avec m >= 1) alors il faut que
la version de B n'ait pas évolué (version de B <= n)". C'est
l'équivalent du verrouillage pessimiste de plusieurs tables lors d'une
transaction. Ainsi on n'a pas vraiment d'audit mais une garantie sur
ce qui n'a pas pu arriver.
D'ailleurs regardons comment git gère les suppresions. Il n'essaye pas
de tracer les modifs qui arrivent individuellement à chaque fichier.
Ça paraît évident si l'on considère que l'identité d'un fichier dépend
de son nom qui est suceptible de changer, donc l'identité d'un fichier
n'a pas vraiment de sens (d'où les cabrioles avec CVS et autres). A la
place git conserve des éléments de contenu (blobs) et des descriptions
d'arborescence (trees). D'après Linus il est possible de retrouver le
chemin d'un //bloc de code// déplacé à travers différents fichiers. Ça
ne peut être possible que si on ne pollue pas au passage le checksum
SHA-1 qui identifie la modification. Mais il faut se méfier de ce
genre de comparaison : d'abord avec une base de données la notion
d'identifiant est maîtrisée, d'autre part git est fait pour des
données qui tiennent entièrement sur le filesystem local.
Mais bien sûr il est indispensable d'ajouter dans l'API des fonctions
pour "differ" deux versions. Je pense d'ailleurs que c'est la
définition de l'API qui fournit le bon point d'entrée pour un sujet
aussi vaste. Au moins on s'attaque à un sujet concret : rendre les
SGBDR faciles d'accès aux développeurs.
Vengeaaaance !
c.
D'après ce que j'ai compris, il stocke les instructions avant qu'elles
n'aient lieu. Donc pas possible de savoir si une entrée du Redo Log
(comme un insert) a eu vraiment lieu. Et puis en inventant un
sur-système on risque de se laisser polluer par des considérations
propres à celui du "dessous". Ne restent du Redo Log que les infos sur
les volumes. Je n'ai rien trouvé mais pour que ça aie du sens il
faudrait étudier une application en détail.
Il y a peut-être plus d'idées à prendre du côté de Prophet, une base
de données conçue pour fonctionner en mode déconnecté (basée sur la
réplication). L'API applique des principes REST et permet d'accéder à
des versions précédentes. A un moment ils ont même implémenté un
backend Subversion. Il y a quelques idées à creuser telles que le vote
pour la résolution de conflit, ou la réutilisation de résolutions
précédentes. Bon c'est un projet qui a vu le jour il y a trois mois.
L'avantage c'est de pouvoir l'étudier sans avoir à se fader dix ans
d'historique d'un coup.
Henri, tu mentionnais le problème de la fragmentation, mais j'ai
l'impression que ce problème n'est aigü que dans le cas d'une base de
donnée qui stocke des états muables. Pour ce qui est des évolutions de
schéma j'ai l'impression que ça va faire mal, surtout en termes d'API.
Ah oui pour l'API j'imagine de fournir les métadonnées... sous forme
de code Java. Disons que ça serait un sous-ensemble du langage. Allô ?
En tous cas ça permet de jeter d'entrée de jeu toutes ces histoires de
mapping O/R. Je compte bientôt poster une maquette.
Enjoy,
c.
Références
Redo Log :
http://www.ngssoftware.com/research/papers/dissecting-the-redo-logs.pdf
http://www.samag.com/documents/s=9797/sam0507a/0507a.htm
Prophet :
http://syncwith.us/prophet/