엔터프라이즈 수준의 ASP.NET 웹 애플리케이션과 IIS(Internet Information Services)를 운영할 때는 성능과 보안을 모두 극대화할 수 있도록 세밀한 설정이 필요합니다.
이 가이드는 최신 IIS 10 및 ASP.NET(Core 포함) 환경을 대상으로 성능 최적화, 보안 설정, 애플리케이션 풀 관리, 로깅 및 모니터링, 문제 해결의 다섯 가지 영역에서 고급 설정 방법과 예제를 제공합니다.
각 섹션별로 단계별 가이드, 설정 파일 예시, 코드 샘플, 그리고 최신 모범 사례를 포함하였으며, 엔터프라이즈 환경에서의 안정성과 확장성을 고려한 권장 사항들을 제시합니다.
1. 성능 최적화
엔터프라이즈 웹 애플리케이션에서는 짧은 응답 시간과 높은 처리량이 중요합니다.
IIS와 ASP.NET의 성능을 높이기 위해 캐싱, 압축, 로드 밸런싱, 프로세스 튜닝 등의 기법을 종합적으로 활용해야 합니다.
아래에서는 각 전략별 최적화 방법을 설명합니다.
1.1 캐싱 전략 (Output Caching 및 ARR 캐싱)
**출력 캐싱(Output Caching)**을 활용하면 동적 페이지의 응답을 메모리에 저장하여 이후 요청에 대한 응답 속도를 크게 높일 수 있습니다.
예를 들어, 데이터베이스 쿼리 결과를 매번 새로 가져오는 동적 페이지의 경우, 출력 캐싱을 통해 최근 결과를 일정 시간동안 재사용함으로써 불필요한 처리 부담을 줄일 수 있습니다.
IIS는 기본적으로 정적 콘텐츠(HTML, 이미지, CSS 등)는 자동으로 캐싱하며 파일이 변경되면 캐시를 무효화합니다.
반면, 반정적(semi-dynamic) 콘텐츠는 출력 캐싱을 통해 처리하는 것이 효과적입니다.
다음은 Web.config를 통한 출력 캐싱 설정 예시입니다 (특정 페이지 ShowStockPrice.asp의 쿼리스트링별로 1초간 캐싱):
<configuration>
<location path="ShowStockPrice.asp">
<system.webServer>
<caching>
<profiles>
<add extension=".asp" policy="CacheForTimePeriod" varyByQueryString="*"
duration="00:00:01" location="Any" />
</profiles>
</caching>
</system.webServer>
</location>
</configuration>
위 설정은 ShowStockPrice.asp의 출력물을 쿼리 문자열별로 구분하여 1초간 캐싱합니다. policy 속성을 KernelCachePolicy로 변경하면 커널 모드 캐싱을 활용하여 성능을 더욱 향상시킬 수 있습니다.
커널 모드 캐시는 HTTP.sys 드라이버 레벨에서 응답을 캐싱하여 사용자 모드까지 요청을 보내지 않고도 응답을 반환하므로 CPU 부하를 크게 줄이고 지연 시간을 단축합니다.
다만, 인증이 필요한 콘텐츠에는 커널 모드 캐시가 동작하지 않으므로(User-mode 캐시로 폴백) 사용자 인증 페이지 등은 출력 캐싱 적용 대상에서 제외해야 합니다.
출력 캐싱은 IIS Manager의 Output Caching 기능을 통해 GUI로도 설정할 수 있습니다.
Output Caching 설정에서 특정 파일 확장자별 캐싱 규칙을 추가하고, 사용자 모드/커널 모드 캐싱 여부를 선택할 수 있습니다.
아래 그림은 IIS 관리자에서 Output Caching 규칙 추가 대화상자를 보여줍니다.
여기서 응답 캐싱 대상 확장자, 캐싱 방식(사용자 모드 또는 커널 모드), 변경 모니터링 방식 등을 설정할 수 있습니다 (Using IIS 7 Output Caching Capabilities | Some thoughts on web 2.0 development).
출력 캐싱 외에도 **Application Request Routing (ARR)**을 캐시 용도로 활용할 수 있습니다. ARR은 IIS 확장 모듈로서 리버스 프록시 및 로드 밸런서 역할을 하며, 백엔드 서버로 요청을 전달하기 전에 디스크 캐싱을 수행할 수 있습니다.
ARR의 디스크 캐시는 주로 정적 콘텐츠나 변경이 적은 동적 콘텐츠를 대상으로 하며, 여러 서버로 구성된 팜 환경에서 공통 캐시로 활용되어 동일한 요청을 백엔드가 반복 처리하지 않도록 합니다.
예를 들어, ARR을 프록시 서버로 설정하고 서버 팜으로 두 대 이상의 앱 서버를 구성하면, ARR이 공유 캐시 역할을 하여 자주 요청되는 자원을 디스크에 저장하고, 이후 요청 시 백엔드에 전달하지 않고 바로 응답할 수 있습니다.
이러한 방식은 여러 웹 서버 간에 캐시를 통합하여 효율을 높이고 백엔드 부하를 감소시킵니다. ARR 캐싱을 사용하려면 IIS Manager의 Application Request Routing Cache 설정에서 디스크 캐시를 활성화하고 캐시 크기, 경로 등을 지정하면 됩니다 (예: “Enable disk cache” 옵션 및 용량 설정).
캐시 전략 요약: 응답 속도를 높이기 위해 적절한 출력 캐싱을 구현하고, 필요에 따라 ARR의 프록시 캐싱을 도입합니다.
개인화된 페이지나 자주 변경되는 데이터는 캐싱을 피하고, 변경 주기가 상대적으로 긴 동적 콘텐츠는 출력 캐싱으로 성능을 향상시킵니다.
캐싱 활성화 후에는 성능 모니터(PerfMon)의 “Web Service Cache” 카운터나 netsh http show cachestate 명령을 통해 캐시 적중률과 동작을 모니터링하는 것이 좋습니다.
1.2 압축 설정 (gzip, Brotli)
HTTP 압축은 네트워크 대역폭을 절약하고 응답 전송 시간을 단축하는 핵심 기술입니다.
IIS는 기본적으로 정적 파일 gzip 압축을 지원하며, 동적 콘텐츠 압축을 옵션으로 제공합니다. 최신 IIS 10 환경에서는 Brotli 압축까지 지원하여 압축 효율을 더욱 높일 수 있습니다 (IIS Compression Overview | Microsoft Learn) (IIS Compression Overview | Microsoft Learn).
Brotli는 gzip 대비 더 높은 압축률로 클라이언트에 전송되는 데이터 크기를 줄여주므로, 특히 정적 파일(예: JavaScript, CSS, WASM 등)을 전송할 때 초기 로딩 속도 향상에 유리합니다 (IIS Compression Overview | Microsoft Learn).
IIS에서 압축을 사용하려면 정적 콘텐츠 압축과 동적 콘텐츠 압축 기능이 설치 및 활성화되어야 합니다.
Windows Server의 역할 서비스에서 해당 기능을 추가하거나, Windows 10/11의 Windows 기능 켜기/끄기에서 “정적 콘텐츠 압축”, “동적 콘텐츠 압축”을 선택하여 설치합니다 (HTTP Compression | Microsoft Learn) (HTTP Compression | Microsoft Learn).
설치 후 IIS 관리자에서 서버 또는 사이트 단위로 Compression(압축) 설정을 열고, Enable static content compression과 Enable dynamic content compression 옵션을 체크합니다. 이렇게 하면 IIS 기본 gzip/Deflate 압축이 동작하여 텍스트 응답에 Content-Encoding: gzip 헤더를 추가하게 됩니다.
Brotli 압축을 사용하려면 IIS Compression 확장(iisbrotli.dll, iiszlib.dll)을 설치해야 합니다. Microsoft가 제공하는 IIS Compression 모듈을 설치하면, IIS의 압축 스키마에 **"br" (Brotli)**가 추가되고 기존 gzip 구현이 개선된 zlib 기반 모듈로 대체됩니다 (IIS Compression Overview | Microsoft Learn) (IIS Compression Overview | Microsoft Learn).
모듈 설치 후 %SystemRoot%\System32\inetsrv\config\applicationHost.config 파일의 <httpCompression> 섹션에 Brotli 및 개선된 gzip 스키마가 등록되며, 주요 MIME 타입에 대한 정적/동적 압축 설정이 활성화됩니다 (IIS Compression Overview | Microsoft Learn) (IIS Compression Overview | Microsoft Learn).
아래는 해당 설정의 예시입니다.
<system.webServer>
<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files">
<scheme name="br" dll="%ProgramFiles%\IIS\IIS Compression\iisbrotli.dll" />
<scheme name="gzip" dll="%ProgramFiles%\IIS\IIS Compression\iiszlib.dll" />
<dynamicTypes>
<add mimeType="text/*" enabled="true" />
<add mimeType="application/json" enabled="true" />
<add mimeType="*/*" enabled="false" />
</dynamicTypes>
<staticTypes>
<add mimeType="text/*" enabled="true" />
<add mimeType="application/javascript" enabled="true" />
<add mimeType="*/*" enabled="false" />
</staticTypes>
</httpCompression>
</system.webServer>
위 설정은 Brotli(name="br")와 Zlib기반 gzip(name="gzip") 스키마를 등록하고, 텍스트 및 자바스크립트 MIME 유형에 대해 동적/정적 압축을 활성화하는 예시입니다 (IIS Compression Overview | Microsoft Learn) (IIS Compression Overview | Microsoft Learn).
압축을 설정한 후에는 클라이언트의 요청 헤더(Accept-Encoding)를 확인하여 gzip 또는 br을 지원하는지에 따라 IIS가 자동으로 해당 방식으로 압축합니다.
압축 수준을 조절해야 한다면 DynamicCompressionLevel이나 StaticCompressionLevel 속성을 applicationHost.config의 <urlCompression>에서 조정할 수 있습니다 (0~10 사이 값, 기본 0=빠름, 10=최대압축).
주의: 압축은 CPU 사용량과의 트레이드오프입니다 (Using IIS Compression | Microsoft Learn). 압축률을 높이면 CPU 부하가 증가하므로, CPU 여유가 적은 서버에서는 동적 압축 대상 MIME 타입을 최소화하고 정적 파일은 배포 시 미리 압축하여 두는 것도 고려하십시오.
또한, 이미지나 PDF와 같이 이미 압축된 형식의 파일은 압축해도 효과가 없으므로 이러한 MIME 타입은 압축 대상에서 제외합니다.
1.3 로드 밸런싱 및 확장성 전략
엔터프라이즈 애플리케이션은 사용자 증가에 따른 확장성이 필수입니다.
IIS는 단일 서버 성능이 한계에 다다를 경우 **수평 확장(Scale-Out)**을 통해 부하를 분산시키는 로드 밸런싱을 지원합니다.
대표적인 방법은 **Application Request Routing (ARR)**을 이용해 여러 웹 서버로 구성된 **웹 팜(Web Farm)**을 만드는 것입니다.
ARR은 URL Rewrite 모듈과 결합되어 **역방향 프록시(reverse proxy)**로 동작하며, 클라이언트의 HTTP 요청을 설정된 로드 밸런싱 알고리즘에 따라 여러 백엔드 콘텐츠 서버로 분배합니다 (HTTP Load Balancing using Application Request Routing | Microsoft Learn).
아래 그림은 ARR을 이용해 두 대의 애플리케이션 서버로 부하를 분산하는 구조를 보여줍니다 (HTTP Load Balancing using Application Request Routing | Microsoft Learn).

ARR 서버가 프록시로 앞단에 위치하고, 그 뒤에 다수의 Application Server로 구성된 서버 팜이 요청을 처리합니다.
로드 밸런싱을 구현할 때 세션 관리에 유의해야 합니다.
만약 애플리케이션이 사용자 세션을 In-Proc 메모리에 저장한다면, 사용자가 매 요청마다 다른 서버로 분산될 경우 세션 유지가 어렵습니다.
이를 해결하기 위해 ARR은 클라이언트 어피니티(Client Affinity) 기능을 제공합니다.
어피니티(일명 "스티키 세션")를 사용하면 특정 클라이언트의 모든 요청을 동일한 서버로 고정시켜 세션 일관성을 유지할 수 있습니다 (HTTP Load Balancing using Application Request Routing | Microsoft Learn). ARR 설정에서 "Client affinity" 옵션을 활성화하면 ARR이 클라이언트별 쿠키를 발급하거나 IP를 기준으로 세션을 고정시킵니다.
단, 세션을 사용하지 않거나 분산 캐시/DB에 세션을 저장하도록 애플리케이션을 구성하는 것이 확장성 면에서는 더 좋은 접근입니다.
또한 ARR은 백엔드 서버 헬스 체크 및 가중치 기반 라우팅도 지원합니다.
정기적으로 각 서버 상태를 모니터링하여 장애시 자동으로 트래픽을 제외하고, 응답 속도 등에 따라 부하 분산 비율을 조정할 수 있습니다.
이처럼 IIS ARR로 소프트웨어적인 로드 밸런싱을 구현하거나, 하드웨어 로드 밸런서/클라우드 로드 밸런서를 사용할 수도 있습니다.
중요한 것은 무중단 배포와 확장을 위해 세션, 캐시, 파일 저장 등을 적절히 분리하여 상태 비저장(stateless) 서버 구조로 애플리케이션을 디자인하는 것입니다.
마지막으로, IIS Web Garden 기능도 확장성에 언급되곤 합니다.
Web Garden은 하나의 서버 내에서 한 애플리케이션 풀에 여러 워커 프로세스(w3wp)를 할당하는 설정(Maximum Worker Processes > 1)입니다.
이는 이론적으로 한 애플리케이션이 멀티코어를 더 잘 활용하게 할 수 있지만, 대부분의 ASP.NET 애플리케이션은 이미 다중 스레드로 동작하므로 Web Garden으로 얻는 이점이 크지 않습니다.
오히려 Web Garden을 사용하면 동일한 서버 내에서도 세션 등이 프로세스마다 분산되는 문제가 생길 수 있으므로 특별한 경우가 아니면 Maximum Worker Processes는 기본값 1을 유지하는 것이 안전합니다 (Process Model Settings for an Application Pool | Microsoft Learn).
(IIS 10에서 MaxProcesses를 0으로 설정하면 NUMA 노드 수 만큼 워커프로세스를 둬서 NUMA 최적화를 하기도 하지만, 일반적인 시나리오에서는 기본값이면 충분합니다 (Process Model Settings for an Application Pool | Microsoft Learn) (Process Model Settings for an Application Pool | Microsoft Learn).)
1.4 애플리케이션 풀 및 워커 프로세스 튜닝
애플리케이션 풀(Application Pool) 설정을 세부 튜닝함으로써 IIS 서버의 안정성과 성능을 향상시킬 수 있습니다.
각 애플리케이션 풀은 **고유한 워커 프로세스(w3wp.exe)**를 생성하며, 이 프로세스의 동작 방식을 제어하는 여러 옵션을 제공합니다.
주요 튜닝 항목으로 재활용(Recycler), Idle 타임아웃, CPU/메모리 제한 등이 있습니다.
- 자동 재활용(Periodic Recycling): IIS는 기본적으로 애플리케이션 풀을 주기적으로 재시작하여 (메모리 누수 등의 문제로부터) 장기 실행 애플리케이션을 보호합니다. 기본값은 1740분(29시간)마다 한 번 재활용이며 (Why is the IIS default app pool recycle set to 1740 minutes?), 이는 매일 같은 시간대가 아닌 시간 간격으로 돌아가도록 한 설정입니다. 운영 환경에서는 **재활용 시간을 예약(Specific Time)**하거나, 메모리 사용량 기준으로 재활용을 트리거할 수도 있습니다 (Recycling Settings for an Application Pool | Microsoft Learn). 예를 들어 메모리가 2GB를 초과하면 풀을 재시작하도록 privateMemory 또는 virtualMemory 한도를 설정할 수 있습니다 (Recycling Settings for an Application Pool | Microsoft Learn). 재활용 시 애플리케이션 도메인이 새로 로드되므로 일정 간격으로 메모리를 청소하는 효과가 있지만, 재활용 순간에는 요청 끊김이 없도록 Overlapping Recycling 기능이 백그라운드에서 새 프로세스를 기동하고 기존 프로세스의 처리를 마칠 때까지 병행 실행합니다 (IIS 기본동작). 재활용 이벤트는 Windows 이벤트 로그의 WAS (Web Administration Service) 항목에 기록되므로, 불시의 재활용이 발생하면 이벤트 로그를 통해 원인(예: 메모리 한도 초과 등)을 확인할 수 있습니다.
- Idle Timeout 및 항상 실행(AlwaysRunning): 기본 설정에서 워커 프로세스는 閑散(Idle) 상태가 20분 지속되면 자동 종료됩니다(Idle Time-out: 20 minutes) (Is there any harm in disabling the idle timeout for an application pool?). 이는 리소스 절약을 위한 설정이지만, 엔터프라이즈 환경에서는 첫 요청 시 **“춥은(startup) 지연”**이 발생할 수 있습니다. 만약 지속적으로 서비스를 제공해야 하는 애플리케이션이라면 Idle Time-out을 0으로 설정하여 비활성화하거나, Start Mode를 AlwaysRunning으로 설정하여 워커 프로세스가 항상 메모리에 상주하도록 구성합니다. 이 경우 Application Initialization 모듈을 사용해 서버 시작 시 미리 애플리케이션을 예열(warm-up)하는 것도 고려합니다. IIS 8 이후에는 IdleTimeout 후 종료 대신 프로세스를 **Suspend(일시 중지)**하는 옵션도 존재합니다 (idleTimeoutAction 설정) (Process Model Settings for an Application Pool | Microsoft Learn). Suspend는 프로세스를 메모리에 유지하면서 요청 처리만 중지했다가, 새로운 요청 도착 시 빠르게 Resume하여 응답할 수 있으므로, 초기 응답 지연을 줄여줍니다 (단, suspend된 동안 메모리는 점유함).
- CPU 및 메모리 제한: 애플리케이션 풀이 과도하게 CPU를 사용하여 다른 애플리케이션에 영향을 주는 것을 방지하기 위해 CPU 모니터링 및 제한을 설정할 수 있습니다. IIS 앱 풀 고급 설정의 CPU 속성에서 **CPU 한도(% 단위)**와 한도 초과 시 동작(스로틀 또는 프로세스 종료)을 지정할 수 있습니다. 예를 들어 CPU %를 80으로 지정하고 Action을 Throttle로 설정하면, 해당 풀의 CPU 사용률이 80%를 넘길 경우 IIS가 워커 프로세스의 스레드를 줄이거나 지연시켜 CPU 사용을 억제합니다. 메모리의 경우 앞서 언급한 Private Memory Limit와 Virtual Memory Limit을 통해 일정 메모리 초과 시 재활용하도록 설정 가능합니다. 이때 메모리 누수가 있는 애플리케이션이라면 private 메모리 한도를 설정해두면 메모리 누적으로 인한 장애를 예방할 수 있습니다. 다만 메모리 한도로 인한 재활용은 결국 애플리케이션 재시작을 의미하므로, 근본적인 해결(코드 수정 등)이 이루어질 때까지 임시 방편으로 활용합니다.
- 스레드 및 IO 튜닝: .NET Framework 기반의 ASP.NET 애플리케이션은 Thread Pool에서 요청을 처리합니다. 일반적으로 별도 튜닝이 필요 없는 영역이지만, 매우 높은 동시성 시나리오에서는 maxConcurrentRequestsPerCPU (또는 .NET Core의 ThreadPool.MaxThreads) 등의 설정으로 극한의 튜닝을 할 수 있습니다. 그러나 대부분의 경우 기본 설정이 최적이며, 동기 코드를 비동기로 개선하는 것이 효과적입니다. 또한, IIS Queue Length(대기 요청 큐 길이) 설정을 조정하여 큐가 가득 찼을 때 새 요청을 거부하는 임계치를 높이거나 낮출 수 있습니다 (기본 1000). 대기 큐 초과는 503 오류를 유발하므로, 트래픽 패턴에 따라 이 값을 조정하되, 근본적으로는 애플리케이션 성능 튜닝이나 서버 확장을 통해 해결하는 것이 바람직합니다.
1.5 CPU 및 메모리 최적화
CPU 최적화와 메모리 최적화는 주로 애플리케이션 코드 품질 및 서버 하드웨어 활용과 연관됩니다. 시스템 수준에서는 다음과 같은 최적화 지침을 고려하십시오.
- HTTP.sys 커널 캐시 활용: HTTP.sys 커널 캐시는 자주 요청되는 정적 파일들을 커널 메모리에 저장하여 고속 제공하므로, 웹 서버의 CPU 부담을 줄여줍니다. Registry에서 UriMaxCacheMegabyteCount 등을 조정하면 커널 캐시에 사용할 메모리 크기를 설정할 수 있습니다. 메모리가 충분하다면 이 값을 늘려서 캐시 적중률을 높이고, 메모리가 부족하다면 큰 파일 캐시를 피하도록 UriMaxUriBytes(단일 캐시 항목 최대 크기)를 조정할 수 있습니다. IIS 10은 기본적으로 커널 캐시가 활성화(UriEnableCache=1)되어 있으므로, 특별한 경우가 아니면 그대로 두고, 캐시 미사용 필터나 모듈을 제거하여 캐시가 비활성화되지 않도록 하는 것이 중요합니다 (예: IIS6 이전용 ISAPI 필터가 있으면 커널 캐시가 꺼질 수 있음).
- NUMA 최적화: 다중 CPU NUMA 아키텍처 서버에서는, 워커 프로세스의 스레드가 NUMA 노드 별로 균형 있게 분산되도록 IIS 10부터 자동 최적 CPU 할당 기능이 도입되었습니다. 기본적으로 활성화되어 있으며 (ThreadPoolUseIdealCpu 레지스트리), 이를 통해 CPU 캐시 로컬리티를 향상시키고 스레드 간 경쟁을 줄입니다. 일반적으로 이 기본값을 유지하면 되지만, 특정 NUMA 설정을 직접 제어하려면 애플리케이션 풀의 numaNodeAssignment 및 numaNodeAffinityMode 설정을 활용할 수 있습니다.
- 64비트 프로세스 사용: IIS의 워커 프로세스는 기본적으로 64비트로 실행되며, 이는 큰 메모리 공간 활용과 성능면에서 유리합니다. 만약 레거시 이유로 32비트 모드로 동작하고 있다면 (응용프로그램 풀의 “Enable 32-bit Applications” 설정이 True인 경우), 가능하면 64비트 구성요소로 마이그레이션하여 해당 옵션을 False로 하여 64비트로 운영하는 것이 좋습니다. 32비트 프로세스는 4GB 메모리 한계가 있어 대용량 트래픽 처리 시 메모리 부족이 날 수 있고, GC 튜닝 면에서도 제약이 있기 때문입니다.
- 가비지 컬렉션(GC) 모드: .NET Framework에서는 워크스테이션 vs 서버 GC, 동시(GCConcurrent) 등의 설정이 있고, .NET Core에서는 GCSettings.IsServerGC 등이 있습니다. IIS 호스팅 환경에서는 기본적으로 **서버 GC (다중 스레드 병렬 수집)**가 사용되며, 대부분 경우에 성능이 더 좋습니다. 별도로 GC를 튜닝할 일은 드물지만, 대용량 객체 할당(LOH) 최적화나, 메모리 누수 진단 등은 애플리케이션 코드를 통해 최적화해야 합니다. (자세한 내용은 아래 문제 해결 섹션의 메모리 누수 부분 참고)
- 프로파일링과 부하 테스트: CPU와 메모리를 최적화하려면 병목이 되는 코드를 찾아내는 것이 우선입니다. Visual Studio Profiler, PerfView, ANTS profiler 등의 도구로 .NET 코드를 프로파일링하여 어떤 함수가 CPU를 많이 쓰는지, 가비지 컬렉션은 얼마나 자주 도는지 등을 분석합니다. 데이터베이스 쿼리 최적화, 알고리즘 개선, 불필요한 객체 할당 제거 등의 작업을 통해 근본적인 최적화를 수행해야 합니다. 서버 설정 튜닝은 이런 코드 최적화가 이루어진 이후에 미세조정에 활용하는 것이 바람직합니다.
2. 보안 설정
엔터프라이즈 환경에서는 보안이 성능만큼이나 중요합니다.
IIS와 ASP.NET의 보안 설정은 데이터 암호화, 인증/인가, 네트워크 액세스 제어, 공격 방어 등의 측면을 포괄합니다.
본 섹션에서는 TLS/SSL 구성부터 애플리케이션 계층, 네트워크 계층의 보안 강화 방법을 다룹니다.
2.1 TLS/SSL 구성 및 강제 설정
전송 계층 보안을 위해 웹 서버에 TLS(SSL)을 올바르게 구성하는 것은 가장 기본적이면서 중요한 보안 작업입니다.
다음 사항들을 고려해야 합니다.
- 최신 프로토콜 및 암호화 알고리즘 사용: 서버에서 TLS 1.2 이상만 지원하도록 설정하고, SSL 2.0/3.0, TLS 1.0/1.1은 취약점으로 인해 사용 중지합니다. Windows 서버에서는 레지스트리(SCHANNEL 설정)를 통해 프로토콜 활성/비활성을 제어할 수 있으며, IIS Crypto와 같은 도구를 사용하면 권장 설정을 쉽게 적용할 수 있습니다. TLS 1.3은 Windows Server 2022 이상에서 지원되므로 가능하다면 활성화하여 최신 프로토콜을 사용하는 것이 좋습니다. 또한, **취약한 암호화 알고리즘(Cipher Suite)**은 제외하고 강력한 알고리즘만 사용하도록 Cipher Suite 우선순위를 재정의해야 합니다. 일반적으로 AES 계열과 ECC 기반의 ECDHE 키 교환을 사용하고, 3DES나 RC4 같은 오래된 알고리즘은 비활성화합니다 (Current (2024) best practices for TLS on IIS 10 / Windows Server 2022). (Cipher Suite 설정은 그룹 정책의 **"SSL Cipher Suite Order"**나 레지스트리 편집을 통해 관리합니다.)
- 인증서 및 바인딩 설정: 공인된 CA에서 발급한 SSL 서버 인증서를 IIS에 등록하고, 해당 사이트의 HTTPS 바인딩에 해당 인증서를 지정합니다. 이때 호스트 이름 바인딩이 필요한 멀티 사이트 환경이라면 SNI(Server Name Indication)를 사용하도록 체크합니다. 또한 강제 HTTPS를 위해 동일 사이트에 대한 HTTP 바인딩은 존재하더라도, URL 재작성(URL Rewrite) 등을 통해 모든 HTTP 요청을 HTTPS로 리다이렉트하도록 설정합니다. 예를 들어, Web.config에 아래와 같은 URL Rewrite 규칙을 추가하면 HTTP 요청을 자동으로 HTTPS로 영구 리다이렉트합니다:이를 통해 사용자가 http:// 로 접속해도 자동으로 https://로 이동하게 되어 모든 통신이 암호화됩니다. IIS 8 이상에서는 URL Rewrite 없이 SSL Settings에서 “Require SSL” 옵션을 켜고 “Custom Errors”로 리다이렉트 페이지를 지정하는 방식으로도 구현 가능합니다.
- <system.webServer> <rewrite> <rules> <rule name="Redirect to HTTPS" stopProcessing="true"> <match url="(.*)" /> <conditions> <add input="{HTTPS}" pattern="off" ignoreCase="true" /> </conditions> <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" /> </rule> </rules> </rewrite> </system.webServer>
- HSTS (HTTP Strict Transport Security): HTTPS를 강제하는 또다른 방법으로, 서버가 HSTS 헤더를 반환하여 브라우저가 해당 도메인에 대해 향후 HTTP 요청을 하지 않도록 지시할 수 있습니다. Web.config의 <system.webServer><httpProtocol><customHeaders>에 Strict-Transport-Security: max-age=31536000; includeSubDomains 같은 헤더를 추가하여 HSTS를 활성화합니다 (max-age=초단위 유효기간). HSTS를 사용하면 사용자가 http를 입력해도 브라우저가 자동으로 https로 바꾸기 때문에 중간자 공격 위험을 낮춥니다.
- 클라이언트 인증서 및 SSL 강화 설정: 필요 시 클라이언트 인증서(Client Certificate) 검증을 설정하여 양방향 SSL을 구현할 수 있습니다. IIS 사이트의 SSL Settings에서 “Require SSL” 및 “Require Client Certificates”를 선택하면, 클라이언트 인증서 없는 사용자는 접근할 수 없게 됩니다. 다만 이는 필요한 경우에만 설정합니다. 그 외에 SSL 옵션으로, system.webServer/security/access의 sslFlags를 통해 SslRequireCert, Ssl128 등을 적용할 수 있습니다. 예를 들어 sslFlags="Ssl, SslRequireCert, Ssl128"로 설정하면 SSL 사용 및 클라이언트 인증서 요구, 128비트 이상 암호화만 허용 등의 제약을 줄 수 있습니다.
- 초기 설정 테스트: SSL Labs의 SSL Server Test와 같은 도구를 활용하여 서버의 SSL 설정을 진단합니다. 설정이 잘 되어 있다면 높은 등급을 받고, 취약한 프로토콜이나 암호가 남아있으면 경고와 함께 수정 가이드를 제공합니다. 이를 통해 우리의 IIS 서버가 안전한 TLS 구성을 가지고 있는지 정기적으로 확인할 수 있습니다.
요약하면, IIS 보안을 위해 TLS1.2+/강력한 Cipher만 허용, 모든 요청 HTTPS화(HSTS 포함), 인증서 적절히 배포 등의 조치를 취하고 주기적으로 검증하는 것이 중요합니다.
최신 .NET Framework 및 .NET Core는 기본적으로 강력한 보안 프로토콜을 사용하도록 설정되어 있지만, 서버 단에서도 이를 뒷받침하도록 환경을 구성해야 합니다.
2.2 인증 및 권한 관리 (Windows Authentication, Forms Authentication, OAuth 등)
**인증(Authentication)**과 **권한 부여(Authorization)**는 애플리케이션 보안의 핵심입니다.
엔터프라이즈 환경에서는 사내 웹 애플리케이션의 경우 Windows 도메인 계정을 이용한 통합 인증을 사용하고, 외부 사용자 대상 서비스의 경우 양식 인증 또는 OAuth/OpenID Connect를 통한 토큰 기반 인증을 사용하는 등 시나리오별로 적절한 방식을 채택합니다.
- Windows 통합 인증 (Kerberos/NTLM): 사내 Intranet 웹 애플리케이션은 IIS의 Windows Authentication을 활성화하여 사용자가 별도 로그인 페이지 없이 Windows 도메인 자격증명으로 자동 인증되게 할 수 있습니다. IIS 관리자의 인증(Authentication) 섹션에서 Windows Authentication을 Enabled, Anonymous Authentication을 Disabled로 설정하면, 브라우저가 사용자의 AD 도메인 크리덴셜을 이용해 Kerberos 또는 NTLM 교환을 수행합니다. 이는 강력한 보안을 제공하며 사용자 편의성도 높습니다(싱글사인온). 단, Chrome, Edge, IE 등 대부분 브라우저는 동일 도메인 내에서 통합 인증을 자동으로 하거나 프롬프트를 띄우지만, 크로스 도메인이나 Internet 존에서는 제한될 수 있으므로 브라우저 정책도 고려해야 합니다.
- 폼 기반 인증 (Forms Authentication): 인터넷 사용자 대상의 ASP.NET 웹 사이트는 일반적으로 Forms Authentication을 사용합니다. Web.config의 <authentication mode="Forms"> 설정과 <authorization> 규칙을 통해 로그인 페이지를 거친 후 애플리케이션 쿠키로 인증 상태를 유지합니다. IIS 레벨에서는 주로 Anonymous Authentication을 Enabled로 두고, 애플리케이션 내부에서 [Authorize] 어트리뷰트나 ASP.NET의 FormsAuthentication.SetAuthCookie 등을 활용하여 권한 제어를 합니다. 폼 인증을 사용할 때는 쿠키 보안에 신경써야 합니다. 쿠키에 Secure, HttpOnly, SameSite 속성을 설정하고(ASP.NET Core의 CookieAuthenticationOptions 등 활용), 필요 시 쿠키 암호화와 유효기간 관리 등을 철저히 해야 합니다.
- OAuth2 / OpenID Connect (외부 인증 서비스 연계): 최신 애플리케이션에서는 소셜 로그인이나 기업용 SSO (예: Azure AD, IdentityServer) 등 OAuth2/OIDC 표준을 통한 인증을 구현하는 경우가 많습니다. ASP.NET Core의 경우 미들웨어(AddAuthentication, AddOpenIdConnect 등)를 통해 OAuth2 흐름을 처리하고 JWT 또는 세션 쿠키로 인증 상태를 유지합니다. 이러한 토큰 기반 인증을 사용할 때 IIS에서 특별히 추가 설정할 부분은 많지 않지만, OIDC 리디렉션 URL이나 토큰 크기 관련 이슈에 대비해야 합니다. 예를 들어, JWT 토큰을 쿠키로 저장 시 크기가 클 경우 IIS의 HTTP 헤더 크기 제한에 걸릴 수 있으므로, 이 한계를 늘리거나(Registry 편집 또는 MaxFieldLength, MaxRequestBytes 설정) 토큰은 쿠키 대신 브라우저 스토리지와 Authorization 헤더를 쓰는 방안을 고려합니다.
- 권한 부여 (Authorization): 인증 후 세부 권한 관리는 애플리케이션 내에서 역할(Role)이나 클레임(Claims)에 기반하여 이루어집니다. 그러나 IIS 단계에서도 IP 주소 제약이나 요청 필터링으로 추가 보호를 할 수 있습니다. 예를 들어, 사내 관리자 페이지는 IIS IP 주소 및 도메인 제한 기능을 사용해 회사 내부망 IP에서만 접근 가능하도록 제한할 수 있습니다. Web.config 예시:위 설정은 203.0.113.0/24 대역에 대해서만 사이트 접근을 허용하고, 그 외 IP의 요청은 연결을 즉시 끊도록(Abort) 구성하는 예입니다. 이처럼 방화벽 수준의 IP 제한을 IIS 설정으로도 구현하여 이중의 보안을 갖출 수 있습니다.
- <system.webServer> <security> <ipSecurity allowUnlisted="false" denyAction="Abort"> <add ipAddress="203.0.113.0" subnetMask="255.255.255.0" allowed="true" /> </ipSecurity> </security> </system.webServer>
- 익명 인증 제어: IIS에서 **익명 인증(Anonymous Authentication)**이 Enabled되어 있으면 누구나 리소스에 접근 가능하므로, 보호가 필요한 경로에는 Web.config의 <authorization> 섹션을 활용해 익명 사용자 ?<deny users="?" />를 추가하거나, 또는 폴더별로 별도 웹 구성으로 인증을 요구하도록 합니다. 특히 WCF 서비스나 Web API 엔드포인트의 경우 아무 설정 없이 두면 익명으로 호출될 수 있으므로, 엔드포인트에 인증을 적용하거나 IIS에서 접근을 차단해야 합니다.
정리하면, 엔터프라이즈 앱 보안을 위해 환경에 맞는 인증 방식을 선택하고, IIS 및 애플리케이션 설정을 일치시켜야 합니다.
Windows 통합 인증은 사내 보안에 유용하며, 폼/토큰 인증은 인터넷 시나리오에 적합합니다. 추가로 IIS 기능(IP 제한, 요청 필터링 등)을 활용해 다층 보안을 구성하는 것이 권장됩니다.
2.3 방화벽 및 네트워크 보안 (IP 제한, 방화벽 규칙 설정)
IIS 레벨 보안 외에도, 네트워크 차원의 보안으로 웹 서버를 보호해야 합니다.
이는 서버 방화벽 설정, IP 제한, 포트 관리 등을 포함합니다.
- Windows 방화벽/네트워크 방화벽 구성: 웹 서버가 위치한 호스트나 데이터센터의 방화벽을 설정하여 불필요한 포트 차단과 허용된 IP 관리를 수행합니다. 일반적인 IIS 웹 서버는 80, 443 포트만 외부에 개방하고 나머지 원격관리(3389)나 DB 연결 포트 등은 내부망 전용으로 제한해야 합니다. 엔터프라이즈 환경에서는 방화벽에 웹 서버용 보안 그룹을 만들어 특정 신뢰된 네트워크만 통신하도록 설정할 수 있습니다. 예를 들어, 관리용 인터페이스는 회사 내부 IP 대역에서만 접속 가능하게 하거나 DB 서버는 웹 서버들만 접근 가능하도록 ACL을 구성합니다.
- IIS IP 및 도메인 제한(IP and Domain Restrictions): 앞서 인증 섹션에서도 언급했듯이, IIS에는 특정 IP를 허용 또는 거부하는 기능이 있습니다. 이는 방화벽이 접근을 차단하기 전에 IIS 레벨에서 1차 필터링을 하는 것으로 볼 수 있습니다. 방화벽이 더 안전하지만, IIS IP 제한은 URL별로 세밀한 제어가 가능하다는 장점이 있습니다. 예를 들어 /admin 경로는 내부망 IP만 허용하고, /api/public 경로는 전 세계 허용 등의 세부 정책을 Web.config 별도로 적용할 수 있습니다. IP 제한 설정 시 허용되지 않은 IP의 요청 처리 방식을 Abort(연결 즉시 끊음) 또는 Unauthorized(401 응답) 등으로 지정할 수 있는데, 보안상으로는 응답을 주지 않고 끊어버리는 것이 정보를 노출하지 않는 면에서 좋습니다. 이 기능은 IIS Dynamic IP Restrictions 모듈과 결합하면, 실시간으로 공격 IP를 차단하는 기능도 제공합니다 (다음 항목에서 설명).
- SQL 인젝션 등 필터링: 네트워크 보안의 일환으로, IIS 요청 필터(Request Filtering) 기능을 이용해 특정 패턴의 URL이나 헤더를 차단할 수 있습니다. 기본적으로 <requestFiltering>에서 최대 요청 길이 제한, 파일 확장자 금지(예: .exe 업로드 차단) 등이 설정가능합니다. SQL 인젝션을 완벽히 방어하진 못하지만, 일반적으로 URL에 '나 -- 등이 들어오는 패턴 등을 필터링하는 등 1차 방어선을 구축할 수 있습니다. 그러나 이러한 필터링은 ModSecurity 같은 전문 웹 방화벽 룰셋ほど 강력하지 않으므로, 필요 시 다음 항목의 WAF를 고려해야 합니다.
2.4 DDoS 방어 및 웹 애플리케이션 방화벽 (WAF)
**분산 서비스 거부 공격(DDoS)**이나 웹 애플리케이션 공격에 대비하려면, 일반적인 설정 외에 추가적인 보호막이 필요합니다.
IIS 자체도 어느 정도 방어 기작을 제공하지만, 대규모 공격에 대비해서는 전용 솔루션 도입이 권장됩니다.
- 동적 IP 제한을 통한 DDoS 완화: IIS 8.0부터 내장된 Dynamic IP Restrictions를 사용하면, 일정 횟수 이상의 요청을 보내는 IP를 자동 차단할 수 있습니다 (IIS 8.0 Dynamic IP Address Restrictions | Microsoft Learn). 예를 들어 “한 IP에서 초당 100개의 요청이 오면 5분간 차단”과 같은 정책을 설정할 수 있습니다. 실제 설정은 IIS Manager에서 IP Address and Domain Restrictions > Edit Dynamic Restriction Settings에서 Concurrent request 제한과 요청 빈도 제한을 체크하고 임계값을 지정합니다 (IIS 8.0 Dynamic IP Address Restrictions | Microsoft Learn). 이렇게 설정하면 IIS가 실시간으로 IP별 요청 수를 추적하여 임계치를 넘는 IP를 일시적으로 차단하고 403이나 404 응답 대신 연결을 거부(Abort)할 수 있습니다 (IIS 8.0 Dynamic IP Address Restrictions | Microsoft Learn) (IIS 8.0 Dynamic IP Address Restrictions | Microsoft Learn). 또한 프록시나 로드 밸런서를 통해 실제 클라이언트 IP가 X-Forwarded-For 헤더로 전달되는 환경이라면, 프록시 모드를 활성화하여 이 헤더에 담긴 원본 IP 기준으로 제한을 할 수 있습니다 (IIS 8.0 Dynamic IP Address Restrictions | Microsoft Learn). 다만 동적 IP 차단은 합법적인 트래픽도 차단할 가능성이 있으므로, 너무 타이트하게 적용하지 말고 서비스 특성에 맞는 적절한 값으로 설정해야 합니다.
- 웹 애플리케이션 방화벽(WAF): SQL 인젝션, XSS, 취약점 공격 등을 막기 위해서는 WAF 솔루션을 사용하는 것이 효과적입니다. WAF는 HTTP 요청의 페이로드를 검사하여 알려진 공격 패턴에 매칭되면 차단하거나 변조합니다. IIS 자체에는 완전한 WAF 기능이 없으므로, 엔터프라이즈 환경에서는 F5 ASM, Imperva, AWS/Azure WAF, Cloudflare 등 전용 WAF를 웹 서버 앞단에 두는 것을 고려해야 합니다. Azure Application Gateway의 WAF나 AWS WAF는 OWASP ModSecurity 규칙셋을 사용하여 일반적인 웹 공격을 방어해줍니다. 만약 이러한 솔루션이 없다면 ModSecurity IIS 버전을 설치하여 오픈소스 룰셋으로 기본적인 방어를 구축할 수도 있습니다.
- DoS 공격 대비 구성: DDoS만큼 강도가 높지 않은 **DoS(단일 소스 공격)**에도 대비해야 합니다. IIS 설정에서 동시 연결 한도(MaxConnections)나 응답 대기열 길이 등을 조정하여 한 클라이언트가 자원을 고갈시키지 못하게 할 수 있습니다. 예를 들어 HTTP Keep-Alive를 비활성화하면 한 연결당 하나의 요청만 처리하고 종료하므로 연결 자원을 빨리 회수할 수 있지만, 일반적인 사용자의 성능에도 영향을 주므로 신중히 고려해야 합니다. 대역폭 소모형 공격에 대해서는 네트워크 장비 레벨에서 Rate Limiting을 설정하는 것도 방법입니다.
- 브루트 포스 방지: 로그인 페이지 등에 대한 무차별 대입 공격을 막기 위해, 애플리케이션 차원에서 계정 잠금 정책이나 **캡챠(CAPTCHA)**를 도입하는 것을 권장합니다. IIS 레벨에서는 위의 동적 IP 제한이 우회적인 도움이 될 수 있습니다. 예를 들어 /login URL에 대한 요청 빈도 제한을 IP당 분당 X회로 제한하면 브루트포스 속도를 늦출 수 있습니다.
요약: 엔터프라이즈 보안은 다층 방어가 핵심입니다.
TLS로 암호화하고, 인증으로 제어하고, 방화벽과 IP 제한으로 네트워크를 보호하며, WAF로 애플리케이션 공격을 필터링하는 복합적인 대책을 적용해야 합니다.
정기적인 취약점 점검과 모의 해킹을 통해 설정의 효과성을 검증하고 업데이트하는 것도 잊지 말아야 합니다.
3. 애플리케이션 풀 관리
IIS 애플리케이션 풀은 웹 애플리케이션 격리와 자원 관리의 기본 단위입니다.
엔터프라이즈 환경에서는 여러 웹 애플리케이션을 한 서버에서 운영할 때도 많으므로, 풀 관리 전략이 중요합니다.
이 섹션에서는 풀 분리 및 아이솔레이션, 풀 별 아이덴티티 관리, 재활성화(리사이클) 정책, 프로세스/스레드 관리, 모니터링 기법을 다룹니다.
3.1 애플리케이션 풀 분리 및 설정 (고유한 Identity 할당, 자동 재활용 정책)
애플리케이션 풀 분리는 보안과 안정성을 위해 권장되는 설정입니다.
각 웹 사이트마다 별도의 애플리케이션 풀을 사용하면, 한 사이트에서 문제가 발생해도 다른 사이트로 확산되지 않고 (프로세스 단위로 격리), 권한도 분리할 수 있습니다 (Ensure Security Isolation for Web Sites | Microsoft Learn).
Microsoft도 “IIS 7 이상에서 웹 사이트를 격리하려면 각각 독립된 애플리케이션 풀을 사용하라”고 권고하고 있습니다 (Ensure Security Isolation for Web Sites | Microsoft Learn).
또한 풀을 분리할 때 **풀 아이덴티티(Identity)**도 개별화하는 것이 좋습니다.
기본적으로 IIS는 ApplicationPoolIdentity라는 가상 계정으로 워커 프로세스를 실행합니다 (Ensure Security Isolation for Web Sites | Microsoft Learn) (Process Model Settings for an Application Pool | Microsoft Learn).
이 계정은 풀 이름과 동일한 이름으로 Windows에 식별자가 존재하며 (예: "IIS AppPool\MyAppPool"), 로컬 컴퓨터에서 일반 사용자 권한으로 동작합니다 (Ensure Security Isolation for Web Sites | Microsoft Learn) (Ensure Security Isolation for Web Sites | Microsoft Learn).
각각의 풀은 서로 다른 AppPoolIdentity로 실행되므로, 파일시스템이나 리소스 권한 부여 시 해당 풀만 접근하도록 ACL 설정이 가능합니다 (Ensure Security Isolation for Web Sites | Microsoft Learn) (Ensure Security Isolation for Web Sites | Microsoft Learn).
예를 들어, C:\WebData\SiteA 폴더는 "IIS AppPool\SiteAPool"만 읽기 권한을 주고, "IIS AppPool\SiteBPool"에는 권한을 주지 않는 식으로 사이트 간 권한을 완전히 격리할 수 있습니다.
도메인 계정을 풀 아이덴티티로 쓰는 방법도 있는데, 여러 서버 간 동일한 권한이 필요하거나 UNC 경로 액세스 등 특별한 경우에만 사용하고, 그렇지 않으면 ApplicationPoolIdentity가 관리 부담도 적고 권장됩니다 (Ensure Security Isolation for Web Sites | Microsoft Learn) (Ensure Security Isolation for Web Sites | Microsoft Learn).
자동 재활용 정책은 앞서 성능 섹션에서 언급한 대로, 풀 별로 주기/시간/요청 수/메모리 임계치 등을 설정할 수 있습니다 (Recycling Settings for an Application Pool | Microsoft Learn).
엔터프라이즈 운영에서 권장되는 전략은 정기 재활용 시간 지정입니다.
예를 들어 새벽 4시에 재활용하도록 설정하면 (recycling.periodicRestart.schedule), 메모리 누수나 상태 누적을 해소하면서도 사용자 활동이 적은 시간에 재시작이 이루어지게 할 수 있습니다.
그러나 .NET Core의 경우 메모리 누수가 거의 없고 장시간 실행에 최적화되어 있으므로, 불필요한 재활용은 오히려 캐시를 날려 성능을 저하시킬 수 있습니다.
따라서 재활용 빈도를 최소화하고, 문제 발생 시에만 수동 재활용하거나 메모리 한도로 트리거되게 설정하는 식으로 운영합니다.
Overlapping Recycling(기본 true)이 켜져 있으면 재활용 시 가용성에는 큰 문제가 없으나, 세션 상태 등이 InProc인 경우 재활용 시 세션이 끊길 수 있으므로 세션 유지에 유의해야 합니다.
추가로, 프리로드(AlwaysRunning + Auto-start) 기능을 활용하면 애플리케이션 풀이 생성 또는 재활용된 후 첫 요청을 기다리지 않고 자동으로 초기화되도록 할 수 있습니다.
IIS Manager > Application Pools > 고급 설정에서 Start Mode = AlwaysRunning으로 설정하고, Application Initialization 모듈이 설치되어 있다면 웹사이트 설정에서 Preload Enabled = True로 지정합니다.
이렇게 하면 풀 시작 시 미리 애플리케이션을 로드하고, 필요한 경우 초기 페이지 요청을 미리 수행하게 하여 사용자에게 Cold Start 지연을 숨길 수 있습니다.
3.2 워커 프로세스 및 스레드 관리
**워커 프로세스(Worker Process)**는 애플리케이션 풀 당 기본 1개 실행되며, 내부에서 다중 스레드로 요청을 처리합니다.
일반적으로 워커 프로세스 수를 늘리는 Web Garden 설정은 권장되지 않지만, 특수한 CPU 바운드 작업이 많은 경우 고려될 수 있습니다.
예를 들어 이미지 인코딩 등 CPU를 장시간 사용하는 작업이 많은 애플리케이션이라면, Web Garden으로 프로세스를 2~4개까지 늘려 병렬 처리를 시도할 수 있습니다.
그러나 이는 코드가 멀티프로세스 환경을 대비했는지 (예: 세션 공유, 락 contention 등) 면밀히 검토한 후 적용해야 합니다.
대부분 상황에서는 차라리 서버 자체를 수평 확장하는 것이 더 효과적입니다.
워커 프로세스의 스레드 풀은 .NET 런타임이 관리하며, 동기/비동기 요청 처리 모두 .NET Thread Pool을 사용합니다.
개발 측면에서 비동기 IO 처리를 도입하면 동일 자원에서 더 많은 동시 요청 처리가 가능해지므로 (스레드가 작업대기중 블록되지 않음),
async/await 패턴 활용을 개발팀에 권장합니다.
한편, IIS의 HTTP.sys 단계에서 연결 처리를 위한 커널 스레드와, 사용자 모드의 IIS I/O 스레드 등도 튜닝 포인트가 될 수 있으나, 최신 IIS는 대부분 자동으로 최적화되어 있어 레지스트리-level 조정은 거의 필요 없습니다. (예전 IIS 6/7 시절에는 MaxConcurrentRequestsPerCPU 또는 MaxIOThreads 같은 튜닝이 있었지만 IIS 10에서는 기본값이 대부분 최적입니다.)
스레드 풀 모니터링: .NET 클라이언트/서버 성능 카운터 중 Thread Pool Busy 등을 활용하면 현재 사용 중인 스레드 수를 모니터링할 수 있습니다.
만약 애플리케이션이 ThreadPool 스레드를 모두 소비하여 대기하게 되면 응답 지연이 발생할 수 있으므로, clr!ThreadPool Total Threads와 같은 성능 카운터를 실시간 모니터링하여 병목 여부를 파악합니다.
필요하다면 minWorkerThreads 값을 올려 초기에 할당되는 스레드 수를 늘릴 수 있지만, 일반적으로 그런 상황이 오면 코드의 동기 처리 부분을 점검하는 것이 우선입니다.
CLR Thread Injection and Throttling: .NET 런타임은 요청 증가 시 자동으로 워커 스레드를 추가 생성하고, 부하가 감소하면 스레드를 회수합니다.
IIS도 동시에 처리 중인 요청이 maxConcurrentRequestsPerCPU (기본 5000, .NET 4.5부터 사실상 제한 해제 수준) 이상이면 대기시키는 메커니즘이 있긴 하나, 현실적으로 이 한도에 닿는 경우는 거의 없습니다.
만약 특정 이벤트로 인해 수천 개 이상의 동시 요청이 들어오는 특이 상황을 다룬다면, Arr 등의 전단 분산으로 나누거나, 해당 범위에서는 웹서버 튜닝보다는 CDN 캐시, 큐잉 시스템 도입 등 아키텍처 변경을 검토해야 합니다.
3.3 리소스 할당 및 가용성 모니터링
애플리케이션 풀별 리소스 사용량을 지속적으로 모니터링하는 것은 운영에 필수입니다. Windows에는 이를 위한 여러 수단이 있습니다:
- 성능 모니터(PerfMon): Windows 성능 모니터에서 Process(w3wp)<InstanceName>* 카운터를 추가하면 각 풀 프로세스별 CPU%, Working Set, Private Bytes 등을 실시간으로 볼 수 있습니다. 예를 들어 Process(w3wp)\% Processor Time (instance: DefaultAppPool)을 추적하면 특정 풀의 CPU 점유율을 알 수 있습니다. 또한 ASP.NET 카운터 (Requests/Sec, Requests Current, Request Execution Time 등)와 IIS 카운터(Current Connections, Anonymous Requests/sec 등)를 활용해 각 풀/웹사이트의 부하 상태를 모니터링합니다.
- 모니터링 도구: 엔터프라이즈 환경에서는 SCOM(System Center)이나 Zabbix, Datadog 같은 모니터링 솔루션을 통해 IIS 풀별 지표를 수집하고 임계치 초과 시 알람을 보내도록 설정합니다 (Mastering IIS Logging: Essential Basics - OpenObserve). 특히 메모리 누수 탐지를 위해 w3wp 프로세스의 Private Bytes 또는 Virtual Bytes 증가 추이를 일별로 기록해두면, 서서히 증가하는 패턴을 조기에 발견할 수 있습니다. CPU도 지속 90% 이상이면 알람을 울려 추가 서버 증설 또는 코드 개선을 검토하게 합니다.
- 고가용성 (High Availability): 한 서버에서 여러 풀을 운영 중일 때, 특정 풀의 장애(예: 프로세스 크래시)로 전체 IIS가 영향을 받지는 않지만, 해당 사이트는 다운타임이 발생할 수 있습니다. 이를 보완하려면 웹 팜 구성으로 같은 앱을 두 대 이상 서버에서 구동하고 로드밸런싱을 함으로써, 한쪽 풀 재시작이나 장애 시에도 다른 노드가 서비스하도록 해야 합니다. 또한, **WAS(Windows Process Activation Service)**가 풀을 관리하는데, 풀에 Rapid-Fail Protection 기능이 있어 단시간 내 연속 크래시 발생 시 해당 풀을 자동 중지합니다. 이는 시스템 보호를 위한 것이지만, 풀 중지 시 서비스 중단이 되므로, 이벤트 로그의 오류를 분석해 원인을 제거하고, 필요시 Rapid-Fail의 기준 (기본 5번/5분)을 조정할 수 있습니다. 그러나 근본 문제 해결 없이 이 수치를 올리는 것은 위험하며, 크래시 원인 (주로 Unhandled Exception이나 OutOfMemory)을 해결하는 것이 우선입니다.
- 아이들 풀 정리: 리소스 회수를 위해 트래픽이 거의 없는 오래된 풀은 앞서 언급한 Idle Timeout에 의해 내려가는데, 만약 이를 끄지 않고 사용한다면, 풀 수가 매우 많을 경우 메모리 사용은 효율적일 수 있으나, 다시 요청 시 재기동하면서 지연이 생깁니다. 풀 수십 개 이상 운영하는 환경에서는 Idle 시 내려가도록 두어 메모리 확보를 택할 수도 있습니다. 이때 각 풀별 초기화 시간을 모니터링하여 지연이 문제되지 않는지 살펴봐야 합니다.
- 헬스 체크 엔드포인트: 애플리케이션 풀의 가용성을 모니터링하기 위해, 각 애플리케이션에 health check용 URL을 만들어두고 주기적으로 외부에서 호출해보는 것도 좋습니다. 예를 들어 /healthz 엔드포인트를 만들어 DB 연결 등 주요 종속성을 점검하게 한 뒤, 모니터링 시스템에서 1분마다 호출하여 HTTP 200을 기대하고, 500 오류나 타임아웃이 발생하면 자동으로 풀 재순환 또는 관리자 통보를 하는 식입니다. 이는 AppInsights의 Availability Test나 자체 스크립트를 통해 구현할 수 있습니다.
결론적으로, 풀 관리에서는 격리와 최소 권한 원칙으로 설정하고, 주기적 재활용과 모니터링으로 안정성을 확보하며, 장애 시 자동 복구 또는 신속 대응이 가능하도록 운영 프로세스를 마련해야 합니다.
4. 로깅 및 모니터링
**로깅(logging)**과 **모니터링(monitoring)**은 문제 발생 시 원인 파악과 성능 추이를 관리하는데 필수적입니다.
IIS와 ASP.NET은 다양한 로그를 제공하며, 추가적으로 응용 프로그램 내부 로그를 남기는 것도 중요합니다.
이 섹션에서는 IIS 로그 설정, ASP.NET 코어 로깅 프레임워크 사용, 실시간 모니터링 도구, 오류 추적 솔루션을 살펴봅니다.
4.1 IIS 로그 설정 및 분석
IIS는 W3C 확장 로그 파일 형식으로 사이트 접속 로그를 남길 수 있습니다.
로그에는 기본적으로 클라이언트 IP, 요청 URL, 상태 코드, 바이트 수 등 유용한 정보가 담깁니다.
엔터프라이즈 운영 시 로그 설정 최적화와 분석 방법을 정립해두어야 합니다.
- 로그 설정: IIS 관리자에서 각 사이트의 Logging 설정을 열면 로그 파일의 저장 위치, 형식, 필드, 롤오버 정책 등을 지정할 수 있습니다. 일반적으로 W3C 형식이 권장되는데, 이는 로그 필드를 커스터마이징할 수 있기 때문입니다 (Configure Logging in IIS | Microsoft Learn). W3C 로그에서 추가로 남기면 좋은 필드로는 Referer, User-Agent, X-Forwarded-For(IP가 프록시 뒤에 있는 경우) 등이 있습니다. 로그 파일은 기본적으로 %SystemDrive%\inetpub\logs\LogFiles 아래 생성되므로, 성능 및 보안 상 별도의 드라이브나 디렉터리로 위치를 변경하는 것이 좋습니다 (Configure Logging in IIS | Microsoft Learn). 시스템 드라이브가 꽉 차는 일을 방지하고, 로그 접근을 제한하기 위함입니다. 또한 파일 롤오버를 적절히 설정하여 로그 파일 크기가 너무 커지지 않도록 합니다 (일별 생성 권장) (Configure Logging in IIS | Microsoft Learn).
- 로그 파일 보존 및 관리: 로그는 운영상의 중요한 데이터이지만 방대해질 수 있으므로 보존 주기를 정하고 주기적으로 아카이빙/삭제합니다. 예를 들어 6개월치 로그는 실시간 디스크에 두고, 그 이전은 압축하여 별도 저장소에 보관하거나 삭제합니다. Windows의 작업 스케줄러나 PowerShell 스크립트를 이용해 오래된 로그 정리 작업을 자동화할 수 있습니다. 또한 로그 폴더에 대한 접근 권한을 최소화하고(예: Administrators만), 개발자에게 필요한 경우 별도 복사본을 제공하는 식으로 운영합니다.
- 실시간 로그 확인: 문제 발생 시 실시간으로 로그를 모니터링하려면 **Failed Request Tracing (FREB)**과 실시간 로그 스트림을 활용합니다. FREB은 특정 조건(예: 500 오류)에 대한 상세 추적 로그를 남겨주며, IIS Manager에서 사이트의 Failed Request Tracing를 설정하고 원하는 상태코드나 시간대 조건을 추가하여 사용합니다. 일반 접속 로그를 실시간으로 보고 싶다면 Powershell의 Get-Content -Tail -Wait로 로그 파일 모니터링이 가능합니다.
- 로그 분석: 수집된 IIS 로그는 Log Parser 도구나 Splunk, Azure Monitor, Elastic Stack 등을 통해 분석할 수 있습니다. Microsoft의 Log Parser 2.2(무료 도구)는 SQL-like한 쿼리로 로그 파일에서 유의미한 정보를 추출할 수 있습니다. 예를 들어, “응답 시간 3초 이상인 요청 top 10 URL” 또는 “상태코드 500 발생 수 시간별 추이” 등을 쉽게 뽑아낼 수 있습니다. Log Parser 예시 쿼리:위 쿼리는 500 오류가 가장 많이 발생한 URL 10개를 집계하는 예입니다. 이처럼 정기적인 로그 분석으로 에러 패턴, 응답 시간 추이, 인기 자원 등을 파악하여 용량 계획 및 튜닝에 활용합니다.
- SELECT TOP 10 cs-uri-stem, COUNT(*) as Hits FROM ex*.log WHERE sc-status = 500 GROUP BY cs-uri-stem ORDER BY Hits DESC;
- 보안 고려: 로그에는 IP, URL 등 민감 정보가 포함될 수 있으며, 경우에 따라 QueryString에 사용자 입력 데이터가 남기도 합니다. 개인정보나 민감정보가 쿼리스트링으로 노출되지 않도록 애플리케이션 측에서 POST 사용이나 암호화 등의 대책을 세워야 합니다. 또한 **로그 자체에 민감한 데이터(PII)**가 포함될 경우 처리 방침을 마련하고 필요 시 익명화하거나 수집하지 않아야 합니다.
정리하면, IIS 로그는 활성화하고 체계적으로 관리하여야 하며, 문제 해결과 성능/사용자 분석에 적극 활용해야 합니다.
로그가 방대한 경우 로그 중앙화 시스템(ELK, Azure Log Analytics 등)을 도입하는 것도 고려해볼 수 있습니다.
4.2 ASP.NET Core 로깅 (Serilog, NLog 활용)
IIS 로그는 웹 서버 관점의 정보만 담고 있으므로, 애플리케이션 내부 로깅을 별도로 구현하는 것이 중요합니다.
ASP.NET (특히 Core)은 Microsoft.Extensions.Logging 기반의 일관된 로깅 API를 제공하며, 다수의 로깅 프레임워크들과 연동됩니다.
대표적으로 Serilog, NLog, log4net 등을 많이 사용합니다.
- 빌트인 로깅 vs 서드파티: .NET Core의 기본 로깅은 Console, Debug, EventSource 등에 출력할 수 있으며, 구성이 간단하지만 로그를 파일이나 DB 등에 저장하려면 구현이 필요합니다. Serilog나 NLog 같은 라이브러리를 사용하면 **구성파일 기반으로 풍부한 대상(sink)**에 쉽게 로깅할 수 있고, 구조적 로그(JSON 등) 출력, 필터링 등의 고급 기능을 활용할 수 있습니다. 엔터프라이즈에서는 Serilog나 NLog를 많이 채택하며, Azure 환경에서는 Application Insights 직접 연동도 고려됩니다.
- Serilog 설정 예시: Serilog는 간단한 초기화 코드와 설정으로 파일 로깅 등을 할 수 있습니다. 예를 들어, ASP.NET Core의 Program.cs에서 다음과 같이 Serilog를 설정할 수 있습니다.그리고 appsettings.json에 Serilog 관련 설정 섹션을 두면 (Serilog:WriteTo 등), 코드에서 ReadFrom.Configuration로 불러와 적용할 수도 있습니다. 위 설정은 일 정보 로그 파일 롤링을 설정한 예로, Logs 폴더에 매일 새로운 로그 파일(myapp-YYYYMMDD.log)을 생성합니다. Serilog를 쓰면 로그 메시지를 변수 바인딩하여 구조적으로 기록할 수 있어 추후 쿼리에 용이합니다 (예: Log.Information("User {UserId} logged in from {IP}", user.Id, userIp)). 출력은 JSON으로도 가능하며, Kibana 등으로 분석하기 좋습니다.
- Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .Enrich.FromLogContext() .WriteTo.Console() .WriteTo.File("Logs/myapp-.log", rollingInterval: RollingInterval.Day) .CreateLogger(); builder.Host.UseSerilog();
- NLog 설정 예시: NLog는 nlog.config(XML) 또는 appsettings.json으로 설정이 가능합니다. 파일 대상, 이벤트 로그, DB 등 다양한 target에 로깅할 수 있습니다. 예를 들어 nlog.config에 파일 타겟과 규칙을 정의하고 최소 LogLevel을 Info로 지정하는 식입니다. ASP.NET Core에서는 NLog.Web.AspNetCore 패키지를 통해 NLog를 DI에 등록합니다.
- 로그 수준 관리: 로깅 프레임워크를 사용하면 로그 레벨(Trace, Debug, Info, Warn, Error, Fatal)을 세밀하게 조정할 수 있습니다. 운영 환경에서는 일반적으로 Info 이상만 기록하고 Debug는 끄는 것이 좋습니다. 그리고 카테고리별 필터링을 통해 너무 빈번한 로그(예: EF Core SQL 로그 등)는 제외하거나 별도 파일에 기록하도록 설정합니다. Serilog의 경우 appsettings.json에서 MinimumLevel.Override 설정으로 특정 네임스페이스 로거의 레벨을 재정의할 수 있고, NLog도 룰셋에서 로거 이름 패턴으로 필터링이 가능합니다.
- 로그 샘플링 및 비동기화: 트래픽이 많을 경우 모든 요청에 대한 상세 로그를 남기면 IO 부하가 커질 수 있습니다. 이럴 때 Serilog의 샘플링 기능이나, NLog의 Async target 등의 기능을 활용하여 성능 영향 없이 로그를 처리하도록 합니다. 예를 들어 Serilog SelfLog나 buffer 기능으로 일시적으로 로그를 메모리에 쌓았다가 쓰는 방식을 고려합니다.
- 보안 로그: 애플리케이션에서 중요한 보안 이벤트 (예: 로그인 시도, 권한 변경, 예외 발생 등)는 특별히 Security 레벨이나 Audit 로그로 남겨 별도로 모니터링할 수 있습니다. 예를 들어 여러 번 로그인 실패한 IP, 중요 설정 변경 등의 이벤트는 윈도우 이벤트 로그(Security 카테고리)에도 남겨 SOC팀에서 수집할 수 있게 하는 등 프로세스를 갖춥니다.
엔터프라이즈 로깅의 목표는, 문제가 발생했을 때 원인을 신속히 파악하고 운영 현황을 투명하게 파악하는 것입니다.
이를 위해 IIS 로그 + 애플리케이션 로그를 종합적으로 활용해야 합니다.
또한 로그는 실시간 모니터링과도 연계되어야 합니다 (다음 섹션 참고).
4.3 실시간 성능 모니터링 도구 (Application Insights, PerfMon 등)
운영 중 애플리케이션의 상태를 실시간으로 모니터링하면, 문제를 사전에 감지하고 대응할 수 있습니다.
앞서 PerfMon 등의 사용을 언급했지만, 더 나아가 애플리케이션 성능 관리(APM) 도구를 도입하는 것을 고려해야 합니다.
- Azure Application Insights: Azure 제공 모니터링 서비스로, ASP.NET(Core) 애플리케이션에 Telemetry SDK를 설치하면 실시간 요청 추적, 예외 보고, 성능 지표 등이 Azure 포털에 수집됩니다. Application Insights는 분산 추적(distributed tracing) 기능을 제공하여, 예를 들어 하나의 웹 요청이 웹 서버 -> API 서버 -> DB로 이어지는 호출 체인에서 어디서 지연이 발생했는지 파악할 수 있습니다. 또한 애플리케이션 지도(Application Map), 실시간 메트릭(Performance counters), 사용자 세션 추적 등 강력한 기능이 있어 엔터프라이즈에서 적극 활용 가치가 높습니다. .NET Core에서는 AddApplicationInsightsTelemetry() 호출만으로 쉽게 연동되고, Azure에 연결되지 않은 온프레미스 환경에서도 Application Insights를 설치해 Azure로 모니터링 데이터를 보낼 수 있습니다.
- 시스템 성능 모니터링 (PerfMon, Event Viewer): Windows PerfMon은 즉각적 시각화와 히스토리 기록에는 한계가 있으나, 특정 문제를 라이브로 볼 때 유용합니다. 또한 **이벤트 뷰어(Event Viewer)**의 Windows Logs\Application, System, Web Server 등을 주기적으로 확인하여 오류나 경고 이벤트가 있는지 살핍니다. 특히 .NET Runtime의 Unhandled Exception이나 IIS-W3SVC-WP의 오류 이벤트는 문제 징후일 수 있으므로 주시합니다. 이벤트 뷰어 로그도 Windows 자체 기능인 **이벤트 구독(Event Subscriptions)**이나 외부 시스템으로 포워딩하여 중앙 관리할 수 있습니다.
- 서드파티 APM: Application Insights 외에도 New Relic, Dynatrace, Datadog APM, Elastic APM 등 다양한 상용/오픈소스 솔루션이 있습니다. 이러한 APM 도구는 코드 레벨 프로파일링, 메모리/CPU 분석, **사용자 경험 모니터링(RUM)**까지 포함하는 경우가 많아, 엔터프라이즈 수준의 가시성을 제공합니다. 조직의 예산과 요건에 따라 적절한 APM을 도입하면 성능 문제 해결에 큰 도움이 됩니다.
- 실시간 대시보드: 모니터링 결과를 대시보드화하여 운영팀이 실시간 확인하도록 하는 것도 중요합니다. Azure Dashboard나 Grafana 등을 이용해 주요 성능 카운터(예: CPU, 메모리, 요청수, 에러수 등)를 시각화하고, 벽면 스크린 등에 띄워두면 현재 상태를 누구나 쉽게 파악할 수 있습니다. 또한 임계치 이상일 때 PagerDuty, 문자, 이메일 등으로 즉시 알림이 가도록 설정합니다.
- PerfView 및 Diagnostics: 개발/운영 중 특정 성능 문제(예: CPU 스파이크, GC 과다 등)가 반복될 경우, Microsoft의 PerfView 툴로 ETW(Event Tracing for Windows) 데이터를 수집해보는 고급 기법도 있습니다. PerfView는 ETW 이벤트를 분석하여 CPU 히트, 할당 힙 크기, LOH 상태 등을 프로파일링할 수 있습니다. 이는 심층적인 진단에 쓰이며, 보통 문제 발생 직후 ETW trace를 짧게 받아 분석하는 식으로 활용합니다.
핵심은, 정상 패턴과 이상 패턴을 정의하고 모니터링을 지속하는 것입니다.
예를 들어 “평소 요청 처리 시간 100ms 이하인데 1초 이상이 지속된다”거나 “메모리 사용이 시간당 100MB 이상 증가한다” 등의 상황을 포착할 수 있어야 합니다.
이를 위해서는 초기 정상 상태의 벤치마크를 측정해두고, 모니터링 임계값을 설정하는 작업이 필요합니다.
4.4 오류 추적 및 관리 (ELMAH, Application Insights 등)
런타임 오류는 완전히 피할 수 없으므로, 발생 시 이를 잘 추적하고 기록하여 신속히 수정하는 것이 중요합니다.
ASP.NET 환경에서 사용할 수 있는 오류 추적/관리 기법들은 다음과 같습니다.
- ELMAH (Error Logging Modules and Handlers): .NET Framework 시대부터 인기있던 오픈소스 오류 로깅 모듈입니다. 간단한 Web.config 설정만으로 Unhandled Exception을 캐치하여 SQL DB, XML 파일, Email 등으로 로그를 남기고, /elmah.axd 경로로 누적된 오류를 조회할 수 있습니다. 현재 .NET Core에는 직접 사용할 수는 없지만 유사 아이디어로 Serilog Sinks나 자체 미들웨어를 활용할 수 있습니다. ELMAH의 장점은 설치가 간편하고 별도 코드 수정 없이 전역 예외를 포착한다는 점입니다. 단, 보안상 elmah.axd 접근을 제한해야 하며, 너무 빈번한 오류(예: 404)에 대해서는 필터링 설정을 하는 것이 좋습니다.
- Application Insights - 예외: Application Insights를 사용하면, 발생한 예외(Exception)가 자동으로 수집됩니다. try-catch로 잡지 않은 전역 예외는 물론, ILogger로 기록한 예외도 함께 전송되므로, 포털에서 Failures 블레이드에서 어떤 오류가 몇 번 발생했는지, 샘플 스택 트레이스는 무엇인지 볼 수 있습니다. 또한 Snapshot Debugger 기능을 활성화하면 예외 시 메모리 덤프 비슷한 상태를 캡처하여, 재현 어려운 이슈를 진단하는 데 활용할 수도 있습니다.
- 개발 환경 vs 운영 환경 오류 노출: Web.config의 <customErrors> 설정을 통해 운영 모드에서는 사용자에게 친절한 오류 페이지를 보여주고, 개발 모드에서는 상세 스택 트레이스를 표시할지를 결정합니다. ASP.NET Core에서는 env.IsDevelopment() 조건으로 Developer Exception Page를 사용할지 여부를 결정합니다. 엔터프라이즈 환경에서는 상세 오류가 외부에 노출되지 않도록 반드시 운영 모드에선 잡아줘야 합니다. 대신 상세 오류는 위에서 언급한 로깅 시스템(ELMAH, App Insights 등)을 통해 개발자에게 전달되도록 해야 합니다.
- Failed Request Tracing (FREB): IIS의 FREB는 특정 상태코드나 요청 시간 초과 등의 조건에 해당하는 요청의 상세 실행 로그를 남겨줍니다. 예컨대 페이지가 간헐적으로 매우 느릴 때 FREB를 설정해두면, 내부적으로 어느 모듈에서 지연되었는지, ASP.NET Handler에서의 처리 시간은 얼마였는지 등의 세부 이벤트 타임라인을 xml로 기록해줍니다. FREB 설정은 IIS Manager > 사이트 > Failed Request Tracing 규칙 추가에서 상태 코드(또는 시간) 조건을 지정하여 활성화하며, 기본 저장 경로는 %SystemDrive%\inetpub\logs\FailedReqLogFiles입니다. FREB 로그는 문제의 원인 구간을 좁히는 용도로 활용하면 좋습니다. (예: 인증 모듈에서 지연인지, 애플리케이션 코드 문제인지 식별)
- 사용자 피드백 및 알림: 엔터프라이즈 앱에서는 사용자로부터 버그 리포트나 오류 신고를 받을 수 있는 채널도 필요합니다. 하지만 궁극적으로는 위의 로그 시스템을 통해 사용자 신고 전에 이미 문제를 파악하고 있어야 이상적입니다. 중요한 오류 (예: 결제 실패 등)는 발생 즉시 SMS나 이메일 알림이 개발/운영 담당자에게 발송되도록 구성하기도 합니다. 예를 들어 Application Insights Alert 규칙을 설정하여 1분간 500 오류 10건 이상이면 운영팀 메일로 경고를 보내는 식입니다.
- 예외 분류와 대응: 로그에 쌓이는 예외들도 **치명도(Level)**에 따라 분류하여 대응 프로세스를 만듭니다. NullReferenceException, SQL Exception 등은 개발팀에 티켓을 열어 수정하도록 하고, 404 Not Found 같은 것은 운영팀에서 라우팅을 수정하거나 무시해도 될 수 있습니다. 즉, Known Issue와 Unknown Issue를 분리하고, Known Issue는 문서화하여 반복 발생시 빠르게 대처하도록 합니다.
결론적으로, 로그와 모니터링은 애플리케이션의 눈과 귀입니다.
설정을 체계적으로 하고, 이를 정기적으로 검토하며, 문제가 생겼을 때 로그를 가장 먼저 확인하는 습관을 들여야, 장애로 인한 영향을 최소화하고 시스템을 지속적으로 개선할 수 있습니다.
5. 문제 해결
마지막으로, 성능 저하나 오류 상황이 발생했을 때 원인을 파악하고 해결하는 방법을 다룹니다. 앞서 언급한 최적화와 설정들을 제대로 했더라도, 실제 트래픽 환경에서 예상치 못한 문제가 일어날 수 있습니다.
대표적인 문제 시나리오와 그 대응 방안을 정리합니다.
5.1 일반적인 성능 저하 원인 및 해결 방법
성능 저하의 원인은 애플리케이션, 데이터베이스, 인프라 등 다양합니다.
여기서는 IIS/ASP.NET 관점에서 흔한 원인을 짚어보겠습니다.
- 데이터베이스 또는 외부 API 병목: 웹 서버 자체는 여유가 있는데 페이지 로딩이 느리다면, 대개 DB 쿼리 지연이나 외부 서비스 호출 대기 때문입니다. 이런 경우 애플리케이션 로그나 Application Insights의 Dependency 추적을 통해 어떤 쿼리/호출이 지연되는지 파악해야 합니다. 해결책은 쿼리 최적화, 인덱스 추가, 캐싱 도입, 외부 호출 병렬화/비동기화 등입니다. 또한 DB 커넥션 누수나 대기열 과다 현상도 검토해야 합니다.
- 서버 자원 고갈(CPU, 메모리): CPU 100%가 지속되면 요청 처리 시간이 느려집니다. 원인이 애플리케이션 코드 루프일 수도 있고, GC가 너무 자주 일어나서일 수도 있습니다. PerfMon의 % Time in GC 카운터를 보아 GC가 과다하게 CPU를 쓰는지 확인하고, 필요하면 Gen2 객체 사용을 줄이는 튜닝을 합니다. 메모리 부족 역시 성능에 영향을 주는데, 작업세트가 커져서 페이지 파일 스왑이 발생하면 응답이 크게 느려집니다. Windows 리소스 모니터에서 물리 메모리 및 스왑 사용을 점검하고, 메모리가 모자라면 증설을 검토해야 합니다.
- 과도한 세션이나 캐시 사용: InProc 세션에 대량의 데이터를 넣거나, 메모리 캐시에 지나치게 큰 객체를 저장하는 것은 메모리 압박 및 직렬화 부하를 유발할 수 있습니다. 세션 상태는 가능한 작게 유지하고, 대용량 데이터는 분산 캐시나 DB로 오프로드합니다. Output Cache도 너무 많은 변형(vary) 조건으로 설정하면 메모리를 잡아먹으니 캐시 키를 적절히 설계해야 합니다.
- 동기식 IO 및 쓰레드 대기: ASP.NET에서 외부 IO작업(파일, 네트워크 등)을 동기로 처리하면 해당 쓰레드가 놀게 되고, 동시 처리율이 떨어집니다. .NET에서는 반드시 비동기 async IO로 전환하여 스레드 풀 스타베이션(고갈) 문제를 예방해야 합니다. 오래 걸리는 연산도 Task.Run으로 태스크로 보내거나 큐로 비동기 처리하는 등 웹 요청 파이프라인을 가볍게 유지합니다.
- 잘못된 IIS 설정: 간혹 설정상의 문제가 성능을 해칠 수 있습니다. 예를 들어, 압축을 켰는데 특정 MIME 타입(이미 압축된 응답)을 굳이 압축하느라 CPU 낭비를 한다거나, 출력 캐싱을 켰지만 쿠키나 Authorization 헤더 때문에 캐시가 계속 무효화된다거나, 또는 HTTP/2가 이득인데 HTTP/2를 비활성화해놓은 경우 등이 있을 수 있습니다. 권장 설정을 다시 한번 점검하고, 필요하면 Microsoft Best Practice Analyzer 같은 도구의 조언을 검토합니다.
- GC 모드/Large Object Heap 단편화: 특별한 경우이지만, .NET Framework에서 LOH 단편화로 인해 메모리는 남아 있어도 큰 객체 할당이 어려워지면 GC가 빈번히 발생하며 CPU를 잡아먹습니다. 이때는 LOH Compaction (Server GC에서는 .NET 4.5.1+ 가능)을 시도하거나, 애플리케이션에서 큰 객체 할당 패턴을 개선해야 합니다. .NET Core에서는 LOH compaction이 기본으로 지원되므로 문제가 줄었지만, 여전히 대용량 컬렉션을 반복 생성/파괴하면 성능 저하가 생길 수 있으므로 풀링 등을 고려합니다.
- 모듈/핸들러 병목: IIS 파이프라인에서 실행되는 모듈(예: 인증, 압축, URL Rewrite 등) 중 혹시 특정 모듈이 느린 경우가 있으면, 그것이 전체 요청을 지연시킬 수 있습니다. FREB 로그나 Application Insights의 transaction view에서 각 단계별 소요 시간을 확인하여, 어느 단계에서 지연이 큰지 분석합니다. 필요하면 사용하지 않는 모듈은 제거/비활성화하여 파이프라인을 단순화합니다.
요약하면, 성능 저하는 **“어디서 시간이 소비되는가”**를 알아내는 게 핵심입니다.
CPU, 메모리, IO, 락경합 등 여러 자원 중 무엇이 임계인지 파악하고, 해당 부분을 개선하거나, 병목인 부분을 분산/병렬화하여 해결합니다.
최적화는 늘 측정이 선행되어야 함을 기억해야 합니다.
5.2 HTTP 오류 코드 (500, 503, 404 등) 해결 방법
운영 시 자주 접하거나 심각한 영향이 있는 HTTP 오류별 원인과 대처법을 정리합니다.
- 500 Internal Server Error: 애플리케이션에서 처리되지 않은 예외가 발생하거나 구성 오류가 있을 때 나타납니다. 사용자가 500 에러를 봤다면 이미 애플리케이션 로직에는 예외가 터져나온 것이므로, 이를 로그에서 상세히 확인해야 합니다. 500 에러 발생 시점의 IIS 로그 (sc-status 500과 sc-substatus, win32-status)를 확인하면 힌트가 될 수 있습니다. 예를 들어 500 0 0은 애플리케이션 예외인 경우가 많고, 500 19는 잘못된 config로 사이트 시작 실패, 500 24는 중복된 확장명 매핑 등 특정 설정 문제입니다. 개발 환경에서는 로 상세 오류를 보고, 운영에선 해당 오류를 전송받아 문제 라인을 파악해야 합니다. NullReferenceException, SQL Exception, HttpRequestValidationException 등 흔한 예외는 코드 수정이 필요하며, DLL 호환성 문제나 web.config 잘못된 섹션 등으로 인한 500은 배포 절차에서 검증을 강화해야 합니다.
- 503 Service Unavailable: 서비스 이용 불가 오류로, IIS 레벨에서 애플리케이션 풀을 서비스할 수 없을 때 발생합니다. 일반적인 원인은 애플리케이션 풀停止입니다 (Understanding and Resolving the HTTP Error 503 Service ...). 풀 정지가 일어나는 경우: 1) 수동으로 풀을 Stop했거나, 2) 연속된 크래시로 Rapid-Fail Protection이 작동해 풀이 중지된 경우, 3) 풀 아이덴티티 계정 비밀번호 변경 등으로 프로세스 시작 실패한 경우, 4) IIS의 Queue Length 초과로 더 이상 요청을 받을 수 없는 경우 등이 있습니다. 이벤트 로그에 WAS 또는 W3SVC 오류 이벤트가 기록되었는지 확인합니다. 예컨대 A process serving application pool '<poolname>' suffered a fatal communication error 등이 있다면 워커 프로세스가 크래시한 것이므로, 그 시간대 Windows Application 이벤트에 .NET 예외가 있는지 봅니다. 503 해결은 원인에 따라: 크래시라면 해당 예외 수정, 아이덴티티 이슈라면 계정 권한/암호 재설정, Queue Length라면 일시적 폭주 트래픽 처리 (스케일아웃) 등이 필요합니다. 또한, 503이 발생하면 IIS는 HttpErr 로그에도 기록을 남기므로 (C:\Windows\System32\LogFiles\HTTPERR), Connection refusal이나 TimerExpire 등의 낮은 단계 오류를 확인할 수도 있습니다.
- 404 Not Found: 요청한 리소스를 찾을 수 없을 때 발생합니다. 일반적으로는 잘못된 URL이나 링크로 인해 존재하지 않는 경로를 요청한 경우이지만, 설정 문제로 의도한 리소스가 404가 나는 경우도 있습니다. 예를 들어 정적 파일이 404라면 실제 파일 존재 여부와 함께 MIME 타입 설정을 확인해야 합니다. IIS는 등록되지 않은 확장자는 기본serving하지 않으므로, 새로운 파일 타입(.json, .apk 등)을 제공하려면 MIME 타입을 추가해야 합니다. 또는 라우팅 시스템을 사용하는 앱(.NET MVC, Web API, .NET Core 등)에서 라우트 설정에 매칭되지 않으면 404가 날 수 있습니다. 이런 경우에는 라우트 테이블을 점검하거나, MapFallback 등을 활용하여 사용자 친화적인 404 처리 페이지로 유도합니다. 한편, URL 대소문자 문제로 리눅스 서버에서 404가 나는 경우도 있지만, IIS/Windows는 기본적으로 대소문자 구분을 하지 않으므로 해당 사항은 없습니다. 404 오류는 로그를 통해 어떤 URL이 404였는지 주기적으로 분석하여, 자주 발생하면 리디렉션을 걸어준다든지, 누락된 리소스를 배포하는 등의 조치를 취합니다.
- 401 Unauthorized/403 Forbidden: 이 오류들은 인증/권한 이슈입니다. 401은 인증되지 않은 사용자가 보안 리소스에 접근한 경우, 403은 인증되었으나 권한이 없는 경우 또는 아예 접근이 금지된 경우(IP 차단 등)입니다. Windows 인증 사용 시 잘못된 도메인 사용자이거나, 권한 부여 규칙이 막았거나, 또는 IP Restriction에 걸리면 403이 나옵니다. 원인에 따라 올바른 자격증명을 제공하거나 IIS 인증 설정을 조정합니다. 개발 중이라면 Anonymous Authentication이 꺼져있어서 생긴 401인지 확인해보고, 필요하면 켜거나 web.config에 <authorization> 설정을 조정합니다.
- 502 Bad Gateway: 만약 IIS를 역프록시(예: ARR)로 사용하거나 ASP.NET Core Out-of-Process 모델(Kestrel 뒤)로 사용한다면 502 오류가 발생할 수 있습니다. 이는 프록시인 IIS 입장에서 백엔드 애플리케이션 (예: kestrel.exe)이 응답을 제대로 안주거나 연결이 안될 때 나타납니다. Event Log에 ASP.NET Core Module(ANCM) 관련 에러가 없는지 보고, 해당 백엔드 프로세스의 로그를 살펴야 합니다. 흔한 원인으로는 백엔드 프로세스 크래시, 포트 바인딩 문제, 요청 헤더 크기 초과 등이 있습니다. 해결은 각각 코드 수정/재배포, 포트 설정 수정, 또는 AspNetCoreModule 설정(예: forwardWindowsAuthToken false 설정이나, maxRequestBodySize 조정) 등이 필요할 수 있습니다.
오류 원인을 파악하는 데는 IIS 로그 + Windows 이벤트 로그 + 애플리케이션 로그의 삼박자가 중요합니다.
또한 동일 오류라도 환경마다 원인이 다를 수 있으므로, 변경된 부분(Deploy)이 있었는지, 특정 트래픽 패턴과 연관있는지 등도 함께 고려해야 합니다.
5.3 메모리 누수 및 GC 튜닝
**메모리 누수(memory leak)**는 장기 실행 애플리케이션을 괴롭히는 문제입니다.
.NET은 가비지 컬렉션으로 메모리를 관리하지만, 개발자가 놓친 객체 참조나 언매니지드 자원 때문에 누수가 발생할 수 있습니다.
증상은 시간이 지날수록 w3wp 프로세스 메모리 사용량이 증가하고, 결국 성능 저하나 OOM 예외, 시스템 스왑 유발 등으로 이어집니다.
메모리 누수 진단과 해결:
- 증상 모니터링: PerfMon에서 프로세스 Private Bytes 혹은 .NET 메모리 카운터(# Bytes in All Heaps 등)를 추적하여 증가 추세를 관찰합니다. 짧은 시간 내 증가는 캐싱 등의 정상 동작일 수 있지만, 요청 처리량에 비해 지속적인 증가가 있으면 누수를 의심해야 합니다. Generation 2 GC 횟수가 일정 주기 이후에도 계속 늘어난다면 메모리가 회수되지 않고 쌓이는 것일 수 있습니다.
- 메모리 덤프 분석: Debug Diagnostics Tool(닷넷 메모리 분석 기능 지원)이나 Visual Studio, WinDbg/SOS 등을 이용해 메모리 덤프를 떠서 어떤 객체들이 누적되고 있는지 분석합니다. 예를 들어 특정 클래스 인스턴스 수가 비정상적으로 많거나, Static 컬렉션에 데이터가 계속 추가되고 있는지 등을 확인합니다. DotMemory 같은 상용 툴도 편리합니다.
- 원인 코드 수정: 누수가 발견되면 코드에서 대응해야 합니다. 대표적인 메모리 누수 패턴은 이벤트 핸들러를 등록하고 해지 안 한다거나, Static 리스트를 사용한다거나, IDisposable 객체(파일, DB 연결 등)를 .Dispose() 호출 없이 방치하는 경우 등입니다. .NET에서는 프로파일링과 리뷰로 이런 부분을 찾아내어 수정해야 합니다. 또한 캐시 사용 시 절대 만료나 슬라이딩 만료를 설정하여 영구히 쌓이지 않게 하고, 필요 없어진 캐시 항목은 제거합니다.
- GC 튜닝: 메모리 문제가 있을 때 GC 설정을 만지는 것은 마지막 수단입니다. .NET Framework에서는 server/workstation GC 전환, concurrent GC 설정 등이 있었으나, 일반 웹서버는 Server GC+Background GC 조합이 최적입니다. .NET Core에서는 대부분 GC가 자동 최적화되지만, GCHeapHardLimit 등을 통해 프로세스별 메모리 상한을 지정하는 고급 설정도 있습니다. 그러나 이보다는 IIS의 App Pool Private Memory Limit을 통해 프로세스 재활용으로 누수 문제를 완화하는 것이 현실적입니다 (Recycling Settings for an Application Pool | Microsoft Learn). 누수를 바로 고칠 수 없다면 임시로 메모리 한도를 설정해두어 일정 이상에서 풀을 재시작하게 하여 서비스 다운을 막습니다 (근본해결은 아니므로 병행해서 코드 수정 진행).
- Large Object Heap(LOH) 관리: LOH에 할당된 >85KB 객체들은 세대별 수집에 포함되지 않고 Gen2 컬렉션 때만 회수됩니다. 큰 배열이나 이미지, 문자열 등을 많이 다루는 앱이라면 LOH 단편화로 메모리가 실질적으로 누수처럼 사용못하게 될 수 있습니다. .NET Framework 4.5.1 이상에서는 GCSettings.LargeObjectHeapCompactionMode를 CompactOnLargeObjectHeap으로 설정하고 Full GC를 호출하면 LOH를 통합할 수 있지만, 실행 중에 할 순 없으므로, 주로 Maintenance 모드에서 호출합니다. .NET 5+에서는 LOH compaction이 백그라운드로 자동향상되었습니다. 근본적으로 LOH 문제를 줄이려면 큰 객체 할당을 줄이고, 풀링(reuse) 패턴을 사용하는 게 좋습니다.
- 해제된 객체 참조 유지: 가끔 버그로 컬렉션에서 객체를 제거했는데도 참조가 유지되는 경우가 있습니다. WeakReference를 써야 하는데 Strong Reference로 남겨뒀다든지 하는 시나리오입니다. 이런 논리 버그는 찾기 어려우나, 메모리 덤프에서 해당 객체가 GC Root 체인으로 어디서 참조되는지 역추적하면 원인을 알 수 있습니다.
메모리 누수는 재현이 어렵고 관찰에 시간이 걸리므로, 일찍 징후를 발견하는 것이 중요합니다. 운영 초기에 메모리 사용 그래프를 잘 관찰하고, 조금이라도 이상 징후가 있으면 개발팀과 함께 분석을 시작해야 합니다.
최악의 경우, 문제 해결 전까지 **주기적 재활용 (예: 하루 1번)**으로 영향만 완화하고 패치 시점을 조율하는 현실적인 방안도 취해질 수 있습니다.
5.4 장기 실행 요청 타임아웃 설정
어떤 작업은 일반 웹 요청보다 오랜 시간이 걸릴 수 있습니다.
예를 들어 대용량 파일 업로드/다운로드, 대규모 리포트 생성, 외부 시스템 대기 등이 있습니다. 기본 설정 하에서는 이러한 장기 요청이 타임아웃되어 중단될 수 있으므로, 시나리오에 맞는 타임아웃 조정이 필요합니다.
- ASP.NET 실행 타임아웃 (executionTimeout): .NET Framework의 <httpRuntime executionTimeout> 설정은 요청 한 개가 얼마 동안 실행될 수 있는지 제어하며, 기본 110초 정도입니다. 이 시간을 넘기면 ASP.NET이 ThreadAbortException을 발생시켜 요청을 강제 종료합니다. 장시간 걸리는 특정 페이지가 있다면, web.config에서 그 경로에 한해 <location path="LongTask.aspx"><system.web><httpRuntime executionTimeout="3600" /></system.web></location>처럼 별도로 타임아웃을 늘릴 수 있습니다. .NET Core에는 이 설정이 없고, Kestrel 자체에는 특별한 타임아웃이 없으나, IIS Integration에서는 requestTimeout(기본 2분) 설정이 있습니다. ASP.NET Core 모듈(ANCM) 사용 시 web.config의 <aspNetCore requestTimeout="00:10:00" />으로 조정 가능합니다.
- IIS 요청 타임아웃: IIS 자체의 Connection Timeout(기본 2분)은 정적 파일 요청 등에 적용됩니다. Idle Connection에 대한 타임아웃도 비슷한 값으로 존재합니다. 만약 다운로드가 2분 넘게 지속되면? 실제로는 다운로드는 지속 데이터 전송이 있어서 Idle로 간주되지 않아 계속 유지됩니다. 그러나 업로드의 경우, 클라이언트가 데이터를 다 못 보내 2분 넘게 걸면 커넥션 타임아웃이 일어날 수 있습니다. 대용량 업로드를 허용해야 한다면, 프런트에 NGINX 등으로 업로드를 받아서 백엔드로 전달하거나, AJAX로 chunk 분할하여 보내는 등 대안을 고려하는 게 낫습니다. 부득이 IIS로 직접 받는다면 connectionTimeout을 늘려야 합니다.
- 프록시/로드밸런서 타임아웃: IIS가 백엔드로서 Azure Front Door, NGINX, 클라우드 로드밸런서 뒤에 있다면, 그 프록시들의 대기 타임아웃 설정도 확인해야 합니다. 예를 들어 Azure Application Gateway는 기본 요청 대기 타임아웃이 20초(또는 30초)로 짧을 수 있습니다. 이럴 경우 IIS에서 오래 기다려도 프록시가 먼저 연결을 끊어버리니, 프록시 설정을 늘려야 합니다. 반대로, 프록시를 둔 환경에서는 클라이언트와의 직접 connectionTimeout보다는 프록시와의 연결 유지는 프록시가 관리하므로 약간 다른 레이어의 문제입니다.
- 장기 작업 패턴 개선: 기본적으로 웹 요청이 너무 오래 걸리지 않게 설계하는 것이 좋습니다. 가능한 한 비동기 처리나 오프라인 처리로 전환합니다. 예를 들어 5분 걸리는 보고서는 요청 시 작업을 시작하고 202 Accepted+작업 ID를 반환, 작업 완료 후 사용자에게 메일 보내거나, 사용자가 나중에 결과를 다운받게 하는 식으로 개선합니다. 실시간성이 중요하지 않은 작업은 Background Service나 배치 작업으로 돌리고, 웹 서버는 빠른 응답만 맡습니다.
- SignalR 및 WebSocket: 실시간 통신을 위해 SignalR이나 WebSocket 연결을 사용하는 경우, 해당 연결은 장기간 유지됩니다. IIS 8 이상의 WebSocket 모듈은 별도 Ping/Pong이나 Idleness 기반으로 타임아웃을 관리할 수 있습니다. 이런 persistent connection은 Timeout을 너무 짧게 하지 말고, 적절히 (몇 시간 또는 필요 시 무제한) 설정합니다. SignalR은 자체 KeepAlive 설정이 있으니 문서 참고 바랍니다.
- DB 쿼리 타임아웃: 웹 요청이 느릴 때, DB나 외부 API 호출이 응답이 없어서 무한 대기하는 경우도 있습니다. ADO.NET의 CommandTimeout 기본 30초 등이 있으나, 환경에 따라 설정되어 있지 않으면 웹 요청은 영원히 기다리게 됩니다. 따라서 DB 호출에도 타임아웃을 걸고, 외부 HTTP 호출도 HttpClient Timeout을 설정하는 것이 중요합니다. 이는 IIS 설정은 아니지만, 전체적인 요청 타임아웃 전략의 일부입니다.
문제 상황: 만약 사용자가 "다운로드 시작이 안된다"거나 "요청이 중간에 끊긴다" 등의 보고를 하면, 위 타임아웃 설정 중 어디서 끊긴 것인지를 로그와 네트워크 트레이스로 파악합니다.
예를 들어 2분쯤에 끊긴다면 IIS나 프록시 타임아웃을 의심할 수 있습니다.
이벤트 로그의 WAS, HTTPERR 등을 보아 timeout 로그가 있는지도 확인합니다.
본 가이드에서는 ASP.NET 및 IIS 환경에서 성능 최적화와 보안 강화를 위한 고급 설정들을 폭넓게 다루었습니다.
캐싱, 압축, 로드밸런싱 등의 기술로 속도를 높이고, TLS 설정, 인증, WAF 등으로 보안을 견고히 하며, 풀 관리와 모니터링으로 안정적 운영을 뒷받침하고, 트러블슈팅 노하우로 이슈 대응력을 높이는 것이 엔터프라이즈 운영의 핵심입니다.
마지막으로, 이러한 설정은 정적인 것이 아니며, 지속적인 점검과 개선이 필요합니다.
서버 OS와 .NET의 업데이트, 새로운 보안 이슈, 증가하는 트래픽에 따라 설정을 재평가하고 조정해야 합니다.
'IT개발' 카테고리의 다른 글
.NET Aspire 정식 출시 – 개발자를 위한 클라우드 네이티브 솔루션 (0) | 2025.03.24 |
---|---|
C#으로 아두이노와의 시리얼 통신 (0) | 2025.03.19 |
MQTT vs SignalR 비교 및 코드 예제 (0) | 2025.03.17 |
스마트팜 시스템 설계 및 구현: Arduino, SignalR, .NET MAUI 연동 가이드 (0) | 2025.03.17 |
C# LINQ와 Lambda 표현식 심층 활용하기 (0) | 2025.03.17 |