← Retour au Blog
Tutorials & How-Tos

Polars : exécution différée en streaming, quand utiliser collect ou sink_parquet

Choisissez la destination d’une requête tabulaire avant de choisir sa méthode finale d’exécution. Ce guide fondé sur la documentation explique les lectures différées depuis des fichiers, les plans logiques et physiques, ainsi que les vérifications qui distinguent un DataFrame de taille limitée d’un résultat Parquet plus volumineux enregistré sur disque.

Rédigé par Hamza Diaz
12 octobre 202610 min de lecture30 vues

Prenons le cas d’un développeur fictif qui travaille à partir d’une seule table source. Un analyste souhaite obtenir un petit résumé par catégorie dans Python ; un pipeline de données doit enregistrer sur disque certains enregistrements. Même entrée, tâches différentes. La destination doit guider le choix du mode d’exécution.

Pour l’exécution différée en streaming avec Polars, commencez par une lecture différée depuis un fichier et inspectez la requête. Utilisez collect lorsque le consommateur a besoin d’un DataFrame dont la taille est volontairement limitée, ou sink_parquet lorsque le résultat doit être enregistré sur disque. Le guide des sources et des destinations explique comment la lecture et le traitement s’articulent avec l’écriture. Le streaming ne garantit pas un plafond de consommation mémoire.

Ce guide s’appuie sur la documentation. L’exemple utilisant un fichier local est hypothétique et n’a pas été exécuté ; aucun résultat de mesure comparative n’est présenté.

Commencez par une lecture différée, plutôt que par un DataFrame déjà chargé

Ce que scan_parquet et scan_csv permettent à l’optimiseur de voir

Un LazyFrame adossé à un fichier décrit les opérations avant que le résultat soit matérialisé. Avec scan_parquet ou scan_csv, l’optimiseur peut rapprocher du lecteur les filtres et les sélections de colonnes qui s’y prêtent. Le guide de l’API d’exécution différée illustre la remontée des prédicats et des projections vers la source à l’aide d’une agrégation filtrée.

La projection conserve les colonnes nécessaires à la requête. La remontée des prédicats rapproche de la source les conditions de sélection de lignes qui s’y prêtent. Ni l’une ni l’autre ne vous autorise à modifier ce que reçoit le consommateur. Si un traitement en aval a besoin d’un identifiant, le supprimer modifie le contrat de données. Il en va de même pour la suppression de valeurs manquantes qui ont une signification.

Toutes les expressions ne peuvent pas être déplacées vers le lecteur. CSV et Parquet n’offrent pas nécessairement les mêmes économies d’entrées-sorties. Inspectez le plan, puis mesurez les entrées-sorties avant d’affirmer que vous réalisez des économies.

Pourquoi read().lazy() ne peut pas annuler une lecture immédiate

Lorsque read_parquet(...).lazy() atteint .lazy(), le chargement immédiat a déjà eu lieu. Les transformations suivantes peuvent être différées ; la lecture initiale ne peut pas être annulée. La documentation des sources et des destinations recommande scan_* plutôt que read_* pour les traitements de fichiers que l’on souhaite exécuter de façon différée.

Cela ne signifie pas qu’un DataFrame chargé immédiatement soit une erreur. Le guide de l’API d’exécution différée décrit son intérêt pendant l’exploration, lorsque les résultats intermédiaires aident à comprendre les données. Choisissez-le pour cette raison, plutôt que de réécrire un travail exploratoire utile dans le seul but de le qualifier de streaming.

L’exemple présenté ici suppose un fichier local hypothétique events.parquet. Ses colonnes record_id et category sont de type String ; value est de type Float64. Aucun schéma n’a été inspecté. Les deux consommateurs possibles ont besoin soit d’enregistrements sélectionnés au niveau des lignes, soit d’un résumé par catégorie dérivé de ces enregistrements.

Lisez séparément le plan logique et le plan physique de streaming

Utilisez explain pour inspecter l’optimisation

Comparez query.explain(optimized=False) à query.explain(optimized=True). Le guide des plans de requête montre un filtre déplacé dans une lecture différée après optimisation. Ces vues décrivent les opérations prévues. Elles ne mesurent ni le temps d’exécution ni le pic de consommation mémoire, et elles n’établissent pas la justesse du résultat.

Vérifiez que la projection et la sélection attendues apparaissent. Si l’une des deux manque, examinez le problème avant d’interpréter les mesures de ressources. Vérifiez ensuite le filtre lui-même au regard de la règle métier : un filtre placé au bon endroit peut malgré tout sélectionner les mauvaises lignes.

Utilisez le graphe physique pour examiner les opérations sensibles à la consommation mémoire

Le guide du streaming documente query.show_graph(plan_stage="physical", engine="streaming") et une légende pour les opérations potentiellement gourmandes en mémoire. Le rendu nécessite que Graphviz soit accessible dans PATH, comme l’explique le guide des plans de requête. Sans cet outil, la sortie textuelle de explain reste utile, mais elle ne fournit pas la même vue physique.

Le streaming fonctionne par lots. Certaines opérations sont intrinsèquement incompatibles avec le streaming ; d’autres ne disposent pas encore d’une implémentation en streaming et se replient sur une exécution en mémoire. Utilisez le graphe physique pour repérer ces points de vigilance. Il ne garantit pas un pic maximal de mémoire résidente, ou RSS.

Vérifiez également les valeurs par défaut propres à chaque version. La référence stable actuelle de collect indique que engine="auto" suit Config.set_engine_affinity ou POLARS_ENGINE_AFFINITY, et utilise sinon le streaming par défaut. Les installations plus anciennes peuvent se comporter différemment. Définir explicitement engine="streaming" exprime votre intention, mais ne prouve pas que toutes les opérations s’exécutent en streaming. Consignez la version installée et la configuration avant de vous appuyer sur la documentation actuelle.

Choisissez collect ou sink_parquet selon le résultat dont vous avez besoin

Déterminez ce dont le consommateur suivant a besoin. La taille du fichier source ne suffit pas à répondre à cette question.

Résultat requisPoint de départMéthode finale ou prochaine décisionRéserve essentielle
Petit résumé dans PythonLecture différée avec les filtres et l’agrégation convenuscollect avec un moteur choisiLimiter le nombre de lignes du résumé et valider les valeurs
Enregistrements transformés sur disqueLecture différée avec la sélection de lignes et la projection convenuessink_parquet vers un chemin provisoireL’état des opérateurs et le contenu écrit doivent encore être vérifiés
Exploration supplémentaire de données chargéesDataFrame existantChoisir les transformations suivantes appropriées.lazy() ne peut pas annuler le chargement antérieur
Traitement continu des modifications de donnéesÉvénements et conception de l’ingestion et de la repriseDécision d’architecture distincteCes méthodes de traitement par lots ne constituent pas un service de CDC

Un résultat en mémoire de taille limitée

collect matérialise un LazyFrame en DataFrame. L’objet renvoyé nécessite de la mémoire, même si les opérations en amont s’exécutent en streaming. L’agrégation seule ne suffit pas à le rendre petit : le nombre de catégories détermine le nombre de lignes restantes dans le résumé.

Un résultat enregistré sans matérialiser toute la sortie

La référence de sink_parquet décrit l’évaluation en streaming et l’écriture Parquet sans exiger au préalable que l’appelant obtienne le DataFrame final complet. La valeur par défaut lazy=False exécute l’écriture. Avec lazy=True, l’exécution attend une matérialisation ; les arguments engine et optimizations de la méthode d’écriture ne s’appliquent pas lors de la construction de ce plan différé. Choisissez les paramètres d’exécution lors de sa matérialisation. L’option différée est signalée comme instable.

Distinguez les règles de repli des deux méthodes. La référence actuelle de la méthode d’écriture suit l’affinité du moteur pour la sélection automatique, utilise sinon le streaming, et décrit un repli du moteur sélectionné vers le streaming. La référence de collect décrit un repli du moteur sélectionné vers une exécution en mémoire. Les limites propres aux opérations continuent de s’appliquer. Les paramètres de compression et de groupes de lignes ne définissent pas un budget mémoire fixe.

La question historique de la communauté sur DuckDB et Polars aborde la capture des modifications de données de PostgreSQL. Considérez-la comme une question de terminologie, plutôt que comme un conseil d’architecture vérifié. Le streaming par lots présenté ici ne fournit pas un service permanent avec des positions de lecture, une gestion du temps des événements ou des garanties de livraison.

Le guide pratique Polars centré sur le résultat

Définissez le contrat du résultat avant de choisir l’exécution

Le guide pratique Polars centré sur le résultat est une liste de vérification éditoriale originale, et non une bibliothèque ou un déploiement validé. Commencez par identifier le consommateur. Convenez des colonnes et des types requis, puis fixez les filtres, le traitement des valeurs nulles et les éventuelles exigences d’ordre. Ces décisions vous donnent des éléments concrets à valider après l’exécution.

Le diagramme représente de manière conceptuelle le choix de la destination. Il ne s’agit pas d’un plan physique Polars observé.

flowchart TD A[Lecture différée depuis un fichier] --> B[Sélectionner les colonnes et filtrer les lignes] B --> C[Inspecter le schéma et les plans] C --> D{Destination requise} D --> E[Résumé de taille limitée] E --> F[Matérialiser un DataFrame] D --> G[Enregistrements sauvegardés] G --> H[Écrire dans un fichier Parquet provisoire] F --> I[Valider avant la transmission] H --> I

Lisez de façon différée, réduisez et inspectez

Cette illustration suppose que Polars est déjà disponible et utilise l’API stable actuellement documentée. Elle n’a pas été exécutée. Ni la configuration ni le contenu du jeu de données n’ont été vérifiés, et aucune sortie affichée n’est présentée comme obtenue. Le filtre supprime les lignes où value est nul uniquement pour illustrer une règle ; votre jeu de données peut nécessiter une autre règle.

import polars as pl

rows = (
    pl.scan_parquet("events.parquet")
    .select("record_id", "category", "value")
    .filter(pl.col("value").is_not_null())
)

planned_schema = rows.collect_schema()
unoptimized_plan = rows.explain(optimized=False)
optimized_plan = rows.explain(optimized=True)

collect_schema résout le schéma prévu. Cette opération peut lire des métadonnées sur disque ou effectuer des requêtes à distance ; la résolution du schéma n’est donc pas nécessairement sans coût. Elle ne valide pas non plus chaque ligne au regard des règles du domaine. Comparez les noms et les types au contrat, puis inspectez le plan logique et, lorsque les outils le permettent, le graphe physique. Le guide des schémas explique pourquoi des schémas connus sont importants lors de la planification différée.

Choisissez l’une des méthodes suivantes selon la sortie requise. Ce sont des options alternatives non exécutées, et non une séquence. La branche du résumé suppose que vous avez établi indépendamment que le nombre de catégories est gérable. Vous devez également décider de la signification d’une catégorie nulle.

L’option A produit le résumé par catégorie nécessaire dans Python.

summary = rows.group_by("category").agg(
    pl.col("value").mean().alias("mean_value")
)
result = summary.collect(engine="streaming")

L’option B écrit les enregistrements sélectionnés nécessaires sur disque.

rows.sink_parquet("events-selected-candidate.parquet", engine="streaming")

L’API collect et l’API de la méthode d’écriture étayent ces descriptions des méthodes finales. Comparer leurs durées reviendrait à comparer des tâches différentes : l’une renvoie un résumé par catégorie, tandis que l’autre exporte des enregistrements au niveau des lignes. Il ne s’agit pas d’une mesure comparative des moteurs.

Proposez une vérification comparable, puis validez la destination

Étape de vérificationÉléments à consigner ou à inspecterCondition d’arrêt
Définir le résultatDestination, colonnes, valeurs nulles et ordreLes exigences du consommateur restent ambiguës
Figer la comparaisonInstantané des entrées, version et paramètres du moteurLes entrées changent entre les essais
Inspecter les opérations prévuesSchéma, plan logique et graphe physiqueProjection, filtre ou état inattendu
Sélectionner la méthode finaleObjet de taille limitée ou fichier provisoireLe résultat requis ne peut pas être traité en toute sécurité
Valider le résultat provisoireComptages, types, valeurs et règles du domaineDifférences inexpliquées ou sortie partielle

Une comparaison utile conserve la même sortie. Utilisez le même instantané des entrées et la même version de Polars, avec des paramètres, une machine et des conditions de stockage comparables. Le guide d’Optijara sur la conservation des protocoles de mesure comparative avec les résultats aborde cette discipline dans un autre domaine. Gardez le protocole proposé distinct des résultats que vous consignerez ensuite.

Mesure proposéeÉléments à rendre comparablesQuestion à laquelle elle répond
Durée réelle écouléeMêmes entrées, même sortie et mêmes limites de chronométrageCombien de temps cette tâche prend-elle ?
Pic de RSSMême configuration du processus et même méthode d’échantillonnageQuelle pression sur la mémoire du processus se produit ?
Justesse de la sortieSchéma, filtres, valeurs nulles et tolérances sur les valeurs convenusLe résultat prévu a-t-il été préservé ?
Opérations de stockageDestination, état du cache et format de sortieQuelles entrées-sorties accompagnent l’exécution ?

Aucune de ces mesures n’a été effectuée ici. Pour un résultat provisoire enregistré, vérifiez le schéma et le nombre de lignes sans charger toute la sortie dans le seul but de la valider. Comparez les valeurs selon les tolérances convenues ; comparez l’ordre uniquement lorsque le contrat l’exige.

Si l’exécution échoue, considérez les fichiers partiels comme non validés. Examinez l’erreur et les opérations concernées. Utilisez une nouvelle destination provisoire pour la tentative suivante, puis validez-la avant toute transmission autorisée. La présence d’un fichier ne constitue pas un contrôle d’acceptation.

Les erreurs des équipes à propos de l’exécution différée en streaming

Le raccourci tentant consiste à traiter la planification différée et l’exécution en streaming comme une seule décision. Ce sont deux décisions distinctes. Ajouter .lazy() après une lecture ne peut pas modifier ce chargement déjà terminé. Choisir le streaming ne supprime pas le DataFrame final renvoyé par collect.

L’inspection peut également sembler plus concluante qu’elle ne l’est. Un plan explique les opérations prévues ; un schéma résolu fournit des noms et des types. Ni l’un ni l’autre ne permet de savoir si la consommation mémoire est acceptable ou si les lignes conservent leur signification prévue. Distinguez ces vérifications de l’inspection.

Une comparaison de performances devient difficile à interpréter si vous changez les filtres ou la projection entre les essais. Modifier l’instantané ou la destination crée le même problème. Consignez le changement de tâche au lieu d’attribuer chaque différence au moteur. Et traitez les exigences d’ingestion continue en dehors du choix d’une méthode de traitement par lots.

Pour les lignes extraites de PDF, vérifiez que les en-têtes et les unités restent associés aux bonnes valeurs, et conservez les références aux sources avant la transformation tabulaire. Le guide d’Optijara sur la préservation du sens des tableaux extraits de PDF et des références aux sources couvre ce travail en amont. Il n’établit pas l’existence d’une intégration avec Polars. Une autre méthode d’écriture ne peut pas récupérer le sens perdu lors de l’extraction.

Les limites à vérifier avant de modifier un pipeline de données

Compromis entre mémoire, version et stockage

La taille des entrées n’est qu’une partie de la question de la mémoire. Tenez compte de l’état intermédiaire et du résultat final, et vérifiez quelles opérations peuvent s’exécuter en streaming. La documentation du streaming et des méthodes finales explique ces limites. L’adoption demande également un effort d’implémentation et ajoute des entrées-sorties de stockage ou de la complexité opérationnelle. Aucune amélioration du temps d’exécution ou du coût n’est promise ici.

L’étude de PythonSpeed sur la mémoire constitue un exemple historique concret d’analyse propre à une charge de travail. Ses mesures s’appliquent à son jeu de données, à ses versions et à sa plateforme. Elles ne constituent ni des résultats actuels universels ni des tests d’Optijara.

Avant d’utiliser des valeurs par défaut ou des paramètres facultatifs, consultez la documentation de votre version installée. La référence de la méthode d’écriture signale plusieurs options comme instables ou expérimentales. Si vous voulez comprendre un changement, évitez d’ajuster la compression et l’ordre en même temps que les paramètres d’exécution.

Le sens des données et la confidentialité doivent encore être examinés

Pendant la préparation, préservez les identifiants et les limites entre les partitions du jeu de données. Explicitez le traitement des valeurs nulles et conservez l’ordre lorsque le consommateur l’exige. Un fichier Parquet valide ne prouve pas que les exemples prévus ont été préservés. Le guide connexe sur la préservation de la sélection des données et de leur identité temporelle explique ces vérifications pour les jeux de données dépendants du temps ; il ne revendique pas d’intégration avec Polars.

Utilisez des instantanés d’entrée autorisés. Déterminez qui peut accéder aux sorties provisoires et pendant combien de temps les conserver. Une destination locale ne règle pas les obligations de confidentialité, même si la requête s’exécute correctement.

Choisissez la destination avant d’appeler collect

Commencez par le résultat requis par le consommateur. Utilisez une lecture différée pour les traitements adossés à des fichiers que vous souhaitez différer, puis inspectez les plans logique et physique selon leurs objectifs respectifs. Validez la méthode finale choisie et conservez les preuves des mesures réellement effectuées avant de modifier un pipeline opérationnel. Pour vous aider à planifier un processus de travail avec l’IA, Optijara propose du conseil en IA.

Points clés

  • 1Choisissez la destination requise avant de sélectionner collect ou sink_parquet.
  • 2Utilisez scan_* pour les traitements adossés à des fichiers que vous souhaitez différer ; .lazy() ne peut pas annuler une lecture immédiate antérieure.
  • 3Les plans logiques expliquent l’optimisation, tandis que les graphes physiques de streaming aident à examiner les problèmes de mémoire propres aux opérations.
  • 4collect matérialise un DataFrame : limitez donc volontairement la taille du résultat attendu.
  • 5sink_parquet répond aux besoins de sorties enregistrées sans exiger que l’appelant obtienne le DataFrame final complet.
  • 6Mesurez des charges de travail équivalentes et validez le sens des lignes ; l’intention d’utiliser le streaming ne garantit ni un plafond de consommation mémoire ni la justesse du résultat.

Conclusion

Choisissez la méthode finale en fonction du résultat dont le consommateur a besoin. Commencez par une lecture différée pour les traitements adossés à des fichiers que vous souhaitez différer ; matérialisez un DataFrame de taille limitée pour Python, ou utilisez sink_parquet pour enregistrer des enregistrements. Le guide pratique Polars centré sur le résultat maintient ce choix lié au contrat de données. Les plans aident à expliquer l’exécution, mais seules des mesures propres à la charge de travail et une validation de la sortie peuvent étayer une modification du pipeline.

Questions fréquentes

Quelle est la différence entre l’exécution différée de Polars et le streaming ?

L’exécution différée reporte les opérations pour permettre leur optimisation. Le streaming traite les opérations par lots. Un LazyFrame ne prouve pas que toutes les opérations s’exécutent en streaming ni que la consommation mémoire est limitée. Inspectez le plan physique et mesurez séparément la charge de travail réelle.

read_parquet().lazy() économise-t-il de la mémoire lors de la lecture initiale ?

Non. read_parquet charge immédiatement les entrées avant l’appel à .lazy(). Utilisez scan_parquet pour commencer les traitements adossés à des fichiers que vous souhaitez exécuter de façon différée. Les transformations différées ultérieures ne peuvent pas annuler une lecture immédiate déjà terminée.

Quand utiliser collect plutôt que sink_parquet ?

Utilisez collect pour obtenir en mémoire un DataFrame dont la taille est volontairement limitée. Utilisez sink_parquet pour enregistrer une sortie Parquet lorsque le DataFrame final complet n’est pas nécessaire. Examinez la prise en charge des opérations et validez le résultat dans les deux cas.

collect(engine="streaming") peut-il encore consommer beaucoup de mémoire ?

Oui. collect matérialise le DataFrame renvoyé. L’état des opérateurs et les opérations sans streaming peuvent ajouter une pression sur la mémoire. Vérifiez le comportement du moteur propre à la version, inspectez le plan physique et mesurez le pic de RSS plutôt que de supposer un plafond de consommation mémoire.

Le streaming de Polars constitue-t-il un pipeline CDC permanent ?

Non. Le moteur de streaming par lots décrit ici n’est pas en lui-même un service de capture des modifications de données. L’ingestion continue, les positions de lecture, la gestion du temps des événements et la reprise nécessitent une architecture distincte. Ce guide ne recommande pas de pile CDC de production.

Sources

Partager cet article

Hamza Diaz

Rédigé par

Hamza Diaz

Hamza Diaz est le fondateur d’Optijara, où il conçoit des agents IA pratiques, des systèmes d’automatisation et des workflows Copilot pour les entreprises de services. Il écrit sur les opérations IA, la stratégie d’agents et la mise en œuvre concrète pour les équipes qui veulent des systèmes utiles plutôt que du battage médiatique.