<p>I have a .net core API using Entity Framework Core. The DB context is registered in startup.cs like this:</p>
<pre><code>  services.AddDbContext&lt;AppDBContext&gt;(options =&gt;
         options.UseSqlServer(connectionString,
         providerOptions =&gt; providerOptions.CommandTimeout(60))); 
</code></pre>
<p>In connection string I set</p>
<pre><code>  Pooling=true;Max Pool Size=100;Connection Timeout=300
</code></pre>
<p>The controller calls methods in a service which in turn makes calls to aysnc methods in a repo for data retrieval and processing.</p>
<p>All worked well if concurrent user is under 500 during load testing. However beyond that number I start to see a lot of timeout expired errors. When I checked the database, there's no deadlock but I could see well over 100 connections in sleeping mode(the API is hosted on two kubernetes pods). I monitored these connections during the testing and it appeared that instead of current sleeping connections being reused, new ones were added to the pool. My understanding is entity framework core manages opening and closing connections but this didn't seem to be the case. Or am I missing anything?</p>
<blockquote>
<p>The error looks like this:</p>
<p><code>StatusCode&quot;:500,&quot;Message&quot;:&quot;Error:Timeout expired</code>.  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. Stack Trace:</p>
</blockquote>
<p>at</p>
<blockquote>
<p>Microsoft.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection
owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions
userOptions, DbConnectionInternal oldConnection, DbConnectionInternal&amp;
connection)\n</p>
</blockquote>
<p>at</p>
<blockquote>
<p>Microsoft.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection
outerConnection, DbConnectionFactory connectionFactory,
TaskCompletionSource<code>1 retry, DbConnectionOptions userOptions)\n   at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource</code>1
retry, SqlConnectionOverrides overrides)\n   at
Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides
overrides)\n</p>
</blockquote>
<p>at Microsoft.Data.SqlClient.SqlConnection.Open()\n   at</p>
<blockquote>
<p>Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenInternal(Boolean
errorsExpected)\n</p>
</blockquote>
<p>at</p>
<blockquote>
<p>Microsoft.EntityFrameworkCore.Storage.RelationalConnection.Open(Boolean
errorsExpected)\n   at
Microsoft.EntityFrameworkCore.Storage.RelationalConnection.BeginTransaction(IsolationLevel
isolationLevel)\n.....................</p>
</blockquote>
<p>An example of how the <code>dbcontext</code> was used:</p>
<p>the controller calls a method in a service class:</p>
<pre><code>  var result = await _myservice.SaveUserStatusAsync(userId, status);
</code></pre>
<p>then in <code>'myservice'</code>:</p>
<pre><code>  var user = await _userRepo.GetUserAsync(userId);

  ....set user status to new value and then

  return await _userRepo.UpdateUserAsync(user);
</code></pre>
<p>then in <code>'userrepo'</code>:</p>
<pre><code>  _context.user.Update(user);
   var updated = await _context.SaveChangesAsync();
   return updated &gt; 0;
</code></pre>
<p>Update:</p>
<p>Thanks very much to Ivan Yang who generously offered the bounty. Although I'm still investigating, I've learned a lot by reading all the comments and answers below. Here is what I've tried so far: I increased the pool size to 200 (I know it's not the right way to deal with the issue), increased the number of pods so that the API now runs on 4 pods and allocated more memory to each pod. The end result so far has been good:500 errors disappear completely with up to 2000 concurrent users. I will update this question with my findings after I try other options.</p>

<blockquote>
<p>Error:Timeout expired. 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.</p>
</blockquote>
<p>This is almost always a connection leak.  And here the fact that your queries are short-running, and you see idle connections on the server confirms it.  Somewhere you're leaving an open connection.</p>
<p>A DbContext will open/close the underlying connection, and return it to the pool on Dispose.  But if you start a transaction on a connection and don't commit or rollback, the connection will be segregated in the pool and won't be reused.  Or if you return an <code>IEnumerable</code> or a <code>DataReader</code> that never gets iterated and disposed, the connection can't be reused.</p>
<p>Look at the &quot;sleeping&quot; sessions to see what their last query was, and cross-reference that with your code to track down the call site that leaked the connection.  First try the DMVs, eg</p>
<pre><code>select s.session_id, s.open_transaction_count, ib.event_info
from sys.dm_exec_sessions s
cross apply sys.dm_exec_input_buffer(s.session_id,null) ib
</code></pre>
<p>Or start an Extended Events trace if necessary.</p>
