24 | | == Idea 1: The `CacheManager` == |
| 24 | == Final implementation == |
| 25 | The final implementation, committed in [8071], is a refinement of the [#CacheManager CacheManager] idea. |
| 26 | |
| 27 | * '''Creating a cached attribute''' is done by defining a retrieval function and decorating it with the '''`@cached_value`''' decorator. For example, for the wiki page names: |
| 28 | {{{ |
| 29 | #!python |
| 30 | @cached_value |
| 31 | def pages(self, db): |
| 32 | """Return the names of all existing wiki pages.""" |
| 33 | cursor = db.cursor() |
| 34 | cursor.execute("SELECT DISTINCT name FROM wiki") |
| 35 | return [name for (name,) in cursor] |
| 36 | }}} |
| 37 | |
| 38 | * '''Invalidating a cached attribute''' is done by '''`del`'''eting the attribute: |
| 39 | {{{ |
| 40 | #!python |
| 41 | def wiki_page_added(self, page): |
| 42 | if not self.has_page(page.name): |
| 43 | del self.pages |
| 44 | }}} |
| 45 | |
| 46 | * If more control is needed, for example to invalidate an attribute within an existing transaction, the attribute should be decorated with the '''`@cached`''' decorator. Accessing the attribute then yields a proxy object with two methods, `get()` and `invalidate()`, taking an optional `db` argument. For example, this is used in the case of ticket fields to invalidate them in the same transaction as e.g. an enum modification. |
| 47 | |
| 48 | * The cache is '''consistent within a request'''. That is, a cached attribute will always have the same value during a given transaction. Obviously, cached values should be treated as immutable. |
| 49 | |
| 50 | * The `CacheManager` component contains all the logic for data retrieval, caching and invalidation. Cache invalidation across processes is done by incrementing a generation counter for the given attribute in the `cache` database table. The invalidation granularity is at the attribute level. |
| 51 | |
| 52 | * There are two cache levels: |
| 53 | - A thread-local (per-request) cache is used to minimize locking and ensure that the cached data is consistent during a request. It is emptied at the beginning of every request. |
| 54 | - A process cache keeps retrieved data as long as it has not been invalidated. |
| 55 | |
| 56 | * The `cache` table is read the first time a cached attribute is accessed during a request. This avoids slowing down requests that don't touch cached attributes, like requests for static content for example. |
| 57 | |
| 58 | |
| 59 | ----- |
| 60 | ''The sections below are kept as documentation of the implementation process.'' |
| 61 | |
| 62 | == Idea 1: The `CacheManager` == #CacheManager |