Fixed ALF-10964: Add back cache for getChildByName

- Originally removed as part of the 'reverse lookup' of parentAssocsCache
 - This cache is NOT clustered; the child target version is checked; requery if necessary
 - NB: Cache misses are NOT cached.  Do do so would mean making the cache clustered.
       It is better to avoid querying for random files that don't exist over and over.
       Add a higher level cache (as is done in CIFS) for that case.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@31417 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2011-10-22 05:06:15 +00:00
parent dee514a741
commit 1dca4cd1fc
6 changed files with 159 additions and 2 deletions

View File

@@ -309,6 +309,39 @@
<property name="disableSharedCache" value="${system.cache.disableMutableSharedCaches}" /> <property name="disableSharedCache" value="${system.cache.disableMutableSharedCaches}" />
</bean> </bean>
<!-- ===================================== -->
<!-- Child by cm:name lookup for nodes -->
<!-- ===================================== -->
<!-- The cross-transaction shared cache for Child-by-name -->
<bean name="node.childByNameSharedCache" class="org.alfresco.repo.cache.EhCacheAdapter">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
<property name="cacheManager">
<ref bean="internalEHCacheManager" />
</property>
<property name="cacheName">
<value>org.alfresco.cache.node.childByNameCache</value>
</property>
</bean>
</property>
</bean>
<!-- The transactional cache for Child-by-name -->
<bean name="node.childByNameCache" class="org.alfresco.repo.cache.TransactionalCache">
<property name="sharedCache">
<ref bean="node.parentAssocsSharedCache" />
</property>
<property name="name">
<value>org.alfresco.cache.node.childByNameTransactionalCache</value>
</property>
<property name="maxCacheSize" value="65000" />
<property name="mutable" value="true" />
<property name="disableSharedCache" value="${system.cache.disableMutableSharedCaches}" />
</bean>
<!-- ===================================== --> <!-- ===================================== -->
<!-- Rules lookup for nodes --> <!-- Rules lookup for nodes -->
<!-- ===================================== --> <!-- ===================================== -->

View File

@@ -120,6 +120,7 @@
<property name="aspectsCache" ref="node.aspectsCache"/> <property name="aspectsCache" ref="node.aspectsCache"/>
<property name="propertiesCache" ref="node.propertiesCache"/> <property name="propertiesCache" ref="node.propertiesCache"/>
<property name="parentAssocsCache" ref="node.parentAssocsCache"/> <property name="parentAssocsCache" ref="node.parentAssocsCache"/>
<property name="childByNameCache" ref="node.childByNameCache"/>
</bean> </bean>
<bean id="nodeDAO.org.hibernate.dialect.Dialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl" parent="nodeDAObase" /> <bean id="nodeDAO.org.hibernate.dialect.Dialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl" parent="nodeDAObase" />
<bean id="nodeDAO.org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl$MSSQL" parent="nodeDAO.org.hibernate.dialect.Dialect" /> <bean id="nodeDAO.org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl$MSSQL" parent="nodeDAO.org.hibernate.dialect.Dialect" />

View File

@@ -35,6 +35,9 @@
overflowToDisk="false" overflowToDisk="false"
statistics="false" statistics="false"
/> />
<!-- Node caches -->
<cache <cache
name="org.alfresco.cache.node.rootNodesCache" name="org.alfresco.cache.node.rootNodesCache"
maxElementsInMemory="500" maxElementsInMemory="500"
@@ -77,6 +80,13 @@
overflowToDisk="false" overflowToDisk="false"
statistics="false" statistics="false"
/> />
<cache
name="org.alfresco.cache.node.childByNameCache"
maxElementsInMemory="130000"
eternal="true"
overflowToDisk="false"
statistics="false"
/>
<!-- AVM caches --> <!-- AVM caches -->

View File

@@ -197,6 +197,16 @@
replicateAsynchronously = false"/> replicateAsynchronously = false"/>
</cache> </cache>
<cache
name="org.alfresco.cache.node.childByNameCache"
maxElementsInMemory="130000"
eternal="true"
overflowToDisk="false"
statistics="false"
>
<!-- Not clustered -->
</cache>
<cache <cache
name="org.alfresco.cache.avm.avmEntityCache" name="org.alfresco.cache.avm.avmEntityCache"
maxElementsInMemory="10000" maxElementsInMemory="10000"

View File

@@ -42,6 +42,7 @@ import org.alfresco.ibatis.BatchingDAO;
import org.alfresco.ibatis.RetryingCallbackHelper; import org.alfresco.ibatis.RetryingCallbackHelper;
import org.alfresco.ibatis.RetryingCallbackHelper.RetryingCallback; import org.alfresco.ibatis.RetryingCallbackHelper.RetryingCallback;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.cache.NullCache;
import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.cache.lookup.EntityLookupCache; import org.alfresco.repo.cache.lookup.EntityLookupCache;
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor; import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor;
@@ -173,6 +174,11 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
*/ */
private EntityLookupCache<NodeVersionKey, ParentAssocsInfo, Serializable> parentAssocsCache; private EntityLookupCache<NodeVersionKey, ParentAssocsInfo, Serializable> parentAssocsCache;
/**
* Cache for fast lookups of child nodes by <b>cm:name</b>.
*/
private SimpleCache<ChildByNameKey, ChildAssocEntity> childByNameCache;
/** /**
* Constructor. Set up various instance-specific members such as caches and locks. * Constructor. Set up various instance-specific members such as caches and locks.
*/ */
@@ -187,6 +193,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
aspectsCache = new EntityLookupCache<NodeVersionKey, Set<QName>, Serializable>(new AspectsCallbackDAO()); aspectsCache = new EntityLookupCache<NodeVersionKey, Set<QName>, Serializable>(new AspectsCallbackDAO());
propertiesCache = new EntityLookupCache<NodeVersionKey, Map<QName, Serializable>, Serializable>(new PropertiesCallbackDAO()); propertiesCache = new EntityLookupCache<NodeVersionKey, Map<QName, Serializable>, Serializable>(new PropertiesCallbackDAO());
parentAssocsCache = new EntityLookupCache<NodeVersionKey, ParentAssocsInfo, Serializable>(new ParentAssocsCallbackDAO()); parentAssocsCache = new EntityLookupCache<NodeVersionKey, ParentAssocsInfo, Serializable>(new ParentAssocsCallbackDAO());
childByNameCache = new NullCache<ChildByNameKey, ChildAssocEntity>();
} }
/** /**
@@ -345,7 +352,17 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
CACHE_REGION_PARENT_ASSOCS, CACHE_REGION_PARENT_ASSOCS,
new ParentAssocsCallbackDAO()); new ParentAssocsCallbackDAO());
} }
/**
* Set the cache that maintains lookups by child <b>cm:name</b>
*
* @param childByNameCache the cache
*/
public void setChildByNameCache(SimpleCache<ChildByNameKey, ChildAssocEntity> childByNameCache)
{
this.childByNameCache = childByNameCache;
}
/* /*
* Initialize * Initialize
*/ */
@@ -2018,6 +2035,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
// Touch to bring into current transaction // Touch to bring into current transaction
if (updated) if (updated)
{ {
// We have to explicitly update the node (sys:locale or cm:auditable)
updateNodeImpl(node, nodeUpdate); updateNodeImpl(node, nodeUpdate);
} }
@@ -3023,9 +3041,59 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
} }
} }
/**
* Checks a cache and then queries.
* <p/>
* Note: If we were to cach misses, then we would have to ensure that the cache is
* kept up to date whenever any affection association is changed. This is actually
* not possible without forcing the cache to be fully clustered. So to
* avoid clustering the cache, we instead watch the node child version,
* which relies on a cache that is already clustered.
*/
public Pair<Long, ChildAssociationRef> getChildAssoc(Long parentNodeId, QName assocTypeQName, String childName) public Pair<Long, ChildAssociationRef> getChildAssoc(Long parentNodeId, QName assocTypeQName, String childName)
{ {
ChildAssocEntity assoc = selectChildAssoc(parentNodeId, assocTypeQName, childName); ChildByNameKey key = new ChildByNameKey(parentNodeId, assocTypeQName, childName);
ChildAssocEntity assoc = childByNameCache.get(key);
boolean query = false;
if (assoc == null)
{
query = true;
}
else
{
// Check that the resultant child node has not moved on
Node childNode = assoc.getChildNode();
Long childNodeId = childNode.getId();
NodeVersionKey childNodeVersionKey = childNode.getNodeVersionKey();
Pair<Long, Node> childNodeFromCache = nodesCache.getByKey(childNodeId);
if (childNodeFromCache == null)
{
// Child node no longer exists (or never did)
query = true;
}
else
{
NodeVersionKey childNodeFromCacheVersionKey = childNodeFromCache.getSecond().getNodeVersionKey();
if (!childNodeFromCacheVersionKey.equals(childNodeVersionKey))
{
// The child node has moved on. We don't know why, but must query again.
query = true;
}
}
}
if (query)
{
assoc = selectChildAssoc(parentNodeId, assocTypeQName, childName);
if (assoc != null)
{
childByNameCache.put(key, assoc);
}
else
{
// We do not cache misses. See javadoc.
}
}
// Now return, checking the assoc's ID for null
return assoc == null ? null : assoc.getPair(qnameDAO); return assoc == null ? null : assoc.getPair(qnameDAO);
} }

View File

@@ -500,4 +500,39 @@ public class NodeServiceTest extends TestCase
List<ChildAssociationRef> parentAssocsPost = nodeService.getParentAssocs(secondaryNodeRef); List<ChildAssociationRef> parentAssocsPost = nodeService.getParentAssocs(secondaryNodeRef);
assertEquals("Incorrect number of parent assocs", 1, parentAssocsPost.size()); assertEquals("Incorrect number of parent assocs", 1, parentAssocsPost.size());
} }
/**
* Checks that file renames are handled when getting children
*/
public void testCaches_RenameNode()
{
final NodeRef[] nodeRefs = new NodeRef[2];
final NodeRef workspaceRootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
buildNodeHierarchy(workspaceRootNodeRef, nodeRefs);
// What is the name of the first child?
String name = (String) nodeService.getProperty(nodeRefs[1], ContentModel.PROP_NAME);
// Now query for it
NodeRef nodeRefCheck = nodeService.getChildByName(nodeRefs[0], ContentModel.ASSOC_CONTAINS, name);
assertNotNull("Did not find node by name", nodeRefCheck);
assertEquals("Node found was not correct", nodeRefs[1], nodeRefCheck);
// Rename the node
nodeService.setProperty(nodeRefs[1], ContentModel.PROP_NAME, "New Name");
// Should find nothing
nodeRefCheck = nodeService.getChildByName(nodeRefs[0], ContentModel.ASSOC_CONTAINS, name);
assertNull("Should not have found anything", nodeRefCheck);
// Add another child with the same original name
NodeRef newChildNodeRef = nodeService.createNode(
nodeRefs[0],
ContentModel.ASSOC_CONTAINS,
QName.createQName(NAMESPACE, name),
ContentModel.TYPE_FOLDER,
Collections.singletonMap(ContentModel.PROP_NAME, (Serializable)name)).getChildRef();
// We should find this new node when looking for the name
nodeRefCheck = nodeService.getChildByName(nodeRefs[0], ContentModel.ASSOC_CONTAINS, name);
assertNotNull("Did not find node by name", nodeRefCheck);
assertEquals("Node found was not correct", newChildNodeRef, nodeRefCheck);
}
} }