Lors du PHP Tour 2017 Nantes, nous avions vu la présentation du composant akeneo/batch. Revenons sur 3 ans supplémentaires d'usage, de réflexions et de refactorisation qui ont abouti à la création d'un framework spécialisé.
Dans des environnement de plus en plus interconnectés, de plus en plus hétéroclites, nous voyons apparaitre l'usage des PWA, la généralisation des API et des tâches en files d'attentes asynchrones. Là où les solutions pour interroger des petits volumes de données dans des bases distantes commencent à atteindre une certaine maturité.
Où en sommes-nous sur les synchronisations en grand volume et aux formats de données hétéroclites ?
2. Résumé de
l’épisode
précédent
PHP Tour 2017, Nantes
● Environnements IT de plus en
plus hétéroclites
● Utilisation d’akeneo/batch sur
des grands volumes
● Difficile équilibre entre RAM,
I/O et CPU pour gagner en
performances
● Écosystème PHP assez pauvre
sur le sujet des flux
3. Rappels akeneo/batch
Lire
Lit un flux
Lit un flux, élément par
élément, depuis :
● fichiers
● entités Doctrine
● Message Queues
● API
● ...
Transformer
Transforme l’élément
Transforme les éléments
lus unitairement pour les
faire correspondre au
format de destination.
Utilise les normaliseurs
Symfony ou des
mappeurs de données
Écrire
Enregistre les éléments
Enregistre par lot les
éléments vers :
● fichiers
● entités Doctrine
● Message Queues
● API
● ...
4. Principales problématiques
Lecture
● Fuites mémoire de
l’UoW Doctrine
● Difficultés à traiter
des sources de
données mixtes
Processeur
● Lookups unitaires
● Pas de scission des
lignes en plusieurs
● Pas de fusion des
lignes
Ecriture
● Fuites mémoire de
l’UoW Doctrine
● Taille d’un batch
dans la RAM
Job
● Traitements “doublons”
● Pas de scission des lignes en plusieurs lignes
6. L’histoire de Louis, le développeur back-end
Louis vient de mettre en production une marketplace pour les 70 magasins d’une enseigne de jardinerie.
L’ensemble du catalogue cumule plus de 150.000 références de produits.
Chaque magasin veut pouvoir piocher parmi ce catalogue une liste de référence, y appliquer ses prix et
ses stocks pour qu’ils soient visibles sur leur section de la marketplace.
7. L’histoire de Louis, le développeur back-end
Louis ne dispose pas des outils qui lui permettent de faire des mises à jour quotidiennes. Ses stocks,
prix et contenus des catalogues doivent être mis à jour. Il va devoir les développer.
Pour cela, il doit faire interagir les ERP des 70 magasins avec la Marketplace.
Seulement, les outils dont il dispose ne sont pas adaptés pour traiter des millions d’enregistrements.
10. La Pipeline
Louis imagine une manière différente de résoudre son problème : il va traiter son besoin ligne à ligne et
permettre d’effectuer autant d’opérations de transformation qu’il sera nécessaire.
Louis conçoit un prototype.
Changeons de méthode
11. Extraction
Extraction des
données depuis la
source primaire
Transformation
Transformation des
données en un format
exploitable
Recherche et
fusion
Fusion des données
de la source principale
avec des données de
sources secondaires
...
Le nombre d’opérations de
transformation et de recherche
n’ont pas de limite
Chargement
Chargement des
données consolidées
dans le système tiers
12. La Pipeline
Cette pipeline intègre 3 grandes familles de traitements :
1. l’extraction
2. la transformation
3. le chargement
Louis utilise le patron de conception ETL
Changeons de méthode
13. La Pipeline
Live coding
git clone git@github.com:php-etl/sandbox.git
git checkout step/1-extract-load
run pipeline:customers:run data/10000-customers.csv php://stdout
● Au lieu de lire les lignes par lot, nous allons lire les lignes une à une et les traiter comme un flux
continu.
● À chaque instant, une seule ligne est placée en mémoire, ce qui permet de traiter le cas de
grosses structures de données
● Nous avons besoin de formats source capables d’être lus ligne à ligne (CSV, LDJSON, SQL, etc..)
Changeons de méthode
15. Le Mapping des données
Louis a réussi à créer ses premières pipelines, il peut lire les données provenant de ses ERP et de son
PIM et peut maintenant facilement les agréger.
Il écrit beaucoup de fonctions de mapping, elles sont efficaces et ses synchronisations sont rapides.
Changeons de méthode
16. Le Mapping des données
Live coding
git clone git@github.com:php-etl/sandbox.git
git checkout step/2-transformation
run pipeline:products:run data/10000-products.csv php://stdout
● Le mapping consiste à transformer une donnée d’un format initial vers un format de destination.
● Il peut être effectué sur des données de tous types, il peut permettre par exemple d’aplatir des
tableaux à plusieurs dimensions, ou placer les données dans des structures d’objets.
Changeons de méthode
18. La génération de code spaghetti
Louis a réussi à effectuer son mapping de données. Au fur et à mesure de l’avancement du projet ses
fonctions de mapping deviennent de plus en plus difficiles à relire et à maintenir.
Il a de plus en plus l’impression d’écrire du code spaghetti, il réfléchit à un moyen de simplifier la
maintenance de son code, sans pénaliser les performances qu’il a durement acquises.
Changeons de méthode
19. La génération de code spaghetti
Louis avait essayé d’utiliser la composition et la configuration pour gérer son mapping, mais les
performances n’étaient pas satisfaisantes du tout.
Et vient une idée à Louis : et si on créait un compilateur de code qui permettrait d’avoir le meilleur des
deux mondes : configurer le mapping et disposer d’un code spaghetti très performant que l’on n’aurait
pas à maintenir ?
Changeons de méthode
20. La génération de code spaghetti
Louis trouve deux pistes très intéressantes :
● nikic/php-parser
● symfony/expression-language
Avec ces deux paquets il aura la souplesse dont il a besoin pour générer du code PHP et avoir un
langage procédural simple similaire à celui d’Excel.
Changeons de méthode
21. La génération de code spaghetti
Live coding
git clone git@github.com:php-etl/sandbox.git
git checkout step/3-fast-map
run fast-map:compile
run pipeline:products:run data/10000-products.csv php://stdout
● Le code spaghetti n’est un problème que si on n’arrive pas à le faire évoluer
● La transformation d’une configuration en un code PHP brut permet d’éviter des actions parasites.
Ces actions n’auront pas de plus-value pendant l’exécution. Elles pourraient ralentir la
transformation de données si elle est exécutée des milliers de fois
Changeons de méthode
23. La recherche, la fusion et la scission
Maintenant que Louis peut synchroniser deux systèmes ensemble, il a une nouvelle difficulté : comment
faire pour lire dans plus d’une source de données ?
De la même manière : comment extraire une liste d’images d’après une liste de produits quand un produit
peut avoir un nombre indéterminé d’images ?
Changeons de méthode
24. La recherche, la fusion et la scission
Pour se libérer des contraintes des sources multiples et du lookup unitaire, Louis divise ses Pipelines en
deux parties.
La partie amont enregistre d’abord les données dans une base de données temporaire. Puis la partie aval
extrait le contenu de la base de données vers le système de destination.
Grâce aux Jobs de akeneo/batch, Louis peut séquencer l’exécution de ses pipelines en 4 étapes.
Changeons de méthode
PIM
WMS
ERP
Système
tiers
2
1
3
4
25. La recherche, la fusion et la scission
La recherche de données complémentaires dans un stockage secondaire peut permettre d’enrichir la
source principale de données.
Par exemple, si ma source principale de données produits est mon PIM, les sources secondaires seront
l’ERP pour les prix et le DAM pour les images.
Louis pourra implémenter une étape de transformation qu’il appellera Lookup pour gérer ce besoin.
Changeons de méthode
26. La recherche, la fusion et la scission
La fusion est l’opération de regrouper plusieurs lignes de la source en une seule ligne.
Par exemple, regrouper toutes les lignes d’une commande lorsque l’on reçoit la liste des produits
commandés.
Louis pourra implémenter une étape de transformation qu’il appellera Merge pour gérer ce besoin.
Changeons de méthode
27. La recherche, la fusion et la scission
La scission est au contraire l’opération de séparation en plusieurs lignes de la source initialement sur
une seule ligne.
Par exemple, à partir d’une liste de produits avec leurs images, en extraire la liste des images seules.
Louis pourra implémenter une étape de transformation qu’il appellera Fork pour gérer ce besoin.
Changeons de méthode
28. Problématiques résolues
Lecture
● Fuites mémoire de
l’UoW Doctrine
● Difficultés à traiter
des sources de
données mixtes
Processeur
● Lookups unitaires
● Pas de scission des
lignes en plusieurs
● Pas de fusion des
lignes
Ecriture
● Fuites mémoire de
l’UoW Doctrine
● Taille d’un batch
dans la RAM
Job
● Traitements “doublons”
● Pas de scission des lignes en plusieurs lignes
30. Utilisation de la RAM
Les pièges
L’usage de la mémoire vive peut être un problème lorsque l’on traite un grand nombre de lignes de
données.
Par exemple, les fonctions json_decode(), json_encode(), file() et les classes de
SimpleXML et DOM vont placer l’ensemble de la source (fichier) en mémoire vive.
De la même manière, l’utilisation des tableaux en tant que liste de lignes provoquera une
augmentation de la consommation de mémoire, en plus de provoquer des ralentissements à
chaque redimensionnement du tableau.
31. Fuites mémoire de l’UnitOfWork de Doctrine
Les pièges
Doctrine utilise de manière interne le pattern Unit of Work pour gérer l’état des entités que l’ORM a
sous sa responsabilité.
Bien que ce pattern soit très efficace sur des processus courts, comme l’affichage de pages, il
peut avoir de lourdes conséquences s’il est mal maîtrisé dans des processus longs ou qui
traiteront de grands volumes de données.
L’idéal serait de se passer de l’ORM et utiliser des requêtes SQL préparées quand c’est possible.
Quand ce n’est pas possible, il faut pouvoir traiter par lots et appeler les méthodes flush() et
clear() de l’EntityManager à la suite de l’enregistrement de chaque lot.