Monday, March 19, 2012

Session Variable & Performance

When I was trying to boost the performance of our web application, I typically try to focus on the "bottlenecks" and most of the time these are apparent, especially when using profiler tools (EQATEC, MvcMiniProfiler, etc). But one performance bottleneck really stumped me because none of the tools I use are really showing where the problem is.

The setup is like this: the aspx page loads the main page and javascript - which then makes 2 AJAX calls (using jQuery $.get) to some controller actions which both return ascx view which then injected into the aspx. So basically when the site loads, it hits /Home/Index and then within Index (aspx) view, there are several lines of jQuery that make ajax calls (to /Home/PartOne and /Home/PartTwo to populate parts of the page.

Below is a screen shot from MvcMiniProfiler - which shows a different controller name and action from the generic example above, but with a similar problem:


This measurement shows that there is a 510ms delay/bottleneck happening even before the starting of the execution of my controller's action. For the sake of simplicity in investigating this, the action of the controller actually only returns a list of predefined integers. So in theory, this should be fast - like less than 10ms fast, not 510ms.

Here is a screen shot from PageSpeed that shows where the javascript makes 2 consecutive AJAX calls and although both of these return the same list of integer, one is taking 500ms longer:

When I experimentally put "OutputCache" attribute on "PartTwo", it behaves as expected, that it was executing fast. This hints that the problem is not in IIS, but somewhere after that and before it hits my action method.

Here is the code for my controller - which is bare-bone basic and you should not expect problems with it:
public class HomeController : Controller {
    public ActionResult Index() {
        return View();
    }

    public ActionResult PartOne() {
        List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
        Session["mylist"] = list;
        return View(list);
    }

    public ActionResult PartTwo() {
        List<int> list = Session["mylist"] as List<int>;
        return View(list);
    }
}

Here is my javascript code to load the parts:
$(document).ready(function () {
    $.ajax({
        url: "/Home/PartOne",
        cache: false,
        success: function (data, textStatus, request) {
            $("#TestContainerOne").html(data);
        }
    });

    $.ajax({
        url: "/Home/PartTwo",
        cache: false,
        success: function (data, textStatus, request) {
            $("#TestContainerTwo").html(data);
        }
    });
});

My views - index.aspx:
<h2>Home page</h2>
<div id="TestContainerOne"></div>
<div id="TestContainerTwo"></div>

PartOne.ascx:
<h2>Part One</h2></pre>
PartTwo.ascx:
<h2>Part Two</h2></pre>

So everything looks simple and harmless enough - so WHAT IS THE PROBLEM?

The issue is how Session variable is being processed and handled. I posted this problem in StackOverflow and several users suggested to decorate the controller with
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
... and it worked like a charm.

I did some more tests and investigation about this issue - like when I remove the usage of Session variables and substituting using Http Cache etc - and as long as there is no reference to Session, everything works fast. Since part of my application relies on Session, it would be too difficult to pull all reference to Session variables out.

Upon more research, readings, testing, etc - what I found is this: Session is thread safe. What this implies is that concurrent client requests is blocking and has to be dealt with synchronously. The 500ms is the preset locking cycle time for Session. Therefore this is why the first request can execute fast and without delay but the second one has 500ms delay. Putting the decoration above allows the controller to do a read-only, hence allowing concurrency for reading. But once there is any attempt to write into the session, the delay will be back.

So here is what my updated controller code looks like:
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
public class HomeController : Controller {
    public ActionResult Index() {
        return View();
    }

    public ActionResult PartOne() {
        List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
        HttpContext.Cache.Insert("mylist", list, null, DateTime.Now.AddSeconds(10), TimeSpan.Zero);
        return View(list);
    }

    public ActionResult PartTwo() {
        List<int> list = HttpContext.Cache.Get("mylist") as List<int>;
        return View(list);
    }
}

Additional readings:
-- read more and comment ...