{"componentChunkName":"component---src-templates-blog-post-js","path":"/one-line-of-python-and-a-3x-drop-in-p99","result":{"data":{"site":{"siteMetadata":{"title":"SoundCloud Backstage Blog","author":"SoundCloud"}},"markdownRemark":{"id":"f8f32b26-58a3-59e1-965f-9e1ea216402f","excerpt":"We removed one log statement from a service’s hot path this week. Its 99th-percentile response latency fell from a steady  to , and CPU usage dropped by about the same factor. The cleanest way to see it: before the change,  never went below  at any hour of any day. After it,  never went above . The two distributions barely touch. In the first hour after the deploy - the daily traffic trough -  touched . That number is real, and it is not the one to quote. The difference between those two…","html":"<p>We removed one log statement from a service’s hot path this week. Its 99th-percentile response latency fell from a steady <code class=\"language-text\">~48ms</code> to <code class=\"language-text\">~18ms</code>, and CPU usage dropped by about the same factor. The cleanest way to see it: before the change, <code class=\"language-text\">p99</code> never went below <code class=\"language-text\">40ms</code> at any hour of any day. After it, <code class=\"language-text\">p99</code> never went <em>above</em> <code class=\"language-text\">40ms</code>. The two distributions barely touch.</p>\n<p>In the first hour after the deploy - the daily traffic trough - <code class=\"language-text\">p99</code> touched <code class=\"language-text\">5ms</code>. That number is real, and it is not the one to quote. The difference between those two readings is most of what this post is about.</p>\n<p>The interesting part isn’t the number. It’s that we spent a while looking for the cost in the wrong place, and the thing that eventually found it was measurement rather than code reading.</p>\n<h2>Some context</h2>\n<p>If you listen to SoundCloud for free, you hear ads. How many and how often isn’t a fixed schedule; the backend decides it per listening session. Part of that decisioning now runs through an internal service: a request comes in describing the current session, and the service returns an action - show an ad pod of a given size, or don’t - along with a cooldown.</p>\n<p>Two properties matter for this story:</p>\n<ul>\n<li>first, it sits directly in the ad-serving request path, so its latency is not an offline concern; it’s user-facing</li>\n<li>second, it serves several policy variants behind the same API, and those variants don’t all use the same inputs.</li>\n</ul>\n<p>That second property is where the bug came from.</p>\n<h2>The API contract, and a caller that doesn’t need it</h2>\n<p>The service’s decision endpoint accepts a <code class=\"language-text\">posteriors</code> field: the per-action statistical state a Bayesian policy needs in order to pick an action. Callers that want a learned policy send it.</p>\n<p>But not every variant is a learned policy. Some run a fixed policy - a deterministic rule with no per-action state at all. Those callers send no posteriors, by design, because there is nothing meaningful to send.</p>\n<p>The endpoint had a validation guard that looked, in essence, like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token comment\"># Log warning for missing actions</span>\nmissing_actions <span class=\"token operator\">=</span> all_actions <span class=\"token operator\">-</span> provided_actions\n<span class=\"token keyword\">if</span> missing_actions<span class=\"token punctuation\">:</span>\n    logger<span class=\"token punctuation\">.</span>warning<span class=\"token punctuation\">(</span>\n        <span class=\"token string\">\"Posteriors missing for actions\"</span><span class=\"token punctuation\">,</span>\n        request_id<span class=\"token operator\">=</span>request<span class=\"token punctuation\">.</span>request_id<span class=\"token punctuation\">,</span>\n        missing_actions<span class=\"token operator\">=</span>missing_actions<span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">for</span> action <span class=\"token keyword\">in</span> missing_actions<span class=\"token punctuation\">:</span>\n        MISSING_POSTERIORS<span class=\"token punctuation\">.</span>labels<span class=\"token punctuation\">(</span>segment_id<span class=\"token operator\">=</span>request<span class=\"token punctuation\">.</span>segment_id<span class=\"token punctuation\">,</span> missing_action<span class=\"token operator\">=</span>action<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">,</span> version<span class=\"token operator\">=</span>version<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>inc<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>The intent was reasonable: if a caller sends posteriors for some actions but omits others, that’s a real anomaly worth surfacing. The implementation didn’t distinguish that case from “this caller sent none at all, as expected.”</p>\n<p>The action space has four entries, so a caller that sends nothing has all four “missing.” For every fixed-policy request the service emitted a structured warning and four metric increments. For zero signal. And that turned out to be effectively all of its traffic: every policy variant then receiving requests tripped the guard, on every request. Not a hot path with an occasional expensive branch - a hot path with an unconditional one.</p>\n<p>Worth being precise about scope. This service sits behind SoundCloud’s ad-serving stack - a high-volume, continuously-hot path that decides ad load for listening sessions across the platform, around the clock. It currently takes the experiment-allocated slice of that traffic, which is already substantial, and the intent is for ad-load decisioning like this to cover a growing share of free-tier listening. Whatever the guard cost per request, it cost it on 100% of them - and that alone was enough to hold <code class=\"language-text\">p99</code> nearly three times above where it needed to be. At the volume this is heading for, that is not a rounding error.</p>\n<p>None of this was gratuitous. The warning and the counter were deliberate instrumentation - <code class=\"language-text\">MISSING_POSTERIORS</code> fed a dashboard panel that let us watch, per policy variant, whether callers were sending the state they were supposed to. The log line had been there for a while. Nothing was broken. Alerts were quiet. It simply cost more than anyone had priced in.</p>\n<h2>The wrong theory first</h2>\n<p>The initial hypothesis was string formatting: building the log message - serialising the set of missing actions, interpolating the request ID - on every request, and paying for that work whether or not anything consumed it. That’s a well-known Python cost and a plausible culprit.</p>\n<p>We never isolated it with a benchmark, and we should be honest about that. What argues against it is the memory graph: if the cost had been building strings and objects we immediately discarded, we would expect the win to show up in allocation. Per-pod memory moved only from <code class=\"language-text\">~170–175MB</code> to <code class=\"language-text\">~150–160MB</code> - real, but nowhere near proportional to the latency change. The cost was in <em>writing</em> the line, not building it.</p>\n<p>That distinction matters because it changes the general lesson. “Don’t format strings you might not log” is a micro-optimisation. “Don’t emit a log line on 100% of hot-path traffic” is an architectural constraint.</p>\n<p>Why the write is expensive is not mysterious once you look at it: a structured logger renders the event, serialises it, and writes it to stdout synchronously, inside the request. That write goes to a log collector. The serialisation holds the GIL, so no other handler runs during it; the write itself releases the GIL but still parks that request mid-flight. Averaged across all requests it disappears into the mean, which is why nobody had noticed it. It surfaces in the tail - here it was most of the tail.</p>\n<h2>The fix</h2>\n<div class=\"gatsby-highlight\" data-language=\"diff\"><pre class=\"language-diff\"><code class=\"language-diff\"><span class=\"token deleted-sign deleted\">- # Log warning for missing actions\n</span><span class=\"token inserted-sign inserted\">+ # Only a real anomaly when the caller sent SOME posteriors but omitted a few.\n+ # Callers using hardcoded_policy (e.g. promoted) send none by design, so treating\n+ # \"all missing\" as an error emitted a structlog warning + 4 metric increments on\n+ # 100% of traffic for zero signal.\n</span><span class=\"token unchanged\">  missing_actions = all_actions - provided_actions\n</span><span class=\"token deleted-sign deleted\">- if missing_actions:\n</span><span class=\"token inserted-sign inserted\">+ if missing_actions and request.posteriors:</span></code></pre></div>\n<p>Five insertions, two deletions - and four of the five are the comments explaining why. The guard now fires only when a caller sent <em>some</em> posteriors and omitted others, the case it was written for. Callers that send none pass through silently.</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/blog/static/9f6f21b9ec5b32296177d3bcda9aaf41/c533c/p99-single-panel-deploy-step.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 33%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA7UlEQVR42oVQS07FMAzsJWiTOHYS59smwFuAqJDYcP9D4fSJBQgJaWSNY48z9lL2kcpR2sj1yK2jj6uGzdh/gY4X6TbowAUgL9CWZg1+tsLf+kUjSe1hM3cIny/GGndN0WDZa6JN/1aKwaWeo38+5de9nj08ZkF5O/aP0d57eml8K+U8IPpVgTI4R1wQ5RRD8CaQjRIdRAfshNjkhUi0OWAOxtM8hLWaUHwpRH35Whxnz8USywFkf0tBiKRCyEcU4gK3WvrYb8+xN0ycxs69UowL58apfm+CEtUV76mCadUghVTlDxfKpnFTsF74AuR+MbE3u6KSAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"p99 stepping down to 4.98ms at the deploy\"\n        title=\"p99 stepping down to 4.98ms at the deploy\"\n        src=\"/blog/static/9f6f21b9ec5b32296177d3bcda9aaf41/a331c/p99-single-panel-deploy-step.png\"\n        srcset=\"/blog/static/9f6f21b9ec5b32296177d3bcda9aaf41/36ca5/p99-single-panel-deploy-step.png 200w,\n/blog/static/9f6f21b9ec5b32296177d3bcda9aaf41/a3397/p99-single-panel-deploy-step.png 400w,\n/blog/static/9f6f21b9ec5b32296177d3bcda9aaf41/a331c/p99-single-panel-deploy-step.png 800w,\n/blog/static/9f6f21b9ec5b32296177d3bcda9aaf41/8537d/p99-single-panel-deploy-step.png 1200w,\n/blog/static/9f6f21b9ec5b32296177d3bcda9aaf41/1a152/p99-single-panel-deploy-step.png 1600w,\n/blog/static/9f6f21b9ec5b32296177d3bcda9aaf41/c533c/p99-single-panel-deploy-step.png 1916w\"\n        sizes=\"(max-width: 800px) 100vw, 800px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p><em>Deploy completes at 09:47 UTC. <code class=\"language-text\">p99</code> goes from a <code class=\"language-text\">~50ms</code> band with regular spikes past <code class=\"language-text\">100ms</code> to <code class=\"language-text\">4.98ms</code> - a single step, no ramp. This is the trough reading; see the seven-day view below for where it settles.</em></p>\n<p>A few things are worth reading off that dashboard carefully, because they’re what make the attribution credible rather than suggestive:</p>\n<ul>\n<li>It was a single-commit deploy</li>\n<li>The service had not shipped for a week</li>\n<li>The only functional change between the previously running image and the new one was that one guard condition. There is nothing else to credit.</li>\n</ul>\n<p><strong>The old level was a plateau, not a bad morning.</strong> Over the two days before the deploy, <code class=\"language-text\">p99</code> sat in a <code class=\"language-text\">~45–55ms</code> band continuously, with regular spikes past <code class=\"language-text\">100ms</code>, through two complete daily traffic cycles - from the evening peak all the way down to the quietest hour of the night. It never dipped below <code class=\"language-text\">40ms</code> at any point, at any volume, including at troughs quieter than the ones the fixed service now handles at <code class=\"language-text\">18ms</code>. This wasn’t a transient we caught at a lucky moment.</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/blog/static/9e10fc064ea6ff127ce17b2b2b07d37f/41913/dashboard-48h.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 33%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAAAsTAAALEwEAmpwYAAABfklEQVR42iWOz0ocQRCH52gQxCi7Oz3T3dVV1TO9M+P83TWuy4bVKIHE5CIh8ZBc8giSk3gJBMFTCHgSfADxKJhDbjn7Bj5DzgG92CHwHap+vyr4AkqGEkgaUoa0B8mvEWAotUaLnEhtpIJY/8O3hlj6Wel+JAOtlTEIYBQA+YoMIjARIGtkZmRCa9lasj793zK5NAHioOpGeVkWVVW2nVur8qot6g44iQG9S1bWVTd2+VpWth5XNGX7LC3qyMsiBTZrMSk1OY9Q4JWUwUEk+yL2z8BNT5a9yNrUGU5CEYrIEwll/E1Q17KuFVqJLLN2MhxtczGGpIiBejF8fC/Pvz3d3xMLS0oA7X44/HR8Od37vLK6HGkMXr5Qu1t6OtHTTdzYft1tvWuev8nX52ALU+W/f+Lf2yd3v1a+Hy2e/dg/ub4/vXn4evXn4PD84MtFILQL9VChCyX2QtEfDAZCeGWFibbpaJ03xmY6oVc7MJtl+WieVhPXzJrZ22E3fwSyHEx7J6UeKgAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"Memory, CPU, p99 and request volume across a 48-hour window\"\n        title=\"Memory, CPU, p99 and request volume across a 48-hour window\"\n        src=\"/blog/static/9e10fc064ea6ff127ce17b2b2b07d37f/a331c/dashboard-48h.png\"\n        srcset=\"/blog/static/9e10fc064ea6ff127ce17b2b2b07d37f/36ca5/dashboard-48h.png 200w,\n/blog/static/9e10fc064ea6ff127ce17b2b2b07d37f/a3397/dashboard-48h.png 400w,\n/blog/static/9e10fc064ea6ff127ce17b2b2b07d37f/a331c/dashboard-48h.png 800w,\n/blog/static/9e10fc064ea6ff127ce17b2b2b07d37f/8537d/dashboard-48h.png 1200w,\n/blog/static/9e10fc064ea6ff127ce17b2b2b07d37f/1a152/dashboard-48h.png 1600w,\n/blog/static/9e10fc064ea6ff127ce17b2b2b07d37f/41913/dashboard-48h.png 2048w\"\n        sizes=\"(max-width: 800px) 100vw, 800px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p><em>The same dashboard across two full traffic cycles. <code class=\"language-text\">p99</code> (bottom left) holds its <code class=\"language-text\">~50ms</code> band through both the evening peak and the morning trough, then steps down at the deploy. CPU (top right) sits at <code class=\"language-text\">8–10</code> and falls to <code class=\"language-text\">2–4</code>. Request volume (bottom right) is strongly diurnal, which is what makes the flat pre-deploy latency band meaningful.</em></p>\n<h2>Where it actually settled</h2>\n<p>The graph above ends a few hours after the deploy, and if we had stopped there we would have published the wrong number. The deploy landed near the daily trough. Over the following days, as load returned, <code class=\"language-text\">p99</code> rose off <code class=\"language-text\">5ms</code> and settled into a <code class=\"language-text\">~15–25ms</code> band, spiking to <code class=\"language-text\">35–40</code> at peak - holding there through four subsequent evening peaks at full traffic.</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/blog/static/c9eb074c888ae1c3f6abb88fc26d8100/41913/p99-seven-days.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 40%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABPklEQVR42qWQ70rDMBTF+wrKtrZpk7RJ/28dazfqlA0dUybIBBV1CPpN9tVH8YUvx7TbE8wPP869STgnHEvFGaROEBgcX/wby+UBbI93tAft7vjydEMmQ4iwNTrARAhXBGY+zdSSsUZVS4ShjcnVCvP7L8hkZC75aYbTVYmfvcDu4QyvX+/Y7n8x33zAZt7xlxwDM9uej4HLDG6nbUVuW9GRgyGHpYoY+VCinnAMRwquTBCVTfegNW0rEFEOrjOoojZU0AYeRuiZkF4baNQxuDyE5TCJc5t39F3fwNBnDGm1RNHc4mLzifr6CfXNMxbbb0yWj2jWbyhHYxQm7E6nWNRrxIsd8ssXWEEUk05S0mluNCMVpxRlJWXjhpJhRWk5Iz9QJFRMPNDkCUFMBKSVpnGU0TTOqUhLCosZqWJGf7Mp4KGFBqIHAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"A week of p99 around the 07/31 deploy, showing two non-overlapping bands\"\n        title=\"A week of p99 around the 07/31 deploy, showing two non-overlapping bands\"\n        src=\"/blog/static/c9eb074c888ae1c3f6abb88fc26d8100/a331c/p99-seven-days.png\"\n        srcset=\"/blog/static/c9eb074c888ae1c3f6abb88fc26d8100/36ca5/p99-seven-days.png 200w,\n/blog/static/c9eb074c888ae1c3f6abb88fc26d8100/a3397/p99-seven-days.png 400w,\n/blog/static/c9eb074c888ae1c3f6abb88fc26d8100/a331c/p99-seven-days.png 800w,\n/blog/static/c9eb074c888ae1c3f6abb88fc26d8100/8537d/p99-seven-days.png 1200w,\n/blog/static/c9eb074c888ae1c3f6abb88fc26d8100/1a152/p99-seven-days.png 1600w,\n/blog/static/c9eb074c888ae1c3f6abb88fc26d8100/41913/p99-seven-days.png 2048w\"\n        sizes=\"(max-width: 800px) 100vw, 800px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p><em>A week of <code class=\"language-text\">p99</code> around the <code class=\"language-text\">07/31</code> deploy, about two days before and five after. The important feature isn’t the step - it’s that the two bands don’t overlap. Before: a <code class=\"language-text\">~45–55ms</code> band that never dipped below <code class=\"language-text\">40ms</code> (the single vertical drop on <code class=\"language-text\">07/30</code> is a pod rotation, not a latency reading), with spikes to <code class=\"language-text\">120ms</code>. After: a <code class=\"language-text\">~15–25ms</code> band that never exceeded <code class=\"language-text\">40ms</code>.</em></p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/blog/static/e7e10d2c0c9c02b28ab316820be968b1/46abd/dashboard-seven-days.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 34.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAAAsTAAALEwEAmpwYAAABTklEQVR42lWOP0rEQBSHcwObZDPJzGTmzf/MJBsTd11cWFwbt7FQ8A6CoKBgr62NjXoEe/EENhZ7Bg/jrFoofPx4PN7j+yXSBONaXIkMkb+kCI8KUtLffZrjf4xwluMEtCMAEUw5Juw7OeWiAklBgjZcKSZ1QfgPiHBcRaAgLIlmF7adb+2GsakbFzplPQVFuJTWu6YzfuyboW5657vQ9nUdQCjKRKJ91/az+COMj4CupQ2UK4RZyUDYHRsGDlAxqGIdLkCZtBLRn2MWa2tpNNhuVNIM4QyVKSrjnGOaIraY08VyEuYn/f5xmB2afiHDdAkGERYvkyib7upmtqpkzU0j/CD9wJgQUm+h5u4Wv76sLp4+r5/XV4/r84ePm/u394PV2dEl2ztNYkMuTSWU9r1rJzFV3XnjvR1T3YK2zjtlAteBKbdJ6bjQRrcFM1/nL0RXkbpcNAAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"Memory, CPU, p99 and request volume across the same week\"\n        title=\"Memory, CPU, p99 and request volume across the same week\"\n        src=\"/blog/static/e7e10d2c0c9c02b28ab316820be968b1/a331c/dashboard-seven-days.png\"\n        srcset=\"/blog/static/e7e10d2c0c9c02b28ab316820be968b1/36ca5/dashboard-seven-days.png 200w,\n/blog/static/e7e10d2c0c9c02b28ab316820be968b1/a3397/dashboard-seven-days.png 400w,\n/blog/static/e7e10d2c0c9c02b28ab316820be968b1/a331c/dashboard-seven-days.png 800w,\n/blog/static/e7e10d2c0c9c02b28ab316820be968b1/8537d/dashboard-seven-days.png 1200w,\n/blog/static/e7e10d2c0c9c02b28ab316820be968b1/1a152/dashboard-seven-days.png 1600w,\n/blog/static/e7e10d2c0c9c02b28ab316820be968b1/46abd/dashboard-seven-days.png 1919w\"\n        sizes=\"(max-width: 800px) 100vw, 800px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p><em>Memory, CPU, <code class=\"language-text\">p99</code> and request volume across the same week. CPU tracks the latency win - <code class=\"language-text\">8–10</code> down to <code class=\"language-text\">2–4</code> - while memory moves only slightly. The diurnal request pattern (bottom right) is what pulls <code class=\"language-text\">p99</code> up off its post-deploy floor.</em></p>\n<p>One scoping caveat: a second deploy on <code class=\"language-text\">08/03</code> took CPU down again, to <code class=\"language-text\">~1–2</code>, and <code class=\"language-text\">p99</code> to a <code class=\"language-text\">~10–15ms</code> band - a separate change that moved a per-request config lookup to startup. Every number above is measured against the <code class=\"language-text\">07/31–08/02</code> window, before that landed, so if anything the current service is faster than this post claims.</p>\n<p>So the honest claim is <code class=\"language-text\">~48ms</code> → <code class=\"language-text\">~18ms</code> sustained, not <code class=\"language-text\">50ms</code> → <code class=\"language-text\">5ms</code>. Just under <code class=\"language-text\">3×</code>, through peak load, on a single commit - and the non-overlap is the part worth keeping: <strong>the best hour of the old service was worse than the worst hour of the new one</strong>. Quoting the <code class=\"language-text\">5ms</code> trough figure would have been an unforced error.</p>\n<h2>The second-order win</h2>\n<p>The nicer outcome wasn’t the latency. It was that a check nobody could act on stopped pretending to be a signal.</p>\n<p>The missing-actions metric had been firing constantly, so its dashboard panel was a solid wall of noise - useless for spotting the real anomaly it was built to catch. After the fix it went flat at zero.</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/blog/static/62b6ff4487328e8e2cdd4326dbdb5d36/b6117/missing-actions-counters.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 21.999999999999996%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAIAAAABPYjBAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAlklEQVR42kWPURLDIAhEc4mmKCAqatUkvf/xSppJO/P+3s4uLDFXbSNpy7UbDsPT049cXqctTVuP2oD+dnW4mMCoTpIPyUkGkssB8grY+s5RPBEyEbNDhhvLLCEpiSCzJQwI0SoNcw/wNosSAQmIHJNnuqu/yyx5zH1s7zGPbR5jOzgphkR2C4tVn3/VWeowtFSKp7rsB4PTHMVJsG+rAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"The missing-actions counters dropping to zero at the deploy\"\n        title=\"The missing-actions counters dropping to zero at the deploy\"\n        src=\"/blog/static/62b6ff4487328e8e2cdd4326dbdb5d36/a331c/missing-actions-counters.png\"\n        srcset=\"/blog/static/62b6ff4487328e8e2cdd4326dbdb5d36/36ca5/missing-actions-counters.png 200w,\n/blog/static/62b6ff4487328e8e2cdd4326dbdb5d36/a3397/missing-actions-counters.png 400w,\n/blog/static/62b6ff4487328e8e2cdd4326dbdb5d36/a331c/missing-actions-counters.png 800w,\n/blog/static/62b6ff4487328e8e2cdd4326dbdb5d36/8537d/missing-actions-counters.png 1200w,\n/blog/static/62b6ff4487328e8e2cdd4326dbdb5d36/1a152/missing-actions-counters.png 1600w,\n/blog/static/62b6ff4487328e8e2cdd4326dbdb5d36/b6117/missing-actions-counters.png 1932w\"\n        sizes=\"(max-width: 800px) 100vw, 800px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<p><em>The missing-actions counters going to zero at <code class=\"language-text\">09:47–09:50</code>, broken out per policy variant and user segment. Every series is the same expected absence reported as an anomaly.</em></p>\n<p>A metric pinned at zero isn’t meaningful either - the interesting case is visible in the logs - so it came out in a follow-up cleanup.</p>\n<p>Two things had been quietly wrong at once, and they were the same thing: a check that couldn’t tell “expected absence” from “unexpected absence” was both expensive and blind.</p>\n<h2>What we’d take from this</h2>\n<p><strong>An unconditional log line is a hot-path dependency, not a detail</strong>. This one put a synchronous write to a log collector inside every request, and under the GIL that write blocks every other handler. It cost nothing in the mean, which is why it survived as long as it did. It cost most of the tail, which is where users live.</p>\n<p><strong>Latency was the symptom; capacity was the finding.</strong> <code class=\"language-text\">p99</code> is what users felt, but the same guard was eating roughly two thirds of the path’s CPU - <code class=\"language-text\">8–10</code> down to <code class=\"language-text\">2–4</code> at identical traffic. A cost paid on 100% of requests compounds with every percentage point of rollout, so hot-path waste is cheapest to find before the traffic arrives.</p>\n<p><strong>“Missing” is not one condition</strong>. Absence that a caller intends and absence that indicates a bug need different handling. Collapsing them produces alerts nobody can act on - and here, a bill nobody had noticed.</p>\n<p><strong>Measure before you optimise, including your own explanation</strong>. The formatting theory was plausible, cheap to believe, and would have led to a fix that bought almost nothing.</p>\n<p><strong>Wait a week before you quote a number.</strong> Our first reading was taken at the daily trough, which made a <code class=\"language-text\">3×</code> win look like <code class=\"language-text\">10×</code>. The inflated number is always the one that travels: it’s the more exciting claim, it’s true for about six hours, and it would not have survived contact with the first person to open the dashboard a week later. Let a change ride through a full traffic cycle before you attach a number to it.</p>\n<p><strong>If a signal is always firing, it isn’t a signal</strong>. A metric pinned at a constant value is either measuring the wrong thing or measuring a bug. Either way it’s worth five minutes of attention, not a permanent dashboard panel.</p>","fields":{"teaserPlainText":null},"frontmatter":{"title":"One line of Python, and a 3× drop in p99","date":"Aug 11th, 2026","categories":["Performance","Engineering","Monitoring","Logging","Python","Ads"],"authors":[{"name":"Filcho Dragunchev","link":null}],"image":null}}},"pageContext":{"slug":"/one-line-of-python-and-a-3x-drop-in-p99","previous":{"fields":{"slug":"/less-is-more-why-soundcloud-low-passes-its-aac-transcodings"},"frontmatter":{"permalink":"less-is-more-why-soundcloud-low-passes-its-aac-transcodings","title":"Less Is More: Why Audio on SoundCloud Looks Different","categories":["Audio","Engineering","Transcodings","Streaming"],"authors":[{"name":"Joe Reid","link":null}]}},"next":null}},"staticQueryHashes":["2630554514"]}