OK, here's a review of how caching worked for WikiTalk in 1.8... (probably 95% 
right due to it being a couple of years ago when I wrote this).  But I did 
spend several months working on this and I was quite proud of the result.  In 
particular, I was surprised that given the dynamic nature of scripting that I 
was able to get a comprehensive caching architecture working.



First, there's an old, but basically right description here: 
http://www.flexwiki.com/default.aspx/FlexWiki/CachingUpdate.html



But you can read this and get the basic idea.



Caching involves two things:

1)   funneling requests for things through something that can store a value in 
the cache and return a cached value when it's available

2)   knowing when an how to invalidate entries



1 - The Cache



The first part is typically easy.  In 1.8, the Federation held the Cache.  The 
Cache was a fairly simple.  It could hold a variety of different types of 
things (e.g., the formatted content of a page, a list of topics in a namespace, 
a list of all namespaces, a list of all topics that contain a certain property, 
etc.).  For each of these kinds of thing that could be help by the cache, there 
was localized code that knew about the shape of keys in the cache (specially 
formatted strings) and what it could expect to find at certain locations in the 
cache.  The key string formats are generated in the methods KeyForXXX() in 
FederationCacheManager.



As an example, the code that formats up a topic and answers back HTML for the 
main section of the topic (as opposed to the borders which are cached 
individually) knows that the cache keys for formatted topics look something 
like FORMATTED_TOPIC.<FULLY-QUALIFIED-TOPIC-NAME> and that key structure is 
built into the code.  So when a formatted topic body is needed, the function 
being executed assembles up the cache key string like this and asks the cache 
if the entry's there.  If it is, it's returned right away.  If it's not, the 
heavy lifting is done to format it up and then the result is first stashed in 
the cache for next time (almost always but see below) and then returned.



WikiTalk doesn't do anything special here except how it enable cache rules 
about what can be cached at all and invalidation. See below.



A very instructive thing to do is go look at the cache viewer in the /admin 
tools in a running instance of 1.8.  You can see the strings used for keys and 
the sort of things held in the cache entries.  They are VERY self-explanatory.  
2-3 minutes of poking around will tell you a lot.



2 - Invalidation



OK...  So invalidation is driven by event that happen in the federation.  For 
example, changing a topic, creating a namespace, etc.  There are about ten 
events that can cause entries in the cache to be invalidated.  The Federation 
generates events when these things happen but the Federation is very loosely 
couple into the invalidation system.  In particular, the Federation just 
generates events for various things but has no idea that they are being 
monitored by the FederationCacheManager.  The FederationCacheManager keeps 
various lists of keys to invalidate in the cache when certain events happen.  
And it invalidates them without the cache knowing where the invalidations are 
coming from.  So the FederationCacheManager is the intermediary.  It gets 
notified when certain things happen in the Federation and then invalidates the 
appropriate entries in the cache.  For example, when a specific topic FOO gets 
Changes, the federation notifies the FCM and then the FCM looks up the list of 
cache keys to invalidate when that topic gets Changed and invalidates them.  It 
doesn't know why.  It just does it because it was magically instructed earlier 
to do so.



So who instructed it?  In short, when the cache entry was created, the FCM was 
also told what sort of events should invalidate the entry.  As an example, when 
the formatted body for topic FOO is put into the cache, the FCM is told that 
when FOO is changed that that particular cache entry should be invalidated.  A 
more interesting example might be: when some WikiTalk asks the Federation for a 
list of all namespaces, the list is built and the stuck in the cache under a 
certain key.  Then the FCM is told that when the namespace creation/deletion 
events fire from the Federation that this certain key should be invalidated in 
the cache.  It sounds complex, but the knowledge is all localized where the 
cache data is generated and used.



3 - WikiTalk and Invalidation



OK, so how does this work for WikiTalk?  You'll recall that by default, the 
cache entry for the formatted body of a topic is set up to be invalidated when 
the topic changed event is fired for that topic.  But that's just the base 
thing that can cause the page to be invalidated.  Depending on the WikiTalk on 
the page, there may be additional federation events that could cause the page 
to be invalidated.



Let's take an example.   Imagine a topic that includes one WikiTalk fragment 
that enumerates all the namespaces in a federation.  As the formatting for this 
page happens (the first time when there's nothing cached), we create with an 
"invalidation collection" of events that would force the cached formatted HTML 
for the page to be invalidated.  That collection starts with only one type of 
event: the topic-changed event for that topic.  But then part way through the 
formatting of the page we fire up the WikiTalk interpreter on the WikiTalk 
fragment.   As the WikiTalk code runs, various calls into the C# 
WikiTalk-implementing classes get made including one to the federation to get 
the list of all namespaces.  As a side-effect of calling this method, the 
"invalidation collection" gets another event stuck into it that says "this guy 
needs to be invalidated if namespaces get added or deleted."  After all the 
formatting is done, all of the information in this invalidation collection is 
enumerated and used to setup the FCM so that it will properly invalidate the 
cached formatted topic when EITHER the topic gets changed OR when namespaces 
are added or removed.



That's the basic idea but it expands a bit:



*         The WikiTalk that executes can generate many, many invalidation rules 
(called CacheRules - see the code).  If a page has WikITalk that reads and 
displayed the value of a property called Foo on Page1 and Bar on Page2 then the 
cache rules will be such that if Page1 or Page2 get changed, the cached 
dependent page will be invalidated.  All of these CacheRules are collected 
together using the Composite pattern during the formatting of a page.

*         Some WikiTalk is such that you can't cache.  The example that started 
this thread (DateTime.Now) is a perfect example.  In these cases, the 
invalidation collection will contain an instance of CacheRuleNever which trumps 
all the other rules and says "I can't ever be cached."



3- Bulk updates



For performance, it's useful to not send hundreds of similar invalidation 
events along from the Federation.  More importantly, the FCM can do some major 
optimization if it gets handed a pile of updates from the Federation all in a 
bundle.  The make this work, there's a single object called a FederationUpdate 
that can represent an arbitrarily complex SET of updates from the federation.  
The FederationUpdateGenerator actually bundles all the updates from the 
Federation into a single FederationUpdate and that's sent along en-masse to the 
FCM.







And a comment on a specific scenario you list below:



> For example, a WikiTalk script that scans the whole namespace looking

> for "Summary" properties and displaying them in a table can only be cached

> until anything at all in the namespace changes.



Right.   Which is why in 1.8 the caching system knows a lot about properties.  
The cache contains things like "all properties that have property 'Summary'".  
And why the federation generates add/delete/change events for properties as 
well as topics.



In 1.8, if I have WikiTalk that asks for all topics with a given topic, and 
then do it again, the underlying calculation is cached.  Importantly, this is 
caching using the same architecture above, but NOT ABOUT CACHING THE FORMATTED 
TOPIC.  There's a cache key like 'TOPICS-WITH-PROPERTY-Summary" that holds the 
list (and will be properly invalidated).  This not only makes the operation 
fast from WikiTalk but also just plain fast if it needs to be invoked natively 
inside the engine.



A scenario that illustrates why just caching at the topic level doesn't work is 
a page that generates a number of dynamically generated lists that are all 
based on the same underlying primitive (e.g., "all topics in namespace FOO").  
If this underlying primitive gets called ten times to generate ten lists and 
it's a slow primitive, then it'll take 10X for the page.  If the primitive is 
cached, it'll cost 1X.





OK, so what to do?



In my view, the above design was a good design.  I haven't followed enough of 
the details of the 2.0 restructuring to say at the class level if it would 
still be right, but I suspect it would.  With one big issue: the one Craig 
raises below about how security and caching play together.  Imagine some 
WikiTalk that displays a list of all topics in the current namespace but Sue 
and Tom both have different permissions for the various topics.  If Sue can see 
topics A, B and C and Tim can see A, B and D, the caching becomes tricky.  I 
don't know the right solution, but something that jumps out at me right away is 
that the cache keys could be extended to encode the dependency on the 
permissions checks that were passed (e.g., dependent data like a user name).  
In this example, that would mean that instead of a cache key like 
TOPICS-IN-NAMESPACE-X, you would end up with TOPICS-IN-NAMESPACE-X(Sue) and 
TOPICS-IN-NAMESPACE-X(Tom).  Not all cache entries would depend on the specific 
user (or maybe they would, not sure).



It's a start of a thought, anyway.



My overall point would be that I think the caching architecture was actually 
pretty loosely coupled and as long as the right events could be generated from 
the federation again (I assume they aren't anymore) that a lot of the old code 
could be made operational again - if we could solve the basic architecture 
question of how the caching and security interact.



/David





> -----Original Message-----

> From: [EMAIL PROTECTED] [mailto:flexwiki-

> [EMAIL PROTECTED] On Behalf Of Craig Andera

> Sent: Wednesday, August 29, 2007 12:30 PM

> To: 'FlexWiki Users Mailing List'

> Subject: Re: [Flexwiki-users] Performance analysis

>

> > I'm still trying to get the tests against my giant (1000 namespace)

> > corpus running with the perf tool.  I suspect we'll find more of

> those

> > more than twice as slow pages in a small number of cases.

> >

> > The real question to me is how do we feel about those cases.  If 98%

> of

> > the pages are "generally as fast as 1.8" but 2% are so slow as to be

> > "unusable" (because they are >10x slower) what do we think about

> that?

> > My view of course is that those are a pretty serious problem because

> if

> > people have them then they're probably part of the all-up solution

> for

> > them and if some of it stops working then some of it stops working.

> > It's a little bit like saying if 95% of the dialog boxes in Visual

> > Studio were usable but 5% were super slow/unusable what would we

> think.

>

> I have to say I generally agree with this assessment, although I'm not

> sure

> Visual Studio should be our quality metric...and yes, I realize there

> are

> two meanings in that statement: I meant it in both senses. :)

>

> Here's my general thinking as to why we're where we're at:

>

> When I added security, that was bound to slow things down a bit because

> it

> does extra checking on just about every core operation. There are

> generally

> several core operations per page. So that probably accounts for a bit

> of the

> slowness. But I think the bigger deal is what I did to the WikiTalk

> engine.

>

> When I went to add security, the biggest motivator for the

> rearchitecture

> was the caching that was present. There was a fair amount of it, and it

> was

> really intertwined with other aspects of the code. Obviously, caching

> and

> security are related: I don't want to serve David a copy of a page that

> was

> rendered for Craig if David is not supposed to see it. As a result, the

> pipelined architecture does this (more or less):

>

> NamespaceManager => Security => Caching => Property Parsing => Built-in

> topics => Filesystem Store

>

> So we check permissions against what comes back from cache, not the

> other

> way around. And that works well.

>

> The issue is that that whole content pipeline is a full level below the

> WikiTalk stuff, which interacts with NamespaceManager and Federation,

> not

> the content pipeline. So incorporating *content* caching into the

> WikiTalk

> engine would result in code that cuts across layers, which is the mess

> I was

> trying to avoid in the first place.

>

> One option is to add another layer of caching up at the web layer.

> Specifically, *output* caching. So we'd get a rendered page, and then

> if

> nothing has changed, we could just spew it again on the next request.

> This

> would make just about every page way, way, way, way faster, once it had

> been

> rendered the first time. The problem is, it's slightly hard to get

> right.

> The caching itself is pretty easy, but the cache expiration stuff is

> not.

> For example, a WikiTalk script that scans the whole namespace looking

> for

> "Summary" properties and displaying them in a table can only be cached

> until

> anything at all in the namespace changes. And any change whatsoever to

> _ContentBaseDefinition invalidates everything else...in case there was

> a

> security change at the namespace level.

>

> I can see how to do that, but it's not a small job. Also, I'm not sure

> it'll

> really solve the problem of "unusably slow pages", because for

> infrequently

> accessed pages, or even just pages that fall out of cache, they're

> still

> going to take just as long to render as they do in the current code. On

> top

> of that, we've got a *scripting engine* in the mix. Cache expiration

> based

> on changes to the content is all well and good when the content

> deterministically produces the output, but you can't cache anything

> else. So

> if WikiTalk had the equivalent of DateTime.Now, you obviously can't

> reliably

> cache that.

>

> Now, maybe it's the case that WikiTalk can only deterministically

> produce

> output based on content. I don't know. Does anyone? If so, it makes

> life

> easier. But even if not, then the other option we can look at is making

> the

> WikiTalk execution engine smarter. I believe this is what was in 1.8 -

> there

> were certainly a bunch of annotations on the WikiTalk code about what

> could

> and could not be cached. Presumably, it let us cache the results of

> particular operations, like an Array.Sort of the topics in a namespace.

> I

> never really understood that stuff, which didn't really matter, since

> it had

> to go to accommodate the new architecture. But maybe it could be added

> back

> in. David - perhaps you could fill me in a bit on how that stuff

> worked.

>

> In the meantime, I'll take a look at the old code to see if I can

> figure out

> how it worked. I don't really have any other ideas at this point: my

> attempts at profiling the existing bottlenecks with the free tools I

> have

> available have failed.

>

>

>

> -----------------------------------------------------------------------

> --

> This SF.net email is sponsored by: Splunk Inc.

> Still grepping through log files to find problems?  Stop.

> Now Search log events and configuration files using AJAX and a browser.

> Download your FREE copy of Splunk now >>  http://get.splunk.com/

> _______________________________________________

> Flexwiki-users mailing list

> [email protected]

> https://lists.sourceforge.net/lists/listinfo/flexwiki-users


-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems?  Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >>  http://get.splunk.com/
_______________________________________________
Flexwiki-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/flexwiki-users

Reply via email to