Pour savoir où on va, il faut savoir d'où l'on vient

Vous avez
une question ?
Un projet ?

Contactez nous !
 

Contactez-nous

Vous avez une question ? un projet ? 
Vous souhaitez plus d'informations sur un produit ? sur notre offre ? 
Contactez-nous, on vous répond sous 4H.

eZHumanCAPTCHACode reload
retour

Gestion des données

Gestion des données

Un problème difficile

Nous avons vu que la gestion des traitements était finalement assez facile à partitionner, et donc à rendre extensible. Simplement parce que dans un paradigme (traitement, données), c’est à l’étage données que se situe le besoin de partage, d’interaction et de synchronisation.

Nous éviterons de parler trop vite de base de données, pour mieux analyser les problématiques relevant plus largement de la gestion des données.

Pensée unique ?

En matière d’architectures hautement extensibles, il faut en premier lieu cesser de considérer la base de données comme solution unique à toutes formes de besoins.

Les SGBD sont des outils très complets et robustes, dont l’usage s’est généralisé au fil des années, à tel point que, pour beaucoup d’architectes, la question ne se pose même plus : les données doivent être gérées dans un SGBD relationnel. Et une fois ce postulat posé, on réfléchit au moyen de rendre cette gestion extensible.

De nombreux architectes ont critiqué cette approche dite du « one size fits all » ( une taille unique convient à tout le monde), qu’on pourrait traduire en français branché par pensée unique.

Car rendre une base de données extensible est complexe, moyennement performant, et finit toujours par buter sur une limite.

Ainsi, les très grandes plateformes de l’Internet ont toutes renoncé non pas aux bases de données en général, mais au principe d’une base centralisée tournant sur un méga-cluster.

Modélisation objet et programmes

Par rapport aux années 90, un changement fondamental est intervenu dans le développement d’applications. L’application des années 90 construisait une requête SQL, qu’elle adressait à un SGBD. Ou bien dans certains cas, elle invoquait une procédure stockée du SGBD.

Au sein de l’application, la modélisation d’entités en forme d’objets métier s’est répandue. Dans un premier temps, cette approche était gênée par la nécessité de convertir les interfaces relationnelles ensemblistes de la base de données vers les objets de l’application, et réciproquement. Rapidement sont apparus des frameworks qui ont pris en charge ce travail. Et la généralisation de ces couches ORM ( object-relationnal mapping), a modifié en profondeur la relation entre une application et son SGBD. L’application ne prépare plus des requêtes SQL ensemblistes, l’application n’a plus une approche ensembliste de la gestion des données, elle est focalisée sur son paradigme objet.

Ainsi, les couches ORM telles que Hibernate, ont beaucoup réduit le spectre de fonctionnalités SGBD effectivement utilisées. A la fois parce que le paradigme objet n’a pas grand usage des possibilités ensemblistes, et également parce que ces couches visaient une totale indépendance par rapport à la base de données, et devaient donc se satisfaire du plus petit dénominateur commun aux différents SGBD.

C’est ainsi par exemple que les langages de procédures stockées, qui étaient un must dans les années 90 pour un SGBD sérieux, sont tombés en désuétude, à la fois par manque de standard, et par incompatibilité avec le développement objet.

Les propriétés ACID

Pour une gestion sûre et cohérente des données, en présence de multiples processus effectuant des mises à jour de manière concurrente, un système de gestion des données doit respecter les propriétés dites « ACID » :

  • Atomicité. Dans une séquence d’opérations liées, une transaction, on doit avoir l’assurance que toutes les opérations ont été exécutées, ou qu’aucune n’a été exécutée.
  • Cohérence. Les données sont toujours dans un état cohérent, il n’y a pas d’états transitoires incohérents qui soit visible.
  • Isolation. Les autres processus ne voient que l’état avant et l’état après une transaction, ils sont isolés des états intermédiaires.
  • Durabilité. Une fois la transaction terminée avec succès, elle est irréversible.

Bien entendu, ces propriétés doivent être vérifiées en toutes circonstances, c'est-à-dire :

  • Quels que soient le nombre de processus concurrents et la chronologie de leurs transactions ;
  • Y compris en cas de panne soudaine d’un composant

Les bonnes bases de données savent assurer les propriétés ACID. Cela inclut MySql, avec le moteur InnoDB.

Pour bien fixer les idées, il est utile de considérer un exemple typique de transaction. Il s’agit d’enregistrer un virement d’un montant M d’un compte S vers un compte D, qui implique la suite d’opérations suivante :

  • Vérifier que le solde du compte S est supérieur ou égal à M.
  • Soustraire M du solde du compte S.
  • Ajouter une ligne dans la table des mouvements associés à S.
  • Ajouter M au solde du compte D.
  • Ajouter une ligne dans la table des mouvements associés à D.

On voit aisément les problèmes qui surviendraient en l’absence de propriétés ACID. Par exemple si la transaction est interrompue après avoir soustrait et avant d’avoir additionné…

 

Nous verrons qu’il faut en général accepter quelques compromis, et bien mesurer le risque d’une gestion non-ACID. Bien sûr, s’il s’agit d’argent ou de sécurité, le compromis n’est pas possible. Mais s’il s’agit des commentaires ajoutés à un blog, on peut se passer de ces propriétés, et accepter des états transitoires incohérents, ou d’une manière plus large, négliger les cas exceptionnels, y compris même la perte de quelques commentaires dans le cas d’une panne disque, si elle est rare.

Pour autant, on ne peut y renoncer à la légère. Une base de forte volumétrie qui se retrouve dans un état incohérent peut être un problème très difficile à résoudre.

Le cluster

Le cluster de base de données est la seule solution qui garantisse la cohérence des données et les propriétés ACID entre plusieurs serveurs. Cela implique des échanges relativement complexes entre les serveurs, c’est ce qu’on appelle le commit à deux phases, ou two-phase commit (2PC) : avant de valider (« commiter ») une transaction, chaque serveur doit vérifier que tous les autres pourront la valider également. Entre cette première vérification et la confirmation de validation, les autres serveurs ne doivent évidemment rien faire qui puisse interdire la transaction.

Ce que l’on peut représenter schématiquement comme suit :

image076

Ces échanges ont un impact sur les performances, de sorte que le mécanisme n’est pas très extensible.

Et en cas d’arrêt brutal au milieu de ces échanges, il faut encore des échanges complexes pour « démêler » les serveurs, débloquer les verrous et s’assurer de la cohérence.

Le cluster de base de données forme un tout, incluant son load-balancing, de sorte qu’il est vu du reste de la plateforme comme un serveur unique. Les deux bases sont à tout instant identiques et une transaction n’est validée sur l’une que si elle peut l’être également sur l’autre. En cas de panne de l’un des serveurs, aucune donnée n’est perdue, le second traite immédiatement toutes les requêtes.

Pour une vraie haute-disponibilité, chaque serveur doit donc avoir une capacité suffisante pour traiter toutes les requêtes. Avec deux serveurs, on ne peut donc pas avoir simultanément haute-disponibilité et extensibilité. C’est à partir de trois serveurs que l’on pourrait avoir à la fois une capacité nominale double et la tolérance à une simple panne. Mais on met assez rarement en œuvre un cluster au-delà de deux serveurs, et donc plus souvent dans une finalité de haute disponibilité que de haute capacité.

 

Lecture seule, extensibilité

Beaucoup de plateformes web sont, sinon en lecture seule, du moins en lecture majoritaire.

Or en lecture, l’extensibilité est facile à obtenir :

  • On copie les données à l’identique sur un grand nombre de serveurs équivalents
  • On lit les données sur n’importe lequel de ces serveurs.

C’est un principe de réplication, que l’on peut représenter comme ceci :

image078

Où M est une base Maître, et E sont des bases Esclaves. Toutes les bases esclaves sont ici en lecture seule.

On peut également mettre en œuvre cette réplication en cascade, de la manière suivante :

image080

Lorsqu’une modification est opérée sur la base maître, elle doit être propagée sur les bases esclaves. Nous verrons plus loin les outils de cette propagation.

La réplication est extensible, simplement et sans limite, mais elle implique un délai, et donc des états transitoires incohérents, aussi bien entre la base maître et une base esclave, mais aussi entre deux bases esclaves.

Ainsi, si un même utilisateur est amené à lire des données depuis une base E1 d’abord, puis depuis une base E2, il est possible qu’il lise des choses différentes, du moins en présence d’écritures exécutées sur M, et en cours de réplication.

 

Ecriture seule, extensibilité

Le cas de l’écriture seule est beaucoup plus rare sur des plateformes web, mais peut arriver. C’est typiquement le cas de l’écriture d’un fichier de log : chaque serveur écrit sa propre log, qu’il ne lit jamais. Les fichiers de log sont acheminés de manière asynchrone sur un serveur où ils sont consolidés en une log unique.

Ce que l’on peut représenter comme ceci :

image082

Le partitionnement des données

Principe du partitionnement

Un autre cas de figure qui permet une gestion extensible des données est le partitionnement.

Partitionner, c’est répartir les données sur N serveurs, de sorte que :

  • Chaque entité partitionnée est gérée sur un serveur et un seul
  • Elle emmène avec elle différentes entités liées, au regard du modèle des données
  • Certaines entités de référence peuvent être répliquées sur les différents serveurs.

Ce qu’on peut représenter symboliquement comme ceci :

image084

Bien entendu, en présence d’une gestion de données partitionnée, les applications doivent savoir facilement sur quel serveur chercher une entité. Elles pourraient adresser des requêtes en parallèle à tous les serveurs, mais dans ce cas la charge ne serait pas partagée, et l’on n’aurait obtenu qu’une extensibilité en termes de volumétrie, non d’accès.

Quelle logique de répartition ?

 

Ce type de répartition manque beaucoup trop de flexibilité. En effet, comment réagencer ces gros volumes de données en cas de saturation d’un serveur, ou bien pour introduire un nouveau serveur ? On voit qu’il est difficile d’accompagner ainsi une montée en charge progressive.

 

Cela requiert bien sûr une table d’allocation, qui gère la correspondance (entité à serveur), et cette table doit être mise à disposition des applications, et tenue scrupuleusement à jour. Mais en échange de cela, on obtient une réelle flexibilité dans le remplissage, même si les opérations de réagencement général restent difficiles.

Requêtes transverses et datawarehouse

En présence d’une gestion de données partitionnée, il demeure presque toujours quelques besoins de requêtes transverses, des requêtes qui ne peuvent pas être traitées par un seul serveur. Par exemple, rechercher tous les internautes inscrits qui habitent en région parisienne.

Il y a deux voies pour obtenir cela :

  • Soit des requêtes adressées en parallèle à tous les serveurs, et un traitement de consolidation des réponses ;
  • Soit la création d’une base consolidée permanente, à la manière d’un datawarehouse.

C’est en général plutôt cette seconde voie que nous préconisons. En effet une fois le datawarehouse central mis en place, toutes sortes de requêtes, d’extractions ou de traitements statistiques de type décisionnel pourront y être opérés. Alors que dans le cas de la parallélisation / consolidation, il faut explicitement réaliser chaque type de traitement.

Le datawarehouse et le partitionnement sont donc complémentaires.

image086

En voyant cette figure, on pourrait se dire : à quoi bon partitionner, pour ensuite consolider ? Mais en fait :

  • Le datawarehouse ne consolide que l’information utile en transverse, le plus gros de l’information reste purement partitionné ;
  • Le datawarehouse peut gérer de gros volumes, mais avec un taux d’accès faible, il est utilisé pour des requêtes occasionnelles, relevant le plus souvent de l’administration de la plateforme ;
  • Le datawarehouse peut avoir une modélisation et des outils différents, plus adaptés à sa fonction et typologie d’accès.
  • Enfin, puisqu’on a dit qu’il nous fallait une table d’allocation à jour, le datawarehouse est le lieu naturel d’élaboration de cette table.

Partitionnement par user

Dans une plateforme web, l’entité appropriée pour le partitionnement est généralement l’utilisateur, pour autant qu’il soit identifié. C’est bien souvent l’entité autour de laquelle gravitent les données : l’utilisateur est inscrit, ou « membre », et différentes données lui sont rattachées : son profil, ses préférences, ses commandes, etc.

Si l’entité de partitionnement est l’utilisateur, alors on peut envisager de répartir dès le niveau frontal :

  • Un même internaute est alors géré sur un même frontal, non pas l’espace d’une session, mais toujours, pour toutes ses visites.
  • Le frontal est en relation avec l’une des partitions de base, gérant les données de l’utilisateur.
  • Dans ce cas, on gèrera le load-balancing sur la base de l’identification.

L’un des avantages est une certaine transparence dans l’accès aux données : les applications n’ont pas à connaître le partitionnement. C’est en particulier approprié lorsque l’application est un progiciel.

On peut représenter cela comme suit :

image088

En revanche, ce type de partitionnement rend plus difficile la gestion du secours : chaque frontal, chaque partition de base, doit disposer de son propre secours, ou alors il faut mettre en œuvre un secours sur du matériel mutualisé, mais cela implique un processus d’activation du secours qui installe la bonne configuration et la bonne sauvegarde.

On a fait figurer un lien entre la base consolidée C, qui porte la table d’allocation, et le dispositif de load-balancing, pour signifier une répartition fonction de l’utilisateur. Dans ce cas de figure, chacun des frontaux ne voit qu’une seule base de données, la sienne, correspondant à la partition qui porte ses utilisateurs.

L’alternative est de gérer l’aiguillage vers la bonne partition en aval des frontaux web, au niveau de l’accès aux données. C’est plus naturel, plus flexible, et plus robuste, et le load-balancing en amont plus simple. Mais le partitionnement n’est alors pas transparent, il doit être géré par l’application.

On peut représenter cette configuration comme suit :

image090

Ici, le load-balancer répartit la charge sans se préoccuper des utilisateurs, et pas même des sessions. C’est au niveau des frontaux qu’intervient la table d’allocation qui définit le partitionnement, et qui peut être répliquée ou cachée sur les frontaux. Et chaque frontal adresse ses requêtes aux différentes partitions selon le besoin. Le secours à l’étage frontal est simplifié puisque les frontaux sont tous équivalents. Il n’y a pas d’ailleurs de relation entre le nombre de frontaux et le nombre de partitions.

En général, comme toute forme de spécialisation des ressources, le partitionnement rend plus difficile la gestion du secours, puisque chaque partition doit bénéficier d’un secours.

En résumé, le partitionnement est souvent la voie la plus extensible dans la gestion des données. Il présente quelques contraintes, mais couplé à une consolidation au sein d’un datawarehouse, c’est une configuration qui répond à un grand nombre de besoins.

Synthèse

Nous avons passé en revue 4 variantes de gestion de données visant l’extensibilité :

  • Gestion ACID centralisée ou en cluster, pour les données critiques.
  • Réplication pour les données en lecture seule, ou en lecture majoritaire.
  • Consolidation asynchrone pour les données en écriture seule.
  • Partitionnement, pour les données partitionnables, associé le cas échéant à un datawarehouse.

Ces quatre voies peuvent évidemment être combinées, pour couvrir le besoin d’une plateforme web.