SimpleIsBest.NET

유경상의 닷넷 블로그
이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다. 저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나 토론을 할 수도 있습니다.

이 글은 월간 마이크로소프트웨어(일명 마소) 2006년 12월호 닷넷 칼럼에 기고한 글입니다. WCF의 서비스 인스턴스 관리에 대한 글인데 이 때 당시만해도 관련 자료와 필자의 충분한 스터디가 없어서 약간 어눌한 내용도 있습니다. 이에 대해서는 문서 내에 별도의 코멘트를 달았으니 양해 바랍니다.

예전에 마소에 기고할 때는 특정하게 페이지 분량이 정해지지 않았는데 개편 이후로 저에게 할당된 페이지가 고정되어 있어서 페이지 분량을 맞추다 보니 내용이 부실한 경우도 있습니다. 게다가 이 글을 쓰는 당시 충분한 자료 조사도 하지 못한 것도 사실이고요. 그래서 글을 읽으시는 여러 독자 분들께 죄송할 따름입니다. 더욱 열심히 하도록 하겠습니다. 따끔한 지도 편달과 따스한 응원 부탁 드립니다.

주) 이 글은 마소 편집부에 제가 발송한 원고를 약간 편집하여 올린 글입니다. 따라서 마소에 실린 글과는 약간의 차이가 있을 수 있습니다.


WCF Service Instance Management

How to manage the service instance in WCF

예제코드 다운로드

지난 11월 10일 닷넷 프레임워크 3.0이 정식 릴리스 되었다. 2.0이 발표된 지 채 1년이 지나지 않은 상태에서 등장한 새 버전이지만 3.0은 기존 2.0에 WPF, WF, WCF, WCS 가 추가되었을 뿐이다. 1.x 에서 2.0으로 변화한 것처럼 변화한 것이 아니라 2.0에 새로운 기능들이 추가된 채로 3.0이 등장했을 뿐이다. 닷넷 프레임워크 3.0의 정식 버전이 릴리스 될 때까지 이들 기술을 습득하는 것을 미뤄온 독자들이 있다면 이젠 핑계거리도 없으니 시간을 내서 공부를 해야 할 때이다. 구구절절이 잔소리는 하지 않겠다. 얼마나 오랫동안 개발자로서 남을 수 있을 것인가는 스스로 결정하는 것이다.

이번 칼럼에서는 WCF 에서 서비스 인스턴스를 관리하는 다양한 방법들을 소개하고자 한다. WCF 서비스를 프로그래밍 할 때 항상 서비스의 계약(contract), 즉 인터페이스를 정의하고 이 인터페이스를 구현하는(implement) 서비스 타입을 정의해야 했다. 서비스 타입은 대개 닷넷의 클래스를 사용하기 마련이다. 서비스가 클라이언트의 요청을 처리하기 위해서는 클래스만 가지고는 아무런 일도 할 수 없다. 클래스의 인스턴스가 있어야 서비스 호출을 처리할 수 있을 것이다. 이렇게 WCF에서 서비스 타입의 인스턴스를 어떻게 생성하고 관리할 것인가에 대해 살펴 보도록 하자.

기존 기술의 서비스 인스턴스 관리

때대로 새로운 것을 배우기 전에 과거의 것을 보고 정리하는 것이 도움이 되는 경우가 많다. WCF의 배경이 되는 기존 ASP.NET 웹 서비스(ASMX)와 닷넷 리모팅에서 제공하는 인스턴스 관리에 대해 간략히 먼저 살펴보도록 하겠다. 지금부터 필자가 언급하는 내용은 대부분 아는 것이겠지만 정리한다는 의미에서 읽어 보는 것도 나쁘지 않을 것이다. 혹은 필자가 언급하는 내용을 처음 듣는 것이라면 10초 정도 반성한 후 읽어 보도록 하자.

ASMX의 인스턴스 관리

ASP.NET이 제공하는 XML 웹 서비스는 매우 간단한 서비스 인스턴스 모델을 가지고 있다. 클라이언트가 웹 서비스의 메쏘드를 호출하면 웹 서비스 클래스의 새로운 인스턴스 생성되고 그 인스턴스의 메쏘드가 호출된다. 그리고 메쏘드 호출이 완료되면 그 인스턴스는 곧바로 버려지게 된다. 그 인스턴스 언제 메모리에서 제거되는 가는 GC (Garbage Collector) 만이 알고 있을 것이다.

구체적인 예제는 리스트 1과 같다. 리스트 1의 웹 서비스를 호출하면 AsmxInstancing 클래스의 인스턴스가 생성되고 이 인스턴스에 대해 Echo 메쏘드가 호출된다. Echo 메쏘드가 리턴하게 되면 ASP.NET 런타임은 생성한 인스턴스에 대해 Dispose를 호출한 후에 그 인스턴스를 버린다. 리스트 1의 웹 서비스를 2회 호출했을 때, 이 코드가 생성하는 디버그 메시지의 내용은 다음과 같다.

Service Instance Created...
Echo invoked... call count = 1
Service Instance Disposing...
Service Instance Created...
Echo invoked... call count = 1
Service Instance Disposing...

매 웹 메쏘드 호출마다 새로운 인스턴스가 생성되므로 _CallCount 필드의 값이 항상 0으로 초기화 됨은 매우 당연하므로 Echo 메쏘드가 호출될 때 표시되는 호출 회수 값은 항상 1이 됨에도 유의하자.

이렇게 ASMX는 매우 간단한 서비스 인스턴스 모델만을 가지고 있다. ASMX는 항상 이러한 단일 호출(Single Call)에 대해 고유의 인스턴스를 생성하는 인스턴스 모델을 가짐으로써 간단하면서도 다수의 호출에도 쉽게 확장될 수 있는(scalable) 모델을 가지고 있다. 이 인스턴스 모델의 장/단점에 대해서는 추후에 좀 더 다루기로 하자.

//

// ASMX의 서비스 인스턴스 테스트

//

[WebService(Namespace = "http://simpleisbest.net/example/wcf/instancing")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

public class AsmxInstancing : System.Web.Services.WebService

{

    private int _CallCount = 0;

 

    public AsmxInstancing ()

    {

        Debug.WriteLine("Service Instance Created...");

    }

 

    protected override void Dispose(bool disposing)

    {

        base.Dispose(disposing);

        Debug.WriteLine("Service Instance Disposing...");

    }

 

    [WebMethod]

    public string Echo(string msg)

    {

        _CallCount++;

        Debug.WriteLine("Echo invoked... call count = " + _CallCount.ToString());

        return "You send, " + msg;

    }

}

리스트1. ASMX 인스턴스 예제

.NET Remoting 인스턴스 관리

닷넷 리모팅은 ASMX에 비해 훨씬(?) 풍부한 인스턴스 모델을 가지고 있다. 닷넷 리모팅을 한 번이라도 접해본 독자라면 누구라도(?) 알고 있으리라 생각되는 SingleCall, Singleton, Client Activated 가 바로 그것이다. SingleCall 은 앞서 살펴본 ASMX의 인스턴스 모델과 동일하다. 원격 호출이 발생할 때마다 원격 객체가 새로이 생성되며 이렇게 생성된 인스턴스가 메쏘드 호출을 처리한다. 메쏘드 호출이 완료되면 그 인스턴스는 곧바로 GC의 대상이 됨은 물론이다. ASMX와 마찬가지로 서비스 클래스(리모팅에 사용하는 클래스)가 IDisposable 인터페이스를 구현하고 있으면 Dispose 메쏘드는 닷넷 리모팅 런타임에 의해 자동으로 호출된다.

Singleton 인스턴스 모델은 말 그대로 Singleton 패턴을 사용한다. 오로지 하나의 서비스 인스턴스만이 생성되며 클라이언트의 개수와 원격 메쏘드 호출 회수에 상관없이 이 하나의 인스턴스가 사용된다. 단일 서비스 인스턴스만이 사용되므로 항상 재진입(re-entrance) 문제, 동기화 문제, 상태 유지 등에 신경을 많이 써야만 하는 인스턴스 모델이다. Singleton 인스턴스 모델에서 인스턴스가 생성되는 시점은 최초로 클라이언트가 리모팅 호출을 하는 경우이며, 생성된 인스턴스는 어플리케이션 도메인이 종료될 때까지 계속 유지됨에도 유의하자. 이 때문에 IDisposable 인터페이스의 구현은 Singleton 모델에서 큰 의미를 갖지 않는다. 아니, IDisposable 인터페이스를 구현하는 것 자체가 문제가 될 수 있다. 왜냐하면 여러 클라이언트들에 의해 하나의 인스턴스가 공유되기 때문에 한 클라이언트가 Dispose 메쏘드를 호출함으로써 다른 클라이언트에게 영향을 줄 수 있기 때문이다.

Client Activated Object 인스턴스 모델, 줄여서 CAO 모델은 인스턴스의 생성과 파괴 심지어 Dispose 메쏘드의 호출까지도 클라이언트에 의해 제어된다. CAO 인스턴스 모델에서 인스턴스의 생성 시점은 클라이언트가 new 를 호출할 때이며, 이 때 생성자(constructor)에 대한 리모팅 호출이 발생하고 서버 측에서 CAO 인스턴스가 생성되게 된다. 클라이언트가 new 키워드를 통해 생성한 객체는 물론 서버 측의 실제 객체에 대한 프록시가 될 것이다. 여러 클라이언트가 CAO 인스턴스를 생성한다면 각 클라이언트는 고유의 인스턴스를 갖게 되며, 클라이언트가 리모팅 호출을 수행할 때마다 자신의 인스턴스가 이 호출을 서비스하게 됨에 유의하자. 인스턴스의 소멸은 클라이언트가 명시적으로 Dispose를 호출하거나 서버 측의 객체 생명 주기 리스(object life-time lease)가 만료되면 객체는 GC의 대상이 되게 된다. 닷넷 리모팅의 CAO 인스턴스 모델은 리스(lease)에 대한 이해를 필요로 하므로 지면 관계상 여기서 더 이상 설명하지 않겠다.

닷넷 리모팅의 세가지 인스턴스 관리 모델은 각기 장단점을 가지고 있다. 이들의 장단점은 WCF에서 제공하는 인스턴스 모델의 장단점과 매우 비슷하므로 WCF의 인스턴스 모델에 대해 설명할 때 이야기하기로 하겠다. 그리고 닷넷 리모팅에 대한 예제는 이달의 디스켓 내의 예제 코드를 참조하기 바란다.

WCF 서비스 인스턴스 관리

ASMX와 닷넷 리모팅과 마찬가지로 WCF 역시 서비스 타입의 인스턴스들에 대한 다양한 관리 모델을 가지고 있다. 이들은 PerCall, Per-Session, Singleton 모델로서 이제부터 각 인스턴스 관리 모델을 상세히 예제와 더불어 설명하도록 하겠다.

PerCall Services

PerCall 서비스는 ASMX의 인스턴스 관리나 닷넷 리모팅의 SingleCall 모델과 동일한 인스턴스 관리 방법으로서 WCF가 선호하는 인스턴스 관리 모델이다. 즉, 클라이언트가 서비스의 메쏘드를 호출하여 SOAP 메시지가 서비스에 도착하면 새로운 서비스 인스턴스가 생성된다. 그리고 이 인스턴스가 SOAP 메시지를 수신하여 메쏘드 호출을 수행하고, 메쏘드가 종료하면 그 인스턴스는 GC의 대상이 되게 된다. 물론, 서비스 타입이 IDisposable 인터페이스를 구현하고 있다면 Dispose 호출이 발생하게 됨은 ASMX와 동일하다.

필자주: WCF의 디폴트 인스턴스 관리 모델은 PerSession 으로써 이 글을 쓴 당시(2006년11월)에 필자가 PerCall을 디폴트 값으로 알고 있었다. 실제 마소 12월호에 실린 글에는 PerCall이 디폴트인 것처럼 기사가 실렸었다. HTTP 와 같은 세션을 지원하지 않는 트랜스포트(transport)를 사용하는 경우 디폴트 값인 PerSession을 사용하더라도 PerCall 처럼 인스턴스가 생성되고 소멸되므로 필자가 혼동을 했을 것이다. 필자도 닭인지라 왜 디폴트가 PerCall이라 글을 썼는지 모르겠다. -_-; 늦게나마 이 글을 통해 정정하며 잘못된 정보를 전달한 점에 대해 사과하는 바이다. 너그러이 용서해 주길....

PerCall 모드는 디폴트가 아니기 때문에 구체적으로 서비스에 PerCall 임을 명시해야 한다. 인스턴스 모드를 명시하기 위해서는 InstanceContextMode 열거자(enumberation)를 사용하면 된다. 서비스의 인스턴스 모델은 서비스의 계약(contract 혹은 interface)과는 무관한 서버 측의 구현에 대한 상세 내용이기 때문에 인터페이스에 인스턴스 모델을 밝히지 않는다. 대신 서비스의 계약을 구현하고 있는 서비스 타입에 서비스가 갖는 여러 가지 행동 방식(인스턴스 모드, 트랜잭션 등)을 명시하기 위해 ServiceBehavior 특성(attribute)를 사용한다. 바로 ServiceBehavior 특성에 InstanceContextMode 열거자를 통해 인스턴스 모드를 명시하면 된다.

필자주: PerCall 모드를 명시하지 않고 InstanceContextMode.PerSession 과 같은 값을 사용하더라도 어떤 바인딩을 사용하느냐에 따라 PerCall 모드처럼 작동할 수도 있음에 유의한다. NetTcpBinding 과 같이 연결 지향형(connection-oriented) 트랜스포트를 사용하는 경우 PerSession 값은 의미를 갖는다. 하지만 BasicHttpBinding을 사용하는 경우 HTTP 라는 비 연결형(connectionless) 트랜스포트 때문에 PerSession 값은 의미를 갖지 않고 PerCall 과 같이 인스턴스가 생성되거나 소멸됨에 유의하자. PerSession에 대한 상세한 내용은 추후에 이 글의 본문에서 다룬다.

리스트 2는 PerCall 모드를 명시적으로 표현하는 서비스 구현을 보여주고 있다. 서비스 타입인 PerCallService 클래스는 ServiceBehavior 특성을 포함하고 있으며 InstanceContextMode 속성에 PerCall 을 명시하고 있다.

[ServiceContract(Namespace = "http://simpleisbest.net/wcf/example/instancing")]

public interface IEchoService

{

    [OperationContract]

    string Echo(string msg);

}

 

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]

public class PerCallService : IEchoService, IDisposable

{

    private int _CallCount = 0;

 

    public PerCallService()

    {

        Console.WriteLine("PerCallService Created...");

    }

 

    public void Dispose()

    {

        Console.WriteLine("PerCallService Disposing...");

    }

 

    public string Echo(string msg)

    {

        System.Threading.Thread.Sleep(10000);

        _CallCount++;

        Console.WriteLine("PerCallService::Echo() invoked... call count = {0}",

            _CallCount);

        return "You send : " + msg;

    }

}

리스트2. PerCall 모드를 사용하는 서비스 구현

리스트 2를 호출하는 클라이언트를 Visual Studio 2005를 사용하여 작성한 코드는 다음과 같다. Visual Studio 2005에서 WCF 클라이언트 프록시를 생성하기 위해서는 Extension for WCF, WPF 애드인을 설치 해야만 한다. 이렇게 하면 ASMX에 대한 프록시를 만드는 것과 동일한 과정을 통해 WCF 서비스에 대한 프록시 코드를 얻을 수 있다. 다음 코드의 EchoServiceClient 클래스가 바로 이 과정을 통해 생성된 클래스이다.

PerCallService.EchoServiceClient service1 = new PerCallService.EchoServiceClient();

service1.Echo("Hello, PerCallService");

service1.Echo("Hello again, PerCallService");

service1.Close();

위와 같은 클라이언트 코드를 수행하면 서비스 측에서 다음과 같은 결과를 얻을 수 있다.

PerCallService Created...
PerCallService::Echo() invoked... call count = 1
PerCallService Disposing...
PerCallService Created...
PerCallService::Echo() invoked... call count = 1
PerCallService Disposing...

예상대로 메쏘드 호출이 발생할 때 마다 서비스 타입인 PerCallService 클래스의 인스턴스가 생성되고 메쏘드 호출이 발생한 후에 곧바로 Dispose 메쏘드가 호출되는 것을 알 수 있을 것이다. 매번 인스턴스가 생성되기 때문에 멤버 필드인 _CallCount의 값이 매번 0으로 초기화되어 호출 카운트가 매번 1임에도 주의를 하자. 이는 ASMX, 닷넷 리모팅의 SingleCall, COM+ Just-In-Time Activation의 작동 방식과 동일하다. 인스턴스가 매번 생성된다는 것은 인스턴스가 상태를 지속적으로 유지할 수 없음을 의미하고 이는 곧 서비스를 state-less 하도록 설계해야 함을 의미한다.

PerCall 인스턴스 모드가 WCF가 선호하는 인스턴스 모드인 것은 다 이유가 있다. 첫째로 가장 간단한 메커니즘으로써 WCF 런타임이 갖는 부담이 매우 적다. 매 호출마다 새로운 인스턴스를 생성하고 호출 후에 이 인스턴스를 버려버림으로써 인스턴스를 유지하고 추적하며 관리해야 하는 부담을 줄일 수 있기 때문이다. 대부분의 경우 매번 인스턴스를 생성하는데 소요되는 비용은 매우 작아서 그것을 유지하는 것보다 훨씬 나은 성능을 유발한다. 그리고 우리는 지금까지 ASMX, COM+ 등에서 이렇게 상태를 유지하지 않고(stateless) 서비스를 작성하는 것에 이미 익숙해져 있지 않은가?

PerCall 모드의 두 번째 장점은 확장성(scalability)이다. 확장성은 클라이언트가 10에서 100, 100에서 1000, 1000에서 10000 으로 늘어나더라도 서비스가 다운되거나 크게 성능저하가 발생하지 않음을 의미한다. 매번 인스턴스를 생성하지 않고 이것을 유지하고 추적해야만 한다면 클라이언트의 숫자가 늘어남에 따라서 관리해야 할 인스턴스의 개수도 늘어나기 마련이다. 이 때문에 클라이언트의 숫자가 크게 늘어나면 서비스는 느려지거나 심지어는 다운되는 경우도 발생하곤 한다. 항상 그렇지는 않지만 PerCall 과 같은 인스턴스 모델은 그런 현상이 발생될 가능성을 줄여주곤 한다.

PerCall의 세 번째 장점은 서버 자원을 효율적으로 사용할 수 있도록 해준다는 것이다. 예를 들어 보자. 서비스의 인스턴스가 생성자에서 데이터베이스를 연결하고 Dispose 시점에서 데이터베이스 연결을 해제한다고 가정해 보자. PerCall 이 아닌 전통적인 클라이언트/서버 모델에서 클라이언트는 프로그램 시작과 더불어 프록시를 생성하곤 한다. 프록시는 서비스의 인스턴스 생성을 유발하며, 생성된 서비스 인스턴스는 클라이언트의 종료와 더불어 메모리에 제거되곤 한다. 이 때 클라이언트가 꾸준히 서비스를 호출한다면 모를까, 많은 시간 동안 서비스 인스턴스는 놀고 있을 것이 자명하다(사용자의 입력 시간, 쉬는 시간 등). 이런 상황이라면 데이터베이스 연결은 불필요하게 오랫동안 유지될 것이며 이는 데이터베이스 서버에 부하를 가중시키는 커다란 요인이 되곤 한다. 만약 PerCall 인스턴스 모드를 사용한다면 클라이언트의 프록시 생성과 서비스 인스턴스 생성은 아무런 관계가 없다. 실질적으로 클라이언트가 서비스 메쏘드를 호출할 때라야 서비스 인스턴스가 생성되고 그 인스턴스도 메쏘드 호출 후에는 곧바로 Dispose 되므로 아주 짧은 시간 동안만 데이터베이스 접속이 유지될 것이다. 여기에 ADO.NET의 연결 풀링이 접목되므로 데이터베이스 연결은 다른 클라이언트를 위해 재사용 될 수도 있다. 이 어찌 효율적이라 아니할 수 있는가?

필자의 경험상, ASMX 이건 닷넷 리모팅 이건 COM+ 이건 PerCall 류의 인스턴스 관리 방법은 대부분 문제를 일으키지 않았으며 만족할만한 성능을 과시하곤 했다. 달리 마이크로소프트에서 이러한 인스턴스 관리 방법을 권장하는 것이 아니다. 그만한 이유가 있기 때문인 것이다. WCF 역시 지난 기술들(ASMX, 닷넷 리모팅, COM+)과 마찬가지로 동등한 인스턴스 관리 모델로서 PerCall 모드를 지원하며 이것을 디폴트 행동으로 삼고 있다. 즉, 곧 다루게 될 PerSession 과 같은 인스턴스 관리 모델을 사용하더라도 바인딩이 세션을 지원하지 않는다면 PerSession은 PerCall 처럼 작동하게 된다.

PerSession 서비스

PerSession 인스턴스 모드는 다소 생소해 보이지만, 닷넷 리모팅의 CAO와 매우 흡사한 인스턴스 관리 방법이다. PerSession 인스턴스 모드를 이해하기 위해 먼저 정확히 해두어야 할 것은 세션의 개념이다. WCF에서 Session은 ASP.NET 의 세션이나 TCP의 세션과는 전혀 다른 의미를 갖는다. WCF에서 하나의 세션이라 함은 서비스와 연결된 하나의 프록시를 말한다. 하나의 클라이언트에서 2개의 프록시를 생성했다면 그 클라이언트는 2개의 WCF 세션을 갖는 것이다. 세션은 프록시를 생성함으로써 생성되고 프록시가 Close 될 때 세션 역시 닫힌다.

필자주: 사실 WCF의 세션은 상당히 혼동스러운 컨셉이다. ASP.NET의 세션과도 다르며 TCP의 세션과도 다르기 때문이다. 이 글에서는 세션이 하나의 클라이언트와 서비스 인스턴스를 연결하는 것처럼 기술되어 있고, 기본 WCF의 동작도 그러하지만, 보다 고급 설정에 의해 단일 세션 내에서도 서비스 인스턴스가 소멸되고 새로운 인스턴스가 생성되도록 할 수도 있다.
OperationBehaviorAttribute 특성의 ReleaseInstanceMode 속성을 어떻게 주는가에 따라서 단일 세션내에서도 서비스의 인스턴스가 PerCall 과 비슷한 행동을 하도록 제어할 수 있다는 말이다.
WCF가 매우 다양한 기존 기술들을 통합하는 원격 호출 프레임워크이기 때문에 풍부한 기능을 제공하지만 다양한 기능 덕택에 상당히 복잡하고 혼란스러운 기능 및 설정이 존재하곤 한다.

PerSession 인스턴스 모드는 세션이 유지되는 동안 서비스 인스턴스 역시 지속되는 인스턴스 모드이다. 세션이 지속되기 위해서 WCF는 바인딩(binding)이 신뢰도(reliability)가 높을 것을 요구한다. 그 이유는 세션이 유지되기 위해서는 클라이언트와 서비스가 신뢰되는 연결, 즉 바인딩을 사용해야 하기 때문이다. 쉽게 말해서 WCF에서 서비스가 어떤 바인딩을 사용하는가에 따라서 PerSession 이 의미를 갖을 수도 있고 그렇지 않을 수도 있다는 것이다. 예를 들어, BasicHttpBinding 과 같이 연결 지향적이지 않고 신뢰성을 지원하지 않는 바인딩에서 PerSession은 PerCall 과 동일하게 작동한다. 하지만 WSHttpBinding 이나 NetTcpBinding 과 같이 신뢰성을 제공하는 바인딩을 사용하면 세션 동안 서비스 인스턴스가 유지되게 된다. 이점이 CAO와 다른 점이다. 닷넷 리모팅의 CAO는 TCP 채널을 사용하건 HTTP 채널을 사용하건 관계가 없다.

필자주: HTTP는 TCP를 사용하는 프로토콜임에도 불구하고 그 근본은 connectionless 프로토콜이다. HTTP 1.0의 스펙을 살펴보면 HTTP Request를 전송하고 Response를 받은 후 TCP 연결이 끊기도록 되어 있다. 이러한 기본적인 행위 상 HTTP를 연결 지향형 프로토콜이 아닌 비 연결 지향형 프로토콜로 보는 것이다. 비록 HTTP 1.1에서 TCP 연결을 유지하는 스펙이 제시되었고 거의 99.9%의 웹 서버가 단일 TCP 연결로 다수의 HTTP Request/Response를 처리하지만 HTTP를 연결 지향형으로 가정하지는 않는다.
이 때문에 WCF에서도 HTTP를 트랜스포트로 사용하는 바인딩들은 기본적으로 PerSession 모드를 지원하지 않는다. 하지만 WSHttpBinding 에 SSL과 같은 트랜스포트 보안을 적용하는 경우 SSL 자체가 보안 세션을 가지므로 이 보안 세션에 기반한 세션을 제공할 수 있다. 또한 WSHttpBinding이 신뢰할 수 있는 메시징(reliable messaging)을 사용하는 경우에도 세션을 지원하게 된다.
WSDualHttpBinding은 항상 신뢰할 수 있는 메시징이 사용되므로 세션을 사용할 수 있다.

PerSession 인스턴스 모드를 사용하기 위해서는 마찬가지로 ServiceBehavior 특성을 이용하면 된다. 그리고 서비스의 바인딩을 WsHttpBinding 이나 NetTcpBinding 등 신뢰성을 지원하는 바인딩을 사용 한다. 리스트 3은 PerSession 모드를 사용하는 서비스 구현을 보여주고 있다.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]

public class PerSessionService : IEchoService, IDisposable

{

    private int _CallCount = 0;

 

    public PerSessionService()

    {

        Console.WriteLine("PerSessionService Created...");

    }

 

    public void Dispose()

    {

        Console.WriteLine("PerSessionService Disposing...");

    }

 

    public string Echo(string msg)

    {

        _CallCount++;

        Console.WriteLine("PerSessionService::Echo() invoked... call count = {0}",

            _CallCount);

        return "You send : " + msg;

    }

}

리스트3. PerSession 모드를 사용하는 서비스 구현

필자주: ServiceBehavior 특성의 InstanceContextMode 속성은 PerSession을 기본값으로 가지고 있음에 주의하자. 비록 사용하는 바인딩에 의해 PerSession 이란 값이 의미가 없어지고 PerCall 처럼 인스턴스가 생성될 지라도 InstanceContextMode 속성의 기본 값이 PerSession 임은 간과하지 말아야 할 것이다.

리스트 3에 대한 클라이언트 코드와 수행 시 서비스 측의 결과는 리스트 4와 같다. 예제 코드에서 2개의 프록시를 생성했음에 유의하자. 즉, 2개의 세션을 사용했다는 말이다. 각 세션 별로 서비스 인스턴스는 하나씩 존재하게 되고 인스턴스의 멤버 필드인 _CallCount 값이 계속 유지되는 것을 알 수 있을 것이다. 또한 프록시에 대해 Close 메쏘드가 호출되는 시점에서 서비스 인스턴스가 제거되고 Dispose 역시 호출됨에도 눈 여겨 볼 필요가 있다.

PerSessionService.EchoServiceClient service21 = new PerSessionService.EchoServiceClient();

service21.Echo("Hello, PerCallService");

service21.Echo("Hello again, PerCallService");

 

PerSessionService.EchoServiceClient service22 = new PerSessionService.EchoServiceClient();

service22.Echo("Hello, PerCallService");

service22.Echo("Hello again, PerCallService");

 

service21.Close();

service22.Close();

 

호출 결과---------------------------------------------------

PerSessionService Created...
PerSessionService::Echo() invoked... call count = 1
PerSessionService::Echo() invoked... call count = 2
PerSessionService Created...
PerSessionService::Echo() invoked... call count = 1
PerSessionService::Echo() invoked... call count = 2
PerSessionService Disposing...
PerSessionService Disposing...

 

리스트4. PerSession 클라이언트 코드와 그 결과

때때로 클라이언트가 프록시를 Close 하지 않는 경우가 발생하곤 한다. 프로그래밍 상의 실수 이거나 어떤 이유로 클라이언트 프로그램 혹은 시스템 전체가 다운되는 경우가 대표적인 사례가 되겠다. 어찌되었건 이런 상황이 발생하면 서비스 인스턴스는 서버 상에 계속 남아 있게 된다. 이러한 문제는 닷넷 리모팅의 CAO에서도 동일하게 발생할 수 있다. WCF에서는 이처럼 서비스 인스턴스가 일정 시간 이상 호출을 받지 않으면(inactivity state) 인스턴스를 제거하는 타임 아웃 기능을 제공한다. 디폴트 타임아웃은 10분이며 이 값은 프로그램적으로나 설정 파일을 통해 변경이 가능하다.

WsHttpBinding 이나 NetTcpBinding 과 같이 ReliableSession 을 지원하는 클래스는 다음과 같은 코드를 통해 ReliableSession 기능을 활성화하면서 타임아웃을 설정할 수 있다.

WSHttpBinding binding = new WSHttpBinding();

binding.ReliableSession.Enabled = true;

binding.ReliableSession.InactivityTimeout = TimeSpan.FromMinute(5);

위 코드는 .config 파일에서 다음과 같은 표현으로써 타임아웃 설정이 가능하다. 일단 타임아웃이 발생한 후에는 어떤 서비스 호출도 예외를 유발하며, 심지어 Close 호출까지도 예외를 유발함을 잊지 말자.

<wsHttpBinding>

    <binding name="CustomBinding">

        <reliableSession inactivityTimeout="00:05:00"

                        enabled="true" />

    </binding>

</wsHttpBinding>

이외에도 PerSession 모드는 인스턴스가 세션 내에서 유지되므로 여러 클라이언트에 의해 세션 인스턴스가 공유되도록 할 수도 있다. 이러한 내용들은 지면 관계 상 여기서 모두 다룰 수 없음을 독자들에게 사과하는 바이다. 다음에 기회가 있다면 상세히 알아볼 것을 약속하는 바이다.

이제 PerSession 모드의 장/단점에 대해 살펴보도록 하자. PerSession 모드의 최대 장점은 인스턴스가 세션 동안 상태를 유지한다는 것이다. 얼핏 머리를 스치는 것이 로그온 정보, 사용자 정보 등등을 인스턴스 내에 기록해 두고 싶다는 생각이 들지 않는가? 클라이언트가 구동되면서 서비스를 최초로 호출 함으로써 인스턴스가 생성되므로 인스턴스 생성자에서 필요한 정보를 모두 멤버 필드에 기록해 두고 이후의 메쏘드 호출에서 기록해 둔 정보를 재사용하는 것이다. ASP.NET 이나 ASP에서 유용하게 써먹었던 방법일 것이다.

PerSession 모드의 또 다른 유용한 사용처는 서비스 인스턴스를 생성하는데 오랜 시간이 소요되는 경우이다. 예를 들어 서비스가 또 다른 원격 서버에 소켓(socket) 데이터를 전송해야 하거나 시리얼 포트 등을 통해 하드웨어를 액세스해야 한다면 PerCall 은 매 호출마다 소켓 연결 혹은 하드웨어 적인 접속 과정을 거쳐야 할 것이므로 매우 효율적이지 못할 것은 자명하다. 이 때 PerSession 은 매우 유용한 인스턴스 모드로서 대두 될 것이다.

하지만 PerSession 모드는 세션의 개수가 늘어감에 따라 효율이 떨어지기 마련이다. 세션이 많으면 많을수록 WCF 런타임이 유지/추적/관리 해야 할 인스턴스가 늘어나기 때문에 일반적으로 확장성이 좋지 못한 경우가 많다. 또한 PerCall 에서 설명한 바 대로 불필요한 서버 자원의 낭비가 발생할 수도 있다. 따라서 PerSession 인스턴스 모드를 선택할 때는 상당한 주의가 필요하다.

Single 서비스

Single 모드는 닷넷 리모팅의 Singleton 과 매우 비슷한 인스턴스 모드이다. 즉, 서비스 인스턴스는 오직 하나의 인스턴스만이 생성되며 모든 클라이언트의 서비스 메쏘드 호출을 하나의 인스턴스가 처리하게 된다. InstanceContextMode 열거자의 값 중 Single 값을 다음과 같이 사용하면 Single 모드가 사용되게 된다.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]

public class SingleService : IEchoService, IDisposable

{

... 내용 생략 ...

}

Single 모드에서 생성될 유일한 인스턴스는 자동으로 WCF 런타임에 의해 생성될 수도 있고, 직접 미리 생성해 놓은 인스턴스를 사용할 수도 있다. 이러한 옵션은 서비스 호스트를 생성할 때 선택할 수 있다. 다음과 같이 서비스 호스트를 생성할 때 서비스 타입을 명시하는 경우에는 최초의 클라이언트 호출이 발생할 때 인스턴스가 생성되고 그 인스턴스가 모든 클라이언트 호출을 처리하게 된다.

ServiceHost host3 = new ServiceHost(

    typeof(SingleService),

    new Uri("http://localhost:8083/wcf/instancing/single"));

서비스 호스트를 구현하는 ServiceHost 클래스의 생성자 중 서비스 타입이 아닌 인스턴스를 매개변수로 취하는 생성자가 존재한다. 이 생성자를 사용하면 미리 만들어 놓은 인스턴스를 Single 모드의 서비스 인스턴스로 사용할 수도 있다. 다음과 같이 말이다.

SingleService instance = new SingleService();

// 생성한 인스턴스에 대해 property 혹은 method 호출을 통해 초기화 한다. (생략)

ServiceHost host3 = new ServiceHost(

    instance,

    new Uri("http://localhost:8083/wcf/instancing/single"));

닷넷 리모팅의 Singleton에 비해 WCF의 Single 모드가 갖는 유연성이라 할 수 있겠다. 이외에도 WCF의 Single 모드는 생성된 Single 모드의 인스턴스를 액세스할 수 있는 속성 역시 제공한다. ServiceHost 클래스의 SingletonInstance 속성이 바로 그것으로써 서비스 호스트 클래스에 접근할 수 있다면 곧 Single 모드의 서비스 인스턴스에도 접근할 수 있다.

Single 모드는 PerSession 와 달리 특정 바인딩을 요구하지 않으므로 가장 기초적인 BasicHttpBinding 을 사용할 수 있음을 알아두자. Single 모드에 대한 구체적인 코드는 지면 관계상 생략한다. 실제 코드는 이달의 디스켓의 내용을 참고하도록 하자.

Throttling

인스턴스 관리 방법과는 좀 거리가 있지만 생성되는 서비스 인스턴스의 개수를 제어하는 방법이 바로 쓰로틀링(throttling) 이다. 일반적으로 쓰로틀링은 CPU 혹은 메모리, 클라이언트 접속 개수가 일정 수준 이상으로 사용되는 것을 막기 위해 사용되는 서버 측 기법이다. 쓰로틀링을 통해 서버에 과부하가 걸리는 것을 막거나 DoS(Denial of Service)와 같은 해킹 공격을 막기 위해 사용된다.

WCF 역시 Max Concurrent Sessions, Max Concurrent Calls, Max Instances 의 세가지 방법을 통해 쓰로틀링을 지원하고 있다. Max Concurrent Sessions은 서비스에 접속하는 클라이언트의 세션 개수를 제한하고 Max Concurrent Calls는 모든 서비스 인스턴스를 통틀어 통시에 호출되는 개수를 제한하고 있다. 마지막으로 Max Instances는 말 그대로 생성되는 서비스 인스턴스의 개수를 제한한다. 디폴트 쓰로틀링 설정은 제한이 없도록 설정되어 있다.

쓰로틀링은 서비스 타입(서비스 인터페이스를 구현하는 클래스) 별로 설정하고 적용된다. 일단 쓰로틀링이 제한하는 최대 임계치에 도달하면 클라이언트의 호출은 큐에 삽입되게 된다. 그리고 WCF 런타임은 쓰로틀링의 임계치를 넘기지 않는 수준에서 순차적으로 큐에서 클라이언트 요청을 꺼내어 서비스를 처리하게 된다.

PerCall 인스턴스 모드에서 동시 호출의 개수는 서비스 인스턴스의 개수와 동일하다. 앞서 필자의 설명을 잘 이해했다면 방금 설명 역시 쉽게 이해할 것이다. 따라서 PerCall 모드를 사용하는 서비스는 Max Concurrent Calls 값과 Max Instances 값 중 작은 값에 대해 동시 호출을 허용하게 된다.

PerSession 인스턴스 모드에서 클라이언트들의 전체 프록시 개수가 세션의 개수와 동일하고 각 세션마다 인스턴스가 생성되므로 Max Concurrent Sessions 가 제한하는 것과 Max Instances 가 제한하는 것이 같다. 따라서 Max Concurrent Session 값과 Max Instances 값 중 작은 것이 실질적으로 쓰로틀링에 의해 클라이언트 요청이 제한될 것이다.

WCF의 쓰로틀링은 프로그램적으로 혹은 설정 파일에 의해 설정될 수 있다. 프로그램적으로 쓰로틀링을 설정하기 위해서는 ServiceThrottlingBehavior 클래스를 통해 다음과 같이 설정할 수 있다.

ServiceHost host1 = new ServiceHost(

    typeof(PerCallService),

    new Uri("http://localhost:8083/wcf/instancing/percall"));

// 코드에 의한 Throttling 설정

ServiceThrottlingBehavior behavior = new ServiceThrottlingBehavior();

behavior.MaxConcurrentCalls = 10;

behavior.MaxConcurrentInstances = 10;

behavior.MaxConcurrentSessions = 10;

host1.Description.Behaviors.Add(behavior);

host1.Open();

설정 파일을 사용하는 경우 다음과 같이 behavior 를 설정하고 서비스에서 이 behavior 설정을 참조 하면 된다.

<system.serviceModel>

    <services>

        <service    name="WCFService.PerCallService"

                    behaviorConfiguration="InstancingTest">

            <endpoint    contract="WCFService.IEchoService"

                        binding="basicHttpBinding"/>

        </service>

    </services>

    <behaviors>

        <serviceBehaviors>

            <behavior name="InstancingTest">

                <serviceThrottling    maxConcurrentSessions="5"

                                    maxConcurrentInstances="5"

                                    maxConcurrentCalls="5" />

            </behavior>

        </serviceBehaviors>

    </behaviors>

</system.serviceModel>

마치며……

WCF는 PerCall, PerSession, Single 의 세가지 인스턴스 모드를 지원하며 각기 인스턴스를 생성하고 유지/추적/관리하는 독특한 특징을 가지고 있다. 이들은 모두 제각기 특성을 가지며 장/단점을 가지고 있다. 일반적으로는 PerCall 모드를 사용하는 것이 좋지만 항상 그렇지는 않다. 각 인스턴스 모드의 특성과 장단점을 잘 파악하고 서비스의 특성에 맞추어 인스턴스 모드를 선택해야 할 것이다. 이를 위해 POC(Proof of Concept) 작업이나 파일럿 프로젝트에서 어떤 인스턴스 모드를 선택할지 신중히 검토해야만 한다. 대개 이러한 과정을 거치지 않기 때문에 실제 프로젝트에서 어려움을 겪는 경우가 많다.

이번 칼럼에서 다룬 WCF의 인스턴스 관리 방법 외에도 WCF는 세션을 두 클라이언트가 공유하거나(Shared Session), 클라이언트 측 프록시를 복사하는(duplicating proxy) 기법을 제공하여 다양한 기법으로 서비스 인스턴스와 세션을 제어하는 방법도 제공한다. 지면 관계상 이들을 모두 다루지 못한 점이 아쉽지만 필자의 블로그나 이곳 칼럼을 통해 다시 다룰 것을 독자들에게 약속하며 글을 마치겠다. 



Comments (read-only)
#re: 2006년 12월호 닷넷 칼럼 :: &nbsp;WCF Instance Management / 즈믄 / 2007-04-27 오후 2:42:00
아.. 역시 잘 보고 값니다..
이어지는 WCF 강좌.. 계속 기대합니다.. 정리 쏙쏙~~

감사합니다..
#re: 2006년 12월호 닷넷 칼럼 :: &nbsp;WCF Instance Management / 오타발견 / 2008-01-07 오후 4:11:00
PerCall Service 섹션에 오타가...^^
enumberation

글 잘 읽고 있습니다.