ASP.NET Session State Partitioning using State Server Load Balancing

It seems like amount of posts on ASP.NET's Session State keeps growing. Here's the list:

Yesterday's post on Session State Partitioning used a round-robin method for partitioning session state over different state server machines. The solution I presented actually works, but can still lead to performance bottlenecks.

Let's say you have a web farm running multiple applications, all using the same pool of state server machines. When having multiple sessions in each application, the situation where one state server handles much more sessions than another state server could occur. For that reason, ASP.NET supports real load balancing of all session state servers.

Download example

Want an instant example? Download it here: SessionPartitioning2.zip (4.16 kb)
Want to know what's behind all this? Please, continue reading.

What we want to achieve...

Here's a scenario: We have different applications running on a web farm. These applications all share the same pool of session state servers. Whenever a session is started, we want to store it on the least-busy state server.

1. Performance counters

To fetch information on the current amount of sessions a state server is storing, we'll use the performance counters ASP.NET state server provides. Here's a code snippet:

[code:c#]

if (PerformanceCounterCategory.CounterExists("State Server Sessions Active", "ASP.NET", "STATESERVER1")) {
    PerformanceCounter pc = new PerformanceCounter("ASP.NET", "State Server Sessions Active", "", "STATESERVER1");
    float currentLoad = pc.NextValue();
}

[/code]

2. Creating a custom session id

Somehow, ASP.NET will have to know on which server a specific session is stored. To do this, let's say we make the first character of the session id the state server id from the following IList:

[code:c#]

IList<StateServer> stateServers = new List<StateServer>();

// Id 0, example session id would be 0ywbtzez3eqxut45ukyzq3qp
stateServers.Add(new StateServer("tcpip=10.0.0.1:42424", "sessionserver1"));

// Id 1, example session id would be 1ywbtzez3eqxut45ukyzq3qp
stateServers.Add(new StateServer("tcpip=10.0.0.2:42424", "sessionserver2"));

[/code]

Next thing we'll have to do is storing these list id's in the session id. For that, we will implement a custom System.Web.SessionState.SessionIDManager class. This class simply creates a regular session id, locates the least-busy state server instance and assign the session to that machine:

[code:c#]

using System;
using System.Diagnostics;


public class SessionIdManager : System.Web.SessionState.SessionIDManager
{
    public override string CreateSessionID(System.Web.HttpContext context)
    {
        // Generate a "regular" session id
        string sessionId = base.CreateSessionID(context);

        // Find the least busy state server
        StateServer leastBusyServer = null;
        float leastBusyValue = 0;
        foreach (StateServer stateServer in StateServers.List)
        {
            // Fetch first state server
            if (leastBusyServer == null) leastBusyServer = stateServer;

            // Fetch server's performance counter
            if (PerformanceCounterCategory.CounterExists("State Server Sessions Active", "ASP.NET", stateServer.ServerName))
            {
                PerformanceCounter pc = new PerformanceCounter("ASP.NET", "State Server Sessions Active", "", stateServer.ServerName);
                if (pc.NextValue() < leastBusyValue || leastBusyValue == 0)
                {
                    leastBusyServer = stateServer;
                    leastBusyValue = pc.NextValue();
                }
            }
        }

        // Modify session id to contain the server's id
        // We will change the first character in the string to be the server's id in the
        // state server list. Notice that this is only for demonstration purposes! (not secure!)
        sessionId = StateServers.List.IndexOf(leastBusyServer).ToString() + sessionId.Substring(1);

        // Return
        return sessionId;
    }
}

[/code]

The class we created will have to be registered in web.config. Here's how:

[code:c#]

<configuration>
  <system.web>
    <!-- ... -->
    <sessionState mode="StateServer"
                partitionResolverType="PartitionResolver"
                sessionIDManagerType="SessionIdManager" />
    <!-- ... -->
  </system.web>
</configuration>

[/code]

You notice our custom SessionIdManager class is now registered to be the sessionIDManager. The PartitionResolver I blogged about is also present in a modified version.

3. Using the correct state server for a specific session id

In the previous code listing, we assigned a session to a specific server. Now for ASP.NET to read session state from the correct server, we still have to use the PartitionResolver class:

[code:c#]

using System;


public class PartitionResolver : System.Web.IPartitionResolver
{

    #region IPartitionResolver Members

    public void Initialize()
    {
        // No need for this!
    }

    public string ResolvePartition(object key)
    {
        // Accept incoming session identifier
        // which looks similar like "2ywbtzez3eqxut45ukyzq3qp"
        string sessionId = key as string;

        // Since we defined the first character in sessionId to contain the
        // state server's list id, strip it off!
        int stateServerId = int.Parse(sessionId.Substring(0, 1));

        // Return the server's connection string
        return StateServers.List[stateServerId].ConnectionString;
    }

    #endregion

}

[/code]

kick it on DotNetKicks.com 

This is an imported post. It was imported from my old blog using an automated tool and may contain formatting errors and/or broken images.

Leave a Comment

avatar

20 responses

  1. Avatar for Fenil Desai
    Fenil Desai January 25th, 2008

    There can't be a better post on Session Partitioning that this one.
    Absolutely Amazing......

  2. Avatar for Scott Hanselman
    Scott Hanselman February 1st, 2008

    Fantastic series! One question, however, isn't there measurable overhead in the creation of that remote Performance Counter? How much optimization have you had to do in that CreateSessionID call?

  3. Avatar for maartenba
    maartenba February 1st, 2008

    Thank's :-) The remote performance counter will definitely give some overhead, but I did not measure this in a real-life environment.

  4. Avatar for Yolion
    Yolion February 20th, 2008

    Great

  5. Avatar for Elan
    Elan March 18th, 2008

    How does this address failover? Am I correct in assuming that when a server goes down, the if (PerformanceCounterCategory.CounterExists) doesn't blow the whole thing up and also prevents that [offline] server from being assigned to store this session state?

    But then what happens if a user's session state had been assigned to the server that just went down? Does the PartitionResolver throw an error? Perhaps to make this more robust, the exception handling can be set to reset and reassign the session? Then the worst that happens to the client is a lost session - but no error messages.

  6. Avatar for maartenba
    maartenba March 18th, 2008

    This example code does NOT include any checks on remote server existance. You would have to check if the state server is running in the code prior to assigning a session to it without failure massages.

  7. Avatar for Elan
    Elan March 18th, 2008

    [quote]if (PerformanceCounterCategory.CounterExists("State Server Sessions Active", "ASP.NET", stateServer.ServerName))[/quote]

    Isn't that an inherent check on whether the State Server is operating? Or will you error out entirely if the machinename reference leads to a server that is down?

    If it works properly as is, then couldn't you include this check in the PartitionResolver class and handle appropriately if your session is pointing at a server that can no longer be found?

  8. Avatar for maartenba
    maartenba March 19th, 2008

    As far as I can see there are 2 things that can go wrong:
    1) The performance counter is unavailable
    2) State server is unavailable

    Possible solutions:
    1) Check for Exceptions on PerformanceCounterCategory.CounterExists(...), respond to Exceptions by trying the next server. If it fails > X times, remove it from the list of servers.
    2) Respond to possible Exceptions. If it fails > X times, remove it from the list of servers.

  9. Avatar for sss
    sss July 16th, 2008

    Pingback from blogs.msdn.com

    JoeOn.net In Japanese : Windows Workflow Foundation &#227ƒ&#227ƒ&#165&#227ƒ&#188&#227ƒˆ&#227ƒ&#170&#227‚&#162&#227ƒ&#171 &#227‚&#183&#227ƒ&#170&#227ƒ&#188&#227‚&#186

  10. Avatar for Chris
    Chris September 2nd, 2008

    This sounds absolutely fab!! I have tried implementing it but have failed. How do I implement this in IIS? thanks

  11. Avatar for submariner_highlander
    submariner_highlander November 1st, 2008

    Lets try it again. Your code is flawed in 2 places:

    1.SessionIdManager - if you have two state servers in the list one will never get hit.

    2.ParftitionResolver - When a state server goes down all live sessions for that server will raise an exception. If that exception is handled in Application_Error handler in Global.asax by redirecting to a custom error page (common scenario) and that page try to access session it will hit PartitionResolver again creating a nasty loop. If you have enough live sessions for the dead state server it might bring your application down.

    To overcome the problem you have to catch that exception, create a new sessionID, recreate session object for the current context with that new sessionID. Sure user will loose the session but the application will recover gracefully and they can start a new session.

  12. Avatar for SamR
    SamR November 19th, 2008

    Hey All,

    We have a website that is running on 2 boxes, and every other click we lose our sessions in production. This doesn't happen in local machine at all. Should I try switching to SQL Server or partitioning.
    Thank u.

  13. Avatar for maartenba
    maartenba November 19th, 2008

    Sam, I guess you should consider both options: SQL server will be more reliable but a little slower, state server / partitioning will be a bit faster but there's still possible loss of session data.

  14. Avatar for submariner_highlander
    submariner_highlander November 24th, 2008

    Sam

    Maarten's post was the great starting point and I am really greatful to him for helping me kick-start my own project. I have done some further work and now I have the solution we are going to use in our production environment. You are free to use it if you wish, you can read about if you visit the following link:

    http://en.aspnet-bhs.info/p...

  15. Avatar for lianglisen
    lianglisen December 31st, 2009

    About your article “ASP.NET Session State Partitioning using State Server Load Balancing”
    I want to ask something:I have download and test you code from maarten balliauw blog,is the webserver appication store the session on the state server one by one?e.g:1st connection,session store in state server A, 2nd connection,session store in state server B,....... cycle again and again。Hoping to get you help.Best Regards.

  16. Avatar for maartenba
    maartenba December 31st, 2009

    Yes, that's correct. Unless the performance counter tells there's too much load, the server is skipped for storing state. Note that this is just an example and should not be used in production without further fine tuning.

  17. Avatar for raees
    raees October 29th, 2010

    Well everything is fine in this post.

    But from the sample application when i host my application on Server A, and use State Server in B and C and try to do load balancing i added

    stateServers.Add(new StateServer("tcpip=192.168.1.11:42424", "."));
    stateServers.Add(new StateServer("tcpip=192.168.1.12:42424", "."));

    Then from my webserver whoose ip is 192.168.1.10 i accessed the information and everytime the nextvalue was 0 only and it was picking the first state server. I accessed the same machine from 10 different machine and all were coming to first state server and nextvalue was always 0 only when i printed that.

    Please help how can i do this.

  18. Avatar for submariner_highlander
    submariner_highlander October 29th, 2010

    Here is full solution with all the issues resolved:

    http://en.aspnet-bhs.info/p...

    It is built upon this excelent post by Maarten.

  19. Avatar for raees
    raees October 29th, 2010

    Hi Echte

    I tried executing your code. Everything appears to be fine. but i get this error

    Access is denied
    Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.ComponentModel.Win32Exception: Access is denied

    Source Error:

    Line 77:
    Line 78: ' Fetch server's performance counter
    Line 79: If PerformanceCounterCategory.CounterExists("State Server Sessions Active", "ASP.NET", stateServer.ServerName) Then
    Line 80:
    Line 81: Dim pc As New PerformanceCounter("ASP.NET", "State Server Sessions Active", "", stateServer.ServerName)

    Source File: D:\Projects\App_Code\BLL\SessionIdManager.vb Line: 79

    On debugging further i found that in the web.config when i change the servername from "." to my machine name it gives this error, where as when i use the name as just "." it works fine. But in that case i doubt it is picking up multple state server it just picks up the first state server.

    Can you let me know the behavious of this.

  20. Avatar for submariner_highlander
    submariner_highlander October 30th, 2010

    raees,

    That may be specific to your environment, it is obviously a security issue. You may wish to read the following post:
    http://west-wind.com/weblog...

    I tested it a few year ago on a couple of virtual machines running on the single box, most likely there were no security limitations of that kind.