Introduction
Oracle Coherence provides the ability to evolve your data objects over time, without an outage in your data grid, or affecting applications that access the data in the grid. Essentially, multiple versions of a particular cache object, say a Customer, can exist in a data grid at the same time while you perform an upgrade. Also multiple clients that understand the different versions, can access this data as well.

This does require a little bit of forethought and planning, but its well worth it. Using the Evolvable interface along with the PortableObject interface plus a methodical approach will get us there.

If you Google “using evolvable portable objects”, you can see a number of great posts about this feature already, but what I wanted to do is explain how you could do this with a simple working example.

The Scenario
In my example I will have a Customer object that starts off with four attributes: customerId, name, region and creditLimit. What we want to do is to upgrade the application, without an application or grid outage, to include loyalty card functionality which requires an additional attribute (loyaltyCard) on the Customer object.
(This is a simple change, but the concepts can be applied to much more complex changes!)

I’m also using the new @Portable annotation to make them use the Portable Object Format (POF) serialisation. You could just use Serializable, but its good practice to get used to using POF as its smaller, faster and portable across Java,.NET and C++.

Setup two different directory structures (or jars), one with the v1 classes and the other with the v2 classes.

One by one, we shutdown the cache servers running v1 code (checking StatusHA in between) and start them up using the new v2 classes. This will update the implementation class version.

When all cache servers are running with v2 code we are able to run v2 clients against the data grid. As v2 clients access data and deserialize and serialize, they will save data in the new data version.

During this time, both old clients and new clients can access the data, with older clients not aware of the new atrributes, but ensuring any “future data” (that was set by v2 clients) they don’t know about is preserved.

When all objects have both the data version and implementation version set to v2, our upgrade is complete and we can then retire our v1 clients.

The diagram below outlines this approach.

Data Version and Impl Version
It is important to note that even when all cache servers are running v2 of the Person class (Impl version), that only until the data is deserialized and then serialized will the data version be updated to equal the Impl version. Normal partition transfers are binary, so they don’t result in data version changes. In my example, after we have upgraded all cache servers to v2 we will run some Entry Processors (EP) that will modify data and as part of the process upgrade the data version to match the Impl version.

The really cool thing is that we can also still run an EP that only understands v1 classes and data, and the v2 data (with extra loyaltyCard attribute) will be maintained and the data versions upgraded to v2.
Enough explaining, lets see this in action.

The Example
I’ll post a link to the example source code below, but effectively i’m using ant for all cache servers and clients. This allows it to be run on any O/S that supports ant and Coherence. Just remember to set the COHERENCE_HOME environment variable before you run ant.

If you run ant without any arguments from the directory with build.xml in, you will see the following options:

Next, in another terminal, run ant populate-cache which adds 10,000 objects into the cache using only V1 classes.

When the populate finishes, run ant customer-count to startup a small GUI that shows the total customers and the different Data and Impl versions. You can see that ad the moment all the data is V1 and all the classes are V1.

Initial Customer Counts

Now we are going to shutdown each v1-cache server in turn and startup a v2-cache server in its place. In normal operation, you would check the StatusHA value to ensure that its safe to do the next cache server. E.g. Coherence has rebalanced partitions. (For more information in StatusHA, see here).

Use CTRL-C to kill the first cache server. Wait a couple of seconds and then run ant cache-server-v2. Look at the GUI and you will see that approx 1/3 of the data is now in V2 Impl. This is because with 3 cache servers, the data is distributed across all cache servers evenly.

Data Totals After One V2-Cach Server Started

Now do the same for the second and third cache server terminals. Use CTRL-C, wait for a few seconds, start v2-cache server using ant cache-server-v2. Note the changes in the totals in between each shutdown and startup. The resulting GUI show now look like the following with all Impl versions now V2.

Totals After All Cache Server V2

Now the cache servers are updated to V2 we need to upgrade all the data to V2. As mentioned previously, when the data is deserialized and serialized, it data version will be upgraded to match the Impl (class) version.

To do this, carry out the following

On a separate terminal run ant customer-card-update. This will display a small GUI through which you can run an EntryProcessor (below) to update the new loyaltyCard attribute of customers of a particular region. (There are 25% of all customers in each region.)

When you click any of the buttons, the EntryProcessor will run against the V2 cache servers correctly, without causing any data loss.

You could also run this EntryProcessor while there are V1 and V2 cache servers running.

Conclusion
Hopefully through this small example, you’ve now got an idea of how you can evolve your cache objects online. In my example I’ve just used standard in memory backing maps (cache storage). The same principles can be applied if your caches have read-write backing maps associated with them, e.g. read from database on miss, write to database via JPA etc. Again, there is a bit of discipline needed for this, but the rewards are great.

Blogroll

Copyright (c) 2017 Tim Middleton and other contributors. All Rights Reserved. The views expressed in this blog are our own and do not necessarily reflect the views of Oracle Corporation. All content is provided on an 'as is' basis, without warranties or conditions of any kind, either express or implied, including, without limitation, any warranties or conditions of title, non-infringement, merchantability, or fitness for a particular purpose. You are solely responsible for determining the appropriateness of using or redistributing and assume any risks.