Advanced Caching
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:
- The unique key that identifies it.
- The life time of the entry that can be infinite
- The set of triggers that can invalidate the cache.
For example we have following triggers set:
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"
- "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 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.
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 ... %>
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:
- Serialization and de-serialization of objects takes time. Rendering even simple takes time. So caching entire page would give the best performance.
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 | Top | Asynchronous I/O →