Coherence Part III : Filtres


Les datagrids fournissent des mécanismes de recherche de données adaptés à leur caractère distribué. Chaque requête est envoyée à chaque noeud du cluster qui retourne une partie du résultat, à la manière de Map/Reduce. En parallélisant le traitement, les datagrids tirent parti de toute la puissance de calcul du cluster.
Dans cet article, nous allons voir comment utiliser les filtres Coherence pour effectuer des recherches optimisées.

Les caches Coherence implémentent l'interface QueryMap qui fournit entre autres des méthodes de recherche spécifiques. Quand une recherche est lancée, le nœud d'origine distribue la requête sur les autres noeuds qui vont effectuer la recherche sur les données qu'ils contiennent localement puis attend le retour de chaque nœud pour constituer le résultat final.

Ces méthodes de recherche font appel à l'API Filter. Le Filter est appliqué sur chaque entrée du cache et détermine si elle appartient au résultat, Coherence fournit un ensemble plutôt complet de Filters pour les comparaisons de base : EqualsFilter, LessFilter, LessEqualsFilter, RegexpFilter etc . Il existe aussi des filtres sur les Collections comme le ContainsAnyFilter.

A la recherche du Owner perdu !

Nous allons remplacer la boucle inefficace de la méthode findOwner() par une recherche par filtre. Dans notre cas, le Filter le plus adapté est le LikeFilter qui permet de comparer des String avec wildcard. C'est l'équivalent du like en SQL. Nous lui passons le nom de la méthode à appeler sur l'entrée du cache. Le prochain article traitera les cas plus complexes de recherche.

public Collection<Owner> findOwners(String lastName) throws DataAccessException {
	// create the filter, search \ is the escape char and the last param is set to true to be case insensitive
	Filter lastNameFilter = new LikeFilter("getLastName", lastName + "%", '\\', true);
 
	// execute the request synchronously
	Set<Entry<Integer, Owner>> entrySet = getOwnersCache().entrySet(lastNameFilter);
 
	// build the result
	List<Owner> result = new ArrayList<Owner>();
	for (Entry<Integer, Owner> entry : entrySet) {
		result.add(entry.getValue());
	}
	return result;
}

Combo !

Coherence propose aussi un ensemble de filtres particuliers pour matcher des entrées avec des critères complexes. Par exemple, le AndFilter combine les conditions de deux filtres sur chaque entrée. Dans la même veine, on trouve les OrFilter et XorFilter ainsi que les variantes avec plus de deux filtres en entrée. On a donc :

Filter filter1 = new LikeFilter("getSomeAttribute", theStringToMatch);
Filter filter2 = new EqualsFilter("getAnotherAttribute", 2);
Filter andFilter = new AndFilter(filter1, filter2);
Set theResult = cache.entrySet(andFilter);

Optimiser la consommation mémoire

La recherche est distribuée sur le cluster mais le résultat finira dans la JVM qui a lancé la recherche, il peut donc parfois être utile de limiter la consommation mémoire du résultat. Il y a deux solutions à ce problème. La première est d'utiliser la méthode keySet(Filter) de QueryMap, qui va retourner uniquement les clés dont la valeur associée est retournée par le Filter, on évite ainsi de faire monter toutes les graphes d'objets contenus dans les valeurs dans la mémoire. Ainsi, il est possible d'itérer sur le keySet et de récupérer les valeurs une par une ou par petit groupe avec get() et getAll(), c'est du lazy loading. Attention avec get(), chaque appel effectue un appel réseau pour récupérer la donnée, ce qui est déconseillé dans le cas d'un grand nombre d'objets et devrait être limité à des cas particuliers pour lesquels le getAll() pose des problèmes.

L'autre manière de faire est d'utiliser un LimitFilter. Ce filtre prend en paramètre un autre filtre et va simplement limiter le nombre de résultats, on peut donc faire de la pagination coté serveur, au niveau de l'accès aux données.

public Collection<Owner> findOwners(String lastName) throws DataAccessException {
	Filter lastNameFilter = new LikeFilter("getLastName", lastName + "%", '\\', true);
	Filter limitFilter = new LimitFilter(lastNameFilter, 30);
 
	// fetch the 30 first Owners matching the lastNameFilter
	Set<Entry<Integer, Owner>> entrySet = getOwnersCache().entrySet(limitFilter);
 
	List<Owner> result = new ArrayList<Owner>();
	for (Entry<Integer, Owner> entry : entrySet) {
		result.add(entry.getValue());
	}
	return result;
}

Pour accéder aux autres pages, il faut appeler la méthode setPage(int) (ou nextPage() et previousPage() qui ne font qu'incrémenter ou décrémenter à partir du numéro de page actuel) puis réexéctuer la requête (eg entrySet() ou keySet()) avec le filtre. La documentation de Coherence précise qu'il faut utiliser la même instance de LimitFilter à chaque appel.

C'est tout pour aujourd'hui

Les filtres peuvent utiliser des index pour accélérer les recherches en évitant de parcourir et donc de-sérialiser l'ensemble des données du cache et ils serviront aussi à exécuter des traitements distribués sur le cluster mais ceci est une autre histoire et d'autres articles.

Comme toujours, le code peut être récupéré :

GitHub :

git clone git://github.com/obourgain/petclinic-coherence.git
git checkout article3-end

et en zip

Index des articles de la série Coherence :


Commentaires

1. Le vendredi 25 novembre 2011, 11:42 par Christophe PARAGEAUD

Bonjour,

tout d'abord beau challenge que de vouloir démystifier Coherence.

Je me permet de réagir à l'assertion suivante "Les filtres peuvent utiliser des index pour accélérer les recherches en évitant de parcourir et donc de-sérialiser l'ensemble des données du cache".

Depuis la version 3.5 il a été introduit la classe PofExtractor.
Comme son nom l'indique c'est un extracteur de valeur basé sur la sérialisation maison POF.
Son immense avantage est de "comprendre" le format binaire utilisé et donc d'éviter l'étape de desérialisation.

S'il y a bien un conseil à retenir lors de l'utilisation de Coherence, c'est : Utilisez POF !

2. Le vendredi 25 novembre 2011, 21:51 par Olivier Bourgain

Bonjour,
Merci pour cette remarque. Effectivement, le format POF et les fonctionnalités associés permettent d'améliorer grandement les performances, au prix d'une augmentation de la maintenance du code.
Le format POF, les PofExtractor, PofUpdater, la sérialisation POF et le support de l'évolution du modèle à chaud seront abordés dans un prochain article, pour le moment nous resterons sur les fonctionnalités les plus courrante.

De plus, les extracteurs sont utilisés pour créer les index, il est donc possible de cumuler utilisation d'un index avec un PofExtractor.

Fil des commentaires de ce billet

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.