Strange Caching Behavior

267 views
Skip to first unread message

mbell697

unread,
Sep 3, 2012, 1:51:26 PM9/3/12
to eb...@googlegroups.com
I've run into a bit of a quandary and am hoping for some insight into, what I believe, is the longevity/sharability of the L1 cache.

I've noticed that if I make a change to the database manually, outside Ebean or from another server also using Ebean, the results do update as a result of queries, that is looking at a paginglist of entites I see the updates, however on a page getting the entity via Ebean.find() by ID the results aren't updated, implying the bean is coming from a cache somewhere.    

At first I assumed that the L2 cache was enabled by default and was to blame but I haven't set @CacheStrategy anywhere implying it should be disabled.

This left me with the bean coming from the L1 cache somehow, but even if I completely log out (destroying any and all session data), or login from a different machine (brand new session), I find the same caching behavior.  I'm starting to wonder if the L1 cache is actually living not only across requests but also is used universally across users.  

Its not clear to me exactly when the L1 cache expires from the documentation, it does indicate it lives through a transaction but there are certainly no transactions open for this period of time, after 10-15 minutes ish the caching behavior disappears.  The docs also indicate that it will live on for lazy loading purposes but I have no idea when it 'ends' (when all beans in the cache are garbage collected?).  

This issue may be caused by the usage of JSF's ViewScope in combination with not being able to guarantee when a view will be destroy (sending the entity bean siting in the controller to garbage collection).  Is the reference to a bean sitting in an unterminated view scope causing the L1 to stick around for all users?

Even further this would seem to imply that even if everything were completely stateless if two requests were in flight they would share a L1 cache?  If you had a string of requests for the same entity on multiple servers such that the entity kept getting accessed, perpetuating the L1 cache I would think the caches could get wildly out of sync, which brings up my next concern, I intentionally triggered an optimistic lock exception on the entity I was testing this issue with and the caching effect continued after.  I would have thought at the very least an OLE would have flushed all caches.  If it doesn't I can easily see issues with multiple servers handling requests that keep the L1 cache alive never getting flushed and brought up to date.

Any thoughts on whats going on here?

Cheers,

Mark


Rob Bygrave

unread,
Sep 3, 2012, 5:15:42 PM9/3/12
to eb...@googlegroups.com
The L1 cache is also known as the persistence context - if you are debugging through the code the interface in question is PersistenceContext and implementation being DefaultPersistenceContext.


>> L1 cache is actually living not only across requests but also is used universally across users. 

No. The PersistenceContext does not live across requests unless the beans themselves live across requests (like using your own caching layer or something like that). So the beans are being stored in session or something like that the persistenceContext will not live across requests.



>> but also is used universally across users. 

No. If beans are shared across threads/requests then sure but you'd have to code that. The L2 cache doesn't maintain persistence context objects but just the raw data.



>> getting the entity via Ebean.find() by ID the results aren't updated

I'd suggest you attach a debugger and step through the find().

mbell697

unread,
Sep 3, 2012, 5:47:03 PM9/3/12
to eb...@googlegroups.com
I've set breakpoints inside DefaultServer.java on in the function with the following signature:

private <T> T findIdCheckPersistenceContextAndCache(Transaction transaction, BeanDescriptor<T> beanDescriptor, SpiQuery<T> query)

2 breakpoints:  

1) inside the check for an active transaction ( if (t != null) )

This breakpoint is never hit in my test, which makes sense since Ebean.find(Entity.class,id); is never called inside a transaction.

2) Inside the If statement:

Object cachedBean = beanDescriptor.cacheGetBean(query.getId(), vanilla, query.isReadOnly());
    if (cachedBean != null) {
      //break here

If I'm understanding the code correctly, the second bit is looking in the bean cache.  

This break point isn't hit at all when just loading the page in question (each load is triggering a call to Ebean.find(Entity.class, id).  However after triggering an action which saves the entity via Ebean.save(entity), each GET request to load the page does hit this breakpoint which implies (i think) its coming out of the bean cache. 

Stepping into the preceding check: 

if (!beanDescriptor.calculateUseCache(query.isUseBeanCache())) {
      // not using bean cache
      return null;
    }

seems to indicate the bean cache is active.  query.isUseBeanCache() is null but tracing this through to BeanDescriptor.java there is a beanCache setup  which causes the check to pass.

So I guess it appears the L2 bean cache is enabled.  I guess my question then becomes why?  The docs indicate that it is off by default unless @CacheStrategy is set on the entity.  I've searched my entire code base for usages of the work 'cache' with no hits related.

Is there something that would enable it automatically?

Cheers,

Mark

Rob Bygrave

unread,
Sep 4, 2012, 5:47:27 AM9/4/12
to eb...@googlegroups.com
>> Is there something that would enable it automatically?

I don't think so.  There is not a property to turn it globally on (although one will be added in the future). So no, not obvious why the bean cache is on. If you add a breakpoint at BeanDescriptor#cacheInitialise() that might show the cause.

It can be turned on at runtime by code like:

ServerCacheManager serverCacheManager = Ebean.getServerCacheManager();
serverCacheManager.setCaching(UUOne.class, true);

... but you are unlikely to be doing that.   


Cheers,Rob.

mbell697

unread,
Sep 4, 2012, 1:21:29 PM9/4/12
to eb...@googlegroups.com
I believe I've found the culprit.

The following bit of code in BeanDescriptor.java struct me as dangerous since it doesn't make any checks to see if the bean cache was enabled explicitly:

 private ServerCache getBeanCache() {
    if (beanCache == null) {
      beanCache = cacheManager.getBeanCache(beanType);
    }
    return beanCache;
  }

So i set about trying to trigger it.  What I found is that if you get a reference to an entity via Ebean.getReference(Entity.class, id) then later access a field of that entity it triggers a lazy load pathway that calls getBeanCache() creating the cache and causing it to be used for that entity from there on out.  

The stack trace looks like this when calling entity.getSomeField() and breaking in getBeanCache() :

          at com.avaje.ebeaninternal.server.deploy.BeanDescriptor.getBeanCache(BeanDescriptor.java:1016)
 at com.avaje.ebeaninternal.server.deploy.BeanDescriptor.loadFromCache(BeanDescriptor.java:1221)
 at com.avaje.ebeaninternal.server.core.DefaultBeanLoader.refreshBeanInternal(DefaultBeanLoader.java:430)
 at com.avaje.ebeaninternal.server.core.DefaultBeanLoader.loadBean(DefaultBeanLoader.java:401)
 at com.avaje.ebeaninternal.server.core.DefaultServer.loadBean(DefaultServer.java:517)
 at com.avaje.ebean.bean.EntityBeanIntercept.loadBeanInternal(EntityBeanIntercept.java:531)
 at com.avaje.ebean.bean.EntityBeanIntercept.loadBean(EntityBeanIntercept.java:491)
 at com.avaje.ebean.bean.EntityBeanIntercept.preGetter(EntityBeanIntercept.java:623)

Cheers,

Mark

Rob Bygrave

unread,
Sep 5, 2012, 4:37:50 PM9/5/12
to eb...@googlegroups.com

Good work. I didn't get to look at it last night.

Cheers, Rob.

Reply all
Reply to author
Forward
0 new messages