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.
Contents
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):
modify the dispatcher entries - the additional information about caching is needed there - sometimes we will not want to cache the given URL
annotate the controllers - we have to tell the controllers what should they invalidate when the operation has succeed
change the server configuration files - the server callback functions should point to the eptic_fe modules
edit the .app file - this step is needed only if the application is intended to run in the embedded mode. In case of running in the interactive mode, the application environment variable will be overwritten.
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:
/item/all - yes, it should be cached permanently since it will be the most frequently visited page
/item/show/N - yes, but it should be a normal cache
/item/buy/N - it should not be cached at all
/item/admin/* - all admin activities should remain uncached
/user/* - all user activities should remain uncached
/login - normal cache
/do_login - not cacheable
/logout - not cacheable
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:
browser:buy_item/1 - /item/all and /item/show/N where N is an id of the bought element
item_admin:do_add/1 - /item/all
item_admin:delete/1 - /item/all and /item/show/N
item_admin:do_edit/1 - /item/all, /item/show/N
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.
