Web Gardens, Web Farms, Clouds, and Session State in C# ASP .NET

Introduction

The majority of C# ASP .NET web applications are often developed with the intention of running in a single web server environment. They’ll often take advantage of ASP .NET’s Session State to store variables and data on the server, per user. As an ASP .NET web application grows in usage and popularity, scalability issues may arise, as the single web server is brought to its maximum capabilities. Whether due to overload of the server’s resources or simply as an enterprise fail-safe architecture, expanding the hosting environment to encompass multiple web servers and processors is often the next step for a growing enterprise-level C# ASP .NET web application.

In this article, we’ll take a look at several solutions for scaling a C# ASP .NET web application across a web garden, web farm, and beyond. In particular, we’ll focus on implications of Session State and potential solutions for scaling session across multiple processors and servers. We’ll also view an example web application utilizing Session (and magic spells) to examine the differences of running InProc versus OutOfProc in a web garden/web farm environment.

Good Old Session State

C# ASP .NET Session State is a memory-based storage repository on the PC or web server, allowing web applications to quickly read and write data on a per-user level. Session is used as a popular medium for storage data throughout the web application usage. It’s important to note that Session data is stored in the server’s memory, therefore as more data is stored within the Session, a greater degree of server memory will be consumed. Session can be utilized as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void Page_Load()
{
int Id = 1;

if (Session["ID"] != null)
{
// Read the ID from the Session.
Id = (int)Session["ID"];
}
else
{
// Save a new ID to the Session.
Session["ID"] = 1;
}
}

In the above example, Session state is first accessed to load an ID variable. If no value is present, a new value is saved into the Session state. The variable is persisted in the web server’s memory and held for the duration of the timeout value, specified in the web.config.

Throw Another Web Server Into the Mix

Consider the case where the above C# ASP .NET web application has grown to the point where a single web server is no longer sufficient to serve all requests. In this scenario, an additional web server would be needed to offload the original. A common solution is to copy the ASP .NET web application to the second web server and utilize a load balancer to route incoming requests between the two servers (usually in a round-robin algorithm or other pattern).

As the user accesses the web application, he’ll be served from one of the web servers for the initial request. Session data will be saved and read from the web server and the initial request will process as expected. However, on the second request, it is possible the user will be served from the second web server. In this case, since the second server has its own Session state, the data saved from the first request will be missing. The first server’s Session state has no knowledge of the second. As the user’s requests alternate between the two servers in the web farm, Session data will become inconsistent, leading to incomplete, corrupt, and missing data within the web application.

Getting our Terminology Straight

As shown in the above scenario, involving two web servers, the core issue with C# ASP .NET web applications in a Web Garden or Web Farm scenario is the persistence and synchronization of Session State data. As you can imagine, if the above scenario were increased to 10, 100, or even 1,000 web servers, the corruption of data would become even more apparent. As cloud services and cloud hosting becomes more popular, Session state will become even further problematic. Luckily, ASP .NET offers solutions for maintaining Session across gardens and farms. Before we discuss the solutions, let’s define the differences between a garden and farm.

What is a Web Garden?

A web garden is a hosting environment, on a single PC, consisting of multiple worker processes within a .NET web application pool. Typically, a web garden is utilized on a multi-core (multi-processor) PC or web server. Ideally, each worker process within the application pool would execute on an individual processor, helping to achieve more robust execution and processing of the C# ASP .NET web application threads.

A web garden can be created within Internet Information Services (IIS) by creating an application pool, selecting the Performance tab, and under the “Web Garden” section, setting the maximum number of worker processes to a value greater than 1. For example, setting the value to 3 would create three worker processes within the application pool. Of course, this would also mean you would have 3 individual Session states, as each worker process has its own memory, independent of the others.

IIS directs incoming requests to the worker processes within the application pool via a round-robin pattern which associates a connection with a worker process and assigns the next connection to the next available process.

Due to the fact that web gardens utilize multiple worker processes with their own Session state and memory, a web garden can actually be used as a precursor for testing a web application for compatibility within a web farm scenario.

When Should You Use a Web Garden?

In general, web gardens use more resources than typical hosting environments. They may also be slower in performance and add unnecessary complexity. While a web garden seems less than ideal, it does have useful scenarios which may include when a non-CPU bound application needs to execute a long running request, such as an intensive computational request, long database transaction, or other long process. Another scenario may involve unreliable web application processing, which could cause a worker process to halt or freeze. In both cases, a web garden could offer a solution by offloading the single worker process and distributing incoming requests amongst the workers in the application pool.

The larger gain for web gardens is not speed, but rather robustness. If one of the worker processes hosting in a web application goes down or freezes, in a single worker-process environment, the entire web application would go down. However, in a web garden scenario, the remaining worker processes could continue serving requests, even as the faulty worker process is taken down and restarted.

Due to their robustness, enabling web gardens can often be a good way to see if a C# ASP .NET web application will function correctly in a multi-server web farm environment. Often, if a web application performs well in a garden scenario, it will typically be easier to migrate to a web farm.

What is a Web Farm?

A web farm is a hosting environment consisting of multiple web servers. The web servers are usually tied together with a load balancer, which routes incoming requests amongst the servers within the web farm.

Incoming web farm requests can be routed in a variety of formats. One popular format is to use IP affinity (also called client affinity or sticky session), which routes incoming requests to the same web server for the duration of the web application session. This is performed per IP address or connection, as configured by the load balancer. The benefit of sticky sessions is that Session state data can be maintained in-process, just as it would on a single web server hosting environment, without the complications from alternating between web servers. However, a down-side to IP affinity is in not taking full advantage of the web farm capability in fully distributing and routing all requests and sessions.

When Should You Use a Web Farm?

Web farms are often used in enterprise environments to provide fail-safe and reliable services for highly-used web applications. Web farms offload incoming traffic by alternating between servers and by evenly distributing web application load. This increases response time, performance, and reliability. Additional web servers may be added or taken away from a web farm to increase or decrease the web application performance, as necessary for the target environment.

What is a Web Cloud?

A web cloud is the next level from a web farm scenario. Where a web farm consists of multiple web servers, connected with a load balancer, a web cloud may consist of multiple web farms, load balancers, and even slices of individual web servers, all provided on demand. A web cloud can scale and reduce resources as needed and throughout the execution of a web application. C# ASP .NET web applications developed for compatibility with a multi-server environment can often be utilized in both web farm and web cloud environments.

Solving the Problem of Web Farm Session State

With terminology out of the way, we can discuss the solutions for migrating a C# ASP .NET web application from the default InProc Session state to an OutOfProc or distributed session state.

Sticky-Sessions and IP Affinity

As mentioned in the web farm topic above, sticky-sessions can be utilized as a quick solution for Session state. By directing incoming requests to the same web server throughout the session, data will always be pulled from memory on the same physical web server. Compatibility and support for configuring sticky sessions can be located within the load balancer settings or web cloud configuration, if available.

The main drawback to sticky-sessions for managing InProc session state is that it opens the individual web servers to potential overload. This results in a less scalable solution when compared with distributed session state or session-less web application architectures.

OutOfProc Session State

Out-of-process session state can be used within C# ASP .NET web applications to distribute session data across multiple servers. ASP .NET allows for two types of default OutOfProc session state, including SQLServer and StateServer.

SQLServer Session State

SQLServer session state stores data within an SQL Server database. Requests from each web server for session data can pull from the database to retrieve session information and resynchronize. A benefit to SQLServer OutOfProc session state is that it allows for fail-over SQL servers and backup of mission critical information. However, a drawback is that it is often slower in processing, due to read/write IO.

StateServer Session State

StateServer session state stores data within a 3rd-party process, aspnet_state.exe. The process is shared by multiple worker processes and web servers and servers as a memory-based database for holding session state data. StateServer serves data over a TCP port, typically port 42424, and allows incoming requests from outside servers. A benefit to StateServer is its resulting speed in memory IO. However, a drawback is that the usage of memory can grow over-time, potentially over-using the available server resources.

To utilize StateServer for OutOfProc in ASP .NET, you will first need to run the aspnet_state service (at a command prompt, execute: net start aspnet_state). This service can be configured to run automatically upon server start-up. You can then configure the web application’s web.config file to use SessionState as follows:

1
2
3
4
<system.web>
<sessionState mode="stateserver" stateConnectionString="tcpip=127.0.0.1:42424" />
</system.web>

It is important to note, for StateServer running on the same PC, the IP address 127.0.0.1 or localhost may be used. For outside servers, designate the required IP address and port. You’ll also want to verify the port is open to any firewalls on the StateServer-hosting server. To allow access to remote web servers to access the session state, you’ll also want to verify the following registry setting is set to a value of 1:

1
2
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters\AllowRemoteConnection

StateServer also requires all web servers to utilize the same machineKey value. The machineKey is used to decrypt ViewState and forms authentication cookie information as incoming data alternates between web servers. For example, in the web.config, the following automatically generated machine key should be replaced with a static key, as follows:

Replace the following line:

1
2
<machineKey validationKey="AutoGenerate,IsolateApps" decryptionKey="AutoGenerate,IsolateApps" validation="SHA1"/>

Use a static machineKey:

1
2
3
4
<system.web>
<machineKey validationKey="91F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD6" decryptionKey="261F793EB53B761503AC445E0CA28DA44AA9B3CF06263B77" validation="SHA1" />
</system.web>

Additional details on setting the machine key can be found on MSDN for How to Configure MachineKey in ASP .NET 2.0.

Simulating a Web Farm environment on One PC

Prior to physical hosting in a web farm scenario, a web garden can be used for preliminary web application testing while developing. Consider the example below, which creates a basic C# ASP .NET MVC web application, using the Razor MVC view engine. The application will read and write to Session and be hosted on a local web garden. We’ll configure OutOfProc SessionState on the web garden to distribute the Session State across the multiple worker processes (simulating a web farm with multiple servers). This example is hosted on a quad-core Windows PC with IIS 6.0 (offering a maximum of 4 cores for compatibility with 4 worker processes in a web garden).

A Basic Serializable Session Object

For this example, we’ll create a basic type object, which will be stored and retrieved from the Session. We’ll use this object to test the distribution of the OutOfProc session state and proper performance across the web garden/web farm. It’s important to note that objects stored in the session must be serializable. For custom type objects, this requires adding the [Serializable] attribute to the beginning of each class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
[Serializable]
public class MagicSpell
{
public int Id { get; set; }
public string Name { get; set; }
public int ViewCount { get; set; }
public bool New
{
get
{
return (ViewCount <= 1);
}
}

#region Names

private string[] spellFirstName = new string[10]
{
"Magic",
"Poison",
"Fire",
"Acid",
"Lightning",
"Silver",
"Energy",
"Chaos",
"Life",
"Force"
};

private string[] spellLastName = new string[10]
{
"Bolt",
"Flame",
"Sword",
"Whip",
"Cloud",
"Stream",
"Lasso",
"Storm",
"Missile",
"Blast"
};

#endregion

public MagicSpell()
{
Random random = new Random((int)DateTime.Now.Ticks);

// Generate a random Id from 0-99.
int randomId = random.Next(100);

// Set the actual Id to be 1-100.
Id = randomId + 1;

// Pad the Id to be 2 digits (01, 10, 99, etc).
string randomString = randomId.ToString().PadLeft(2, '0');

// Take the left digit to use as an index // for the first name and the right digit to use as an index for the last name.
int leftIndex = Convert.ToInt32(randomString[0].ToString());
int rightIndex = Convert.ToInt32(randomString[1].ToString());

Name = spellFirstName[leftIndex] + " " + spellLastName[rightIndex];
}

public MagicSpell(int id, string name)
{
Id = id;
Name = name;
}
}

In the above code, we’ve created a basic type object for storing in the session. We’ll create this object initially when the ASP .NET MVC Razor view page loads. We’ll maintain a count of how many times the object has been viewed in the same session. We’ve also included some additional code to generate a random “magic spell” name, which helps demonstrate when different objects are created, depending on session, user, and InProc versus OutOfProc configuration.

The Test MVC Controller

We’ll define our C# ASP .NET MVC controller to create a new MagicSpell object upon loading the view, and attempt to read the object from the Session on each page refresh. If the object is not found, a new one will be created.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class SessionController : Controller
{
private const string SESSION_KEY = "MagicSpell";

public ActionResult Index()
{
MagicSpell magicSpell = GetOrCreateMagicSpell();

return View(magicSpell);
}

public ActionResult Submit()
{
MagicSpell magicSpell = GetOrCreateMagicSpell();

return PartialView("SessionProductUserControl", magicSpell);
}

private MagicSpell GetOrCreateMagicSpell()
{
MagicSpell magicSpell = (MagicSpell)Session[SESSION_KEY];

// If an object is not found in the Session, create a new one.
if (magicSpell == null)
{
magicSpell = new MagicSpell();
}

// Increment view count.
magicSpell.ViewCount++;
Session[SESSION_KEY] = magicSpell;

return magicSpell;
}
}

Notice in the above MVC controller, we attempt to create or read a MagicSpell object from the session upon loading the MVC view page. Upon each refresh, we’ll again try to read or create the object. We’ve also included a button on the page which calls the Submit() method via an AJAX callback, returning a PartialView (user control) with the same content.

The MVC Razor View Markup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@model MvcApplication3.Models.MagicSpell

@{
View.Title = "Session Test";
Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Session Test</h2>

Loaded from Session:
<div id="lblText" style="border: 1px solid #c0c0c0; width:400px; height:50px; padding: 10px 10px 10px 10px;">
@if (Model != null)
{
<text>(ID @Model.Id) @Model.Name (@Model.ViewCount views)</text>

if (Model.New)
{
<br />
<text>New Magic Spell Created! </text>
}
}
</div>

@using (Ajax.BeginForm("Submit", new AjaxOptions { UpdateTargetId = "lblText" }))
{
<p>
<input type="submit" id="btnSubmit" value="Read Session" />
</p>
}

For purposes of this example, the MVC Razor markup code has been included above. We’re strongly-binding the view to our type MagicSpell, which is used to display the contents within the view. In addition to refreshing the page to reload the object, we’ve also provided an AJAX-submit form to refresh the content.

Running the Example in a Web Garden InProc

For our initial example, we’ll setup the web application to run in a web garden environment with 3 worker processes, but we’ll leave the web.config set to run InProc. That is, Session data will be expected to exist in the same location, in-process. Upon executing, we can see the following results:

C# ASP .NET Web Farm Session State

Notice in the first run, upon accessing the page the first time, a new magical spell (Fire Storm) is created and saved in Session.

C# ASP .NET Web Farm SessionState

Upon refreshing the page (or clicking the Read Session button to re-load with AJAX), the same magical spell is loaded (Fire Storm), this time with the view count incremented to 2, as expected. This indicates the same worker process within the web garden has served us the page consecutively. This would be same result as if IP affinity (or sticky sessions) were configured for the web garden or web farm. If we continue to refresh the page, we would expect the same magical spell to display with the view count incremented accordingly.

C# .NET Session State Web Farm Cloud Hosting

As shown in the above screenshot, the 3rd refresh of the page results in a new magical spell being created, with a fresh view count of 1. IP affinity is indeed not setup, and the prior consecutive executions on the same worker process were simply first-time luck. The third refresh has executed on a different worker process. In this new worker process (or web server in the web farm), our session data did not exist. Therefore, a new object was created and initialized.

Subsequent page refreshes of the ASP .NET MVC Razor web application would display additional view counts of the same two magical spells and a third magical spell created for the last worker process. After all worker processes have executed at least once, any remaining page refreshes will display at least one of the three previous magical spells already created.

As you can tell in the above execution, running as InProc in this environment is less than ideal. However, we can solve this by re-configuring the web application’s web.config to utilize OutOfProc StateServer storage.

Scaling our Example

We can correct the issue above with regard to losing our session state, by configuring the web application to use OutOfProc session state. For simplicity (and speed), we’ll utilize StateServer by adding the following line to our web.config file:

1
2
<sessionState mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424"    cookieless="false" />

Upon executing the web application, now a single magical spell is created and stored within session. Subsequent page refreshes continue to display the same magical spell and increment the view count each time. Since our web application is now loading session state from the 3rd-party StateServer process, all of the worker processes within the web garden can access the same, synchronized, session state data.

Scaling a Web Farm with Web Services

Growing a web application’s architecture beyond a single web server design and utilizing the power of a web farm can greatly enhance the reliability and performance of a web application. However, for some high frequency and traffic-intensive web applications, a single web farm alone may not be enough. In these cases, the web application architecture can be expanded to span multiple web farms, particularly through the usage of WCF web services.

It is common to abstract a layer of the web application design for handling data processing and database activity. This layer can be abstracted further into a WCF web service layer, hosted and executed within its own web server, separate from the main web application’s web server. Taking this one step further, the main web application can execute on its own web farm and load balancer, while the WCF web service executes on its own web farm and load balancer. Each tier of the solution can be expanded and grown as necessary to serve the users’ needs. The same principle can be applied to migrating the two tiers to web clouds or even multiple web services on multiple tiers, complexity aside.

Conclusion

Web farms provide a necessary solution for offloading and expanding high-traffic and demanding C# ASP .NET web applications. When not available during development, web gardens may be utilized to help simulate the demands of a web farm environment by providing multiple worker processes on a multi-core machine. Configuring the web application to use OutOfProc session in a web garden scenario, can help resolve issues prior to application deployment on a web farm. Through the process of connecting web servers and load balancers to create web farms, and even linking multiple web farms with tiers together, a more robust and reliable enterprise-hosted solution can be creating for supporting your web application needs. Further abstraction and migration to web clouds can provide on-demand web application sizing and scalability. In conclusion, while many web applications will not require multi-server hosting solutions, it is important to keep in mind scalability and growth issues in order to provide an optimal solution for future enterprise requirements.

About the Author

This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.

Share