---
title: "Slow Admin Pages, No 500s, and a SELECT Inside Session Init"
canonical: https://dxdev.com/blog/recovery-code-hot-path/
datePublished: 2026-04-22
---
The original ticket was a two-hour fix. Someone deleted an organization record that still had an active staff session pointing at it. Session init hit the stale reference and entered a loop: redirect to the org selector, check session, find stale org, redirect again. Staff were stuck, and the fix looked straightforward.

Four hours later, a second ticket was open.

## What session init actually is

Session init runs at the top of every authenticated admin request. Every page load, every AJAX call, every status ping. It is the first thing that runs on any page that requires authentication.

The fix we shipped put a SELECT directly into that function to check whether the session's current org still exists, plus an UPDATE to clear it if not. That added two SQL operations to every authenticated request, including all the ones where the session was healthy and no record had ever been deleted.

That was the wrong model. We had treated session init as a recovery pathway. It is a hot path.

## What the pileup looked like

The symptoms did not announce themselves. No 500s, no error pages. Pages just loaded slowly. That is the dangerous shape: the code was doing exactly what we told it to do, on a path that was never designed to carry that cost.

The admin panel has around a dozen active staff on a busy afternoon. Each page load now carried two extra SQL operations, and staff were clicking through pages, updating schedules and pulling reports. IIS worker threads started queuing. Slow pages prompted retries, and retries queued behind slower pages. The thread pool filled up with work that was, individually, fine.

During the incident we added a live SQL activity page, because our other visibility into database state was not fast enough. Running a DMV query by hand in SSMS while also reading IIS logs and session traces produces wrong conclusions. The activity page pulls `sys.dm_exec_requests` and `sys.dm_exec_sql_text` on demand and renders the live picture in the admin UI itself. We should have built it before the incident.

It showed a column of near-identical SELECT and UPDATE calls, all hitting the session table, all slightly overlapping, all from different worker threads. The query ran fast. It ran constantly.

## Moving the cleanup to where the loop lived

For the second ticket we did not optimize the SELECT or cache its result. We moved the cleanup out of session init entirely and put it in the one place where a stale org reference can actually cause harm: the code path that fires when session init decides the current session is invalid and cannot proceed.

That redirect to the org selector is the exact spot where the loop lived, so it is the exact spot where the cleanup belongs. Now it strips dead org refs and retries session init once. If the retry succeeds, the request continues. If it fails again, the user is redirected. There is no SELECT on every healthy request and no UPDATE on every authenticated page load.

The IIS thread queue cleared in under two minutes after the fix deployed.

## Two other things from the same day

A third issue surfaced that afternoon. A view shipped with updated source HTML, but the deployed bundle still contained CSS and JS compiled against the old structure. The view loaded unstyled because the compiled assets did not match the source they were supposed to style. A rebuild cleared it.

We also tightened two pieces of the agent tooling. The sync skill had no concept of a hotfix branch, so running it mid-hotfix would try to pull develop changes in and break the branch topology. It now detects the branch prefix and skips that step. The investigation skill used to start every investigation by waiting for us to supply the repro URL. It now reads the ticket's Links field first and uses whatever is already there. That saves two minutes per investigation and removes one more thing to track by hand.

## What defensive code costs on the healthy case

Defensive code in authentication and session plumbing gets scrutinized for correctness. Does it handle the bad case? Does it catch the stale reference? Does it prevent the loop? Those are the questions that get asked before it ships.

The question that rarely gets asked is what the code costs on the good case, which runs a thousand times more often than the case it was written for. Before approving defensive session or auth logic, check how often that path runs when everything is healthy. If it sits on a hot path like session init, the check and the cleanup belong on the branch that already knows something is wrong, such as the invalid-session redirect, and nowhere else.
