Avoiding Unintended Locking When Retrieving Related Objects in Entry Processors

Introduction
In the post I wrote on Partition Level Transactions, I showed how we could use data affinity (see here) to update multiple caches in a single atomic operation. This is a very powerful feature and I thought I would just add a bit more to the conversation around this.

When you implement key association, you get “related” entries always stored in the same partitions. E.g. you may have key association between a Customer and Orders on the customerId attribute. In this case you will always get the Orders for customer 1 in the same partition as the customer object “Customer 1”.

As described in the post above you can use “Partition Level Transactions” to update related cache objects in this way. But the other more obvious advantage is that if you are running an Entry Processor (EP) on Order 200 for Customer 1, you know that Customer 1 is in the same partition and  you can get directly access the related object (Customer) via the BackingMapManagerContext.getBackingMapEntry() method.

This means direct memory access to the Objects rather than having to get them from another cluster member. Very fast!

The Potential Unintended Side Effect
On first glance the following code (from the previous blogs EP) makes sense if you just wanted to access the Customer object while running an EP against an Order entry:

@Override
public Object process(Entry entry)
    {
    ...
    BackingMapManagerContext ctx = ((BinaryEntry) entry).getContext();

    // Get the Customer but we dont want to update. This will work, but the side effect
    // is the Customer key will be added to "sandbox" of changes and locked.
    BinaryEntry customerEntry =
            (BinaryEntry) orderEntry.getContext().getBackingMapContext(Customer.CACHENAME)
                .getBackingMapEntry(ctx.getKeyToInternalConverter()
                    .convert(newOrder.getCustomerId()));

    Customer thisCustomer = ((Customer) customerEntry.getValue());
    ...

This will certainly work, but remember that calling getBackingMapEntry() adds the retrieved BinaryEntry to the “sandbox” of changes, which includes also applying the EP locking semantics to that key. So even if you wanted to just read the value, you are locking it for the duration of the EP as well.

This would then queue any other updates via EP’s to the Customer which is not what we intend in this case.

How to Avoid Locking When Getting Related Objects

This is where you need to subtly change the code to access the BackingMap directly (which only works for caches related via key association) to get the required Customer object. The following code shows how to get an Object, which is related object using getBackingMap().get() instead of getBackingMapEntry() in the case where you don’t want to update the related object:

@Override
public Object process(Entry entry)
{
...
    BackingMapManagerContext ctx = ((BinaryEntry) entry).getContext();

    // Get the Customer but we dont want to update. This will work, and no side effect of
    // of locking.
    Binary binCustomer =
           (Binary)ctx.getBackingMapContext(Customer.CACHENAME)
               .getBackingMap().get(ctx.getKeyToInternalConverter()
                   .convert(newOrder.getCustomerId()));

    // Because we are getting a Binary instead of BinaryEntry, we need to deserialize with the
    // serlializer for the Service.
    Customer c = (Customer) ExternalizableHelper
      .fromBinary(binCustomer, ctx.getCacheService().getSerializer());

...

Conclusion
So key things to remember are:

  • Use getBackingMap().get() if you just want to retrieve related cache objects you know are in the same partition and don’t want to update them.
  • Use getBackingMapEntry() if you want to update a related entry using Partition Level Transactions.

Enjoy you holiday break and see you next year.

Advertisements
This entry was posted in Uncategorized and tagged , , . Bookmark the permalink.

5 Responses to Avoiding Unintended Locking When Retrieving Related Objects in Entry Processors

  1. Island Chen says:

    Very useful!

    I guest that’s the reason why we got poor performance now!

    • Thanks. Basically it depends on your use-case. If you want partition level transactions then use getBackingMapEntry() , otherwise use getBackingMap().get().

      Regards

      Tim

      • Island Chen says:

        Yes, we are using EP, and we need to update some entries based on rules read form other entries, and we used getBackingMapEntry() for all entries before. Now we’ll try to use getBackingMapEntry() or getBackingMap().get() for different entries.

  2. Ramya B says:

    This is very helpful. I have a question more basic though.
    I tried to typecast the Entry object to a BinaryEntry object but I receive a classcastexception continuously. To allow this typecast, would there be any other prerequisites? The caches I am working with are all DistributedCaches.

    • Hi.
      At which point to you get the exception?
      Is it at the BackingMapManagerContext ctx = ((BinaryEntry) entry).getContext();
      Or are you casting to BinaryEntry when you may need to cast to Binary.
      Tim

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s