5 min read

Removal versus eviction

Setting an eviction policy on a BackingMap makes more sense now that we’re using a Loader. Imagine that our cache holds only a fraction of the total data stored in the database. Under heavy load, the cache is constantly asked to hold more and more data, but it operates at capacity. What happens when we ask the cache to hold on to one more payment? The BackingMap needs to remove some payments in order to make room for more.

BackingMaps have three basic eviction policies: LRU (least-recently used), LFU (least-frequently used), and TTL (time-to-live). Each policy tells the BackingMap which objects should be removed in order to make room for more. In the event that an object is evicted from the cache, its status in the database is not changed. With eviction, objects enter and leave the cache due to cache misses and evictions innumerable times, and their presence in the database remains unchanged.

The only thing that affects an object in the database is an explicit call to change (either persist or merge) or remove it as per our application. Removal means the object is removed from the cache, and the Loader executes the delete from SQL to delete the corresponding row(s) from the database. Your data is safe when using evictions. The cache simply provides a window into your data. A remove operation explicitly tells both ObjectGrid and the database to delete an object.

Write-through and write-behind

Getting back to the slow down due to the Loader configuration, by default, the Loader uses write-through behavior:

IBM WebSphere eXtreme Scale 6

Now we know the problem. Write-through behavior wraps a database transaction for every write! For every ObjectGrid transaction, we execute one database transaction. On the up side, every object assuredly reaches the database, provided it doesn’t violate any relational constraints. Despite this harsh reaction to write-through behavior, it is essential for objects that absolutely must get to the database as fast as possible. The problem is that we hit the database for every write operation on every BackingMap. It would be nice not to incur the cost of a database transaction every time we write to the cache.

Write-behind behavior gives us the help we need. Write-behind gives us the speed of an ObjectGrid transaction and the flexibility that comes with storing data in a database:

IBM WebSphere eXtreme Scale 6

Each ObjectGrid transaction is now separate from a database transaction. BackingMap now has two jobs. The first job is to store our objects as it always does. The second job is to send those objects to the JPAEntityLoader. The JPAEntityLoader then generates SQL statements to insert the data into a database.

We configured each BackingMap with its own JPAEntityLoader. Each BackingMap requires its own Loader because each Loader is specific to a JPA entity class. The relationship between JPAEntityLoader and a JPA entity is established when the BackingMap is initialized. The jpaTxCallback we specified in the ObjectGrid configuration coordinates the transactions between ObjectGrid and a JPA EntityManager.

In a write-through situation, our database transactions are only as large as our ObjectGrid transactions. Update one object in the BackingMap and one object is written to the database. With write-behind, our ObjectGrid transaction is complete, and our objects are put in a write-behind queue map. That queue map does not immediately synchronize with the database. It waits for some specified time or for some number of updates, to write out its contents to the database:

IBM WebSphere eXtreme Scale 6

We configure the database synchronization conditions with the setWriteBehind(“time;conditions”) method on a BackingMap instance. Programmatically the setWriteBehind method looks like this:

BackingMap paymentMap = grid.getMap("Payment");
paymentMap.setLoader(new JPAEntityLoader());
paymentMap.setWriteBehind("T120;C5001");

The same configuration in XML looks like this:

<backingMap name="Payment" writeBehind="T120;C5001"
pluginCollectionRef="Payment" />

Enabling write-behind is as simple as that. The setWriteBehind method takes one string parameter, but it is actually a two-in-one. At first, the T part is the time in seconds between syncing with the database. Here, we set the payment BackingMap to wait two minutes between syncs. The C part indicates the number (count) of changes made to the BackingMap that triggers a database sync.

Between these two parameters, the sync occurs on a whichever comes first basis. If two minutes elapse between syncs, and only 400 changes (persists, merges, or removals) have been put in the write-behind queue map, then those 400 changes are written out to the database. If only 30 seconds elapse, but we reach 5001 changes, then those changes will be written to the database.

ObjectGrid does not guarantee that the sync will take place exactly when either of those conditions is met. The sync may happen a little bit before (116 seconds or 4998 changes) or a little bit later (123 seconds or 5005 changes). The sync will happen as close to those conditions as ObjectGrid can reasonably do it.

The default value is “T300;C1000”. This syncs a BackingMap to the database every five minutes, or 1000 changes to the BackingMap. This default is specified either with the string “T300;C1000″ or with an empty string (” “). Omitting either part of the sync parameters is acceptable. The missing part will use the default value. Calling setWriteBehind(“T60”) has the BackingMap sync to the database every 60 seconds, or 1000 changes. Calling setWriteBehind(“C500”) syncs every five minutes, or 500 changes.

Write-behind behavior is enabled if the setWriteBehind method is called with an empty string. If you do not want write-behind behavior on a BackingMap, then do not call the setWriteBehind method at all.

A great feature of the write-behind behavior is that an object changed multiple times in the cache is only written in its final form to the database. If a payment object is changed in three different ObjectGrid transactions, the SQL produced by the JPAEntityLoader will reflect the object’s final state before the sync. For example:

entityManager.getTransaction().begin();
Payment payment = createPayment(line, batch);
entityManager.getTransaction().commit();
some time later...
entityManager.getTransaction().begin();
payment.setAmount(new BigDecimal("44.95"));
entityManager.getTransaction().commit();
some time later...
entityManager.getTransaction().begin();
payment.setPaymentType(PaymentType.REAUTH);
entityManager.getTransaction().commit();

With write-through behavior, this would produce the following SQL:

insert into payment (id, amount, batch_id, card_id, payment_type) 
values (12345, 75.00, 31, 6087, 'AUTH');
update payment set (id, amount, batch_id, card_id, payment_type)
values (12345, 44.95, 31, 6087, 'AUTH')
where id = 12345;
update payment set (id, amount, batch_id, card_id, payment_type)
values (12345, 44.95, 31, 6087, 'REAUTH')
where id = 12345;

Now that we’re using write-behind, that same application behavior produces just one SQL statement:

insert into payment (id, amount, batch_id, card_id, payment_type) 
values (12345, 44.95, 31, 6087, 'REAUTH');

LEAVE A REPLY

Please enter your comment!
Please enter your name here