Skip to content

Instantly share code, notes, and snippets.

@amoslanka
Created June 10, 2015 00:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amoslanka/8ad9d992c5eea9d81194 to your computer and use it in GitHub Desktop.
Save amoslanka/8ad9d992c5eea9d81194 to your computer and use it in GitHub Desktop.
Cache Marks

Cache Marks

An introduction of the pattern of using cache “marks” for use in building cache keys and indicating resource state uniqueness.

A cache mark is a simple value that represents a unique state of a resource or group of resources as a whole or in a particular context. A commonly used (but not referred to as) cache mark is a resource’s updated_at timestamp. It is updated to the current time when a resource is updated allowing for cache keys that use that mark to automatically invalidate caches based on previous versions of that resource’s mark. In the case of a group of resources a mark may be used to invalidate keys whenever any member of the collection is modified or removed.

Cache marks must be stored separately from the resource itself when:

  • a lookup of that mark must be performed more quickly than, for example, a database query
  • the mark represents a context of the resource, such as in the context of an associated record
  • the mark represents a group of resources and is not reliably updated as a result of all possible changes to the group. For example, if a member is removed from a collection, a query for the latest updated_at timestamp is not reliable in that the removed member may not have represented the most recently updated item and its updated_at timestamp would not be used in a query before or after its removal.

Cache marks could be stored on any persistence layer, depending on the needs of the users of those marks. For the most simple implementation, they could be treated as cached values themselves using Rails.cache or as key/value pairs in Redis. For most cases it makes sense to use the current AppTime as the value of the mark, as it removes the risk of unintentionally using formerly used mark values and removes the need to query other data (such as collecting a list of ids of all items in a collection) in order to encode a unique mark.

In a practical use case, building a Recommendations System relies heavily on cached recommendation calculations. Recommendations are generated for a user on demand and are requested often. In a system that relies on querying a database many times over for the various ways content can be recommended, relying on implicit cache marks like the timestamps of resources on which the recommendation affinities are implied would result in multiple queries just to fetch the marks required to build a cache key. At that point, the same queries necessary to return the recommendations have already been performed by the building of the cache key. A cache mark, stored in a more immediately available persistence layer removes the need for cache key building queries and speeds up the entire process of building recommendations. A simple recommender example is the BookmarkedRecommender. The resource group represented is a collection of HomeContent records and the context is the user for which the recommendations are being generated. Updating a cache mark “marks/user/1/home_contents” with the latest timestamp whenever any of that user’s bookmarks are added, removed, or updated will automatically establish that mark as unique to the latest state of all of a user’s bookmarks. At this point, a cache key can be built without any queries at all.

Outside of the use of timestamps, current existing uses of this pattern are already in use on the app, including the use of a cache_version in AsariIndex::Cache and APP_VERSION in LibraryProgressSerializer, among others. In the case of the latter, APP_VERSION is a mark that represents a unique state of the app as a whole, as the code that renders anything cached using that mark may have changed with the latest deployment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment