[Java programming language only]

Map entry locks with query and indexes

The eXtreme Scale Query APIs and the MapRangeIndex indexing plug-in interact with locks. You can increase concurrency and decrease deadlocks when you are using the pessimistic locking strategy for maps.

Overview

The ObjectGrid Query API allows SELECT queries over ObjectMap cache objects and entities. When a query is run, the query engine uses a MapRangeIndex when possible to find matching keys that match values in the query's WHERE clause or to bridge relationships. When an index isn't available, the query engine will scan each entry in one or more maps to find the appropriate entries. Both the query engine and index plug-ins acquire locks to verify consistent data, depending on the locking strategy, transaction isolation level, and transaction state.

Locking with the HashIndex plug-in

The eXtreme Scale HashIndex plug-in allows finding keys that are based on a single attribute that is stored in the cache entry value. The index stores the indexed value in a separate data structure from the cache map. The index validates the keys against map entries before returning to the user to try to achieve an accurate result set. When you are using the pessimistic lock strategy and the index against a local ObjectMap instance (versus a client/server ObjectMap), the index acquires locks for each matching entry. When you are using optimistic locking or a remote ObjectMap, the locks are always immediately released.

The type of lock that is acquired depends upon the forUpdate argument that is passed to the ObjectMap.getIndex method. The forUpdate argument specifies the type of lock that the index should acquire. If false, a shareable (S) lock is acquired and if true, an upgradeable (U) lock is acquired.

If the lock type is shareable, the transaction isolation setting for the session is applied and affects the duration of the lock. See the transaction isolation topic for details on how transaction isolation is used to add concurrency to applications.

Shared locks with query

The eXtreme Scale query engine acquires S locks when needed to introspect the cache entries to discover if they satisfy the query's filter criteria. When you are using repeatable read transaction isolation with pessimistic locking, the S locks are only retained for the elements that are included in the query result. The locks are released for any entries that are not included in the result. If you are using a lower transaction isolation level or optimistic locking, the S locks are not retained.

Shared locks with client to server query

When you are using the eXtreme Scale query from a client, the query typically runs on the server unless all of the maps or entities referenced in the query are local to the client, for example, a client-replicated map or a query result entity. All queries that run in a read/write transaction retain S locks. If the transaction is not a read/write transaction, then a session is not retained on the server and the S locks are released.

A read/write transaction is only routed to a primary partition and a session is maintained on the server for the client session. A transaction can be promoted to read/write under the following conditions:
  1. Any map that is configured to use pessimistic locking is accessed with the ObjectMap get and getAll API methods or the EntityManager.find methods.
  2. The transaction is flushed, causing updates to be sent to the server.
  3. Any map that is configured to use optimistic locking is accessed with the ObjectMap.getForUpdate or EntityManager.findForUpdate method.

Upgradeable locks with query

Shareable locks are useful when concurrency and consistency are important. It guarantees that an entry's value does not change for the life of the transaction. No other transaction can change the value while any other S locks are held, and only one other transaction can establish an intent to update the entry.

Upgradeable locks are used to identify the intent to update a cache entry when using the pessimistic lock strategy. It allows synchronization between transactions that want to modify a cache entry. Transactions can still view the entry using an S lock, but other transactions are prevented from acquiring a U lock or an X lock. In many scenarios, acquiring a U lock without first acquiring an S lock is necessary to avoid deadlocks. See the Pessimistic Locking Mode topic for common deadlock examples.

The ObjectQuery and EntityManager Query interfaces provide the setForUpdate method to identify the intended use for the query result. Specifically, the query engine acquires U locks instead of S locks for each map entry that is involved in the query result:
ObjectMap orderMap = session.getMap("Order");
ObjectQuery q = session.createQuery("SELECT o FROM Order o WHERE o.orderDate=?1");
q.setParameter(1, "20080101");
q.setForUpdate(true);
session.begin();
// Run the query.  Each order has  U lock
Iterator result = q.getResultIterator();
// For each order, update the status.
while(result.hasNext()) {
    Order o = (Order) result.next();
    o.status = "shipped";
    orderMap.update(o.getId(), o);
}
// When committed, the 
session.commit();
Query q = em.createQuery("SELECT o FROM Order o WHERE o.orderDate=?1");
q.setParameter(1, "20080101");
q.setForUpdate(true);
emTran.begin();
// Run the query.  Each order has  U lock
Iterator result = q.getResultIterator();
// For each order, update the status.
while(result.hasNext()) {
    Order o = (Order) result.next();
    o.status = "shipped";
}
tmTran.commit();

When the setForUpdate attribute is enabled, the transaction is automatically converted to a read/write transaction and the locks are held on the server as expected. If the query cannot use any indexes, then the map must be scanned which results in temporary U locks for map entries that do not satisfy the query result. U locks are held for entries that are included in the result.