<!--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.
|
|
When we update an 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 clear cache for all objects that depend on it
|
including `my_page`.
|
|
If you use the 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 we call `fetch_data()` or `store_data()`
|
the "settings" trigger is added as a dependency to "foo".
|
|
More than 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 recorded 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 exists 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 de-serialization 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,
|
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.
|
|
---
|
|
← [Internationalization and Localization][prev]
|
| [Top](#maincontent)
|
| [Asynchronous I/O][next] →
|
|
[toc]: /wikipp/en/page/cppcms_1x
|
[prev]: /wikipp/en/page/cppcms_1x_i18n_and_l10n
|
[next]: /wikipp/en/page/cppcms_1x_aio |
. |