JRuby 1.0 : experiences

J’ai développé une application Rails pour Ohm Force en interne. Il est un peu trop tôt pour parler du contenu de cette application qui aura de toute manière une portée limitée – ce sera un extranet, qui devrait être en soi une expérience intéressante.

JRuby : Késako ?

Ruby est un langage de script plutôt populaire, et largement popularisé par Ruby On Rails, le framework qui rend le développement web amical.
Il existe plusieurs interpréteurs Ruby. Le plus connu est l’implémentation de référence, celle qu’on appelle la MRI (Matz’s Ruby Interpreter ou Matz’s Reference Implementation) qui est développée principalement par Matz, l’inventeur de Ruby.

Depuis quelques temps, la machine virtuelle Java s’est enrichie d’autres langages que le Java. Le plus connu est probablement Groovy . Mais certains se sont mis en tête de développer une implémentation de Ruby sur la JVM. Ca a bien plu à Sun qui a embauché les devs principaux de JRuby.

JRuby : Pourquoi ?

  • Parce qu’on peut le faire !

  • Avec la popularité de Java en entreprise, cela permet très facilement de réutiliser des classes et des objets Java directement en Ruby.

  • Les conteneurs d’application Java ont 7 ans d’existence, sont fiables et permettent une grosse montée en charge, ce que les techniques d’hébergement actuelles Rails ne permettent pas avec autant de souplesse.

JRuby : A Ohm Force ?

C’est pas un mystère pour ceux qui lisent ce blog, mais ca fait un moment que je suis à fond dans Ruby, et j’aimerai bien que mes développements futurs soient en Ruby.
Actuellement à Ohm Force, on utilise Tomcat 5.5 pour le site web, avec un suite de Frameworks relativement standards : Struts, OJB, ACEGI, une pincée de Spring, Javamail.
Il est évident que si on doit déployer une nouvelle application, si c’est une webapp avec des servlets et des JSP, on sait faire en interne.

L’appli est maintenant déployé sur le serveur de test en attendant une validation (qui finira bien par arriver😉 )

Et c’était facile ?

Oui et non.

Oui : pour le développement, c’est plutôt simple, une fois que JRuby est installé, il suffit de taper jruby plus ruby dans la console, et ca marche.
Le démarrage est un peu plus long qu’avec la MRI, parce qu’il faut que la JVM démarre, mais une fois en route, les performances semble vraiment similaires.

Pour le déploiement par contre, j’ai eu plus de problèmes qui m’ont coûté 2 jours et quelques surprises.

La mémoire !

La JVM limite la mémoire disponible. Par défaut, le tas est à 64Mo, c’est très largement insuffisant pour l’intégration Rails sur Tomcat. Avec 256Mo, ca passe beaucoup mieux🙂
Donc JRuby coûte très cher avec Tomcat. J’avais 4 webapps qui tournaient sur 64Mo de RAM sur mon MacBook Pro, donc évidemment peu de trafic. En ajoutant mon appli sous JRuby, les besoins passent à 170Mo. Pour une utilisation mono-utilisateur …. Par contre, mon analyse me laisse à penser que plus de trafic et une application Rails beaucoup plus complexe n’augmentent pas significativement l’occupation mémoire.

Toutefois il faut être prudent dans le développement/portage d’applis sous JRuby. La MRI demande au système autant de mémoire que nécessaire, attaquant la swap si nécessaire. JRuby est complètement limité par la mémoire allouée à la JVM, et si ça dépasse, on se prend ça :

  java.lang.OutOfMemoryError : Java heap space.

Et ca fait tomber aussi les autres applis dans le même conteneur, vu qu’elles n’ont plus de mémoire non plus.

Le réglage de la JVM :
Il faut passer les paramètres suivant à la JVM pour un tas de taille fixe de 256Mo :
java -Xms256m -Xmx256m

L’intégration et Tomcat : GoldSpike

Il y a un plugin Rails qui s’appelle GoldSpike .
Ce plugin contient :

  • Des tâches Rake pour construire un WAR

  • 2 servlets qui servent les fichiers dans public/ et l’application Rails proprement dite.

L’utilisation est assez simple, mais plusieurs problèmes se sont présentés.
GoldSpike embarque la jar deJRuby, sauf qu’il utilise la version 0.9.9. J’ai du aller modifier dans le code source la version de la Jar :
Dans le fichier lib/war_config.rb à la ligne il faut mettre


addjavalibrary(maven_library('org.jruby', 'jruby-complete', '1.0'))

Ensuite pour ajouter les jar autres nécessaires à mon projet (le driver Postgres et une jar de classes internes à Ohm Force), il a fallu lire un peu de code source pour comprendre.
La recette :

  1. Copier les jar dans lib/java de votre projet Rails

  2. Créer un fichier config/war.rb avec pour chaque jar la ligne :

    
    include_library 'postgresql', :version=>'8.0', :locations => 'lib/java'
  3. rake war:standalone

L’archive est bien construite il faut l’envoyer sur le tomcat.

Finalement j’ai décompressé le war, et c’est ce que j’ai créé comme projet sur le subversion de boite. Plus simple de déployer, et ça me fait une arborescence qui marche à la fois avec Mongrel et sous Tomcat.

Une fois détectée par Tomcat, l’archive s’installe et jruby est lancé.

Mais c’est pas fini, il faut que je modifie le schéma de la base. Problème, je n’ai pas rake. Du coup je suis obligé d’installer JRuby sur le serveur avec un set de gem minimum pour que rake db:migrate fonctionne. Faudrait creuser quand même, je pense qu’on peut faire mieux🙂

L’appli démarre !

C’est pas encore fini, sur mon MacBook Pro, il faut une dizaine de secondes pour que les interpréteurs ruby soient lancés et que Rails commence à accepter les requêtes.

Comment ça marche ?
Ce paragraphe fait suite à une analyse très superficielle du code source des servlets GoldSpike .
Rails n’est pas du tout threadsafe. Vraiment pas. Donc ce que fait la servlet, c’est charger un certain nombre d’interpréteurs Ruby qui chargeront chacun une instance de l’application Rails. (D’où les problèmes de mémoire). Du coup chaque instance Rails est isolée.

Affiner la configuration de la webapp.
Dans le fichier WEB-INF/web.xml, on peut passer des paramètres à la servlet pour indiquer le nombre d’instance de Rails il démarre :


 <servlet>
  <servlet-name>rails</servlet-name>
  <servlet-class>org.jruby.webapp.RailsServlet</servlet-class>
    <init-param>
        <param-name>jruby.pool.maxActive</param-name>
        <param-value>4</param-value>
    </init-param>
        <init-param>
        <param-name>jruby.pool.maxIdle</param-name>
        <param-value>4</param-value>
    </init-param>
        <init-param>
        <param-name>jruby.pool.minIdle</param-name>
        <param-value>2</param-value>
    </init-param>
      <init-param>
        <param-name>jruby.pool.timeBetweenEvictionRunsMillis
            </param-name>
        <param-value>10000</param-value>
    </init-param>
 </servlet>
   

J’ai mis les valeurs par défaut. Le dernier paramètre correspond au temps entre deux invocations du thread destructeur d’instances.

ActiveRecord-JDBC

Pour se connecter à la base, JDBC est là ! Et la gem activerecord-jdbc permet de se connecter à une base de données directement ou via un pool de connexions managé par Tomcat.

Pour conclure

Jusque-là mes expériences ont été globalement positives. J’ai eu des petits soucis, mais comme à chaque fois lorsqu’on teste une nouvelle techno. J’attends évidemment les versions à venir de JRuby qui vont maintenant se focaliser sur la performance – JRuby est un interpréteur valide de Ruby 1.8.5.
Pour le passage en production, je m’inquièterai juste de la consommation mémoire.

Pas de fuites toutefois. La JVM reste stable autour de 170Mo depuis 4 jours et 17h.

[MAJ : Je ne poste plus trop sur le sujet – ça marche bien et pas de problème.]

%d bloggers like this: