J'ai brièvement évoqué dans le second article de cette série la sortie de DSQL (pour "Distributed SQL database", et je l'ai fait d'abord en soulignant les compromis de conception, pour ne pas faire croire à une solution miracle.
Dans cet article, je reviens sur cette solution après avoir pris le temps de me documenter, et je reviens beaucoup plus enthousiaste !
L'information publique sur DSQL, outre la documentation du produit, comprend principalement une série d'articles publiés sur le blog de Marc Brooker (Distinguished Engineer et VP de l'équipe Bases de données d'AWS) et cette vidéo de sa conférence lors de re:Invent, qui en reprend les principaux points :
Les propriétés essentielles d'une base de données relationnelles
L'objectif des SGBD relationnels de type transactionnels (OLTP, par opposition par exemple aux cubes OLAP pour l'analytique) est de traiter de façon performante mais vertueuse des transactions. Une transaction est un ensemble :
BEGIN SELECT ... UPDATE ... INSERT ... COMMIT
Dans une base de données relationnelles, on essaie de donner aux transactions quatre propriétés essentielles :
- l'Atomicité : une transaction est écrite entièrement ou pas du tout
- la Cohérence : les données passent d'un état cohérent à un état cohérent, sans état intermédiaire.
- l'Isolation : une transaction n'est pas affectée par les autres transactions qui ont lieu de façon concurrente. Il y a différents niveaux d'isolation possible (on y revient en-dessous).
- la Durabilité : lorsqu'une transaction est commitée, elle est persiste de telle sorte qu'un arrêt de la base de donnée n'entraîne pas de perte de données.
Concevoir une base de données capables de traiter des transactions à l'échelle ou présentant une haute-disponibilité nécessite une infrastructure distribuée dont la mise en oeuvre nécessite des compromis par rapport à ces propriétés, en particulier l'isolation.
Au niveau le plus fort d'isolation, dit "sérialisable", les transactions posent des "verrous" en lecture non seulement sur des lignes mais également sur des 'intervalles' (de sorte qu'une requête qui va compter le nombre d'enregistrement vérifiant un prédicat sera garantie "juste" au moment du commit) et ces verrous ne sont tous relâchés qu'au commit. Comme l'indique le terme "sérialisable", cela a des incidences importantes sur les performances car une transaction doit attendre parfois longuement pour obtenir un accès exclusif.
Le niveau retenu pour DSQL est celui mis en oeuvre par défaut dans PostgreSQL, l'isolation d'instantané. L'idée est qu'une transaction voit un 'instantané' (snapshot) de la base telle qu'elle était à l'instant de début de commit, ce qui suffit à éviter beaucoup de difficultés liées à l'accès concurrent sans sacrifier les performances (au prix de refus de commit - OptimisticLockException en Java - à gérer au niveau applicatif).
Si donc ce choix est "classique", en quoi l'architecture de DSQL diffère-t-elle d'une base de donnée traditionnelle ?
Un moteur de base de données éclaté, avec quatre composants essentiels
Outre le router, point d'entrée des requêtes, dont Marc Brooker dit peu de choses, DSQL s'appuie sur 4 composants essentiels qui sont complètement scalables indépendamment les uns des autres :
- Le journal : c'est lui qui assure les propriétés de durabilité et l'atomicité des transactions. Une transaction commitée est une transaction qui figure dans le journal. Si tous les noeuds de stockage étaient perdus, le système pourrait les reconstituer à partir du journal.
- Les noeuds de stockage : ils viennent en aval du journal et permettent de servir de façon scalable les requêtes en lecture. Dans l'isolation d'instantané, la lecture se fait "comme si on était à l'instant Tdébut". De sorte qu'un noeud de stockage qui a appliqué (pour la fraction des données qu'il porte) toutes les entrées du journal jusqu'à Tdébut peut servir avec confiance les requêtes en lecture).
- Les noeuds d'adjudication : leur job est de traiter les conflits entre commits. Chaque noeud est responsable d'une partition des clés (si on voit tout le schéma comme un grand ensemble clé-valeur) et seules les transactions qui concernent plusieurs partitions nécessitent une concertation entre adjudicateurs, avec une logique proche du two-phase commit. Lorsqu'un noeud octroie la possibilité d'un commit à Tcommit, il prend l'engagement de ne plus octroyer de commits avant cet instant.
- les noeuds de traitement des requêtes (query processors) comme leur nom l'indiquent prennent en charge une transaction de bout en bout, le choix de l'isolation d'instantanné leur permettant de faire appel directement au stockage pour la lecture et d'appliquer les modifications de façon autonome, ne concertant les adjudicateurs qu'au moment du commit. Ce même choix permet que ces noeuds ne se coordonnent jamais entre eux, ils sont purement stateless. Ces noeuds utilisent FireCracker, le même système de micro-VMs qui est au coeur de l'architecture de Lambda.
Dans cette architecture, les noeuds d'adjudication sont chacun responsable d'une partition des données et, qu'il s'agisse d'une base multi-AZ au sein d'une même région, ou d'une base multi-régions, un noeud leader est désigné pour chaque partition. Pour commîter sur une partition, l'accord du leader est nécessaire et ici, c'est la vitesse de la lumière qui est la limite ! Mais le nombre d'aller-retours est limité à 1 du fait de la synchronisation d'horloge permise par AWS TimeSync entre l'ensemble des noeuds !
Une base de données vraiment serverless ?
Aurora et Redshift Serverless ont galvaudé le terme serverless (pour ma part je l'appellerais plutôt elastic!) et même si le scale-to-zero a rapproché Aurora de ce modèle, ça reste un service payé à l'heure.
Marc Brooker le reconnaît d'ailleurs : « nous avons un peu abusé du terme ces dernières années »..
Arrive DSQL.
- Des composants qui ressemblent à s'y méprendre à des Lambdas,
- qui scalent horizontalement, indépendamment les uns des autres, en fonction de la charge..
Attendez : va t-on vers un produit vraiment serverless ? qui sera facturé par ex. au nombre d'écritures, de lectures et au Go de données lues et écrites ? ou au coût d'une requête (évaluable avec un plan d'exécution) ?
Le service étant à l'heure où j'écris en preview sans facturation, il est encore trop tôt pour le dire avec certitude, mais Marc dans son talk laisse quelques indices : il annonce un service
- où il n'est pas nécessaire de créer des instances (writer, reader... noeuds de secours etc)
- et une base de données « faite aussi bien pour faire plusieurs millions de transactions par secondes que quelques dizaines de requêtes par jour »
Bref, avec DSQL nous sommes peut-être à l'aube d'une révolution pour l'univers des bases de données aussi profonde que Lambda l'a été pour le calcul. A suivre !