Welcome back to TechXposer! Today, we’re diving into a concept that often pops up when we scale our applications: Session Affinity, commonly known as Sticky Sessions.

If you’ve been working with distributed systems or deploying applications to the cloud, you’ve likely encountered load balancers. But what happens when your application needs to “remember” a user across multiple requests? That’s where sticky sessions come in.

The “Bank Teller” Analogy

Imagine you walk into a large bank with multiple tellers. On your first visit, you are randomly assigned to Teller #3. You explain a complex issue regarding your account.

  • Without Sticky Sessions (Round Robin): The next time you walk into the bank to continue the transaction, the security guard sends you to Teller #1. You have to explain your entire complex issue all over again because Teller #1 doesn’t know your history.

  • With Sticky Sessions: The security guard gives you a special ticket. Every time you return to the bank, the guard looks at the ticket and routes you directly back to Teller #3, who already has all your paperwork on their desk.

In the web world, the “bank” is your server farm, the “security guard” is the Load Balancer, the “teller” is a specific server instance, and the “ticket” is usually an HTTP cookie.

When is Session Affinity Actually Useful?

As architects, we generally strive for stateless microservices. But reality isn’t always that clean. Sticky sessions are incredibly useful in a few specific scenarios:

  1. Stateful Applications (Legacy Migrations): If you are lifting and shifting an older application that relies heavily on in-memory session state, sticky sessions are a lifesaver. It buys you time to refactor the app to be stateless.

  2. Real-Time Communications: Applications using WebSockets or SignalR often require a persistent connection to a single server to maintain the communication pipeline without constant renegotiation.

  3. Heavy In-Memory Caching: Let’s say you are designing a multi-tenant Inventory Management System. If a specific node has already loaded a massive, complex tenant-specific catalog into its local RAM, routing that tenant’s subsequent requests to the same server can drastically reduce database hits and improve latency.

The Trade-Offs

Before you turn it on, it’s crucial to weigh the pros and cons.

Advantages

  • Performance Boosts: Leveraging local in-memory caches is much faster than fetching from a distributed cache like Redis or querying a database.

  • Simplifies State Management: You don’t have to immediately re-architect legacy apps to use distributed state stores.

  • Network Efficiency: Reduces the chattiness between your application servers and your backend data layers.

Disadvantages

  • Uneven Load Distribution: If one user (or tenant) generates 80% of your traffic, the server they are “stuck” to will get overloaded, while other servers sit idle.

  • Fault Tolerance Issues: If Teller #3 goes home sick (the server crashes), all the temporary state on their machine is lost. The user will be routed to a new server and lose their session data.

  • Scaling Complexity: Auto-scaling becomes trickier because gracefully draining connections from an affinitized server requires more complex orchestration.


How to Set It Up in ASP.NET Core 10

In the modern .NET ecosystem, the cleanest way to implement an API Gateway or Load Balancer with sticky sessions is using YARP (Yet Another Reverse Proxy).

Here is how you can set up Session Affinity using YARP in a .NET 10 application.

1. Install the NuGet Package

First, add the YARP package to your proxy project:

dotnet add package Yarp.ReverseProxy

 

2. Configure Program.cs

The setup in .NET 10 remains beautifully minimal. We just add the reverse proxy services and middleware.

var builder = WebApplication.CreateBuilder(args);

// Add YARP reverse proxy services
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();

// Map the endpoints
app.MapReverseProxy();

app.Run();

 

3. Configure appsettings.json

This is where the magic happens. We define our cluster, tell YARP to enable session affinity, and define the policy (typically Cookie-based).

{
  "ReverseProxy": {
    "Routes": {
      "inventory-route": {
        "ClusterId": "inventory-cluster",
        "Match": {
          "Path": "{**catch-all}"
        }
      }
    },
    "Clusters": {
      "inventory-cluster": {
        "SessionAffinity": {
          "Enabled": true,
          "Policy": "Cookie",
          "FailurePolicy": "Redistribute",
          "AffinityKeyName": "TechXposerAffinity"
        },
        "Destinations": {
          "node-1": {
            "Address": "https://localhost:5001"
          },
          "node-2": {
            "Address": "https://localhost:5002"
          }
        }
      }
    }
  }
}

 

What’s happening here?

  • Enabled: true turns on sticky sessions.

  • Policy: "Cookie" tells YARP to drop a cookie on the client’s browser after the first request.

  • FailurePolicy: "Redistribute" handles the “sick teller” scenario. If node-1 dies, YARP will gracefully redistribute the request to another healthy node instead of failing outright.

Final Thoughts

While stateless architectures are the gold standard, Session Affinity remains a powerful tool in your architectural toolbelt. Use it wisely, monitor your load distribution, and always plan for node failures!

Happy Coding 🙂