Files d'attente des requêtes d'entité

Les applications peuvent créer des files d'attente qualifiées par des requêtes sur une entité dans l'environnement eXtreme Scale local ou côté serveur. Les entités du résultat de la requête sont stockées dans cette file d'attente. Les files d'attente sont actuellement prises en charge uniquement dans les mappes utilisant la stratégie de verrouillage pessimiste.

Une même file d'attente de requêtes est partagée par plusieurs transactions et plusieurs clients. Une fois la file d'attente vide, la requête d'entité associée à cette file d'attente est exécutée à nouveau et de nouveaux résultats sont ajoutés à la file. La file d'attente est identifiée uniquement par la chaîne et les paramètres de la requête d'entité. Une instance ObjectGrid contient une seule instance de chaque file d'attente de requête unique. Pour plus d'informations, consultez la documentation relative à l'API EntityManager.

Exemple de requête de file d'attente

L'exemple suivant présente comment utiliser une file d'attente de requêtes.

/**
 * Get a unassigned question type task 
 */
private void getUnassignedQuestionTask() throws Exception {
    EntityManager em = og.getSession().getEntityManager();
    EntityTransaction tran = em.getTransaction();

    QueryQueue queue = em.createQueryQueue("SELECT t FROM Task t
				WHERE t.type=?1 AND t.status=?2", Task.class);
    queue.setParameter(1, new Integer(Task.TYPE_QUESTION));
    queue.setParameter(2, new Integer(Task.STATUS_UNASSIGNED));

    tran.begin();
    Task nextTask = (Task) queue.getNextEntity(10000);
    System.out.println("next task is " + nextTask);
    if (nextTask != null) {
        assignTask(em, nextTask);
    }
    tran.commit();
}

Dans cet exemple, une file d'attente est créée avec une chaîne de requête d'entité : "SELECT t FROM Task t WHERE t.type=?1 AND t.status=?2". Les paramètres de l'objet QueryQueue sont ensuite définis. Cette file d'attente de requêtes représente toutes les tâches "non affectées" de type "question". L'objet QueryQueue est très semblable à un objet Query d'entité.

Une fois la file d'attente créée, la transaction d'entité démarre et la méthode getNextEntity est appelée. Cette méthode extrait l'entité disponible suivante dans un délai de 10 secondes. Une fois l'entité extraite, elle est traitée par la méthode assignTask. Celle-ci modifie l'instance d'entité de tâche et lui donne le statut "affecté". Comme elle ne correspond plus au filtre de la file d'attente, elle est supprimée. Une fois affectée, la transaction est validée.

Cet exemple simple vous montre qu'une file d'attente de requête est semblable à une requête d'entité. Des différences sont toutefois à noter :
  1. Il est possible d'extraire des entités de la file d'attente de manière itérative. L'application utilisateur décide du nombre d'entités à extraire. Par exemple, si vous utilisez QueryQueue.getNextEntity(timeout), une seule entité est extraite alors que si vous utilisez QueryQueue.getNextEntities(5, timeout), 5 entités le sont. Dans un environnement réparti, le nombre d'entités décide directement du nombre d'octets à transférer du serveur au client.
  2. Lorsqu'une entité est extraite d'une file d'attente de requête, un verrou U est placé sur l'entité afin qu'aucune autre transaction ne puisse y accéder.

Extraction d'entités dans une boucle

Vous pouvez extraire des entités dans une boucle. L'exemple qui suit illustre comment obtenir toutes les tâches de type question non affectées.

/**
 * Get all unassigned question type tasks 
 */
private void getAllUnassignedQuestionTask() throws Exception {
    EntityManager em = og.getSession().getEntityManager();
    EntityTransaction tran = em.getTransaction();

    QueryQueue queue = em.createQueryQueue("SELECT t FROM Task t WHERE 
			t.type=?1 AND t.status=?2", Task.class);
    queue.setParameter(1, new Integer(Task.TYPE_QUESTION));
    queue.setParameter(2, new Integer(Task.STATUS_UNASSIGNED));

    Task nextTask = null;

    do {
        tran.begin();
        nextTask = (Task) queue.getNextEntity(10000);
        if (nextTask != null) {
            System.out.println("next task is " + nextTask);
        }
        tran.commit();
    } while (nextTask != null);
}

Si la mappe d'entités contient 10 tâches de type question non affectées, vous vous attendez à ce que ces 10 entités vont apparaître sur la console. Toutefois, lors de l'exécution de la requête, vous constatez, contrairement à vos suppositions, que le programme ne se termine jamais.

Lorsqu'une file d'attente de requête est créée et que la méthode getNextEntity est appelée, la requête d'entité associée à la file d'attente est exécutée et les 10 résultats sont ajoutés à la file d'attente. Lors de l'appel de cette méthode, une entité est retirée de la file d'attente. Après 10 appels, la file d'attente est vide. La requête d'entité est automatiquement réexécutée. Comme ces 10 entités existent toujours et qu'elles correspondent toujours aux critères de filtre de la file d'attente de la requête, elles sont à nouveau ajoutées à la file d'attente.

Si la ligne suivante est ajoutée après l'instruction println(), 10 entités seulement apparaissent.

em.remove(nextTask);

Pour plus d'informations sur l'utilisation de SessionHandle avec QueryQueue dans un déploiement de positionnement par conteneur, consultez la section Intégration de l'objet SessionHandle.

Déploiement des files d'attente de requêtes à toutes les partitions

Dans un environnement eXtreme Scale réparti, vous pouvez créer une file d'attente pour une partition ou pour toutes les partitions. Si la file d'attente est créée pour toutes les partitions, chaque partition sera associée à une instance de celle-ci.

Lorsque le client tente d'obtenir l'entité suivante à l'aide de la méthode QueryQueue.getNextEntity ou QueryQueue.getNextEntities, il envoie une requête à l'une des partitions. Un client envoie une demande d'exécution immédiate et une demande d'exécution différée au serveur :

  • Avec une demande d'exécution immédiate, le client envoie une demande à une partition et le serveur répond immédiatement. Si la file d'attente contient une entité, le serveur envoie une réponse avec l'entité. Dans le cas contraire, le serveur envoie une réponse sans entité. Dans les deux cas, le serveur répond immédiatement.
  • Avec une demande d'exécution différée, le client envoie une demande à une partition et le serveur attend qu'une entité devienne disponible. Si la file d'attente contient une entité, le serveur envoie immédiatement une réponse avec l'entité. Dans le cas contraire, il attend qu'une entité devienne disponible, ou le délai d'attente de la requête est dépassé.

L'exemple suivant montre comment récupérer une entité pour une file d'attente de requête déployée sur toutes les partitions (n) :

  1. Lorsque la méthode QueryQueue.getNextEntity ou QueryQueue.getNextEntities est appelée, le client sélectionne au hasard un numéro de partition compris entre 0 et n-1.
  2. Le client envoie une demande d'exécution immédiate à la partition sélectionnée.
    • Si une entité est disponible, la méthode QueryQueue.getNextEntity ou QueryQueue.getNextEntities se termine en renvoyant l'entité.
    • Si une entité n'est pas disponible et que d'autres partitions non visitées existent, le client envoie une demande d'exécution immédiate à la partition suivante.
    • Si une entité n'est pas disponible et qu'aucune autre partition non visitée n'existe, le client envoie une demande d'exécution différée.
    • Si la demande d'exécution différée envoyée à la dernière partition arrive à expiration et qu'aucune donnée disponible n'existe, le client fait une dernière tentative et envoie une demande d'exécution immédiate à toutes les partitions en série. Si une entité est disponible dans les partitions précédentes, le client pourra ainsi l'obtenir.

Prise en charge des entités de sous-ensemble et des non-entités

Voici la méthode permettant de créer un objet QueryQueue dans le gestionnaire d'entités :

public QueryQueue createQueryQueue(String qlString, Class entityClass);

Le résultat en file d'attente doit être projeté vers l'objet défini par le second paramètre de la méthode, Class entityClass.

Si ce paramètre est indiqué, le nom d'entité associé à la classe doit être identique à celui indiqué dans la chaîne de requête. Cela se révèle utile si vous souhaitez projeter une entité dans une entité de sous-ensemble. Si une valeur null est associée à la classe d'entités, le résultat n'est pas projeté. La valeur stockée dans la mappe aura le format d'un bloc de format d'entité.

Collision de clé côté client

Dans un environnement eXtreme Scale réparti, les files d'attente de requête sont uniquement prises en charge pour les mappes eXtreme Scale dont le mode de verrouillage est pessimiste. Aucun cache local n'existe côté client. La mappe transactionnelle peut toutefois contenir les données (clé et valeur) d'un client. Une collision de clé peut en résulter lorsqu'une entité extraite du serveur et une entrée déjà présente dans la mappe transactionnelle partagent la même clé.

En cas de collision de clé, le client eXtreme Scale utilise la règle suivante pour émettre une exception ou pour remplacer les données en mode silencieux.
  1. Si la clé concernée est la clé de l'entité indiquée dans la requête d'entité associée à la file d'attente, une exception est émise. Dans ce cas, la transaction est annulée et le verrou U de l'entité est libéré côté serveur.
  2. Si la clé concernée est la clé de l'association d'entité, les données de la mappe transactionnelle sont remplacées sans avertissement.

La collision de clé produit uniquement lorsque la mappe transactionnelle contient des données. En d'autres termes, elle se produit uniquement lorsqu'un appel getNextEntity ou getNextEntities est émis dans une transaction ayant déjà été modifiée (une nouvelle donnée a été insérée ou une donnée a été mise à jour). Lorsqu'une application ne souhaite pas qu'une collision de clé se produise, elle doit appeler getNextEntity ou getNextEntities dans une transaction n'ayant pas encore été modifiée.

Echecs du client

Un échec du client peut se produire après l'envoi d'une demande getNextEntity ou getNextEntities du client au serveur, par exemple :
  1. Le client envoie une demande au serveur, puis l'échec se produit.
  2. Le client obtient une ou plusieurs entités du serveur, puis l'échec se produit.

Dans le premier cas, le serveur découvre que l'échec du client se produit lorsqu'il tente de renvoyer la réponse à ce dernier. Dans le second cas, lorsque le client obtient une ou plusieurs entités du serveur, un verrou X est placé sur ces entités. En cas d'échec du client, la transaction expire et le verrou X est libéré.

Requête contenant une clause ORDER BY

Les files d'attente de requête n'honorent généralement pas la clause ORDER BY. Si vous appelez getNextEntity ou getNextEntities à partir de la file d'attente, rien ne garantit que les entités seront renvoyées dans l'ordre indiqué. Il est en effet impossible de classer les entités de plusieurs partitions. Au cas où la file d'attente serait déployée sur toutes les partitions, une partition est sélectionnée au hasard en vue du traitement de la demande lorsqu'un appel getNextEntity ou getNextEntities est émis. L'ordre des entités n'est donc pas garanti.

La clause ORDER BY est honorée si une file d'attente est déployée sur une seule partition.

Pour plus d'informations, voir API de requête EntityManager.

Un appel par transaction

Chaque appel à QueryQueue.getNextEntity ou à QueryQueue.getNextEntities extrait les entités correspondantes d'une partition prise au hasard. Les applications doivent n'appeler par transaction qu'un seule QueryQueue.getNextEntity ou qu'un seul QueryQueue.getNextEntities. Sinon, eXtreme Scale se retrouverait avec des entités provenant d'une multiplicité de partitions, ce qui provoquerait une exception au moment de valider la transaction.