Blog Zenika

#CodeTheWorld

Architecture

Coherence Part IV : extracteurs et recherches distribuées sur le cluster

Cet article est la suite du précédent, qui montrait comment réaliser des recherches distribuées sur le cluster avec des filtres. Celui ci va plus loin et expose le mécanisme d’extraction des données à présenter au filtre à partir des entrées du cache.

Préparatifs : adaptation du modèle

Pour continuer à approcher d’une application adaptée à un datagrid, nous devons modifier le modèle. Cela nous permettra aussi de vous présenter toujours plus de features sympas de Coherence 😉
Le premier défaut du modèle actuel réside dans l’accès aux Visits qui requiert de récupérer un Owner puis d’itérer sur les Pets. Cette structure rend donc la manipulation des Visits dans le datagrid peu performante et pratique.
Pour améliorer les choses, nous vous proposons de stocker les Visits dans un cache séparé. Ceci impose une première modification du modèle pour remplacer la référence directe entre Pet et Visit par une référence sur les IDs des entités correspondantes. Sans cette modification, l’insertion d’une Visit dans le cache associé sérialiserait à la fois la Visit et tout le graphe d’objets concerné. De plus, le cache des Owners contiendrait toujours les Visits.
Nous pouvons en profiter pour réaliser une seconde modification qui nous simplifiera la vie. L’application PetClinic n’utilise le lien entre Pet et Visit que dans le sens Pet -> Visit. Nous allons donc retirer toute référence aux Visits dans Pet.
Nous avons fait toutes ces modifications fastidieuses pour vous (qui aime corriger des JSPs ?), comme d’habitude, le projet est disponible : – GitHub

git clone git://github.com/obourgain/petclinic-coherence.git
git checkout article4-start

zip

ValueExtractor

Dans l’article précédent nous avions requêté le cache avec une opération de type map/reduce grâce à un filtre avec le nom de la méthode à appeler par réflexion, mais Coherence peut aussi répondre à des problématiques de recherche plus poussées et de façon plus robuste grâce aux extracteurs.
L’interface ValueExtractor de Coherence permet de récupérer une valeur particulière pour chaque objet du cache. Il est possible de récupérer un attribut, appeler une méthode ou retourner n’importe quelle valeur qui vous passe par la tête. Les extracteurs peuvent êtres combinés aux filtres pour requêter le cache selon des critères complexes : le filtre s’applique sur la valeur retournée par l’extracteur.
Le filtre de l’article précédent utilise d’ailleurs implicitement un extracteur pour récupérer le résultat de la méthode donnée par reflexion. On pourrait modifier le LikeFilter de l’article précédent pour utiliser explicitement un ReflexionExtractor, le fonctionnement resterait le même :

Filter lastNameFilter = new LikeFilter(new ReflectionExtractor("getLastName"), lastName + "%", '\\', true);

Les extracteurs apportent beaucoup de souplesse dans les possibilités de requêtage. Coherence propose quelques autres extracteurs mais dans la plupart des cas il faudra une nouvelle implémentation de l’interface pour adapter les requêtes au modèle : query = code !

Back to business

La méthode loadPet(), qui itère sur le cache des Owners, va pouvoir tirer parti des extracteurs pour paralléliser et distribuer le traitement. Nous allons utiliser un extracteur qui retourne une Collection des IDs de tous les Pets pour chaque Owner. Les IDs extraient vous ensuite se voir appliquer un ContainsFilter pour chercher l’ID demandé dans la Collection d’IDs. Nous récupérons la Collection d’Entry pour lesquelles le filtre a retourné true, c’est à dire une Collection qui contient le seul Owner lié au Pet recherché. Il faut maintenant parcourir les Pets de ce Owner pour trouver le bon. Voici l’extracteur :

public class PetIdsExtractor implements ValueExtractor {
	public Object extract(Object o) {
		Owner owner = (Owner) o;
		List<Integer> extractedPetIds = new ArrayList<Integer>();
		for (Pet pet : owner.getPets()) {
			extractedPetIds.add(pet.getId());
		}
		return extractedPetIds;
	}
}

et son utilisation dans le filtre :

public Pet loadPet(int id) throws DataAccessException {
	ContainsFilter filter = new ContainsFilter(new PetIdsExtractor(), id);
	// there should be at most one owner matching the filter
	Set<Entry<Integer, Owner>> entries = getOwnersCache().entrySet(filter);
	if (entries.isEmpty()) {
		return null;
	}
	Entry<Integer, Owner> entry = entries.iterator().next();
	Owner owner = entry.getValue();
	for (Pet pet : owner.getPets()) {
		if (pet.getId().equals(id)) {
			return pet;
		}
	}
	return null;
}

Ce code est plus long qu’avant, mais c’est le prix de l’efficacité : maintenant le traitement est distribué sur le cluster. Rassurez-vous, les recherches dans la datagrid peuvent être beaucoup plus courtes, ici c’est la recherche dans la Collection qui est verbeuse.
De manière analogue à une base de données relationelle, plus le cache contient d’entrées, plus l’opération devient coûteuse en CPU puisque le cache est parcouru et chaque entrée doit être de-sérialisée avant d’appliquer l’extracteur, même si le traitement est distribué il peut y avoir une augmentation du temps de traitement. Les solutions pour pallier ce problème sont d’utiliser un index, ce sera le sujet du prochain article, ou un algorithme de sérialisation plus performant.
Avec les modifications du modèle, nous devons ajouter une méthode pour récupérer les Visits d’un Pet. Cette méthode peut aussi bénéficier de la puissance d’un extracteur :

public class PetIdFromVisitExtractor implements ValueExtractor {
	public Object extract(Object o) {
		Visit visit = (Visit) o;
		return visit.getPetId();
	}
}

Avec cet extracteur, nous pouvons récupérer l’ensemble des Visits comme suit :

public Set<Visit> loadVisitsForPet(int petId) throws DataAccessException {
	Filter filter = new EqualsFilter(new PetIdFromVisitExtractor(), petId);
	Set<Entry<Integer, Visit>> visits = getVisitsCache().entrySet(filter);
	Set<Visit> result = new HashSet<Visit>();
	for (Entry<Integer, Visit> entry : visits) {
		result.add(entry.getValue());
	}
	return result;
}

Dans ce cas nous retournons l’ensemble des résultats récupérés via le filtre. Coherence ne propose pas de méthode du type values() avec un filtre, il faut donc itérer sur l’entrySet pour les récupérer.
Le projet est téléchargeable : Sur GitHub

git clone git://github.com/obourgain/petclinic-coherence.git
git checkout article4-start

En zip

Conclusion

Les extracteurs de Coherence sont puissants et combinés aux filtres que nous avons présentés dans l’article précédent, vous êtes maintenant capable de récupérer de façon efficace et distribuée les données dans les caches. Il est toujours nécessaire de de-sérialiser chaque entrée puis appliquer l’extracteur et le filtre dessus, ce qui peut être problématique sur des caches contenant beaucoup d’entrées ou avec des contraintes de vitesse. Le prochain article montrera comment ajouter des index dans le caches afin d’accélérer les recherches.
Index des articles de la série Coherence :

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

En savoir plus sur Blog Zenika

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Continue reading