Wednesday, November 2, 2011

Optimiser les accès en lecture sur HBase

Increase HBase read performance checklist

Accès en parallèle, client "multihtread"
Dans HBase les données sont découpées en Régions et ces Régions peuvent être servies par des Region Servers différents. La charge de lecture doit donc être dans la mesure du possible distribuée sur l'ensemble des serveurs. 

Privilégier les lectures en mode "batch"
L'API HBase permet de lotir des appels à la base dans des "Batch Operations". L’intérêt est d'éviter de trop nombreux aller/retour sur le réseau.
Ainsi les opérations PUT, GET, DELETE peuvent être regroupées dans un seul appel à la base de donnée.


HTable hTable = new HTable("cachetestdb");
List listOfget = new ArrayList();
for (int i = 0; i < 10; i++) {
String keyAsString = "17#" + i;
Get get = new Get(Bytes.toBytes(keyAsString));
listOfget.add(get);
}

Object[] batch = hTable.batch(listOfget);

for (Object object : batch) {
Result result = (Result) object; 
/* TODO : process data */
}



Le cache joue t-il correctement son rôle ?
Il est important de vérifier si le cache joue efficacement son rôle. HBase dispose en effet d'un cache de type LRU pour accélérer l'accès aux données récemment accédées.
Si ce n'est pas le cas car les accès se font sur des données non contiguës alors il peut être envisageable de diminuer la taille des blocs de données qui par défaut est de 64Ko Attention à ne pas confondre les blocs de données gérés par HBase avec les blocs de données du système de fichiers HDFS qui par défaut sont de 64Mo.
Au contraire si le cache est souvent atteint (lecture de plages de données contiguës) on peut augmenter la taille de ses blocs (e.g. 640Ko)




Paramètre IN_MEMORY sur les Column Family
Lors de la déclaration d'une Column Family il est possible de positionner le paramètre IN_MEMORY à true (par défaut il est à false). Dans ce cas HBase va faire son maximum pour garder les données en mémoire en les rendant plus prioritaire lors du nettoyage du cache.
Par défaut seules les 2 tables "systèmes" -ROOT- et .META. possèdent cette propriété afin de garantir un accès rapide aux données qu'elles contiennent. L'activation de cette propriété place donc vos données dans le cache avec le même niveau de priorité.

Utilisation des "Bloom filters"
Les Bloom filters permettent d'accélérer la recherche d'une ligne en excluant certains fichiers de données HFile de la recherche.
L'activation des Bloom filters peut se faire sur des données existantes, ils seront simplement générés et activés lors de la création de nouveau fichiers de données HFile lors de flush ou de (major/minor) compact.
De plus depuis l'arrivée du format HFile V2 avec la version 0.92 de HBase (en release candidate  à l'heure où j'écris ces lignes) la pression sur la mémoire engendrée par leur activation s'est considérablement amoindrie. A l'ouverture d'un fichier HFile V1 il fallait charger l'intégralité des données des bloom filters en mémoire : cela ralentissait l'ouverture du fichier et donc la disponibilité des données de la Region. Ce n'est plus le cas avec le nouveau format.
Depuis la version 0.92 HBase lit les HFile V1 et les convertis en V2 lors des opérations de flush ou de (major/minor) compact

Compression des données
HBase permet de compresser les données, le gain en terme de rapidité de lecture des données est en règle générale supérieur à l'augmentation de la consommation CPU induite par les algorithmes de compression. L'intégration de l'algorithme Google Snappy dans la version 0.92 vient compléter la liste des algorithmes déjà intégrés dans les versions antérieures : Gzip et LZO (attention cependant aux dépendances sur les bibliothèques natives pour LZO et Snappy)
Si il est en règle générale toujours une bonne chose d'activer la compression des données il faudra noter que cela n'apportera rien sur des données ayant déjà une trop grande entropie (archives .zip, images jpeg ...)
La compression des données se définie au niveau des Column Family.
Quel algorithme choisir ?
  • GZip : très bon taux de compression mais aussi très gourmand en CPU, ce qui diminue les débit de lectures et d'écritures
  • LZO : bon compromis entre taux de compression et débit
  • Snappy : taux de compression en retrait par rapport aux autres algorithme mais le débit qu'il assure est excellent
N.B : il s'agit de comportements généralement observés, si vous parcourez la mailing list vous trouverez des avis divergents concernant l'efficacité des algorithmes de compression



Tuesday, September 13, 2011

HBase + Subversion + Eclipse + Windows

HBase + Subversion + Eclipse + Windows
(it should be easy to adapt for Linux)

Update : please note that since HBase-4336 / HBase 0.96 the source tree is split in more than one Maven module this post is no more relevant, i have created a new post on this subject : http://michaelmorello.blogspot.fr/2012/06/hbase-096-eclipse-maven.html

This is a simple setup in order to play with the source code of HBase under Microsoft Windows.
Since HBase use some Unix specific commands like chmod the only requirements here are Cygwin and a working Maven 3 environment. (It is obvious that you need Java and Eclipse, but you DON'T need anything else like the Eclipse Maven plugin or any SSH configuration)

1. Checkout the source code

The first step is to check out the source code from the Subversion repository. I did it under my cygwin home repository. In this example i want to play with the 0.90 branch :

svn co http://svn.apache.org/repos/asf/hbase/branches/0.90/ hbase-0.90


2. Generate the Eclipse configuration files
Go into the hbase-0.90 directory and run the following maven command :

mvn eclipse:eclipse

Maven will download half of the internet the first time and at the end you sould have .classpath and .project files at the root of the project directory.

3. Prepare your Eclipse workspace

Start Eclipse and select a new workspace :

Declare the M2_REPO variable :
Menu -> Window -> Preferences
Java -> Build Path -> Classpath Variable

4. Import the source code into your Eclipse workspace
Menu -> File -> Import ...
General -> Existing Projects into Workspace
In "Select root directory" select the directory where you have checked-out and executed the "mvn eclipse:eclipse" command

Now the project HBase should appear in your workspace :


5. Create a simple hbase-site.xml configuration file

In the "conf" directory edit the hbase-site.xml file (it should just contains the root xml element "configuration" for the moment) and add the following lines :

  <property>
    <name>hbase.defaults.for.version</name>
    <value>0.90.5-SNAPSHOT</value>
  </property>


  <property>
    <name>hbase.rootdir</name>
    <value>D:/cygwin/home/MORELLO/HBASE/WORK</value>
  </property>

The value of the hbase.rootdir should be an empty writable directory. It is used by HBase in replacement of the HDFS file system in order to store stuff like filestores, wal files, etc....

6. Prepare for ignition

Open the "Run Configurations ..." window from the menu Run -> Run configuration ...
     
        6.1 Main tab

  1. At the top replace "New_configuration" with "HBase (start)"
  2. Select the hbase project and in the Main class field enter : org.apache.hadoop.hbase.master.HMaster 

        6.2 Arguments tab

Add "start" as program argument

        6.3 Classpath tab

Add the "conf" directory in the bootstrap entries

The conf directory must appear in the bootstraps entries
        6.3 Environment tab

      As i said earlier HBase will use some Unix commands, so you must add the /bin directory of your Cygwin directory in your PATH environment variable :

7. Ignition !
Press the Run button, and if everything is ok you should see something like that :

You can also check the web ui with your favorite browser : http://localhost:60010/master.jsp


8. Play with the shell
In order to play with the shell create a new "Run configuration"
Menu -> Run -> Run Configurations

Create a new Run Configuration called HBase (Shell) :

Add org.jruby.Main as the main class

In the arguments tab add the absolute path to the hbase-0.90/bin/hirb.rb file
As in the 6.3 part add the conf directory to the bootstarp entries of the classpath

Press the Run button, and if everything is ok you should see something like that in the console :


In green are some commands that you can type in order to check that everything is ok.

9. Stop HBase
It is not recommended to press the red square in Eclipse and kill abruptly HBase. You can duplicate the "HBase (Start)" Run configuration into a "HBase (Stop)" one :

Just replace "start" by "stop"

Now if you select the "HBase (STOP)" run configuration and press the Run button HBase should be  gently stopped.

Small conclusion
I hope that this post will be useful to anybody that want to discover and play with HBase. It should be very easy to adapt for a Unix/Linux environment since you don't have to care about all the cygwin stuff.
Enjoy !

Saturday, August 27, 2011

HBase, versions de données: principes et usage chez Facebook

Lorsque l'on insère une donnée dans HBase, la précédente version, si elle existe, n'est pas écrasée. HBase comme son mentor BigTable peut gérer plusieurs versions du contenu d'une même cellule.
Pour ceux qui se demandent comment exploiter au mieux cette capacité de HBase voici un conseil de Bill Graham sur la mailing list HBase :

"[...] Once someone realizes that you can store multiple values for the same cell, each with a timestamp there can be a natural tendency to think "hey, I can store a one-to-many using multiple version of a cell". That's not the intent of versioned cell values.


Versioned cell values can be thought of as a way to keep a history of change for a single entity that at any given time only has one value. Like keeping track of a state change over time. For a one-to-many relationship (i.e., a user with many events), favor either multiple rows or multiple columns instead."

Pour résumer les versions multiples d'une même cellule ne sont pas faites pour faire du mapping de données de type one-to-many. Dans ce dernier cas il ne faudra pas hésiter à créer plusieurs lignes ou ajouter des colonnes car contrairement à une base de donnée classique ajouter une colonne à une table HBase ne coûte rien (il ne faut donc pas s'en priver).
Il ne faut pas oublier qu'à l'origine cette fonctionnalité de "version de cellule" a été introduite dans la base de donnée BigTable de Google pour conserver un historique des évolutions des sites qui sont indexés par Google.

Il existe pourtant des cas où cela peut être nécessaire et astucieux : le meilleur exemple est sans doute Facebook et son nouveau système de messagerie (qui repose sur HBase) dans lequel on se sert de l'identifiant du message comme numéro de version et signifier la présence d'un mot dans le contenu du message  :

Modèle de donnée du nouveau système de messagerie facebook
d'après http://www.slideshare.net/brizzzdotcom/facebook-messages-hbase
Dans l'exemple ci dessus les messages msgid1 et msgid3 de l'utilisateur userid contiennent les mots mot1 et mot2. La valeur de la cellule, la donnée, n'est que la position ('offset') du mot dans le message. Il est alors facile pour Facebook de trouver les messages d'un utilisateur qui contiennent un mot voire même une succession précise de mots en exploitant les offsets.

D'un point de vu technique le principal reproche que l'on pourrait faire à ce type d'usage est que toutes les données d'un même utilisateur vont être stockées dans le même FileStore. Comme toutes ces données ne seront liées qu'à une seule clé il ne sera pas possible de les éclater par la suite sur plusieurs nœux. Les ingénieurs de Facebook ont du calculer que si ils s'en tenaient à stocker un 'offset', c'est à dire l'endroit dans le message où le mot apparaît alors cela devait être acceptable.

Dans HBase il n'est pas conseillé de garder plus d'une centaine de versions, il existe quelques subtilités à connaitre lorsque l'on joue avec les versions, la page http://hbase.apache.org/book.html#versions  est un bon début pour approfondir le sujet en s'aidant du glossaire si nécessaire.

Thursday, August 18, 2011

Mockito ou comment simuler des objets dans les tests unitaires


Enfin un framework qui me réconcilie avec les tests unitaires, non pas que j'étais très fâché, les tests unitaires sont nécessaires mais je ne prenais pas un grand plaisir à les faire.
Je travaille en ce moment sur un projet de client/serveur basé sur les excellents framework Netty et Protobuf et écrire des test unitaires sur ce type de projet très technique peut se révéler être assez difficile.
Pourtant depuis peu je dois confesser que je prend un certain plaisir à en écrire, ceci depuis que j'ai découvert Mockito.

Mockito ça sert à quoi ?
L'un des problèmes avec les tests unitaires ce sont les objets qui sont mis en jeu. Certains objets sont en effet très difficile à instancier, il peut être même impossible de le faire si on est en phase de spécification et que seule l'interface a été écrite.
Il est encore plus difficile de contrôler leur comportement pour recréer des contextes particuliers d’exécution comme recréer un contexte d'erreur.
C'est là qu'intervient Mockito : Mockito permet de créer des classes "fantoches", des "marionnettes" à partir de n'importe quelle classe ou même à partir d'une simple interface. En anglais on appelle ça des "mock objects".
Cette classe vous allez pouvoir contrôler finement son comportement, comme les valeurs de retour de ses méthodes : c'est qu'on appelle le "stubbing".

Exemples

Je vais reprendre les exemples du site, ceux basés sur l'interface List, mais avec mes propres commentaires :

1. Création de "mock objects"

import static org.mockito.Mockito.*;
List mockedList = mock(List.class);

Dans cette exemple nous venons de créer un objet à partir de l'interface List. Cet objet implémente l'interface par contre son comportement est pour le moment assez limité, un appel à mockedList.get(0) retournera null.
Pour controler son comportement nous allons faire du "stubbing"

2. Le "stubbing"


when(mockedList.get(0)).thenReturn("toto");

Maintenant un appel à mockedList.get(0)retournera la chaine "toto".
Si l'on souhaite répondre "toto" pour n'importe qu'elle indice de la liste :

when(mockedList.get(anyInt())).thenReturn("toto");

Maintenant un appel à mockedList.get(indice) avec n'importe quel entier en indice retournera la chaine "toto".

3. Vérifier les appels
Il peut être intéressant de contrôler qu'une méthode a bien été appelée et avec les bon arguments en paramètre.

System.out.println(mockedList.get(888));
// Doit afficher "toto" sur la sortie standard
// Si l'on souhaite vérifier qu'il y a bien eu un appel à la méthode get avec comme paramètre 888
verify(mockedList).get(888);

Voilà pour la petite intro, les autres fonctionnalités sont décrites ici. Au passage Mockito est un bon tremplein vers le Behaviour Driven Developement alors pourquoi se priver ?

Thursday, August 4, 2011

Glossaire à l'usage de ceux qui découvrent HBase

Avant-propos : à l'origine ce glossaire était destiné à consigner pour mon propre usage les définitions et les fonctions des différents éléments qui constituent la base de donnée NoSQL HBase. Il peut s'agir aussi bien de simples clarifications sur la sémantique comme de précisions beaucoup plus techniques sur le rôle joué par certains éléments. Il n'est pas exhaustif et est amené à être mis à jour au fil des évolutions de HBase.

BloomFilter
Filtre permettant d'exclure d'une recherche les FileStore d'une Region. Utilisé par exemple lorsque que l'on cherche la valeur associée à une clé, et que les données de cette clé sont potentiellement éclatées sur le MemStore et/ou plusieurs FileStore. Un BloomFilter permet alors de ne pas avoir à parcourir tous les FileStore présents dans la Region.

Column
Dans HBase la notion de colonne n'a pas réellement de sens.  Le modèle de gestion des données de HBase repose sur une notion de couple {clé, valeur}. Dans HBase le nom de la colonne peut être vue comme la clé (en partie, voir la définition d'une Table). Il n'y a en interne aucune notion de colonne telle que l'on peut l'entendre dans une base de donnée relationnelle. Le terme a surement été conservé pour donner un (mauvais) point de comparaison.

Colum Family
Groupe de colonnes. Physiquement si des colonnes appartiennent à une même famille alors elles seront stockées dans une même Region. A noter : HBase gère pour le moment assez mal un trop grand nombre de famille de colonnes. Par contre une famille de colonne peut gérer un très grand nombre de colonne.

Compaction (Minor)
Concaténation d'une partie des FileStores. HBase utilise un algorithme qui va privilégier les fichiers volumineux en concaténant des petits fichiers.

Compaction(Major)
Concaténation de tous les FileStores. Généralement exécuté une fois par jour (c.f. paramètre hbase.hregion.majorcompaction), une bonne pratique consiste à la forcer "manuellement" lors de moments de faible activité (voir la commande shell major_compact 'MYTABLE')

Coprocessor
API permettant d'écrire des programmes pouvant être appliqués à une Region et exécutés directement sur un RegionServer. Exemples d'utilisation : peut être utilisé pour construire des indexes secondaires sur les données ou contrôler finement l'accès à certaines partie la base de donnée.

FileStore
Fichier au format HFile qui stocke tout ou partie des données d'une Region.
Flush
Opération qui consiste à stocker dans un FileStore le contenu d'un MemStore. Par défaut cette sérialisation est demandée :
  • si un MemStore dépasse les 64Mo (c.f. HTableDescriptor.DEFAULT_MEMSTORE_FLUSH_SIZE)
  • si l'ensemble des MemStores d'un RegionServer occupent 35% du Heap (modifiable via le paramètre hbase.regionserver.global.memstore.lowerLimit). Au delà de 40% (paramètre hbase.regionserver.global.memstore.upperLimit) la sérialisation est forcé et les mises à jour sont bloquées.
HFile
Format de fichier utilisé par HBase pour stocker les données sur le système de fichier (voir HDFS). 

HDFS
Système de fichier massivement distribué développé dans le cadre du projet Hadoop. HDFS est généralement utilisé par HBase pour stocker les données. Il existe cependant des adaptateurs pour le service Amazon S3. HBase tire ainsi profit des fonctionnalités de HDFS comme la duplication des données et la possibilité d'effectuer sur ces dernières des traitements complexes de manière distribuée, rapide et fiable.

MapReduce
Framework du projet Hadoop permettant le traitement de donnée. HBase peut en tirer nativement profit afin de traiter les données.

MemStore
Structure mémoire qui conserve les dernières modifications effectués dans une Region. Lorsqu'un MemStore atteint une certaine taille il est "flushé" dans un FileStore.

MemStoreLAB (ou MSLAB)
Zone mémoire contiguë (de fait garantie d'être non fragmentée) , d'une taille 2Mo allouée dans la JVM dans laquelle un RegionServer va stocker les données d'un MemStore. Le but est d'éviter une fragmentation mémoire lors de la sérialisation des données (flush) du MemStore vers HDFS et du passage du Garbage Collector.

MasterServer
Serveur dont le rôle est de gérer l'assignation des Regions auprès des RegionServers. Il ne peut y avoir qu'un seul MasterServer en fonction à un instant donné mais un MasterServer de secours peut être déployé pour assurer une continuité de service automatiquement en cas de défaillance du premier.

Region : Rassemble tout ou partie des données d'une Column Family pour une Table. Dans une Region les données sont ordonnées par rapport à la clé, par défaut c'est l'ordre lexicographique qui est utilisé. Une Region rassemble les données d'un couple (Table, Column Family) d'une clé de départ à une clé de fin. (voir Split)

Region Server
Serveur en charge de la gestion d'une ou plusieurs Region

Split
Action qui consiste à diviser une région. Par défaut une Region ne peut excéder 256MB (paramétrable via hbase.hregion.max.filesize)

Table
Structure de donnée qui a un triplet {Key, Column, Timestamp} associe une valeur. Dans HBase les valeurs ne sont pas typées, tout est codé en interne comme un tableau d'octets (byte[]). HBase propose une classe "utilitaire" Bytes qui permet de convertir les types les plus courants.

Timestamp
Pour une clé et une colonne données HBase conserve plusieurs versions de la donnée, le Timestamp permet de distinguer les différentes versions. Par défaut HBase conserve 3 versions, ce paramètre est défini à la création de la Column Family.

TTL (ou Time To Live)
A chaque valeur est attribué une durée de vie. Ce paramètre, tout comme le nombre de versions conservées est définie au niveau de la ColumnFamily. Les données obsolètes sont supprimées lors des MajorCompactions.

WAL (Write Ahead Log)
Journal traçant les modifications apportées à une Region. C'est seulement une fois la modification enregistrée dans celui ci qu'elle est chargée en mémoire dans le MemStore. Le WAL peut être vu comme un point de reprise en cas d'arrêt brutal du RegionServer. Tout comme les autres fichiers qui contiennent des données le WAL est un fichier stocké sur HDFS ce qui permet à un RegionServer d'être secouru par d'autres RegionServer en cas de problème.


Zookeeper
Système de synchronisation massivement distribué du projet Hadoop. Il permet aux différents membres du cluster HBase de se synchroniser (e.g. élection d'un leader)

Tuesday, May 24, 2011

Analyse d'un "thread dump" d'une JVM IBM sous AIX

Dans quels cas le thread dump est utile ?
Le thread dump est un instantané de l'activité des threads de la JVM. Leur analyse est intéressante dans les cas où l'activité de la JVM ne semble pas normale :
  • Activité suspendue (deadlock/interblocage) ou partiellement suspendue (starvation/famine)
  • Activité existante mais le "débit" est en deçà de ce qui est attendu (Goulot d'étranglement / Bottleneck)
  • Activité existante mais le "débit" reste nul (Boucle infinie / Infinite Loop)
Comment avoir un thread dump ?
Nous nous limitons ici à la machine virtuel IBM sous AIX. Dans ce cas là il est extrêmement simple de déclencher la création d'un thread dump : il suffit de faire un kill -3 sur le processus Java.

Un fichier dont le nom est javacore.[date].[numero_processus].[compteur].txt est produit. Sur la sortie standard du processus vous devriez voir la ligne suivante s'afficher :
JVMDUMP010I Java Dump written to .....
En général le dump est produit dans le répertoire de travail du processus Java. Sous Oracle Weblogic il s'agit du répertoire du domaine : /..../domain/javacore...ou sous Tomcat il s'agit du répertoire bin
Attention : la production d'un tread dump est une opération couteuse qui fige l'activité de la machine virtuelle pendant un certain temps. Elle ralentie donc l'activité de cette dernière.

Comment lire le thread dump ?
Un thread dump est un fichier texte, vous pouvez l'ouvrir avec n'importe quel éditeur de texte. Mais à la longue, si vous devez en analyser plusieurs et que vous travaillez sur un serveur d'application qui fait tourner plusieurs centaines de threads en parallèle cela devient vite fastidieux.
Heureusement un monsieur chez IBM, Jinwoo Hwang, a réalisé un excellent logiciel pour l'analyse des thread dumps : IBM Thread and Monitor Dump Analyzer for Java. Cerise sur le gâteau on peut le télécharger librement sur le site d'IBM (à condition de s'inscrire) : http://www.alphaworks.ibm.com/tech/jca/download

Exemple sur un "Bottleneck"
Le code suivant va simuler un traitement long avec verrou d'un moniteur afin de simuler un goulot d'étranglement :

@WebServlet("/wait")
public class WaitServlet extends HttpServlet {
    private static Object lock = new Object();
    protected void doGet(
            HttpServletRequest request,
            HttpServletResponse response
    ) throws ServletException, IOException {
        response.getWriter().println("We need the lock");
        synchronized (lock) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        response.getWriter().println("OK");
    }
}

Cette servlet va être embarquée dans un Tomcat 7 et nous allons utiliser une JVM IBM 1.6.0 SR9FP1
N.B. Sur les versions antérieur à la SR9 un blocage de la JVM peut se produire lors de la demande du thread dump. On se retrouve alors avec un thread dump incomplet et une JVM complètement bloquée. Plus de détail sur http://www-01.ibm.com/support/docview.wss?uid=swg1IZ84925

Analyse du thread dump de l'exemple
Une  fois l'utilitaire démarré charger le thread dump :
  1. File ->  Open Thread Dumps : sélectionner le fichier qui commence par javacore.
  2. Cliquez sur le thread dump qui apparait alors dans la liste
  3. Analysis -> Monitor Detail
Une vue semblable à celle ci-dessous s'affiche : 
Exemple d'analyse d'analyse des moniteurs d'un thread dump IBM
J'ai démarré 10 clients en simultané avec JMeter.
Ce thread dump montre qu'un thread (celui qui s'appelle "http-bio-8080"-exec-7) possède le verrou sur notre objet partagé décrit dans la section Monitor : Owns Monitor Lock on java/lang/Object@0xB0A65A60
On peut aussi voir précisément qu'elle partie du code possède le verrou, pas de surprise ici il s'agit bien de la méthode doGet de la servlet.
A gauche, sous le thread qui possède le verrou, on peut voir la liste des 9 autres threads (et donc client HTTP) en attente. Si vous cliquez sur l'un de ces threads vous verrez où ces derniers sont en attentes et sur quel objet :
Détail d'un thread en attente

En vous souhaitant une bonne analyse de vos threads sous IBM AIX.

Monday, April 18, 2011

Configuration avancée d'un serveur JMS sous Oracle Weblogic

Ou « Combien de messages JMS un serveur JMS peut-t-il emmagasiner ? » Sous Weblogic (je ne sais pas comment cela se passe avec les autres) tout message JMS est stocké au moins partiellement dans la mémoire de la JVM, et cette mémoire n'étant pas infinie l'arrivée d'un grand nombre de messages associée à une mauvaise configuration du sous système JMS se solde souvent par un OutOfMemory.

Le schéma ci dessous montrent les différents mécanismes qui peuvent être mis œuvre par un serveur JMS afin de gérer l'accumulation des messages JMS et de réguler leur arrivée.


1. Définir un quota
Un message JMS consomme toujours de la mémoire. Partant de ce postulat un quota devrait toujours être défini sur un serveur JMS. Il évitera une saturation mémoire en refusant aux producteurs de poster des nouveaux messages lorsque le quota est atteint.
Les quotas peuvent être définis au niveau des modules JMS, cependant il peut être plus "sécurisant" de les placer directement au niveau du serveur JMS :
JMS Servers - Configuration - Thresholds and Quotas
Que se passe-t-il du coté des producteurs lorsqu'un quota est atteint ?
La « connection factory » peut spécifier un « Send Timeout ». Cela permet de mettre en attente un client un certain temps lorsque le quota est atteint. A l'issue de ce temps d'attente deux cas sont possibles :
  • Suffisamment d'espace a été libéré pour accepter le message sans repasser au dessus du quota : le producteur peut au final poster son message.
  • Le quota est toujours atteint : une exception de type "ressource allocation" est levée
Notez que par défaut si un message volumineux arrive et que sa taille entraîne un dépassement du quota il bloquera des messages plus petits qui pourraient être délivrés sans le dépasser. Si l'ordre des messages n'est pas une nécessité vous pouvez permettre aux petits messages de passer devant les plus gros : toujours dans la section "quotas", choisissez Preemptive à la place de FIFO dans le champ Blocking Send Policy.

2. Paging
Le « Paging » consiste à ne conserver en mémoire que l'entête des messages JMS. Le corps est sérialisé dans un répertoire appelé Paging Directory. Par défaut les serveurs JMS sont tous capables de faire du paging et ils le font lorsque la mémoire occupée atteint 1/3 du Heap ou 512Mo. Le champ Message Buffer Size vous permet de personnaliser cette valeur.
JMS Servers - Configuration
Il faut toujours garder à l'esprit que le paging ne suffit pas à prévenir une saturation mémoire, les entêtes des messages JMS étant toujours gardées en mémoire.
 Comme on peut le voir dans l'extrait ci dessus de la mémoire d'un serveur Weblogic, les messages JMS de type TextMessage qui occupent ici presque 8Ko sont remplacés au fur et à mesure de leur arrivée par une simple référence qui n'occupe plus que 200 octets.
Attention à ceux qui utilisent les "JMS Message Selector" pour trouver des messages particuliers dans une destination, seuls les entêtes "systèmes", propres à Weblogic, sont conservés en mémoire. Une requête basée sur d'autres entêtes et effectuée sur un message "paginé" sera plus lente que si le message était en mémoire.

3. Contrôle de débit (Flow Control)
Le contrôle de débit permet de ralentir la production de message lorsqu'un seuil (Threshold) est atteint, on dit alors d'un serveur JMS ou d'une destination qu'il devient « armé ». Ce seuil peut être aussi bien exprimé en terme de nombres de messages ou d'octets occupés par les messages.
Configuration du contrôle de débit
La configuration du Flow Control se fait dans la configuration de la Connection Factory. Les 2 paramètres intéressants sont :
  • Flow Maximum : le nombre maximum de messages par seconde qui peuvent être posté par un producteur dans une condition de seuil.
  • Flow Minimum :le débit minimal qui pourra être demandé par le serveur JMS à un producteur.
    JMS Modules - Connection factory - Configuration - Flow Control
Contrôle des seuils
2 seuils sont exprimés dans la configuration d'un serveur JMS
  • Le seuil haut : c'est le niveau à partir duquel le serveur JMS est « armé »
  • Le seuil bas : c'est le niveau en dessous duquel les producteurs peuvent augmenter le débit d'envoi des messages.
Configuration du serveur JMS : définition des seuils (threshold)
Lorsqu'un seuil est dépassé l'état de santé du serveur qui héberge le serveur Weblogic passe de "Ok" à "Warning"

Si vous avez mis en place une supervision basée sur l'interrogation des MBeans du serveurs vous serez donc averti de l'incident.
Le message suivant devrait aussi s'afficher dans les logs du serveur lorsqu'une condition de seuil est atteinte :
BEA-040030 JMSServer "JMSServer-0" message threshold for the server has been exceeded.
BEA-040028 JMSServer "JMSServer-0" byte threshold for the server has been exceeded