ASP.NET 마스터하기 #3 - ASP.NET의 아키텍처

필자의 잡담~

잡담이 없어서... ^^; 안재우님의 블로그는 http://blog.naver.com/saltynut 입니다

지난 시간에 우리는 ASP.NET의 설치에 대해서 알아봤습니다. 여러분들 중에는 이미 이전에 ASP.NET을 설치해두신 분들도 계실 것이고, 행여나 제 글을 읽고 무작정 설치하신 분들도 계실 겁니다.

자, 그럼 일단 우리가 생각해봐야 질문은...

"ASP.NET이 대체 뭘까요?"

"내가 ASP.NET을 설치한 이유는 뭘까요? 어디다 써 먹으려고 설치하셨습니까?"

어떤 개발자들은 자신이 설치하는 녀석이 뭔지도 모르고 일단 설치부터 하고 보는 사람들이 많습니다. 왜? 남들이 다 하니깐. 좋은 거라고 하니깐. 새로 나온 거라고 하니깐.

ASP.NET 아키텍처 가르쳐 준다면서 왜 이런 질문을 하냐구요? 성격이랑 정체도 모르면서 얘가 왜 이런 아키텍처로 만들어 졌는지를 이해할 수 있겠습니까?

ASP.NET을 한 줄로 정의하자면, ‘.NET 프레임워크를 기반으로 한 웹 애플리케이션 개발 모델, 프레임워크, 관련 기술을 총칭해서 부르는 것’이라고 하겠습니다. MSDN의 정의로는 ‘엔터프라이즈 수준의 웹 응용프로그램을 최소한의 코딩으로 구축하는데 필요한 서비스를 포함하는 통합 웹 개발 모델’이라고 합니다.

이 정의는 간단한 내용이지만, 상당히 중요한 사항을 담고 있습니다.

첫째, ASP.NET은 .NET 프레임워크를 기반으로 한다는 점입니다. 따라서 .NET에서 제공하는 클래스 라이브러리를 사용할 수 있으며, 프로그래밍 언어로는 .NET이 제공하는 모든 언어를 사용할 수 있고, .NET 프레임워크 기반의 애플리케이션에 적용되는 모든 요소들이 ASP.NET에도 적용된다는 것입니다. 그 얘기는 .NET에 대해서 잘 알지 못하면서 ASP.NET을 잘 안다는 것은 불가능하다는 의미도 됩니다.

둘째, ASP.NET은 웹 애플리케이션을 만들기 위한 것이라는 점입니다. 따라서 일반적인 웹 애플리케이션의 특성을 그대로 가지며, 웹 애플리케이션이 수행 가능한 범위 내에서만 능력을 발휘할 수 있다는 점입니다.

셋째, ASP.NET은 ‘엔터프라이즈 수준’의 애플리케이션을 맞추는데 가장 큰 초점을 맞추고 있다는 점입니다. 따라서 간단한 애플리케이션 등을 만드는데는 최적의 개발 모델이 아닐 수도 있다는 점을 염두에 두어야 하며, 엔터프라이즈 애플리케이션을 개발할 때 고려해야 할 사항들에 대해서도 알 필요가 있다는 점입니다.

이 세가지가 별거 아닌 거 같지만, 이후에 ASP.NET을 이해하면서 이 3가지 사항을 잘 염두에 두고 있느냐 아니냐에 따라서 많은 차이가 나게 됩니다.

절대 변하지 않는 진리, 웹의 동작방식

두 번째 항목, ASP.NET이 웹 애플리케이션이라는 것은 ASP.NET이 웹이라는 한계를 벗어나지는 못한다는 것은 이미 강조된 사실입니다. 마찬가지로 웹의 동작방식은 적어도 당분간은 절대 변하지 않을 특성들을 다음과 같이 가지고 있습니다.

  • Request(요청) & Response(응답)의 구조를 취한다.
  • 기본적으로 전송 시에 HTTP 프로토콜을 사용한다.
        HTTP 프로토콜은 기본적으로 상태를 가지지 않는다(Stateless).
  • Request를 보내고, Response를 처리하는 웹 클라이언트가 있어야 한다.
  • Request를 처리하고, Response를 보내는 웹 서버가 있어야 한다.
  • 기본적으로 Request에는 Get 방식과 Post 방식이 존재한다.
  • 기본적으로 Response의 최종 형태는 정적 HTML이다.

    위의 내용을 포함해서 보다 순차적으로 이 내용을 도식화하면 다음과 같습니다.

    뻔한 내용입니다만, 위 그림 내에 지금까지의 웹 기술들이 발전해오면서 이루고자 했던 목표들이 다 존재합니다. 예를 몇 가지 들어볼까요? 대용량 바이너리 데이터를 전송하기 위해 HTTP 업로드 컴포넌트가 나왔고 HTTP 이어받기 기능이 등장합니다. 주고 받는 프로토콜의 전송 수준에서의 보안(Transport Level Security)을 위해 SSL(Secured Socket Layer)가 등장합니다. 최대한 많은 요청을, 가장 빨리 처리하기 위해 웹 서버 기술이 발전합니다. 요청에 따라 동적으로 응답을 생성해 내기 위해 웹 서버 애플리케이션 기술(CGI, ISAPI, ASP, ASP.NET, PHP 등)이 나왔습니다. 정적 컨텐츠인 HTML에 동적인 측면과 상호작용성(Interactivity), 재사용성을 부여하기 위해 DHTML, JavaScript, CSS, ActiveX, 애플릿 등이 나오게 됩니다. 효율적으로 요청을 처리하고, 응답 속도를 보다 빠르게 하고, 응답 결과를 렌더링 해서 보여주기 위해 브라우저 기술이 발전합니다. 전세계는 저 그림 속의 내용을 보다 효율적으로 개선하기 위해 그 오랜 시간을 투자한 셈입니다.

    그럼 위 그림에서 ASP.NET에 해당하는 부분은 어디일까요? (이거에 대한 답이 아직도 안 떠오르신다면 별로 이 글을 성의 있게 읽지 않으셨거나, 관심이 없으시거나, 빨리 이 길을 접고 리니지 게임머니 작업장을 차리시는게 개인과 사회를 위한 도움이 되실지도…)

    위 그림처럼 웹 서버 단의 영역입니다. 다시 한번 정리하자면 요청을 받아서 응답을 만들어내는 역할을 하는 부분입니다. 이미 잘 아실 역사적인 얘기를 간단히 해보자면, 초기의 웹 서버는 HTML(*.htm, *.html), 그림파일(.gif, .jpg)와 같은 정적 컨텐츠에 대한 요청을 받아서 이를 그대로 전달할 뿐이었습니다. 초기의 웹은 FTP나 Gopher와 같이 뭔가 ‘통신’을 한다는 의미보다는 단순히 ‘전송’을 한다는 의미가 강했습니다. 웹이 진화되면서 정적 컨텐츠가 아닌 동적 컨텐츠를 전달할 방법이 없을까를 궁리하게 되었고, 동적 요청을 위한 매개변수를 어떻게 전달할 것인가와 이를 받아서 어떻게 처리해야 하는가라는 문제가 대두되게 됩니다.

    매개변수의 전달은 웹 프로그래밍을 해봤으면 들어봤음직한 Get 방식과 Post 방식이 존재합니다. Get 방식은 URL 뒤에 ? 이후에 키-값 쌍으로 이루어진 Query String을 붙여서 매개변수를 전달하는 방법입니다. 이에 비해 Post 방식은

    태그를 사용하며, 키-값 쌍을 HTTP 헤더 내에 집어넣어서 전달하는 방법입니다.

    정적 컨텐츠에 대한 요청과 동적 컨텐츠에 대한 요청을 구분하기 위해 편의상 동적 컨텐츠를 구분하기 위한 확장자를 사용하게 됩니다. 즉, .html이나 .htm 대신에 .cgi, .asp, .php, .jsp, .aspx 등을 사용하게 된 것입니다. 이렇게 동적 컨텐츠로 구분되는 확장자로 요청이 들어올 경우, 웹 서버는 이를 가로채서 동적 컨텐츠 처리 모듈에 던져주게 됩니다.

    동적 컨텐츠를 처리하는 모듈, 즉 웹 애플리케이션은 최초에는 CGI 형태가 사용되었지만, 시간이 흐름에 따라 ISAPI 방식과 Script 기반 방식 등으로 진화되어 나갔습니다. 현재 우리가 사용하고 있는 대부분의 방식은 Script 기반 방식으로 보면 되겠습니다. ASP.NET 역시 여기에 속한다고 보면 되겠습니다. (사실 이런 얘기들은 옛날 ASP 책들을 보면 서두에 자세하게 잘 설명되어 있습니다.)

    그런데, CGI -> ISAPI -> Script 기반 방식 등으로 변경되어 온 이유는 무엇일까요?

    우선 첫째는 복잡한 것에서 간단한 것으로의 이동입니다. 간혹 예외도 있긴 하지만, 모든 IT 기술은 복잡한 것이 간단해질 때 성공하는 경우가 많습니다. 물론 복잡도의 감소와 해당 기술 보유자의 월급은 반비례합니다만.. ^^ CGI 시절까지만 해도 일단 C나 C++를 알아야 하는 것은 기본이었습니다. 이 당시 유명했던 게시판 중에 CrazyBoard가 있었던 걸로 기억하는데 게시판 소스의 양도 엄청난데다 분석하려면 상당한 시간을 투자해야 했습니다. 현재 우리가 쓰고 있는 ASP 게시판 소스는 그 시절과는 비교할 수 없을 정도로 분량도 적은데다 이해하기 쉽습니다. 게시판 소스만 있으면 초등학생도 직접 게시판을 올릴 수 있을 정도이니까요.

    둘째로 보다 적은 리소스를 소모하면서 보다 많은 요청에 대응해야 한다는 목적을 달성하기 위한 것입니다. 해묵은 얘기입니다만, CGI 시절에는 요청당 하나씩의 프로세스가 만들어져서 사용자 수가 적을 때는 괜찮지만 사용자 수가 늘어날 경우 엄청난 리소스 소모와 함께 성능이 떨어지기 시작했습니다. ISAPI 방식은 이를 보완하여, 요청 수와 관계없이 프로세스는 하나만 만들고, 처리모듈은 DLL 형태로 만드는 것이라고 생각하면 이해가 쉬울 겁니다. ASP의 예를 들면, asp.dll이 .asp 확장자에 대해 처리하는 ISAPI 모듈입니다.

    ASP.NET 역시 이러한 ISAPI 방식을 동일하게 사용합니다만 기존 ASP에서 발생되는 문제점을 해결하기 위해서 아키텍처가 변경되었습니다. 앗, 감이 오셨습니까? 지금까지의 글은 이번 글의 핵심내용을 유도하기 위한 설명이고, 우리가 알아봐야 할 건 대체 기존 ASP 아키텍처에서 어떤 문제가 있었고, ASP.NET의 아키텍처는 이를 극복하기 위해 어떤 형태를 취하고 있는 가입니다.

    ASP의 아키텍처와 문제점

    사실 ASP의 아키텍처에 대해 자세히 알고 싶다면 이 글이 아니라 다른 ASP 글이나 서적을 참고하십시오. 저는 어디까지나 간단하게 설명을 하도록 하겠습니다.

    ASP는 기본적으로 IIS 웹 서버 상에서 사용될 목적으로 만들어졌습니다. ASP가 처음 등장한 건 훨씬 이전이긴 하지만, 널리 사용된 것이 NT 4.0의 옵션팩에 탑재된 IIS 4.0부터 이니깐 이 때를 기준으로 설명을 하도록 하겠습니다.

    IIS 4.0 시절, ASP가 구동되는 기본 아키텍처는 다음과 같은 In-Process 모델입니다.

    inetinfo.exe는 System 계정으로 구동되는 IIS의 메인 프로세스입니다. 이 프로세스는 .asp 확장자에 대한 요청이 올 때 asp.dll을 자신의 프로세스 내에 로딩해서 요청을 처리하게 됩니다.

    In-Process 모델의 문제는 asp.dll 내에서 문제가 발생해서 asp.dll이 죽어버린 경우, 메인 프로세스인 inetinfo.exe마저 죽어버린다는 겁니다. 쉽게 말해서 무한루프를 도는 .asp 페이지가 있는 경우, 그 페이지 하나 때문에 웹 서버 전체가 먹통이 되어 버린다는 겁니다.

    Windows 2000과 Windows XP의 IIS 5.x에서는 이를 방지하기 위해 COM+ 서비스와 IIS를 결합하여 다음과 같은 옵션을 가지게 됩니다.

    IIS를 다뤄본 분이시라면 한번쯤은 본 적이 있을 겁니다만, 구체적으로 이게 뭐하는 건지 모르시는 분들도 많더군요. 우선 ‘낮음(IIS 프로세스)’은 IIS 4.0의 In Process 모드와 동일한 것입니다. 따라서 성능은 가장 좋지만, 안정성은 가장 떨어집니다.

    보통(풀링됨)과 높음(격리됨)은 웹 애플리케이션을 inetinfo.exe 내에서 구동하는게 아니라 별도의 프로세스(dllhost.exe)에서 구동하는 Out-of-Process 모델입니다. 둘 다 기본 개념은 유사하지만 약간의 차이점이 있는데, 먼저 ‘보통(풀링됨)’으로 지정된 웹 애플리케이션들은 하나의 dllhost.exe 내에서 풀링되어 동작하게 됩니다.

    쉽게 설명하면 inetinfo.exe가 아닌 별도의 dllhost.exe 안에서 In Process 모드처럼 동작한다고 보면 됩니다. 풀링된 애플리케이션들은 모두 단일한 dllhost.exe 내에서 동작됩니다.

    이와 관련하여 구성요소 서비스에서는 Out-of-Process Pooled Applications라는 COM+ 애플리케이션을 찾아볼 수 있습니다.

    풀링은 성능(리소스)과 안정성이라는 양쪽을 나름대로 절충한 것이라 볼 수 있습니다. 일단 inetinfo.exe와 dllhost.exe라는 두 개의 프로세스로 분리함으로써 안정성은 어느 정도 확보됩니다만, 역시 풀링되어 있는 애플리케이션이 죽었을 경우에는 같은 dllhost.exe 내의 다른 애플리케이션도 같이 죽는다는 단점이 있습니다.

    높음(격리됨)은 각 웹 애플리케이션을 별도의 dllhost.exe에서 동작시키는 방법입니다.

    완전히 격리를 시키는 것이기 때문에 안정성 측면에서는 가장 우수하겠지만, inetinfo.exe 입장에서는 n개의 dllhost.exe와 Out-Of-Process 통신을 해야 하며, n개의 dllhost.exe가 돌게 되므로 사용되는 리소스가 더 많아져서 성능이 다소 떨어지게 됩니다.

    IIS 5.x에서는 이러한 형태로 ASP 애플리케이션에 대해 나름대로의 안정성을 확보했습니다만, 역시 여기서도 제기되는 단점들은 존재합니다. dllhost.exe는 한번 구동되면 명시적으로 종료를 시켜주기 전까지는 계속해서 구동하게 됩니다. 물론 잘 동작한다면 상관이 없지만, 문제(데드락 상황과 같은)가 발생했을 때는 반드시 수동으로 재시작을 해줘야 한다는 단점이 존재합니다. 즉, 자신이 죽는다고 해서 웹 서버 전체나 다른 웹 애플리케이션에 영향을 미치는 건 해결했지만, 자기 자신이 죽었을 때 처리할 수 있는 방법이 모호하다는 것입니다. 또한 버그 등으로 인해 리소스의 누수가 발생하는 경우 애플리케이션을 재시작함으로써 리소스의 클린업을 할 수 있는데(시스템을 재부팅하는 걸 생각해보세요), 이러한 과정(통상 Recycling이라고 합니다)을 지원하지 않는다는 것입니다. 또한 dllhost.exe는 웹 애플리케이션만을 위한 전용이 아닌 COM+ 서버 활성화 모드(Out-of-Process)에서 사용되는 범용 호스팅 프로세스입니다. 그렇기 때문에 웹 애플리케이션을 위한 구성 정보를 설정해서 동작 특성을 변경하는 것을 지원하지 않는다는 것입니다.

    IIS에서 COM+ 애플리케이션을 구동할 때도 동일한 문제가 발생했습니다. 예전에는 VB 6.0이나 ATL로 비즈니스 로직 컴포넌트를 만들어서 COM+에 올려 구동하곤 했는데, COM+가 라이브러리 활성화(In Process)일 때는 컴포넌트가 죽을 경우 호스팅 프로세스인 IIS가 죽게 되므로, IIS 안정성을 위해 서버 활성화(Out-Of-Process)로 돌리는 경우가 많았습니다.

    웹 서버가 죽는다는 것이 얼마나 큰 문제인지는 다들 알고 계실 겁니다. 당연히 죽게 되면 서비스를 못하게 되니깐 문제가 되죠. 죽은 웹 서버를 살리려면 재시작을 하면 되는데, 재시작을 해도 해결이 되지 않는 문제도 역시 존재합니다. 대표적인 것으로 ASP 시절에는 상태 정보, 즉 세션을 IIS 프로세스 내에서 In Process 모드로 관리합니다. 즉 IIS를 재시작하게 되면 세션 정보가 날아가 버린다는 것입니다.

    IIS가 죽을 때 상태 정보가 손실되는 것도 문제지만, 웹 팜(Web Farm) 환경처럼 웹 서버를 여러 대 두고 로드밸런싱하는 경우 역시 문제가 됩니다. 이 경우 서버 간의 상태 정보를 어떻게 공유해야 할지 방법이 막연해집니다. 결국은 L4를 Dedicated 모드(Per Session)로 설정해서 세션을 고정시켜버리거나 상태 정보를 DB와 같은 제3의 저장소로 돌려서 우회해야 합니다. 전자는 로드밸런싱의 취지를 저하시키게 될테고, 후자는 이를 지원하기 위한 코드를 직접 다 작성해야 한다는 단점이 있습니다.

    ASP.NET의 아키텍처와 개선사항

    지금까지 진정한 이번 글의 주제를 위해 기나긴 여정을 왔습니다. 지금까지 언급된 문제점들을 극복하기 위해 ASP.NET은 다음과 같은 구조를 가지고 있습니다.

    지난 번 ASP.NET의 설치 시에 aspnet_isapi.dll이라는 ISAPI 처리기가 등록되는 것을 봤을 겁니다. 이 놈은 .aspx와 같이 ASP.NET 관련 확장자를 가로채서 실제로 처리하는 작업자 프로세스(Worker Process)인 aspnet_wp.exe에게 전달합니다.

    aspnet_wp.exe는 내부에 요청의 전후에 개입하기 위한 HttpModule과 개별 요청을 처리하기 위한 HttpHandler로 구성됩니다. HttpModule이나 HttpHandler는 기본으로 제공되는 것 외에 사용자가 정의해서 확장하는 것이 가능합니다. aspnet_wp.exe 내에서 실제로 일어나는 일들은 다음 번에 좀 더 자세하게 알아 보도록 하겠습니다. 일단 여기서는 aspnet_wp.exe가 실제 요청을 처리해서 응답을 만들어내는 녀석이라고만 생각해 둡시다.

    일단 여기까지만 봤을 때, ASP.NET은 별도의 전용 프로세스인 aspnet_wp.exe에서 구동되므로 자신이 죽더라도 inetinfo.exe에는 영향을 미치지 않습니다. 따라서 이전 dllhost.exe에서 구동될 때처럼 안정성 측면에서는 확보가 된 셈이죠. aspnet_isapi.dll은 실행 중인 aspnet_wp.exe가 없을 때는 바로 이 프로세스를 다시 실행하며, 구성정보에 따라서 aspnet_wp.exe의 동작을 제어할 수도 있습니다. 이전 글에서 machine.config의 내용을 보면 이를 제어하기 위한 내용들이 존재합니다. 예를 들어 timeout을 지정해서 일정시간 동안 요청이 없으면 종료시켜버릴 수도 있고, 일정시간 이상 데드락이 걸렸을 때는 재시작하는 기능도 존재합니다. 다양한 구성정보에 따라서 동작 특성을 제어하는 것 역시 가능해졌다는 의미입니다.

    위 그림의 우측에 보면 aspnet_state.exe라는 프로세스가 있습니다. ASP.NET 역시 기본적으로는 세션을 In Process 모드로 관리하지만, 설정에 따라서 그림과 같이 세션을 별도의 프로세스(aspnet_state.exe)에서 관리할 수 있습니다. 따라서 aspnet_wp.exe가 죽는다고 하더라도 세션 정보 자체는 여전히 살아있게 되는 거죠. 상태 정보 관리에 대한 자세한 내용 역시 다음에 다루도록 하겠습니다.

    이리하여 대략 ASP.NET의 아키텍처가 왜, 무엇을 개선하기 위해 어떻게 구성되었는지에 대해 다뤄보았습니다. 사실 몇 가지 빠진 부분도 있긴 한데 여기에서 설명해야 할지 다음 글에서 설명해야 할지 약간 애매한데다, 너무 글이 길어지는 것 같아서 다음으로 넘기도록 하겠습니다.


  • authored by Lancers (안재우)


    Posted by Sting!
    ,