{"id":54298,"date":"2025-12-02T06:04:44","date_gmt":"2025-12-02T06:04:44","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=54298"},"modified":"2025-12-02T06:04:44","modified_gmt":"2025-12-02T06:04:44","slug":"dotnet-how-to-improve-an-application-performance-of-dotnet-a-complete-checklist","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/dotnet-how-to-improve-an-application-performance-of-dotnet-a-complete-checklist\/","title":{"rendered":"DOTNET: How to improve an application performance of DOTNET? A Complete Checklist"},"content":{"rendered":"\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">0. First principle: measure before tuning<\/h2>\n\n\n\n<p>Before touching settings:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use: <strong>PerfMon<\/strong>, <strong>dotnet-counters<\/strong>, <strong>dotnet-trace<\/strong>, <strong>Failed Request Tracing<\/strong>, IIS logs.<\/li>\n\n\n\n<li>Track at least:\n<ul class=\"wp-block-list\">\n<li>Requests\/sec<\/li>\n\n\n\n<li>Avg\/95th latency<\/li>\n\n\n\n<li>CPU %<\/li>\n\n\n\n<li>RAM \/ GC pauses<\/li>\n\n\n\n<li>HTTP errors (4xx\/5xx)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Change <strong>one thing at a time<\/strong> and compare.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1. OS &amp; Hardware-level optimizations<\/h2>\n\n\n\n<p>These are outside IIS but critical:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Use modern Windows Server<\/strong> (2019+ ideally) \u2013 better HTTP.sys, HTTP\/2\/3 support, TLS performance, etc.<\/li>\n\n\n\n<li><strong>Enough CPU &amp; RAM<\/strong>; avoid constant >80% CPU.<\/li>\n\n\n\n<li><strong>Use SSD\/NVMe<\/strong> for web content and logs.<\/li>\n\n\n\n<li><strong>Network<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Enable <strong>RSS\/Receive Side Scaling<\/strong>, <strong>offloads<\/strong> (checksum, LSO) if appropriate.<\/li>\n\n\n\n<li>Make sure NIC drivers are updated.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Power plan<\/strong>: set to <strong>High performance<\/strong> on servers.<\/li>\n\n\n\n<li>Disable unnecessary background services and scheduled tasks that contend for CPU\/IO.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">2. IIS application pool tuning<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">2.1 .NET CLR &amp; pipeline<\/h3>\n\n\n\n<p>For ASP.NET Core with ANCM:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use <strong>No Managed Code<\/strong> in app pool (since Core runs out-of-process) \u2013 avoids extra .NET overhead in w3wp.<\/li>\n<\/ul>\n\n\n\n<p>For classic ASP.NET:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use <strong>Integrated pipeline mode<\/strong> (not Classic) for better performance &amp; features.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2.2 Recycling<\/h3>\n\n\n\n<p>Recycling too often kills warm state and hurts latency:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Disable time-based recycling<\/strong> (e.g., every 29 hours) unless you have a strong reason.<\/li>\n\n\n\n<li>Prefer recycling on:\n<ul class=\"wp-block-list\">\n<li><strong>Specific memory limits<\/strong> (if leak suspected)<\/li>\n\n\n\n<li><strong>Configuration changes<\/strong> (default)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Use <strong>overlapped recycling<\/strong> and preloading to avoid cold start spikes.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2.3 Idle timeout &amp; start mode<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>For high-traffic \/ critical apps:\n<ul class=\"wp-block-list\">\n<li>Set <strong>Idle Time-out<\/strong> higher or <strong>0<\/strong> (no timeout).<\/li>\n\n\n\n<li>Enable <strong>AlwaysRunning<\/strong> + <strong>Application Initialization<\/strong> so app is always warm.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>For many small sites:\n<ul class=\"wp-block-list\">\n<li>Keep idle timeout to auto-suspend low-traffic apps to save RAM.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2.4 Queue length &amp; throttling<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>queueLength<\/code> (Application pool \u2192 Advanced settings):\n<ul class=\"wp-block-list\">\n<li>Increase from default (1000) if you see 503.2 (queue full) and backend is still healthy.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Consider <strong>CPU throttling<\/strong> only when you want fairness between sites; it can also become a bottleneck if set too low.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3. Site &amp; connection-level settings<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">3.1 Max concurrent connections and limits<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Tune <strong>connectionTimeout<\/strong>, <strong>maxConnections<\/strong>, and <strong>request limits<\/strong> (Request Filtering \u2192 maxAllowedContentLength, maxRequestLength).<\/li>\n\n\n\n<li>Too low \u2192 users get 400\/413; too high \u2192 risk of memory exhaustion during attacks.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3.2 HTTP\/2 &amp; TLS<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Enable <strong>HTTP\/2<\/strong> where possible \u2013 especially for many small static resources.<\/li>\n\n\n\n<li>Use <strong>modern cipher suites<\/strong> and <strong>TLS session resumption<\/strong> for lower CPU per SSL handshake.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3.3 Keep-alive &amp; pipelining<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Keep-alive <strong>enabled<\/strong> to reuse TCP connections.<\/li>\n\n\n\n<li>Keep-alive timeout: balance between:\n<ul class=\"wp-block-list\">\n<li>High enough to reuse connections<\/li>\n\n\n\n<li>Low enough to avoid too many idle connections consuming memory.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4. Caching (the big win)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">4.1 Static content caching<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Enable <strong>kernel-mode caching<\/strong> and <strong>static file caching<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Client cache: set <strong>long <code>Cache-Control<\/code> headers<\/strong> (<code>public, max-age=31536000<\/code>) for versioned static assets (CSS\/JS\/images with hashes).<\/li>\n\n\n\n<li>IIS Output Cache \/ HTTP.sys: cache frequently requested static responses in memory.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">4.2 Dynamic output caching<\/h3>\n\n\n\n<p>For pages whose output doesn\u2019t change per-user:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use <strong>Output Caching<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Cache by URL + query string + headers as needed.<\/li>\n\n\n\n<li>Use proper <strong>vary-by<\/strong> configuration (e.g., <code>VaryByParam<\/code>, <code>VaryByHeader<\/code>).<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>For ASP.NET Core:\n<ul class=\"wp-block-list\">\n<li>Use <strong>Response Caching middleware<\/strong> or application-level memory\/Redis cache.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Beware: wrong vary rules can show other users\u2019 data \u2192 test carefully.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">4.3 Partial caching<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>For classic ASP.NET: use <strong>fragment caching<\/strong> for expensive controls\/user controls.<\/li>\n\n\n\n<li>At app layer: cache heavy data (e.g., dropdown lists, configs) in memory\/Redis so IIS doesn\u2019t call DB every request.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">5. Compression &amp; content optimization<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">5.1 Static compression<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Enable <strong>static compression<\/strong> for text content (HTML, JS, CSS, JSON, XML).<\/li>\n\n\n\n<li>Don\u2019t compress:\n<ul class=\"wp-block-list\">\n<li>Already compressed formats (JPEG, PNG, MP4, ZIP, etc.).<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Make sure compression cache directory is on fast disk.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">5.2 Dynamic compression<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Enable <strong>dynamic compression<\/strong> carefully:\n<ul class=\"wp-block-list\">\n<li>Good for APIs returning JSON\/HTML.<\/li>\n\n\n\n<li>Watch CPU \u2013 heavy dynamic compression + high concurrency can become bottleneck.<\/li>\n\n\n\n<li>Use thresholds (e.g., only compress responses > some size).<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">5.3 Brotli \/ newer algorithms<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>On newer Windows \/ reverse proxies: enable <strong>Brotli<\/strong> for extra savings, especially on text content (if supported by your stack).<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">6. Modules, handlers &amp; pipeline trimming<\/h2>\n\n\n\n<p>Every extra module = extra work per request.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>For each site:\n<ul class=\"wp-block-list\">\n<li>Go to <strong>Modules<\/strong> and remove what you don\u2019t need:\n<ul class=\"wp-block-list\">\n<li><code>WebDAV<\/code>, <code>ASP<\/code>, <code>ISAPI<\/code>, <code>CGI<\/code>, etc., if unused.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>For static-only sites:\n<ul class=\"wp-block-list\">\n<li>Disable ASP.NET, authentication modules, etc.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>For API-only sites:\n<ul class=\"wp-block-list\">\n<li>You might not need session state \/ forms auth \/ etc.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<p>This reduces CPU per request and speeds up pipeline.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">7. Logging &amp; diagnostics tuning<\/h2>\n\n\n\n<p>Logging is essential but can slow you down if misconfigured.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>W3C access logs<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Use <strong>separate log directory<\/strong> on fast disk.<\/li>\n\n\n\n<li>Avoid logging <strong>fields you don\u2019t need<\/strong>.<\/li>\n\n\n\n<li>Roll logs daily or by size.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Failed Request Tracing (FREB)<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Enable only for troubleshooting specific status codes (e.g., 500, 503).<\/li>\n\n\n\n<li>Turn off once done.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Don\u2019t write massive logs from app to disk synchronously; use async logging + batching (Serilog\/Seq, ELK, etc.).<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">8. Request filtering &amp; security (for performance &amp; safety)<\/h2>\n\n\n\n<p>Tuning security helps performance by rejecting bad traffic early.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Request Filtering<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Limit maximum URL length, headers length, body size.<\/li>\n\n\n\n<li>Block unwanted file extensions, methods, verbs.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>IP restrictions \/ WAF<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Use <strong>IPRestrictions<\/strong> to block abusive IPs.<\/li>\n\n\n\n<li>Use external <strong>WAF \/ reverse proxy<\/strong> (F5, Nginx, Azure App Gateway, Cloudflare) to filter bots\/attacks before IIS.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Authentication<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Avoid enabling multiple auth schemes unnecessarily (e.g., Windows + Anonymous + Basic).<\/li>\n\n\n\n<li>Use the lightest authentication compatible with your requirements.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">9. Pooling &amp; concurrency strategy<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">9.1 Web gardens (multiple worker processes in one app pool)<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Generally <strong>NOT recommended<\/strong> for ASP.NET \/ ASP.NET Core apps that rely on in-process state (Session\/InMemory cache).<\/li>\n\n\n\n<li>Use <strong>scaling out with multiple servers or load balancer<\/strong> instead.<\/li>\n\n\n\n<li>Only consider web gardens when:\n<ul class=\"wp-block-list\">\n<li>Stateless app<\/li>\n\n\n\n<li>You understand affinity\/load-balancer behavior.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">9.2 Multiple sites on same server<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Make sure heavy sites don\u2019t starve lightweight ones:\n<ul class=\"wp-block-list\">\n<li>Separate app pools.<\/li>\n\n\n\n<li>Use CPU limits or move heavy sites to dedicated server.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">10. Content &amp; front-end optimizations (indirect IIS wins)<\/h2>\n\n\n\n<p>These don\u2019t change IIS itself, but dramatically reduce its load:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Use CDN<\/strong> for static assets (images, JS, CSS) \u2192 fewer hits to IIS.<\/li>\n\n\n\n<li><strong>Minify &amp; bundle<\/strong> JS\/CSS.<\/li>\n\n\n\n<li>Use <strong>image optimization<\/strong>:\n<ul class=\"wp-block-list\">\n<li>WebP\/AVIF where possible.<\/li>\n\n\n\n<li>Proper sizing; avoid 4K images for thumbnails.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Reduce number of HTTP requests (sprites, bundling, HTTP\/2 multiplexing).<\/li>\n<\/ol>\n\n\n\n<p>Less work for IIS = better performance and lower cost.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">11. ASP.NET \/ ASP.NET Core specific tuning (on top of IIS)<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>GC mode<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Use <strong>Server GC<\/strong> for high-throughput server apps.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Thread pool tuning<\/strong> only when necessary (usually defaults are fine).<\/li>\n\n\n\n<li>Avoid blocking calls (<code>.Result<\/code>, <code>.Wait()<\/code>) on async flows \u2192 thread starvation.<\/li>\n\n\n\n<li>Optimize EF Core \/ DB access:\n<ul class=\"wp-block-list\">\n<li>Use pooling, compiled queries, proper indexes.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Cache config &amp; lookups; avoid reading from disk\/DB each request.<\/li>\n<\/ol>\n\n\n\n<p>IIS is only part of the chain; app inefficiencies dominate many real issues.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">12. Scaling strategies with IIS<\/h2>\n\n\n\n<p>When vertical tuning isn\u2019t enough:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Scale out<\/strong> using:\n<ul class=\"wp-block-list\">\n<li>Multiple IIS servers behind a <strong>load balancer<\/strong> (hardware \/ software \/ cloud LB).<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Use <strong>sticky sessions<\/strong> if you rely on in-process session state; otherwise move session state to:\n<ul class=\"wp-block-list\">\n<li>SQL Server \/ Redis \/ distributed cache.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Use <strong>blue\u2013green deployments<\/strong> or <strong>slots<\/strong> (e.g., Azure App Service) to avoid downtime during releases.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">13. A simple priority checklist (what to do first)<\/h2>\n\n\n\n<p>If you want a quick actionable order for a new\/slow IIS app:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Measure<\/strong> (PerfMon + app metrics).<\/li>\n\n\n\n<li>Enable <strong>static &amp; dynamic compression<\/strong> (watch CPU).<\/li>\n\n\n\n<li>Set <strong>caching headers<\/strong> for static content.<\/li>\n\n\n\n<li>Trim <strong>unnecessary IIS modules<\/strong>.<\/li>\n\n\n\n<li>Configure <strong>app pool<\/strong>:\n<ul class=\"wp-block-list\">\n<li>No Managed Code (for ASP.NET Core).<\/li>\n\n\n\n<li>Disable frequent recycling.<\/li>\n\n\n\n<li>Increase queue length if necessary.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Enable <strong>Application Initialization + AlwaysRunning<\/strong> for warm startup.<\/li>\n\n\n\n<li>Move logs to <strong>fast disk<\/strong>, trim logged fields.<\/li>\n\n\n\n<li>Add <strong>CDN<\/strong> for static content if traffic is high.<\/li>\n\n\n\n<li>Optimize <strong>DB calls and app-layer caching<\/strong>.<\/li>\n\n\n\n<li>If still slow: consider <strong>scaling out<\/strong> with load balancer.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>0. First principle: measure before tuning Before touching settings: 1. OS &amp; Hardware-level optimizations These are outside IIS but critical: 2. IIS application pool tuning 2.1 .NET CLR &amp; pipeline&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[11138],"tags":[],"class_list":["post-54298","post","type-post","status-publish","format-standard","hentry","category-best-tools"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/54298","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=54298"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/54298\/revisions"}],"predecessor-version":[{"id":54299,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/54298\/revisions\/54299"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=54298"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=54298"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=54298"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}