<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Observability on Liu Bo</title>
        <link>https://csliubo.com/tags/observability/</link>
        <description>Recent content in Observability on Liu Bo</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <lastBuildDate>Tue, 16 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://csliubo.com/tags/observability/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>The Unresponsive Service Was the Victim, Not the Cause</title>
            <link>https://csliubo.com/p/mysql-cpu-cascade-red-herring/</link>
            <pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate>
            <guid>https://csliubo.com/p/mysql-cpu-cascade-red-herring/</guid>
            <description>&lt;p&gt;Early one evening, a new client institution started mass-uploading files. The uploads went through RocketMQ to an upload-processing consumer, and that consumer went unresponsive. Messages backed up, the team&amp;rsquo;s chat lit up, and the consumer was for all practical purposes dead.&lt;/p&gt;&#xA;&lt;p&gt;I didn&amp;rsquo;t debug it by hand. I pointed Claude Code (Opus 4.8) at the cluster and let it run the investigation autonomously: &lt;code&gt;kubectl exec&lt;/code&gt; into the pods, &lt;code&gt;jstack&lt;/code&gt; the JVMs, &lt;code&gt;jstat&lt;/code&gt; the GC state, report back. My own working hypothesis at that moment was the obvious one, that threads were starving on some resource and waiting for something that never came.&lt;/p&gt;&#xA;&lt;p&gt;The agent came back with a confident answer: a thread leak in our cloud vendor&amp;rsquo;s object-storage SDK, thousands of &lt;code&gt;IdleConnectionMonitorThread&lt;/code&gt; daemons never reclaimed. It fingered this as the root cause, and I acted on it. I doubled the pods to stop the bleeding, had a colleague drain the backed-up queue, and asked the customer to re-upload later.&lt;/p&gt;&#xA;&lt;p&gt;It didn&amp;rsquo;t work. Other teams&amp;rsquo; systems kept failing, and the database&amp;rsquo;s CPU stayed pegged at 100%. That symptom, the one that refused to die no matter what I did to the consumer, is what eventually dragged my attention to a MySQL CPU alert that had been firing the entire time. The real culprit was three hops away, in a query I had never looked at, against a table the unresponsive consumer didn&amp;rsquo;t even own.&lt;/p&gt;&#xA;&lt;p&gt;This post is about that misdirection. A thread dump, a profiler, a dashboard, and an AI agent all answer the same question extremely well: &lt;em&gt;what is the system mostly doing?&lt;/em&gt; An incident&amp;rsquo;s true culprit is almost never the thing the system is mostly doing. It&amp;rsquo;s the binding constraint. And there&amp;rsquo;s a bookend I have to put up front, because it&amp;rsquo;s the honest core of the whole thing: the unindexed query that caused the outage was AI-generated. I&amp;rsquo;d had Claude Code write that data-access code, it was functionally correct, it passed tests, it ran fine on a small dev table. So the same AI both &lt;em&gt;wrote&lt;/em&gt; the bug and later &lt;em&gt;misdiagnosed&lt;/em&gt; it. I own both.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;tldr&#34;&gt;TL;DR&#xA;&lt;/h2&gt;&lt;ol&gt;&#xA;&lt;li&gt;A new institution started mass-uploading files. The path was RocketMQ to an upload consumer, and the consumer went unresponsive. My first instinct, and the agent&amp;rsquo;s, was thread starvation.&lt;/li&gt;&#xA;&lt;li&gt;I let Claude Code run &lt;code&gt;jstack&lt;/code&gt; autonomously. The biggest bucket in the dump: &lt;strong&gt;1,347 of 1,912 threads were a leaked object-storage daemon&lt;/strong&gt; (&lt;code&gt;IdleConnectionMonitorThread&lt;/code&gt;). The agent called it the root cause. It was a real bug, with no causal connection to this incident.&lt;/li&gt;&#xA;&lt;li&gt;I acted on that wrong answer: doubled the pods, drained the queue, deferred the customer. Doubling pods backfired, because more consumers triggered more downstream work against an already-saturated shared database. The bleeding didn&amp;rsquo;t stop, and other teams&amp;rsquo; systems kept failing.&lt;/li&gt;&#xA;&lt;li&gt;The real culprit: a separate summarization service, triggered by the same upload pipeline, ran an unindexed full-table scan against a &lt;strong&gt;shared&lt;/strong&gt; MySQL instance. It &lt;strong&gt;examined 1,171,955 rows to return 1&lt;/strong&gt;, took 223 seconds per query, and pegged the instance&amp;rsquo;s CPU at 100%. CPU is shared across the whole instance, so every other system on that database, even ones with perfectly indexed queries, got starved.&lt;/li&gt;&#xA;&lt;li&gt;The discriminator: &lt;strong&gt;&amp;ldquo;how many systems are affected?&amp;rdquo;&lt;/strong&gt; (more than one points at a shared resource) and &lt;strong&gt;&amp;ldquo;what change restored service?&amp;rdquo;&lt;/strong&gt; (the answer was the index, not fixing the thread leak). Bookend: the offending query was AI-written, so the same AI wrote the bug and then misdiagnosed it.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;the-misdirection-jstack-the-loudest-box&#34;&gt;The Misdirection: jstack the Loudest Box&#xA;&lt;/h2&gt;&lt;p&gt;The upload consumer was the loudest thing in the room. It was unresponsive, it was the system everyone was talking about, and it was trivially reachable for a thread dump. When a service goes dark and you can &lt;code&gt;jstack&lt;/code&gt; it in one command, that&amp;rsquo;s where attention goes, naturally and wrongly.&lt;/p&gt;&#xA;&lt;p&gt;The first thing the autonomous investigation taught me is a meta-lesson about diagnostic tooling: the tool you reach for fails exactly when you need it most. The old instance, the one that had been running for 4 days 23 hours and was deepest into the incident, would not produce a thread dump at all. &lt;code&gt;jstack&lt;/code&gt; hung for over 31 minutes without emitting a single line and had to be killed. The mechanism is worth knowing: &lt;code&gt;jstack&lt;/code&gt; attaches to the JVM and needs every thread to reach a safepoint before it can enumerate stacks, and a JVM thrashing under native-memory pressure with thousands of threads can take many minutes to bring them all to one. We were forced to fall back to a fresher, healthier instance (33 minutes of uptime) to get a dump out at all. The sickest patient was the one we couldn&amp;rsquo;t examine.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the dump that came back from that 33-minute-old instance:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;&lt;/th&gt;&#xA;          &lt;th&gt;&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Total threads&lt;/td&gt;&#xA;          &lt;td&gt;1,912&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;TIMED_WAITING&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;1,665 (87%)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Anonymous &lt;code&gt;Thread-*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;1,349&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;⤷ of which &lt;code&gt;IdleConnectionMonitorThread&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;1,347&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;And the one stack fragment that mattered:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;#34;Thread-50&amp;#34; #587 [585] daemon prio=5 os_prio=0 cpu=26.90ms elapsed=1987.55s&#xA;   java.lang.Thread.State: TIMED_WAITING (on object monitor)&#xA;        at java.lang.Object.wait0(java.base@21.0.10/Native Method)&#xA;        at java.lang.Object.wait(java.base@21.0.10/Object.java:366)&#xA;        at com.cloudvendor.storage.http.IdleConnectionMonitorThread.run(IdleConnectionMonitorThread.java:48)&#xA;        - locked &amp;lt;0x00000007fe8ec870&amp;gt; (a com.cloudvendor.storage.http.IdleConnectionMonitorThread)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Read this dump the way a triage instinct wants to read it. Eighty-seven percent of threads are in &lt;code&gt;TIMED_WAITING&lt;/code&gt;. The single largest bucket, nearly all of the anonymous threads, is a leaked object-storage daemon. The surface story writes itself: the service is wedged because it&amp;rsquo;s drowning in threads, and the threads are leaking, so the leak is what wedged the service. It is a seductive wrong answer, and both the agent and I bought it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-tell-idle-daemons-dont-block-consumption&#34;&gt;The Tell: Idle Daemons Don&amp;rsquo;t Block Consumption&#xA;&lt;/h2&gt;&lt;p&gt;A screen full of &lt;code&gt;WAITING&lt;/code&gt; threads has two completely different possible meanings, and conflating them is the trap.&lt;/p&gt;&#xA;&lt;p&gt;The first is &lt;strong&gt;contention&lt;/strong&gt;: a bounded number of worker threads all blocked on the &lt;em&gt;same&lt;/em&gt; shared resource. You&amp;rsquo;d see a pool of identically-named threads parked on one monitor, the signature of N workers fighting over a lock or a connection. That story is consistent with &amp;ldquo;the service is wedged waiting for something.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;The second is a &lt;strong&gt;leak&lt;/strong&gt;: a large number of &lt;em&gt;independent&lt;/em&gt; threads, each parked on its own monitor, that should never have existed. That&amp;rsquo;s what this dump was. The 1,347 leaked entries were each &lt;code&gt;locked&lt;/code&gt; on their own distinct &lt;code&gt;IdleConnectionMonitorThread&lt;/code&gt; instance. They weren&amp;rsquo;t contending over anything shared. They were unrelated leftovers, accumulating.&lt;/p&gt;&#xA;&lt;p&gt;The decisive point isn&amp;rsquo;t even the leak-versus-contention distinction. It&amp;rsquo;s what these threads &lt;em&gt;are&lt;/em&gt;. An &lt;code&gt;IdleConnectionMonitorThread&lt;/code&gt; is a housekeeping daemon: it wakes every few seconds, sweeps idle HTTP connections, and goes back to &lt;code&gt;Object.wait()&lt;/code&gt;. It does nothing in the request path. It consumes native memory, one stack each, and at scale over days that can absolutely kill an instance, but it does not participate in message processing. It cannot be the acute cause of a minute-scale &amp;ldquo;the consumer is unresponsive &lt;em&gt;right now&lt;/em&gt;&amp;rdquo; event. Idle daemons don&amp;rsquo;t block consumption; they just sit there being idle.&lt;/p&gt;&#xA;&lt;p&gt;The numbers don&amp;rsquo;t support &amp;ldquo;the leak did this,&amp;rdquo; either. The fresh instance accumulated 1,347 of these in 33 minutes. The old instance had 3,402 threads in 4 days 23 hours. If the fresh instance&amp;rsquo;s rate were the steady state, five days would have produced something like 290,000 threads, orders of magnitude past where it would have died. The fresh count is a burst-period snapshot, not a steady-state rate. There&amp;rsquo;s even a self-limiting feedback here: once consumption stalls, the leak slows down too, because the code path that creates the clients runs less often. The leak is real, but in this incident it&amp;rsquo;s a passenger, not the driver.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-jstat-red-herring-inside-the-red-herring&#34;&gt;The jstat Red Herring Inside the Red Herring&#xA;&lt;/h3&gt;&lt;p&gt;While we were anchored on the leak, &lt;code&gt;jstat -gcutil&lt;/code&gt; on the dying old instance handed us a second misleading signal that seemed to corroborate the first:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;&lt;/th&gt;&#xA;          &lt;th&gt;&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;M&lt;/code&gt; (metaspace)&lt;/td&gt;&#xA;          &lt;td&gt;98.18%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;CCS&lt;/code&gt; (compressed class space)&lt;/td&gt;&#xA;          &lt;td&gt;90.13%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;FGC&lt;/code&gt; (full GC count)&lt;/td&gt;&#xA;          &lt;td&gt;0&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;GCT&lt;/code&gt; (total GC time, ~5 days uptime)&lt;/td&gt;&#xA;          &lt;td&gt;~26 s&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Metaspace at 98% looks like an instance about to die of metaspace exhaustion. It is not, and two facts dismantle it.&lt;/p&gt;&#xA;&lt;p&gt;First, &lt;code&gt;jstat&lt;/code&gt;&amp;rsquo;s &lt;code&gt;M&lt;/code&gt; column is &lt;code&gt;used / committed&lt;/code&gt;, not &lt;code&gt;used / MaxMetaspaceSize&lt;/code&gt;. It tells you how full the &lt;em&gt;currently committed&lt;/em&gt; metaspace region is, not how close you are to the configured ceiling. This JVM did set &lt;code&gt;-XX:MaxMetaspaceSize=384m&lt;/code&gt;, but &lt;code&gt;M = 98%&lt;/code&gt; only says the slice committed so far is nearly full, which is the normal steady state, because the JVM commits metaspace lazily and keeps it tight. To know whether you&amp;rsquo;re approaching the 384m wall, watch &lt;code&gt;MC&lt;/code&gt; (committed capacity) and whether &lt;em&gt;it&lt;/em&gt; is climbing toward 384m. An &lt;code&gt;M&lt;/code&gt; reading of 95–99% is not by itself alarming.&lt;/p&gt;&#xA;&lt;p&gt;Second, and decisively: &lt;code&gt;FGC = 0&lt;/code&gt;. A JVM genuinely running out of its 384m metaspace budget triggers Full GCs trying to reclaim class metadata, and failing that throws &lt;code&gt;OutOfMemoryError: Metaspace&lt;/code&gt;. This instance had triggered zero Full GCs across nearly five days, with a cumulative &lt;code&gt;GCT&lt;/code&gt; of about 26 seconds. Metaspace was never under pressure. The 98% was &lt;code&gt;jstat&lt;/code&gt; noise.&lt;/p&gt;&#xA;&lt;p&gt;That leaves a question I&amp;rsquo;ll pay off later: if metaspace, which &lt;em&gt;is&lt;/em&gt; capped at 384m, was fine, then what region was actually being exhausted on the old instance as it accumulated 3,402 threads? Hold that thought.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-breakthrough-the-symptom-that-refused-to-die&#34;&gt;The Breakthrough: The Symptom That Refused to Die&#xA;&lt;/h2&gt;&lt;p&gt;The satisfying version of this story would be that some clever human stepped back, correlated across systems, and intuited a shared database. That&amp;rsquo;s not what happened.&lt;/p&gt;&#xA;&lt;p&gt;What happened is that my first aid didn&amp;rsquo;t stop the bleeding. I&amp;rsquo;d doubled the pods, drained the queue, and asked the customer to re-upload, and afterward colleagues from &lt;em&gt;other&lt;/em&gt; business systems were &lt;em&gt;still&lt;/em&gt; reporting lag and errors. The consumer I&amp;rsquo;d been frantically treating wasn&amp;rsquo;t the only thing broken, and nothing I did to it helped anything.&lt;/p&gt;&#xA;&lt;p&gt;Worse, doubling the pods was the wrong move. The bottleneck wasn&amp;rsquo;t the consumer&amp;rsquo;s capacity, it was the shared MySQL instance&amp;rsquo;s CPU. Each upload the consumer processes triggers the summarization service, and the summarization service is what runs the expensive query. So adding consumer pods didn&amp;rsquo;t add throughput; it pushed more uploads through faster, which fired more summarization queries, which slammed a database that was already saturated. Scaling out a service whose real bottleneck is a shared downstream amplifies the root cause instead of relieving it. I&amp;rsquo;d taken the one action that made the binding constraint bind harder.&lt;/p&gt;&#xA;&lt;p&gt;The symptom that refused to die is what finally turned me. Multiple unrelated teams failing at once, plus a CPU graph stuck flat at 100%, was a signal no amount of poking at one consumer could explain. That pulled me into the database monitoring, where I found the MySQL CPU alert that had been screaming the entire time. The blast radius wasn&amp;rsquo;t a clue I cleverly discovered. It was a symptom that wouldn&amp;rsquo;t go away and dragged me, late and against my own initial focus, to the actual culprit.&lt;/p&gt;&#xA;&lt;p&gt;This is the human-versus-AI fork, and it isn&amp;rsquo;t flattering to either side. The agent was deep inside a single JVM&amp;rsquo;s thread dump and structurally could not see that &lt;em&gt;other&lt;/em&gt; systems were also down, because that cross-system view simply isn&amp;rsquo;t in a &lt;code&gt;jstack&lt;/code&gt;. But I didn&amp;rsquo;t look at the cross-system view either, not at first. I had to be forced into it by a failed remediation. Correlating across services is the thing a human is supposed to add, and I added it slower than I&amp;rsquo;d like to admit.&lt;/p&gt;&#xA;&lt;p&gt;The framing that should have come faster: the loudest victim is rarely the culprit. The upload consumer was unresponsive because &lt;em&gt;its own&lt;/em&gt; database calls, against that same shared instance, were timing out on a dying MySQL. It was a victim. The perpetrator was three hops away, a different application abusing a shared CPU.&lt;/p&gt;&#xA;&lt;h2 id=&#34;root-cause-one-unindexed-query-117-million-rows-one-result&#34;&gt;Root Cause: One Unindexed Query, 1.17 Million Rows, One Result&#xA;&lt;/h2&gt;&lt;p&gt;Here is the query at the bottom of it all, from the slow-query digest:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;select&lt;/span&gt; ..., vendor, vendor_id, ...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; llm_invocation&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;where&lt;/span&gt; is_del &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;and&lt;/span&gt; (vendor &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;and&lt;/span&gt; vendor_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;limit&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Metric&lt;/th&gt;&#xA;          &lt;th&gt;Value&lt;/th&gt;&#xA;          &lt;th&gt;What it means&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Database&lt;/td&gt;&#xA;          &lt;td&gt;the summarization service&amp;rsquo;s own schema&lt;/td&gt;&#xA;          &lt;td&gt;Same &lt;strong&gt;physical instance&lt;/strong&gt; as other systems; separate logical schema, &lt;strong&gt;shared CPU&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Executions&lt;/td&gt;&#xA;          &lt;td&gt;685&lt;/td&gt;&#xA;          &lt;td&gt;Concurrency during the upload spike&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Avg execution time&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;223.1 s&lt;/strong&gt; (max 278.2 s)&lt;/td&gt;&#xA;          &lt;td&gt;A single SELECT running for nearly four minutes&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Total time&lt;/td&gt;&#xA;          &lt;td&gt;152,832 s&lt;/td&gt;&#xA;          &lt;td&gt;~42 hours of cumulative CPU burned by this one statement&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Avg rows examined&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;1,171,955&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Full table scan&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Avg rows returned&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;A 1,171,955 : 1 examined-to-returned ratio&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Lock wait&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Decisive: this is not lock contention or transaction starvation. It&amp;rsquo;s pure CPU.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Two columns do the diagnostic work. The first is &lt;code&gt;lock wait = 0&lt;/code&gt;, which kills my original starvation hypothesis outright: nothing here was waiting on a lock. Whatever was wrong, it was burning CPU, not blocking on contention.&lt;/p&gt;&#xA;&lt;p&gt;The second is the examined-to-returned ratio of 1,171,955 : 1. This, not the execution time, is the real tell. Execution time tells you a query is slow without telling you why. The examined-to-returned ratio is the autopsy report for a missing index: to return one row, MySQL read the entire table. The &lt;code&gt;limit ?&lt;/code&gt; gives a false sense of safety, since &lt;code&gt;limit&lt;/code&gt; caps the &lt;em&gt;output&lt;/em&gt;, not the &lt;em&gt;work&lt;/em&gt;. With no index on the &lt;code&gt;WHERE&lt;/code&gt; predicate, finding that one matching row meant scanning nearly the whole table, every time, 685 times during the spike.&lt;/p&gt;&#xA;&lt;h3 id=&#34;why-a-shared-mysql-turns-one-bad-query-into-a-platform-outage&#34;&gt;Why a Shared MySQL Turns One Bad Query Into a Platform Outage&#xA;&lt;/h3&gt;&lt;p&gt;The mechanism by which one query takes down a platform is more insidious than the connection-exhaustion failure mode most people picture.&lt;/p&gt;&#xA;&lt;p&gt;It wasn&amp;rsquo;t that the query held connections or locks, since &lt;code&gt;lock wait&lt;/code&gt; was zero. It was CPU saturation. A missing index means a full table scan means &lt;code&gt;rows_examined&lt;/code&gt; scales with the size of the table, a CPU bomb. Multiply that by burst concurrency, 685 executions clustered in the spike, and &lt;code&gt;threads_running&lt;/code&gt; climbs far past the instance&amp;rsquo;s core count. Once &lt;code&gt;threads_running&lt;/code&gt; exceeds the number of cores, MySQL spends an increasing share of its time scheduling rather than executing, and even a cheap, well-indexed query that examines three rows can&amp;rsquo;t get a CPU slice. It queues behind the scans.&lt;/p&gt;&#xA;&lt;p&gt;This is a noisy-neighbor problem on shared CPU, and it&amp;rsquo;s why logical separation doesn&amp;rsquo;t save you. The summarization service lived in its own logical schema. Didn&amp;rsquo;t matter. A shared physical CPU is a shared failure domain. Every other system on that instance, each with its own well-behaved, indexed queries, degraded in lockstep, because they all drew from the same exhausted CPU pool. That is the entire blast radius: one application&amp;rsquo;s unindexed query, and a CPU that everyone shares.&lt;/p&gt;&#xA;&lt;p&gt;The CPU utilization graph from that evening tells the story with brutal economy. Before 17:57:00 the line is flat against 100%, the instance&amp;rsquo;s CPU completely pegged, no ripple, no headroom. At 17:57 it falls off a cliff to a noisy 10–30%, the normal working range, and stays there. That flat ceiling is the physical proof that full-table scans had burned through the shared CPU. The cliff is &amp;ldquo;we changed one thing and the system recovered instantly,&amp;rdquo; rendered as a chart.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://csliubo.com/p/mysql-cpu-cascade-red-herring/cpu.png&#34;&#xA;&#x9;width=&#34;1576&#34;&#xA;&#x9;height=&#34;510&#34;&#xA;&#x9;loading=&#34;lazy&#34;&#xA;&#x9;&#xA;&#x9;&#x9;alt=&#34;Shared MySQL CPU pegged flat at 100% until 17:57, when the index landed and it cliffed to a normal 10–30%&#34;&#xA;&#x9;&#xA;&#x9;&#xA;&#x9;&#x9;class=&#34;gallery-image&#34; &#xA;&#x9;&#x9;data-flex-grow=&#34;309&#34;&#xA;&#x9;&#x9;data-flex-basis=&#34;741px&#34;&#xA;&#x9;&#xA;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-fix-and-the-discriminator&#34;&gt;The Fix, and the Discriminator&#xA;&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;-- turn the full-table scan into an index lookup&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;ALTER&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;TABLE&lt;/span&gt; llm_invocation &lt;span style=&#34;color:#ff79c6&#34;&gt;ADD&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;INDEX&lt;/span&gt; idx_vendor_id (vendor, vendor_id, is_del);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; after the index existed:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;-&amp;gt; Limit: 1 row(s)  (cost=1.07 rows=1) (actual time=0.179..0.179 rows=1 loops=1)&#xA;    -&amp;gt; Index lookup on llm_invocation using idx_vendor_id&#xA;       (vendor=&amp;#39;&amp;lt;vendor&amp;gt;&amp;#39;, vendor_id=&amp;#39;&amp;lt;call_id&amp;gt;&amp;#39;, is_del=0)&#xA;       (cost=1.07 rows=1) (actual time=0.178..0.178 rows=1 loops=1)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Put that next to the slow-query digest and you get the single hardest table in this post:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;&lt;/th&gt;&#xA;          &lt;th&gt;Before (no index)&lt;/th&gt;&#xA;          &lt;th&gt;After (&lt;code&gt;idx_vendor_id&lt;/code&gt;)&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Access method&lt;/td&gt;&#xA;          &lt;td&gt;full table scan (&lt;code&gt;type: ALL&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;index lookup&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Rows examined&lt;/td&gt;&#xA;          &lt;td&gt;~1,171,955&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Per-query time&lt;/td&gt;&#xA;          &lt;td&gt;~223 s (avg)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;0.18 ms&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;That&amp;rsquo;s roughly a 1.2-million-fold speedup, with rows examined falling from 1,171,955 to 1.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;(One honest caveat: we never captured a formal before-&lt;code&gt;EXPLAIN&lt;/code&gt;. The index went on under incident pressure, with no time to record the broken plan first. The &amp;ldquo;before&amp;rdquo; numbers come from the slow-query digest, not a captured &lt;code&gt;EXPLAIN&lt;/code&gt;.)&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;Two things this fix earns the right to say.&lt;/p&gt;&#xA;&lt;p&gt;The index didn&amp;rsquo;t cure a slow query. It defused a bomb sitting on the shared CPU. When a single query collapses from examining 1.17 million rows to examining one, the CPU saturation breaks, the time-slice contention clears, and &lt;em&gt;everyone&lt;/em&gt; recovers, not just the summarization service. Latency was the symptom; CPU was the contended resource. Fix the resource and the symptom disappears across every affected system at once. That&amp;rsquo;s the 17:57 cliff.&lt;/p&gt;&#xA;&lt;p&gt;The two halves of recovery map onto queuing theory. Draining the queue cut the &lt;em&gt;arrival rate&lt;/em&gt;; adding the index cut the &lt;em&gt;service cost per query&lt;/em&gt;. You need both. The LLM-summarization step has inherent latency no index can remove, so &amp;ldquo;ask the customer to re-upload later&amp;rdquo; was legitimate rate-limiting, not deflection. But draining the queue alone did not drop the CPU; only the index produced the cliff. First aid bought time, and the index was the cure. Which is the discriminator, and the one I&amp;rsquo;d tattoo on the next on-call engineer: you don&amp;rsquo;t find the root cause by asking which bucket in the dump is biggest. You find it by asking what change actually restored service. We added an index and the platform recovered, so the binding constraint was the unindexed query. The 1,347 leaked threads, the biggest number we saw all night, were never on the causal path.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-chronic-bug-we-found-by-accident&#34;&gt;The Chronic Bug We Found by Accident&#xA;&lt;/h2&gt;&lt;p&gt;The object-storage client thread leak is a real bug. Every &lt;code&gt;new ObjectStorageClient()&lt;/code&gt; spins up an &lt;code&gt;IdleConnectionMonitorThread&lt;/code&gt; daemon, and if you never call &lt;code&gt;shutdown()&lt;/code&gt; that thread is never reclaimed. Over five days the old instance had quietly accumulated 3,402 threads this way, and it would have eventually died of it.&lt;/p&gt;&#xA;&lt;p&gt;But it&amp;rsquo;s a chronic condition we tripped over while investigating an acute one. The leak kills an instance over days; the incident killed the platform in minutes. They share nothing except being visible in the same dump. The fix is straightforward: reuse a single client, or if you genuinely need a transient instance, wrap it in &lt;code&gt;try/finally&lt;/code&gt; and call &lt;code&gt;shutdown()&lt;/code&gt;. (One caveat: with temporary credentials, a long-lived singleton needs a refreshable credentials provider rather than fixed credentials welded in at construction.)&lt;/p&gt;&#xA;&lt;p&gt;The lesson is about diagnosis, not about object storage. Stumbling onto a real bug mid-investigation is good. Declaring it the root cause and going home is mistaking &amp;ldquo;happened to be at the scene&amp;rdquo; for &amp;ldquo;committed the crime.&amp;rdquo;&lt;/p&gt;&#xA;&lt;h3 id=&#34;which-memory-region-does-a-thread-leak-actually-attack&#34;&gt;Which Memory Region Does a Thread Leak Actually Attack?&#xA;&lt;/h3&gt;&lt;p&gt;This is the most elegant thing the JVM flags handed us, and it pays off the loose end from the &lt;code&gt;jstat&lt;/code&gt; section. Here&amp;rsquo;s how the instance was launched:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=70.0&#xA;-XX:MaxMetaspaceSize=384m -XX:MaxDirectMemorySize=256m&#xA;-XX:ReservedCodeCacheSize=128m -XX:+UseG1GC&#xA;(OpenJDK 21.0.10)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The team capped every region of memory they could think of: heap at 70% of RAM, metaspace at 384m, direct memory at 256m, code cache at 128m. Every region they could name, they bounded.&lt;/p&gt;&#xA;&lt;p&gt;But thread stacks aren&amp;rsquo;t in any of those budgets. Each thread gets roughly a 1 MB native stack, allocated from native memory that lives outside every one of those &lt;code&gt;-XX&lt;/code&gt; flags. So a thread leak grows in precisely the one region nobody put a ceiling on. 3,402 threads is gigabytes of stack space growing unchecked, in the one place no flag bounds, until the container hits its memory limit and gets OOMKilled by Kubernetes, or the JVM gives up with &lt;code&gt;unable to create new native thread&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;That closes the &lt;code&gt;jstat&lt;/code&gt; loop. Metaspace was capped at 384m and was never the problem; &lt;code&gt;FGC=0&lt;/code&gt; proved it. The region actually being consumed was the uncapped one, native thread stacks. The metric that looked scary, metaspace at 98%, was bounded and safe. The danger was in a region no metric on our dashboard was even watching.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-the-investigation-went-wrong&#34;&gt;Why the Investigation Went Wrong&#xA;&lt;/h2&gt;&lt;p&gt;The companion post in this series ends on why static review missed a latent bug. This one needs the same accounting, and this time both the human and the AI anchored, and the human acted on the anchor.&lt;/p&gt;&#xA;&lt;p&gt;The core failure mode here is double anchoring. The upload consumer was the loudest victim, and the leaked daemons were the biggest number in the dump. Both answer &amp;ldquo;what is most prominent?&amp;rdquo; Both the agent and I nailed our attention to a single victim and a single unrelated bug, and then I made it worse by acting on it.&lt;/p&gt;&#xA;&lt;p&gt;The agent&amp;rsquo;s blind spot generalizes uncomfortably. It drilled deep into one machine&amp;rsquo;s thread dump and structurally could not see that other systems were failing. The better an autonomous agent is at drilling into one signal, the easier it is for it to drill into the &lt;em&gt;wrong&lt;/em&gt; signal. Its confidence is exactly as narrow as its field of view. But my own failure was a broken action-feedback loop: after I doubled the pods, the CPU graph didn&amp;rsquo;t move, and that negative result should have immediately falsified the thread-leak hypothesis. A remediation that changes nothing is a loud signal you&amp;rsquo;re treating the wrong thing.&lt;/p&gt;&#xA;&lt;p&gt;And the bookend. The same AI wrote the offending query, functionally correct and test-passing and never validated for behavior at scale, and then misdiagnosed it during the incident. This isn&amp;rsquo;t &amp;ldquo;AI is dumb, humans are smart.&amp;rdquo; It&amp;rsquo;s that AI review and human review share the same blind spot: both read code and reason about correctness, and neither sees runtime behavior at scale. The query was correct in every sense a reader can check. It was catastrophic in a sense only a running system under load can reveal. That&amp;rsquo;s why the defenses below are runtime signals, not more careful reading. You cannot eyeball your way to an examined-to-returned ratio.&lt;/p&gt;&#xA;&lt;h2 id=&#34;operational-checklist&#34;&gt;Operational Checklist&#xA;&lt;/h2&gt;&lt;p&gt;For anyone who operates services that share a database, cache, or message broker, which is most of us:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Ask &amp;ldquo;how many systems are affected?&amp;rdquo; before &amp;ldquo;which one is loudest?&amp;rdquo;&lt;/strong&gt; More than one independent system failing points at a shared resource, before any single victim&amp;rsquo;s local state. The loudest service is usually downstream of the actual fault.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t scale out a service before confirming its bottleneck is local.&lt;/strong&gt; Adding capacity to a service whose real constraint is a shared downstream multiplies load on that constraint instead of relieving it.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Gate new query paths on &lt;code&gt;EXPLAIN type&lt;/code&gt; and the &lt;code&gt;rows_examined / rows_sent&lt;/code&gt; ratio before they ship to a shared database.&lt;/strong&gt; Make the check part of the change, not something you reconstruct from a slow-query digest after the outage.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Alert on &lt;code&gt;threads_running&lt;/code&gt; exceeding core count and on the &lt;code&gt;rows_examined / rows_sent&lt;/code&gt; ratio&lt;/strong&gt;, not on connection count or raw slow-query count. Connection alerting points at the victim; the examined-to-returned ratio points at the culprit.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Before writing the postmortem, ask what change restored service.&lt;/strong&gt; Whatever that change touched is your binding constraint.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Read &lt;code&gt;jstat&lt;/code&gt; correctly.&lt;/strong&gt; &lt;code&gt;M&lt;/code&gt; and &lt;code&gt;CCS&lt;/code&gt; are &lt;code&gt;used / committed&lt;/code&gt;, so 95–99% is routine; judge metaspace danger by whether &lt;code&gt;MC&lt;/code&gt; climbs toward &lt;code&gt;MaxMetaspaceSize&lt;/code&gt;. And remember thread stacks live in native memory outside every &lt;code&gt;-XX&lt;/code&gt; budget, so a thread leak attacks the one region you can&amp;rsquo;t cap with a flag.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;While you&amp;rsquo;re in there, audit every &lt;code&gt;new XxxClient()&lt;/code&gt; for reuse and &lt;code&gt;shutdown()&lt;/code&gt;.&lt;/strong&gt; Any client that starts a background thread is a leak candidate if it&amp;rsquo;s constructed per-call and never closed.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;&lt;em&gt;References:&lt;/em&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Your cloud vendor&amp;rsquo;s object-storage SDK documentation, on client lifecycle and &lt;code&gt;shutdown()&lt;/code&gt; semantics (the leaking &lt;code&gt;IdleConnectionMonitorThread&lt;/code&gt; is the connection-pool sweeper that a per-call client never stops).&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://dev.mysql.com/doc/refman/8.0/en/explain-output.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;MySQL Reference Manual — &lt;code&gt;EXPLAIN&lt;/code&gt; output format&lt;/a&gt; and &lt;a class=&#34;link&#34; href=&#34;https://dev.mysql.com/doc/refman/8.0/en/server-status-variables.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Server Status Variables&lt;/a&gt; (&lt;code&gt;Threads_running&lt;/code&gt;, &lt;code&gt;Rows_examined&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://docs.oracle.com/en/java/javase/21/docs/specs/man/jstat.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;HotSpot &lt;code&gt;jstat&lt;/code&gt; reference&lt;/a&gt; for the &lt;code&gt;-gcutil&lt;/code&gt; column definitions (&lt;code&gt;M&lt;/code&gt; and &lt;code&gt;CCS&lt;/code&gt; are &lt;code&gt;used / committed&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;Companion post: &lt;a class=&#34;link&#34; href=&#34;https://csliubo.com/p/latent-bug-threadlocal-pollution/&#34; &gt;&lt;em&gt;When Long-Stable Code Suddenly Starts Failing&lt;/em&gt;&lt;/a&gt;, the same theme from the other direction, a defect born on day one and detonated later by scale and load.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;&lt;em&gt;A note on desensitization: internal service names have been abstracted (the upload-processing consumer; a separate summarization service), and the cloud vendor&amp;rsquo;s object-storage SDK is referred to generically, with the leaking class&amp;rsquo;s package name anonymized. The table name is abstracted to &lt;code&gt;llm_invocation&lt;/code&gt; (a ledger of the business&amp;rsquo;s LLM calls), and the literal values of &lt;code&gt;vendor&lt;/code&gt; / &lt;code&gt;vendor_id&lt;/code&gt; are replaced with placeholders. The index name &lt;code&gt;idx_vendor_id&lt;/code&gt; and generic column names (&lt;code&gt;is_del&lt;/code&gt;, and so on) are industry-standard and kept as-is. The CPU graph carries no hostnames, instance IDs, or dashboard identifiers, only a generic &lt;code&gt;cpu_use_rate&lt;/code&gt; metric and timestamps. The mechanism (shared-instance CPU saturation, a missing-index full-table scan, an agent&amp;rsquo;s single-machine deep dive leading to a misdiagnosis) and the timeline (2026-06-16, recovery at 17:57) are accurate to what happened in production.&lt;/em&gt;&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
