The marketing overview page in our admin tool ran over 80 blocking SQL queries on a single load. We opened a ticket expecting to find one bad query to tune.
That was the wrong expectation. The page had no single slow query. It had a blocking posture nobody had ever named as the problem, so nobody had ever fixed it.
What the page was actually doing
The production app is an older web application. The marketing overview is for internal ops: product performance, session counts, and summary metrics across the customer base. It looked normal and always had. It was just slow.
When we traced the execution path, the page turned out to rest on an implicit contract: the server finishes all its work before the browser gets anything. Every panel queued behind the one before it. SQL ran serially, one recordset closed before the next one opened. The ribbon section, the product table, the session counts, the PPC numbers, all of it waited in line.
That count of over 80 wasn’t designed. It accumulated. One quarter someone adds a product breakdown. The next year someone adds session stats. Nobody checks how the page performs after each addition, because the page still loads. Slowly, but it loads, and everyone gets used to waiting.
The first move: hunt the worst query
The first instinct on a slow page is to find the offending query: a missing index, a cross join, a subquery that should be a join. Fix the worst one, rerun, measure. We have followed that instinct on other pages in this codebase, and it is often right.
Here it was the wrong move. The slowness was spread across a contract nobody had challenged, with no single place to point at. Even a big improvement to one query would have left the blocking structure largely intact.
Ribbon first, everything else later
So the fix was to throw out the page contract.
We kept only the ribbon data in the initial server response. The ribbon is the summary strip visible first. The product panels, session counts, and auxiliary metrics now load afterward through independently requested JSON endpoints, with the client responsible for keeping the work bounded and recoverable.
On the client side, we rebuilt the section loader so each section renders as its data arrives. The initial response carries the ribbon, and the remaining sections fill in on their own schedules. A slow follow-up section no longer blocks the initial response or any other section.
Deletion with receipts
Performance work on legacy code often includes deletion. Alongside “how do we make this faster,” ask whether the work is still load-bearing at all.
Two things were not.
The PPC computation was server-side work that ran on every load and fed a section that, when we actually looked, wasn’t wired up to display anything. The computation was real. The consumer had been gone for who knows how long. We cut it.
The session count queries were running twice: once in the ribbon pass and once in the main body. Same query, same parameters, same recordset, two trips to SQL. The second was a copy that had drifted away from where the data was actually used. We kept one and removed the other.
Neither was necessarily the dominant bottleneck. But dead work is still work, and these two cases showed how an accumulated page keeps computations and duplicate queries long after their original purpose has changed.
The ribbon styling fix
After the architecture change went in, we needed to fix a styling issue on the ribbon. A spacing adjustment, something cosmetic.
On the old page, that would have meant touching the same server-side template that controlled everything else. Any edit was a risk to the whole load path, and we would have read the surrounding code more carefully than the fix warranted, just to be sure nothing downstream broke.
On the new page, the ribbon is isolated. We opened the relevant section, made the adjustment, confirmed it in the browser, and the rest of the page was not a concern. That says something about maintainability and nothing about performance: the ribbon became a smaller surface, so a local presentation fix no longer required reasoning about the entire page-load path.
We haven’t answered how many other admin pages in the app are in the same shape. The session reports, billing summaries, and reporting views all load. We haven’t traced their query counts, and the marketing overview felt normal before we traced it too. The only way to tell the difference is to count.
A page that “always loads” can still be doing far more work than anyone intended. Here, the useful fix was changing what the page waits for rather than tuning one query. Count the queries your internal admin pages run per load, and check which of that work still feeds something on screen.