Capstone — Triaging Three Real Application Tickets

Web & Application Troubleshooting

Chapter 10 · Capstone — Triaging Three Real Application Tickets

Nine chapters built the pieces — knowing this course's own boundary against its three siblings, reading error bodies, connection pools, caching, sessions, slow queries, deployments, health checks, and rate limits. This capstone applies all of it to three fresh tickets, worked more briskly than earlier chapters' own dedicated walkthroughs, since the underlying discipline should already feel familiar by now.

Ticket 1: "The admin dashboard is unusably slow for our biggest customers"

The admin dashboard's user list page loads instantly for small organizations, but takes 12+ seconds for organizations with hundreds of users.

Applying Chapter 1's scoping instinct

Load time scales with a specific count (organization size), not with overall traffic or time of day — exactly the signature Chapter 6 taught to recognize before even opening a query log.

Applying Chapter 6

Query logging confirms it directly: one query for the organization's user list, followed by one additional query per user to fetch that user's role and permission set — 340 total queries for an organization with 339 users. Replacing the per-user loop with a single batched query fetching all roles at once cuts the page load from 12 seconds to well under a second.

Ticket 2: "Since this morning's deploy, we're intermittently getting connection pool exhaustion"

Roughly a third of requests to a specific endpoint have started failing with pool timeouts since this morning's release; the rest work fine.

Applying Chapters 1 and 2

Sudden onset, tied to today's deploy, some requests failing rather than all — the error body confirms a familiar shape: {"error": "pool_timeout", "request_id": "..."}, with a correlation ID ready for deeper investigation.

Ruling out Chapter 9 first

An early guess — a partner integration retrying aggressively and exhausting shared resources — is checked directly via per-key rate-limit consumption. Every key shows normal, unremarkable usage. Ruled out; the real cause lies elsewhere.

Applying Chapter 7

Checking each instance's own version confirms the failures cluster specifically on instances already running this morning's release — a genuine version-skew signature, narrowing the search to what actually changed in the new code.

Applying Chapter 3, one level deeper
SELECT pid, now() - state_change AS duration, state FROM pg_stat_activity WHERE state = 'idle in transaction' ORDER BY duration DESC; pid | duration | state -------+----------+--------------------- 18921 | 00:52:10 | idle in transaction 19042 | 00:41:03 | idle in transaction

Not simply a slow query this time — connections stuck in PostgreSQL's own idle in transaction state, meaning a transaction was opened and never committed or rolled back. This morning's new code path opens a transaction, but under one specific error condition throws before ever reaching the commit — silently leaking the connection forever, on every instance running the new version. The fix: wrap the transaction in a proper try/finally so it's always closed regardless of how the request ends, shipped as an immediate hotfix.

Ticket 3: "Since today's scale-up, users are getting logged out, and our real capacity seems lower than it should be"

Two complaints arriving together right after scaling up for a traffic event: random session loss, and capacity that doesn't match the number of instances actually running.

Ruling out Chapter 4 first

An early guess — new instances stampeding a cold cache — is checked directly: TTLs are properly jittered, and there's no clock-aligned periodicity in the load pattern. Ruled out.

Applying Chapter 5

The session-loss complaint matches this course's own established pattern exactly: the load balancer redistributed sticky-session assignments when the instance pool changed size, and the application still relies on local, per-server session storage rather than a shared store.

Applying Chapter 8

Separately, the orchestrator's own event log shows the new instances repeatedly cycling in and out of ready state — flapping, with no actual crashes. Their readiness check includes a call to a third-party shipping-rates API that's been running unusually slowly today, and a slow (not down) dependency is enough to fail the check. The application itself is otherwise perfectly capable of serving most requests; readiness was checking something it shouldn't have owned.

Two genuinely separate fixes for two genuinely separate causes, both surfaced by the same scaling event: migrating to a shared, Redis-backed session store, and narrowing the readiness check to only cover this instance's own true dependencies.

Chapter Attribution

Technique used aboveSource chapter
Scoping instincts — what a symptom's own shape suggests before checking anything (all three tickets)Chapter 1
Reading the structured error body and correlation ID (Ticket 2)Chapter 2
pg_stat_activity, extended to "idle in transaction" (Ticket 2)Chapter 3
Ruling out a cache stampede via TTL/jitter check (Ticket 3) — not the primary finding, but the same technique applies directly to a genuine stampede ticketChapter 4
Sticky-session reshuffling on a scaling event (Ticket 3)Chapter 5
N+1 recognized via load time scaling with a count, confirmed by query logging (Ticket 1)Chapter 6
Checking each instance's own version to confirm version-skew (Ticket 2)Chapter 7
Reading the orchestrator's own event log for readiness flapping (Ticket 3)Chapter 8
Ruling out rate limiting via per-key consumption (Ticket 2) — not the primary finding, but the same technique applies directly to a genuine throttling ticketChapter 9

Honest Scope Note

What this course deliberately doesn't cover
  • No deep, ORM-specific syntax walkthroughs or commercial APM tool tutorials — the underlying patterns transfer, but specific tool interfaces vary too much to cover here
  • No service mesh or sidecar-specific troubleshooting (Istio, Linkerd, and similar) — a real, separate discipline on top of what's covered here
  • No distributed tracing systems in depth (Jaeger, Zipkin) — correlation IDs get you far; a full tracing setup is a bigger, separate topic
  • No chaos engineering or fault-injection testing methodology — this course diagnoses real incidents, not designing tests that manufacture them
  • No application-security-specific vulnerabilities — XSS, CSRF, SQL injection, and similar are covered in this site's own dedicated Security courses, not here
Each is a legitimate, separate topic — not silently assumed solved by what this course actually covers.

Hands-On Exercises

Exercise 1

Explain what made Ticket 1's symptom recognizable as N+1 before a single query log was checked, and how that matched Chapter 6's own reasoning.

📄 View solution
Exercise 2

Explain what "idle in transaction" specifically means in Ticket 2, and why it's a genuinely different finding than the slow single query Chapter 3 originally taught with.

📄 View solution
Exercise 3

Explain why Ticket 3 needed two separate fixes rather than one, even though both complaints started at the exact same moment.

📄 View solution

Chapter 10 Quick Reference — Course Complete

  • Ticket 1: N+1 on the admin dashboard, recognized by load time scaling with organization size — confirmed and fixed with batch loading
  • Ticket 2: a version-skew-scoped connection leak, found by ruling out rate limiting, confirming the affected instances' version, then reading "idle in transaction" state directly
  • Ticket 3: one scaling event, two genuinely separate causes — sticky-session reshuffling and an overly broad readiness check — needing two separate fixes
  • The recurring theme across all ten chapters: know which of the four Technical Support courses actually owns a symptom, gather real evidence, and don't assume a fix is needed before confirming the cause
  • This closes Web & Application Troubleshooting, 10/10 chapters — the fourth complete course under the Technical Support subject, alongside Logging & Log Analysis, Network Troubleshooting, and System Monitoring & Performance Diagnosis