Le pipeline tournait en vingt minutes il y a six mois. Le volume a triplé, il en prend deux heures, et la fenêtre de traitement nocturne ne suffit plus. Les métriques désignent un seul transform, un Group By ou un Calculator, qui plafonne pendant que tout le reste attend.

Le réflexe est connu : on demande à ce transform de démarrer quatre copies, on relance. Le pipeline va plus vite — et les totaux sont faux. Voici pourquoi ce piège existe, et comment le partitionnement permet de paralléliser un transform agrégeant sans perdre la justesse du résultat.

Prérequis

1. Ce qu’Apache Hop parallélise déjà sans vous

Un pipeline Hop n’est pas une suite d’étapes exécutées l’une après l’autre. La documentation est explicite : par défaut, chaque transform s’exécute en parallèle, dans son propre thread. Les lignes circulent d’un transform au suivant à travers des tampons mémoire appelés row sets, et tous travaillent en même temps.

Votre pipeline est donc déjà parallèle. L’objectif n’est jamais de « le paralléliser », mais d’élargir le seul point qui bride la chaîne. Hop distingue deux axes : le scaling up, qui exploite les cœurs d’un serveur, et le scaling out, qui répartit le travail sur plusieurs serveurs.

2. Augmenter le nombre de copies d’un transform

Le mécanisme de base s’appelle Specify copies. Cliquez sur l’icône du transform : un dialogue contextuel s’ouvre, où vous choisissez l’icône Specify copies, catégorie Data routing. Hop demande un nombre, puis l’affiche dans le coin supérieur gauche de l’icône — la notation x2 indique que deux copies seront démarrées. La page Partitioning du manuel nomme cette option Change Number of Copies to Start.

Sans autre réglage, Hop répartit les lignes en round-robin : avec N copies, la ligne 1 part vers la copie 1, la ligne 2 vers la copie 2, la ligne N vers la copie N, puis la ligne N+1 revient à la copie 1. Aucune règle métier, juste une distribution tournante. Le réglage est persisté dans le fichier .hpl :

<copies>4</copies>
<partitioning>
  <method>none</method>
  <schema_name/>
</partitioning>

La documentation est inhabituellement insistante sur les précautions : l’option « n’est pas une solution miracle ni une option performance=fast », et un usage excessif « peut facilement dégrader les performances de vos pipelines au lieu de les améliorer ». Les critères proposés : y a-t-il réellement un problème de performance ? Quels transforms sont les plus lents — dans Hop GUI, un goulot d’étranglement reçoit une bordure en pointillés pendant l’exécution. Et surtout, ce transform est-il limité par le CPU ? Si le processeur n’est pas le facteur limitant, augmenter le nombre de copies n’apportera rien.

Les transforms cités comme potentiellement gourmands en CPU sont Calculator, Formula, Javascript, Script, Sort rows, User defined Java class et User defined java expression. Pour un transform sur base relationnelle comme Table output, le bénéfice dépend de la technologie : certaines bases encaissent plusieurs transactions simultanées, d’autres non.

3. Pourquoi les copies faussent les agrégations

Voici le piège, avec l’exemple de la documentation. Un CSV file input lit un fichier en deux copies, chacune traitant 5 000 lignes : lire en parallèle ne pose aucun problème. En aval, un comptage par État agrège les lignes, lui aussi en deux copies. Le résultat est incorrect : les lignes ont été réparties entre les deux copies de façon arbitraire, sans règle particulière, et chacune n’a vu qu’une partie des lignes de chaque État.

La règle générale : tout transform qui regroupe, déduplique, trie ou mémorise un état entre les lignes donne un résultat faux si les lignes d’un même groupe sont dispersées sur plusieurs copies. Le problème n’est pas la parallélisation, c’est le routage.

4. Le partitionnement : router les lignes par une règle

Un partition schema, selon la documentation, « définit en combien de parties le flux de lignes sera découpé » ; les noms de partitions peuvent être ce que vous voulez. C’est un objet de métadonnées du projet, avec quatre options :

Option Défaut Rôle
Partition schema name — Le nom à utiliser pour ce schéma
Dynamically create the schema definition — Bascule vers une définition dynamique
Number of partitions? 4 Nombre de partitions en mode dynamique
Partitions — Liste des identifiants de partitions, en mode statique

La mise en place se fait en deux temps : créer le schéma, puis choisir la méthode de partitionnement. La seule méthode nommée dans le manuel est Remainder of division, le reste de la division. Vous désignez un champ et Hop applique un modulo sur sa valeur : toutes les lignes portant la même valeur aboutissent sur la même copie. Si la valeur ne permet pas de modulo direct, Hop le calcule sur une somme de contrôle construite à partir de la valeur String, Date et Number.

Point important : appliquer un schéma démarre automatiquement l’ensemble correspondant de copies. Trois partitions, trois copies lancées — vous ne réglez pas Specify copies en plus. Dans le .hpl, cela donne (extrait d’un pipeline de tests d’intégration d’Apache Hop, réduit à l’essentiel) :

<transform>
  <name>partitioned</name>
  <type>WriteToLog</type>
  <distribute>Y</distribute>
  <custom_distribution/>
  <copies>1</copies>
  <partitioning>
    <method>ModPartitioner</method>
    <schema_name>static3</schema_name>
    <field_name>id</field_name>
  </partitioning>
</transform>

ModPartitioner est le nom sous lequel la méthode Remainder of division est sérialisée. <copies> reste à 1 : le nombre de copies effectives vient du schéma nommé dans <schema_name>. Le même pipeline exploite la variable interne ${Internal.Transform.Partition.ID}, qui donne, dans un Write to log, l’identifiant de la partition traitant la ligne : le moyen le plus direct de vérifier que votre routage fait ce que vous croyez.

Une réserve du manuel mérite d’être connue : rien ne garantit quelle valeur sera traitée par quelle copie. La seule garantie, et c’est la seule qui compte, est qu’une valeur donnée va systématiquement vers la même copie.

5. Les swimlanes et le coût du repartitionnement

Lorsqu’un transform doit repartitionner les lignes, il maintient des tampons internes de chaque copie source vers chaque copie cible : quatre copies en amont et quatre partitions en aval, cela fait seize tampons. D’où l’intérêt des swimlanes. Quand un transform partitionné alimente un autre transform partitionné avec le même schéma, aucun repartitionnement n’est nécessaire, aucun tampon supplémentaire n’est alloué — et vous pouvez enchaîner autant de transforms partitionnés que nécessaire.

Situation Conséquence
Transform partitionné Une copie du transform par partition du schéma
Un transform doit repartitionner Des tampons de chaque copie source vers chaque copie cible
Non partitionné → partitionné Repartitionnement, tampons supplémentaires alloués
Partitionné → partitionné, même schéma Pas de repartitionnement
Partitionné → partitionné, schéma différent Repartitionnement

6. Deux cas particuliers à ne pas rater

Sort rows. Le piège le plus coûteux, et la documentation le signale par une mise en garde spéciale. Si vous démarrez plusieurs copies d’un Sort rows, vous devez ajouter un Sorted merge derrière : chaque copie ne trie qu’un sous-ensemble du flux, et Sorted merge fusionne ces sorties déjà triées en un flux entièrement trié. Sans lui, vous obtenez des blocs triés concaténés, ce qui n’est pas un flux trié.

Table output. Ce transform sait répartir les lignes vers des tables différentes : configuré pour prendre le nom de table dans un Partitioning field, il envoie chaque ligne vers la table correspondante. Le manuel mentionne aussi les options Partition data per month et Partition data per day, et recommande de créer toutes les tables nécessaires dans un pipeline séparé, pour être certain qu’elles existent avant le chargement.

7. La méthode de travail

La documentation donne une consigne que l’expérience confirme : faites de tout petits changements et mesurez les performances avant et après chacun. Relancez le pipeline en ligne de commande entre deux essais, pour écarter les effets de l’interface graphique :

./hop-run.sh \
  --project=mon-projet \
  --environment=prod \
  --runconfig=local \
  --file=${PROJECT_HOME}/pipelines/agregation-ventes.hpl \
  --level=DETAILED

Les options courtes équivalentes sont -j, -e, -r, -f et -l. Le niveau de log accepte NOTHING, ERROR, MINIMAL, BASIC, DETAILED, DEBUG et ROWLEVEL. Sous Windows, le lanceur est hop-run.bat, options identiques. Un code de sortie 0 signifie que tout s’est bien passé, 1 signale une erreur d’exécution.

Un dernier levier, souvent plus rentable qu’une copie supplémentaire : la configuration d’exécution locale expose une option Row set size, la taille des tampons entre transforms, par défaut 10 000 lignes. Sur un pipeline aux lignes larges ou aux débits très inégaux, elle mérite d’être essayée avant de multiplier les threads.

Conclusion

Vous disposez maintenant de la distinction qui sépare un pipeline rapide d’un pipeline rapide et juste. Specify copies multiplie les threads et convient aux transforms sans mémoire, limités par le CPU. Dès qu’un transform regroupe, trie ou déduplique, il faut un schéma de partitionnement et une méthode de routage — et des transforms successifs partageant ce schéma vous offrent du parallélisme sans coût de redistribution. Reste la discipline : mesurer avant, mesurer après, accepter de revenir en arrière. Un pipeline à seize copies plus lent qu’à quatre est un cas banal, pas une anomalie.

Pour aller plus loin

Un projet Apache Hop ? Parlons-en.

Devis et programme détaillé sous 48 h ouvrées. Convention et attestation de formation fournies.

Demander un devis