allBlogsList

Disabling Replication and More Using Custom Solr Commands from Sitecore

Recently my team and I faced an interesting issue regarding Sitecore and Solr. Consider the following set of circumstances.

  1. A Solr master/slave configuration with replication from master to slaves in real time.

  2. A front-facing website the pulls data directly from slave Solr indexes.

  3. No switch on rebuild enabled/available.

You might see how this is a problem already but we quickly noticed that if an index rebuild is triggered on the master Solr server, it is emptied until the rebuild finishes. With our real time replication this lead to the empty index being replicated to the slave indexes, and in turn, loss of site functionality until the indexing was complete.

Our solution to this issue was to disable replication on the master server at the start of indexing, and to re-enable it upon completion using custom Solr commands.

To start on the solution to this problem, the first thing I did was to look at how Sitecore already communicates with the Solr server. By looking at the configuration file of the any index we can find out what and where the controlling code is.

Decompiling the DLL Sitecore.ContentSearch.SolrProvider and reviewing the class implementation lead me to notice that the Solr Index has an object property that implements the interface of ISolrCoreAdmin from the third party library Solr.Net. If we follow this string we will notice that the constructor of the SolrSearchIndex calls a method that sets the SolrCoreAdmin from the SolrConnector.

    protected virtual void InitializeSolrAdmin()
    {
      this.SolrAdmin = (ISolrCoreAdmin) this.SolrConnector.CoreAdmin;
    }

If we look at the class SolrConnecter we will see that the Admin on the ISolrIndex is initialized from the DefaultSolrFactory and is of the type SolrCoreAdminEx. Finally, we can review this class to notice that many of the core Solr operations are handled by sending ISolrCommands to the connection that was initialized in the DefaultSolrFactory. To summarize, all of this means that as long as we have access to the SolrSearchIndex object, we can use the CoreAdmin of the SolrConnector to send any command we want directly to Solr.

For examples of existing commands we can look at the decomplied Solr.Net DLL (Solr.Net.Commands). A very simple one is the PingCommand. The full code is below.

namespace SolrNet.Commands
{
  public class PingCommand : ISolrCommand
  {
    public string Execute(ISolrConnection connection)
    {
      return connection.Get("/admin/ping");
    }
  }
}

As you can see the command itself is just sending the string /admin/ping directly to the solr connection. You can see this response yourself by hitting the url [your solr server]/solr/core/admin/ping in a web browser.

Now that we have established all this we can assume that any API command that is available for Solr can be used and called from Sitecore code using a SolrCommand. This is perfect for the issue described at the start of this blog post as there are many API commands available for replication, the most relevant being

  1. http://master_host:port/solr/core_name/replication?command=enablereplication

  2. http://master_host:port/solr/core_name/replication?command=disablereplication

I couldn’t find these commands in Solr.Net or and Siteore libraries so in my solution I simply created two new classes called EnableReplicationComand and DisableReplicationCommand that implement the ISolrCommand interface. Below is an example of one of them.

    public class DisableReplicationCommand : ISolrCommand
    {
        public string Core { get; set; }
        public DisableReplicationCommand(string coreName)
        {
            if (string.IsNullOrEmpty(coreName))
                return;
            Core = coreName;
        }

        public string Execute(ISolrConnection connection)
        {
            return connection.Get($"/{Core}/replication?command=disablereplication");
        }
    }

As you can see it is a quite simple class that simply hits a URL for the Solr connection which in the Solr documentation disables replication for all slaves of a master Solr instance.

This solves the first part of the problem but now for the second part: calling this command on rebuild.

If you remember looking at the SolrSearchIndex class you will notice there are many methods that perform many crucial tasks. Since I am focused on the rebuilding functionality I simply searched this class for “rebuild” and found a protected method named PerformRebuild. I noticed that this method looks like the place where the actual rebuild takes place, and also ends with a call to OptimizeIndex, which tells me that this runs synchronously. To me, this looked like a perfect place to both disable the replication and then re-enable it after the rebuild had completed.

To do this I created a new class in my solution and named it CustomSolrIndex. I simply inherited from the class SolrSearchIndex and implemented all the required constructors. Finally, using the override keyword I overwrote the protected method PerformRebuild and inserted my code to run the commands to start and stop replication. Below is the full class (excluding the constructors).

    public class CustomSolrIndex : SolrSearchIndex
    {

        protected override void PerformRebuild(
            bool resetIndex,
            bool optimizeOnComplete,
            IndexingOptions indexingOptions,
            CancellationToken cancellationToken)
        {
            SolrConnector.CoreAdmin.SendAndParseHeader(new DisableReplicationCommand(Core));
            base.PerformRebuild(resetIndex, optimizeOnComplete, indexingOptions, cancellationToken);
            SolrConnector.CoreAdmin.SendAndParseHeader(new EnableReplicationCommand(Core));
        }
    }

Lastly, now that we have our custom commands and our custom Solr index with an overridden build method the last thing we need to do is update the index config for each index that we want to disable replication for.

By using this patch file I can control each specific index that I want my custom code to run on.

After all of these changes are deployed we no longer have the issue of our web indexes being empty on the slaves during full rebuild.

In summary I believe the solution described here is a very simple and elegant as it requires very little custom code. Also, in this post I have covered how to disable replication but by using the same method of custom Solr commands and a custom Solr index that inherits from the default Solr index, you can essentially run any Solr command from any of the existing Solr index methods. Hopefully this helps as a baseline for any future Solr index customization.