I have a busy ASP.NET 5 Core app (thousands of requests per second) that uses SQL Server. Recently we decided to try to switch some hot code paths to async
database access and... the app didn't even start. I get this error:
The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.
And I see the number of threads in the thread pool growing to 40... 50... 100...
The code pattern we use is fairly simple:
using (var cn = new SqlConnection(connenctionStrng))
{
cn.Open();
var data = await cn.QueryAsync("SELECT x FROM Stuff WHERE id=@id"); //QueryAsync is from Dapper
}
I made a process dump and all threads are stuck on the cn.Open()
line, just sitting there and waiting.
This mostly happens during application "recycles" on IIS, when the app process is restarted and HTTP requests are queued from one process to another. Resulting in tens of thousands requests in the queue, that need to be processed.
Well, yeah,, I get it. I think I know what's happening. async
makes the app scale more. And while the database is busy responding to my query, the control is returned to other threads. Which try to open more, and more, and more connections in parallel. The connection pool maxes out. But why the closed connections are not returned to the pool immediately after the work is finished?
Switching from async
to "traditional" code fixes the problem immediately.
What are my options?
- Increasing max pool size from the default
100
? Tried200
, didn't help. Should I try, like,10000
? - Using
OpenAsync
instead ofOpen
? Didn't help. - I thought I'm running into this problem https://github.com/dotnet/SqlClient/issues/18 but nope, I'm on a newer version of
SqlClient
and it's said to be fixed. Supposedly. - Not use
async
with database access at all? Huh... - Do we really have to come up with our own throttling mechanisms when using
async
like this answer suggests? I'm surprised there's no built-in workaround...
P.S. Taking a closer look at the process dump - I checked the Tasks
report and discovered literally tens of thousands blocked tasks in the waiting state. And there's exactly 200 db-querying tasks (which is the size of connection pool) waiting for queries to finish.
await
ing theQueryAsync
call. – Coccyxasync
doesn't flood the connection pool. High traffic combined with slow queries do. What do the actual queries do? Loading an entire table into memory is slow. Using long transactions causes blocking and thus increases the need for connections. CarelessUPDATE
s will block other writers and readers – SeuratResulting in tens of thousands requests in the queue, that need to be processed.
that means there's a need for tens of thousands of concurrent connections. That's not a SqlClient problem. That's the request wave problem that causes trouble in all architectures, languages and platforms. Throttling can only get so far. BTW throttling is available at several levels, from the load balancer to IIS (at least). The best way to handle such spikes though is to use queueing instead of direct HTTP calls, changing the number of queue workers to handle spikes as needed – SeuratTransactionScope
? This would keep any connections opened under it open until the TransactionScope itself is disposed. That's the only way changes in those connections could be committed or rolled back – Seuratasync
was introduced. If the app pool recycles, requests get queued. If the application can't handle all of them, it will get flooded, maybe start hitting 100% CPU which .... leads to another recycle. Blocking causes more CPU usage, which can lead to more recycles. – Seuratweb.config
or the IIS manager. The same can be done at the server level.The appConcurrentRequestLimit attribute specifies the maximum number of concurrent requests that can be queued for an application.
– Seuratproblem occurred long before async was introduced. If the app pool recycles, requests get queued. If the application can't handle all of them, it will get flooded
not if I usesync
code - it works just fine. – Porta