Stefan Holm Olsen

Performance tip: Disable sticky sessions in Optimizely DXP

This one is for all website developers who work with multi-server load balanced sites.

Routing affinity, going by names like ARR Affinity (on Azure App Service and IIS), Sticky Session (in Nginx, Apache mod_proxy etc.) and similar, are load balancing techniques. They all ensure that requests from a single browser session get served exclusively by the same origin server. This may be useful if you utilize server-side session storage or rely on writing to the local file system (which is not a good idea for many other reasons).

But while it may sound nice, it can hurt the load balancer efficiency. Under heavy load, a single server simply might get overwhelmed by requests, while new servers (especially those newly spun-up by an auto-scaler) receive less traffic. Simply because the newest servers cannot take over the existing browser sessions.

This kind of routing affinity is enabled by default on Azure App Service and Optimizely DXP.

For on-premises hosting configurations, this is an optional feature that can be quite complex to configure. So, it is usually only configured if it really is needed.

Disabling the routing affinity

The simplest solution is to just disable this routing. This will allow the load balancer to forward requests to any of the web server instances, using a selected load balancing algorithm.

But not so fast with Optimizely DXP sites! In Optimizely DXP, the site instance warm-up process relies on sticky sessions. Because of this, an Optimizely representative once sugested me to instead disable ARR affinity in the application code. And so I made this very simple pipeline middleware implementation for this purpose.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace DemoSite;

public class DisableArrAffinityMiddleware
{
    private readonly RequestDelegate _next;

    public DisableArrAffinityMiddleware(RequestDelegate next) =>
        _next = next ?? throw new ArgumentNullException(nameof(next));

    public Task Invoke(HttpContext context)
    {
        context.Response.Headers.Add("ARR-Disable-Session-Affinity", "true");

        return _next.Invoke(context);
    }
}

Then in the Startup.Configure method I can simply add this middleware to get going.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;

namespace DemoSite;

public class Startup
{
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // TODO: Find a good spot for this, among your other middleware configuration lines.
        app.UseMiddleware<DisableArrAffinityMiddleware>();
    }
}

Words of warning

Before disabling ARR affinity, sticky sessions or any other similar concept on a site, verify that the code is not depending on session storage, writing and reading files to local file system or anything else that relies on two requests to be handled by the same server.

Also be aware that, at the time of writing this, there is a bug in Optimizely’s Import Content and Export Content features that simply makes them not work. But I have been told that a fix is on the way.

The latest public news is that Azure App Service uses the simple round robin algorithm to distribute load among web server instances. Without ARR affinity, the load balancer will therefore distribute requests evenly, with no regard to capacity of instances etc. The downside to this is that an individual browser session may, in worst case, need each server to load (and cache) some user data. So plan your loading and caching strategies for that. One solution could be to introduce a distributed cache (like Redis).