Main  /  Edit  /  History  /   /  Users Area

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:

  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

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:

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:

  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 | Top | Asynchronous I/O

About

CppCMS is a web development framework for performance demanding applications.

Support This Project

SourceForge.net Logo

Поддержать проект

CppCMS needs You


Navigation

Main Page



Valid CSS | Valid XHTML 1.0