"The query is slow" is one of the most common misdiagnoses I see. Often, the query is fine, and the problem lies elsewhere. Next time you hit a slow endpoint, check these too.
ā”š§šµšæš²š®š± š½š¼š¼š¹ ššš®šæšš®šš¶š¼š»
You call DoSomethingAsync() and then block on it with .Wait() or .Result. That thread now sits there waiting, and another thread from the pool may be needed to finish the async work.
Under a burst of requests, this quickly eats up thread pool threads, and the whole API slows down, not just that endpoint.
ā”šš¼š»š»š²š°šš¶š¼š» š½š¼š¼š¹ š²š šµš®šššš¶š¼š»
Quite similar to thread pool starvation, but for database connections. Once the app has used up every available connection, new requests have to wait for one to free up. The query itself might take 5ms, but the request waits seconds before it even runs.
Common causes: connections or DbContexts not disposed properly, long-running queries, or a pool size that's too small for your load.
ā”šš¼š°šøš¶š»š“ š®š»š± šÆš¹š¼š°šøš¶š»š“
Locking is normal. SQL Server locks data while a transaction works with it, so another transaction can't make a conflicting change at the same time.
Blocking is when another transaction has to wait for that lock. A little is fine. It becomes a problem when a transaction holds the lock for too long, and your "slow" query is really just stuck in a queue.
ā”š¦š²šæš¶š®š¹š¶šš®šš¶š¼š»
Turning a large or deeply nested object into JSON takes time. Returning entities directly is a common trap because navigation properties can pull in huge object graphs. Return DTOs with only what the client needs.
ā”šš¶š±š±š²š» š½š²šæ-šæš²š¾šš²šš šš¼šæšø
Before a request reaches your controller, it may already go through logging, tracing, auth checks, and other middleware. After the controller, there's serialization, response logging, and so on.
Most of this is necessary. But an expensive logging setup or one extra DB call in an auth handler runs on every single request. Know what happens before and after your code runs.
There's still plenty to tune inside EF Core itself. But knowing where else time goes is what makes debugging faster.
What's the strangest cause you've found behind a "slow query"?
I share more engineering notes and production lessons on LinkedIn: Dipendra Neupane.
Top comments (1)
For the "not disposed properly" cause there's a direct signal: Microsoft.Data.SqlClient 3.0+ has an event counter,
number-of-reclaimed-connections, that counts connections the GC reclaimed because Close or Dispose was never called. Watching it in dotnet-counters next tonumber-of-free-connectionstells a leak apart from a pool that's just too small.