#pragma section-numbers off

= introduction =
MoinMoin needs to cache a lot of data to perform better. This includes:
 * rendered pages (html)
 * raw translations
 * marked up translations (since wiki markup is now used in translations)
 * ACLs
 * Groups and Dicts

= requirements =

A good caching framework for moin should be able to:
 * track dependencies
 * have the ability to define cache timeouts
 * have automatic dependencies to some extent

== tracking dependencies ==
If, for example, the wiki source of a page is modified, everything derived from that source (cached html for example) must be discarded.

Therefore, I propose that whenever something is stored into the cache, it is listed whatever it depends on. But also see below.

== cache timeouts ==
The month calendar plugin for example should note that the cache for a page containing it must be refreshed at least after midnight, because then a new day must be displayed. A timeout should always be given as an absolute date/time, not as an offset, to ease implementation. If a timeout is set multiple times, the timeout is set to the most recent (ie. it is only set if the new value is more recent than the old value).

== implied dependencies ==
Since MoinMoin has a lot of dependencies on the page source code, it would be ideal if the cache used a multi dimensional space rather than a 1D space, so that it would automatically invalidate the sub-dimensionions for a page if the page source was changed.
Calling {{{cache.invalidate(pagename)}}} would invalidate everything that has the same pagename, which was for example stored by a call to {{{cache.get( (pagename,'text/html'), fun)}}} (note the pair being used!).

= two-level cache discussion =
The CGI version of MoinMoin will not benefit from an in-memory cache, so it has to write data out to the disk at all times.

It is conceivable that the persistent versions of MoinMoin would benefit from caching some frequently used data in-memory, but the algorithm for that would need to be able to limit the cache size. But I need to find some algorithms for implementing a LRU fixed-size cache with items that are not fixed-size.

= proposed API =

The cache should be able to store any python object that is immutable in the application logic.

{{{#!python
class cache:
    def get(self, item, creationfun)
    def invalidate(self, item)
    def set_timeout(self, item, timeout)
    def add_dependency(self, item, on)
}}}

Throughout, {{{item}}} is either a string or a tuple. If it is a tuple, then the tuple values are regarded as coordinates in a multi-dimensional space. When invalidating a certain tuple, all sub-dimensions of that tuple are invalidated as well.

 get:: You call {{{get}}} whenever you need some result that should be cached. If the cache has nothing stored for the requested item, then it calls {{{creationfun}}} and stores the result. {{{creationfun}}} is called without arguments, in most cases it is probably going to be a lambda defined inline in the call to {{{get}}}. You should never call {{{creationfun}}} or equivalent itself as that would bypass the cache (unless of course you need to bypass the cache for some reason).
 invalidate:: invalidate an entry in the cache, also invalidating everything that depends on the given item. {{{invalidate}}} could possibly be implemented as {{{set_timeout(self, item, 0)}}}
 set_timeout:: sets the timeout for the given item. This call must be valid as a nested call from {{{creationfun}}} inside {{{get}}}. Whenever an item times out, everything that depends on it must also time out.
 add_dependency:: adds a dependency to a cached item. {{{on}}} can either be a string (then it is treated as a ''name'' key) or a tuple {{{(dim1, dim2, ...)}}}

= examples for multi-dimensional caching =
Lets say something was stored as:
{{{cache.get( ('FrontPage', 'text/html'), make_frontpage_html)
cache.get( ('FrontPage', 'linklist'), make_frontpage_linklist)
}}}

Now calling
{{{cache.invalidate('FrontPage')}}}
(which is just a shortcut for 
{{{cache.invalidate(('FrontPage'))}}}
would invalidate the 3 items:
 * `('FrontPage')`
 * `('FrontPage', 'text/html')`
 * `('FrontPage', 'linklist')`
It would also invalidate an item {{{('IncludeFrontPage')}}} if that item was linked to the frontpage with, for example, the following call:

{{{
cache.add_dependency('IncludeFrontPage', 'FrontPage')
}}}

It would also invalidate that item if it was linked via

{{{
cache.add_dependency('IncludeFrontPage', ('FrontPage', 'anything'))
}}}

even if the tuple {{{('FrontPage', 'anything')}}} doesn't really exist in the cache.

----
See also [[http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/276642|FIFOCache]].
----
CategoryFeatureRequest