Author: jukka Date: Sat Oct 2 11:02:06 2010 New Revision: 1003773 URL: http://svn.apache.org/viewvc?rev=1003773&view=rev Log: JCR-2699: Improve read/write concurrency Revert revision 1003542 until I have time to solve the NPE issue. Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleCache.java - copied unchanged from r1003541, jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleCache.java Removed: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cache/LRUCache.java jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cache/ Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/AbstractBundlePersistenceManager.java jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateCache.java jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ManagedMLRUItemStateCacheFactory.java Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java?rev=1003773&r1=1003772&r2=1003773&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java Sat Oct 2 11:02:06 2010 @@ -18,7 +18,6 @@ package org.apache.jackrabbit.core.persi import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.jackrabbit.core.cache.LRUCache; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.state.ItemState; @@ -38,6 +37,7 @@ import org.apache.jackrabbit.core.persis import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.util.StringIndex; import org.apache.jackrabbit.core.persistence.util.BLOBStore; +import org.apache.jackrabbit.core.persistence.util.BundleCache; import org.apache.jackrabbit.core.persistence.util.FileBasedIndex; import org.apache.jackrabbit.core.persistence.util.LRUNodeIdCache; import org.apache.jackrabbit.core.persistence.util.NodePropBundle; @@ -68,7 +68,7 @@ import javax.jcr.PropertyType; * included in the bundle but generated when required. *

* In order to increase performance, there are 2 caches maintained. One is the - * bundle cache that caches already loaded bundles. The other is the + * {@link BundleCache} that caches already loaded bundles. The other is the * {@link LRUNodeIdCache} that caches non-existent bundles. This is useful * because a lot of {@link #exists(NodeId)} calls are issued that would result * in a useless SQL execution if the desired bundle does not exist. @@ -103,7 +103,7 @@ public abstract class AbstractBundlePers private StringIndex nameIndex; /** the cache of loaded bundles */ - private LRUCache bundles; + private BundleCache bundles; /** the cache of non-existent bundles */ private LRUNodeIdCache missing; @@ -387,7 +387,7 @@ public abstract class AbstractBundlePers public void init(PMContext context) throws Exception { this.context = context; // init bundle cache - bundles = new LRUCache(bundleCacheSize); + bundles = new BundleCache(bundleCacheSize); missing = new LRUNodeIdCache(); } @@ -656,7 +656,7 @@ public abstract class AbstractBundlePers bundle = loadBundle(id); if (bundle != null) { bundle.markOld(); - bundles.put(id, bundle, bundle.getSize()); + bundles.put(bundle); } else { missing.put(id); } @@ -692,9 +692,8 @@ public abstract class AbstractBundlePers missing.remove(bundle.getId()); // only put to cache if already exists. this is to ensure proper overwrite // and not creating big contention during bulk loads - if (bundles.containsKey(bundle.getId())) { - bundles.remove(bundle.getId()); - bundles.put(bundle.getId(), bundle, bundle.getSize()); + if (bundles.contains(bundle.getId())) { + bundles.put(bundle); } } Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/AbstractBundlePersistenceManager.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/AbstractBundlePersistenceManager.java?rev=1003773&r1=1003772&r2=1003773&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/AbstractBundlePersistenceManager.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/AbstractBundlePersistenceManager.java Sat Oct 2 11:02:06 2010 @@ -24,7 +24,6 @@ import javax.jcr.PropertyType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.jackrabbit.core.cache.LRUCache; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.id.ItemId; @@ -35,6 +34,7 @@ import org.apache.jackrabbit.core.persis import org.apache.jackrabbit.core.persistence.PMContext; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.persistence.util.BLOBStore; +import org.apache.jackrabbit.core.persistence.util.BundleCache; import org.apache.jackrabbit.core.persistence.util.FileBasedIndex; import org.apache.jackrabbit.core.persistence.util.LRUNodeIdCache; import org.apache.jackrabbit.core.persistence.util.NodePropBundle; @@ -68,7 +68,7 @@ import org.apache.jackrabbit.spi.commons * included in the bundle but generated when required. *

* In order to increase performance, there are 2 caches maintained. One is the - * bundle cache that caches already loaded bundles. The other is the + * {@link BundleCache} that caches already loaded bundles. The other is the * {@link LRUNodeIdCache} that caches non-existent bundles. This is useful * because a lot of {@link #exists(NodeId)} calls are issued that would result * in a useless SQL execution if the desired bundle does not exist. @@ -103,7 +103,7 @@ public abstract class AbstractBundlePers private StringIndex nameIndex; /** the cache of loaded bundles */ - private LRUCache bundles; + private BundleCache bundles; /** the cache of non-existent bundles */ private LRUNodeIdCache missing; @@ -387,7 +387,7 @@ public abstract class AbstractBundlePers public void init(PMContext context) throws Exception { this.context = context; // init bundle cache - bundles = new LRUCache(bundleCacheSize); + bundles = new BundleCache(bundleCacheSize); missing = new LRUNodeIdCache(); } @@ -654,7 +654,7 @@ public abstract class AbstractBundlePers bundle = loadBundle(id); if (bundle != null) { bundle.markOld(); - bundles.put(id, bundle, bundle.getSize()); + bundles.put(bundle); } else { missing.put(id); } @@ -690,9 +690,8 @@ public abstract class AbstractBundlePers missing.remove(bundle.getId()); // only put to cache if already exists. this is to ensure proper overwrite // and not creating big contention during bulk loads - if (bundles.containsKey(bundle.getId())) { - bundles.remove(bundle.getId()); - bundles.put(bundle.getId(), bundle, bundle.getSize()); + if (bundles.contains(bundle.getId())) { + bundles.put(bundle); } } Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateCache.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateCache.java?rev=1003773&r1=1003772&r2=1003773&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateCache.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateCache.java Sat Oct 2 11:02:06 2010 @@ -83,6 +83,14 @@ public interface ItemStateCache { boolean isEmpty(); /** + * Informs the cache that the item was modified and the cache might need to + * recalculate the items caching weight. + * + * @param id the id of the item that was modified. + */ + void update(ItemId id); + + /** * Informs the cache that it is no longer in use. */ void dispose(); Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java?rev=1003773&r1=1003772&r2=1003773&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java Sat Oct 2 11:02:06 2010 @@ -162,6 +162,14 @@ public class ItemStateReferenceCache imp /** * {@inheritDoc} */ + public synchronized void update(ItemId id) { + // delegate + cache.update(id); + } + + /** + * {@inheritDoc} + */ public synchronized boolean isEmpty() { // check primary cache return refs.isEmpty(); Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java?rev=1003773&r1=1003772&r2=1003773&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java Sat Oct 2 11:02:06 2010 @@ -16,9 +16,14 @@ */ package org.apache.jackrabbit.core.state; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + import org.apache.commons.collections.map.LinkedMap; -import org.apache.jackrabbit.core.cache.CacheManager; -import org.apache.jackrabbit.core.cache.LRUCache; +import org.apache.jackrabbit.core.cache.Cache; +import org.apache.jackrabbit.core.cache.CacheAccessListener; import org.apache.jackrabbit.core.id.ItemId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,58 +38,139 @@ import org.slf4j.LoggerFactory; * TODO rename class to something more appropriate, e.g. FIFOItemSateCache since * it doesn't use a LRU eviction policy anymore. */ -public class MLRUItemStateCache implements ItemStateCache { - +public class MLRUItemStateCache implements ItemStateCache, Cache { /** Logger instance */ private static Logger log = LoggerFactory.getLogger(MLRUItemStateCache.class); /** default maximum memory to use */ public static final int DEFAULT_MAX_MEM = 4 * 1024 * 1024; + /** the amount of memory the entries use */ + private volatile long totalMem; + + /** the maximum of memory the cache may use */ + private volatile long maxMem; + /** the number of writes */ - private volatile long numWrites = 0; + private volatile long numWrites; + + /** the access count */ + private volatile long accessCount; - private final LRUCache cache = - new LRUCache(DEFAULT_MAX_MEM); + /** the cache access listeners */ + private CacheAccessListener accessListener; - public MLRUItemStateCache(CacheManager cacheMgr) { - cacheMgr.add(cache); - cache.setAccessListener(cacheMgr); + /** + * A cache for ItemState instances + */ + private final Map cache; + + /** + * Constructs a new, empty ItemStateCache with a maximum amount + * of memory of {@link #DEFAULT_MAX_MEM}. + */ + public MLRUItemStateCache() { + this(DEFAULT_MAX_MEM); } - //-------------------------------------------------------< ItemStateCache > + /** + * Constructs a new, empty ItemStateCache with the specified + * maximum memory. + * + * @param maxMem the maximum amount of memory this cache may use. + */ + @SuppressWarnings("serial") + private MLRUItemStateCache(int maxMem) { + this.maxMem = maxMem; + this.cache = new LinkedHashMap( + maxMem / 1024, 0.75f, true /* access-ordered */) { + @Override + protected boolean removeEldestEntry(Map.Entry e) { + long maxMem = MLRUItemStateCache.this.maxMem; + if (totalMem <= maxMem) { + return false; + } else if (totalMem - e.getValue().size <= maxMem) { + totalMem -= e.getValue().size; + return true; + } else { + shrink(); + return false; + } + } + }; + } + //-------------------------------------------------------< ItemStateCache > /** * {@inheritDoc} */ public boolean isCached(ItemId id) { - return cache.containsKey(id); + synchronized (cache) { + return cache.containsKey(id); + } } /** * {@inheritDoc} */ public ItemState retrieve(ItemId id) { - return cache.get(id); + touch(); + synchronized (cache) { + Entry entry = cache.get(id); + if (entry != null) { + return entry.state; + } else { + return null; + } + } } /** * {@inheritDoc} */ public ItemState[] retrieveAll() { - return cache.values(); + synchronized (cache) { + ItemState[] states = new ItemState[cache.size()]; + int i = 0; + for (Entry entry : cache.values()) { + states[i++] = entry.state; + } + return states; + } } /** * {@inheritDoc} */ - public synchronized void cache(ItemState state) { - cache.put(state.getId(), state, state.calculateMemoryFootprint()); + public void update(ItemId id) { + touch(); + synchronized (cache) { + Entry entry = cache.get(id); + if (entry != null) { + totalMem -= entry.size; + entry.recalc(); + totalMem += entry.size; + } + } + } - if (numWrites++ % 10000 == 0 && log.isDebugEnabled()) { - log.debug("Item state cache size: {}% of {} bytes", - cache.getMemoryUsed() * 100 / cache.getMaxMemorySize(), - cache.getMaxMemorySize()); + /** + * {@inheritDoc} + */ + public void cache(ItemState state) { + touch(); + synchronized (cache) { + ItemId id = state.getId(); + if (cache.containsKey(id)) { + log.warn("overwriting cached entry " + id); + evict(id); + } + Entry entry = new Entry(state); + totalMem += entry.size; + cache.put(id, entry); + if (numWrites++ % 10000 == 0 && log.isDebugEnabled()) { + log.debug(this + " size=" + cache.size() + ", " + totalMem + "/" + maxMem); + } } } @@ -92,28 +178,135 @@ public class MLRUItemStateCache implemen * {@inheritDoc} */ public void evict(ItemId id) { - cache.remove(id); + touch(); + synchronized (cache) { + Entry entry = cache.remove(id); + if (entry != null) { + totalMem -= entry.size; + } + } } /** * {@inheritDoc} */ public void evictAll() { - cache.clear(); + synchronized (cache) { + cache.clear(); + totalMem = 0; + } } /** * {@inheritDoc} */ public boolean isEmpty() { - return cache.isEmpty(); + synchronized (cache) { + return cache.isEmpty(); + } + } + + private void touch() { + accessCount++; + if ((accessCount % CacheAccessListener.ACCESS_INTERVAL) == 0) { + if (accessListener != null) { + accessListener.cacheAccessed(); + } + } + } + + /** + * {@inheritDoc} + */ + public long getAccessCount() { + return accessCount; + } + + /** + * {@inheritDoc} + */ + public long getMaxMemorySize() { + return maxMem; + } + + /** + * {@inheritDoc} + */ + public long getMemoryUsed() { + return totalMem; + } + + /** + * {@inheritDoc} + */ + public void resetAccessCount() { + synchronized (cache) { + accessCount = 0; + } + } + + /** + * {@inheritDoc} + */ + public void setMaxMemorySize(long size) { + synchronized (cache) { + this.maxMem = size; + + // remove items, if too many + if (totalMem > maxMem) { + shrink(); + } + } + } + + private void shrink() { + List> list = + new ArrayList>(cache.entrySet()); + for (int i = list.size() - 1; totalMem > maxMem && i >= 0; i--) { + Map.Entry last = list.get(i); + totalMem -= last.getValue().size; + cache.remove(last.getKey()); + } + } + + /** + * Set the cache access listener. Only one listener per cache is supported. + * + * @param listener the new listener + */ + public void setAccessListener(CacheAccessListener listener) { + this.accessListener = listener; } /** * {@inheritDoc} */ public void dispose() { - cache.dispose(); + synchronized (cache) { + if (accessListener != null) { + accessListener.disposeCache(this); + } + } + } + + + /** + * Internal cache entry. + */ + private static class Entry { + + private final ItemState state; + + private long size; + + public Entry(ItemState state) { + this.state = state; + this.size = 64 + state.calculateMemoryFootprint(); + } + + public void recalc() { + size = 64 + state.calculateMemoryFootprint(); + } } } Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ManagedMLRUItemStateCacheFactory.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ManagedMLRUItemStateCacheFactory.java?rev=1003773&r1=1003772&r2=1003773&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ManagedMLRUItemStateCacheFactory.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ManagedMLRUItemStateCacheFactory.java Sat Oct 2 11:02:06 2010 @@ -41,7 +41,10 @@ public class ManagedMLRUItemStateCacheFa * Create a new cache instance and link it to the cache manager. */ public ItemStateCache newItemStateCache() { - return new MLRUItemStateCache(cacheMgr); + MLRUItemStateCache cache = new MLRUItemStateCache(); + cacheMgr.add(cache); + cache.setAccessListener(cacheMgr); + return cache; } }