A page took 15.6 seconds to load. Not the first load, not a cold cache. Every single time. Someone opened a ticket that just said “the list page is broken,” which is how users report “slow.”
The wrong first guess
I did the obvious thing and blamed a query. Opened the slow query log expecting one monster SELECT with a missing index. It wasn't there. Every query was fast, single-digit milliseconds, all of them. That's the tell, and it took me longer than I'd like to admit to actually see it: the problem wasn't a slow query. It was a hundred fast ones.
101 queries for one page load. One to fetch the list, then one more per row to load its relations, in a loop. The classic N+1. And the ORM made it invisible, because item.owner.name looks like reading a property. It isn't. It's a round trip to the database, and I'd hidden a hundred of them inside a single .map().
const items = await repo.findAll(); // 1 query
for (const item of items) {
item.owner = await ownerRepo.find(item.ownerId); // +1 query each
} // 100 rows → 101 queriesThe unsatisfying fix
The fix isn't clever, which is the annoying part. Fetch the relations in one shot. A join, an IN (...), or the ORM's eager-load. One query instead of a hundred.
const items = await repo.findAll({ include: ["owner"] }); // 1 query, joined15.6s down to 2.4s on that endpoint. I'll be honest, 2.4s still isn't fast, so Redis caching came next for the hot ones. But the bulk of the win was simply not talking to the database a hundred times to render one page.
Then I went looking, and found the same shape across 53 endpoints. Same lazy loop, different nouns. Same fix every time.
The habit that would've caught all of it
Count your queries per request and put the number in your logs. That's it. An endpoint that fires 100 queries will never be fast no matter how good each one is, and you can't fix a number you never look at. Once that count was in front of us, the bad endpoints basically raised their own hands.
The database is rarely slow. The number of times you talk to it usually is.