ASP.NET이 성공한 이유 중 하나는 웹 개발자의 진입 장벽을 낮추는 것입니다. ASP.NET 코드를 작성하기 위해 컴퓨터 공학 박사 학위가 필요하지 않습니다. 제가 직장에서 만난 많은 ASP.NET 개발자들은 C# 또는 Visual Basic®을 작성하기 전에 독학으로 Microsoft® Excel® 스프레드시트를 작성하고 있었습니다. 이제 그들은 웹 애플리케이션을 작성하고 있으며 전반적으로 그들이 하고 있는 작업에 대해 칭찬을 받을 자격이 있습니다.
그러나 힘에는 책임이 따르기 때문에 숙련된 ASP.NET 개발자라도 실수를 할 수 있습니다. 수년간 ASP.NET 프로젝트에 대해 컨설팅하면서 특정 오류가 특히 반복적인 결함으로 이어질 가능성이 높다는 사실을 발견했습니다. 이러한 오류 중 일부는 성능에 영향을 미칠 수 있습니다. 다른 오류로 인해 확장성이 저하될 수 있습니다. 일부 버그로 인해 개발팀은 버그와 예상치 못한 동작을 추적하는 데 귀중한 시간을 낭비할 수도 있습니다.
ASP.NET 프로덕션 응용 프로그램을 출시하는 동안 문제를 일으킬 수 있는 10가지 함정과 이를 방지하는 방법은 다음과 같습니다. 모든 예제는 실제 회사에서 실제 웹 응용 프로그램을 구축한 경험에서 비롯되었으며, 어떤 경우에는 개발 프로세스 중에 ASP.NET 개발 팀이 직면한 몇 가지 문제를 설명하여 컨텍스트를 제공합니다.
LoadControl 및 출력 캐싱 사용자 컨트롤을 사용하지 않는 ASP.NET 응용 프로그램은 거의 없습니다. 마스터 페이지 이전에 개발자는 사용자 컨트롤을 사용하여 머리글 및 바닥글과 같은 일반적인 콘텐츠를 추출했습니다. ASP.NET 2.0에서도 사용자 컨트롤은 콘텐츠와 동작을 캡슐화하고 페이지 전체와 독립적으로 캐시 가능성을 제어할 수 있는 영역으로 페이지를 나누는 효율적인 방법을 제공합니다(특수한 형태의 출력 캐싱 프로세스). ).
사용자 컨트롤은 선언적으로 또는 강제로 로드될 수 있습니다. 강제 로드는 사용자 컨트롤을 인스턴스화하고 컨트롤 참조를 반환하는 Page.LoadControl에 의존합니다. 사용자 정의 컨트롤에 사용자 지정 형식의 멤버(예: 공용 속성)가 포함된 경우 참조를 캐스팅하고 코드에서 사용자 지정 멤버에 액세스할 수 있습니다. 그림 1의 사용자 컨트롤은 BackColor라는 속성을 구현합니다. 다음 코드는 사용자 컨트롤을 로드하고 BackColor에 값을 할당합니다.
protected void Page_Load(object sender, EventArgs e){// 사용자 컨트롤을 로드하고 페이지에 추가합니다. Control control = LoadControl("~/MyUserControl.ascx") ;PlaceHolder1 .Controls.Add(control);//배경색 설정 ((MyUserControl)control).BackColor = Color.Yellow;}
위 코드는 실제로는 매우 간단하지만, 부주의한 개발자가 빠지기를 기다리는 함정입니다. 결함을 찾을 수 있나요?
문제가 출력 캐싱과 관련이 있다고 추측했다면 정답입니다. 보시다시피 위의 코드 예제는 정상적으로 컴파일되고 실행되지만 MyUserControl.ascx에 다음 문(완벽하게 합법적임)을 추가하려고 하면 다음과 같습니다.
<%@ OutputCache Duration="5" VaryByParam="None" %>
그런 다음 다음에 페이지를 실행하면 InvalidCastException(아, 기쁘네요!)과 다음 오류 메시지가 표시됩니다.
"'System.Web.UI.PartialCachingControl' 유형의 개체를 'MyUserControl' 유형으로 캐스팅할 수 없습니다."
따라서 이 코드는 OutputCache 지시문 없이는 잘 실행되지만 OutputCache 지시문이 추가되면 실패합니다. ASP.NET은 이런 방식으로 동작해서는 안 됩니다. 페이지(및 컨트롤)는 출력 캐싱에 대해 독립적이어야 합니다. 그렇다면 이것은 무엇을 의미합니까?
문제는 사용자 컨트롤에 대해 출력 캐싱이 활성화되면 LoadControl이 더 이상 컨트롤 인스턴스에 대한 참조를 반환하지 않고 대신 PartialCachingControl 인스턴스에 대한 참조를 반환한다는 것입니다. 컨트롤의 출력은 캐시입니다. 따라서 개발자가 LoadControl을 호출하여 사용자 컨트롤을 동적으로 로드하고 컨트롤별 메서드 및 속성에 액세스하기 위해 컨트롤 참조를 변환하는 경우 코드가 있는지 여부에 관계없이 코드가 실행되도록 이를 수행하는 방법에 주의해야 합니다. OutputCache 지시어.
그림 2는 사용자 컨트롤을 동적으로 로드하고 반환된 컨트롤 참조를 변환하는 올바른 방법을 보여줍니다. 작동 방식에 대한 요약은 다음과 같습니다.
• ASCX 파일에 OutputCache 지시문이 누락된 경우 LoadControl은 MyUserControl 참조를 반환합니다. Page_Load는 참조를 MyUserControl로 변환하고 컨트롤의 BackColor 속성을 설정합니다.
• ASCX 파일에 OutputCache 지시문이 포함되어 있고 컨트롤의 출력이 캐시되지 않은 경우 LoadControl은 CachedControl 속성에 기본 MyUserControl에 대한 참조가 포함된 PartialCachingControl에 대한 참조를 반환합니다. Page_Load는 PartialCachingControl.CachedControl을 MyUserControl로 변환하고 컨트롤의 BackColor 속성을 설정합니다.
• ASCX 파일에 OutputCache 지시어가 포함되어 있고 컨트롤의 출력이 캐시된 경우 LoadControl은 CachedControl 속성이 비어 있는 PartialCachingControl에 대한 참조를 반환합니다. Page_Load는 더 이상 진행되지 않습니다. 컨트롤의 출력이 출력 캐시에서 나오므로 컨트롤의 BackColor 속성을 설정할 수 없습니다. 즉, 속성을 설정할 MyUserControl이 전혀 없습니다.
그림 2의 코드는 .ascx 파일에 OutputCache 지시문이 있는지 여부에 관계없이 실행됩니다. 조금 더 복잡해 보이지만 성가신 실수를 피할 수 있습니다. 단순하다고 해서 항상 유지 관리가 쉽다는 의미는 아닙니다.
맨 위로 이동 세션 및 출력 캐싱 출력 캐싱에 관해 ASP.NET 1.1과 ASP.NET 2.0 모두 Windows Server™ 2003 및 IIS 6.0을 실행하는 서버의 출력 캐시 페이지에 영향을 미치는 잠재적인 문제를 가지고 있습니다. 개인적으로 ASP.NET 프로덕션 서버에서 이 문제가 두 번 발생하는 것을 보았으며 두 번 모두 출력 버퍼링을 꺼서 문제가 해결되었습니다. 나중에 출력 캐싱을 비활성화하는 것보다 더 나은 해결책이 있다는 것을 알게 되었습니다. 이 문제가 처음 발생했을 때의 모습은 다음과 같습니다.
작은 ASP.NET 웹 영역에서 공용 전자 상거래 응용 프로그램을 실행하는 웹 사이트(여기서는 Contoso.com이라고 함)에서 우리 팀에 연락하여 "크로스 스레딩" 실수가 발생했다고 불평했습니다. Contoso.com 웹 사이트를 사용하는 고객은 입력한 데이터가 갑자기 손실되었지만 대신 다른 사용자와 관련된 데이터가 표시되는 경우가 많습니다. 약간의 분석 후에 우리는 크로스 스레딩에 대한 설명이 정확하지 않다는 것을 발견했습니다. "크로스 세션" 오류가 더 적절합니다. Contoso.com은 데이터를 세션 상태로 저장하고 어떤 이유로 사용자가 가끔 무작위로 다른 사용자의 세션에 연결하는 것으로 보입니다.
우리 팀 구성원 중 한 명이 쿠키 헤더를 포함하여 각 HTTP 요청 및 응답의 주요 요소를 기록하는 진단 도구를 작성했습니다. 그런 다음 Contoso.com의 웹 서버에 도구를 설치하고 며칠 동안 실행해 보았습니다. 결과는 매우 분명합니다. 대략 100,000개의 요청마다 ASP.NET은 완전히 새로운 세션에 세션 ID를 올바르게 할당하고 Set-Cookie 헤더에 세션 ID를 반환합니다. 그런 다음 요청이 이미 유효한 세션과 연결되어 있고 쿠키의 세션 ID가 올바르게 제출되었더라도 바로 인접한 다음 요청에서 동일한 세션 ID(즉, 동일한 Set-Cookie 헤더)를 반환합니다. 실제로 ASP.NET은 사용자를 자신의 세션에서 무작위로 전환하고 다른 세션에 연결합니다.
우리는 놀랐고 그 이유를 알아보기 시작했습니다. 먼저 Contoso.com의 소스 코드를 확인한 결과 다행스럽게도 문제가 없었습니다. 다음으로 문제가 웹 영역의 애플리케이션 호스트와 관련되지 않았는지 확인하기 위해 서버 하나만 실행되도록 두고 나머지는 모두 종료했습니다. 문제는 지속됩니다. 로그에 일치하는 Set-Cookie 헤더가 서로 다른 두 서버에서 나오지 않는다는 사실이 나와 있으므로 이는 놀라운 일이 아닙니다. ASP.NET은 실수로 중복 세션 ID를 생성하는데, 이는 .NET Framework RNGCryptoServiceProvider 클래스를 사용하여 이러한 ID를 생성하고 세션 ID가 동일한 ID가 두 번 생성되지 않도록(적어도 다음 번에는 생성되지 않도록) 충분히 길기 때문에 믿기지 않습니다. 수조년 안에 두 번 생성되지는 않습니다). 그 외에도 RNGCryptoServiceProvider가 실수로 반복되는 난수를 생성하더라도 ASP.NET이 왜 유효한 세션 ID를 고유하지 않은 새 ID로 대체하는지 설명하지 않습니다.
직감적으로 우리는 출력 캐싱을 살펴보기로 결정했습니다. OutputCacheModule이 HTTP 응답을 캐시할 때 Set-Cookie 헤더를 캐시하지 않도록 주의해야 합니다. 그렇지 않으면 새 세션 ID가 포함된 캐시된 응답이 캐시된 응답의 모든 수신자(및 캐시된 응답을 생성한 요청의 사용자)에 연결됩니다. 같은 세션에. 소스 코드를 확인하면 Contoso.com에는 두 페이지 모두에서 출력 캐싱이 활성화되어 있습니다. 출력 캐싱을 껐습니다. 그 결과 애플리케이션은 단일 세션 간 문제 없이 며칠 동안 실행되었습니다. 그 후 2년 넘게 오류 없이 잘 돌아갔다. 다른 애플리케이션과 다른 웹 서버 세트를 사용하는 다른 회사에서는 똑같은 문제가 사라지는 것을 보았습니다. Contoso.com에서와 마찬가지로 출력 캐시를 제거하면 문제가 해결됩니다.
Microsoft는 나중에 이 동작이 OutputCacheModule의 문제로 인해 발생함을 확인했습니다. (이 문서를 읽을 때 업데이트가 릴리스되었을 수 있습니다.) ASP.NET을 IIS 6.0과 함께 사용하고 커널 모드 캐싱이 활성화된 경우 OutputCacheModule이 통과한 캐시된 응답에서 Set-Cookie 헤더를 제거하지 못하는 경우가 있습니다. Http.sys에. 다음은 오류를 발생시키는 구체적인 이벤트 순서입니다.
• 최근에 사이트를 방문하지 않았으므로 해당 세션이 없는 사용자가 출력 캐싱이 활성화되어 있지만 출력을 현재 사용할 수 없는 페이지를 요청합니다. 캐시에.
• 요청은 사용자가 가장 최근에 생성한 세션에 액세스하는 코드를 실행하여 세션 ID 쿠키가 응답의 Set-Cookie 헤더에 반환되도록 합니다.
• OutputCacheModule은 Http.sys에 출력을 제공하지만 응답에서 Set-Cookie 헤더를 제거할 수는 없습니다.
• Http.sys는 후속 요청에 대해 캐시된 응답을 반환하여 실수로 다른 사용자를 세션에 연결합니다.
이야기의 교훈은 세션 상태와 커널 모드 출력 캐싱이 혼합되지 않는다는 것입니다. 출력 캐싱이 활성화된 페이지에서 세션 상태를 사용하고 응용 프로그램이 IIS 6.0에서 실행 중인 경우 커널 모드 출력 캐싱을 해제해야 합니다. 여전히 출력 캐싱의 이점을 누릴 수 있지만 커널 모드 출력 캐싱은 일반 출력 캐싱보다 훨씬 빠르기 때문에 캐싱이 효율적이지 않습니다. 이 문제에 대한 자세한 내용은 support.microsoft.com/kb/917072를 참조하세요.
페이지의 OutputCache 지시문에 VaryByParam="*" 특성을 포함하여 개별 페이지에 대한 커널 모드 출력 캐싱을 끌 수 있습니다. 하지만 이렇게 하면 메모리 요구 사항이 갑자기 증가할 수 있습니다. 보다 안전한 또 다른 접근 방식은 web.config에 다음 요소를 포함하여 전체 애플리케이션에 대한 커널 모드 캐싱을 끄는 것입니다.
<httpRuntime 활성화KernelOutputCache="false" />
또한 레지스트리 설정을 사용하여 커널 모드 출력 캐싱을 전역적으로 비활성화할 수 있습니다. 즉, 모든 서버에 대해 커널 모드 출력 캐싱을 비활성화할 수 있습니다. 자세한 내용은 support.microsoft.com/kb/820129를 참조하세요.
세션 문제에 대해 고객이 보고하는 것을 들을 때마다 나는 페이지에서 출력 캐싱을 사용하고 있는지 묻습니다. 출력 캐싱을 사용하고 호스트 OS가 Windows Server 2003인 경우 커널 모드 출력 캐싱을 비활성화하는 것이 좋습니다. 문제는 일반적으로 해결됩니다. 문제가 해결되지 않으면 코드에 버그가 존재하는 것입니다. 주의하세요!
맨 위로 돌아가기
양식 인증 티켓 수명 다음 코드의 문제를 식별할 수 있습니까?
FormsAuthentication.RedirectFromLoginPage(사용자 이름, true);
이 코드는 괜찮아 보일 수 있지만 응용 프로그램의 다른 코드가 이 문의 부정적인 영향을 상쇄하지 않는 한 ASP.NET 1.x 응용 프로그램에서 사용해서는 안 됩니다. 이유가 확실하지 않다면 계속 읽어보세요.
FormsAuthentication.RedirectFromLoginPage는 두 가지 작업을 수행합니다. 첫째, FormsAuthenticationModule이 사용자를 로그인 페이지로 리디렉션하면 FormsAuthentication.RedirectFromLoginPage는 사용자를 원래 요청한 페이지로 리디렉션합니다. 둘째, 사용자가 미리 정해진 기간 동안 인증 상태를 유지할 수 있도록 하는 인증 티켓(일반적으로 쿠키에 전달되고 ASP.NET 1.x에서는 항상 쿠키에 전달됨)을 발행합니다.
문제는 이 시기에 있다. ASP.NET 1.x에서 RedirectFromLoginPage에 false인 다른 매개 변수를 전달하면 기본적으로 30분 후에 만료되는 임시 인증 티켓이 발행됩니다. (web.config 요소의 Timeout 속성을 사용하여 시간 초과 기간을 변경할 수 있습니다.) 그러나 true의 다른 매개 변수를 전달하면 50년 동안 유효한 영구 인증 티켓이 발급됩니다. 누군가 해당 인증을 도용하면 문제가 발생합니다! 티켓이 있는 동안 피해자의 신원을 사용하여 웹사이트에 접속할 수 있습니다. 인증 티켓을 훔치는 방법에는 여러 가지가 있습니다(공용 무선 액세스 포인트에서 암호화되지 않은 트래픽 조사, 웹사이트 전반의 스크립팅, 피해자 컴퓨터에 대한 물리적 접근 권한 획득 등). 따라서 RedirectFromLoginPage에 true를 전달하는 것이 웹사이트를 비활성화하는 것보다 더 안전합니다. 별로 좋지 않습니다. 다행히 이 문제는 ASP.NET 2.0에서 해결되었습니다. RedirectFromLoginPage는 이제 동일한 방식으로 임시 및 영구 인증 티켓에 대해 web.config에 지정된 시간 초과를 허용합니다.
한 가지 해결 방법은 ASP.NET 1.x 응용 프로그램에서 RedirectFromLoginPage의 두 번째 매개 변수에 true를 전달하지 않는 것입니다. 그러나 로그인 페이지에는 임시 인증 쿠키가 아닌 영구 인증 쿠키를 받기 위해 사용자가 확인할 수 있는 "로그인 상태 유지" 상자가 있는 경우가 많기 때문에 이는 실용적이지 않습니다. 또 다른 해결책은 Global.asax(또는 원하는 경우 HTTP 모듈)의 코드 조각을 사용하여 영구 인증 티켓이 포함된 쿠키를 브라우저에 반환하기 전에 수정하는 것입니다.
그림 3에는 그러한 코드 조각 중 하나가 포함되어 있습니다. 이 코드 조각이 Global.asax에 있으면 쿠키가 24시간 후에 만료되도록 나가는 영구 양식 인증 쿠키의 Expires 속성을 수정합니다. "새 만료 날짜"라는 주석이 달린 줄을 수정하여 시간 초과를 원하는 날짜로 설정할 수 있습니다.
Application_EndRequest 메서드가 로컬 도우미 메서드(GetCookieFromResponse)를 호출하여 나가는 응답에 대한 인증 쿠키를 확인하는 것이 이상하다는 것을 알 수 있습니다. Helper 메서드는 존재하지 않는 쿠키를 확인하기 위해 HttpCookieCollection의 문자열 인덱스 생성기를 사용한 경우 가짜 쿠키가 응답에 추가되도록 하는 ASP.NET 1.1의 또 다른 버그에 대한 해결 방법입니다. GetCookieFromResponse로 정수 인덱스 생성기를 사용하면 문제가 해결됩니다.
맨 위로 이동 보기 상태: 조용한 성능 킬러 어떤 의미에서 보기 상태는 역대 최고입니다. 결국 보기 상태를 사용하면 페이지와 컨트롤이 포스트백 간에 상태를 유지할 수 있습니다. 따라서 단추를 클릭할 때 텍스트 상자의 텍스트가 사라지는 것을 방지하거나 기존 ASP에서처럼 포스트백 후에 데이터베이스를 다시 쿼리하고 DataGrid를 다시 바인딩하는 코드를 작성할 필요가 없습니다.
그러나 뷰 상태에는 단점이 있습니다. 뷰 상태가 너무 커지면 조용한 성능 저하가 됩니다. 텍스트 상자와 같은 일부 컨트롤은 보기 상태에 따라 결정을 내립니다. 다른 컨트롤(특히 DataGrid 및 GridView)은 표시되는 정보의 양에 따라 뷰 상태를 결정합니다. GridView에 200~300행의 데이터가 표시된다면 당황스러울 것입니다. ASP.NET 2.0 보기 상태는 ASP.NET 1.x 보기 상태 크기의 약 절반이지만 잘못된 GridView는 브라우저와 웹 서버 간 연결의 유효 대역폭을 50% 이상 쉽게 줄일 수 있습니다.
EnableViewState를 false로 설정하여 개별 컨트롤의 보기 상태를 끌 수 있지만 일부 컨트롤(특히 DataGrid)은 보기 상태를 사용할 수 없으면 일부 기능이 손실됩니다. 뷰 상태를 제어하는 더 나은 솔루션은 이를 서버에 유지하는 것입니다. ASP.NET 1.x에서는 페이지의 LoadPageStateFromPersistenceMedium 및 SavePageStateToPersistenceMedium 메서드를 재정의하고 원하는 방식으로 뷰 상태를 처리할 수 있습니다. 그림 4의 코드는 뷰 상태가 숨겨진 필드에 유지되는 것을 방지하고 대신 세션 상태에 유지하는 재정의를 보여줍니다. 세션 상태에 뷰 상태를 저장하는 것은 기본 세션 상태 프로세스 모델과 함께 사용될 때(즉, 세션 상태가 메모리의 ASP.NET 작업자 프로세스에 저장될 때) 특히 효과적입니다. 반면에 세션 상태가 데이터베이스에 저장되어 있는 경우 세션 상태에 뷰 상태를 유지하면 성능이 향상되는지 저하되는지 여부는 테스트로만 확인할 수 있습니다.
ASP.NET 2.0에서도 동일한 접근 방식이 사용되지만 ASP.NET 2.0은 세션 상태에서 뷰 상태를 유지하는 더 쉬운 방법을 제공합니다. 먼저 GetStatePersister 메서드가 .NET Framework SessionPageStatePersister 클래스의 인스턴스를 반환하는 사용자 지정 페이지 어댑터를 정의합니다.
public class SessionPageStateAdapter :System.Web.UI.Adapters.PageAdapter{public override PageStatePersister GetStatePersister () {return new SessionPageStatePersister(this.Page ) ; }}
그런 다음 다음과 같이 App.browsers 파일을 애플리케이션의 App_Browsers 폴더에 배치하여 사용자 정의 페이지 어댑터를 기본 페이지 어댑터로 등록합니다.
<browsers><browser refID="Default"><controlAdapters><adapter controlType=" System.Web. UI.Page"adapterType="SessionPageStateAdapter" /></controlAdapters></browser></browsers>
(.browsers 확장자가 있는 한 원하는 대로 파일 이름을 지정할 수 있습니다.) 그런 다음 ASP.NET은 페이지 어댑터를 로드하고 반환된 SessionPageStatePersister를 사용하여 보기 상태를 포함한 모든 페이지 상태를 보존합니다.
사용자 정의 페이지 어댑터를 사용할 때의 한 가지 단점은 애플리케이션의 모든 페이지에 전역적으로 적용된다는 것입니다. 세션 상태에서 일부 페이지의 보기 상태를 유지하고 다른 페이지는 유지하지 않으려면 그림 4에 표시된 방법을 사용하십시오. 또한 사용자가 동일한 세션에서 여러 브라우저 창을 만드는 경우 이 방법을 사용하면 문제가 발생할 수 있습니다.
맨 위로 돌아가기
SQL Server 세션 상태: 또 다른 성능 킬러
ASP.NET을 사용하면 세션 상태를 데이터베이스에 쉽게 저장할 수 있습니다. web.config에서 스위치를 전환하기만 하면 세션 상태가 백엔드 데이터베이스로 쉽게 이동됩니다. 이는 웹 영역의 모든 서버가 세션 상태의 공통 저장소를 공유할 수 있도록 허용하므로 웹 영역에서 실행되는 애플리케이션에 중요한 기능입니다. 추가된 데이터베이스 활동으로 인해 개별 요청의 성능이 저하되지만 확장성이 향상되어 성능 저하가 보상됩니다.
이 모든 것이 좋아 보이지만 몇 가지 사항을 고려하면 상황이 달라집니다.
• 세션 상태를 사용하는 응용 프로그램에서도 대부분의 페이지는 세션 상태를 사용하지 않습니다.
• 기본적으로 ASP.NET 세션 상태 관리자는 요청된 페이지가 세션 상태를 사용하는지 여부에 관계없이 각 요청의 세션 데이터 저장소에 대해 두 가지 액세스(읽기 액세스 한 번, 쓰기 액세스 한 번)를 수행합니다.
즉, SQL Server™ 세션 상태 옵션을 사용하면 모든 요청에 대해 비용(2개의 데이터베이스 액세스)을 지불하게 됩니다. 심지어 세션 상태와 관련이 없는 페이지에 대한 요청에도 마찬가지입니다. 이는 전체 웹사이트의 처리량에 직접적인 부정적인 영향을 미칩니다.
그림 5 불필요한 세션 상태 데이터베이스 액세스 제거
그렇다면 어떻게 해야 할까요? 간단합니다. 세션 상태를 사용하지 않는 페이지에서 세션 상태를 비활성화하면 됩니다. 이는 항상 좋은 생각이지만 세션 상태가 데이터베이스에 저장되어 있는 경우 특히 중요합니다. 그림 5는 세션 상태를 비활성화하는 방법을 보여줍니다. 페이지에서 세션 상태를 전혀 사용하지 않는 경우 다음과 같이 페이지 지시문에 EnableSessionState="false"를 포함합니다.
<%@ Page EnableSessionState="false" ... %>
이 지시문은 세션 상태 관리자가 모든 요청에서 세션 상태 데이터베이스를 읽고 쓰는 것을 방지합니다. 페이지가 세션 상태에서 데이터를 읽지만 데이터를 쓰지 않는 경우(즉, 사용자 세션의 내용을 수정하지 않는 경우) 다음과 같이 EnableSessionState를 ReadOnly로 설정합니다.
<%@ Page EnableSessionState="ReadOnly" ... %>
마지막으로 페이지에 세션 상태에 대한 읽기/쓰기 액세스가 필요한 경우 EnableSessionState 속성을 생략하거나 true로 설정합니다.
<%@ Page EnableSessionState="true" ... %>
이러한 방식으로 세션 상태를 제어하면 ASP.NET이 실제로 필요할 때만 세션 상태 데이터베이스에 액세스하도록 할 수 있습니다. 불필요한 데이터베이스 액세스를 제거하는 것은 고성능 애플리케이션을 구축하는 첫 번째 단계입니다.
그런데 EnableSessionState 속성은 공개입니다. 이 속성은 ASP.NET 1.0부터 문서화되었지만 여전히 개발자가 이 속성을 활용하는 경우는 거의 없습니다. 아마도 메모리의 기본 세션 상태 모델에는 그다지 중요하지 않기 때문일 수 있습니다. 그러나 SQL Server 모델에서는 이것이 중요합니다.
맨 위로 이동 캐시되지 않은 역할 다음 문은 ASP.NET 2.0 응용 프로그램의 web.config 파일과 ASP.NET 2.0 역할 관리자를 소개하는 예제에 자주 나타납니다.
<roleManager 활성화="true" />
그러나 위에서 본 것처럼 이 진술은 성능에 상당히 부정적인 영향을 미칩니다. 이유를 아시나요?
기본적으로 ASP.NET 2.0 역할 관리자는 역할 데이터를 캐시하지 않습니다. 대신 사용자가 속한 역할(있는 경우)을 결정해야 할 때마다 역할 데이터 저장소를 참조합니다. 즉, 사용자가 인증되면 역할 데이터를 활용하는 모든 페이지(예: 보안 클리핑 설정이 활성화된 사이트맵을 사용하는 페이지, web.config의 역할 기반 URL 지시문을 사용하여 액세스가 제한된 페이지)는 역할을 유발합니다. 관리자는 역할 데이터 저장소를 쿼리합니다. 역할이 데이터베이스에 저장되어 있으면 각 요청에 대해 여러 데이터베이스에 액세스할 필요가 없습니다. 해결책은 쿠키에 역할 데이터를 캐시하도록 역할 관리자를 구성하는 것입니다.
<roleManager 활성화="true" 캐시RolesInCookie="true" />
다른 <roleManager> 속성을 사용하여 역할 쿠키의 특성을 제어할 수 있습니다. 예를 들어 쿠키가 유효한 상태로 유지되어야 하는 기간(따라서 역할 관리자가 역할 데이터베이스에 반환하는 빈도)을 제어할 수 있습니다. 역할 쿠키는 기본적으로 서명 및 암호화되므로 보안 위험이 0은 아니지만 완화됩니다.
맨 위로구성 파일 속성 직렬화
ASP.NET 2.0 프로필 서비스는 개인 설정 및 언어 기본 설정과 같은 사용자별 상태를 유지 관리하는 문제에 대해 미리 만들어진 솔루션을 제공합니다. 프로필 서비스를 사용하려면 개별 사용자를 대신하여 보존하려는 특성이 포함된 XML 프로필을 정의합니다. 그런 다음 ASP.NET은 동일한 속성을 포함하는 클래스를 컴파일하고 페이지에 추가된 구성 파일 속성을 통해 클래스 인스턴스에 대한 강력한 형식의 액세스를 제공합니다.
프로필 유연성은 매우 뛰어나 사용자 정의 데이터 유형을 프로필 속성으로 사용할 수도 있습니다. 그러나 개발자들이 실수를 저지르는 것을 개인적으로 본 문제가 있습니다. 그림 6에는 Posts라는 간단한 클래스와 Posts를 프로필 속성으로 사용하는 프로필 정의가 포함되어 있습니다. 그러나 이 클래스와 구성 파일은 런타임 시 예기치 않은 동작을 발생시킵니다. 이유를 알 수 있나요?
문제는 Posts에 클래스 인스턴스를 완전히 고정하고 다시 고정하기 위해 직렬화 및 역직렬화해야 하는 _count라는 비공개 필드가 포함되어 있다는 것입니다. 그러나 _count는 비공개이므로 직렬화 및 역직렬화되지 않으며 ASP.NET 프로필 관리자는 기본적으로 XML 직렬화를 사용하여 사용자 지정 형식을 직렬화 및 역직렬화합니다. XML 직렬 변환기는 비공개 멤버를 무시합니다. 따라서 Post의 인스턴스는 직렬화 및 역직렬화되지만 클래스 인스턴스가 역직렬화될 때마다 _count는 0으로 재설정됩니다.
한 가지 해결책은 _count를 비공개 필드 대신 공개 필드로 만드는 것입니다. 또 다른 해결책은 _count를 공개 읽기/쓰기 속성으로 캡슐화하는 것입니다. 가장 좋은 해결 방법은 게시물을 직렬화 가능으로 표시하고(SerializedAttribute 사용) .NET Framework 이진 직렬 변환기를 사용하여 클래스 인스턴스를 직렬화 및 역직렬화하도록 프로필 관리자를 구성하는 것입니다. 이 솔루션은 클래스 자체의 디자인을 유지합니다. XML 직렬 변환기와 달리 이진 직렬 변환기는 액세스 가능 여부에 관계없이 필드를 직렬화합니다. 그림 7은 Posts 클래스의 수정된 버전을 보여주고 변경된 프로필 정의를 강조 표시합니다.
명심해야 할 한 가지는 사용자 정의 데이터 유형을 프로필 속성으로 사용하고 해당 데이터 유형에 해당 유형의 인스턴스를 완전히 직렬화하기 위해 직렬화해야 하는 비공개 데이터 멤버가 있는 경우 serializeAs="를 사용한다는 것입니다. 속성 선언 속성의 Binary"를 선택하고 유형 자체가 직렬화 가능한지 확인하세요. 그렇지 않으면 전체 직렬화가 발생하지 않으며 프로필이 작동하지 않는 이유를 확인하는 데 시간을 낭비하게 됩니다.
맨 위로 이동 스레드 풀 포화 데이터베이스 쿼리를 실행하고 쿼리 결과가 반환될 때까지 15초 이상 기다릴 때 표시되는 실제 ASP.NET 페이지 수에 종종 매우 놀랐습니다. (또한 쿼리 결과를 보기 전에 15분을 기다렸습니다.) 때때로 지연은 많은 양의 데이터가 반환되기 때문에 피할 수 없는 결과입니다. 다른 경우에는 잘못된 데이터베이스 설계로 인해 지연이 발생합니다. 그러나 이유에 관계없이 긴 데이터베이스 쿼리나 모든 유형의 긴 I/O 작업으로 인해 ASP.NET 응용 프로그램의 처리량이 감소합니다.
이 문제에 대해서는 이전에 자세히 설명했으므로 여기서는 너무 자세히 설명하지 않겠습니다. ASP.NET은 제한된 스레드 풀에 의존하여 요청을 처리한다고만 말하면 충분합니다. 모든 스레드가 데이터베이스 쿼리, 웹 서비스 호출 또는 기타 I/O 작업이 완료되기를 기다리는 동안 점유된 경우 작업이 완료되면 해제됩니다. 스레드가 실행되기 전에 다른 요청이 대기열에 추가되어 대기해야 합니다. 요청이 대기열에 추가되면 성능이 크게 저하됩니다. 큐가 가득 차면 ASP.NET에서는 HTTP 503 오류로 인해 후속 요청이 실패하게 됩니다. 이는 프로덕션 웹 서버의 프로덕션 애플리케이션에서 보고 싶은 상황이 아닙니다.
해결책은 ASP.NET 2.0의 가장 훌륭하지만 잘 알려지지 않은 기능 중 하나인 비동기 페이지입니다. 비동기 페이지에 대한 요청은 스레드에서 시작되지만 I/O 작업이 시작되면 해당 스레드와 ASP.NET의 IAsyncResult 인터페이스로 반환됩니다. 작업이 완료되면 요청은 IAsyncResult를 통해 ASP.NET에 알리고 ASP.NET은 풀에서 다른 스레드를 가져와 요청 처리를 완료합니다. I/O 작업이 발생할 때 스레드 풀 스레드가 점유되지 않는다는 점은 주목할 가치가 있습니다. 이렇게 하면 다른 페이지(오랜 I/O 작업을 수행하지 않는 페이지)에 대한 요청이 대기열에서 대기하는 것을 방지하여 처리량을 크게 향상시킬 수 있습니다.
MSDN® Magazine 2005년 10월호에서 비동기 페이지에 대한 모든 내용을 읽을 수 있습니다. 머신 바운드가 아닌 I/O 바운드이고 실행하는 데 오랜 시간이 걸리는 페이지는 비동기 페이지가 될 가능성이 높습니다.
개발자에게 비동기 페이지에 대해 설명하면 "좋습니다. 하지만 내 애플리케이션에는 필요하지 않습니다."라고 대답하는 경우가 많습니다.
웹 서비스대기
중인 요청 및 평균 대기 시간에 대한 통계를 확인하기 위해 ASP.NET 성능 카운터를 확인하셨습니까? 지금까지 응용 프로그램이 제대로 실행되고 있더라도 클라이언트 크기가 커지면 로드가 증가할 수 있습니다.
실제 ASP.NET 애플리케이션에는 비동기 페이지가 필요합니다. 이것을 기억해주세요!
맨 위로 이동 가장 및 ACL 인증 다음은 간단한 구성 지시문이지만 web.config에서 볼 때마다 눈이 번쩍 뜨이는 내용입니다.
<identity impersonate="true" />
이 지시문을 사용하면 ASP.NET 응용 프로그램에서 클라이언트측 가장을 사용할 수 있습니다. 클라이언트를 나타내는 액세스 토큰을 요청을 처리하는 스레드에 연결하여 운영 체제에서 수행하는 보안 검사가 작업자 프로세스 ID가 아닌 클라이언트 ID에 대해 이루어지도록 합니다. ASP.NET 응용 프로그램에서는 모의 작업이 필요한 경우가 거의 없습니다. 제 경험에 따르면 개발자는 잘못된 이유로 모의 작업을 사용하는 경우가 많습니다. 이유는 다음과 같습니다.
개발자는 파일 시스템 권한을 사용하여 페이지에 대한 액세스를 제한할 수 있도록 ASP.NET 응용 프로그램에서 가장을 활성화하는 경우가 많습니다. Bob에게 Salaries.aspx를 볼 수 있는 권한이 없는 경우 개발자는 Bob 읽기 권한을 거부하도록 ACL(액세스 제어 목록)을 설정하여 Bob이 Salaries.aspx를 볼 수 없도록 가장을 활성화합니다. 그러나 다음과 같은 숨겨진 위험이 있습니다. ACL 인증에는 가장이 필요하지 않습니다. ASP.NET 응용 프로그램에서 Windows 인증을 활성화하면 ASP.NET은 요청된 각 .aspx 페이지에 대해 자동으로 ACL을 확인하고 파일을 읽을 수 있는 권한이 없는 호출자의 요청을 거부합니다. 시뮬레이션이 비활성화된 경우에도 여전히 이와 같이 작동합니다.
때로는 시뮬레이션을 정당화할 필요가 있습니다. 하지만 일반적으로 좋은 디자인으로 이를 피할 수 있습니다. 예를 들어 Salaries.aspx가 관리자만 알고 있는 급여 정보를 데이터베이스에 쿼리한다고 가정해 보겠습니다. 가장을 사용하면 데이터베이스 권한을 사용하여 비관리 직원이 급여 데이터를 쿼리하는 기능을 거부할 수 있습니다. 또는 관리자가 아닌 사람이 읽기 액세스 권한을 갖지 못하도록 Salaries.aspx에 대한 ACL을 설정하여 가장을 무시하고 급여 데이터에 대한 액세스를 제한할 수 있습니다. 후자의 접근 방식은 조롱을 완전히 방지하므로 더 나은 성능을 제공합니다. 또한 불필요한 데이터베이스 액세스를 제거합니다. 보안상의 이유로 데이터베이스 쿼리가 거부되는 이유는 무엇입니까?
그런데 저는 무제한 메모리 사용으로 인해 주기적으로 다시 시작되는 레거시 ASP 응용 프로그램의 문제를 해결하는 데 도움을 준 적이 있습니다. 경험이 없는 개발자가 쿼리되는 테이블에 크고 많은 이미지가 포함되어 있다는 점을 고려하지 않고 대상 SELECT 문을 SELECT *로 변환했습니다. 감지되지 않은 메모리 누수로 인해 문제가 더욱 악화됩니다. (내 관리 코드 영역!) 수년 동안 잘 작동하던 응용 프로그램이 갑자기 작동을 멈췄습니다. 1~2킬로바이트의 데이터를 반환하던 SELECT 문이 이제 몇 메가바이트를 반환하게 되었기 때문입니다. 여기에 부적절한 버전 관리 문제까지 더해지면 개발팀의 생활은 "과잉"되어야 합니다. 그리고 "과잉"이란 여러분이 게임을 하는 동안 아이들이 짜증나는 게임을 하는 것을 지켜보아야 하는 것과 같습니다. 지루한 축구 경기.
이론적으로 관리 코드로만 구성된 ASP.NET 응용 프로그램에서는 전통적인 메모리 누수가 발생할 수 없습니다. 그러나 메모리 사용량이 부족하면 가비지 수집이 더 자주 발생하여 성능에 영향을 줄 수 있습니다. ASP.NET 애플리케이션에서도 SELECT *를 주의하세요!
위로 전적으로 의존하지 마세요. 데이터베이스의 구성 파일을 설정하세요!
컨설턴트로서 저는 애플리케이션이 예상대로 작동하지 않는 이유에 대한 질문을 자주 받습니다. 최근 누군가가 우리 팀에게 ASP.NET 응용 프로그램이 문서를 요청하는 데 필요한 처리량(초당 요청 수)의 약 1/100만 완료하는 이유를 물었습니다. 이전에 우리가 발견한 문제는 제대로 작동하지 않는 웹 애플리케이션에서 본 문제에만 해당되며, 우리 모두가 진지하게 받아들여야 할 교훈입니다.
우리는 SQL Server Profiler를 실행하고 이 애플리케이션과 백엔드 데이터베이스 간의 상호 작용을 모니터링합니다. 더 극단적인 경우에는 단 한 번의 버튼 클릭만으로 데이터베이스에서 1,500개 이상의 오류가 발생했습니다. 그런 식으로 고성능 응용 프로그램을 구축 할 수 없습니다. 좋은 아키텍처는 항상 좋은 데이터베이스 디자인으로 시작합니다. 코드가 아무리 효율적이든 코드가 잘못 쓰여진 데이터베이스에 의해 무게가 떨어지면 작동하지 않습니다.
불량한 데이터 액세스 아키텍처는 일반적으로 다음 중 하나 이상에서 발생합니다.
• 데이터베이스 설계 불량 (일반적으로 데이터베이스 관리자가 아닌 개발자가 설계).
• Windows Forms Applications 및 기타 풍부한 클라이언트에 적합하지만 일반적으로 웹 애플리케이션에는 이상적이지 않습니다.
• 비교적 간단한 작업을 수행하기 위해 많은 CPU주기를 프로그래밍하고 많은 CPU 사이클을 소비하는 DAL (Data Access Layer)이 잘못 설계된 데이터 액세스 계층 (DAL).
문제를 치료하기 전에 문제를 식별해야합니다. 데이터 액세스 문제를 식별하는 방법은 SQL Server Profiler 또는 동등한 도구를 실행하여 무대 뒤에서 무슨 일이 일어나고 있는지 확인하는 것입니다. 응용 프로그램과 데이터베이스 간의 통신을 확인한 후 성능 튜닝이 완료됩니다. 시도해보십시오. 당신은 당신이 찾은 것에 놀랄 것입니다.
이제 최상위 결론으로 돌아가서 ASP.NET 프로덕션 응용 프로그램을 구축 할 때 발생할 수있는 몇 가지 문제와 해당 솔루션을 알 수 있습니다. 다음 단계는 자신의 코드를 자세히 살펴보고 여기에 설명한 몇 가지 문제를 피하는 것입니다. ASP.NET은 웹 개발자의 진입 장벽을 낮출 수 있지만 응용 프로그램은 유연하고 안정적이며 효율적이어야 할 모든 이유가 있습니다. 초보자 실수를 피하기 위해 이것을 신중하게 고려하십시오.
그림 8 은이 기사에 설명 된 함정을 피하는 데 사용할 수있는 짧은 체크리스트를 제공합니다. 유사한 보안 결함 점검표를 만들 수 있습니다. 예를 들면 다음과 같습니다.
• 민감한 데이터가 포함 된 구성 섹션을 암호화 했습니까?
• 데이터베이스 작업에 사용 된 입력을 확인하고 검증하고 있으며 HTML 인코딩 입력을 출력으로 사용하고 있습니까?
• 가상 디렉토리에 보호되지 않은 확장 장치가있는 파일이 포함되어 있습니까?
이러한 질문은 웹 사이트의 무결성, 호스팅 서버 및 그들이 의존하는 백엔드 리소스를 소중히 여기는 경우 중요합니다.
Jeff ProSise는 MSDN Magazine의 편집자이며 Microsoft .NET (Microsoft Press, 2002) 프로그래밍을 포함한 여러 권의 책의 저자입니다. 또한 소프트웨어 컨설팅 및 교육 회사 인 Wintellect의 공동 설립자이기도합니다.
2006 년 7 월 MSDN Magazine에서.