Sitecore 10 – Solr Search & Index Update Strategies
Overview
This Blog explains how Sitecore integrates with Apache Solr and dives deep into the Index Update Strategies that control when and how indexes are updated. It starts with the basics (what Solr does in Sitecore and how to query it) and builds up to advanced configuration (publish-driven strategies, batching, thresholds, and zero-downtime rebuilds).
Solr in Sitecore
Sitecore uses Solr for two big areas:
- Content Search – searching Sitecore items like pages and components.
- xConnect Search – analytics/marketing data in xDB (if used).
For a standard setup you: (a) install Solr compatible with your Sitecore build, (b) create cores for each Sitecore index, (c) set the solr.search connection string, (d) populate the managed schema using the Control Panel tool, and (e) rebuild indexes.
Minimal configuration pointers
<add name="solr.search" connectionString="https://localhost:8983/solr" />After Solr is reachable, use Control Panel → Populate Solr Managed Schema, then Indexing Manager → Rebuild.
Basic querying with ContentSearch API (C#)
var rootItem = Sitecore.Context.Database.GetItem("/sitecore/content/Home");
var indexable = new Sitecore.ContentSearch.SitecoreIndexableItem(rootItem);
using (var context = Sitecore.ContentSearch.ContentSearchManager
.GetIndex(indexable)
.CreateSearchContext())
{
var results = context.GetQueryable<Sitecore.ContentSearch.SearchTypes.SearchResultItem>()
.Where(x => x.TemplateId == new Sitecore.Data.ID("{TEMPLATE-GUID}"))
.ToList();
foreach (var r in results)
{
var item = r.GetItem();
// use item
}
}
You can map custom fields via [IndexField] attributes and query strongly-typed results.
Index Update Strategies – What, Why, and When
An Index Update Strategy defines when Sitecore updates search indexes (e.g., after publish vs. every N seconds) and whether updates are incremental or full rebuilds. Each index can have its own strategies (keep it to ≤3).
The most useful built-in strategies
OnPublishEndAsync
• Trigger: after publish completes (publish:end). Uses EventQueue so it works CM→CD. • Mode: incremental updates; can switch to full rebuild when many items changed. Best for web indexes. Do not combine with Synchronous or Interval on the same index.
OnPublishEndAsyncSingleInstance
• Like OnPublishEndAsync but fetches publish events once and reuses them for multiple indexes → lower DB load. Ideal when you have multiple web-facing indexes.
IntervalAsynchronous
• Runs every N seconds and updates from the history engine. Great for master/core (authoring) where near-real-time is not necessary. Can auto-switch to full rebuild beyond a threshold.
RebuildAfterFullPublish
• After a full publish finishes, trigger a full rebuild. Pair with SwitchOnRebuild to avoid downtime.
Synchronous (generally avoid)
• Updates immediately per item change. This can slow publishing heavily; prefer async strategies with batching.
Critical concepts that affect strategies.
Event Queue
Required for OnPublishEnd… strategies across instances. The strategy reads from the EventQueue of the database you pass (usually web). Ensure EventQueue is enabled and the database is listed in <databases>.
Threshold & FullRebuildItemCount
Strategies have CheckForThreshold. If unprocessed events/changes exceed ContentSearch.FullRebuildItemCountThreshold (default 100,000), Sitecore performs a full rebuild instead of incremental. Tune per index size and change patterns.
Do not mix conflicting strategies
Avoid combining OnPublishEndAsync / OnPublishEndAsyncSingleInstance with Synchronous or IntervalAsynchronous on the same index to prevent redundant work.
Configuration – Ready-to-use patches (Sitecore 10 + Solr)
Place these patches under /App_Config/Include/zzz.* so they load after defaults. Adjust core names to match your Solr cores.
Debug crawling logs (optional)
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<log4net>
<logger name="Sitecore.Diagnostics.Crawling" additivity="false" patch:instead="logger[@name='Sitecore.Diagnostics.Crawling']">
<level value="DEBUG" />
<appender-ref ref="CrawlingLogFileAppender" />
</logger>
</log4net>
</sitecore>
</configuration>Web indexes: publish-driven + zero downtime
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<contentSearch>
<indexes hint="list:AddIndex">
<index id="sitecore_web_index" type="Sitecore.ContentSearch.SolrProvider.SwitchOnRebuildSolrSearchIndex, Sitecore.ContentSearch.SolrProvider">
<param desc="name">$(id)</param>
<param desc="core">sitecore_web_index</param>
<param desc="rebuildcore">sitecore_web_index_sec</param>
<strategies hint="list:AddStrategy">
<strategy ref="contentSearch/indexConfigurations/indexUpdateStrategies/onPublishEndAsyncSingleInstance" />
</strategies>
</index>
</indexes>
<indexConfigurations>
<indexUpdateStrategies>
<onPublishEndAsyncSingleInstance type="Sitecore.ContentSearch.Maintenance.Strategies.OnPublishEndAsynchronousSingleInstanceStrategy, Sitecore.ContentSearch"
singleInstance="true">
<param desc="database">web</param>
<CheckForThreshold>true</CheckForThreshold>
</onPublishEndAsyncSingleInstance>
</indexUpdateStrategies>
</indexConfigurations>
</contentSearch>
</sitecore>
</configuration>Using SwitchOnRebuild builds in a secondary core and then swaps cores using Solr SWAP, avoiding downtime when a full rebuild is required
Master/Core indexes: batched interval updates
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<contentSearch>
<indexes hint="list:AddIndex">
<index id="sitecore_master_index"
type="Sitecore.ContentSearch.SolrProvider.SolrSearchIndex, Sitecore.ContentSearch.SolrProvider">
<param desc="name">$(id)</param>
<param desc="core">sitecore_master_index</param>
<strategies hint="list:AddStrategy">
<strategy ref="contentSearch/indexConfigurations/indexUpdateStrategies/intervalAsyncMaster" />
</strategies>
</index>
<index id="sitecore_core_index"
type="Sitecore.ContentSearch.SolrProvider.SolrSearchIndex, Sitecore.ContentSearch.SolrProvider">
<param desc="name">$(id)</param>
<param desc="core">sitecore_core_index</param>
<strategies hint="list:AddStrategy">
<strategy ref="contentSearch/indexConfigurations/indexUpdateStrategies/intervalAsyncCore" />
</strategies>
</index>
</indexes>
<indexConfigurations>
<indexUpdateStrategies>
<intervalAsyncMaster type="Sitecore.ContentSearch.Maintenance.Strategies.IntervalAsynchronousStrategy, Sitecore.ContentSearch">
<param desc="database">master</param>
<param desc="interval">00:02:00</param>
<CheckForThreshold>true</CheckForThreshold>
</intervalAsyncMaster>
<intervalAsyncCore type="Sitecore.ContentSearch.Maintenance.Strategies.IntervalAsynchronousStrategy, Sitecore.ContentSearch">
<param desc="database">core</param>
<param desc="interval">00:02:00</param>
<CheckForThreshold>true</CheckForThreshold>
</intervalAsyncCore>
</indexUpdateStrategies>
</indexConfigurations>
</contentSearch>
</sitecore>
</configuration>Two minutes is a practical starting point; it batches changes and reduces Solr load while keeping authoring search reasonably fresh.
Real-world Use case
Standard CM/CD
• Web: OnPublishEndAsyncSingleInstance + SwitchOnRebuild. • Master/Core: IntervalAsynchronous (≈2 min).
Frequent full republish days
• Add RebuildAfterFullPublish to web indexes (still keep SwitchOnRebuild).
Multiple web-facing indexes
• Prefer OnPublishEndAsyncSingleInstance across those indexes to reduce database load.
(Optional) SPE script – inspect index status quickly
If you have Sitecore PowerShell Extensions (SPE), you can run a quick check of index names and last update times:
$indexes = [Sitecore.ContentSearch.ContentSearchManager]::Indexes
$indexes | ForEach-Object {
$name = $_.Name
$props = $_.PropertyStore.Get(new-object Sitecore.ContentSearch.Maintenance.IndexPropertyStoreKey("$name`_LAST_UPDATED_TIMESTAMP"))
[PSCustomObject]@{ Index = $name; LastUpdated = $props }
} | Format-Table -AutoSizeYou can extend this to check EventQueue counts or to trigger rebuilds as needed.
Appendix – Basic ContentSearch code patterns
public class AuthorSearchResultItem : Sitecore.ContentSearch.SearchTypes.SearchResultItem
{
[Sitecore.ContentSearch.IndexField("author_id_s")]
public string AuthorId { get; set; }
}
using (var ctx = Sitecore.ContentSearch.ContentSearchManager
.GetIndex("sitecore_web_index").CreateSearchContext())
{
var predicate = Sitecore.ContentSearch.Linq.Utilities.PredicateBuilder.True<AuthorSearchResultItem>();
predicate = predicate.And(x => x.AuthorId == "12345");
var results = ctx.GetQueryable<AuthorSearchResultItem>()
.Where(predicate)
.Take(10)
.ToList();
}
Custom field mapping and predicate queries
please do share your comments and thoughts on the same.