Speeding up the Sitecore Experience Editor

If you’ve spent some time with Sitecore 8.x, you’ve probably experienced the tremendously slow SPEAK driven Experience Editor (formerly known as Page Editor). This is due to multiple problems. The design of SPEAK is really chatty and cache is vital, some XHR requests are really slow and some are caused by heavy precompilations.

Kam Figy wrote a really good post about Sitecore 8 Experience Editor Performance Optimization. Read this first if you haven’t already gone through this one!

Brijesh Patel also addressed the “Suggested Tests Count” problem, where the Experience Editor ribbon blocks the rendering for a very long time just to get a list of suggested tests.

With those problems solved, I’ve found that there is a similar call for getting the number of locked items the current user have. This little digit can easily block the UI for several seconds. The figure is quite nice for authors, so let’s optimize this one.

My locked items counter

In the Sitecore config, we can replace the ExperienceEditor.MyItems.Count hadler in the <sitecore.experienceeditor.speak.requests> section, with our own class. Looking at the default implementation, we see that Sitecore looks through all the items and finds items where the __lock field contains the current user name. Though it uses Fast Query, it’s still quite slow. So I decided to go with a search implementation instead.

I’m using Solr, so this example is based on that, but it’s virtually the same if you’re using Lucene

First, we need to add the lock owner to the index. By default, the __lock field is ignored and there is a separate computed lock field that indicates locked items. But for this function we need to know the owner of the lock. Such field can be implemented like this:

public class IsLockedByComputedField : AbstractComputedIndexField
{
    public override object ComputeFieldValue(IIndexable indexable)
    {
        Item obj = indexable as SitecoreIndexableItem;
        if (obj == null)
            return null;
        if (!obj.Locking.IsLocked())
            return null;
        return obj.Locking.GetOwner();
    }
}

Then we need a replacement for the MyItemsCountRequest class that uses the index for counting instead. This code should probably be adapted so that it fits your environment regarding getting the right search context. Here it’s pretty hard coded:

public class MyItemsCountRequest : PipelineProcessorRequest<ItemContext>
{
    public override PipelineProcessorResponseValue ProcessRequest()
    {
        var searchContext = ContentSearchManager.GetIndex("sitecore_master_index").CreateSearchContext();
        var myLockedItemsCount = searchContext.GetQueryable<LockedItem>()
            .Filter(f => f.LockedBy == Sitecore.Context.User.Name &&
                     f.LanguageName == Sitecore.Context.Language.Name &&
                     f.LatestVersion)
            .Take(0)
            .GetResults()
            .TotalSearchResults;
        return new PipelineProcessorResponseValue { Value = myLockedItemsCount };
    }

    protected class LockedItem
    {
        [IndexField("lockedby")]
        internal string LockedBy { get; set; }

        [IndexField("_language")]
        internal string LanguageName { get; set; }

        [IndexField("_latestversion")]
        internal bool LatestVersion { get; set; }
    }
}

Now we can just put this together with a config patch file, such as this:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <sitecore.experienceeditor.speak.requests>
      <request name="ExperienceEditor.MyItems.Count">
        <patch:attribute name="type">YourNamespace.MyItemsCountRequest, YourAssembly</patch:attribute>
      </request>
    </sitecore.experienceeditor.speak.requests>

    <contentSearch>
      <indexConfigurations>
        <!-- change this if you're using lucene! -->
        <defaultSolrIndexConfiguration>
          <fields hint="raw:AddComputedIndexField">
            <field fieldName="lockedby" returnType="string">YourNamespace.IsLockedByComputedField, YourAssembly</field>
          </fields>
        </defaultSolrIndexConfiguration>
      </indexConfigurations>
    </contentSearch>
  </sitecore>
</configuration>

With this solution in place, the MyItem.Count handler returns in less than 0.1 seconds. That’s in the magnitude of 100 times faster.

Now lets just hope that Sitecore starts bundling some of the XHR requests, such as “CanEdit”, “CanAddVersion”, “CanResetFields” etc, into one fewer requests. Then the Experience Editor might gain a decent performance again.

4 thoughts on “Speeding up the Sitecore Experience Editor

Leave a Reply