Archives pour la catégorie Zooms Techniques

Chronique d’optimisation : Réduction de 2/3 du temps de déploiement sur JBoss AS 6

En ajoutant un nouveau composant à une application J2EE, j’ai vu le temps de déploiement se multiplier par plus que 3. Bien que cela n’aie pas beaucoups d’importance sur un environnement de production, cela est critique dans un environnement de développement :

  1. Perte de temps : si on déploie 5 fois par heure et avec 2 minutes par déploiement, c’est plus d’une heure de travail perdue par jour.
  2. Perte de perfomance : La Rupture du Flux mental due au déploiement lent réduit d’autant l’efficacité du développeur.
  3. Perte de qualité : La tendance qu’on aura alors à déployer moins va faire qu’on réalisera moins de test, qu’on auras moins tendance à améliorer ou optimiser le code …

Mais que se passe-t-il donc ? Le threaddump nous dira

En regardant les logs, je voyais bien qu’au déploiement l’ancienne version d’application était « dé-déployée » en quelques secondes puis le JBoss marquait une pause de plusieurs secondes avant de commencer le déploiement de la nouvelle version, sans aucune ligne de log. Que faisait-il donc pendant cette période ?

Pour diagnostiquer cela, la JVM nous permet de faire des ThreadDump, il s’agit d’une photo instantanée de ce qu’est en train de faire la VM.

Il existe pour cela plusieurs outils, j’ai utilisé VisualVM mais il existe d’autres moyens (kill -3 <PID> sous unix, jstack, Outil IBM …)

Thread Dump dans VisualVM

Dans les différents ThreadDump réalisés pendant cette pause de JBoss, j’ai remarqué qu’il y avait un point commun : l’AnnotationScanningPlugin. En effet, depuis J2EE 5 la configuration en XML a été allégée voir rendue optionnelle en faveur d’annotation dans le code. Plutôt propre et pratique comme approche. Seulement voila : cela oblige le serveur d’application à parcourir l’ensemble des classes pour rechercher d’éventuelles annotations.

Optimiser le scan d’annotation sur JBoss

Je vous passe le gros défaut de JBoss : la documentation, toujours difficile de trouver de la documentation de référence à jour. Après quelques recherches, j’ai en effet trouvé que depuis le AS5, un fichier jboss-scanning.xml permet d’optimiser cette étape voir Wiki JBoss ici. Dans mon cas c’était un war, j’ai donc crée jboss-scanning.xml dans le WEB-INF.

Avec le contenu suivant par exemple, JBoss limite le scan au package com.acme.foo mais sans parcourir les classes de com.acme.foo.bar:

<scanning xmlns="urn:jboss:scanning:1.0">
  <path name="WEB-INF/classes">
    <include name="com.acme.foo"/>
    <exclude name="com.acme.foo.bar"/>
  </path>
</scanning>

Et pour empêcher totalement le scan on peut utiliser :

<scanning xmlns="urn:jboss:scanning:1.0">
  <path name="WEB-INF" excluded="true"/>
</scanning>

Pour comprendre le format de ce fichier, la seule documentation que j’ai trouvée est le code source annoté en JAX-B

Résultat : 2/3 de gain de temps au déploiement

Le temps de déploiement a été réduit de 2/3 et un gain en ergonomie et en temps considérable pour l’équipe de développement.

APIs REST ouvertes : Considérations de sécurité et implémentation

Le site Programmable Web recense plus de 10000 APIs qui se trouvent au centre du web moderne. Ces APIs permettent d’intégrer différents services allant des réseaux sociaux à la cartographie en passant par des flux d’information, de l’analyse de données, de flux le multimédia …

Fort heureusement, le simple respect des bonnes pratiques de l’architecture du Web permettent d’en lever certains. Mais créer des API ouvertes (utilisables depuis n’import quel site web) comporte tout de même certains risques de sécurité qu’il faut prendre en compte.

Considérations de sécurité

L’attaque la plus typique que peuvent subir les API REST ouvertes et applé le « Cross Site Request Forgery ». Il s’agit du fait qu’un site utilise les données d’authentification stockées dans le navigateur pour appeler des APIs sécurisées à l’insu de l’utilisateur lui même. Voir exemple sur la page wikipedia dédie au Cross Site Request Forgery

De fait, les navigateurs web limitent fortement les possibilités qu’a une page HTML a faire appel à une URL ou une API qui n’est pas sur le même serveur source que la page HTML elle même. De fait, par défaut, si je suis sur la page www.toto.com/unepage.html, je ne pourrait lire www.mesinfosprivees.com/mes_secrets/list.html ni executer www.mesinfosprivees.com/mes_secrets/api/publier. Ce qui est une bonne chose.

CORS un vrai enabler pour les API ouvertes

L’approche se sécurité citée plus haut permet de protéger les informations et services privés de la CSRF, cependant, cela limite la possibilité de mettre en place des services ouverts qui peuvent être appelés par des domaines tiers. Ainsi, je ne pourrais pas dans www.toto.com/unepage.html afficher www.mesinfospubliques.com/mesannonces.html or c’est exactement ce que je souhaite pour les API ouverte.

Le CORS (Cross Origin Resource Sharing) est une norme, implémentée par la quasi totalité des navigateurs modernes et qui permet aux site qui héberge l’API d’autoriser l’accès aux APIs depuis d’autres domaines tout en gardant un certain niveau de contrôle.

Avant d’activer CORS : regarder la sécurité

Mettre en oeuvre CORS coté serveur est en soit relativement simple, le plus complexe est de s’assurer de la maîtrise de la sécurité contre le CSRF. Pour cela :

  1. Ignorer coté serveur d’API toute authentification implicite basée sur les Cookies.
  2. De forcer chaque appel d’API à comporter les informations d’authentification s’il n’est pas public.

Pour ce second point, le pattern le plus pratique est le Synchronizer Token Pattern, il s’agit de :

  1. Générer à l’issue de l’authentification une chaine aléatoire dite (token)
  2. Transmettre à chaque appel d’API se token. (Dans un header http, dans l’URL, dans le corps)

Par contre il est extrêmement important de protéger ce token correctement :

  1. Il ne faut pas qu’il apparaisse dans le DOM (c’est à dire dans le HTML de la page statique ou dynamique)
  2. Qu’il ne soit pas accessible depuis une variable globale.

L’idéal est de le stocker dans un module javascript.

Quelques liens utiles

PayPal abandonne Java pour JavaScript coté serveur

Node.js a permis un gain important en performance et en temps à l’entreprise, un retex intéressant même si le javascript pour des plates formes d’entreprise ne doit pas être mis entre toutes les mains : la souplesse du langage exige des developpeurs expérimentés pour le manier correctement.

http://www.developpez.com/actu/65013/PayPal-abandonne-Java-pour-JavaScript-Node-js-a-permis-un-gain-important-en-performance-et-en-temps-a-l-entreprise/

Factorisation d’écriture dans les arrays pour optimiser la taille d’un Cardlet JavaCard

Il m’a été donné l’occasion de travailler sur une optimisation d’un Cardlet Sim Toolkit pour un opérateur télécom. Le problème principale était la taille de mémoire nécessaire pour installer l’Applet sur la carte. Il était donc question d’optimiser la taille du code.

Cette applet offrait un certain nombre de services par SMS, au bout du menu, de façon très classique, un SMS était construit et envoyé à un numéro précis pour fournir le service. Ci dessous un exemple du code utilisé pour constuire le SMS:

int index=0;
buffer[index++]=(byte)'A';
buffer[index++]=(byte)'B';
buffer[index++]=(byte)'C';
...

J’appellerai cela une écriture inline dans le buffer
Il se trouve que un des axes majeurs d’optimisation de cette applet était de transformer ce genre de pattern en :

index = addBytes(buffer,index,'A','B','C');
index = addBytes(buffer,index,...);
...

J’ai en effet créé une fonction statique addBytes surchargée en version à 3, 4 et 5 bytes que j’appelle en lieu et place de la définition manuelle des bytes.
Elle ajoute les parametres au buffer et retourne la nouvelle position d’index. J’appelerai ça une écriture factorisée dans le buffer.

Pourquoi est-ce une optimisation de la taille du code ?

Cela tien à la structure du code compilé java du .class (dit bytecode) que la JVM exécute. Comme la bonne vieille HP 48 GX le bytecode utilise une pile pour décrire à la JVM les paramètres des instructions à exécuter. Ainsi pour faire une addition de 1 + 1 le byte code sera :

  1. Pousser 1 dans la pile.
  2. Pousser 2 dans la pile.
  3. Additionner les deux au sommet de la pile.

Soit en byte code (en suppostant travailler avec des int) :

  1. iconst_1 (push constant value 1 into the stack)
  2. iconst_2 (push constant value 2 into the stack)
  3. iadd (add the two last values and store result into the stack)

La version avec les insersion dans l’array factorisés dans une methode statique est beaucoups moins gourmande en byte code.

Comparaison des deux approches : inline vs factorisée

Chaque ligne du type :
buffer[index++]=’A’
Reviens à faire les opérations suviante:

  1. Charge la référence de buffer dans la pile : Instruction aload
  2. Charge la valeur de index dans la pile : Instruction iload
  3. Charge la constant ‘A’ dans la pile : Instruction iconst
  4. Stock dans le buffer : Instruction bastore
  5. Incremente index : Instruction iinc

Faire cela 3 fois créer donc environ 15 insructions de bytecode.

Comparons cela avec la version factorisée :

index = addBytes(buffer,index,'A','B','C') + index;

Les instructions sont les suviantes :

  1. Charge la référence de buffer dans la pile : Instruction aload
  2. Charge la valeur de index dans la pile : Instruction iload
  3. Charge la constant ‘A’ dans la pile : Instruction iconst
  4. Charge la constant ‘B’ dans la pile : Instruction iconst
  5. Charge la constant ‘C’ dans la pile : Instruction iconst
  6. Appeler addBytes : Instruction invokestatic
  7. Ecriture de la valeur de retour dans index : Instruction istore

En bout de compte la version factorisée consomme 7 instructions de bytecode.

Comparaison des deux approches :

L’approche inline consomme 5 instructions par byte écris dans l’array (soit 15 instructions pour 3 bytes). L’approche factorisée en écrivant 3 bytes d’un coups consomme 7 instructions pour les trois bytes, donc en fonction du nombre d’écritures faites dans le code le gain est de l’ordre de 50% :

Nombre de bytes écris Nombre d’instructions inline Nombre d’instructions fatorisé Gain
3 15 7 53%
6 30 14 53%
60 300 140 53%

Avec une versions d’addBytes qui écris 5 bytes en un seul appel le gain devient de l’ordre de 60%.

Conclusion

Ainsi une connaissance plus fine du fonctionnement du bytecode permet d’optimiser la taille d’un code JavaCard, ceci dit, comme toujours lors de l’optimisation cela reste un tradeoff entre la performance et la taille : la version factorisée nécessite un invokestatic qui est plus gourmand en performance que une simple instruction astore.

Quelques références utiles :

  1. Specifications de la JVM
  2. Liste des instructions du bytecode java
  3. Intro au bytecode sur wikipediat