OSCache - Chain Caching Model

## Discussion Lars wrote: Would it be possible to use the intercepting filter pattern to support all possible cache models with a lot of combination capabilities? It's possible to define the capacity for each cache etc. DiskPersistence, SoftReferenceCache etc. would implement the Command interface of Commons Chain or a new interface of OSCache. http://jakarta.apache.org/commons/chain/ http://java.sun.com/blueprints/corej2eepatterns/Patterns/InterceptingFilter.html Andres wrote: I think that is a good idea but it may be overkill for the most use cases. Also, not every cache interceptor could have all the capabilities. In particular, how would you imagine a realistic use case for a cache.get() call. Should multiple interceptors return values? I think disk persistence is still in, although I don't think it will be like it is now. We will be accepting Object keys, so any cache impl will need to accept them. I have been thinking about a lightweight object db that has persistence built-in but I'm not sure. This is definitely an interesting topic and I'd like to discuss it more. Now is the time to make these sort of decisions. Lars wrote: The cache interceptors are cascaded. If the first interceptor doesn't return a value then the 2nd interceptor will be requested. Andres wrote: The scenarios below basically describe a more flexible implementation of the 2 cache (memory and disk) architecture in place. There is no doubt that a chained design can be better. I would be mindful of the abuse that can occur with a inifinitely flexible system. I prefer though to allow people to shoot themselves in the foot but make it extremely easy not to do so by providing very simple out of the box implementations. I have attached a simple sequence diagram. I think further documenting the scenarios you have below as well as all the other use cases we intend to provide is the best course of action at this point. All the old code has been torn out. We are at the point where we should be conscious of the new design that we allow to take shape. Lars wrote: The interface needed for the Cache Link (which you describe in a simple sequence diagram) is exactly the same interface as the EvictionAlgorithm. The Cache Chain has to handle in which Cache Link a cache content should be. Furthermore the Cache Chain has to put an evicted cache content from the memory cache to the disk cache. 1.) How do you want to synchronize the access to the same cache content? In OSCache 2 this is done by the EntryUpdateState based on the key. 2.) Should the Cache Chain contain all the cache keys without knowing in which Cache Link the content is? Or should each Cache Link contain it's own cache keys? Some other points: 3.) Please update the java doc of EvictionAlgorithm, because some parameters are wrong. 4.) currently you synchronize the cache events, I think there maybe a performance loss, because in my environments a lot of events are fired I added a simple class diagramm and saved the diagramm in the Fujaba format. Andres wrote: The cache chain should have no knowledge of what is in any of the cache links. However, the issue of eviction is clear. When a put() is called, the link should return an evicted entry or null if the cache is not full. The chain will then know if it needs to continue the put into the next link. To clarify the interfaces, I think a Chain interface should extend Map. The Link and EvictionAlgorithm should themselves be interfaces. Link could have implementations such as memory, disk, database. EvictionAlgorithm could have implementations such as LRU, FIFO, etc.... 1.) In my branch, I have synchronized the entire cache on each cache access. I think this will still be fast enough and will surely be more stable. basically, get, put, and remove are sync'd. I do not think we need to achieve a highly concurrent cache in order to provide a solution that is hundreds of times faster than db or disk access. However, we could add functionality the improves performance but does not cause deadlocks, such as a write behind feature on puts, so that puts get queued and another thread does the work when it has time. 2.) I don't think this would be wise. I don't think the chain should have knowledge of the keys. I think all it should have is references to the links and stateless logic. Either way each link would need to keep its own keys, therefore putting them in the chain would add another map that would have to be accessed and slow performance. There are 3 places I believe the keys must exist: in the store (duh), in the algorithm (or we could generalize this as any metrics collector), and in the groups map. Group functionality is a similar issue. I had wanted to drop this functionality but it seems the people that use cache tags (I never have yet) really depend on them. This functionality is unique to OSCache as far as I am aware. Maintaining the groups in each link could kill performance. I think we need a GroupManager that exists outside of the cache links and is referenced from the chain. This way it is only called once per chain. One disadvantage would be that when a group is removed each remove would have to be called on each link until it found the correct store. 3./4.) Yeah, that is sort of borrowed code and is not necessary. However, we need to be mindful of the access to the listener list. The easiest way is to probably make the list implementation a SynchronizedArrayList or something. The way I am thinking the current code in my branch could be moved over to a chain model is: most of the BaseCache code gets put into the chain minus the algorithm and group map code. the MemoryCache gets turned into one of the link implementations and gets the algorithm reference the group map code gets refactored into a GroupManager and called from the chain. Lars wrote: I think the implementations of the CacheChain interface could be a SimplePipeCacheChain and a SizeBasedCacheChain. The SimplePipeCacheChain is comparable to the current architectur. The SizeBasesCacheChain puts the cache objects to the different CacheLinks based on the cache content sizes, e.g. large images a stored to disk and not in memory. The default CacheChain should be the SimplePipeCacheChain. The SizeBasedCacheChain can be implemented as part of a 3.1 release. ## Scenarios to be checked and tested Configuration with a LRU algorithm: (1) MemoryCache -> (2) SoftRefCache -> (3) DiskPersistCache ### Scenario A: Get for a object in SoftRefCache the cache object x1 is in the SoftRefCache the 1st getEntry will return null for the MemoryCache the 2nd getEntry will find the cache object x1 in SoftRefCache cache object x1 has to be removed from the SoftRefCache and has to put into the 1st cache (or maybe in the previous cache <- design decission). the cache object x1 will edge out the cache object xi (LRU) from the MemoryCache and the cache object xi has to me removed from the MemoryCache and to put in the next cache (SoftRefCache). ### Scenario B: Get for a object in DiskPersistCache the cache object x2 is in the DiskPersistCache the 1st getEntry will return null for the MemoryCache the 2nd getEntry will return null for the SoftRefCache the 3rd getEntry will find the cache object x2 in DiskPersistCache cache object x2 has to be removed from the DiskPersistCache and has to put into the 1st cache. The cache object x2 will edge out xj in MemoryCache, which has to be put in SoftRefCache. Hence xj will edge out xk in SoftRefCache, which has to be put in DiskPersistCache ### Scenario C: Put a new object the new cache object x3 should be put into the cache the cache object x3 will be edge out a cache object xa from MemoryCache xa has to be put into SoftRefCache, where xa will edge out xb xb has to be put in DiskPersistCache, where xb will edge out xc until DiskPersistCache is not unlimited the xc cache object has to be removed from cache. Hence the cache key for xc has to be removed from the map. ### Scenario D: Put a stale object or get a stale object TODO