Home » Blog

rounded header

What’s the deal with LightSpeed caching?

tag icon Tagged as LightSpeed, Products

Recently a LightSpeed user asked for more information about second level caching – in particular there did not seem to be an abundance of information about how it worked in the documentation. We’re set to beef up the documentation to provide exciting bed time reading however Ivan suggested it would be interesting enough to blog about. So, without further ado, here’s a write up covering many of the questions that developers may have regarding LightSpeed and caching.

What’s the difference between first and second level caches?

A first level cache is a cache that is scoped only to the current unit of work. A second level cache is scoped across the entire process. A better way of showing what this means is to give a code example.

using(var uow = LightSpeedContext.Default.CreateUnitOfWork())
{
  var contrib1 = uow.FindById<Contribution>(52);
 
  // This request will be returned from the level one cache in memory
  var contrib2 = uow.FindById<Contribution>(52);
}
 
using (var uow = LightSpeedContext.Default.CreateUnitOfWork())
{
  // If level two cache is not enabled, this would generate
  // a database call. With the level two cache enabled it would
  // be fetched from the cache as we fetched it in the previous
  // unit of work
  var contrib1 = uow.FindById<Contribution>(52);
}

Can I turn off the first level cache?

No, this is a simple lookup mechanism baked into the unit of work. The second level cache however is disabled by default and it is your choice if you wish to use it.

Do I need a second level cache? When should I use one?

You do not need to use second level caching at all however you may wish to enable it to improve performance of your application. If you’re noticing that your application is querying the database frequently for the same information (for example, reference data) then adding caching will provide an improvement in performance immediately.

Web applications also benefit well from the second level cache as generally pages are constructed relatively quickly within a tightly defined unit of work and therefore the first level cache is not going to be of use across multiple requests. On the topic of caching with web applications, it’s important to note that you will likely be able to deliver much larger performance improvements by simply enabling output caching on your web pages so that no code execution (and therefore queries) are required on the server side.

Why wouldn’t I use a second level cache?

Great question – I’m glad you asked. Given the benefits of not having to query your database as often and simply fetch things from memory you would wonder why it is not enabled by default. The reason is that you want to avoid stale objects being returned. Cache expiry is a very complex topic in its own right and most caching products include many options for tuning cache item expiry (time based expiry, memory pressure expiry, etc).

Can I configure the second level cache?

Absolutely – caching can be configured on a per-entity basis. This is super handy for situations where data is largely static (e.g. reference data in look up tables) but not impacting highly volatile data such as statistics tracking entities. Further to this it is possible to configure a timespan for data to be stored in the cache before it expires and would be re-fetched from the database.

Like almost everything in LightSpeed, you can configure this both in the designer or in code.

Configure LightSpeed ORM cache

Or

[Cached]
public class Instance : Entity<long>
{
  // entity properties and other guff
}

When will I get a cache hit?

For the first level cache, LightSpeed will check if it already has an entity loaded for the given Id when you perform any type of fetch where you specify an ID. For example, if you use the FindById() method, LightSpeed will check the internal first level cache first and, if found, return that and save a database call. If an explicit ID is not provided (for example, a query that would return all entities from a table) then as LightSpeed receives the result and looks to hydrate an entity it will first check if it already has an entity in the first level cache and, if found, return that entity. This type of logic applies when fetching explicitly as well as when lazy loading occurs.

The second level cache, if enabled, will also be checked in all of the same situations however it is more likely to contain objects as they will live across the unit of work boundaries.

To summarise when a cache hit would occur:

  • You fetch an entity by Id
  • You fetch a collection of entities
  • You lazy load a collection or entity

When is an entity loaded into the cache?

An entity is loaded into the cache as soon as it is fetched from the database by LightSpeed.

What exactly is the second level cache? How does it manage the cache objects?

LightSpeed has always provided the cache as an extensibility point meaning that you have a choice in cache providers. Any class that implements the Mindscape.LightSpeed.Caching.ICache interface can be plugged in to provide your caching needs.

LightSpeed does ship with two cache providers out of the box however:

DefaultCache: This cache uses the System.Web.Caching.Cache class and, despite the namespace, is a perfectly good cache for all environments (web, desktop apps, etc). This is the most common and recommended second level caching choice for LightSpeed users. To leverage it all you need to do is set it on your LightSpeedContext.

MemcachedCache: The DefaultCache will handle most solutions however if you have really large caching needs then the MemcachedCache is the caching choice for you! Memcached is an open source caching system that uses large distributed hash tables that can span multiple servers. This is unlikely to be of use to most people but we have included it for larger customers and as an example of another cache provider.

How do I apply a caching provider?

The second level caching provider can be applied either in your .config file:

  <lightSpeedContexts>
    <add name="default" 
         connectionStringName="Dev" 
         cacheClass="Mindscape.LightSpeed.Caching.DefaultCache, Mindscape.LightSpeed" />
  </lightSpeedContexts>

or in code:

LightSpeedContext.Default.Cache = new CacheBroker(new DefaultCache());

Can I read items from the cache myself, without queries?

Absolutely, you can add, update and read items from the cache by accessing the UnitOfWork.Context.Cache object.

That’s it!

If you have any questions regarding LightSpeed caching please post a comment and I’ll be happy to answer. All feedback will directly influence the changes made to the documentation updates for LightSpeed 3.0.

9 Responses to “What’s the deal with LightSpeed caching?”

  1. Is query caching (rather than just FindById) in the pipeline for 3.0?

  2. Hi Richard,

    No, not currently, but we will look into this post 3.0 as it would be a nice enhancement.

    Thanks for your feedback!

  3. Is the cache used for new items(items not yet saved to the database)?

    And when a query is run are new items returned if they match the query results?

  4. Hi MiddleTommy,

    New objects would be in the first level cache, not in the second level cache. Of course, they won’t match any results returned until they’re saved so it’s not “caching” so much as just where they live before they get saved or thrown away :-)

    If I understand your second question correctly you mean “if I add a comment to a contribution, don’t save it, then fetch all comments, do I get all the comments including the new one that is not saved yet?” then no – you do not. This is for several reasons – complexity of translating DB queries into in-memory queries, unexpected results for most users, etc.

    I hope that helps answer your questions MiddleTommy – let me know if I’ve missed the point of your second question or if you’d like more clarification :-)

  5. Have you thought about allowing Velocity or Memcached for the second level cache provider? Being able to cache entities across a server farm would really raise the bar.

  6. Hi Dan,

    About 3/4 of the way through the post I mentioned that we currently ship with a Memcached second level cache.

    Due to the pluggable nature of the second level cache it shouldn’t be too difficult to plug in a Velocity provider as well.

    I hope that helps.

  7. How does the 2nd level caching infratructure deal with Unit of Work issues that could result from the UnitOfWork references (and other entity references) being propagated across Entities used in different UnitOfWork contexts?

    For instance, I have a unit of work for editing a Student entity. I pull a Category entity (reference data) from cache and associate it with the Student entity. The UnitOfWork from my Student Entity gets propagated to my Category entity and the bidirectional object reference from Category to Student (and back) gets set. When my form / unit of work for editing that student goes out of scope, what prevents the cached Category from keeping references around for the UnitOfWork that was used and the student object reference that ideally should be garbage collected?

  8. To be clear, something like:

    using (var uow = _ctx.CreateUnitOfWork())
    {
    var transfers = uow.OpenTransfers;
    } // OpenTransfers is an entity. So this loads them all.

    is never going to be cached? I routinely use dictionary tables that could be cached for hours.

  9. It won’t be cached by default. The first level caching is done at the UnitOfWork scope. If you add a cache provider (second level cache) to the LightSpeed context then they will be cached outside of your unit of work scope (so outside of your “//OpenTransfers is an entity” line.

    So add a second level cache and you’re good to go Mike.

Leave a Reply

Data Products Visual Controls Community Store
LightSpeed ORM
NHibernate Designer
SimpleDB Tools
SharePoint Tools
WPF Elements
WPF Diagrams
Silverlight Elements
Forums
Blog
Register
Login
Subscribe to newsletter
Buy Now
My Account
Volume Discounts
Purchase Orders
Contact Us