Great work Andreas. I'm eager to see this applied.
Thanks
-Vincent
On Jun 9, 2010, at 10:38 PM, Andreas Jonsson wrote:
Hi,
I did a quick benchmarking on the right service, to see what the
benefits of caching is. I measured the time in nano-seconds over the
methods checkAccess and hasAccessLevel. The server is run on a
virtual machine, so the statistics is not entirely reliable.
I have made some interesting observations, however:
* hasAccessLevel is called a large number of times per page view. It
is called around 20 times as often as checkAccess, which should
normally be called once per accessed wiki-document.
* The main reason for the large number of calls to hasAccessLevel are
these variable initializations in xwikivars.vm:
#set($hasEdit = $xwiki.hasAccessLevel("edit"))
#set($hasAdmin = $xwiki.hasAccessLevel("admin"))
## Note: In order to know if the user has the right to create a space we
compute a space name that doesn't exist and check edit rights on that space
#set($hasCreateSpace = $xwiki.hasAccessLevel("edit",
"${doc.space}${mathtool.random(0,
999)}.DocumentReservedForInternalXWikiUsage"))
## Note: In order to know if the user has the right to create a page we
compute a page name that doesn't exist and check edit rights on that page
#set($hasCreatePage = $xwiki.hasAccessLevel("edit",
"${doc.space}.DocumentReservedForInternalXWikiUsage${mathtool.random(0,
999)}"))
#set($hasGlobalAdmin = $xwiki.hasAccessLevel("admin", $context.user,
"XWiki.XWikiPreferences"))
## Note: The document name is not internally used to determine if a user
has programming access level. We pass XWiki.XWikiPreferences for
consistency with the call for global admin
#set($hasProgramming = $xwiki.hasAccessLevel("programming",
$context.user, "XWiki.XWikiPreferences"))
#set($hasSpaceAdmin = $xwiki.hasAccessLevel("admin", $context.user,
"${doc.space}.WebPreferences"))
Due to the ugly hack with randomly generated space names and document
names for initiating the variables "$hasCreateSpace" and
"$hasCreatePage", the caching implementation perform worse than it
could, as this will cause many cache misses and waste cache space.
However, my new implementation internally operates by computing and
caching the rights corresponding to an EntityReference, which may
refer to a document, a space, or a wiki. If this is exposed via the
API it will be possible to query for the rights on a space and wiki
directly.
By removing the randomness :
#set($hasCreateSpace = $xwiki.hasAccessLevel("edit",
"${doc.space}INTERNAL.DocumentReservedForInternalXWikiUsage"))
#set($hasCreatePage = $xwiki.hasAccessLevel("edit",
"${doc.space}.DocumentReservedForInternalXWikiUsage"))
and running a test by reloading the same page over and over, we see
the benefit of caching:
Original implementation:
Statistics for checkAccess:
DescriptiveStatistics:
n: 2030
min: 522859.0
max: 5.39733259E8
mean: 1.7198616874876842E7
std dev: 3.7962038152575314E7
median: 659100.0
skewness: 4.262358507299374
kurtosis: 30.885271611362477
Statistics for hasAccessLevel:
DescriptiveStatistics:
n: 39402
min: 69016.0
max: 6.40750283E8
mean: 8595126.655296639
std dev: 2.770284412153291E7
median: 592985.0
skewness: 5.682505086745415
kurtosis: 50.72341779375682
-----------------------------------------
My new implementation:
Statistics for checkAccess:
DescriptiveStatistics:
n: 2030
min: 27763.0
max: 3.01349107E8
mean: 3118090.7699507335
std dev: 1.6890870361759447E7
median: 132935.0
skewness: 8.995243673145804
kurtosis: 105.30119067606974
Statistics for hasAccessLevel:
DescriptiveStatistics:
n: 33368
min: 26086.0
max: 1.239094745E9
mean: 767290.0766902551
std dev: 1.0969784229187453E7
median: 32915.5
skewness: 55.482434651126056
kurtosis: 5256.531317809735
Considering the skewed nature of the distribution, the mean value
cannot be used to predict the delay caused by the right service.
Considering that hasAccessLevel seems to be called more than 30 times
per page request, a naive interpretation suggests that over 200 ms was
polished of the average request time (7.8 ms * 30). This is obviously
bogus, since the mean request time was 170.027 ms for the original
implementation and 144.743 ms for my new implementation.
The requests were performed with 'ab -c 10 -n 1000 -k ':
Output for original implementation:
Server Software: Apache-Coyote/1.1
Server Hostname: wiki
Server Port: 8080
Document Path: /xw/bin/view/Gob/WebHome
Document Length: 0 bytes
Concurrency Level: 10
Time taken for tests: 170.027 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Non-2xx responses: 1000
Keep-Alive requests: 995
Total transferred: 414975 bytes
HTML transferred: 0 bytes
Requests per second: 5.88 [#/sec] (mean)
Time per request: 1700.269 [ms] (mean)
Time per request: 170.027 [ms] (mean, across all concurrent requests)
Transfer rate: 2.38 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.2 0 3
Processing: 445 1694 412.3 1654 3663
Waiting: 445 1694 412.3 1654 3663
Total: 445 1694 412.4 1654 3663
--------------------------------------------------------------------------------
Output for my new implementation:
Server Software: Apache-Coyote/1.1
Server Hostname: wiki
Server Port: 8080
Document Path: /xw/bin/view/Gob/WebHome
Document Length: 0 bytes
Concurrency Level: 10
Time taken for tests: 144.743 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Non-2xx responses: 1000
Keep-Alive requests: 993
Total transferred: 414965 bytes
HTML transferred: 0 bytes
Requests per second: 6.91 [#/sec] (mean)
Time per request: 1447.426 [ms] (mean)
Time per request: 144.743 [ms] (mean, across all concurrent requests)
Transfer rate: 2.80 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.2 0 4
Processing: 374 1441 428.1 1415 3228
Waiting: 374 1441 428.1 1415 3228
Total: 374 1441 428.1 1415 3228
My implementation needs some more testing, but I will try to upload it
to Jira by tomorrow.
Best regards,
Andreas Jonsson