Showing posts with label Caching. Show all posts
Showing posts with label Caching. Show all posts

Tuesday, December 29, 2015

OutputCache & Instrument using Flatwhite


If you are looking for OutputCache for WebApi, go here

What is Flatwhite?

Flatwhite is an AOP library with MVC and WebAPI ActionFilter style using Castle dynamic proxy. There are many libraries out there to help you intercept a method call such as PostSharp, recently CodeCop and they're really cool tools. However, I've been using Castle dynamic proxy for a many years and I think it offers enough needs for my projects. Therefore, Flatwhite is an opinionated library to facilitate usages of Castle dynamic proxy for method interceptions.

Current release only supports Autofac but I think other IOC containers also use Castle dynamic proxy when they come to interception so they will be supported in the future.

You can create MethodFilterAttribute to add custom logic to any methods as soon as it is interceptable by Castle Dynamic Proxy (virtual not final). Flatwhite has a built-in OutputCacheFilter to cache method result which can auto refresh stale content. You can use Flatwhite simply for caching or extending behavior of your code such as profiling, logging by implement MethodFilterAttribute similar to MVC's ActionFilterAttribute

When to use Flatwhite?

You have classes implemented interfaces and registered using Autofac (for now). You have a need to intercept method calls so you possibly have 2 quick options:

  • Use Autofac.Extras and call EnableInterfaceInterceptor() on type registrations then create/register custom IInterceptor.
  • Or use Flatwhite, implement an MethodFilterAttribute and decorate on the methods on your interfaces which you want to intercept.

As mentioned above, Flatwhite has a built-in OutputCacheFilter to cache method output. It works for methods that have a return value both sync and async methods. Beside caching, you can also implement MethodFilterAttribute and ExceptionFilterAttribute to add custom logic to your code.

How to use Flatwhite?

** Required packages: and
For now, Flatwhite needs to be used with Autofac (except Flatwhite.WebApi package). It requires Castle Dynamic proxy to intercept methods so it's a requirement to have public interface or your methods must be virtual and not final to be intercepted.

For caching:


1/ Enable class interceptor
If you modify the class to make the method virtual and decorate the method with OutputCacheAttribute, you will register the class like this:
public class UserService
{
    [OutputCache(Duration = 2, VaryByParam = "userId")]
    public virtual object GetById(Guid userId) 
    {
        // ...
    }    
}
var builder = new ContainerBuilder().EnableFlatwhite();
builder
    .RegisterType<CustomerService>()    
    .EnableInterceptors();

2/ Enable interface interceptor
If the methods are not virtual, but the class implements an interface, you can decorate the methods on the interface with OutputCacheAttribute and register the type like this
public interface IUserService
{
    [OutputCache(Duration = 2, VaryByParam = "userId")]
    object GetById(Guid userId);

    [NoCache]
    object GetByEmail(string email);

    IEnumerable<object> GetRoles(Guid userId);
}

var builder = new ContainerBuilder().EnableFlatwhite();
builder.RegisterType<UserService>()   
       .As<IUserService>()   
       .EnableInterceptors();

3/ Quick enable cache on all methods
If you don't want to decorate the OutputCache attribute on the interface, you can do like this to enable cache on all methods
var builder = new ContainerBuilder().EnableFlatwhite();
builder.RegisterType<UserService>()   
       .As<IUserService>()   
       .CacheWithStrategy(CacheStrategies
            .AllMethods()
            .Duration(5)
            .VaryByParam("*")
        );

4/ Choose the method to cache without using Attribute filter
If you want to cache on just some methods, you can selectively do like below. Again, it works only on virtual methods if you are registering class service; interface services are fine.
var builder = new ContainerBuilder().EnableFlatwhite();
builder.RegisterType<BlogService>()
       .As<IBlogService>()
       .CacheWithStrategy(
               CacheStrategies.ForService<IBlogService>()
                              .ForMember(x => x.GetById(Argument.Any<Guid>()))
                              .Duration(2)
                              .VaryByParam("postId")

                              .ForMember(x => x.GetComments(Argument.Any<Guid>(), Argument.Any<int>()))
                              .Duration(2)
                              .VaryByCustom("custom")
                              .VaryByParam("postId")
                              .WithChangeMonitors((i, context) => 
                              {
                                    return new[] {new YourCustomCacheChangeMonitor()};
                              })                                    
       );

5/ Enable interceptors on all previous registrations
If you're a fan of assembly scanning, you can decorate the OutputCache attribute on classes & interfaces you want to cache and enable them by RegisterModule FlatwhiteBuilderInterceptModule before building the container
var builder = new ContainerBuilder();
builder
        .RegisterType<BlogService>()
        .AsImplementedInterfaces()
        .AsSelf();

// Register other types normally
...

// Register FlatwhiteBuilderInterceptModule at the end
builder.RegisterModule<FlatwhiteBuilderInterceptModule>();            
var container = builder.Build();

Note that you don't have to call EnableFlatwhite() after creating ContainerBuilder like the other methods.

6/ Auto refresh stale data
Flatwhite can auto refresh the stale content if you set StaleWhileRevalidate with a value greater than 0. This should be used with Duration to indicates that caches MAY serve the cached result in which it appears after it becomes stale, up to the indicated number of seconds The first call comes to the service and gets a stale cache result will also make the cache system auto refresh once in the background. So if the method is not called many times in a short period, it's better to turn on AutoRefresh to make the cache alive and refreshed as soon as it starts to be stale
public interface IBlogService
{
    // For method with too many cache variations because of VaryByParam settings
    [OutputCache(Duration = 5, VaryByParam = "tag, from, authorId", StaleWhileRevalidate = 5)]
    IEnumerable<object> Search(string tag, DateTime from, Guid authorId);    


    // For method with not many cache variations and data is likely to changed every 5 seconds
    [OutputCache(Duration = 5, VaryByParam = "blogId", StaleWhileRevalidate = 5)]
    object GetById(int blogId);    


    // You can turn on AutoRefresh to keep the cache active if there are limited variations of the cache
    [OutputCache(Duration = 5, VaryByParam = "blogId", StaleWhileRevalidate = 5, AutoRefresh = true)]
    IEnumerable<string> GetBlogCategory();    
}

7/ Using CacheProfile
public interface IUserService
{
    [OutputCache(CacheProfile="profileName")]
    object GetById(Guid userId);
}

Profile setting default file name is cacheProfile.yaml located at the same folder of your app.config/web.config file and has a "yaml like" format:
-- Cache profile settings, everything is case-sensitive
-- Profile name
Profile1-Two-Seconds
    -- Profile propertyName:propertyValue, start with a tab or 4+ empty spaces
    Duration:2
    StaleWhileRevalidate:5
    VaryByParam:packageId   
    VaryByCustom:*
    AutoRefresh:true
    RevalidationKey:anything-about-user

Web-Profile2-Three-Seconds  
    MaxAge:3
    StaleWhileRevalidate:6
    VaryByParam:*
    VaryByHeader:UserAgent
    IgnoreRevalidationRequest:true      

You can implement another IOutputCacheProfileProvider and set to Global.OutputCacheProfileProvider or simply change the location/name of the yaml file. At the moment, only yaml file is supported.

8/ Revalidate cache
Even though you can use AutoRefresh or StaleWhileRevalidate to auto refresh cache data. Some time you want to remove the cache item after you call a certain method. You can use RevalidateAttribute to remove the cache item or some related cache items. Decorate the attribute on another method and the cache item will be removed once the method is invoked successfully. On example below, when you call method DisableUser, because it has the Revalidate attribute decorated with "User" as the key, all related caches created for method with attribute OutputCache which has RevalidationKey = "User" will be reset.
public interface IUserService
{
    [OutputCache(Duration = 2, StaleWhileRevalidate = 2, VaryByParam = "userId", RevalidationKey = "User")]
    object GetById(Guid userId);

    [OutputCache(Duration = 2, VaryByParam = "userId", RevalidationKey = "User")]
    Task<object> GetByIdAsync(Guid userId); 

    [Revalidate("User")]
    void DisableUser(Guid userId);  
}

Unfortunately, this is not working for distributed services. That means the method is called on one server cannot notify the other service instances on remote servers. However, it's technically achievable to extend this filter using queueing or something like that to notify remote system.

For additional logic before/after calling methods



Flatwhite is inspired by WebAPI and ASP.NET MVC ActionFilterAttribute, so it works quite similar. The base filter attribute has following methods. So simply implement your filter class and do whatever you want.
public abstract class MethodFilterAttribute : Attribute
{

    public virtual void OnMethodExecuting(MethodExecutingContext methodExecutingContext);    
    public virtual Task OnMethodExecutingAsync(MethodExecutingContext methodExecutingContext);   
    public virtual void OnMethodExecuted(MethodExecutedContext methodExecutedContext);    
    public virtual Task OnMethodExecutedAsync(MethodExecutedContext methodExecutedContext);    
}
If you decorate the filter on async methods, only OnMethodExecutingAsync and OnMethodExecutedAsync are called. During the filters are being executed, if the Result value is set to the MethodExecutingContext, the remaining filters will be ignored.

For error handling


Similar to MethodFilterAttribute, you can implement ExceptionFilterAttribute to provide custom error handling logic. If the property MethodExceptionContext.Handled is true, all remaining ExceptionFilter will be ignored.
public abstract class ExceptionFilterAttribute : Attribute
{    
    public virtual void OnException(MethodExceptionContext exceptionContext);    
    public virtual Task OnExceptionAsync(MethodExceptionContext exceptionContext);       
}


What's else?

Flatwhite for WebAPI: https://github.com/vanthoainguyen/Flatwhite/wiki/Flatwhite.WebApi
Wiki: https://github.com/vanthoainguyen/Flatwhite/wiki

Saturday, August 20, 2011

MVC Donut hole caching for Razor View

    I was interested with Donut caching for a while and today I have a need for the Donut hole caching or partial view caching. As many of us, I tried to find whether some smart guys had solved this problem. And it seems like Mr.Haacked had mentioned this in his post. However, that solution is for ASP.NET view engine. I have no choice and have to solve it myself. Hopefully MVC team will support this feature in next versions of MVC framework. Hmmm indeed, it could be supported in version 4: See road map.


    My idea is if a view engine renders the view's content to a stream or text writer, we can intercept that process, cache the output string to somewhere together with the view's id or what ever that can identify the view. Then next time the view is rendered, we can determine whether or not to get the content from cache or let the view engine continue it's job. So i dig into the MVC source code and find this:
public class RazorView : BuildManagerCompiledView
{
    protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
    {
        //.............
    }
}
    That's exactly what i need, isn't it? We probably can create a derived class from RazorView and override above method, get the content that the text writer receive and write it directly to the writer if the view is cached. So, i need a custom text writer that could return to me what it receive :D
public class TrackableTextWriter : TextWriter
{
    private readonly TextWriter _writer;
    private StringBuilder _mem;
    public TrackableTextWriter(TextWriter writer)
    {
        _writer = writer;
        _mem = new StringBuilder();
    }
    public override Encoding Encoding
    {
        get { return _writer.Encoding; }
    }
    public override void Write(string value)
    {
        _writer.Write(value);
        _mem.Append(value);
    }
    public string GetWrittenString()
    {
        return _mem.ToString();
    }
    protected override void Dispose(bool disposing)
    {
        if (!disposing)
        {
            _writer.Dispose();           
        }
        base.Dispose(disposing);
        _mem = null;
    }
}
    Alright, now it's time to create a derived of RazorView:
public class CachableRazorView : RazorView
{
	private const string ViewCachePrefix = "ViewCache__";
    protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
	{
		var appCache = viewContext.HttpContext.Cache;
		var cacheKey = ViewCachePrefix + ViewPath;
		// Check if there was a Cache config that had been fully added to the cache
		var cacheConfiguration = appCache[cacheKey] as CacheConfiguration;
		if (cacheConfiguration != null && cacheConfiguration.Data != null)
		{
			writer.Write(cacheConfiguration.Data);
			return;
		}
		var trackableTextWriter = new TrackableTextWriter(writer);
		base.RenderView(viewContext, trackableTextWriter, instance);
		
		// Cache config has just been added when the view is rendered the first time thanks to the HtmlHelper
		cacheConfiguration = appCache[cacheKey] as CacheConfiguration;
		if (cacheConfiguration != null)
		{
			var writtenString = trackableTextWriter.GetWrittenString();
			cacheConfiguration.Data = writtenString;
			appCache.Remove(cacheConfiguration.Id);
			appCache.Add(cacheConfiguration.Id,
						cacheConfiguration,
						null,
						Cache.NoAbsoluteExpiration,
						TimeSpan.FromSeconds(cacheConfiguration.Duration),
						CacheItemPriority.Default,
						null);
		}
	}
}
    Then there's of course a place we can return this custom view instead of RazorView. That's definitely the RazorViewEngine. So next step is creating our custom razor view engine:
public class CachableRazorViewEngine : RazorViewEngine
{
	protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
	{
		return new CachableRazorView(controllerContext, partialPath, null, false, FileExtensions, ViewPageActivator);
	}

	protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
	{
		return new CachableRazorView(controllerContext, viewPath, masterPath, true, FileExtensions, ViewPageActivator);
	}
}
    As usual, we need to make the web application use this view engine by modifying the globals.asax
protected void Application_Start()
{
	AreaRegistration.RegisterAllAreas();

	RegisterGlobalFilters(GlobalFilters.Filters);

	ViewEngines.Engines.Clear();
	ViewEngines.Engines.Add(new CachableRazorViewEngine());

	RegisterRoutes(RouteTable.Routes);
}
    Using this approach, the traditional OutputCache will not have value. So the idea pops in my head is making a HtmlHelper extension and calling it in the view, something like this:
@{
	Html.OutputCache(new CacheConfiguration { Duration = 10 });
}
    So here is the implementation:
public static class CacheHtmlHelperExtensions
{
	public const string ViewCachePrefix = "ViewCache__";
	public static void OutputCache(this HtmlHelper helper, CacheConfiguration cacheConfiguration)
	{
		var view = helper.ViewContext.View as BuildManagerCompiledView;
		if (view != null)
		{
			cacheConfiguration.Id = ViewCachePrefix + view.ViewPath;
			helper.ViewContext.HttpContext.Cache.Add(cacheConfiguration.Id,
													cacheConfiguration,
                                                    null,
                                                    Cache.NoAbsoluteExpiration,
                                                    TimeSpan.FromSeconds(cacheConfiguration.Duration),
                                                    CacheItemPriority.Default,
                                                    null);
		}
	}
}
    Okey, it's time to test this implementation. I put the Html.OutputCache method in the Index page of default MVC application like above. So the view will be cached in 10 seconds. If the view is not accessed in next 10 seconds, the cache engine will remove the content from the cache. However, within 10 seconds, if the view is accessed again, the cache will remain for next 10 seconds and so on. Pretty cool, huh? I need to run the performance test on this page to see the actual result. There is a very cool tool in Linux named "curl-loader" but I didn't know any for Windows. After a while searching, I found apache benchmark very similar and usefull :D. I use "ab" tool to test the web app when enable and disable the cache. The result is very interesting. Eventhough the Index page is quite simple with only text, no database access but when I enable the cache, the web server can serve 1107 requests per second when I run 10000 requests to server at concurency level at 100 compare to only 531 requests per second when I disable the cache, 2 times faster:

benchmark result

    Summary, there is a outstanding issue using this approach, the builtin output cache of ASP.NET can not be utilised. It also ignores the value of ViewModel, that's mean this implementation has not supported caching by different view model. But I think we can do it if we really need that feature. Just find a way to distinguish the different between view model values, it could be a hash code or something :D.

Source code: Razor.DonutHoleCaching.zip

Cheers