C# ASP.NET MVC 세션 동시 접근 테스트

http://msdn.microsoft.com/ko-kr/library/system.web.sessionstate.sessionstatebehavior.aspx


 ASP.NET MVC에서 서버 객체에서 세션 값을 동시적으로 접근 하였을 경우 처리하는 방법에 대해 알아 보자. 일반적인  컨트롤러에서 세션에 접근하면 thread unsafe 상태에서 세션의 값을 수정하게 되어 있다. 예로 "코드1"번 처럼 같은 사용자의 세션에서 접근하여 값을 수정하거나 삭제를 할 수 있다.

//세션에 저장
Session["testSerialCallValue"= "Value";

[코드1] 세션 객체 수정


 위와 같은 코드에서는 ajax를 통해 동시 다발적으로 처리를 수행 하는 케이스에서는 기대하는 수행 결과가 나오지 않을 수도 있다. 다음과 같은 코드가 오류가 발생할 수 있는 케이스이다.

Session["incValue"= ((int.Parse(Session["incValue"].ToString())) + 1).ToString();

[코드2] 세션 객체 수정 ( 오류가 발생 할 수 있는 케이스 )


 이와 같은 케이스에서는 Controller 클래스에서 아래와 같은 Attribute를 추가해 줌으로써 해결이 가능 하다.

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]

[코드3] 세션 객체 컨트롤 Attribute


 이제 키워드를 알았으니 예제를 통해 전체 흐름을 알아 보자.( SessionStateBehavior 알아 보기 )



<script type="text/javascript">
    var start = '';
    $(function () {
        //ajax cache 옵션 해제
        //$.ajaxSetup({ async: false });    //ajax 1.9.1에서 fale로 지정하면 server controller에 상관 없이 순차적으로 ajax calling 진행
        start = new Date().getTime();
 
        //SessionStateBehavior.ReadOnly 
        //동시 호출 테스트 - 병렬 수행 케이스
        for (var i = 1; i <= 3; i++) {
            $.ajax('SessionConcurrentParallel/GetParallelCall' + i, {
                cache: false
            }).done(logParallel);
        }
 
        //SessionStateBehavior.Required
        //동시 호출 테스트 - 순차 수행 케이스
        for (var i = 1; i <= 3; i++) {
            $.ajax('SessionConcurrentSerial/GetSerialCall' + i, {
                cache: false
            }).done(logSerial);
        }
    });
 
    function logParallel(data, stateText, responseObject) {
        console.log(['Parallel', data, stateText, responseObject, start, new Date().getTime()]);
    }
 
    function logSerial(data, stateText, responseObject) {
        console.log(['Serial', data, stateText, responseObject, start, new Date().getTime()]);
    }
</script>

[코드4] 웹 페이지에서 ajax를 통해 동시적으로 controller 호출 하기


 "코드4"에서와 같은 코드로 첫번째 ajax 호출은 서버 컨트롤러에서 병렬로 Session 객체에 접근할 수 있는 액션을 호출 하고, 두번째 ajax 호출은 Session 객체를 순차적으로만 접근 하도록 되어 있는 컨트롤러의 액션을 호출 하도록 구성 하였다. 이제 이 코드에 상응하는 컨트롤러를 구성해 보자.

[SessionState(System.Web.SessionState.SessionStateBehavior.Required)]
[OutputCache(NoStore = trueDuration = 0VaryByParam = "*")]
public class SessionConcurrentSerialController : Controller
{
    #region Serial Call processing test
    public JsonResult GetSerialCall1()
    {
        //세션에 저장
        Session["testSerialCallValue"= "Value";
        Thread.Sleep(3000);
        return Json(new { obj = "SeriallCall1 Value #1" }, JsonRequestBehavior.AllowGet);
    }
 
    public JsonResult GetSerialCall2()
    {
        //세션에 저장
        Session["testSerialCallValue"= "Value";
        Thread.Sleep(3000);
        return Json(new { obj = "SeriallCall1 Value #2" }, JsonRequestBehavior.AllowGet);
    }
 
    public JsonResult GetSerialCall3()
    {
        //세션에 저장
        Session["testSerialCallValue"= "Value";
        Thread.Sleep(3000);
        return Json(new { obj = "SeriallCall1 Value #3" }, JsonRequestBehavior.AllowGet);
    }
    #endregion
}

[코드5] 순차적 Session 객체 접근


[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
[OutputCache(NoStore = trueDuration = 0VaryByParam = "*")]
public class SessionConcurrentParallelController : Controller
{
    //
    // GET: /SessionConcurrent/
 
    public ActionResult Index()
    {
        Session["testValue"= "TempValue";
        Session["incValue"= ((int.Parse(Session["incValue"].ToString())) + 1).ToString();
 
        return View();
    }
 
    #region Parallel Call processing test
    public JsonResult GetParallelCall1()
    {
        //세션에서 읽기
        var x = Session["testParallelCallValue"];
        Thread.Sleep(3000);
        return Json(new { obj = "GetParallelCall1 Value #1" }, JsonRequestBehavior.AllowGet);
    }
 
    public JsonResult GetParallelCall2()
    {
        //세션에서 읽기
        var x = Session["testParallelCallValue"];
        Thread.Sleep(3000);
        return Json(new { obj = "GetParallelCall1 Value #2" }, JsonRequestBehavior.AllowGet);
    }
 
    public JsonResult GetParallelCall3()
    {
        //세션에서 읽기
        var x = Session["testParallelCallValue"];
        Thread.Sleep(3000);
        return Json(new { obj = "GetParallelCall1 Value #3" }, JsonRequestBehavior.AllowGet);
    }
    #endregion
}

[코드6] 병렬로 Session 객체 접근


 컨트롤러 선언시 클래스 선언부에서 "SessionState"의 Attribute를 선언하여 Session 접근 유형을 패턴을 정의 하였으며 Index 액션과 연결된 웹 페이지에서 ajax를 통해 테스트할 수 있는 뷰 페이지를 연결 하였다.


 이제 아래 그림을 통해 결과를 확인해 보도록 하자.


[그림1] 컨트롤러의 액션 로딩 타임


 "그림1"에서와 같이 Parallel 형식으로 선언된 컨트롤러의 액션 호출은 일률적인 시간으로 완료가 되었으나 Serial 형식으로 접근하도록 선언된 컨트롤러 액션은 순차적으로 처리가 되어 총 처리 시간이 3s * 3회인 9초가 걸린 결과가 나왔다. 세션을 사용할 때 동시성 처리에 대한 처리를 Attribute를 통해서 컨트롤 할 수 있도록 해주는 방안을 ASP.NET MVC에서 제공해 주고 있는 것이다. 다만 예외적으로 "그림2"와 같은 결과가 나올 수 있다.


[그림2] 컨트롤러 액션 로딩 타임 - ajax request limit에 따른 결과


 "그림2"와 같은 결과가 나오게 되는 케이스는 브라우저마다 한번에 ajax콜을 할 수 있는 connection 수가 제한 되어 있기 때문이다. 브라우저(버전에 따라 다름) 마다 2 ~ 6개 정도를 지원하도록 되어 있어서 측정 수치는 달라질 수 있다. 필자와 같은 케이스로 결과값이 나오기를 바란다면 최신 브라우저로 테스트를 해 보기를 바란다.



 이 포스트에 있는 내용이 언제나 확실한 정답은 아닙니다. 진실이라고 생각해 왔던 전제가 시간의 지남에 따라 들어나지 않았던 다른 이면 때문에 좋은 방향으로 이끌어 낼 수 있는 역할로 변환 되는게 역사적으로도 많은 증명 있었습니다. 그렇지만 저는 현재 상황에서 최선의 답을 찾고자 노력하였으며 이 글을 읽는 다른 분들에게 다음 길을 갈 수 있도록 도와주는 디딤돌이 되고자 노력하고자 포스팅을 통해 공유하고자 하는 것입니다. 그리고 프로그래머라는 타이틀을 달고 살아야 한다면 "왜"라는 의문을 항상 가지고 다니면서 자신의 위치에 안주하지 않고 항상 노력하는 모습으로 살아 가고자 합니다. 언제든 지적이나 오류가 있으면 피드백 부탁 드리겠습니다.

ing™       


+ Recent posts