[Logo] Terracotta Discussion Forums
  [Search] Search   [Recent Topics] Recent Topics   [Members]  Member Listing   [Groups] Back to home page 
[Register] Register / 
[Login] Login 
[Expert]
background update  XML
Forum Index -> Ehcache
Author Message
nopte

journeyman

Joined: 05/08/2011 14:01:33
Messages: 12
Offline

Hi,
I'm new to ehcache and I'm looking for a way to update eternal cache entries asynchronously, using a jdk5+ ThreadPool (or anything else). That way, no http request is blocked; my users (and Google) are happy.
The closest thing I found is extending an UpdatingSelfPopulatingCache.
I'll have to manage my Futures so that the same update can't be triggered twice at the same time. Ideally, I would find a way to prevent it on the entire cluster.

I have missed something obvious ? Does this request make sense ?

Thx in advance,

Olivier
alexsnaps

consul

Joined: 06/19/2009 09:06:00
Messages: 481
Offline

I don't think UpdatingSelfPopulatingCache will do what you want, or at least not out of the box. Being based on the BlockingCache blocking policy, it also requires the same thread hitting the null value on get to do the subsequent put to release the segment/key. If you plan on using another thread to do the put, this will not work. Besides, all threads coming in during the refresh time will also be blocked.
You probably can still use it to put the keys to be updated on a Queue and have those elements being consumed by as many threads as required.
Basically UpdatingCacheEntryFactory.updateEntryValue would simply put the key on say a j.u.c.LinkedBlockingQueue, while one or more threads would take from it, retrieve the values to the key from the underlying system of record and update the cache asynchronously.
Technically though, you could simply decorate your cache with your own decorator, as you do not need any of the locking capabilities of BlockingCache, as far as I can tell at least. All you need to do is intercept get() calls to the cache and add the key to the queue when an update is required (based on Element.getCreationTime() ?!)...

Alex Snaps (Terracotta engineer)
nopte

journeyman

Joined: 05/08/2011 14:01:33
Messages: 12
Offline

Thx Alex,

What's odd is that another thread just popped in about the same request.
I think I'll stick with a BlockingCache because I want the blocking feature on initial creation.
And I'll add a TTU (Time To Update) in the entries. I think I'll also have to manage flag like "duringUpdate".
I invite you to read the other forum thread and I'll post here when I'm done.

What surprises me is that, in this age of SEO frenzy, this feature hasn't been requested strongly enough to have it in the core.

Thx again

Olivier
nopte

journeyman

Joined: 05/08/2011 14:01:33
Messages: 12
Offline

Just to give my humble implementation (and to get experts' approval), here's what I came up with:
Code:
 public class BackgroundUpdatingCache extends SelfPopulatingCache {
     private static final Log log = LogFactory.getLog(BackgroundUpdatingCache.class);
 	
 	private CacheEntryFactory underlyingFactory;
 	private ExecutorService executor;
 	private int timeToUpdate;
 	
     public BackgroundUpdatingCache(Ehcache cache, final CacheEntryFactory factory, ExecutorService threadPool, final int timeToUpdate) throws CacheException {
 		super(cache, new CacheEntryFactory() {
 			@Override
 			public Object createEntry(Object key) throws Exception {
 				Object entry = factory.createEntry(key);
 				return new UpdatingElement((Serializable) key, entry);
 			}
 		});
 		this.underlyingFactory = factory;
 		this.executor = threadPool;
 		this.timeToUpdate = timeToUpdate;
 	}
 
 	@Override
 	public Element get(Object key) throws LockTimeoutException {
 		final UpdatingElement element = (UpdatingElement) super.get(key);
 		if (element != null) {
 			if (!element.isDuringUpdate()) {
 				//-------------- is an update needed ? ----------------//
 				long nextUpdateTime = element.getLatestOfCreationAndUpdateTime() + TimeUtil.toMillis(timeToUpdate);
 				boolean updateIsNeeded = System.currentTimeMillis() > nextUpdateTime;
 				//--------------- actual async update -----------------//
 				if (updateIsNeeded) {
 					element.setDuringUpdate(true);
 					Runnable runnable = new Runnable() {
 						@Override
 						public void run() {
 							try {
 								Serializable key = element.getKey();
 								Object entry = underlyingFactory.createEntry(key);
 								UpdatingElement element = new UpdatingElement(key, entry); // we can't change the value to the current Element
 								put(element);
 							} catch (Exception ex) {
 								log.info("Error while recreation the Element for the key: " + element.getKey(), ex);//LogUtils.logExceptionCausesInfo(ex, log);
 							} finally {
 								element.setDuringUpdate(false);
 							}
 						}
 					};
 					executor.execute(runnable);
 				}
 			}
 		}
 		return element;
 	}
 	
 	
 	public static class UpdatingElement extends Element {
 		private AtomicBoolean duringUpdate = new AtomicBoolean();
 		
 		public UpdatingElement(Serializable key, Object value) {
 			// we unwraps the value out of an eventual Element
 			this(key, (Serializable) (value instanceof Element ? value = ((Element) value).getObjectValue() : value));
 		}
 		public UpdatingElement(Serializable key, Serializable value) {
 			super(key, value);
 		}
 		
 		public boolean isDuringUpdate() {
 			return duringUpdate.get();
 		}
 		public void setDuringUpdate(boolean value) {
 			duringUpdate.set(value);
 		}
 	}
 }
 


As you can see, I've kept the sync code light (too light ?)
alexsnaps

consul

Joined: 06/19/2009 09:06:00
Messages: 481
Offline

One thing that's sad imho is the blocking behavior you inherit from BlockingCache, but you might be using this as well to populate missing entries...
Another issue I see is that this is currently racy... The problem is the check for the isDuringUpdate()... Many threads might evaluate that to false, while a they will all race to update the element from there on.
You probably could do a getAndSet on the AtomicBoolean, if it returns true, it means some other thread already set it, so that
Code:
if(element.setDuringUpdate(true) == false) { // schedule update }

That's of course if setDuringUpdate() returns atomicBoolean.getAndSet()
This still doesn't fix all races, as you get ABA problem, where the thread actually did finish updating between the element.getLatestOfCreationAndUpdateTime() call and the element.setDuringUpdate(true) == false check, that would pass...

Also, finally, this would only work with onHeap caches on a single VM. It is worth mentioning that with ehcache you are strongly encouraged to put back in the cache on any change! Indeed, for any other use case than on heap caching, your code wouldn't work anymore as you couldn't be sure that the element wasn't faulted to disk, offHeap or wherever where the actual reference would be different (copyOnRead caches would have the same issue)...

Alex Snaps (Terracotta engineer)
nopte

journeyman

Joined: 05/08/2011 14:01:33
Messages: 12
Offline

Thx a lot, the code is a lot better with your change.
I know that this solution isn't perfect, but I really wish it was in the core.
It seems such a basic requirement in this days where response time (and SEO) is so important.
rajoshi

seraphim

Joined: 07/04/2011 04:36:10
Messages: 1491
Offline

Issue seems to be resolved.Please let us know if more information is required.

Rakesh Joshi
Senior Consultant
Terracotta.
nopte

journeyman

Joined: 05/08/2011 14:01:33
Messages: 12
Offline

When you say "resolved", do you mean "integrated in the standard distribution" ?
If not, it's not a problem, the code pasted in this thread works like a charm on my production servers.
 
Forum Index -> Ehcache
Go to:   
Powered by JForum 2.1.7 © JForum Team