Tutorial Step 7 - Caching the content

This step covers the activities that must be performed to migrate from the single_node architecture to the single_node_with_cache one.

To Do list

In fact, switching between the architecture is almost painless: it does not force us to rewrite the code, change the concept of the project and other invasive activities. The main engine of the cache is stored inside the eptic_fe application - see wiki page for details.

At first we should prepare a list of things we need to do (we do not want to miss anything):

Let's do this!

Modifying the dispatcher entries

As a first step towards the cache migration we must edit the dispatcher entries. By default, all entries are treated as a normal cache what means that will be stored in cache for a given time (10 minutes by default). The modification will lay on adding the fourth element to the dispatcher configuration tuples: a list of the optional parameters.

Let's decide which actions we want to cache:

For the permanent cache we should use the following entry:

   1 {dynamic, "^/item/all$", {browser, list_all}, [{cache, persistent}]}.

For the normal cache we can skip the options list (or if we want to be expressive, we can write it):

   1 {dynamic, "^/item/show/(?<id>[0-9]+)$", {browser, show_item}, [{cache, normal}]}.

The non cacheable content should be marked with no_cache parameter:

   1 {dynamic, "^/item/buy/(?<id>[0-9]+)$", {browser, buy_item}, [{cache, no_cache}]}.

Annotating the controllers

Since we must know when the cache is going to be invalidated (although there is a special type of cache - persistent - that does not remove its content after some time, though we might want changes to propagate immediately - so when someone removes the item we want that change to be visible right after removal - not after, let's say 15 minutes).

The Erlang Web framework provides four annotations for dealing with that: invalidate, invalidate_if, invalidate_groups and invalidate_groups_if. All of them are of type after what means that will execute after processing the controller's function.

Usually, the cache entries are stored under URLs as a keys. Let's point out, which controller functions should invalidate which cache entries:

Other elements should not be annotated.

For invalidating /item/all we will use the exact regular expression: ^/item/all$. Since in the compliance time we do not know the N in /item/show/N we must use the parametrized regexp: {expand, /item/show/{id}} where the id must be set within the controller. So for the pair /item/all and /item/show/N we will include e_cluster_annotations.hrl header file and use the following annotation:

   1 -include("../../eptic-1.3/include/e_cluster_annotations.hrl").
   2 
   3 ...
   4 
   5 ?INVALIDATE(["^/item/all$", {expand, "^/item/show/{[integer]id}$"}]).
   6 buy_item(Item) ->
   7     wpart:fset("id", Item#item.id),
   8 ...

For the single item invalidation, we should use:

   1 ?INVALIDATE([{expand, "^/item/show/{[integer]id}$"}]).
   2 ...

Do not forget about the setting the id request dictionary variable issuing the command wpart:fset("id", Item#item.id) inside the controller function annotated with ?INVALIDATE that uses wpart expand.

Change the server configuration files

Our next step is editing the server configuration file (for Inets it is config/inets.conf). The Modules entry should now point to e_fe_mod_inets instead of e_mod_inets:

Modules e_fe_mod_inets mod_get mod_head mod_log

Edit the .app file

The last thing to do is to change the application environment variable. Open lib/eptic-1.3/ebin/eptic.app file and change the env section from containing entry node_type from single_node to single_node_with_cache:

...
        {env, [
                {upload_dir, "/tmp"},
                {template_expander, wpart_xs},
                {template_root, "templates"},   
                {node_type, single_node_with_cache}
        ]},
...

Running the project

Great! That's it - we are now ready to run the application in the single_node_with_cache mode! Let's try it out:

   1 $ ./bin/start_interactive inets single_node_with_cache

Visit some pages, see how the application behaves - from the user point of view nothing should change (apart of the performance - but running the service on the localhost only will not make a difference).

Cache internally is kept in four ETS tables: cache_persistent, cache_a, cache_b and cache_timeout. You can list their content to make sure that the content is really there:

   1 1> ets:tab2list(cache_persistent).
   2 [{<<"/item/all">>,
   3   ["controller"],
   4   <<131,104,2,108,0,0,0,3,104,2,100,0,12,99,111,110,116,
   5     101,110,116,95,116,121,112,101,...>>}]
   6 2> ets:tab2list(cache_a).         
   7 [{<<"/item/show/1">>,
   8   ["controller"],
   9   <<131,104,2,108,0,0,0,3,104,2,100,0,12,99,111,110,116,
  10     101,110,116,95,116,121,112,101,...>>},
  11  {<<"/item/show/2">>,
  12   ["controller"],
  13   <<131,104,2,108,0,0,0,3,104,2,100,0,12,99,111,110,116,
  14     101,110,116,95,116,121,112,...>>}]

Ok, now is the time for the invalidation. Let's assume, that the item number 2 was unavailable. We will edit it as an admin and make it once again ready to buy. After this operation, our cache entries for number 2 and for all items should be erased (but the /item/show/1 should stay in the cache though).

Download

This tutorial step is over, the package containing the sources of the application can be downloaded from here.

Tutorial/Step7_Caching (last edited 2011-03-02 15:33:46 by Michal Ptaszek)