Ysance - Your Data in Action

    Conseil N°2 pour être un super Data Engineer

    [fa icon="calendar"] 06/01/20 15:36 / par Laurent Letourmy

    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.

    Thèmes : Actu Ysance, Data Services, Data Engineer

    Laurent Letourmy

    Par Laurent Letourmy

    Ingénieur Epita, il a débuté sa carrière dans le groupe Cross Systems dont il a co-fondé la filiale parisienne en 1996. La société connaît une forte croissance et le groupe s'introduit sur le Nouveau Marché en 1999 et atteindra une capitalisation de 800M€. Spécialiste reconnu des architecture transactionnelles, il y exerce diverses responsabilités techniques, managériales et commerciales et travaille avec des clients tels que le Club Med, Voyages-Sncf.com, Orange. Entrepreneur passionné de technologies innovantes, vainqueur de deux hackathons dans la Silicon Valley, investisseur actif au sein de nombreux de nombreux projets pionniers B2B et B2C. En 2005, il co-fonde et dirige Ysance qui connaît un fort développement depuis ses débuts.

    S'abonner au blog