Parquet ou comment même des data-engineers peuvent devenir des "sups"

Le format Parquet, grâce à son stockage hybride colonnaire et ses techniques avancées d'encodage, permet une compression remarquable des données, réduisant significativement l'espace de stockage nécessaire et l'empreinte carbone associée. En optimisant l'utilisation de Parquet, notamment par le tri judicieux des données avant l'écriture, les ingénieurs de données peuvent maximiser ces bénéfices, faisant de la gestion efficace des données un acte concret en faveur de l'environnement.

Aujourd'hui, les données numériques représentent 3 à 4% des émissions mondiales de gaz à effet de serre.

La big data n'est pas étrangère à cela...

4% représente la même part d'émissions que tous les pays d'Afrique subsaharienne réunis.

Voyons comment, avec juste une ligne de code et 1-2 calories cérébrales brûlées, nous pouvons réduire considérablement cela.

Parquet est désormais toujours référencé comme un format colonnaire. Dans chaque documentation ou guide, il est devenu la norme pour les données structurées.

Le principal conseil que l'on trouve est que pour le big data ou l'analytique, Parquet est le format roi. On nous encourage à l'utiliser pour économiser de l'espace et accélérer la lecture.

Mais voyons comment une légère compréhension du format pourrait nous aider à gagner encore plus que ce qui est promis en termes de compression et d'économie d'espace.

Bien sûr, nous perdrons un peu en temps de traitement. Mais pour les données fréquemment consultées, nous économiserons beaucoup de bande passante. On peut le considérer comme un surcoût à l'écriture mais avec un énorme retour sur investissement !

Nous passerons rapidement en revue 1 - Le stockage colonnaire vs le stockage par lignes pour couvrir les bases du stockage de données. Ensuite, dans 2 - Les secrets d'encodage de Parquet qui ne sont pas si secrets (il suffisait de lire la documentation), nous entrerons dans le vif du sujet.

I - Stockage colonnaire vs Stockage par lignes

Tout d'abord, nous devons comprendre qu'il existe différents types de stockage dans le domaine des données structurées.

Chacun a sa propre utilisation qui peut être résumée comme suit :

  • Stockage par lignes : Idéal pour les systèmes OLTP (traitement transactionnel en ligne) où un accès rapide à des enregistrements individuels est nécessaire.
  • Stockage colonnaire : Idéal pour les systèmes OLAP (traitement analytique en ligne) où des analyses de données à grande échelle sont effectuées, et où seul un sous-ensemble de colonnes est généralement interrogé.
Représentation pour le stockage par ligne ou par colonne

CSV, Json sont des exemples de stockage par lignes. Ou encore Avro une version plus optimisé pour le row-based storage mais principalement utilisé pour la sérialisation des données en streaming.

PARQUET ou ORC sont communément connus comme étant colonnaires... mais...POURQUOI PARQUET EST-IL SPÉCIAL ?

PARQUET est un TYPE hybride, combinant le stockage colonnaire et par lignes

où les données sont divisées en [Groupes de lignes x Colonnes]

--> Groupe de lignes 1

   ---> Colonne 0...

   ---> Colonne x

--> Groupe de lignes 2.

   ...

Chaque GROUPE DE LIGNES ( Row Group ) stocke ses propres métadonnées sur les colonnes afin de les utiliser plus tard. En effet, pour chaque colonne d'un groupe de lignes, il y a des métadonnées min/max/count qui seront utilisées dans les prédicats push-down lors des lectures de parquet.

II. Secrets de Parquet

Comme les données sont stockées en colonnes, dans Parquet, elles peuvent être facilement encodées avant l'écriture.

Résultant en une grande compression des données. Pour illustrer cela rapidement, générons un jeu de données et stockons-le en csv et parquet.

Étape 1 : Générer un jeu de données d'exemple

import uuid
import pandas as pd
import numpy as np
import os
 
dataset_size = 500_000
# Fonction pour générer une liste de GUID
def generate_guids(size):
    return [str(uuid.uuid4()) for _ in range(size)]

# Créer un DataFrame d'exemple avec des données répétitives
data = {
    'id': np.random.randint(0, 202020, size=dataset_size),
    'guid': generate_guids(dataset_size),
    'category': np.random.choice(['A', 'B', 'C', 'D'], size=dataset_size),
    'country': np.random.choice(['United States', 'France', 'The Netherlands', 'Germany'], size=dataset_size),
    'value': np.random.randn(dataset_size),
}

df = pd.DataFrame(data)

Comparaison de taille entre CSV et Parquet

Avant d'écrire df sur le disque, chaque colonne est encodée avec la meilleure technique pour son type.

Exemple 1 : Colonne Pays

Étape I - le pays est encodé comme un dictionnaire de pays.

Étape II - L'encodage RLE + Bit packing est ensuite appliqué pour maximiser l'encodage et la compression.

Qu'est-ce que cela signifie ?

Si nous trions par Pays avant l'écriture, nous aurions une séquence triée qui pourrait conduire à un packing massif.

Si nous prenons l'exemple ci-dessus, nous aurions ce bit packing :

[0,2] [1,2] [2,1] [3,3] - zéro apparaît 2 fois, 1 apparaît 2 fois, etc...

Pour 1 000 lignes, nous pourrions les encoder en un nombre très limité d'octets !!!!!

Par exemple, ce serait encodé en [0,650] [1,75] [2,150] [3,125] résultant en une réduction de taille de 99% dans ce cas spécifique.

Exemple 2 : Colonne ID et Delta byte Packing

Étape I - Calculer le delta entre chaque ligne pour la colonne ID.

Supposons que cette dernière est triée et incrémentée de 1 Alors cela conduirait à un Delta entre ligne de :

1, 1, 1, 1, 1 ..... 1

Étape II - prendre le delta minimum et calculer le delta relatif.

Le delta minimum est 1 et les deltas relatifs deviennent :

0, 0, 0, 0 ...... 0

Les données encodées finales sont :

en-tête : 8 (taille du bloc), 1 (nombre de miniblocs), 5 (nombre de valeurs), 1 (première valeur)

nous finissons avec pratiquement aucune donnée en comparaison avec une colonne id contenant des millions de valeurs si stockée en texte brut. tel que CSV.

et en pratique, nous n'avons pas grand chose à faire. Parquet s'occupe de tout ... 


parquet_sizes = {}

# Itérer sur chaque colonne, trier, sauvegarder en Parquet, et enregistrer la taille du fichier
for column in df.columns:
    df_sorted = df.sort_values(by=[column])
    parquet_file_name = f'sample_data_sorted_{column}.parquet'
    df_sorted.to_parquet(parquet_file_name, compression='snappy')
    
    # Obtenir la taille du fichier en MB
    parquet_size_sorted = os.path.getsize(parquet_file_name) / (1024 * 1024)
    parquet_sizes[column] = parquet_size_sorted
    print(f"Taille du fichier Parquet (trié par {column}) : {parquet_size_sorted:.2f} MB")

Si vous voulez allez plus loin je vous invite à explorer le notebook que j'ai mis en place.

Conclusion

L'utilisation du format Parquet offre des avantages considérables en termes de compression et d'efficacité de stockage des données. En comprenant et en exploitant les mécanismes d'encodage et de compression de Parquet, notamment le stockage colonnaire et les techniques comme le RLE et le Delta encoding, nous pouvons optimiser significativement nos jeux de données. Cette optimisation ne se traduit pas seulement par des économies d'espace de stockage, mais aussi par une réduction de la consommation d'énergie et, par conséquent, de l'empreinte carbone associée au stockage et à la transmission des données.

En tant qu'ingénieurs de données, nous avons la responsabilité et l'opportunité d'utiliser ces outils de manière intelligente pour contribuer à la réduction de l'impact environnemental du big data.

À propos de l'auteur

Erraji Badr

Inscrivez vous à notre newsletter !

Incroyable !

Merci ! Vous vous êtes bien inscrit à la newsletter !

Oops! Something went wrong while submitting the form :(

Articles suggérés