How To Improve Sitecore Performance in Microsoft Azure

Delete’s Solutions Architect and Sitecore MVP, Kate Orlova, recently covered this topic on the Sitecore Documentation Portal. But if you want to know even more about this subject, here’s a detailed guide of how to leverage Microsoft Azure features to improve Sitecore performance in Azure.

How to protect from a random Web App restart

Azure platform installs system updates and platform upgrades. It can often lead to unexpected Web App restarts. Follow these steps to make your Sitecore solution robust and reduce the chance of issues caused by unplanned Web App restarts:

1. Enable App Service Local Cache as described in this guide.

2. Setup Auto Healing to restart application after downtime following the instructions from this article.

3. Set ARR-Disable-Session-Affinity to true to disable sticky sessions and remove dependency between user requests and processing CD instances. More information about it can be found here.

When sticky session behaviour is disabled ensure that an external session state provider is configured for Private and Shared session storage:

Disable sticky sessions via web.config file:

<system.webServer>
           <httpProtocol>
               <customHeaders>
                    <add name="ARR-Disable-Session-Affinity" value="true"/>
                </customHeaders>
            </httpProtocol>
        </system.webServer>
Configure Private and Shared session as described here.

 

How to prevent an unexpected app pool recycling

By default, for each subdirectory, the application creates an object that monitors it and then for changed files it initiates a recycle notification. For example, changes of DDLs, configuration or any other files can cause the unexpected app pool recycling.

To minimise the risk of an “Overwhelming File Change Notifications” error leading to the application pool recycling, turn off a File Change Notification (FCN) setting by adding fcnMode="Disabled" to the httpRuntime section in web.config as follows:

<system.web>
     <httpRuntime fcnMode="Disabled" />
 </system.web>

 

How to take control over the app pool recycling

By default, Application Pool Recycling Settings -> Recycling Conditions -> Regular time intervals setting is set to 1,740 minutes, or 29 hours. It means that the application pool can get recycles at different times on different days. You can switch the recycling conditions to a specific time once a day. Ideally, choose the time when the traffic volumes are low, for example, 4.00 am before business hours.

Recycling conditions

How to Write Sitecore Logs to Application Insights

Sitecore can write log information to Microsoft Application Insights® when running on the Microsoft Azure® App Service. You can reconfigure file system logs to use Azure App Insights by patching Sitecore config files.

1. Add the Application Insights Instrumentation Key to ConnectionString.config file:

<add name="appinsights.instrumentationkey" connectionString="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" /> 

2. Add the Sitecore config patch to the /App_Config/Include/ folder:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <log4net>
      <appender name="LogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
        <patch:attribute name="type">Sitecore.Cloud.ApplicationInsights.Logging.LevelTraceAppender, Sitecore.Cloud.ApplicationInsights</patch:attribute>
        <file>
          <patch:delete />
        </file>
        <appendToFile>
          <patch:delete />
        </appendToFile>
        <encoding>
          <patch:delete />
        </encoding>
      </appender>

      <appender name="WebDAVLogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
        <patch:attribute name="type">Sitecore.Cloud.ApplicationInsights.Logging.LevelTraceAppender, Sitecore.Cloud.ApplicationInsights</patch:attribute>
        <file>
          <patch:delete />
        </file>
        <appendToFile>
          <patch:delete />
        </appendToFile>
        <encoding>
          <patch:delete />
        </encoding>
      </appender>

     <appender name="PublishingLogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
        <patch:attribute name="type">Sitecore.Cloud.ApplicationInsights.Logging.LevelTraceAppender, Sitecore.Cloud.ApplicationInsights</patch:attribute>
        <file>
          <patch:delete />
        </file>
        <appendToFile>
          <patch:delete />
        </appendToFile>
        <encoding>
          <patch:delete />
        </encoding>
      </appender>

      <appender name="SearchLogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
        <patch:attribute name="type">Sitecore.Cloud.ApplicationInsights.Logging.LevelTraceAppender, Sitecore.Cloud.ApplicationInsights</patch:attribute>
        <file>
          <patch:delete />
        </file>
        <appendToFile>
          <patch:delete />
        </appendToFile>
        <encoding>
          <patch:delete />
        </encoding>
      </appender>

      <appender name="CrawlingLogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
        <patch:attribute name="type">Sitecore.Cloud.ApplicationInsights.Logging.LevelTraceAppender, Sitecore.Cloud.ApplicationInsights</patch:attribute>
        <file>
          <patch:delete />
        </file>
        <appendToFile>
          <patch:delete />
        </appendToFile>
        <encoding>
          <patch:delete />
        </encoding>
      </appender>

      <appender name="ClientLogAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
        <patch:attribute name="type">Sitecore.Cloud.ApplicationInsights.Logging.LevelTraceAppender, Sitecore.Cloud.ApplicationInsights</patch:attribute>
        <file>
          <patch:delete />
        </file>
        <appendToFile>
          <patch:delete />
        </appendToFile>
        <encoding>
          <patch:delete />
        </encoding>
      </appender>

      <appender name="FxmLogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
        <patch:attribute name="type">Sitecore.Cloud.ApplicationInsights.Logging.LevelTraceAppender, Sitecore.Cloud.ApplicationInsights</patch:attribute>
        <file>
          <patch:delete />v
        </file>
        <appendToFile>
          <patch:delete />
        </appendToFile>
        <encoding>
          <patch:delete />
        </encoding>
      </appender>

      <appender name="OwinLogFileAppender" type="log4net.Appender.RollingFileAppender, Sitecore.Logging">
        <patch:attribute name="type">Sitecore.Cloud.ApplicationInsights.Logging.LevelTraceAppender, Sitecore.Cloud.ApplicationInsights</patch:attribute>
        <file>
          <patch:delete />
        </file>
        <appendToFile>
          <patch:delete />
        </appendToFile>
        <encoding>
          <patch:delete />
        </encoding>
      </appender>     
    </log4net>

    <pipelines>
      <initialize>
        <processor type="Sitecore.Cloud.ApplicationInsights.TelemertyInitializers.InjectTelemertyInitializers, Sitecore.Cloud.ApplicationInsights" patch:before="*[1]" />  
<processor type="Sitecore.Cloud.ApplicationInsights.Logging.RemoveSitecoreTraceListeners, Sitecore.Cloud.ApplicationInsights" patch:before="*[1]" />
      </initialize>
    </pipelines>
    
    <scheduling>
      <agent type="Sitecore.Cloud.ApplicationInsights.PerformanceCounterGrabberAgent, Sitecore.Cloud.ApplicationInsights" method="Run" interval="00:05:00" resolve="true" />
    </scheduling>

    <counterLoader type="Sitecore.Cloud.ApplicationInsights.CounterLoader,Sitecore.Cloud.ApplicationInsights">
      <includedCounters hint="raw:AddCounterToReport">
        <add category="Sitecore.Data" name="Data | Client Data Reads / sec" />
        <add category="Sitecore.Data" name="Data | Client Data Writes / sec" />
        <add category="Sitecore.Data" name="Data | Items Accessed / sec" />
        <add category="Sitecore.Data" name="Data | Physical Reads / sec" />
        <add category="Sitecore.Data" name="Data | Physical Writes / sec" />
        <add category="Sitecore.Caching" name="Cache Clearings / sec" />
        <add category="Sitecore.Caching" name="Cache Hits / sec" />
        <add category="Sitecore.Caching" name="Cache Misses / sec" />
        <add category="Sitecore.Caching" name="Data Cache Clearings / sec" />
        <add category="Sitecore.Caching" name="Data Cache Hits / sec" />
        <add category="Sitecore.Caching" name="Data Cache Misses / sec" />
        <add category="Sitecore.Caching" name="Html Cache Clearings / sec" />
        <add category="Sitecore.Caching" name="Html Cache Hits / sec" />
        <add category="Sitecore.Caching" name="Html Cache Misses / sec" />
        <add category="Sitecore.Caching" name="Path Cache Clearings / sec" />
        <add category="Sitecore.Caching" name="Path Cache Hits / sec" />
        <add category="Sitecore.Caching" name="Path Cache Misses / sec" />
        <add category="Sitecore.Caching" name="Registry Cache Clearings / sec" />
        <add category="Sitecore.Caching" name="Registry Cache Hits / sec" />
        <add category="Sitecore.Caching" name="Registry Cache Misses / sec" />
        <add category="Sitecore.Caching" name="View State Cache Clearings / sec" />
        <add category="Sitecore.Caching" name="View State Cache Hits / sec" />
        <add category="Sitecore.Caching" name="View State Cache Misses / sec" />
        <add category="Sitecore.Caching" name="XslCache Clearings / sec" />
        <add category="Sitecore.Caching" name="XslCache Hits / sec" />
        <add category="Sitecore.Caching" name="XslCache Misses / sec" />
        <add category="Sitecore.Analytics" name="Aggregation | Live Interactions Processed / sec" />
        <add category="Sitecore.Analytics" name="Aggregation | History Interactions Processed / sec" />
        <add category="Sitecore.Analytics" name="Aggregation | Total Interactions Processed / sec" />
        <add category="Sitecore.Analytics" name="Aggregation | Live Aggregation Errors / sec" />
        <add category="Sitecore.Analytics" name="Aggregation | History Aggregation Errors / sec" />
        <add category="Sitecore.Analytics" name="Aggregation | Total Aggregation Errors / sec" />
        <add category="Sitecore.Analytics" name="Aggregation | Contacts Processed / sec" />
        <add category="Sitecore.Analytics" name="Aggregation | Contact Processing Errors / sec" />
        <add category="Sitecore.Analytics" name="Aggregation | Average Batch Size - Live" />
        <add category="Sitecore.Analytics" name="Aggregation | Average Batch Size - History" />
        <add category="Sitecore.Analytics" name="Aggregation | Number of Batches Containing Failing Items - Live" />
        <add category="Sitecore.Analytics" name="Aggregation | Number of Batches Containing Failing Items - History" />
        <add category="Sitecore.Analytics" name="Aggregation | Average Aggregation Pipeline Time (ms)" />
        <add category="Sitecore.Analytics" name="Aggregation | Average Write Time (ms)" />
        <add category="Sitecore.Analytics" name="Aggregation | Average Contact Processing Pipeline Time (ms)" />
        <add category="Sitecore.Analytics" name="Aggregation | Average Write Time - Primary (ms)" />
        <add category="Sitecore.Analytics" name="Aggregation | Average Write Time - Secondary category=Live (ms)" />
        <add category="Sitecore.Analytics" name="Aggregation | Average Write Time - Secondary category=History (ms)" />
        <add category="Sitecore.Analytics" name="Aggregation | Active Interaction Aggregators" />
        <add category="Sitecore.Analytics" name="Aggregation | Average Check Out Time - Live (ms)" />
        <add category="Sitecore.Analytics" name="Aggregation | Average Check In Time - Live (ms)" />
        <add category="Sitecore.Analytics" name="Aggregation | Average Check Out Time - History (ms)" />
        <add category="Sitecore.Analytics" name="Aggregation | Average Check In Time - History (ms)" />
      </includedCounters>
    </counterLoader>
    
    <settings>
      <!--  SERVER ROLE
            The name for grouping metrics from instances by server role.
            Default value: Single
      -->
      <setting name="ApplicationInsights.Role" value="CD" />
      
      <!--  TELEMETRY TAGS
            Tags that are included in telemetry data to identify the metrics from an instance.-->
      <setting name="ApplicationInsights.Tag" value="" />
      
      <!--  DEVELOPER MODE
            Enables developer mode in Application Insights TelemetryConfiguration.-->
      <setting name="ApplicationInsights.DeveloperMode" value="false" />
      <setting name="Counters.Enabled">         <patch:attribute name="value">false</patch:attribute>       </setting>     </settings>   </sitecore> </configuration>

How to optimise the Disk I/O

You can optimise any reading from disk and write operations on the file system by:

  • Caching a file after the first request, for example, critical CSS or JavaScript files
  • Reducing the total number of operations on disk, for example, writing to a Sitecore Log you can control via the Priority setting assigned to the main Sitecore log, crawling and search logs, etc. and make sure that you only record errors on production, i.e. <priority value="ERROR" />;
  • Subscribing to the Premium App Service plan in Azure and using SSD disks to deliver better availability and latency compared to HDD ones.

How to boost cold start

To support cold start occurring after a force node restart, or after moving to a new worker process, you can specify an Application Initialisation configuration in web.config with key pages such as the homepage, search specific and section landing pages as follows:

<system.webServer>
    <applicationInitialization doAppInitAfterRestart="true">
        <add initializationPage="/" />
        <add initializationPage="/key-page1/" />
        <add initializationPage="/key-section/page2/" />
    </applicationInitialization>
</system.webServer>

The Application Initialisation module is designed to send warm-up requests over HTTP protocol, not HTTPS, therefore, it will not work if your website is configured to redirect all HTTP requests to the relevant HTTPS ones. The following configuration can be added to exclude the Application Initialisation from a rewrite rule via web.config file:

<rewrite> 
  <rules>
    <rule name="No redirect on warmup request" stopProcessing="true">
      <match url=".*" />
      <conditions> 
        <add input="{HTTP_HOST}" pattern="localhost" /> 
        <add input="{HTTP_USER_AGENT}" pattern="Initialization" /> 
      </conditions> 
      <action type="Rewrite" url="{URL}" /> 
    </rule> 
    <rule name="HTTP to HTTPS redirect for all requests" stopProcessing="true"> 
      <match url="(.*)" /> 
      <conditions>
        <add input="{HTTPS}" pattern="off" /> 
      </conditions> 
      <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" /> 
    </rule> 
  </rules> 
</rewrite>

How to configure a reverse proxy

Sitecore websites sometimes require you to serve external resources without unnecessary redirects. A reverse proxy function can be enabled by adding an applicationHost.xdt transformation to the “site” folder of the app service. For example, all requests starting with “/blog/” or legacy files can be overridden behind the scenes with the help of transformation rules, and by being served from another resource without 301/302 redirects.

Here’s what you need to do:

1. Enable the reverse proxy functionality using applicationHost.xdt file:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <system.webServer>
        <proxy xdt:Transform="InsertIfMissing" enabled="true" preserveHostHeader="false" reverseRewriteHostInResponseHeaders="false"/>
    </system.webServer>
</configuration>

2. Add a proxy rewrite rule to the web.config file:

<rewrite>
    <rules>
        <rule name="Proxy" stopprocessing="true">
            <match url="^blog/?(.*)" />
            <action type="Rewrite" url="http://www.yourblogdomain.com/{R:1}"/>
        </rule>
    </rules>
</rewrite>

3. Make sure that Sitecore does not intercept proxy requests by adding Sitecore config patch file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <settings>
            <setting name="IgnoreUrlPrefixes" value="/health/explore|/sitecore/default.aspx|..."  patch:instead="setting[@name='IgnoreUrlPrefixes']"/>
        </settings>
    </sitecore>
</configuration>

If you follow these recommendations you will significantly improve the performance of your Sitecore in Azure.

GET IN TOUCH
Make enquiry

Delete Limited.

Registered in England.

03933385

Registered Address.

3370 Century Way, Thorpe Park, Leeds, LS15 8ZB

VAT Registration.

GB 927 1409 27