Afin d’optimiser les requêtes effectuées sur des fichiers parquets depuis un pool serverless, on travail généralement avec des datasets partitionnés. Cet article décrit comment générer un dataset partitionné via un data flow Azure Synapse Analystics.
Introduction
Lorsque l’on observe la structure hiérarchique d’un Datalake, on tombe très souvent sur une architecture partitionnée (et le plus souvent par date). Ceci est principalement fait pour optimiser les requêtes d’extraction de données qui exploitent souvent des plages de dates bien spécifiques.
Des cas d’usages très fréquents sont :
- Sélection des données d’un mois ou d’une année spécifique
- Sélection des données depuis un mois ou une année spécifique
- Sélection des données entre une plage de date
Ces cas d’usage sont fréquents et lorsque l’on a beaucoup de données, il peut devenir très consommateur de ressource de lire une infime partie des données dans de très gros fichiers parquets.
C’est là qu’intervient le partitionnement. Au lieu d’avoir un « gros » fichier parquet qui (malgré toutes les optimisations du format) demande à être parsé pour retrouver seulement les données nécessaires, on va se limiter à lire uniquement une sélection précise de petits fichiers.
Pour ce faire il faut changer la façon dont on écrit nos données et passer de :
à :
Forcément vu comme ça il y a un peut de travail. Il existe de nombreuses façons de faire ce partitioning et nous allons nous intéresser a produire celui-ci via les pipelines Azure Synapse Analytics (et ça marche aussi via ADF évidemment).
Cas d’usage – Générer un dataset partitionné de nos factures
Cet article peut se suffire à lui-même en utilisant un fichier spécifique et adaptant en conséquence nos développements, mais il utilise la sortie générée dans l’article : Paramétrer les Pipeline Azure Synapse Analytics sans ouvrir Synapse !
En prenant comme source la base WWI de Microsoft on remarque que nos factures sont décomposées en deux tables « Invoices » et « InvoicesLines ». Ce design est très standard ou une table « d’entête » comporte des données globales à plusieurs « lignes » de données.
Si ce design est particulièrement adapté au modèle relationnel, dans le monde du bigdata, on lui préfère très souvent le « grosse table à plat » dans un fichier. Ceci permet à nos scripts ou utilisateurs de se sourcer sur un seul dataset comprenant l’ensemble de leurs données et n’ayant pas besoin de faire des jointures ou autres transformations récurrentes.
Dans le cas des factures, les informations de prix, quantités et articles sont sur les « lignes » cependant la date, le client, l’adresse de livraison sont dans les entêtes. Pour s’simplifier l’usage de ces données, nous allons créer un dataset « Invoice » qui fera la jointure des deux tables et pour optimiser son requêtage futur nous allons partitionner celui-ci par Année/Mois.
Et comme nous créons un nouveau « dataset intelligent » nous allons le positionné dans la zone « Curated » de notre datalake pour pouvoir éventuellement l’exposer à des utilisateurs.
Les mains dedans !
Les ressources utilisées dans cette expérience sont:
- Un Workspace Synapse (la base)
- Un Azure data lake gen 2 (le datalake)
- Deux fichiers parquet qui contiennent les données des tables « Invoices » et « InvoiceLines » de la base WWI (voir article Paramétrer les Pipeline Azure Synapse Analytics sans ouvrir Synapse ! pour les générer)
Vue d’ensemble
La solution consiste en un data flow comprenant les activités suivantes :
- Deux sources
- Une jointure
- Une colonne dérivée
- Une cible
Création du data flow
Pour démarrer, nous allons donc dans la partie développement créer un nouveau data flow :
Nous arrivons maintenant dans l’éditeur de data flow qui nous propose directement d’ajouter une nouvelle source de donnée ainsi que donner un nom à notre data flow :
Ajout de la 1ere source
En ajoutant la source, l’éditeur nous propose de renseigner deux champs importants :
- le « nom de la boite » : oui c’est important pour s’y retrouver plutôt que de tout laisser par défaut de de se retrouver avec source1 / source2 / … dans nos flows !
- le « Dataset » source : on va connecter notre fichier des entêtes de factures « Invoices.parquet »
Création du dataset source
Cette manipulation est récurrente, nous allons donc la décrire pour le premier dataset et vous serez ensuite seul pour les suivants 😉 (tant qu’ils n’auront rien de particulié).
Donc très simplement, nous allons cliquer sur « + » :
La première étape est de sélectionner le « data store », le type de stockage ou se trouve dans notre fichier. Dans notre cas, ce sera un Azure Data Lakke Storage Gen2 :
Nous devons ensuite sélectionner le format de fichier. Nous utilisons des fichiers parquet :
Nous devons maintenant donner un nom et définir le service lié utilisé pour retrouver notre fichier. Nous choisissons donc notre datalake :
Pour ensuite configurer le chemin de notre fichier. Pour se faire, on peut remplir à la main le chemin ou utiliser le navigateur :
Notre dataset est maintenant configuré :
Le reste de la configuration peut rester par défaut. Nous avons la possibilité de faire une preview des données en démarrant une session de debug au besoin.
Ajout de la 2ème source
La 2ème source ce configure exactement de la même manière que précédemment en changeant simplement le fichier source par « InvoiceLines.parquet ».
Création de la jointure
En cliquant sur le petit « + » nous allons ajouter notre composant de jointure :
Le paramétrage est simple. On cherche à ajouter à chaque entête ces lignes. On fait donc une jointure gauche des « Invoices » vers les « InvoiceLines ». La clef de jointure est en général simple (si le modèle source est propre) et dans notre cas « InvoiceID » = « InvoiceID » :
Création des colonnes dérivées
Jusqu’à présent, nous avons surtout préparé notre jeu de donnée. Nous arrivons maintenant au moment ou l’on va préparer notre partitionnement. Pour rappel, nous voulons partitionner par Année/Mois. Nous avons bien une date de commande, mais s’il est possible de configurer le partitionnement en utilisant une expression spécifique, je recommande vivement de générer les valeurs correctement dans des colonnes pour ensuite utilise directement cette valeur dans le partitionnement.
Pour ce faire, nous allons donc générer deux colonnes année et mois à partir de notre date de commande via les derived column. On ajoute donc le composant :
La configuration est très simple dans notre cas et il suffit d’écrire les expressions correspondantes en ajoutant les nouvelles colonnes :
- year –> year(InvoiceDate)
- month –> month(InvoiceDate)
Création des partitions
Notre dataset est prêt, il nous reste à écrire celui-ci dans sa destination en paramétrant le partitionnement. C’est sur la destination que tout va se configurer et nous allons ajouter un récepteur :
Création du dataset de destination
Techniquement parlant, la création du dataset passe par les mêmes étapes que précédemment sauf que dans notre cas nous ne souhaitons pas « forcer » le chemin du fichier ni le nom de ceux-ci, car nous espérons justement partitionner notre dataset.
Pour se faire, il suffit de configurer notre dataset avec uniquement les informations connues pour le file path. Nous souhaitons que notre dataset soit partitionné dans la zone « curated » de notre lake et dans le répertoire « invoice » de cette zone. C’est ce que nous allons renseigner sans préciser de nom de fichier :
Notre destination est maintenant configurée :
Définition du partitionnement
S’il est possible de configurer très finement notre destination, la chose qui nous intéresse dans cet article est le partitionnement et pour ce faire nous avons rendez-vous dans l’onglet d’optimisation de notre récepteur.
ici plusieurs options s’offrent à nous. Cependant, tout le travail réalisé précédemment a eu pour but de définir correctement nos partitions en créant des colones spécifiques. notre choix se porte donc sur un partitionnement défini par clé pour laquelle nous allons définir dans l’ordre nos colonnes :
C’est cette configuration précisément qui va « éclater » notre dataset en plusieurs fichiers déposé dans des répertoires différents pour chaque valeur de la colonne « year » et à l’intérieur de celle-ci dans différents répertoires pour chaque valeur de « month ». L’interface nous indique que chaque partition aura des valeurs uniques pour chaque colonne définie.
Vérification
Il ne nous reste plus qu’à exécuter notre data flow pour partitionner notre dataset. Pour ce faire nous pouvons l’intégrer à un pipeline existant ou en créer un nouveau.
Pour l’occasion, nous allons un nouveau pipeline avec l’ajoute de l’activité « Data flow » configurée pour exécuter le data flow développé précédemment.
Nous nous assurons que notre datalake est « propre » afin de bien observer notre résultat :
Lançons maintenant l’exécution (ou le debug) de notre pipeline pour réellement traiter notre fichier :
Si tout s’est bien passé, après le succès de notre pipeline, nous pouvons naviguer dans notre nouveau dataset partitionné :
Conclusion
Le partitionnement d’un dataset pour optimiser sa lecture future n’est pas compliqué à réaliser techniquement, mais demande cependant une phase de réflexion pour la définition de nos partitions.
Cet article avait pour but de décrire le partitionnement en utilisant des dataflow Synapse, mais il est évidemment possible de créer ce type de partitionnement différemment notamment avec Spark directement.
Pour aller plus loin
Pour utiliser ce nouveau dataset de façon optimal avec un pool serverless vous pouvez maintenant suivre l’excellente vidéo avec Stijn Wynants (@Azure_Synapse) et Filip Popovic (@SQLStijn) sur le sujet : Synapse Espresso: Partitioning
Une réponse à “Générer un dataset partitionné via un data flow Azure Synapse Analytics”
[…] Il reste maintenant de nombreuses optimisations techniques à mettre en place avec de la mise à jour incrémentale, de l’optimisation sur la structure de nos fichiers comme le partitionnement de fichier vu dans cet article : Générer un dataset partitionné via un data flow Azure Synapse Analytics – NiceData […]