Después de buscar en todo el Internet, me encontré con los siguientes artículos de Microsoft que se relacionan con el problema:
KB 821268: Contention, poor performance, and deadlocks when you make Web service requests from ASP.NET applications
Este artículo ofrece algunos consejos excelentes para ajustar el rendimiento, sin embargo, no menciona algunos techos MUY importantes con los que nos encontramos.
La solución para nosotros fue modificar nuestra machine.config, y llenar los siguientes nodos XML:
<system.web>
<processModel autoConfig="false" maxWorkerThreads="xxx" maxIoThreads="xxx" minWorkerThreads="xxx" minIoThreads="xxx" requestQueueLimit="5000" responseDeadlockInterval="00:03:00"/>
<httpRuntime minFreeThreads="xxx" minLocalRequestFreeThreads="xxx"/>
</system.web>
propósito que establece algunos de estos números para "xxx", ya que dependen de su hardware.
En el artículo anterior de KB, Microsoft sugiere algunas ecuaciones para determinar estos valores. Sin embargo, no mencionan que el valor máximo para estos números es el tamaño de un INT, o 32767.
Así, los CORRECTO ecuaciones para calcular estos fuera son los siguientes:
- maxWorkerThreads: 32767/#Cores
- En nuestro caso, tenemos un servidor de 24 núcleos. Por lo tanto, nuestro valor maxWorkerThreads está ajustado para entrada: 1365. Cualquier número que se traduce en un entero mayor que 32767, el servidor fijará los maxWorkerThreads a 32767.
- maxIoThreads: Igual que maxWorkerThreads (32767/#Cores)
- minWorkerThreads: maxWorkerThreads/2
- Esto era un asunto difícil. Si se excedió un valor entero MÁS GRANDE que 32767 (ya pesar de lo que dice el artículo de KB, este número se multiplica por la cantidad de núcleos que tiene) y, a diferencia del valor "máximo" , el valor predeterminado es el número de núcleos en su máquina! En nuestro caso, esto se estaba estableciendo en 24 (porque establecimos un valor arbitrariamente alto para el mínimo), y eso era MATAR el rendimiento en nuestro servidor.
- minIoThreads: Lo mismo que minWorkerThreads
- minFreeThreads: 88 * #Cores (tomada directamente del artículo de KB)
- minLocalRequestFreeThreads: 76 * #Cores (tomada directamente del artículo de KB)
Esta solución no es para todos, y solo debe utilizarse si cumple con los criterios del artículo de KB.
Otra herramienta que utilizamos para ayudarnos a diagnosticar esto fue una página .ASPX sin código subyacente que pudimos tirar en cualquier servidor (sin restablecer el grupo de aplicaciones). Esta página usa la reflexión para indicarle qué está sucediendo realmente en el grupo de subprocesos y a qué se refieren los valores de estos ajustes en su servidor.
<%@ Page Language="C#" %>
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body { margin: 20pt; padding: 0pt; font-family: Verdana, "san-serif";}
fieldset { border-radius: 5px; border: none; background-color: #fff; margin: 10pt;}
fieldset.parent { background-color: #f0f0f0; }
legend { font-size: 10pt; color: #888; margin: 5pt; }
.ports div { padding: 10pt 0pt 0pt 0pt; clear: both; }
.ports div:first-child { padding: 0pt; }
.ports div div { padding: 0pt; clear: none; margin: 1pt; background-color: #eef; display: block; float: left; border: 5pt solid #eef; }
.ports div div:first-child { border-top-left-radius: 5pt; border-bottom-left-radius: 5pt; background-color: #ccf; border-color: #ccf;}
.ports div div:last-child { border-top-right-radius: 5pt; border-bottom-right-radius: 5pt; background-color: #ccf; border-color: #ccf; padding: 0pt 10pt 0pt 10pt; }
</style>
</head>
<body>
<%
Response.Cache.SetCacheability(HttpCacheability.NoCache);
int worker, workerMIN, workerMAX;
int port, portMIN, portMAX;
System.Threading.ThreadPool.GetAvailableThreads(out worker, out port);
System.Threading.ThreadPool.GetMinThreads(out workerMIN, out portMIN);
System.Threading.ThreadPool.GetMaxThreads(out workerMAX, out portMAX);
%>
<fieldset class="parent">
<legend>Thread Information</legend>
<fieldset>
<legend>Worker Threads</legend>
<div class="ports">
<div>
<div>Min: <%=workerMIN %></div>
<div>Current: <%=workerMAX - worker %></div>
<div>Max: <%=workerMAX %></div>
</div>
</div>
</fieldset>
<fieldset>
<legend>Completion Port Threads</legend>
<div class="ports">
<div>
<div>Min: <%=portMIN %></div>
<div>Current: <%=portMAX - port %></div>
<div>Max: <%=portMAX %></div>
</div>
</div>
</fieldset>
<fieldset>
<legend>Request Queue Information</legend>
<div class="ports">
<%
var fi = typeof(HttpRuntime).GetField("_theRuntime", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Static).GetValue(null);
var rq = typeof(HttpRuntime).GetField("_requestQueue", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(fi);
var fields = rq.GetType().GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
foreach (var field in fields)
{
string name = field.Name;
string value = "";
switch (name)
{
case "_localQueue":
case "_externQueue":
System.Collections.Queue queue = field.GetValue(rq) as System.Collections.Queue;
value = queue.Count.ToString();
break;
default:
value = field.GetValue(rq).ToString();
break;
}
%>
<div>
<div><%=name %></div>
<div><%=value %></div>
</div>
<%
//Response.Write(string.Format("{0}={1}<br/>", name, value));
}
%>
</div>
</fieldset>
</fieldset>
</body></html>
¿Por qué maxWorkerThreads sería inversamente proporcional a #Cores? es decir, cuantos más núcleos tenga maxWorkerThreads es menor. Tiene más sentido para mí que maxWorkerThreads sea x * #Cores, digamos maxWorkerThreads = 16 * #Cores. – DavidF
¿Qué tan seguro está de que minWorkerThreads y minIoThreads se multiplican por la cantidad de núcleos? ¿Eso es específicamente para .net 4.5? –
Aparentemente confirmado aquí - https://msdn.microsoft.com/en-us/library/system.web.configuration.processmodelsection.minworkerthreads(v=vs.100).aspx –