지난 11월 10일 닷넷 프레임워크 3.0이 정식 릴리스 되었다. 2.0이 발표된 지 채 1년도 안 돼 등장한 새 버전이지만 3.0은 기존 2.0에 WPF, WF, WCF, CardSpace 등이 추가되었을 뿐이다. 1.x 에서 2.0으로 변화한 것처럼 진화된 것이 아니라 2.0에 새로운 기능들이 추가된 채로 3.0이 발표되었을 뿐이다. 닷넷 프레임워크 3.0의 정식 버전이 릴리스 될 때까지 이들 기술을 습득하는 것을 미뤄온 독자들이 있다면 이젠 핑계거리가 없게 되었다.
이번 호에서는 WCF 에서 서비스 인스턴스를 관리하는 다양한 방법들을 소개하고자 한다. WCF 서비스를 프로그래밍할 때 항상 서비스의 계약(contract), 즉 인터페이스를 정의하고 이 인터페이스를 구현하는(implement) 서비스 타입을 정의해야 했다.
서비스 타입은 대개 닷넷의 클래스를 사용하기 마련이다. 서비스가 클라이언트의 요청을 처리하기 위해서는 클래스만 가지고는 아무런 일도 할 수 없다. 클래스의 인스턴스가 있어야 서비스 호출을 처리할 수 있을 것이다. 이렇게 WCF에서 서비스 타입의 인스턴스를 어떻게 생성하고 관리할 것인가에 대해 살펴보도록 하자.
때때로 새로운 것을 배우기 전에 과거의 것을 보고 정리하는 것이 도움이 되는 경우가 많다. WCF의 배경이 되는 기존 ASP.NET 웹 서비스(ASMX)와 닷넷 리모팅에서 제공하는 인스턴스 관리에 대해 간략하게 먼저 살펴보도록 하겠다. 지금부터 필자가 언급하는 내용은 대부분 독자들도 익히 아는 내용일런지 모르겠지만, 정리한다는 의미에서 읽어 보는 것도 나쁘지 않을 것이다. 그럼 기존 기술의 서비스 인스턴스 관리를 살펴보자.
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는 항상 이러한 단일 호출(SingleCall)에 대해 고유의 인스턴스를 생성하는 인스턴스 모델을 가짐으로써 간단하면서도 다수의 호출에도 쉽게 확장 될 수 있는(scalable) 모델을 가지고 있다. 이 인스턴스 모델의 장/단점에 대해서는 추후에 좀 더 다루기로 하자.
<리스트 1> 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;
}
}
.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와 동일하다.
PerCall 모드는 디폴트이기 때문에 구체적으로 서비스에 명시하지 않아도 되지만, PerCall 임을 명시할 수 있다. 인스턴스 모드를 명시하기 위해서는 InstanceContextMode 열거자(enumberation)를 사용하면 된다. 서비스의 인스턴스 모델은 서비스의 계약(contract 혹은 interface)과는 무관한 서버 측의 구현에 대한 상세 내용이기 때문에 인터페이스에 인스턴스 모델을 밝히지 않는다. 대신 서비스의 계약을 구현하고 있는 서비스 타입에 서비스가 갖는 여러 가지 행동 방식(인스턴스 모드, 트랜잭션 등)을 명시 하기 위해 ServiceBehavior 특성(attribute)를 사용한다. 바로 ServiceBehavior 특성에 InstanceContextMode 열거자를 통해 인스턴스 모드를 명시하면 된다.
리스트 2는 PerCall 모드를 명시적으로 표현하는 서비스 구현을 보여주고 있다. 서비스 타입인 PerCallService 클래스는 ServiceBehavior 특성을 포함하고 있으며 InstanceContextMode 속성에 PerCall 을 명시하고 있다. 앞에서 언급한 바 대로 PerCall 모드가 디폴트이므로 이와 같은 특성 선언은 불필요하다. 하지만 인스턴스 모드를 선언하는 방법을 보여주기 위해 포함된 것으로 알아두자.
<리스트 2> 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)
{
_CallCount++;
Console.WriteLine("PerCallService::Echo() invoked... call count = {0}", _CallCount);
return "You send : " + msg;
}
}
리스트 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+ 등에서 이렇게 상태를 유지하지 않고 서비스를 작성하는 것에 이미 익숙해져 있지 않은가?
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 인스턴스 모드는 다소 생소해 보이지만, 닷넷 리모팅의 CAO와 매우 흡사한 인스턴스 관리 방법이다. PerSession 인스턴스 모드를 이해하기 위해 먼저 정확히 해두어야 할 것은 세션의 개념이다. WCF에서 Session은 ASP.NET 의 세션이나 TCP의 세션과는 전혀 다른 의미를 갖는다. WCF에서 하나의 세션이라 함은 서비스와 연결된 하나의 프록시를 말한다. 하나의 클라이언트에서 2개의 프록시를 생성했다면 그 클라이언트는 2개의 WCF 세션을 갖는 것이다. 세션은 프록시를 생성함으로써 생성되고 프록시가 Close 될 때 세션 역시 닫힌다.
PerSession 인스턴스 모드는 세션이 유지되는 동안 서비스 인스턴스 역시 지속되는 인스턴스 모드이다. 세션이 지속되기 위해서 WCF는 바인딩(binding)이 신뢰도(reliability)가 높을 것을 요구한다. 그 이유는 세션이 유지되기 위해서는 클라이언트와 서비스가 신뢰되는 연결, 즉 바인딩을 사용해야 하기 때문이다. 쉽게 말해서 WCF에서 서비스가 어떤 바인딩을 사용하는가에 따라서 PerSession 이 의미를 가질 수도 있고, 그렇지 않을 수도 있다는 것이다.
예를 들어, BasicHttpBinding과 같이 신뢰성을 지원하지 않는 바인딩에서 PerSession은 PerCall과 동일하게 작동한다. 하지만 WsHttpBinding이나 NetTcpBinding과 같이 신뢰성을 제공하는 바인딩을 사용하면 세션 동안 서비스 인스턴스가 유지되게 된다. 바로 이 점이 CAO와 다른 점이다. 닷넷 리모팅의 CAO는 TCP 채널을 사용하건, HTTP 채널을 사용하건 관계가 없다.
<리스트 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;
}
}
PerSession 인스턴스 모드를 사용하기 위해서는 마찬가지로 ServiceBehavior 특성을 이용하면 된다. 그리고 서비스의 바인딩을 WsHttpBinding 이나 NetTcpBinding 등 신뢰성을 지원하는 바인딩을 사용한다. 리스트 3은 PerSession 모드를 사용하는 서비스 구현을 보여주고 있다. 리스트 3에 대한 클라이언트 코드와 수행 시 서비스 측의 결과는 리스트 4와 같다. 예제 코드에서 2개의 프록시를 생성했음에 유의하자. 즉, 2개의 세션을 사용했다는 말이다. 각 세션 별로 서비스 인스턴스는 하나씩 존재하게 되고 인스턴스의 멤버 필드인 _CallCount 값이 계속 유지되는 것을 알 수 있을 것이다. 또한 프록시에 대해 Close 메소드가 호출되는 시점에서 서비스 인스턴스가 제거되고 Dispose 역시 호출됨에도 눈여겨 볼 필요가 있다.
<리스트 4> PerSession 클라이언트 코드와 그 결과
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...
때때로 클라이언트가 프록시를 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 인스턴스 모드를 선택할 때는 상당한 주의가 필요하다.
이달의 디스켓 :
WCF 서비스 인스턴스 관리
Single 서비스
Single 모드는 닷넷 리모팅의 Singleton과 매우 비슷한 인스턴스 모드이다. 즉, 서비스 인스턴스는 오직 하나의 인스턴스만이 생성되며 모든 클라이언트의 서비스 메소드 호출을 하나의 인스턴스가 처리하게 된다. Instance ContextMode 열거자의 값 중 Single 값을 다음과 같이 사용하면 Single 모드가 사용되게 된다.
[ServiceBehavior(InstanceContextMode = Instance ContextMode.Single)]
public class SingleService : IEchoService, IDisposable
{ …… }
Single 모드에서 생성될 유일한 인스턴스는 자동으로 WCF 런타임에 의해 생성될 수도 있고, 직접 미리 생성해 놓은 인스턴스를 사용할 수도 있다. 이러한 옵션은 서비스 호스트를 생성할 때 선택할 수 있다. 다음과 같이 서비스 호스트를 생성할 때 서비스 타입을 명시하는 경우에는 최초의 클라이언트 호출이 발생할 때 인스턴스가 생성되고 그 인스턴스가 모든 클라이언트 호출을 처리하게 된다.
ServiceHost host = new ServiceHost(typeof(SingleService),
new Uri("http://localhost:8083/wcf/instancing/single"));
서비스 호스트를 구현하는 ServiceHost 클래스의 생성자 중 서비스 타입이 아닌 인스턴스를 매개변수로 취하는 생성자가 존재한다. 이 생성자를 사용하면 미리 만들어 놓은 인스턴스를 Single 모드의 서비스 인스턴스로 사용할 수도 있다. 다음과 같이 말이다.
SingleService instance = new SingleService();
// 생성한 인스턴스에 대해 property 혹은 method 호출을 통해 초기화 한다.
ServiceHost host = new ServiceHost(instance, new Uri("http://localhost:8083/wcf/instancing/single"));
닷넷 리모팅의 Singleton에 비해 WCF의 Single 모드가 갖는 유연성이라 할 수 있겠다. 이외에도 WCF의 Single 모드는 생성된 Single 모드의 인스턴스를 액세스할 수 있는 속성 역시 제공한다. ServiceHost 클래스의 Singleton Instance 속성이 바로 그것으로써 서비스 호스트 클래스에 접근할 수 있다면 곧 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"));
ServiceThrottlingBehavior behavior = new ServiceThrottling
Behavior();
behavior.MaxConcurrentCalls = 1;
behavior.MaxConcurrentInstances = 1;
behavior.MaxConcurrentSessions = 1;
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) 기법을 제공하여 다양한 기법으로 서비스 인스턴스와 세션을 제어하는 방법도 제공한다. 지면 관계상 이들을 모두 다루지 못한 점이 아쉽지만 필자의 블로그나 마소 칼럼을 통해 다시 다룰 것을 독자들에게 약속한다.