A production application is very slow — how would you investigate it end-to-end?
When a production application is slow, the first thing I would avoid is guessing. A common mistake is to immediately blame SQL Server, the frontend framework, Azure, or the API. A senior developer investigates the full request journey with evidence.
Let’s say the application uses Angular, NgRx, ASP.NET Core Web API, CQRS, Repository Pattern, SQL Server and stored procedures. A user clicks “View Customer Orders” and says the screen takes 10 seconds to load.
The full journey might look like this:
Angular Component
↓
NgRx Action
↓
NgRx Effect
↓
HTTP API Call
↓
ASP.NET Core Controller
↓
CQRS Query Handler
↓
Repository
↓
Stored Procedure
↓
SQL Server
↓
JSON Response
↓
NgRx Store Update
↓
Angular UI Rendering
I would start in the browser using Chrome DevTools. I would check the Network tab and ask: is the API call itself slow, or is the API fast but the UI is slow to render? If the API takes 8 seconds, the backend or database is likely involved. If the API returns in 200ms but the screen freezes, the issue may be Angular rendering, large data, change detection, or NgRx state updates.
Next, I would inspect NgRx DevTools. Sometimes the same action is dispatched multiple times because several components trigger the same load. For example:
this.store.dispatch(loadCustomerOrders({ customerId }));
If this action fires five times on one page load, the backend may be receiving duplicate requests. That is not a SQL problem; it is a frontend state-flow problem.
Then I would look at the API. I want structured logs with a correlation ID so I can trace one request across the backend:
logger.LogInformation(
"Loading orders for CustomerId {CustomerId}", customerId);
In ASP.NET Core, I would check Application Insights or another monitoring tool to identify slow endpoints, dependency calls, exceptions and SQL timings. If the endpoint is slow, I would inspect the controller, CQRS handler, repository and stored procedure.
A clean controller might look like this:
[HttpGet("{customerId:int}/orders")]
public async Task<IActionResult> GetOrders(
int customerId,
CancellationToken cancellationToken)
{
var result = await _mediator.Send(
new GetCustomerOrdersQuery(customerId),
cancellationToken);
return Ok(result);
}
The controller may be fine, but the handler or repository may be loading too much data. A bad repository might fetch all orders and filter in memory. A good repository fetches only what the screen needs, with pagination and projection.
At SQL level, I would check Query Store, actual execution plan, logical reads, CPU time, blocking, wait types, indexes, parameter sniffing and statistics.
For example:
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
EXEC dbo.GetCustomerOrders @CustomerId = 123;
The key reason behind this approach is simple: performance is a chain. The user does not care whether SQL is fast if Angular renders 50,000 rows and freezes. A senior developer follows the evidence from browser to database and back.