Primer: the cache that knows too much

Every so often at Songkick we have what we call innovation days – a day or two where everyone can work on whatever they like to make our product, technology or workplace better. We did one this last Friday, and I made a start on something I’ve been wanting to do for a long time: it’s called Primer.

This year a couple of development platforms have sprung up that make doing real-time applications considerably easier: LunaScript and Fun. They both aim to let you write a web app by writing your templates in the usual way, but then they deal with updating the client and synching state for you. From the Fun intro:

When you say "Hello " user.name, you don’t mean just “render the value of user.name right now”. Rather, you mean “display the value of user.name here, and any time user.name changes update the UI.

Fun will update your UI in real-time, keystroke by keystroke. I can’t do that yet, but I can do something similar: automatically update caches when your ActiveRecord model changes. Consider:

<% primer '/users/1/name' do %>
  <p><%= @user.full_name %></p>
<% end %>

This is just wrapping part of a template in a caching block, much like Rails fragment caching. Now, surely your development tools ought to be able to figure out that the cache key /users/1/name depends on the full_name attribute of @user, right? It says so right there. So this is what Primer does: it caches your HTML fragments and figures out which bits of your database they depend on. When your database changes, it updates the cache. No sweeper code, no combing through call stacks, no dealing with message buses.

Now it turns out that using paths as cache keys has two really nice side effects. Firstly, you can use a declarative routing scheme to tell Primer how to calculate values:

Primer.cache.routes do
  get '/users/:id/name' do
    user = User.find_by_id(params[:id])
    user.full_name
  end
end

This means that, by moving some rendering logic out of your templates, you can tell Primer how to regenerate cache keys, so it can update the cache with new data instead of just invalidating it. This can be useful for long-tail pages that don’t get much user traffic but need to be fast for the Googlebots.

Secondly, it means that all your cache keys are valid Faye channel names, and can be used to update the client in real time. If you put this in your view:

<%= primer '/users/1/name' %>

Primer will use the '/users/:id/name' route to generate the value, cache it, and also will update any clients viewing the page when that cache slot is updated. You don’t need to write any of the network code for this, it’s all done for you, so that template fragment gets you real-time updates for free.

Right now it’s very much a proof-of-concept, just to show that this sort of thing is possible. It needs to store a lot of data about your model to do its job, and needs to perform a lot of ugly reflection on ActiveRecord to propagate changes correctly. It still probably screws up a lot of edge cases, most glaringly it can’t figure out when a has_and_belongs_to_many collection changes. It can figure out a lot of common has_many/belongs_to/has_many :through relationships. I have taken some steps to ease the load by making it really easy to create background workers to update your caches, but it’s definitely not for production.

I’ve rambled on enough, so if you want to poke at a half-baked week-old project I’d really like feedback on whether this can be turned into a viable bit of production software. Just gem install primer or get it on GitHub.