High Performance ASP.NET application (1) – Optimizing state management

Today we’ll talk a little about how to optimize state management when coding your ASP.NET application. I believe most of ASP.NET developers more or less have some knowledge of using state in ASP.NET. In this article, we’ll not be focusing on how to use state in ASP.NET but how to more efficiently and effectively manage state in ASP.NET. Web application present specific challenges for state management especially for Web farm/Garden scenarios. The choices that you make regarding where and how state is stored have a significant impact on the performance and scalability of your application.

 

Generally, there are three kinds of states in ASP.NET: Application state, Session state and View state. They need to be used in different scenarios respectively. Application state is used for storing application wide state for all clients. Session state is used for storing per-user state. Here per-user can be translated to the requests coming from the same browser. View state is used for storing per-page state information like all the contents in the controls on this specific page. In general, there are two guidelines in state management: 1) Store simple state on the client 2) Consider serialization costs. For the first point, you can leverage client cookies, query strings or even hidden controls when view state is unavailable. Regarding the serialization of state, keep in mind that only store what is necessary and prefer simple types (15% - 25% impact to performance) rather than complex objects (even more impact).

 

Now we’ll talk about how to optimize application state, session state and view state management respectively.

 

Application state management tips:

 

1.       HttpApplicationState VS Static Properties: You should avoid storing data in Application Object. Instead, you can use the static members of the application class. You can gain performance improvement from it because accessing to static members is much faster than accessing to Application Object. A little example here:

 

<%

private static string[] _states[];

private static object _lock = new object();

public static string[] States

{

  get {return _states;}

}

public static void PopulateStates()

{

  //ensure this is thread safe

  if(_states == null)

  {

    lock(_lock)

    {

        //populate the states… }

  }

}

public void Application_OnStart(object sender, EventArgs e)

{

  PopulateStates();

}

%>

 

2.       Application State VS ASP.NET Cache. Prefer to store read-only date in Application state while you can use cache to store read-write data to avoid server/process affinity issues.

 

3.       Avoid storing STA COM objects in application state. All the threads accessing to STA COM object will be serialized which would incur severe performance issue.

 

Session state management tips: Basically, we have 3 choices to store session state: InProc, StateServer and SQL server. InProc is the fastest choice but the state data will be lost upon the process recycling or crashing. It can’t be used in Web Farm or Web Garden deployment. StateServer can be installaed on either local server or a remote server. This approach scales well but it will impact the performance due to state serialization and de-serialization that is required to transfer the state to and from the state store. SQL server can provide a high scalability and capability in a large web platform that needs to store myriads of user session data. The serialization and de-serialization cost is a little more than StateServer. However, SQL server provides much better robustness with SQL cluster.

 

Session state management tips:

 

1.       Prefer basic types to reduce serialization costs. Basic types are Int, Byte, Decimal, String, DataTime, TimeSpan, Guid, IntPtr and UintPtr. The reason why basic types serialization is faster than complex types is ASP.NET uses an optimized internal serialization method to serialize basic types while complex types users BinaryFormatter object.

 

2.       Disable Session State if you do not use it.

 

3.       Just like Application State. Avoid storing STA COM objects in Session state as well.

 

4.       Use ReadOnly Attribute as much as you can. Because Page requests that use session state internally use a ReaderWriterLock which means at any given time it allows either concurrent read access for multiple threads, or write access for a single thread. If you requests need to modify the session state the requests will be serialized. Below is a simple example. When you browse Main.aspx and you click the Refresh button in slow zone and the click the button in quick zone, you’ll see the requests will be serialized which means the quick and the slow pages will be rendered to the client simultaneously.

 

Main.aspx:

 

<%@ Page Language="C#" enableSessionState="true"%>

<frameset rows="50%, 50%">

  <frame src="quickpage.aspx" />

  <frame src="slowpage.aspx" />

</frameset>

 

Quickpage.aspx:

 

<%@ Page Language="C#" enableSessionState="true"%>

<script runat="server">

  protected void Page_Load(object sender, EventArgs e)

  {

    Session["test"] = "testquickpage";

    Response.Write(DateTime.Now.ToLongTimeString());

    Response.Write(" " + Session["test"]);

  }

</script>

<form runat="server">

  <asp:button runat="server" text="refresh" />

</form>

 

Slowpage.aspx:

 

<%@ Page Language="C#" enableSessionState="true"%>

<script runat="server">

  protected void Page_Load(object sender, EventArgs e)

  {

    Session["test"] = "testslowpage";

    System.Threading.Thread.Sleep(50000);

    Response.Write(DateTime.Now.ToLongTimeString());

    Response.Write(" " + Session["test"]);

  }

</script>

<form id="Form1" runat="server">

  <asp:button runat="server" text="refresh" />

</form>

>

Access to main.aspx will cause the three pages to be in the same session. The request processing would be serialized. The call stack shows that thread 30 is processing slowpage.aspx while quickpage.aspx will be processed in another thread but is pending for thread 30 to complete. Check out the Timeout value, it increases nearly in the same pace between slowpage and quickpage.

HttpContext Timeout Completed Running ThreadId ReturnCode Verb RequestPath+QueryString

0x0000000155740530 110 Sec no 10 Sec 28 200 POST /sessionRWLock/slowpage.aspx

0x000000019570b2d8 110 Sec no 305 Sec XXX 200 GET /sessionRWLock/main.aspx

0x00000001957af010 110 Sec no 9 Sec XXX 200 POST /sessionRWLock/quickpage.aspx

 

HttpContext Timeout Completed Running ThreadId ReturnCode Verb RequestPath+QueryString

0x0000000155740530 110 Sec no 89 Sec XXX 200 POST /sessionRWLock/slowpage.aspx

0x000000019570b2d8 110 Sec no 384 Sec XXX 200 GET /sessionRWLock/main.aspx

0x00000001957af010 110 Sec no 88 Sec XXX 200 POST /sessionRWLock/quickpage.aspx

 

This is the managed call stack of thread 30 and it shows it’s stuck in Page_Load.

 

0:030> !clrstack

OS Thread Id: 0x664 (30)

Child-SP RetAddr Call Site

000000000436de70 000007feece23ec9 ASP.slowpage_aspx.Page_Load(System.Object, System.EventArgs)

000000000436ded0 000007fee3e4800a System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)

000000000436df00 000007fee3e3e7e4 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs)

000000000436df30 000007fee3e3e842 System.Web.UI.Control.OnLoad(System.EventArgs)

000000000436df70 000007fee3e3adcc System.Web.UI.Control.LoadRecursive()

000000000436dfc0 000007fee3e3a2d0 System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)

000000000436e090 000007fee3e3a1fb System.Web.UI.Page.ProcessRequest(Boolean, Boolean)

000000000436e0f0 000007fee3e3a190 System.Web.UI.Page.ProcessRequest()

000000000436e150 000007ff00190219 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)

000000000436e1b0 000007fee3e41637 ASP.slowpage_aspx.ProcessRequest(System.Web.HttpContext)

 

View state management tips:

 

View state is used by server control to retain the content that post data back to themselves (same page). A specific hidden variable called _VIEWSTATE will contain all the view state information. Load view state happens after the page initialization and the save view state happens before the render of the web page. Because it’s so easy to store/retrieve the data from view state, view state can be easily misused. View state is actually an unnecessary overhead for pages that do not need it. Below is some tips:

 

1.       Disable View State if possible. If your page doesn’t post back, no server control events happened or no old data is stored, you can simply disable view state.

 

2.       Minimize the information stored in view state. If you need to store data in view state, remember to store basic types and small objects.

 

3.       Keep an eye on the size of view state. Enable a trace or something to monitor the view state size for each control.

Reference: Understanding ASP.NET ViewStat

https://msdn.microsoft.com/en-us/library/ms972976.aspx

Above is basically what I think is important in ASP.NET State Management. I hope it is helpful. If you have any questions or concerns just let me know I am glad to discuss any technical issues with you. Thanks for your time reading through my team blog!

Yawei