Vindle optimise les importations massives de CSV en remplaçant PHP par Rust
Vindle a réduit le temps de traitement de 27 Go de données produits en déplaçant le filtrage CSV de PHP vers un outil Rust personnalisé, éliminant ainsi les passes d'analyse redondantes.
Traduit automatiquement depuis l’original en anglais.
Vindle, une plateforme de comparaison de prix, a récemment refondu son pipeline d'ingestion de données pour gérer une nouvelle intégration massive avec Bol.com. Face à une augmentation du volume de produits de trois ordres de grandeur, l'équipe d'ingénierie a remplacé son analyseur initial basé sur PHP par une application Rust personnalisée. Ce changement leur a permis de traiter efficacement 27 Go de flux CSV compressés tout en respectant des horaires de mise à jour stricts toutes les heures.
Ce qui s'est passé
Le défi a commencé lorsque Vindle a intégré Bol.com, un détaillant généraliste proposant plus de 73 millions de produits. Les données arrivaient sous forme de 26 fichiers CSV compressés totalisant environ 27 Go, avec des tailles de fichiers individuelles allant de 1,5 Mo à 4,8 Go. Comme Vindle met à jour les prix chaque heure, le système devait analyser, filtrer et stocker ces entrées rapidement. La première implémentation utilisait la fonction fgetcsv() de PHP au sein d'une application Symfony. Bien que cette approche ait été rapide à développer et suffisante pour des flux plus petits, elle est devenue un goulot d'étranglement lors du traitement de millions de lignes dont plus de 99 % étaient filtrées en raison de catégories non pertinentes ou de données manquantes.
Pour combler ce déficit de performance, l'équipe a d'abord introduit Xan, un outil CSV en ligne de commande écrit en Rust. Cela leur a permis de déléguer le filtrage des lignes et la sélection des colonnes depuis PHP, ne renvoyant que les données pertinentes dans l'application principale. Cependant, Vindle nécessitait également des statistiques détaillées sur les raisons du rejet des lignes, telles que des marques invalides ou des régions d'expédition non prises en charge. Xan ne pouvait pas fournir ces métriques en une seule passe, obligeant l'équipe à exécuter l'outil deux fois : une fois pour les statistiques et une fois pour les données filtrées. Cette approche à double passe ajoutait 20 à 40 secondes par flux et dupliquait le travail coûteux d'analyse CSV.
Une tentative de parallélisation de ces deux processus Xan à l'aide de tuyaux Unix et de tee a échoué en raison de problèmes de contre-pression (backpressure). La branche la plus lente bloquait l'ensemble du pipeline, et la complexité de la gestion des erreurs augmentait sans apporter de gains de vitesse. Finalement, l'équipe a écrit un petit programme Rust dédié qui effectuait le filtrage et la collecte de statistiques en une seule passe. Cet outil lit depuis l'entrée standard, applique les règles d'exclusion, écrit les lignes valides sur la sortie standard et envoie les statistiques sur la sortie d'erreur standard. Cette solution a restauré l'efficacité d'une passe unique tout en fournissant les informations nécessaires, sans nécessiter une réécriture complète du cœur Symfony.
Détails clés
- Bol.com fournit plus de 73 millions de produits répartis sur 26 fichiers CSV compressés totalisant environ 27 Go.
- L'implémentation PHP initiale utilisait
fgetcsv()mais peinait face au volume de données nécessitant un filtrage intensif. - Le filtrage supprime entre 90 % et 99,9977 % des lignes selon des critères tels que la catégorie, la validité du prix et l'emplacement d'expédition.
- L'utilisation de Xan pour le prétraitement a amélioré la vitesse mais nécessitait deux passes distinctes pour recueillir à la fois les données filtrées et les statistiques de rejet.
- La parallélisation des processus Xan via des tuyaux Unix a introduit des goulots d'étranglement liés à la contre-pression et n'a pas amélioré le débit global.
- Un outil CLI Rust personnalisé utilisant la bibliothèque
simd-csvgère désormais le filtrage et les statistiques en une seule passe, diffusant les résultats directement vers PHP.
Contexte
L'analyse CSV est souvent considérée comme triviale, mais à grande échelle, elle devient intensive en CPU et en E/S. Dans des scénarios à haut volume, le coût de lecture, de décompression et de tokenisation de chaque caractère d'un fichier s'accumule rapidement. Lorsque la plupart des lignes sont jetées, la surcharge liée à leur analyse complète avant rejet représente un calcul gaspillé. Des outils comme Xan exploitent des optimisations de bas niveau, telles que les instructions SIMD (Single Instruction, Multiple Data), pour accélérer cette analyse. Cependant, les outils génériques peuvent manquer de la logique métier spécifique nécessaire aux filtres complexes ou au suivi statistique. Écrire un petit binaire ciblé dans un langage système comme Rust permet aux développeurs de combiner une analyse haute performance avec une logique personnalisée, évitant ainsi la surcharge des interpréteurs polyvalents comme PHP pour les boucles serrées.
Pourquoi c'est important
Pour les équipes exécutant des applications auto-hébergées, ce cas souligne l'importance d'identifier la bonne frontière entre le code applicatif de haut niveau et le traitement des données de bas niveau. Déplacer l'ensemble du workflow d'ingestion vers Rust aurait introduit une dette de maintenance et une complexité significatives. En laissant Symfony assurer l'orchestration, la gestion des identifiants et la persistance en base de données, l'équipe a conservé les avantages de productivité de PHP tout en résolvant le goulot d'étranglement de performance spécifique avec un outil spécialisé. Cette approche hybride permet aux ingénieurs d'optimiser les chemins critiques sans sacrifier la facilité de développement offerte par leur framework principal.
De plus, le passage au streaming de données réduit la dépendance au disque. Le processus original nécessitait l'écriture de gros fichiers temporaires sur le disque, consommant un espace de stockage et une bande passante E/S importants. Le nouvel outil Rust traite les données de l'entrée standard à la sortie standard, permettant un workflow sans disque. Ceci est crucial pour les environnements ayant un stockage limité ou où la contention E/S peut impacter d'autres services. Cela démontre que les améliorations de performance proviennent souvent de changements architecturaux, tels que l'élimination des passes redondantes et la réduction du stockage intermédiaire, plutôt que de la simple optimisation de la syntaxe du code.
Ce que vous pouvez faire
- Analysez vos pipelines d'ingestion de données pour identifier si l'analyse ou le filtrage est le principal goulot d'étranglement avant de réécrire le code.
- Envisagez de déléguer le traitement lourd ligne par ligne à des binaires externes écrits dans des langages système comme Rust ou Go.
- Évitez de paralléliser des tâches d'analyse identiques via des tuyaux Unix si les branches ont des charges de travail inégales, car la contre-pression limitera le débit.
- Concevez des outils CLI pour accepter l'entrée depuis l'entrée standard et écrire sur la sortie standard afin de permettre le streaming et de réduire l'utilisation du disque.
- Conservez la logique métier et la gestion des états dans votre framework applicatif principal, en utilisant des outils externes uniquement pour des transformations déterministes à haut volume.
- Mesurez l'impact de plusieurs passes sur de gros fichiers ; combiner le filtrage et les statistiques en une seule passe donne souvent de meilleurs résultats que la parallélisation.



