Contexte et limites de Pandas

Pandas reste le choix dominant pour la manipulation de DataFrames en Python jusqu’à quelques dizaines de gigaoctets. Au‑delà de 10 Go, les utilisateurs rencontrent des dépassements de mémoire et des ralentissements notables, ce qui crée ce que l’auteur appelle le « Pandas cliff ». Le problème provient d’une exécution stricte, eager, où chaque étape charge l’ensemble du jeu de données en RAM avant d’appliquer les transformations.

Analyse des données d’Amazon Redshift

Le rapport d’Amazon (2024) indique que 94,68 % des tables de la flotte Redshift contiennent moins de 100 GB, et que 86,9 % des requêtes traitent 80 GB ou moins. Ces chiffres reposent sur deux hypothèses : une taille moyenne de ligne de 1 KB et une capacité de lecture de 8 GB/s par machine sur un cluster de 10 nœuds. En combinant ces paramètres, le calcul montre qu’un cluster peut ingérer 80 GB en une seconde (10 machines × 8 GB × 1 s). Même en augmentant la taille de ligne à 10 KB, la plupart des tables restent en dessous du téraoctet, confirmant que la majorité des charges de travail ne justifient pas un système distribué.

Alternatives : Polars et DuckDB

Polars, bibliothèque Rust, adopte une exécution lazy similaire à un SGBD. La fonction scan_csv lit les fichiers par blocs, construit un graphe d’opérations optimisé, puis exécute le pipeline lors de l’appel à .collect(). Cette approche permet de paralléliser les agrégations sur tous les cœurs disponibles et de réduire l’usage de la mémoire grâce au streaming. DuckDB, quant à lui, se présente comme un SQLite dédié à l’analyse analytique : il charge les données en mémoire, exploite des algorithmes vectorisés et offre un moteur de requêtes SQL complet, éliminant ainsi le besoin de convertir les DataFrames en structures intermédiaires.

def do_1brc_pandas(file_path: str):
    df = (
        pd.read_csv(file_path, sep=";", names=["station", "measurement"])\
        .groupby("station")\
        .agg({"measurement": ["min", "mean", "max"]})\
        .round(2)
    )

Dans cet extrait, chaque opération s’exécute immédiatement, ce qui oblige Pandas à conserver l’ensemble du CSV (potentiellement plusieurs dizaines de gigaoctets) en mémoire.

def do_1brc_polars(file_path: str):
    df = (
        pl.scan_csv(
            file_path,
            separator=";",
            new_columns=["station", "measurement"],
            has_header=False,
        )
        .group_by("station")
        .agg(
            pl.col("measurement").min().round(2).alias("min"),
            pl.col("measurement").mean().round(2).alias("mean"),
            pl.col("measurement").max().round(2).alias("max"),
        )
        .collect(new_streaming=True)
    )

Évaluation de performance (1 Billion Row Challenge)

Le défi 1 Billion Row Challenge impose le calcul du minimum, de la moyenne et du maximum sur un CSV de 1 milliard de lignes. Sur une instance AWS m7a.8xlarge (32 cœurs, 128 GB RAM, CPU AMD), la solution la plus rapide a atteint 1,5 s. Les implémentations Polars et DuckDB reproduisent ce résultat en moins de deux secondes, grâce à la lecture en flux et à la parallélisation native. En comparaison, le même code Pandas dépasse largement la minute, limité par le chargement complet du fichier et l’absence de pipeline d’optimisation.

Ces mesures confirment que, pour les charges de travail situées entre 10 GB et 100 GB, les outils monolithiques modernes offrent un compromis de performance et de simplicité supérieur à Pandas, tout en évitant la complexité d’une infrastructure distribuée.