Program guide > Access data with client applications > Cache objects and their relationships (EntityManager API)
Entity query queues
Query queues allow applications to create a queue qualified by a query in the server-side or local eXtreme Scale over an entity. Entities from the query result are stored in this queue. Currently, query queue is only supported in a map that is using the pessimistic lock strategy.
A query queue is shared by multiple transactions and clients. After the query queue becomes empty, the entity query associated with this queue is rerun and new results are added to the queue. A query queue is uniquely identified by the entity query string and parameters. There is only one instance for each unique query queue in one ObjectGrid instance. See the EntityManager API documentation for additional information.
Query queue example
The following example shows how query queue can be used.
/** * 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(); }
The previous example first creates a QueryQueue with a entity query string, "SELECT t FROM Task t WHERE t.type=?1 AND t.status=?2". Then it sets the parameters for the QueryQueue object. This query queue represents all "unassigned" tasks of the type "question". The QueryQueue object is very similar to an entity Query object.
After the QueryQueue is created, an entity transaction is started and the getNextEntity method is invoked, which retrieves the next available entity with a timeout value set to 10 seconds. After the entity is retrieved, it is processed in the assignTask method. The assignTask modifies the Task entity instance and changes the status to "assigned" which effectively removes it from the queue since it no longer matches the QueryQueue's filter. Once assigned, the transaction is committed.
From this simple example, you can see a query queue is similar to an entity query. However, they differ in the following ways:
- Entities in the query queue can be retrieved in an iterative manner. The user application decides the number of entities to be retrieved. For example, if QueryQueue.getNextEntity(timeout) is used, only one entity is retrieved, and if QueryQueue.getNextEntities(5, timeout) is used, 5 entities are retrieved. In a distributed environment, the number of entities directly decides the number of bytes to be transferred from the server to client.
- When an entity is retrieved from the query queue, a U lock is placed on the entity so no other transactions can access it.
Retrieve entities in a loop
You can retrieve entities in a loop. An example that illustrates how to get all the unassigned, question type tasks completed follows.
/** * 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); }
If there are 10 unassigned question-type tasks in the entity map, you might expect that we will have 10 entities printed to the console. However, if this example is run, we will see the program never exits, which might be contrary to what you assumed.
When a query queue is created and the getNextEntity is called, the entity query associated with the queue is executed and the 10 results are populated into the queue. When getNextEntity is called, an entity is taken off the queue. After 10 getNextEntity calls are executed, the queue is empty. The entity query will automatically re-run. Since these 10 entities still exist and match the query queue's filter criteria, they are populated into the queue again.
If the following line is added after the println() statement, we will see only 10 entities printed.
em.remove(nextTask);
For information on using SessionHandle with QueryQueue in a per-container placement deployment, read about SessionHandle integration.
Query queues deployed to all partitions
In a distributed eXtreme Scale, a query queue can be created for one partition or all partitions. If a query queue is created for all partitions, there will be one query queue instance in each partition.
When a client tries to get the next entity using the QueryQueue.getNextEntity or QueryQueue.getNextEntities method, the client sends a request to one of the partitions. A client sends peek and pin requests to the server:
- With a peek request, the client sends a request to one partition and the server returns immediately. If there is an entity in the queue, the server sends a response with the entity; if there is not, the server sends a response with no entity. In either case, the server will return immediately.
- With a pin request, the client sends a request to one partition and the server waits until an entity is available. If there is an entity in the queue, the server sends a response with the entity immediately; if there is not, the server waits on the queue until either an entity is available or the request times out.
An example of how an entity is retrieved for a query queue which is deployed to all partitions (n) follows:
- When a QueryQueue.getNextEntity or QueryQueue.getNextEntities method is called, the client picks a random partition number from 0 to n-1.
- The client sends peek request to the random partition.
- If an entity is available, the QueryQueue.getNextEntity or QueryQueue.getNextEntities method exits by returning the entity.
- If an entity is not available and is not the last unvisited partition, the client sends a peek request to the next partition.
- If an entity is not available and it is the last unvisited partition, the client instead sends a pin request.
- If the pin request to the last partition times-out and there is still no data available, the client will make a last effort by sending peek request to all partitions serially one more round. Therefore, if any entity is available in the previous partitions, the client will be able to get it.
Subset entity and no-entity support
The method to create a QueryQueue object in the entity manager follows:
public QueryQueue createQueryQueue(String qlString, Class entityClass);
The result in the query queue should be projected to the object defined by the second parameter to the method, Class entityClass.
If this parameter is specified, the class must have the same entity name as specified in the query string. This is useful to project an entity into a subset entity. If a null value is used as the entity class, then the result will not be projected. The value stored in the map will be in a entity tuple format.
Client-side key collision
In distributed eXtreme Scale environment, query queue is only supported for eXtreme Scale maps with pessimistic locking mode. Therefore, there is no near cache on the client side. However, a client could have data (key and value) in the transactional map. This potentially could lead to a key collision when an entity retrieved from the server share the same key as an entry already in the transactional map.
When a key collision happens, the eXtreme Scale client run time uses the following rule to either throw an exception or silently override the data.
- If the collided key is the key of the entity specified in the entity query associated with the query queue, then an exception is thrown. In this case, the transaction is rolled back, and the U lock on this entity key will be released on the server side.
- Otherwise, if the collided key is the key of the entity association, the data in the transactional map will be overridden without warning.
The key collision only happens when there is a data in the transactional map. In other words, it only happens when a getNextEntity or getNextEntities call is called in a transaction which has already been dirtied (a new data has been inserted or a data has been updated). If an application does not want a key collision happen, it should always call getNextEntity or getNextEntities in a transaction which has not been dirtied.
Client failures
After a client sends a getNextEntity or getNextEntities request to the server, the client could fail as follows:
- The client sends a request to the server and then goes down.
- The client gets one or more entities from the server and then goes down.
In the first case, the server discovers that the client is going down when it tries to send back the response to the client. In the second case, when the client gets one or more entities from the server, an X lock is placed on these entities. If the client goes down, the transaction will eventually time out, and the X lock will be released.
Query with ORDER BY clause
Generally, query queues do not honor the ORDER BY clause. If you call getNextEntity or getNextEntities from the query queue, there is no guarantee the entities are returned according to the order. The reason is that the entities cannot be ordered across partitions. In the case that the query queue is deployed to all partitions, when a getNextEntity or getNextEntities call is executed, a random partition is picked to process the request. Therefore, the order is not guaranteed.
ORDER BY is honored if a query queue is deployed to a single partition.
For more information see EntityManager Query API.
One call per transaction
Each QueryQueue.getNextEntity or QueryQueue.getNextEntities call retrieves matched entities from one random partition. Applications should call exactly one QueryQueue.getNextEntity or QueryQueue.getNextEntities on one transaction. Otherwise eXtreme Scale could end up touching entities from multiple partitions, causing an exception to be thrown at the commit time.
Parent topic:
Cache objects and their relationships (EntityManager API)
Related concepts
EntityManager in a distributed environment
Interacting with EntityManager
EntityManager fetch plan support
EntityManager interface performance impact
Related tasks
Entity manager tutorial: Overview
Related reference