A series of posts about “Web, caching and memcached” continues. Start here:
1 ,
2 ,
3 and
4 .
In these posts, we talked about memcached, its architecture, possible use, caching key selection, clustering, atomic operations and the implementation of
memcached counters, as well as the problem of simultaneously rebuilding caches.
Today we will talk about tagging caches and the possibility of dumping a group of caches into memcached right away.

')
The last, sixth post, will be devoted to various technical issues of working with memcached: analysis of statistics, debugging, etc.
Reset cache group
If we cache some data from the backend, for example, a sample from the database, sooner or later the source data is changed, and the cache ceases to be valid. And it is very desirable that the cache be dropped immediately after the change, otherwise the user, after editing, may see the old version of the object, which will undoubtedly confuse him. There is a simple variant of the situation: we change the information about the object with ID 35, and reset the sample cache of this object by the parameter ID = 35. In practice, most often the same object is explicitly or implicitly included in a large number of samples, and therefore caches.
Consider this example: we wrote a blog host, it has a large number of blogs. When one of the authors creates a new post, a large number of selections change: posts on the main page and all the second pages of the list of posts (because all posts have moved one), the number of entries in the calendar of posts has changed, the RSS has changed, and etc. Of course, we could put the caches of these samples a short lifetime, then after some time they will be reset and will display the correct information, but a too short caching time (5 seconds, for example) will give a low ratio of hits to the cache, increasing the load on DB, and longer will give the user the feeling that the information after creating the post has not been updated, which means that the post has not been added. At the same time, it can be noted that within the framework of the blog hosting site, even if we reset all the caches associated with this blog, it is a very small percentage of the total mass of caching (since there are a lot of blogs). The question remains: how to find and identify all the caches of this blog? Some of them we can easily build, for some it becomes inconvenient: for example, the number of caches for the page list of posts depends on the number of pages that still need to be calculated. What to do?
One possible solution is tagging caches. The tagging method described below is essentially the same as that described by Dmitry Koterov in his
nabla , but was developed by us independently. There are other options for tagging, for example, the
memcached-tag patch on memcached.
Cache tag
So, we introduce a new concept - cache tag. The cache is associated with some tag list. A tag in itself is a name and a version (number) associated with it. Tag version can only increase monotonically. Group caches we will call caches that have one common tag. In order to reset a group of caches, it is enough to increase the version of the corresponding tag.
At the program level, we know that this sample must be cached and that its cache will be associated with tags
tag1
and
tag2
(this fact is determined by the logic of our application). When creating the cache, we write to it, in addition to the cached sample data, the current (at the time of the cache creation) versions of the tags
tag1
and
tag2
. When receiving a cache, we consider it valid if it has not expired, and at the same time the current versions of tags
tag1
and
tag2
are equal to the versions recorded in the cache. Thus, if we change (increase) the version of the
tag1
tag, all caches associated with this tag that were built earlier will no longer be valid (because they contain a smaller version of the
tag1
tag).
Consider an example with our sample, let it be like this:
: tag1 -> 25 tag2 -> 63 : [ : 2008-11-07 21:00 : [ … ] : [ tag1: 25 tag2: 63 ] ]
Then a certain event occurred, and we decided to reset all the caches associated with
tag2
tag, i.e. we increased the tag version:
tag2++
. Changed versions of tags:
: tag1 -> 25 tag2 -> 64
Now our cache has ceased to be valid, despite the fact that its “expiration date” has not expired: the tag2 tag version, stored in it (63) does not match the current version (64).
Tag versions
Tags (that is, their versions) should be stored in the same place where we store our caches, that is, in memcached. For each tag, we will create a key with the same name as the tag, its value will be the version of the tag. It remains to decide what to use as a version of the tag? You could just use numbers, incrementing them when changing the version of the tag, but this can lead to incorrect behavior, subject to possible loss of keys. Let the version of the tag be one, we cache the sample with this tag, write the value of the tag in the cache - one. Then the key with the tag version was removed from memcached, and at the next point in time we wanted to reset the samples associated with the tag, that is, we need to increase the tag version. Since we have lost the value of the tag version, we will again set the unit, and now our cache will be considered valid, although it is reset (no matter what value to choose when increasing the version of the tag, if it was lost - the situation is always possible that the same value was used and earlier).
As a version, it is more convenient to use the current time (with sufficient accuracy, for example, to milliseconds). Then increasing the tag version will always give a new, larger version, even if the previous version is lost. The tag version is generated on the frontend, their system clock must be synchronized (other functionality will not work without it, for example, correct calculation of the shelf life of caches with a short lifetime), so there should not be any problems with this choice of the version calculation method
Using the current time as a version of the tag gives another advantage in the situation when the project database is arranged according to the master slave replication scheme. When changing the original object in the database, we change the version of the tag associated with it (write the current time there, that is, the time of the change). In another process, we find that the cache is outdated, that is, it needs to be rebuilt, rebuilding is a read request (
SELECT
) that needs to be sent to the database slave server, but due to replication delays, the slave server still might not receive the current version of the object in DB, as a result, we dropped the cache, but when it was rebuilt, the old version of the object was again cached, which is unacceptable. You can use the version of the tag when deciding which database server to send the request to: if the difference between the current time and the version of a cache tag is less than a certain interval determined by the maximum replication delay, we send the request to the master database server instead of the slave.
Using such a tagging scheme increases the number of requests to memcached, since
we need for each cache to get versions of its tags. Overhead can be reduced by using multi-get memcached requests, as well as local caching of memcached keys within the same process (if the same tag is tied to multiple caches).