Passer

Conseil N°2 pour être un super Data Engineer

2019-12-04-Conseil2-Data-Engineer

Les développeurs d’applications dites “de gestion” (métier, e-commerce, finance…) ont appris durant leur formation à modéliser selon des règles bien précises appelées “modélisation relationnelle”…

Conseil N°2 : La modélisation relationnelle des données sous une forme normale ne s’applique pas aux bases de données distribuées.

…Alors que les développeurs décisionnels sont formés à l’usage de modèles dits “en étoile” ou “en flocon” avec comme référence le livre The Datawarehouse Toolkit de Raph Kimbell.

Rappelez-vous les règles de normalisation en forme normale :

1ère forme normale : les attributs d’une table doivent être atomiques (une seule information dans un attribut)

2ème forme normale : une table doit posséder des attributs de clés primaires.  De plus, un attribut non clé ne dépend pas d’une partie de la clé mais de toute la clé

3ème forme normale : un attribut non clé ne dépend pas d’un attribut non clé

Nous vous épargnons la 4ème et la 5ème forme normale mais ce qu’il faut retenir est que ces règles ont pour objectif d’éviter à tout prix la duplication d’une information dans une base de données. Ces règles ont pour conséquence concrète de décomposer un modèle de données en de nombreuses tables, l’accès aux données communes à différentes tables s’effectuant grâce au mécanisme de “jointure”, largement éprouvé… sur les bases de données relationnelles monolithiques.

Bienvenue dans le (nouveau) monde de la (big) data avec ses systèmes distribués : Hadoop, Presto, Apache Cassandra, Elasticsearch, Amazon Redshift, BigQuery, etc. Vous avez peut être entendu parler du théorème de CAP comme Consistency, Availability, Performance. Ce théorème dit que, dans un système de base de données distribué, on ne peut avoir ces trois critères réunis et qu’il ne peut en avoir que deux réunis. Notre sujet ici est celui de l’impact de la modélisation sur la performance. Une modélisation relationnelle sur un système distribué peut avoir des impacts catastrophiques sur les temps d’exécution des requêtes pour une simple raison : les jointures et les systèmes distribués ne font pas bon ménage.

Mettons-nous en situation sur Hadoop avec Hive/Spark d’une jointure sur des données réparties sur différentes machines. Les données sont sous la forme de blocs, chaque bloc appartenant à un fichier logique (au sens HDFS), un ou plusieurs fichiers forment une “table” telle qu’elle est déclarée sous la forme de métadonnées dans le metastore de Hive. Les blocs sont répartis sur les différents disques d’une machine et sur plusieurs machines.

Prenons maintenant deux tables et réalisons une jointure sur des champs : que fait le job Spark déclenché par Hive ?

Avant de répondre à cette question, il convient d’expliquer comment une base de données relationnelle réalise l’opération de jointure de manière classique :

  • La première étape est de construire une “hashtable” faisant la correspondance entre le (ou les) champ(s) de jointure et l’ID interne de la ligne dans la table. Le système choisit la plus petite des deux tables. Cette hashtable est construite en mémoire dans la limite d’une taille définie et de l’espace disponible.
  • La deuxième étape consiste à filtrer les données de la deuxième table en associant l’ID de ligne correspondant dans la hashtable.
  • Le moteur de la base de donnée réitère l’opération depuis la première étape jusqu’à ce que toutes les données de la 1ère table aient été parcourues. Et voilà, le tour est joué, les lignes des 2 tables ayant la même valeur pour le (ou les) champ(s) choisis ont été associés (c’est ce qu’on appelle une jointure).

Dans un système distribué, la première étant globalement la même en répartissant le travail sur plusieurs processus (Executor en Spark) répartis sur plusieurs machines. Chaque Executor construit un morceau de la hashtable correspondant  aux blocs de données présents sur la machine. On obtient donc une hashtable répartie. La deuxième étape est beaucoup complexe, les données de hashtable et de la table à joindre n’étant pas sur les mêmes machines, il est nécessaire de rendre disponible la hashtable pour tous les Executors qui participent au job. Ce mécanisme s’appelle “Shuffle”. Il implique des opérations intensives en CPU, réseau et IO disques pour que les Executors qui vont parcourir la deuxième table obtiennent toutes les données de la hashtable en appelant successivement les autres Executors via une API distante (utilisant le framework Netty) avec des étapes de sérialisation / dé-sérialisation des données consommatrice en CPU et en mémoire. Restons dans un cas extrêmement simplifié car Spark utilise tout une panoplie de mécanismes avancés pour optimiser l’opération (notamment Tungsten).

Vous l’aurez compris, les jointures sont les ennemies de la performance dans un système distribué. Quel est la solution ? Dé-normalisez, oubliez un instant ce que vous avez appris et vous allez regrouper les données dans une même table. Cela implique de la duplication de données mais avec un socle big data, cela a été rendu possible à un coût raisonnable.

Evidemment cela n’est pas toujours possible de dé-normaliser sans augmenter de manière trop conséquente le volume d’une base de données et les jointures restent souvent nécessaires. Il n’y a hélas pas de règle absolue pour décider de dénormaliser. Cependant, quand une entité a une faible cardinalité, il est plutôt souhaitable de ne pas en faire une table.

Un exemple concret pour illustrer cette idée :

 

Table Clients (plusieurs dizaines de millions de lignes)

ID Client

Nom Client

Magasin préféré

007

BOND

MILLENIUM

008

DUPONT

KWARK

009

MARTIN

LES 5 TEMPS

010

HOLMES

KWARK

 

Table Magasins (Plusieurs centaines de lignes)

Nom

Adresse du magasin

MILLENIUM

NANTES

KWARK

LILLE

LES 5 TEMPS

PARIS

 

En modèle normal, il faut faire une jointure pour obtenir l’adresse du magasin préféré d’un client.

En dé-normalisant, on obtiendrait une table client avec de la donnée dupliquée (violation de la 2ème fn) mais avec un accès direct à une information importante pour le traitement que vous voulez réaliser.

ID Client

Nom Client

Magasin préféré

Adresse du magasin

007

BOND

MILLENIUM

NANTES

008

DUPONT

KWARK

LILLE

009

MARTIN

LES 5 TEMPS

PARIS

010

HOLMES

KWARK

LILLE

 

Conclusion :

Pour ceux qui connaissent les formes normales, voici donc comment l’évolution des technologies amène à remettre en cause quelques fondements de notre enseignement. Il n’est pas rare qu’on fasse des concessions pour garantir la performance, car finalement le monde parfait n’existe que dans les livres 🙂

 

Pour postuler directement et joindre notre équipe de Data Engineer, c’est par ici.