Play framework, binding et performance

Après avoir bataillé un bon moment avec les performances sur la page d’importation de Nemrod je me suis dit que ce serait intéressant de partager ce que j’ai appris sur le binding de Play.
Bref, je vous propose ce petit sujet autour du binding dans Play framework et une optimisation qui m’a sauvé la vie.

Resituons le contexte tout d’abord. Si vous utilisez Play framework, vous avez peut être déjà utilisé des bindings avec des collections d’éléments dans un de vos contrôleur.

Ce type de binding est excessivement utile dans la majorité des cas. Prenons par exemple le bout de template suivant :

    #{list items:results, as:'result' }
       
       
       
       
    #{/list}

Ceci va vous créer dans votre page html une suite d’élément html comme suit :

          
        
        
        
               …..........

Pour traiter ensuite ce résultat dans votre contrôleur, il ne vous reste plus qu’à déclarer :

public static void process(Listresults)

ImportBean étant un simple objet ayant 4 propriété : filename, module, modulepath et localeId

Autrement dit, Play framework a fait le binding pour vous entre les données de votre formulaire et une collection d’objets.

C’est vrai, c’est super puissant et c’est la méthode conseillée dans la majorité des cas.

De mon côté j’ai rencontré un cas où la taille de ma collection était de plus de 400 éléments et cela s’est avéré catastrophique.

Prenons la méthode d’un contrôleur comme suit :

    public static void process(Listresults)     {
     
          for (ImportBean importBean : results)
          {
              await(doSomething());
          }

           render();

    }

Le temps nécessaire pour effectuer le traitement durait plus de 2h !
J’ai évidemment soupçonné le temps de traitement dans doSomething mais après avoir mis en place quelques éléments pour mesurer les temps d’exécution, je n’ai rien trouvé de concluant dans la méthode doSomething.
Par contre, j’ai remarqué qu’à chaque tour de boucle la récupération d’un élément de results prenait un temps fou. L’entrée même dans la méthode process dépassait les 10 secondes.

J’ai donc soupçonné le binding effectué par Play. Même si j’avoue ne pas avoir compris pourquoi je perdais du temps à chaque itération (du lazy loading pour le binding peut être ?)
Pour valider cette théorie, j’ai modifié le code de la façon suivante :

J’ai remplacé

    #{list items:results, as:'result' }
       
       
       
       
    #{/list}

par


resultJson étant une chaîne représentant ma collection au format json.

Côté contrôleur, je récupère donc une chaîne de caractère et non plus une collection. Cette chaine est désérialisé puis utilisé dans le même traitement que précédemment.

public static void process(String resultJson)
{
       GsonBuilder builder = new GsonBuilder();
       Type collectionType = new TypeToken<List>(){}.getType();
       Listresults = builder.create().fromJson(resultJson, collectionType);

       for (ImportBean importBean : results)
       {
           await(doSomething());
       }

       render();
}

Résultat des courses, le traitement qui s’effectuait auparavant en environ 2h s’effectue désormais en moins de deux minutes !

Bref, heureusement que j’ai posé les sondes aux bons endroits avant d’entamer des optimisations dans doSomething, ce qui ne m’aurait rien apporté au final.

Voila, en espérant que ça vous serve peut être.

8 réflexions sur “Play framework, binding et performance

  1. Pourquoi avoir fait une boucle sur await(doSomething()); et ne pas avoir mis le await sur une methode executant la boucle en elle même?

    Je ne connais pas forcement bien cette partie de Play, mais le await a un coup dans la mesure où Play doit faire en sorte de reprendre le process la ou il avait été stoppé et donc le contexte associé.

  2. Pourquoi avoir fait une boucle sur await(doSomething()); et ne pas avoir mis le await sur une methode executant la boucle en elle même?

    Je ne connais pas forcement bien cette partie de Play, mais le await a un coup dans la mesure où Play doit faire en sorte de reprendre le process la ou il avait été stoppé et donc le contexte associé.

  3. Eh bien a l’origine l’opération était assez longue (et elle continue de l’être, 2 minutes ca reste long pour une appli web) et je ne souhaitais pas bloquer les autres utilisateurs en monopolisant certaines tables trop longtemps.
    Du coup l’objectif d’utiliser await c’était de découper en plusieurs transactions plus petites. Chaque transaction étant en charge du traitement d’un fichier ca gardait son sens.
    La remarque est très bonne cependant, maintenant que j’ai réduit le temps d’exécution je pourrais réessayer en une seule transaction et voir ce que j’y gagne.

  4. Eh bien a l’origine l’opération était assez longue (et elle continue de l’être, 2 minutes ca reste long pour une appli web) et je ne souhaitais pas bloquer les autres utilisateurs en monopolisant certaines tables trop longtemps.
    Du coup l’objectif d’utiliser await c’était de découper en plusieurs transactions plus petites. Chaque transaction étant en charge du traitement d’un fichier ca gardait son sens.
    La remarque est très bonne cependant, maintenant que j’ai réduit le temps d’exécution je pourrais réessayer en une seule transaction et voir ce que j’y gagne.

  5. Qu’est ce qui change entre :

    for (ImportBean importBean : results)
    {
    await(doSomething());
    }

    et

    await(doSomethings());

    où doSomethings fait :
    for (ImportBean importBean : results)
    {
    doSomething();
    }

    C’est une vrai question d’un neophyte play 🙂

  6. await prend en paramètre une Promise (une extension de Future) et chaque Promise est lancé dans son propre contexte JPA. Autrement dit, chaque fois que j’appelle doSomething dans le premier cas la méthode s’execute dans une transaction.
    Dans le second cas, j’ai une transaction globale pour toute les opérations. Ce qui permet par contre de garantir que soit tout est fait, soit tout est rollbacké contrairement au premier cas.

    Dans les deux cas par contre, ca utilise le concept de requête suspendue dans Play Framework.

  7. Qu’est ce qui change entre :

    for (ImportBean importBean : results)
    {
    await(doSomething());
    }

    et

    await(doSomethings());

    où doSomethings fait :
    for (ImportBean importBean : results)
    {
    doSomething();
    }

    C’est une vrai question d’un neophyte play 🙂

  8. await prend en paramètre une Promise (une extension de Future) et chaque Promise est lancé dans son propre contexte JPA. Autrement dit, chaque fois que j’appelle doSomething dans le premier cas la méthode s’execute dans une transaction.
    Dans le second cas, j’ai une transaction globale pour toute les opérations. Ce qui permet par contre de garantir que soit tout est fait, soit tout est rollbacké contrairement au premier cas.

    Dans les deux cas par contre, ca utilise le concept de requête suspendue dans Play Framework.

Laisser un commentaire