눈으로 확인하는 connectionManagement의 maxconnection 설정값
"눈으로 확인하는..." 시리즈 물이 될 것 같군요. ^^
눈으로 확인하는 maxWorkerThreads, minFreeThreads 설정값
; https://www.sysnet.pe.kr/2/0/983
이번에는 connectionManagement 노드에서 제공되는 maxconnection 값에 대한 테스트를 해보겠습니다. 이 값은 .NET 응용 프로그램에서 특정 호스트로 보내는 "동시 연결 수"를 제어합니다. 쉽게 풀어보면, 여러분들의 .NET 응용 프로그램에서 HttpWebRequest를 이용하여 다음과 같이 연결을 한다고 가정할 때,
string url = "http://testhost/test.aspx";
HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest;
using (HttpWebResponse httpResponse = req.GetResponse() as HttpWebResponse)
using (Stream stream = httpResponse.GetResponseStream())
using (StreamReader sr = new StreamReader(stream, Encoding.Default))
{
string txt = sr.ReadToEnd();
}
만약, test.aspx 내의 코드 수행 시간이 10초가 걸리는 상황에서 위의 코드를 스레드 100개로 돌리면 maxconnection에 지정된 수의 스레드만 testhost 컴퓨터에 연결이 되고 나머지 스레드들의 HttpWebRequest 개체들은 10초가 지난 후 기존 연결들이 해제되고 나서야 하나씩 차례대로 접속할 수 있는 효과가 있습니다.
덧붙여, 아래의 문서에 보면 maxconnection에 대한 설명이 잘 나와 있습니다.
<add> Element for connectionManagement (Network Settings)
; https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/network/add-element-for-connectionmanagement-network-settings
Don't forget to tune your application
; http://weblogs.asp.net/johnbilliris/archive/2010/06/30/don-t-forget-to-tune-your-application.aspx
대부분의 app/web.config 파일에는 connectionManagement 설정이 없는데요. 그렇다면 기본값은 몇일까요?
확인을 위해 간단한 테스트 환경을 구성했습니다.
A, B 호스트를 마련했고, A 호스트의 test.aspx는 B 호스트의 delay.aspx를 호출하도록 했습니다. delay.aspx는 일부러 Page_Load에서 Thread.Sleep(60 * 1000)을 해서 60초의 지연 시간을 갖고!
(별다른 의미는 없지만, B 호스트는 Web Garden == 2로 설정했습니다.)
부하 발생기를 이용해서 A 호스트의 test.aspx에 요청을 집중했더니 아래와 같이 maxconnection == 10의 결과가 나왔습니다.
위의 그림에서 "W11"이 "A 호스트"이고, "W21", "W22"는 "Web Garden"이 2개로 설정된 "B 호스트"의 웹 응용 프로그램을 나타냅니다.
보시는 것처럼, test.aspx에서 HttpWebRequest를 이용하여 "http://bhost/delay.aspx"로 호출했을 때 최대 10개의 연결만 맺어지는 것을 볼 수 있습니다. 즉, connectionManagement의 maxconnection 기본값은 10이 됩니다.
확실히 하기 위해 이 값을 30으로 바꿔보았습니다.
<system.net>
<connectionManagement>
<add address="*" maxconnection="30" />
</connectionManagement>
</system.net>
자, 그럼 결과가 어떻게 나올까요? ^^ 예상하시는 것처럼 "B 호스트"로의 연결은 각각 15개씩 총 30개의 연결로 제한이 됩니다.
한 가지 짚고 넘어가야 할 것이 있는데요. 문서가 약간 혼동스럽다는 점입니다. maxconnection이 지정되어 있지 않으면 기본값이 2라고 되어 있는데요.
maxconnection - The maximum number of connections allowed to a server. If not supplied, the default is 2.
"%Program Files%\Microsoft Visual Studio 10.0\xml\Schemas\DotNetConfig35.xsd" 파일에 명시된 바에 의하면 maxconnection은 생략할 수 없는 "required" 속성을 가지고 있습니다.
<xs:element name="connectionManagement" vs:help="configuration/system.net/connectionManagement">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="add" vs:help="configuration/system.net/connectionManagement/add">
<xs:complexType>
<xs:attribute name="address" type="xs:string" use="required" />
<xs:attribute name="maxconnection" type="xs:int" use="required" />
<xs:attribute name="lockAttributes" type="xs:string" use="optional" />
...[생략]...
그런데, 실제로 ^^; 생략이 되어 다음과 같이 설정하는 것이 가능합니다.
<system.net>
<connectionManagement>
<add address="*" />
</connectionManagement>
</system.net>
다시 부하 발생기로 테스트해 보면 값이 어떻게 나올까요?
예상했던 2가 아니라... 1입니다. (혹시, 제가 테스트를 잘못한 것 같다면 덧글 부탁드립니다.)
고객사에서 오류 문의가 왔습니다. 제니퍼 닷넷에서 다음과 같은 오류가 보고된 것입니다.
...[생략]...
[0005][13:39:23 477][ 15][ 15] TX-CALL[WebReq.http://[...this_public_domain_name...]/top.aspx] [15 ms]
[0006][13:39:23 477][ 0][ 0] SOCKET-CLOSE R=52174,W=106,uid=7568243
[0007][13:40:03 523][40,046][ 16] TX-CALL[WebReq.http://[...this_public_domain_name...]/bottom.aspx] [40,046 ms]
[0008][13:40:03 523][ 0][ 0] TX-EXCEPTION [System.Net.WebException: 작업 시간이 초과되었습니다. ...[이하 콜스택 생략]...
그런데... 가만 보면 해당 웹 페이지의 구성 자체가 재미있습니다. 보통 웹 페이지를 만들면 내부에 레이아웃을 구성하게 되는데요. 위의 고객 사이트는 웹 페이지의 top과 bottom에 해당하는 내용을 별도의 aspx로 빼고 사용자로부터 요청이 왔을 때 HttpWebRequest 개체를 이용해서 직접 top/bottom 관련해서 aspx를 호출하여 내용을 가져온 후 화면에 추가하는 것이었습니다.
위와 같은 구성이 성능에 어떤 영향을 미칠까요?
1) 우선, 해당 고객 사이트가 web.config에 connectionManagement / maxconnection 설정을 하지 않았다고 가정해 보겠습니다. 이 글의 처음에 설명했던 것처럼 특정 호스트에 대해 동시 연결이 기본값 10으로 동작하기 때문에, 위의 웹 사이트는 마치 10개의 상한을 가진 세마포어를 사용하는 효과를 가지게 됩니다. 동시 연결 10개 이후부터는 쓰레드들이 대기 현상이 발생하게 되고 사용자가 폭주하는 시간에는 그 현상이 더욱 심하게 됩니다.
게다가 위에서는 top과 bottom에 대해 각각 요청을 보냈기 때문에 스레드 대기 현상은 더욱 심해집니다.
2) 두 번째 문제는, HttpWebRequest 요청의 URL에 내부 IP가 아닌, 일반 사용자들이 접근할 때와 동일한 공용 DNS 이름을 사용했다는 것입니다. 이렇게 되면 내부 IP가 아닌 외부 공용 IP가 구해지게 되고, 방화벽/NAT/L4 장비를 거쳐서 호출이 되어 응답 시간이 늘어나게 됩니다.
3) 세 번째 문제는, dead-lock 문제에 빠질 수 있습니다. 이에 대해서는 지난번에도 한번 사례를 설명했었습니다.
제니퍼 닷넷 적용 사례 (2) - 웹 애플리케이션 hang의 원인을 알려주다.
; https://www.sysnet.pe.kr/2/0/1117
위의 글에서는 config.xml 파일 하나를 서비스하는 유형이라서 간단하게 AppPool을 별도로 생성하여 스레드 풀을 새롭게 할당함으로써 해결을 했는데요. System.Net.WebException 예외가 발생한 이번 글의 고객사 경우에는 문제가 좀 복잡해 질 수 있습니다. 왜냐하면, top/bottom.aspx 요청을 받는 그 웹 사이트는 그 외에도 많은 요청을 받아서 처리할 수 있는 구조였기 때문입니다.
가정을 해볼까요? 사용자가 갑자기 몰려서 L4로 묶인 호스트 중에서 "192.168.0.10" 서버가 동시에 1,200개의 요청을 받았다고 해보겠습니다. 그 서버는 maxWorkerThreads == 100 상태에서 쿼드 코어라면 총 400개의 동시 요청만을 처리할 수가 있습니다. 나머지 800개는 IIS가 요청 대기 큐에 쌓아두고 앞서의 처리가 끝나면 차례대로 처리를 할 텐데요. 설상 가상으로 DB까지 지연되면서 하나의 aspx 처리 시간이 5초가 걸리는 상황이 발생한다고 하면?
그런 와중에 "192.168.0.11" 서버로 top/bottom.aspx를 포함한 웹 페이지 요청이 발생했고 하필 그것이 "192.168.0.10" 서버로 전달되었다고 하면 어떤 일이 벌어질까요? 현재 10번 서버에 발생한 요청만 처리하는데 1,200 / 400 = 3 * 5초 == 15초가 소요되어 11번 서버로 요청을 보냈던 사용자는 11번 서버가 한가로운데도 불구하고 15초를 대기한 후에야 정상적인 aspx 웹 페이지를 볼 수 있게 됩니다.
결론적으로, 아무리 어떤 이유를 갖다댄다 해도... 하나의 aspx에서 top/bottom.aspx의 내용을 별도의 HTTP 요청으로 분리해서 구하는 것은 꽤나 불합리한 구조입니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]