W ostatnim wpisie wyjaśniłem jak bardzo sesja wpływa na wydajność i skalowalność aplikacji. Dzisiaj chciałbym pokazać przykład i konkretne liczby, które pozwolą nam oszacować skalę problemu.
Zacznijmy od ASP.NET MVC. Stworzymy trzy kontrolery:
- SessionlessCotroller – kontroler będzie miał zablokowaną sesję.
- SessionController – kontroler zapisuje dane do sesji.
- SessionReadOnlyController – kontroler ma dostęp tylko do odczytu.
Kod:
[SessionState(SessionStateBehavior.Required)] public class SessionController : Controller { public ActionResult Index() { Session["Test"] = "test"; Thread.Sleep(1000); return new HttpStatusCodeResult(200); } } [SessionState(SessionStateBehavior.Disabled)] public class SessionLessController : Controller { public ActionResult Index() { Thread.Sleep(1000); return new HttpStatusCodeResult(200); } } [SessionState(SessionStateBehavior.ReadOnly)] public class SessionReadOnlyController : Controller { public ActionResult Index() { var value = Session["Test"]; Thread.Sleep(1000); return new HttpStatusCodeResult(200); } }
Następnie napiszemy prostą aplikację konsolową, która będzie po prostu wywoływać powyższe metody z różnych wątków:
class Program { private static void Main(string[] args) { RunTests(@"http://localhost/Session"); RunTests(@"http://localhost/Sessionless"); RunTests(@"http://localhost/SessionReadOnly"); } private static void RunTests(string url,int tests=3) { Console.WriteLine(url); for (int test = 0; test < tests; test++) { var cookieContainer = new CookieContainer(); SendRequest(url, cookieContainer); var stopwatch = Stopwatch.StartNew(); const int requestNumber = 20; Parallel.For(0,requestNumber, (i) => SendRequest(url, cookieContainer)); Console.WriteLine(stopwatch.ElapsedMilliseconds); } Console.WriteLine(); } static void SendRequest(string sourceURL,CookieContainer cookies) { var webRequest = (HttpWebRequest) WebRequest.Create(sourceURL); webRequest.CookieContainer = cookies; var response = (HttpWebResponse)webRequest.GetResponse(); } }
Kolejne wywołania dzielą te same ciasteczka, aby przekazywać id sesji.
Wyniki są następujące:
Wyraźnie widać, że zapis do sesji jest najwolniejszy ponieważ wtedy ASP.NET MVC (patrz poprzedni post), musi kolejkować zapytania. Ze względu na to, że korzystamy tutaj z wielowątkowej pętli, zapytania mogłyby być wykonywane po kilka naraz. W przypadku zapisu do sesji jest to niemożliwe – kod jest wykonywany sekwencyjnie. Odczyt sesji nie spowoduje żadnych problemów (jest thread-safe) więc nie musi być kolejkowany – stąd wydajność taka sama jak w przypadku zablokowanej sesji.
Ktoś może zadać pytanie, że to sam zapis jest po prostu wolniejszy ponieważ trzeba modyfikować dane. Zmodyfikujmy powyższy kod tak, aby wykonywał się sekwencyjnie:
Parallel.For(0,requestNumber,new ParallelOptions{MaxDegreeOfParallelism = 1}, (i) => SendRequest(url, cookieContainer));
Wynik:
Przykład wyraźnie pokazuje, że to kwestia szeregowania zapytań a nie faktu, że zapis jest wolniejszy sam w sobie. Z tego inny wniosek nasuwa się – im więcej wątków z puli po stronie ASP.NET MVC tym bardziej obniżamy przepustowość kontrolera poprzez wprowadzenie sesji. Jeśli mamy np. 30 wątków w ASP.NET MVC wtedy moglibyśmy aż tyle zapytań obsłużyć w tym samym czasie gdyby nie sesja i jej implementacja w ASP.NET MVC.
Bardzo ciekawy wpis obrazujący problem który wcześniej lub później pojawia się w złożonych AJAX-owych aplikacjach. Zakładam że sesja była InProc. Proponuje pójść o krok dalej i zbadać opóźnienia gdy sesja jest przechowywana poza serwerem IIS np. w bazie danych. Wówczas odczyt powinien być już wolniejszy ponieważ sesję trzeba pobrać i odserializować. Zapis dodatkowo bedzię musiał zserializować i wysłać sesję do bazy. Oczywiście sesja powinna trochę “ważyć” aby te czasy były zauważalne. Przy okazji można nakreślić kolejny problem jakim jest nadmierna ilość danych przechowywanych często niepotrzebnie w sesji.