Apache Camel et performance

Après avoir bien pris en main Camel, je dois dire que je suis tout à fait satisfait des possibilités offertes (voir mon précédent article). Cependant, j'ai remarqué quelques comportements peu adaptés au traitement des volumétries importantes de données.

En effet, il semblerait que certains composants et/ou outils proposés par défaut causent des pics de mémoire voir même systématiquement des crash OutOfMemory. Je vais donc essayer ici de les décrire afin de permettre aux futurs développeurs de les prendre en compte dès le départ. Je précise tout de même que j'utilise actuellement la version 2.8.3.

Marshalers et OutOfMemory

Voici un exemple de code simpliste situant le problème :

from("file://...").unmarshal().zip().to("file://...");

Ici, le but est de dezipper un fichier pour écrire son contenu vers la destination spécifiée. Utilisez un fichier de 500Mo par exemple et l'application devrait planter assez rapidement!

En effet, la méthode unmarshall().zip() va charger en mémoire (!) un DeflaterOutputStream correspondant au contenu total du fichier AVANT de l'écrire. Qui plus est, un fichier plus modeste (quelques Mo), ne causant pas de crash, restera chargé en mémoire jusqu'à l'arrivé du garbage collector.

L'intérêt de conserver en mémoire ces informations peut être compréhensible dans le cas du traitement de petits fichiers, mais dès lors que l'on pousse l'implémentation vers des volumétries importantes, il vaut mieux privilégier une autre stratégie (Camel ne prévoyant à priori pas la possibilité d'écrire le flux de données au fur et à mesure de se décompression). Un code plus robuste ressemblerait à :

from("file://...").convertBodyTo(File.class).to("bean://com.MyDezipperBean");

Qui se contente de rediriger le fichier vers un bean personnalisé qui permettra de mieux gérer les flux de données, notamment grâce au classique java.util.zip.*

Notez qu'il en va de même pour le CSV. L'appel à la méthode unmarshal().csv() produira le même comportement. Préférez donc l'utilisation de org.apache.commons.csv.CSVParser par l'intermédiaire d'un bean, beaucoup plus à-même de gérer les fichiers CSV volumineux.

Split et montées en charge

Imaginons la nécessité de découper un gros fichier CSV entrant en plusieurs plus petits (quelques soient les conditions et méthodes employées). Une implémentation évidente ressemblerait à :

from("file://...").convertBodyTo(File.class)
  .split().method("com.services.CsvService","splitDatas")
  .to("file://...");

com.services.CsvService est un bean recevant le fichier CSV initial, le parcourant pour découper le contenu selon X règles métier puis retournant une liste de contenus CSV destinés à être écrits dans des fichiers séparés.

En observant de près le comportement de l'application, on aura vite fait de se rendre compte de la rapide montée en charge due essentiellement au stockage en mémoire des informations CSV (lors de leur transfert sous forme de message du bean vers l'endpoint fichier). Une fois de plus donc, le cas du traitement de fichiers nombreux et/ou conséquents peut vite devenir problématique. Une solution de contournement peut consister à faire écrire directement au bean les fichiers (pour ne pas avoir à les re-transporter sur Camel) puis éventuellement de faire transiter simplement leur chemin si besoin.

Voici donc les problématiques que j'ai rencontrées à ce jour. Après avoir été plusieurs fois agréablement surpris par l'intelligence des composants et des diverses implémentations de EIP proposées par Camel, je dois avouer que j'ai quelque peu été dérouté par ces comportements.

Hope this helps!


Fichier(s) joint(s) :

6 commentaires: