Préchargement de mappe

Les mappes peuvent être associées à des chargeurs. Un chargeur sert à aller chercher des objets quand ils sont introuvables dans la mappe (absence du cache) et il sert également à écrire les modifications à un système dorsal lors de la validation des transactions. Les chargeurs peuvent également servir à précharger des données dans une mappe. La méthode preloadMap de l'interface Loader est appelée sur chaque mappe lorsque sa partition correspondante dans le groupe de mappes devient la partition principale. La méthode preloadMap n'est pas appelée sur les répliques. Cette méthode tente de charger dans la mappe à partir du dorsal toutes les données de référence concernées à l'aide de la session fournie. La mappe pertinente est identifiée par l'argument BackingMap qui est passé à la méthode preloadMap.

void preloadMap(Session session, BackingMap backingMap) throws LoaderException;

Préchargement dans un groupe de mappes partitionné

Les mappes peuvent être partitionnées en N partitions. Elles peuvent donc s'étendre sur plusieurs serveurs, chaque entrée étant identifiée par une clé qui n'est stockée que sur l'un de ces serveurs. Les mappes de très grande taille peuvent être détenues dans une grille de données car l'application n'est plus limitée par la taille du segment d'une seule machine virtuelle Java (JVM) pour contenir toutes les entrées d'une mappe. Les applications qui veulent effectuer un préchargement avec la méthode preloadMap de l'interface Loader doivent identifier le sous-ensemble des données à précharger. Les partitions existent toujours en nombre fixe. Il est possible de déterminer ce nombre à l'aide de cet exemple de code :

int numPartitions = backingMap.getPartitionManager().getNumOfPartitions();
int myPartition = backingMap.getPartitionId();
Cet exemple de code montre comment une application peut identifier le sous-ensemble de données qu'elle doit précharge à partir de la base de données. Les applications doivent toujours utiliser ces méthodes même si la mappe n'est pas initialement partitionnée. Ces méthodes permettent la flexibilité : si la mappe est ultérieurement partitionnée par les administrateurs, le chargeur continuera à opérer correctement.

L'application doit émettre des requêtes pour extraire du système dorsal le sous-ensemble myPartition. Si une base de données est utilisée, il peut être plus facile d'avoir une colonne avec l'identificateur de partition d'un enregistrement donné à moins qu'il n'y ait une requête naturelle permettant aux données de la table de se partitionner facilement.

Pour un exemple d'implémentation d'un chargeur pour une grille de données répliquée, voir Ecriture d'un chargeur avec un contrôleur de préchargement de fragments réplique.

Performances

L'implémentation du préchargement copie dans la mappe les données à partir du dorsal en stockant plusieurs objets dans la mappe en une seule transaction. Le nombre optimal d'enregistrements à stocker par transaction dépend de plusieurs facteurs, notamment la complexité et la taille. Ainsi, après que la transaction comprend des blocs de plus de 100 entrées, les avantages en termes de performances diminuent au fur et à mesure que l'on augmente le nombre des entrées. Pour déterminer le nombre optimal, commencez par 100 entrées, puis augmentez le nombre jusqu'à ce que les performances deviennent nulles. Les transactions de grande taille donnent de meilleures performances de réplication. N'oubliez pas que seul le fragment primaire exécute le code de préchargement. Les données préchargées sont répliquées depuis le fragment primaire vers les fragments réplique qui sont en ligne.

Préchargement d'un groupe de mappes

Si l'application utilise un groupe de mappes avec plusieurs mappes, chaque mappe a son propre chargeur. Chaque chargeur a une méthode de préchargement. Chaque mappe est chargée en série par la grille de données. Ce sera plus efficace de précharger toutes les mappes en désignant une mappe comme la mappe de préchargement. Ce processus est une convention d'application. On pourrait, par exemple, avoir deux mappes, Department et Employee, qui utilisent le chargeur Department pour précharger les deux mappes. Cette procédure garantit que, de manière transactionnelle, si une application veut un département, les salariés de ce département seront dans le cache. Lorsque le chargeur Department précharge un département depuis le dorsal, il récupère également les salariés de ce département. L'objet Department et les objets Employee qui lui sont associés sont ajoutés à la mappe à l'aide d'une seule transaction.

Préchargement récupérable

Il arrive que les clients aient des ensembles de données de très grosse taille et qui nécessitent d'être mis en cache. Précharger ces données peut prendre énormément de temps. Parfois, le préchargement doit être terminé pour que l'application puisse aller en ligne. C'est là que rendre récupérable le préchargement devient intéressant. Supposons qu'il y ait un million d'enregistrements à précharger. Le fragment primaire les télécharge et échoue au 800 000e enregistrement. En principe, le fragment réplique choisi pour être le nouveau fragment primaire efface tout état répliqué et repart du début. eXtreme Scale peut utiliser une interface ReplicaPreloadController. Le chargeur de l'application aura également besoin d'implémenter l'interface ReplicaPreloadController. Notre exemple ajoute une seule méthode au chargeur : Status checkPreloadStatus(Session session, BackingMap bmap);. Cette méthode est appelée par l'environnement d'exécution eXtreme Scale avant la méthode de préchargement de l'interface Loader. eXtreme Scale teste le résultat de cette méthode (Status) pour déterminer son comportement au cas où un fragment réplique passe au statut de fragment primaire.

Tableau 1. Valeur du statut et réponse
Valeur de statut retournée Réponse d'eXtreme Scale
Status.PRELOADED_ALREADY eXtreme Scale n'appelle pas du tout la méthode de préchargement car ce statut indique que la mappe est complètement préchargée.
Status.FULL_PRELOAD_NEEDED eXtreme Scale efface la mappe et appelle de manière normale la méthode de préchargement.
Status.PARTIAL_PRELOAD_NEEDED eXtreme Scale laisse la mappe comme elle est et appelle la méthode de préchargement. Cette stratégie permet au chargeur de l'application de continuer le préchargement à partir du point où il en était resté.

A l'évidence, lorsqu'un fragment primaire précharge la mappe, il doit laisser un état dans une mappe du groupe de mappes à répliquer pour que la réplique détermine l'état à retourner. Vous pouvez utiliser une mappe supplémentaire appelée, par exemple, RecoveryMap. Cette mappe doit faire partie du groupe de mappes à précharger pour que la mappe soit répliquée de manière cohérente avec les données à précharger. Nous allons suggérer une implémentation.

Lorsque le préchargement valide chaque bloc d'enregistrements, dans le cadre de cette transaction, le processus actualise également un compteur ou une valeur dans la mappe RecoveryMap. Les données préchargées et celles de la mappe RecoveryMap sont répliquées de manière atomique vers les fragments réplique. Lorsque le fragment réplique passe au statut de fragment primaire, il est à présent en mesure de vérifier la mappe RecoveryMap pour voir ce qui s'est passé.

La mappe RecoveryMap peut contenir une seule entrée avec la clé d'état. Si aucun objet n'existe pour cette clé, vous avez besoin d'une méthode complète preload (checkPreloadStatus returns FULL_PRELOAD_NEEDED). Si un objet existe pour cette clé d'état et que la valeur est COMPLETE, le préchargement prend fin et la méthode checkPreloadStatus retourne PRELOADED_ALREADY. Autrement, l'objet value indique le point de redémarrage du préchargement et la méthode checkPreloadStatus retourne PARTIAL_PRELOAD_NEEDED. Le chargeur peut stocker le point de récupération dans une variable d'instance du chargeur de manière à ce que, lorsque le préchargement est appelé, le chargeur sache d'où partir. La mappe RecoveryMap peut également détenir une entrée par mappe si chacune des mappes est préchargée de manière indépendante.

Gérer la récupération en mode de réplication synchrone avec un chargeur

L'environnement d'exécution d'eXtreme Scale est conçu pour ne pas perdre de données validées en cas de défaillance du fragment primaire. Nous allons voir quels algorithmes sont utilisés à cet effet. Ces algorithmes ne s'appliquent que lorsqu'un groupe de réplication utilise la réplication synchrone. L'usage d'un chargeur n'est pas obligatoire.

Il est possible de configurer l'environnement d'exécution d'eXtreme Scale pour répliquer de manière synchrone vers les fragments réplique toutes les modifications d'un fragment primaire. Lorsqu'il est placé, un fragment réplique synchrone reçoit une copie des données existant dans le fragment primaire. Pendant ce temps, le fragment primaire continue de recevoir des transactions qu'il copie de manière asynchrone vers le fragment réplique. A ce moment-là, le fragment réplique n'est pas encore considéré comme étant en ligne.

Une fois que le fragment réplique a rattrapé le fragment primaire, il passe en mode homologue et la réplication synchrone peut commencer. Toute transaction validée sur le fragment primaire est envoyée aux fragments réplique synchrones et le fragment primaire attend la réponse de chacun de ces fragments. Une séquence de validation synchrone avec un chargeur dans le fragment primaire ressemble à la procédure suivante :

Tableau 2. Séquence de validation dans le fragment primaire
Avec chargeur Sans chargeur
Obtention des verrous pour les entrées Identique
Vidage des modifications vers le chargeur "No operation"
Enregistrement des modifications dans le cache Identique
Envoi des modifications vers les fragments réplique et attente d'accusé de réception Identique
Validation vers le chargeur via le plug-in TransactionCallback Appel de la validation via le plug-in, mais sans effet
Libération des verrous pour les entrées Identique

Vous remarquerez que les modifications sont envoyées aux fragments réplique avant d'être validées vers le chargeur. Pour déterminer lorsque les modifications sont validées dans le fragment réplique, modifiez cette séquence : lors de l'initialisation, initialisez de la manière suivante les listes tx dans le fragment primaire.

CommitedTx = {}, RolledBackTx = {}

Pendant le traitement synchrone des validations, utilisez la séquence suivante :

Tableau 3. Traitement synchrone des validations
Avec chargeur Sans chargeur
Obtention des verrous pour les entrées Identique
Vidage des modifications vers le chargeur "No operation"
Enregistrement des modifications dans le cache Identique
Envoi des modifications avec une transaction validée, annulation de la transaction vers le fragment réplique et attente d'accusé de réception Identique
Effacement de la liste des transactions validées et des transactions annulées Identique
Validation vers le chargeur via le plug-in TransactionCallBack La validation via le plug-in TransactionCallBack est toujours appelée, mais en principe sans effet
Si la validation réussit, ajout de la transaction aux transactions validées, sinon, ajout aux transactions annulées "No operation"
Libération des verrous pour les entrées Identique

Pour le traitement des fragments réplique, utilisez la séquence suivante :

  1. Réception des modifications
  2. Validation de toutes les transactions reçues dans la liste des transactions validées
  3. Annulation de toutes les transactions reçues dans la liste des transactions annulées
  4. Démarrage d'une transaction ou d'une session
  5. Application des modifications à la transaction ou à la session
  6. Enregistrement de la transaction ou de la session dans la liste en attente
  7. Renvoi de la réponse

Vous remarquerez que, dans le fragment réplique, il ne se passe aucune interaction avec le chargeur tant que le fragment réplique est en mode réplique. C'est au fragment primaire d'envoyer toutes les modifications via le chargeur. La réplique ne modifie pas les données. Un effet collatéral de cet algorithme est que le fragment réplique dispose toujours des transactions, mais celles-ci ne sont validées qu'après que la transaction primaire suivante a envoyé le statut de validation de ces transactions. Les transactions sont alors validées ou annulées dans le fragment réplique. Jusque-là, les transactions ne sont pas validées. Il est possible d'ajouter dans le fragment primaire un minuteur qui envoie le résultat de la transaction après un bref délai (quelques secondes). Ce minuteur limite, sans l'éliminer tout à fait, le décalage de ce créneau. Ce décalage n'est un problème qu'en mode de lecture de réplique. Sinon, il n'a aucun impact sur l'application.

Lorsque le fragment primaire échoue, c'est vraisemblablement que quelques transactions ont été validées ou annulées dans ce fragment primaire, mais que le message n'a jamais été transmis au fragment réplique avec ces résultats. Lorsqu'un fragment réplique passe au rang de nouveau fragment primaire, l'une de ses premières actions est de gérer cette situation. Chaque transaction en attente est traitée à nouveau par rapport à l'ensemble de mappes du nouveau fragment primaire. S'il y a un chargeur, chaque transaction est remise à ce dernier. Ces transactions sont appliquées dans un ordre FIFO strict. Si une transaction échoue, elle est ignorée. Si trois transactions A, B et C sont en attente, A peut être validée, B peut être annulée et C peut être aussi validée. Aucune de ces trois transactions n'a d'impact sur les autres. Supposons qu'elles soient indépendantes.

Lorsqu'il se trouve en mode de reprise par basculement, un chargeur pourra vouloir utiliser une logique légèrement différente de celle utilisée en mode normal. L'implémentation de l'interface ReplicaPreloadController permet au chargeur de savoir facilement lorsqu'il est en mode de reprise par basculement. La méthode checkPreloadStatus n'est appelée que lorsque la reprise par basculement est terminée. Par conséquent, si la méthode apply de l'interface Loader est appelée avant la méthode checkPreloadStatus, il y a une transaction de récupération. Après l'appel de la méthode checkPreloadStatus, la récupération est terminée.