Spark output caching
aspnetmvc, caching, internet, programming, spark July 28th, 2009
Just finished writing the documentation for the most recent feature added to Spark. It provides output caching for sections of a template that could be costly in terms of rendering or data acquisition.
Part of this comes back to Phil’s blog post about Donut Hole Caching in ASP.NET MVC. Especially below where he writes, as he’s thinking out loud, “This would have to work in tandem with some means to specify that the bit of view data intended for the partial view is only recreated when the output cache is expired for that partial view, so we don’t incur the cost of creating it on every request.”
I have to admit I’m not necessarily a huge fan of the built-in ASP.NET page caching directives… Also didn’t particularly care for the forced alignment of partial-boundaries and cache-boundaries. I would hate to have a larger number of smaller files on disk simply because that’s the breakdown at which I wanted to cache the individual parts. In an ideal world I would like to be able to quickly and easily mark an expensive bit as cacheable, and provide the caching details that relate specifically to that bit, and be done with it.
So - enter Spark caching:
stuff <cache key="employeeId" expires="300"> this is stuff about the employee </cache> stuff
Kind of simple, really. The key works like the <%@OutputCache%> VaryByParam value, but it can be a comma delimited csharp expressions of anything which makes the cached information unique.
There’s a ICacheSignal interface which takes the place of custom CacheDependency classes in the ASP.NET caching framework.
public interface ICacheSignal
{
event EventHandler Changed;
}
If you want fine-grained control to clear out-of-date cached data you would provide an instance of your implementation of this signal, or you can use the built-in ChangeSignal class. So that could be something as simple as:
stuff <cache key="employeeId" signal="MyApp.Signals.EmployeeTables"> this is stuff about the employee </cache> stuff
public static class Signals
{
public static ChangeSignal EmployeeTables = new ChangeSignal();
}
You app would need to provide the glue to watch the tables and call Signals.EmployeeTables.FireChanged() of course. It does require a bit more effort, but you’re free to implement your signaling down to whatever level of granularity you would like. If you cache all of the comment html on a blog post, for example, you could keep a dictionary of change signals per-post-id around to drop exactly the correct amount of cached information when a comment is added or removed.
The implementation was a bit tricky in parts, but it should support the all of the cases where once attributes and named content are used inside or around cache elements.
Ah, yes! Back to the original need for avoiding the cost of gathering view data when the related bit of the view is cached. For that you’d use a trick with a ValueHolder class which enables you to keep your data acquisition code in the controller’s action, yet enables a cache hit to avoid the cost of that data acquisition.
public ActionResult Details(int id)
{
var data = new NorthwindDataContext();
ViewData["employeeId"] = id;
ViewData["employee"] = ValueHolder.For(
() => data.Employees.Single(x => x.EmployeeID == id));
return View();
}
In that case for example you could have a cache key=”employeeId” and only use the employee.Value property inside that cache. That way if the html for that section (for that employeeId) has already been created and stored, then the second time through .Value property will never be used and the lambda will never be executed. For example:
<viewdata employeeId="int" employee="ValueHolder[[Employee]]" />
<cache key="employeeId">
<h3>${employee.Value.FirstName} ${employee.Value.LastName}</h3>
this is stuff about employee number ${employee.Value.EmployeeID}.
</cache>
So that’s a kind of nice way to cut out WCF and SQL costs without violating MVC best practices by having your views fetch data. After all it’s almost never the rendering costs that are the real problem in the first place.
There’s also a trick you can use with <viewdata> to get rid of the .Value. when you’re using the object inside a ValueHolder.
<viewdata employeeId="int" employee.Value="Employee employee" />
<cache key="employeeId">
<h3>${employee.FirstName} ${employee.LastName}</h3>
this is stuff about employee number ${employee.EmployeeID}.
</cache>
Updated, in answer to Adam’s question in the comments
A value holder can also by used inside of a strongly typed model, like this.
public class EmployeeDetailsModel
{
// lazy-loaded data
public ValueHolder<int, Employee> EmployeeHolder {get;set;}
// aliasing .Key and .Value
public int EmployeeID {get {return EmployeeHolder.Key;} }
public Employee Employee {get {return EmployeeHolder.Value;} }
}
public ActionResult Details(int id)
{
var data = new NorthwindDataContext();
var model = new EmployeeDetailsModel
{
EmployeeHolder = ValueHolder.For(id,
key => data.Employees.Single(x => x.EmployeeID == key))
};
return View(model);
}
And the usage in the view is about what you would expect.
<viewdata model="EmployeeDetailsModel" />
<cache key="Model.EmployeeID">
<h3>${Model.Employee.FirstName} ${Model.Employee.LastName}</h3>
this is stuff about employee number ${Model.Employee.EmployeeID}.
</cache>
July 29th, 2009 at 12:19 am
Great work Lou.
Going to give this a try.
Can you still use the strongly typed model?
July 29th, 2009 at 1:36 am
[...] Spark output caching - Louis DeJardin talks about the new caching features of the Spark View Engine, influenced by Phil Haack’s discussions of donut caching [...]
July 29th, 2009 at 1:38 am
The ValueHolder idea is pretty slick. I had a similar idea for strongly typed models sent to the view too, but it’ll be more difficult to implement in medium trust. But maybe in ASP.NET 4! I’ll see if I can prototype it at some point.
July 29th, 2009 at 7:02 am
[...] Spark output caching (Louis DeJardin) [...]
July 29th, 2009 at 10:07 am
Yep, @Adam, that should work fine if you don’t mind changing the view’s model to have ValueHolder properties. I appended an example above because I can’t put formatted code in comments.
Thanks, @Haacked! The holder class itself turned out to be a very simple device - 77 lines of code without comments.
http://github.com/loudej/spark/blob/master/src/Spark/ValueHolder.cs
I look forward to seeing what you’re considering. The caching element in spark isn’t directly coupled to the value holder at all, so I’m sure something intrinsic to ASP.NET for just-in-time data acquisition would drop right in place.
July 29th, 2009 at 10:40 am
Spark output caching - Louis DeJardin…
Thank you for submitting this cool story - Trackback from DotNetShoutout…
July 29th, 2009 at 11:48 am
I don’t know the answer to this … but if you spend a lot of time fine tuning your data cache, is there benefit to view/output caching? I guess I’m wondering if a two pronged approach is best, or a real focus on the data side more?
July 30th, 2009 at 10:28 am
Well, as with most things when you’re considering design alternatives, the answer is probably “it depends.” :)
If you have aggressive caching at more than one level, say you’re real pushing for ninety-plus percent coverage of both the output and the data, from a utilization standpoint you’re keeping the innermost level resident for no real reason. Even if the memory isn’t an issue it’s probably not an efficient use of your time.
Whether to focus on data cache or output cache probably depends on the nature of your data, your data access technology, and the nature of how the site is displaying the information.
If your template is bringing dozens of aspects of related data together, you might be better off caching that output and consider letting the data fall through to raw access rather than bother caching it. A threaded forum conversation for example has all the text of the replies and their relationships as well as certain amount of information about every user, their community ranking, profile image, online status, etc.
On the other hand your data could be in nice atomic chunks that has a natural key without a lot of complex relationships. The access technology could also be one that gives you plain-old-class-objects which are beautifully cacheable. WCF data contract classes for example are very nice. NHibernate objects which have session affinity, or “hot” Linq collections, are not so much.
That information could be used in many different places on your site. So caching the data, instead of each of the places it’s used, would cover more of the problem with less effort.
July 30th, 2009 at 3:37 pm
Thats for that awesome reply.
July 30th, 2009 at 3:37 pm
er …. Thanks!
August 9th, 2009 at 8:04 pm
Good staff, Lou.
But why should you make ValueHolder class synchronized? And it synchronized incorrectly anyways: if context switch occurs between lines #44 and #46 there is a possibility for duplicate evaluation.
August 10th, 2009 at 10:17 am
There’s not a real reason if it’s only used between action and view, but as a general-purpose class you never know what use it might be put to.
I’m pretty sure the synchronization is okay in any case. Only one thread at a time will pass through the lock, and the _acquire is nulled before the first thread exits, so no additional threads which may have reached the lock statement in that time will never reach the statement that calls _acquire.
August 17th, 2009 at 11:44 am
[...] Spark Output Caching: Louis DeJardin writes about the latest feature of the Spark view engine - the ability to do granular output caching. [...]
December 22nd, 2009 at 12:20 am
Вот это да! Спасибочки! Теперь на целый час есть работа! :)