Search query performance is dis-proportionaly affected by (read) permission evaluation on nodes returned by a query (compared to the actual search), especially so for larger repositories. This enhancement improves read permission evaluation performance by optimising the read permission evaluation code and using more caching where possible.
A new method PermissionService.hasReadPermission provides the optimised read permission evaluation. It is used by the Alfresco Acegi security filters to filter search results by read permission. It performs the following checks in order for each node in the query results:
i) can the current user read the node (does the user have explicit READ permission on the node). This is accomplished by looking up the node ref from the lucene index, then looking up the nodes acl from the nodes cache, then looking up the read authorities for that acl from the aclReaders cache and then checking whether the authenticated user's authorisations include any of the authorities with read permission. If this information is cached, the calculation of the result will be very quick. ii) is the current user the admin user iii) is the current user the owner of the node. This is a lookup in the nodeOwner cache. If this information is cached, the calculation of the result will be very quick.
The 'nodes' and 'nodeOwner' caches already exist. A new cache 'aclReaders' (which maps between node acls and authorities with read permissions on the acl) is introduced. A transaction-specific cache is used to store the authorisations for the authenticated user (an expensive operation when looping through the results of a search).
This optimisation can be turned off and on with the 'system.readpermissions.optimise' property.
a) In order to achieve the speed improvement, the only supported dynamic authorities are those present in the default permissions model. The presence of dynamic authorities other than those in the default permissions model will force use of the standard permissions evaluation. b) Again, in order to achieve the speed improvement, the permission check is restricted to explicit read permission - combinations of lower-level read permissions will not be found e.g. given group A with READ_CONTENT and group B with READ_PROPERTIES, a user in both groups should have read permission but will not be found.
The setup is a store with 1000000 nodes that have inherited read permission for user x and 10 nodes that have explicit read permission for user Y.
The test comprises running a 'TYPE:\'cm:content\'' search 4 times in a row against a store for a newly-started repository.
i) The nodes cache (txn and shared) has max entries set to 2001000 (bi-directional cache, so multiply number of nodes by 2). The nodeOwner cache (txn and shared) has max entries set to 1001000. The aclReaders cache has max entries set to 10000 (more than enough in this case given that permissions are inherited).
User Y, 10 nodes in 81.312237 User Y, 10 nodes in 11.170863 User Y, 10 nodes in 11.195379 User Y, 10 nodes in 11.189759
User Y, 10 nodes in 103.71217 User Y, 10 nodes in 104.995179 User Y, 10 nodes in 106.320423 User Y, 10 nodes in 106.424857
ii) The nodes cache (txn and shared) has max entries set to 2001000 (bi-directional cache, so multiply number of nodes by 2). The nodeProperties, nodeAspects and nodeOwner caches (txn and shared) have max entries set to 1001000. The aclReaders cache has max entries set to 10000 (more than enough in this case given that permissions are inherited).
User Y, 10 nodes in 97.32361 User Y, 10 nodes in 25.924495 User Y, 10 nodes in 25.829163 User Y, 10 nodes in 25.990601
User Y, 10 nodes in 81.277027 User Y, 10 nodes in 11.420287 User Y, 10 nodes in 11.214065 User Y, 10 nodes in 11.299918
iii) The nodes, nodeAspects, nodeProperties and nodeOwner caches are not big enough to hold all mappings.
User Y, 10 nodes in 80.149073 User Y, 10 nodes in 74.073088
User Y, 10 nodes in 94.331461 User Y, 10 nodes in 86.959984 User Y, 10 nodes in 87.951549 User Y, 10 nodes in 87.681294
The optimised version is faster with cold caches. For warm caches, the speed improvement depends on the sizes of the caches. The optimised version benefits from nodes, nodeOwner and aclReaders caches being sized to store all mappings that will be needed for searches, but does not require large aspects and properties caches. The non-optimised version requires the aspects and properties caches to be sized to store all mappings to come close, but even then is slower than the optimised version.
For a cold aclReaders cache, having lots of non-inherited permissions (and hence more acls) will slow down the permissions evaluation as it builds up the aclReaders cache.
In order to achieve the best performance, ensure that your nodes, nodeOwner and aclReaders caches are sized appropriately. For the aclReaders cache make sure the transactional cache is large too, otherwise it will overflow and the shared cache will be cleared.
Note that larger result sets will generally result in more of an improvement in performance than smaller result sets (depending on appropriately sized caches). This is because permission evaluation essentially loops over the result set checking read permissions for each result, so that the time savings from the faster read permissions evaluation code amortizes over the complete result set.