<!--toc--> |
|
## Introduction |
|
Unlike many caching systems available today in different |
web frameworks that are based on "key-value-timeout" |
concepts, CppCMS's cache provides "key-triggers-value-timeout" |
model that provides an easy and efficient way to |
invalidate cache - keep it consistent. |
|
Each cached object has following properties: |
|
1. The unique key that identifies it. |
2. The life time of the entry that can be infinite |
3. The set of triggers that can invalidate the cache. |
|
For example we have following triggers set: |
|
![Triggers](/pics/triggers-scheme.png) |
|
So we can invalidate multiple keys by rising different |
triggers. |
|
For example. when we update article about `drugs` we |
rise a trigger `new_drugs` and invalidate both `health` |
news and `news_all` that depend on this trigger. |
|
On the other hand the "technology" news remain untouched. |
|
This method allows us to keep the web site both |
up-to-date and fully cached. |
|
|
## Managing Triggers |
|
### Direct Handling |
|
When you cache a page you can easily add a trigger |
to it using `cppcms::cache_interface::add_trigger()` function. |
|
For example: |
|
if(cache().fetch_page("my_page")) |
return; |
|
// Create some content |
|
cache().add_trigger("my_trigger"); |
cache().store_page("my_page"); |
|
Now the `my_page` object would depend on two triggers |
`my_trigger` and `my_page` itself. |
|
You can clear the cache by rising a trigger: |
|
cache().rise("my_trigger"); |
|
Would create cache for all objects that depend on it |
including `my_page`. |
|
If you use cache interface to store some objects |
or text frames in it you can pass a set of triggers |
directly as a parameter. |
|
std::set<std::string> my_triggers; |
my_triggers.insert("my_trigger"); |
|
cache().store_data("my_object",my_object,my_triggers); |
|
Note: `my_object` trigger would be automatically added |
to the `my_object` key. |
|
### Automatic Dependencies Tracking |
|
CppCMS cache allows to track object dependencies |
automatically. |
|
Let's assume we have following code: |
|
if(cache().fetch_page("foo")) |
return; |
|
if(!cache().fetch_data("settings",settings)) { |
load_settings(settings); |
cache().store_data("settings",settings); |
} |
|
// do something |
|
cache().store_page("foo"); |
|
Now we use a "setting" object in order to create |
"foo" page. When cal call `fetch_data()` or `store_data()` |
the "settings" trigger is added as a dependency to "foo". |
|
More then that, if "settings" has its own triggers: |
|
if(!cache().fetch_data("settings",settings)) { |
load_settings(settings); |
std::set<std::string> settings_triggers; |
settings_triggers.insert("bar"); |
cache().store_data("settings",settings); |
} |
|
|
Then our "foo" page would automatically depend |
on "settings" and "bar" triggers, so for example |
rising "bar" would invalidate both "foo" and "settings". |
|
The automatic recording can be disabled by |
setting `no_triggers` parameter to "true" for `store_data()` |
and "fetch_data()" for example: |
|
|
if(!cache().fetch_data("settings",settings,true)) { |
load_settings(settings); |
cache().store_data("settings",settings,-1,true); |
// -1 for infinite timeout |
} |
|
### Nested Tacking |
|
The triggers are recorder for the frame, but sometimes |
we want to record the triggers on lower level. For example: |
|
- "page" |
- "settings" |
- "local_settings" |
- "global_settings" |
|
Like: |
|
if(cache().fetch_page("foo")) |
return; |
|
if(!cache().fetch_data("settings",settings)) { |
if(!cache().fetch_data("local_settings",ls) { |
... |
} |
if(!cache().fetch_data("global_settings",gs) { |
... |
} |
load_settings(settings,ls,gs) |
cache().store_data("settings",settings); |
} |
|
cache().store_page("foo"); |
|
But the `local_settings` and `global_settings` triggers |
would not be recorder for "settings" because "store\_data()" |
function does not know about them. |
|
So in order to handle such situation we have a |
[triggers\_recorder](/cppcms_ref_v0_99/classcppcms_1_1triggers__recorder.html) object: |
|
We change our code in following way: |
|
if(!cache().fetch_data("settings",settings)) { |
cppcms::triggers_recorder rec(cache()); |
if(!cache().fetch_data("local_settings",ls) { |
... |
} |
if(!cache().fetch_data("global_settings",gs) { |
... |
} |
load_settings(settings,ls,gs); |
cache().store_data("settings",settings,rec.detach()); |
} |
|
We start an instance of a `triggers_recorder` and |
and the end of the section we recorder the triggers |
on we detach it by calling `detach()` member function |
that returns a set of triggers, in our case |
the `local_settings` and `global_settings` |
|
We got all we needed |
|
## Recording Text Frames |
|
### From The Application |
|
Consider we have a situation where some part |
of the HTML frame is shared for many pages |
and it requires some non-trivial methods |
to create it (for example make some complex query |
to the DB in order to fetch all the data we need). |
|
CppCMS provides us a simple [copy_filter](/cppcms_ref_v0_99/classcppcms_1_1copy__filter.html). |
|
We can use it as following: |
|
std::string frame; |
if(cache().fetch_frame("key",frame)) { |
response().out() << frame; |
} |
else { |
cppcms::copy_filter tee(response().out()); |
... |
// generate something heavy |
... |
cache().store_frame("key",tee.detach()); |
} |
|
Note: If the frame exist in the cache, we |
write it to the output stream directly, but if |
it does not, we generate it in a usual way, |
but we attach a small filter `tee` to the |
output stream that would copy all the data |
to a temporary buffer and would write it |
to the stream as well. |
|
When we call `tee.detach()` we remove the filter |
and return the entire frame we had recorder. |
|
### From The View |
|
It is much more natural to cache some frames |
from the views - the template engine. CppCMS |
provides such a tool: the [`<% cache ... %>`](/wikipp/en/page/cppcms_1x_templates_flow#Caching.Elements) tag. |
|
For example |
|
<% template foo() %> |
<html> |
... |
<body> |
|
<% cache "sidebar" %> |
<div id="sidebar"> |
... |
</div> |
<% end cache %> |
... |
</body> |
</html> |
<% end template> |
|
|
However frequently it is not enough to "just cache" |
some HTML frame, we usually need to prepare some data |
that would be rendered, so we would like to be |
able to do something, like updating the rendering |
content with some data from database in case of |
cache miss. |
|
We can add a callback to the content that would |
be called in case of cache miss. |
|
|
<% cache "sidebar" on miss update_content() %> |
<div id="sidebar"> |
... |
</div> |
<% end cache %> |
|
|
So we add the the content that is rendered a callback |
`update_content()` that may look like: |
|
my_content : public cppcms::base_content { |
booster::function<void()> update_content; |
}; |
|
|
And in the application we would define it like: |
|
my_content c; |
// bind a member function `update_content` to the |
// callback |
c.update_content = std::bind(&my_app::update_content,this,&c); |
|
And our update function would look like: |
|
void update_content(my_content *c) |
{ |
c->some_data = fetch_some_bid_data_from_db(); |
} |
|
So on cache miss `update_content` would be called |
and setup all the content for the rendering of the frame. |
|
|
## Notes about Page level caching |
|
The page level caching is the best cache that can be |
done. If you can always cache entire page rather |
then for example database objects that are rendered |
in the view. |
|
There are two reasons for this: |
|
1. Serialization and deseralization of objects takes time. Rendering even simple takes time. So caching entire page |
would give the best performance. |
2. CppCMS pages are cached already gzip compressed. So you do not only save the time required to compress the page you |
also reduce the response time as the data that is sent is |
much smaller then original HTML. |
|
Thanks to the fact the _all_ modern browsers support |
gzip compression makes the page level caching fast |
and efficient. |
|
|
## Triggers or Timeouts |
|
CppCMS provides you two ways to manage cached objects |
life time: by limiting their life time by a timeout or |
by explicitly removing them from the cache using triggers. |
|
If certain page is updated very frequently and visited |
very frequently then it may be wise to setup a small timeout and never invalidate it manually. |
|
However if you can define a sources of page invalidation |
However if you can define a sources of page invalidation, |
design a triggers set carefully and define dependencies clearly then you will be able to almost never use |
timeouts and keep the web site up-to-date. |
|
For example CppCMS blog's cache system is entirely |
triggers based and never uses timeouts. On the other |
side this wiki uses triggers for some pages and uses |
timeouts for some others like for example index that |
any page update would trigger its invalidation. |
|
Finally the safest thing is to use both, thus even |
if you have a bug and missed some dependency the page |
would eventually be updated. |
|