alphagov/whitehall — pull request #1677 by tekin (2014-08-21 11:37:02+00:00)
Title: FOR COMMENT: Form field caching

**This demonstrates a pattern we can use to speed up the document creation/editing forms in whitehall.**

The edit and create pages for new documents on whitehall takes anything between 3 and 10 seconds to load. This is both unacceptable as a user experience, and also has the potential to cause us some real headaches as more and more organisations (and users) transition over to whitehall. By using some careful caching techniques, we can reduce the load time significantly (closer to 1 second) without reworking the entire user interface.

The reason these pages take so long to load is that the form is huge and takes a long time to construct. The new publication form for example is 919KB of HTML. A large chunk of this is the 12,446 `
` tags that are used to tag the content to other entities (for example: policies; topics; ministers; lead organisations; supporting organisations; world locations; topical events... etc). Generating these select/option fields is what takes the bulk of the time to construct the page. Ultimately, we want to redesign the document workflow to simplify these pages, but in the mean time we can improve things considerably with some caching.

The technique demonstrated in this pull request works on two levels, and the commits are split as such.

**1. Cache a data structure representing the list of taggable things**

Most of the taggable things change infrequently (e.g. the list of topics, ministers, etc). Instead of loading all the records from the DB to construct the select fields on each request, we can cache a data structure representing the taggable things (name and ID pairs). We combine this with a digest-based caching key that will automatically expire when any of the taggable things should be updated, deleted, or if a new one is added. In the example here for topics, this is generated by hashing all the "updated_at" timestamps of the existing topics. Note that this means a (quick and cheap) query is still required to generate the digest. Compared to the cost of the full query and loading of all the records however, this is very cheap.

**2. Cache the fragment representing the option tags where possible**

The second level of caching is of the option tags themselves. Assuming the cached data structure hasn't changed, and none of the option tags are currently "selected", we can use a cached fragment of the HTML itself instead of re-rendering all the option tags.

In this instance, around 300ms is shaved off the overall page load time. This is only a marginal gain, but the cumulative effect of applying this technique to all the select fields will be significant. It's also worth noting that the "topics" select is probably the simplest of the lot (no translations or other associations to be loaded and a relatively small number of them) and so has the smallest potential performance gain.

**Other notes:**

* Generating the digest key is probably going to be the trickiest part of this. For example, the "ministers" list is generated by combining the list of ministerial roles with their current role holders. The dependency structure will need to be appropriately set using the `:touch` option on the associations such that a reliable digest key can be generated (see [How key-based cache expiration works](https://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works). The advantage of using this technique is that there is no need to have explicit calls to expire cached data - you simply rely on the fact that the digest will change when the data changes.
* Because we never explicitly expire old data, we need to use a cache store that will automatically evict old data, e.g. memcached.
* We'll see the most gains from this if/when we have a centralised cache server. Currently, we only have provisions for individual memcached servers on each backend box, which means each backend box will need to maintain its own cache rather than sharing a central one.
* This pattern fits fairly nicely with the proposed "register-of-all-the-things". Once that exists, we can simply replace the cached data containers with those pulled directly from the registry itself.