SimpleIsBest.NET

유경상의 닷넷 블로그

웹 서비스 호출 시 성능 문제...

by 블로그쥔장 | 작성일자: 2005-12-07 오후 5:20:00
이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다. 저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나 토론을 할 수도 있습니다.
웹 서비스를 호출할 때, 성능이 예상 보다 너무 안 나올 때가 종종 있습니다. 코드를 어떻게 작성했냐를 떠나서 아주 간단한 웹 서비스 호출임에도 불구하고 성능이 안 나오는 경우가 종종 있지요. 이 때는 항상 네트워크를 의심해 봐야 합니다. 결론만 쨉싸게 먼저 말하면 HttpWebRequest를 사용하는 코드는 디폴트로 한 서버에 2개를 초과하는 TCP 연결을 맺지 않도록 되어 있습니다. 따라서 여러 쓰레드가 동시에 하나의 웹 서비스 서버에 요청을 하는 경우, 2개를 제외한 나머지 쓰레드는 운 좋은 그 두 놈이 종료하기를 기다려야 합니다.

이러한 제한에 대해 상세히 알아보고, 이 제한을 없애는 설정에 대해서도 살펴보겠습니다. 사실 예전부터 이 내용을 다루어야지 하다가... 모 게시판에 어떤 분이 질문하셔서 이제서야 글을 쓴다는... 아웅...

Web Service Bottleneck

필자가 예전에 스트레스 테스트를 할 때였다. 웹 어플리케이션이 다른 어플리케이션 서버의 비즈니스 로직을 호출하는데, 이 때 사용된 방법이 웹 서비스였다. 테스트 방법은 비주얼 스튜디오에 포함된 ACT(Application Center Test)로 웹 어플리케이션에 졸라 스트레스를 주고 그 결과를 살펴보는 것 이였다. 그런데... 성능이 예상 보다 너무 안 나오는 것 이였다. 하도 이상해서 ACT가 직접 어플리케이션 서버로 스트레스를 주도록 바꾸어 보았더니 엄청난 성능이 나오는 것이 아닌가? 쓰바 이게 뭔 일이냐...

Problem

닷넷에서 웹 서비스를 호출하고자 하면, 일반적으로 웹 서비스 프록시를 만들게 된다. 비주얼 스튜디오에서 "웹 참조"를 하거나 wsdl.exe 유틸리티를 수행시키면 웹 서비스에 대한 클라이언트 프록시가 이뿌게 만들어지고, 웹 서비스를 호출하는 것은 이제 프록시를 호출하는 것에 지나지 않을 정도로 간단해 진다.

웹 서비스 프록시는 System.Web.Protocol 네임스페이스의 HttpSoapClientProtocol 클래스를 상속 받아 구현된다. 사실 웹 서비스를 호출하는 중요한 작업은 모두 이 클래스에서 수행한다고 알면 되는데... 이 클래스가 웹 서비스 서버를 호출할 때, System.Net 네임스페이스의 HttpWebRequest 클래스를 사용한다는 것이다. 이 클래스야 닷넷에서 HTTP 프로토콜이 필요할 때 많이 애용되는 클래스 이므로 대충은 어떤 클래스인지 알고 있을 것이다.

그런데... 이 클래스에는 기본적으로 제약 사항을 갖고 있다. HttpWebRequest 클래스는 동일 호스트에 대해 최대 2개까지의 TCP 연결 만을 맺도록 기본 설정되어 있다. 대개의 경우 이러한 제약은 문제가 되지 않는다. 사실 바꿀 수 있으므로 제약이라고 하기도 좀 뭐한 것이 되겠다. IE가 HTTP 액세스 할 때 사용되는 WinInet 역시 동일한 제약을 갖고 있으며, 이것이 불편하게 느껴진 적이 별로 없을 것이다.

그러나, 웹 어플리케이션이 또 다른 웹 서비스 서버를 호출하는 경우에는 문제가 달라진다. 웹 서비스 서버에 2개의 TCP 연결만을 허용한다면 한번에 웹 서비스를 호출할 수 있는 쓰레드는 기껏해야 둘이다. 하지만 웹 어플리케이션에 접속하여 사용하는 사용자가 많고 동시에 수십, 수백명이 웹 서비스 호출이 요구되는 페이지를 액세스 한다면? 완전히 조뙤는 것이 되겠다. 거의 순차적으로 요청이 처리될 것이고 사용자는 느린 반응 속도에 불평할 것이 틀림이 없다.

구체적으로 테스트를 해보자. 먼저 웹 서비스 코드(리스트1)는 다음과 같이 간단하게 작성한다. 웹 메쏘드 DoWebMethod() 는 단순히 1초 동안의 딜레이를 주도록 되어 있다.

    1 [WebMethod]

    2 public void DoWebMethod()

    3 {

    4     // 1초 동안 딜레이를 준다.

    5     System.Threading.Thread.Sleep(1000);

    6 }

리스트1. 테스트에 사용할 웹 서비스의 웹 메쏘드

이제 테스트용 클라이언트 코드를 살펴보자. 리스트2에서 보인 것 처럼 10 개의 쓰레드를 만들어 이 쓰레드들이 동시에 웹 서비스를 호출한다. 그리고 10개의 쓰레드들이 모두 웹 서비스 호출을 마칠 때까지 기다린다. 그리고, 이들 10개의 쓰레드를 만들어 수행하고 이들이 모두 종료될 때까지의 시간을 Win32 API인 GetTickCount를 통해 구한다. 리스트2의 코드는 웹 서비스의 클라이언트이므로 앞서 말한 웹 어플리케이션 시나리오라면 ASP.NET의 코드 정도로 생각해야 할 것이다.

    1 // 간단한 웹 서비스 테스트 클라이언트

    2 class TestClientApp

    3 {

    4     // Win32 API의 GetTickCount를 호출하여 시간을 잰다.

    5     [System.Runtime.InteropServices.DllImport("kernel32")]

    6     private static extern int GetTickCount();

    7 

    8     [STAThread]

    9     static void Main(string[] args)

   10     {

   11         int nrThreads = 10;

   12         Thread[] threads = new Thread[nrThreads];

   13         int start, end;

   14 

   15         Console.WriteLine("Begin Invoke...");

   16         // 쓰레드를 생성하여 동시에 웹 서비스를 호출하도록 한다.

   17         start = GetTickCount();

   18         for(int i=0; i < nrThreads; i++) {

   19             threads[i] = new Thread(new ThreadStart(WorkerMain));

   20             threads[i].Start();

   21         }

   22         // 쓰레드들이 종료되기를 기다린다.

   23         foreach(Thread thread in threads) {

   24             thread.Join();

   25         }

   26         // 소요된 시간을 계산 하여 나타낸다.

   27         end = GetTickCount();

   28         Console.WriteLine("End Invoke... Elapsed time = {0:0.000} sec", (end-start) / 1000.0);

   29     }

   30 

   31     static void WorkerMain()

   32     {

   33         TestService proxy = new TestService();

   34         proxy.DoWebMethod();

   35     }

   36 }

리스트2. 테스트용 웹 서비스 클라이언트

리스트2를 컴파일 하고 수행하면 어떤 결과가 나올까? 10개의 쓰레드가 1초가 소요되는 웹 서비스 메쏘드를 동시에(concurrently) 호출했다면, 수행결과는 1초 정도 소요되어야 옳을 것이다. 쓰레드 생성 및 종료에 소요되는 오버헤드를 계산하더라도 2초 이내로 결과가 나와야 상식이라 할 수 있다. 하지만 수행 결과는 쌩뚱맞게 5.xx 초 정도 소요된다. 10.xxx 초도 아니고 5.xx 초?

여기서 호스트 당 연결이 2개로 제한되어 있음을 알 수 있다. 10개의 쓰레드 중 2개는 호출이 수행될 것이고 나머지 8개의 쓰레드는 앞서 간 2 쓰레드의 호출이 끝나기를 기다리게 되며, 이런 식으로 매번 동시에 호출 가능한 쓰레드는 2개로 제한된다. 따라서 결과가 5초정도 소요되게 되는 것이다.

Solution

해결하는 방법은 의외로 매우 간단하다. Configuration 파일의 <system.net> 섹션에 <connectionManagement> 요소를 추가하고 Url 혹은 호스트 별로 최대 연결 개수를 명시해 주면 된다. 기본 설정은 machine.config 에 다음과 같이 되어 있다. 웹 어플리케이션에게는 졸라 엿 같은 설정이라 할 수 있겠다.

  <system.net>
    
<!-- 다른 부분 생략... -->
    
<connectionManagement>
      
<add address="*" maxconnection="2" />
    </
connectionManagement>
  
</system.net>

닷넷 프레임워크 2.0에는 이러한 설정이 없다. 즉 2개의 연결 제한이 기본적으로 없다는 얘기이다. 어찌 되었건 이러한 제한을 없앨려면 클라이언트의 configuration 설정을 다음과 같이 바꾼다. 웹 서비스를 호출하는 클라이언트가 웹 어플리케이션(ASP.NET)이라면 web.config 일 것이고 그냥 콘솔 혹은 윈도우 프로그램이라면 app.config 가 될 것이다.

  <system.net>
    
<!-- skip... -->
    
<connectionManagement>
      
<add address="*" maxconnection="100" />
    </
connectionManagement>
  
</system.net>

MSDN을 뒤져보면 <add> 태그에 address 속성에 호스트 이름으로 주어 각 호스트 별로 서로 다른 최대 연결 개수를 지정할 수 있는 것처럼 되어 있다. 하지만 닷넷 프레임워크 1.1에서는 아무리 address 속성을 이렇게 저렇게 넣어보아도 효과가 없었다. 다만 address를 * 로 주는 경우에만 효과가 있었다. 1.1 버전의 버그인 듯하다. 닷넷 프레임워크 2.0 에서는 address 속성에 http://hostname 형태로 프로토콜까지 지정해 주면 설정이 효과를 나타내었다.

Conclusion

결론은 닷넷 프레임워크 1.1에서 HttpWebRequest를 사용하는 경우, 기본적으로 2개의 TCP 연결만이 허용되는 제한을 갖고 있다는 것이고 이 제한을 없애기 위해서는 configuration 파일에 connectionManagement 설정을 해주어야 한다. HttpWebRequest를 사용하는 웹 서비스, 그리고 닷넷 리모팅에서 HTTP 채널을 사용하는 경우에는 모두 이러한 제약에 걸릴 수 있음에 유의해야 한다. 그리고 이러한 제약은 닷넷 프레임워크 2.0에서는 없어진 듯하다.

1.1 버전을 사용할 때 항상 connectionManagement 설정을 해 주어야 할까? 그렇지는 않다. 단순한 C/S 클라이언트는 대개, 여러 쓰레드를 사용하여 서버를 동시에 호출하는 경우가 없기 때문에 굳이 이 설정을 해주지 않아도 된다. 하지만 웹 어플리케이션은 동시에 여러 웹 서비스 호출이 발생할 수 있으므로 반드시 connectionManagement 설정을 해주어야만 한다. 그렇지 않으면 느린 응답속도에 괴로워하며 현업들의 등살에 몸부림 치게 될 것이다. (너무 무섭나? -_-)



Comments (read-only)
#re: 웹 서비스 호출 시 성능 문제... / 이주용 / 2005-12-07 오후 10:20:00
정말 고맙습니다.
해결책을 못찾고 있었는데.. 감사합니다.
덕분에 많은 도움되었습니다.
#re: 웹 서비스 호출 시 성능 문제... / 블로그쥔장 / 2005-12-08 오전 10:27:00
이런 제약 자체를 없앨려면

<connectionManagement>
<clear />
</connectionManagement>

이렇게 해도 됩니다. 이렇게 함으로써 machine.config의 제약 설정을 제거하는 효과가 납니다.
(테스트는 안해봐씀다... -_-; 텨텨텨)
#re: 웹 서비스 호출 시 성능 문제... / bandb2002 / 2006-03-12 오후 12:40:00
여기도 좋은 글이 있는 줄 몰랐네요.

"웹 서비스를 호출하는 클라이언트가 웹 어플리케이션(ASP.NET)이라면 web.config 일 것이고 그냥 콘솔 혹은 윈도우 프로그램이라면 app.config 가 될 것이다."
라고 하셨는데요.

웹서비스를 호출하는 클라이언트가 스마트클라이언트라면? 설정파일은 머가되나요?
웹페이지에 삽입된 스마트 클라이언트가 동시에 한화면에서 6개의 쓰레드로 웹서비스를 호출하거등요.
동시접속자수가 2명만되도... 12개의 호출...
현재, 클라이언트들은 대부분 이 화면을 띄워놓고 있을거거등요. 그렇다면, 오우~ 빨리 풀어야할 숙제네요. ㅡㅡ;


스마트의 설정파일은 iexplore.exe.config라 알고있고 , 그렇게 사용하고있거등요?(근데, 이넘이 제대로 다운로드가 안되는거 같아요.
그래서, 스마트의 환경설정파일을 그냥, iexplore.exe.config를 안쓰고, 스마트 다운받기전에 클라이언트단에 미리 임의의 xml파일을 복사해 놓고 사용합니다.^^)
#re: 웹 서비스 호출 시 성능 문제... / 블로그쥔장 / 2006-03-12 오후 5:07:00
스마트 클라이언트의 config 파일에 대한 글은 다음 URL을 참고 하십시요.

http://www.simpleisbest.net/archive/2006/01/25/399.aspx

#re: 웹 서비스 호출 시 성능 문제... / 황승재 / 2006-06-01 오전 9:30:00
가끔식 당연하다고 생각하는 것이 독이 될수도 있다는 생각이.....

SM 담당자 분이 위 글 중에 "클라이언트의 configuration 설정을 다음과 같이 바꾼다."
란 말에 의문이 가져 말하는 데 할 말이 반론을 하기가...
내 글이 아니라서..

WEB SERVICE가 있는 서버의 MACHINE.CONFIG 를 바꾸라는 건지
아님 호출하는 클라이언트의 환경 파일에서 추가하라는 건지요...
위 말의 애매함이 들어있네요..

전 당연히 서버라 생각되지만...
#re: 웹 서비스 호출 시 성능 문제... / 황승재 / 2006-06-01 오전 9:38:00
음.. 평서문인거 같아서요..

"클라이언트의 configuration 설정을 다음과 같이 바꾼다."
이 말의 뜻이 정확히 해주세요..

^^
#re: 웹 서비스 호출 시 성능 문제... / 황승재 / 2006-06-01 오전 9:39:00
댓글 삭제가 없네요..

바로 위글의 요청 글이 좀 무례한거 같아서.
설명좀 부탁합니다. 란 말로 바꾸고 싶은데...
#re: 웹 서비스 호출 시 성능 문제... / 블로그쥔장 / 2006-06-01 오전 10:50:00
-_-;
말그대로 웹 서비스의 "클라이언트 측 설정"을 바꾸라는 뜻입니다.
일반적으로 WinForm 클라이언트 같은 경우는 문제가 되지 않습니다만,
웹 어플리케이션이 웹 서비스를 호출한다면, 이 경우, 웹 어플리케이션이 웹 서비스의 클라이언트가
되는 겁니다. 이 경우, 본문에서 설명한 대로 다수의 웹 브라우저의 호출을 웹 어플리케이션이
처리하게 되므로 2개의 연결 제약이 문제를 유발한다는 뜻이지요.
따라서 본문의 "클라이언트의 configuration 설정"이라 함은 일반적으로 생각할 수 있는 WinForm 클라이언트 혹은
웹 서비스를 호출하는 웹 어플리케이션의 configuration 설정을 말합니다.
무조건 혹은 당연히 서버라고 생각하시면 정말 독이 될 수 있습니다.

그리고 본문 어디에도 machine.config를 수정하라는 말은 없습니다. 일반적으로 machine.config를 수정하는 것은
좋지 않습니다. 모든 닷넷 어플리케이션이 영향을 받기 때문입니다.
app.config 혹은 web.config에 설정을 추가하도록 하십시요.
#re: 웹 서비스 호출 시 성능 문제... / 황승재 / 2006-06-02 오전 10:41:00
설명 잘 들었습니다.

하나 더 질문을 드리고 싶어서요.
"? maxconnection 매개 변수의 값을 12*N으로 설정합니다. 여기서 N은 보유하고 있는 CPU 수입니다. "
란 말이 있습니다.
해당 MSDN 문서에 http://support.microsoft.com/?id=821268 에 있는거 봐서 권장 사항이네요.

설정 값은 "48" 로 셋팅 되어있습니다.
그러나, 현재 서버 2대 CPU 4 개에서 분당 30건 이상의 같은 페이지 호출을 하는데요.
좀 부하가 많을때 에러가 발생하네요.

값을 크게 잡아도 되는것인지 위 값보다 커서 오히려 문제가 되는지 의견 부탁합니다.

#re: 웹 서비스 호출 시 성능 문제... / 블로그쥔장 / 2006-06-05 오전 11:22:00
글쎄요 초당 30건도 아니고 분당 30건이라면 부하가 많다고 할 수는 없습니다.
48 설정값이라면 웹 서비스 서버당 최대 48개의 TCP 연결을 허용하는 것이므로
동시에 48개의 호출을 처리할 수 있는 설정입니다. 권장값보다 크게 잡는 다고
해가 되진 않을 듯 싶구요.

웹 페이지가 다른 웹 서버의 웹 서비스를 호출하는 것이므로
웹 서비스 서버에 문제가 있지 않을까요? 네트워크 모니터, 성능 카운터 등을 이용하여 모니터링을 해 보시고
원인을 파악한 후에 설정값을 변경하는 등의 행동이 필요할 듯 싶습니다.
#re: 웹 서비스 호출 시 성능 문제... / 김지영 / 2006-08-04 오전 11:36:00
그럼 웹 어플리케이션에서 MTS 올라가 있는 COM+ 를 호출하는데
그 COM+ 에 올라가 있는 dll 내에서 HttpWebRequest 사용시에는 따로 고쳐줘야 할 부분이 있는가요?
그냥 web.config 만 고치면 모두 적용되는지요..
#re: 웹 서비스 호출 시 성능 문제... / 블로그쥔장 / 2006-08-07 오후 10:26:00
COM+ 컴포넌트가 라이브러리 타입이라면 web.config 만 수정하시면 됩니다.
만약 서버 타입이라면 약간 복잡해집니다만...
설마 서버 타입을 사용하고 계시지는 않겠지요? 그러리라 믿습니다... ^^
#re: 웹 서비스 호출 시 성능 문제... / 김지영 / 2006-08-14 오전 10:32:00
서버 타입을 사용하고 있는데요..
라이브러리 타입하고 서버 타입의 차이가 큰가요?
닷넷 전부터 com+ 올라가는 dll 들은 서버 타입으로만 만들어서
당연히 그걸로 만들었는데..
#re: 웹 서비스 호출 시 성능 문제... / 블로그쥔장 / 2006-08-14 오후 6:12:00
닷넷으로 COM+ 컴포넌트를 작성할 때 서버 타입은 당연히 라이브러리 타입보다 상당히 느립니다.
닷넷 이전에는 원격 컴포넌트 호출을 위해 서버 타입외엔 대안이 없었지만
이젠 리모팅이 존재하므로 서버 타입을 굳이 선택해야 할 이유는 아주 드물게 몇가지 경우를 제외하고는 없습니다.
웹 서버와 COM+ 컴포넌트가 동일 컴퓨터에 존재한다면 생각할 것도 없이 라이브러리 타입을 선택해야 합니다.

그리고 서버 타입의 컴포넌트라면 .config 파일은 dllhost.exe.config 파일 명을 사용해야 하며
COM+ 어플리케이션의 응용프로그램루트 디렉터리를 먼저 설정한 후 그 디렉터리에 .config 파일을
두시면 됩니다.
#re: 웹 서비스 호출 시 성능 문제... / 어흥이 / 2006-08-24 오후 5:28:00
헉...이것도 모르고 쓰레드로 최대 동시에 5개를 처리하는 코드를 구현한 후
왜 안되지?...라고 6개월여를 그냥 넘기고 있었네요.ㅜㅜ.
쥔장님을 6개월만 일찍 알았다면 좋았을텐데....

좋은글 감사합니다.
#re: 웹 서비스 호출 시 성능 문제... / 블로그쥔장 / 2006-08-25 오전 9:59:00
저같은 초 울트라 호좁이 도움이 되었다니 저도 기쁩니다...
#re: 웹 서비스 호출 시 성능 문제... / 정해봉 / 2007-06-11 오전 11:02:00
.NET프레임웍 2.0을 사용하고 있습니다.
SmartClient를 IE 내장방식으로 사용하고 있습니다.
작업시간이 짧을때는 괜찮은데 시간이 길어질경우 다음과 같은 오류 메세지가 발생합니다.

----------------------------------------------------------
응용 프로그램의 구성 요소에서 처리되지 않은 예외가 발생했습니다.

클라이언트의 응답 콘텐츠 형식이 'text/html; charset=euc-kr'입니다.
'text/xml'여야 합니다.
다음 오류 메시지로 인해 요청하지 못했습니다.
--
<html>
<head>
<title>요청 시간이 초과되었습니다.</title>
----------------------------------------------------------

그런데 웹서비스 Proxy 에서 타임아웃을 설정해주거든요.
아래가 클라이언트DLL에서 타임아웃을 설정해주는 코드입니다.

Dim proxyWS As New WSPlot.Service
proxyWS.Timeout = 300000 '5분

proxyWS.웹메소드(...)
등등해서 데이터를 리턴 받아서 사용합니다.

뭐가 문제죠?
#re: 웹 서비스 호출 시 성능 문제... / 미노 / 2007-08-27 오전 10:24:00
http://support.microsoft.com/kb/915599/ko

참고maxConnection 속성의 기본값은 2입니다. 웹 서비스를 호출하는 Microsoft ASP.NET 연결 응용 프로그램의 경우 이 값을 프로세서 수의 12배로 설정하는 것이 좋습니다.