Monday 27 November 2017

Custom Remote Events for Cache Rebuild mechanism

A couple of months ago I had to jump into a project for a while and I came across a problem with refreshing custom cache on content delivery servers. There was a custom cache mechanism that was running on each instance separately. It was relying on items based on specific template, every time one of these items was updated or created/removed, cache should be updated. I found out that the best way to refresh that is to prepare custom remote event.

The problem

I had to make sure that after publishing specific items, cache will be rebuilt. My first thought was to use publish:end event, so I could check if one of that items was published. But here the problem with checking came out. There is a parameter of class Publisher that is put to EventArgs on position 0. Unfortunately, it doesn't contain any list of published items, just the root item:

publisher.Options.RootItem

...or maybe fortunately, for huge amount of items published :) Anyway, if we are relying only on this property, we need to go through all items recursively to find the specific one:

publisher.Options.RootItem.Axes.GetDescendants().Any(x => x.TemplateID.ToString() == SpecificTemplateId)
You might see that here, on some publish events, using Axes and GetDescendants() is not very good idea (most of times it's not!). And we need to keep in mind that RootItem will be null if user ran full site publish...

Improve the performance

Okay, maybe some better ideas? Yes. There is an event called publish:itemProcessed that is fired every time an item is processed during publish. We can use it to find if currently processed item is based on specific template and then set some flag.


Flag used here will be static property of the event handler class:

/// <summary>
/// Static flag that indicates if any specific item was published
/// </summary>
protected static bool SpecificItemWasPublished = false;


After all items have been processed, the publish:end event will be raised. Here we can check if flag is set to true and run cache refresh action. This solution will work gracefully on single server application, but on CM/CD separated instances it will work only on Content Management server. We need to work out a better solution using custom remote events.

Using custom remote event

Custom remote event will be the best option to solve this problem. Our cache refreshing system will work like this
  1. While publishing we check if specific item is published - on publish:itemProcessed event. If there is one, we set up the flag.
  2. When publishing finishes (publish:end) we check the flag. If flag is set up, we raise cache rebuild event
  3. Cache rebuild event will raise remote event for cache rebuild.
Using this approach all hard work, like checking specific item published, will take place on the local server, remote servers will just do what first told them. It all works using EventQueue.

1. Class for remote event representation
First thing that we will need will be class that represents an event. There is no base class to derive or interface to implement, it can be plain class but members need to be serializable. In our example we will have a boolean flag that will indicate if full rebuild is needed.

2. Event raiser class
Next we will need a handy class that will be able to raise the event locally and add request to Event Queue for raising it on remote servers.

3. Pipeline processor for initializing event subscription
We need to create a pipeline processor that will subscribe to those remote events.

4. Event handlers class
This class will contain our event handlers. As described earlier OnItemProcessed will check if currently processed item is based on specific template and set up flag if found. OnPublishEnd will check that flag when publishing is finished and raise cache rebuild event using prepared earlier event raiser. Last event handler OnCustomCacheRebuild will do the actual call to index mechanism for cache rebuild.

5. Sitecore config patch
At last we have to bind all event handlers and processors into Sitecore. This Sitecore config patch does that:
 
And that's all, with all these parts our system is ready to use.


Testing solution 

To test our solution we need to set up two Sitecore instances on our local machine, first for CM role, latter for CD role.They need to have different InstanceName properties set in App_Config\Include\ScalabilitySettings.config.

<setting name="InstanceName">
  <patch:attribute name="value">SitecoreCoffeeCM</patch:attribute>
</setting> 

As well those two instances need to use the same databases.

After setting up those instances and publishing item based on specified template on CM server, there will be new record created in Core database in EventQueue table:

Id:            C3386293-8E1B-4EBB-A490-D57DC663E61B
EventType:     SitecoreCoffee.Foundation.RemoteEvents.Events.CacheRebuildEvent, SitecoreCoffee.Foundation.RemoteEvents, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
InstanceType:  SitecoreCoffee.Foundation.RemoteEvents.Events.CacheRebuildEvent, SitecoreCoffee.Foundation.RemoteEvents, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
InstanceData:  {"FullRebuild":true}
InstanceName:  SitecoreCoffeeCM
RaiseLocally:  0
RaiseGlobally: 1
UserName:      sitecore\admin
Stamp:         0x0000000000032E9E
Created:       2017-11-21 23:20:01.267

And as well on those two instances cache will be rebuilt:

ManagedPoolThread #2 23:20:01 INFO  CacheRebuildEventHandler: Rebuilding the cache.
ManagedPoolThread #2 23:20:01 INFO  CacheRebuildService: Cache rebuilt (full: True)
ManagedPoolThread #2 23:20:01 INFO  CacheRebuildEventHandler: Cache rebuilt in 0 seconds.

Summary

Solution described above explains how to use custom events in Sitecore to create efficient mechanism that will rebuild cache on local and remote servers, when specific item is published. It can be used to perform any other type of action that will require additional activity taken on remote servers.

Full code is available in my Helix solution here: https://github.com/ReoKzK/SitecoreCoffee

Happy Sitecoring!