Dans le cadre d'un projet que j'ai démarré, et dont je parlerais peut-être plus tard, je dois effectuer de l'ETL : Extract / Transform / Load, afin de transformer une shitload de données XML en un montant bien plus raisonnable de données CSV.

Le fichier XML faisant 13Go, j'ai exclu d'emblée DOM pour partir sur un parseur Sax, beaucoup plus efficace sur les gros sets de données de ce genre (pour information, Sax consiste à parser le flux XML tel qu'il arrive, tandis que DOM reconstruit l'arbre en mémoire).

Je suis parti sur du Node, d'abord parce que j'aime bien le Javascript, ensuite parce que j'ai l'habitude de bosser avec, et enfin parce qu'un langage de script me permettait d'abstraire beaucoup de problématiques.

Finalement, après avoir réfléchi un peu comment lire correctement du Sax avec une machine a état et tout le bordel, j'ai désespéré, et me suis tourné vers une solution dont on m'avait précédemment parlé : Kettle. Je n'aborderais pas le sujet en détail, mais considérez qu'il s'agit d'un software spécialement conçu pour effectuer des ETL. Malheureusement, il a les deux défauts d'être à la fois GUI et Java-based, donc je l'ai finalement laissé tomber pour retourner sur mon Node.

Plutôt que de partir sur une machine a état comme je le pensais originellement, j'ai réfléchi un peu à la raison pour laquelle j'utilisais Sax : j'avais beaucoup de données. Beaucoup de lignes dans ma table. Et là, ça a été l'évidence : mon arbre XML n'était pas gros en largeur, uniquement en hauteur.

Et je suis donc parti sur l'idée d'utiliser Sax pour construire un arbre XML en mémoire (en utilisant de simples objets Javascript très basiques) puis, une fois qu'un certain 'path' était construit, le transformer en sous-document XML DOM, et utiliser une poignées d'expressions XPath dessus afin de récupérer les informations qui m'intéressaient.

Cette approche a donc été implémentée (en utilisant les deux plugins node sax et jsdom), mais j'ai été confronté à un problème que je n'avais pas anticipé à ce point : la vitesse d'exécution était atroce.

Pour donner un ordre d'idée, j'ai laissé tourner le script de conversion depuis hier soir 19h. Cela fait maintenant 24h, nous en sommes à la 727500eme ligne. Le document en possède plus detrois millions, et j'ai un autre document beaucoup, beaucoup plus gros à traiter derrière.

Autant dire que si je voulais avoir fini avant la fin du monde mois, il allait falloir trouver mieux. J'ai donc essayé quelques optimisations, telles que supprimer la récursion de la fonction vérifiant si l'on avait rencontré un noeud XML 'intéressant', réutiliser le même objet XML à chaque noeud, etc, mais rien n'y faisait, le script restait toujours désespérement lent.

Et puis vint l'idée que j'aurais dû avoir avant : commenter les expressions XPath. Et là, le changement fût vraiment significatif, passant de 5s pour 1000 lignes à moins d'une seconde.

Bien évidemment, j'avais vraiment besoin de ces expressions, pour éviter de devoir écrire beaucoup de code et risquer de tout voir s'effondrer à une possible mise à jour du format d'entrée (toujours être générique ;). Cependant, je n'avais pas vraiment besoin du moteur XPath en lui-même ... il était conçu pour supporter énormément de features, et je n'en utilisait presque aucune. Bref, long story short (ce post commence à s'éterniser, pardon :), j'ai supprimé la conversion Object -> DOM, et ait rajouté une fonction au prototype de mes objets permettant de trouver un sous-noeud. Une autre fonction pour trouver leur contenu textuel, et j'étais paré. Le résultat final est beaucoup plus acceptable : 21 minutes pour traiter les trois millions de lignes ! J'aime node.

En ce qui concerne le titre de ce post, ne cherchez pas, c'est simplement un jeu de mot avec le marteau de Thor. Oui. Pardon.

[note] Pour référence, le fichier de 32 millions de lignes s'est processé en 308 minutes. Je ne sais pas si c'est très respectable, mais étant donné qu'il tourne dans une VM, et que je peux tout de même lancer plusieurs dumps par jour si nécessaire, je suis plutôt satisfait.