This discussion is archived
1 2 Previous Next 18 Replies Latest reply: Apr 23, 2013 6:17 AM by 922866 RSS

How to avoid Cache misses?

660069 Newbie
Currently Being Moderated
Hi,


Before I explain the problem here's my current setup.

- Distributed/partitioned cache
- Annotated JPA classes
- Backing map linked to an oracle database
- Objects are stored in POF format
- C++ extend client

When I request an item that does not exist in the cache, the JPA magic forms a query and assembles the object and stores that inside the cache.

However if the query returns no results then coherence sends back a cache miss. Our existing object hierarchy can request items that don't exist (this infrastructure is vast and entrenched and changing it is not an option). This blows any near cache performance out of the water.

What I want to do is to intercept a cache miss and store a null object in the cache on that key (by null it will be 4 bytes in length). The client code can interpret the null object as a cache miss and everything will work as usual - however the null object will be stored in the near cache and performance will return.

My problem is, as annotated JPA does all the 'magic', I don't get to intercept if the query returns an empty set. I've tried both map triggers and listeners, however as expected they don't get called as no result set is generated.

Does anyone know of an entry point where I can return an object to coherence in the event of a query returning an empty set. I'd also like the ability to configure this behaviour on a per cache basis.

Any help gratefully received.

Thanks
Rich

Edited by: Rich Carless on Jan 6, 2011 1:56 PM
  • 1. Re: How to avoid Cache misses?
    769299 Explorer
    Currently Being Moderated
    Just thinking off the top of my head...

    Maybe use a customised CacheLoader and/or CacheStore? We use EclipseLink for our JPA connectivity, and it comes with all the source code. All the JPA calls are enclosed in EclipseLinkJPACacheLoader.java and EclipseLinkJPACacheStore.java files. You could customize these files (or whichever CacheStore/Loader you are using) to get the low-level access to all that "JPA magic" you refer to.
  • 2. Re: How to avoid Cache misses?
    660069 Newbie
    Currently Being Moderated
    Unless someone offers a more efficient solution, I have gone with the following.

    If I don't find an object in the cache (and conversely the database) I create a dummy object on the client and place that in the cache. It would be nice to avoid the additional network hops, however this should suffice for now.

    Thanks
    Rich
  • 3. Re: How to avoid Cache misses?
    660069 Newbie
    Currently Being Moderated
    Sorry Stevephe,

    I wrote my reply at the same time as you :)

    I use Eclipselink too, you'll have to forgive my ignorance as I'm a C++ guy and I'm slowly finding my way in java.

    I'm don't think that re-writing EclipseLink is the way to go, sounds like a world of pain.

    Perhaps as you suggest a custom cache loader is closer to what I want.

    Thanks for the reply
    Rich
  • 4. Re: How to avoid Cache misses?
    769299 Explorer
    Currently Being Moderated
    I wasn't suggesting you "re-write EclipseLink". :)

    What I was saying was that the CacheStore and CacheLoader implementations that EclipseLink uses for the Coherence cache integration are provided for free in the "toplink\jlib\toplink-grid-src.zip" file. Simply copy these and use them as a basis for your new custom versions. That way you will avoid that "world of pain" you are keen to avoid. :)
  • 5. Re: How to avoid Cache misses?
    Jonathan.Knight Expert
    Currently Being Moderated
    Hi,

    If you are using 3.6 you can do this by writing a sub-class of JpaCacheStore that implements BinaryEntryStore or a more genric way (which would suit other people who have asked similar questions recently) would be to write an implementation of BinaryEntryStore that wraps another cache store.

    Here is one I knocked up recently...
    package org.gridman.coherence.cachestore;
    
    import com.tangosol.net.BackingMapManagerContext;
    import com.tangosol.net.CacheFactory;
    import com.tangosol.net.DefaultConfigurableCacheFactory;
    import com.tangosol.net.cache.BinaryEntryStore;
    import com.tangosol.net.cache.CacheStore;
    import com.tangosol.run.xml.XmlElement;
    import com.tangosol.util.Binary;
    import com.tangosol.util.BinaryEntry;
    
    import java.util.Set;
    
    public class WrapperBinaryCacheStore implements BinaryEntryStore {
    
        private BackingMapManagerContext context;
    
        private CacheStore wrapped;
    
        public WrapperBinaryCacheStore(BackingMapManagerContext context, ClassLoader loader, String cacheName, XmlElement cacheStoreConfig) {
            this.context = context;
            DefaultConfigurableCacheFactory cacheFactory = (DefaultConfigurableCacheFactory) CacheFactory.getConfigurableCacheFactory();
            DefaultConfigurableCacheFactory.CacheInfo info = cacheFactory.findSchemeMapping(cacheName);
            XmlElement xmlConfig = cacheStoreConfig.getSafeElement("class-scheme");
            wrapped = (CacheStore)cacheFactory.instantiateAny(info, xmlConfig, context, loader);
        }
    
        @Override
        public void erase(BinaryEntry binaryEntry) {
            wrapped.erase(binaryEntry.getKey());
        }
    
        @SuppressWarnings({"unchecked"})
        @Override
        public void eraseAll(Set entries) {
            for (BinaryEntry entry : (Set<BinaryEntry>)entries) {
                erase(entry);
            }
        }
    
        @Override
        public void load(BinaryEntry binaryEntry) {
            Object value = wrapped.load(binaryEntry.getKey());
            binaryEntry.updateBinaryValue((Binary) context.getValueToInternalConverter().convert(value));
        }
    
        @SuppressWarnings({"unchecked"})
        @Override
        public void loadAll(Set entries) {
            for (BinaryEntry entry : (Set<BinaryEntry>)entries) {
                load(entry);
            }
        }
    
        @Override
        public void store(BinaryEntry binaryEntry) {
            wrapped.store(binaryEntry.getKey(), binaryEntry.getValue());
        }
    
        @SuppressWarnings({"unchecked"})
        @Override
        public void storeAll(Set entries) {
            for (BinaryEntry entry : (Set<BinaryEntry>)entries) {
                store(entry);
            }
        }
    
    }
    Using the JPA example from the Coherence 3.6 Tutorial you would configure it like this...
    <distributed-scheme>
        <scheme-name>jpa-distributed</scheme-name>
        <service-name>JpaDistributedCache</service-name>
        <backing-map-scheme>
            <read-write-backing-map-scheme>
                <internal-cache-scheme>
                    <local-scheme/>
                </internal-cache-scheme>
                <cachestore-scheme>
                    <class-scheme>
                        <class-name>org.gridman.coherence.cachestore.WrapperBinaryCacheStore</class-name>
                        <init-params>
                            <init-param>
                                <param-type>com.tangosol.net.BackingMapManagerContext</param-type>
                                <param-value>{manager-context}</param-value>
                            </init-param>
                            <init-param>
                                <param-type>java.lang.ClassLoader</param-type>
                                <param-value>{class-loader}</param-value>
                            </init-param>
                            <init-param>
                                <param-type>java.lang.String</param-type>
                                <param-value>{cache-name}</param-value>
                            </init-param>
                            <init-param>
                                <param-type>com.tangosol.run.xml.XmlElement</param-type>
                                <param-value>
                                    <class-scheme>
                                        <class-name>com.tangosol.coherence.jpa.JpaCacheStore</class-name>
                                        <init-params>
                                            <init-param>
                                                <param-type>java.lang.String</param-type>
                                                <param-value>{cache-name}</param-value>
                                            </init-param>
                                            <init-param>
                                                <param-type>java.lang.String</param-type>
                                                <param-value>com.oracle.handson.{cache-name}</param-value>
                                            </init-param>
                                            <init-param>
                                                <param-type>java.lang.String</param-type>
                                                <param-value>JPA</param-value>
                                            </init-param>
                                        </init-params>
                                    </class-scheme>
                                </param-value>
                            </init-param>
                        </init-params>
                    </class-scheme>
                </cachestore-scheme>
            </read-write-backing-map-scheme>
        </backing-map-scheme>
        <autostart>true</autostart>
    </distributed-scheme>
    As you can see the WrapperBinaryCacheStore takes four cpnstructor parameters (set up in the init-params)
    <li>First is the Backing Map Context
    <li>Second is the ClassLoader
    <li>Third is the cache name
    <li>Fourth is the XML configuration for the cache store you want to wrap

    If the load method of the wrapped cache store returns null (i.e. nothing in the DB matches the key) then instead of returning null, the BinaryEntry is updated with a Binary representing null. Because the corresponding key is now in the cache with a value of null then the cache store will not be called again for the same key.

    Note If you do this and then subsequently your DB is updated with values for the keys thet were previously null (by something other than Coherence) then Coherence will not load them as it is never going to call load for those keys again.

    I have given the code above a quick test and it seems to work fine.

    If you are using 3.5 then you can still do this but you need to use the Coherence Incubator Commons library which has a version of BinaryCacheStore. The code and config will be similar but not identical.

    JK

    Edited by: Jonathan.Knight on Jan 6, 2011 3:50 PM
  • 6. Re: How to avoid Cache misses?
    660069 Newbie
    Currently Being Moderated
    Hi JK,

    Thanks for the very detailed reply.

    Will having the objects stored in POF effect the use of the BinaryEntryStore?

    Thanks
    Rich
    p.s. I am using 3.6

    Edited by: Rich Carless on Jan 6, 2011 4:23 PM
  • 7. Re: How to avoid Cache misses?
    Jonathan.Knight Expert
    Currently Being Moderated
    Rich,

    The BinaryEntryStore works with POF if that is what you are asking; the tests I did used POF.

    JK
  • 8. Re: How to avoid Cache misses?
    660069 Newbie
    Currently Being Moderated
    Thanks.

    I'm going to see if I can get one of these caches up and running.

    I might have a few queries about the config, do you mind if I come back to you if any problems arise?

    Cheers
    Rich
  • 9. Re: How to avoid Cache misses?
    Jonathan.Knight Expert
    Currently Being Moderated
    Hi Rich,

    Any questions ask away. I could say I am happy to help because I love Coherence but really I only do it for the forum points - I dream of one day catching Robbie :-)

    JK
  • 10. Re: How to avoid Cache misses?
    660069 Newbie
    Currently Being Moderated
    :)

    Ok well you're close with this one.

    It nearly work, however throws an exception
    Caused by: (Wrapped: Missing or inaccessible constructor "com.tangosol.coherence.jpa.JpaCacheStore(String,String,String)"
    Any ideas?
  • 11. Re: How to avoid Cache misses?
    Jonathan.Knight Expert
    Currently Being Moderated
    That is strange as the 3.6.1.0 JavaDocs for JpaCacheStore here http://download.oracle.com/docs/cd/E15357_01/coh.360/e18814/com/tangosol/coherence/jpa/JpaCacheStore.html specify a constructor with three String parameters.

    I did not do my test with the JpaCacheStore though, I just has a stub of a normal CacheStore implementation.

    JK
  • 12. Re: How to avoid Cache misses?
    660069 Newbie
    Currently Being Moderated
    I can open up the class in the coherence-jpa and indeed there are 3 string constructors.


    I could post the full dump but it's a bit spammy.


    I'll keep digging see if I can find out what the problem is.
  • 13. Re: How to avoid Cache misses?
    660069 Newbie
    Currently Being Moderated
    Hi,

    Finally got to the bottom of it, it was a configuration issue. My persitance name was not JPA.

    Works like a dream JK, thanks.

    Cheers
    Rich
  • 14. Re: How to avoid Cache misses?
    660069 Newbie
    Currently Being Moderated
    Hello,

    Sorry to revisit this thread, however I have encountered a performance issue (one which I'm hoping can be resolved via config).

    If you remember the WrappedBinaryCacheStore inserted a null object into the cache when the results of a database query failed (to improve performance).

    I have been benchmarking my cache access, and have found that the 'null' object created in the cluster is not held in the near cache. Hence every time the null object is requested a network hop(s) is incurred (at a significant performance cost).

    Is there a way to configure the near cache, so that the null object is stored (in the near cache), and no subsequent network fetch is required? (my near cache is a C++ an extend client).

    If this can't be solved via config, does anyone have a neat way of solving this?

    Thanks
    Rich
1 2 Previous Next

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points