<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Optimized Infra]]></title><description><![CDATA[Sharing my experiences in achieving optimal performance and efficiency in infrastructure]]></description><link>https://blog.zmalik.dev</link><image><url>https://substackcdn.com/image/fetch/$s_!csb0!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01337def-03c5-4291-b06e-cd2d75129c13_80x80.png</url><title>Optimized Infra</title><link>https://blog.zmalik.dev</link></image><generator>Substack</generator><lastBuildDate>Fri, 24 Apr 2026 12:59:35 GMT</lastBuildDate><atom:link href="https://blog.zmalik.dev/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Zain]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[zainwrites@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[zainwrites@substack.com]]></itunes:email><itunes:name><![CDATA[Zain]]></itunes:name></itunes:owner><itunes:author><![CDATA[Zain]]></itunes:author><googleplay:owner><![CDATA[zainwrites@substack.com]]></googleplay:owner><googleplay:email><![CDATA[zainwrites@substack.com]]></googleplay:email><googleplay:author><![CDATA[Zain]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Intellectual Honesty in the Age of Vibes]]></title><description><![CDATA[I didn't know intellectual honesty was a skill until it started appearing in my performance reviews.]]></description><link>https://blog.zmalik.dev/p/intellectual-honesty-in-the-age-of</link><guid isPermaLink="false">https://blog.zmalik.dev/p/intellectual-honesty-in-the-age-of</guid><dc:creator><![CDATA[Zain]]></dc:creator><pubDate>Mon, 23 Feb 2026 21:19:54 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!TkW2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The first time I encountered it as a company value, it was called &#8220;intellectual honesty&#8221; The expectation was that you do the deep dive, that you seek the truth, that you don&#8217;t stop at the first plausible explanation. I was told I was good at it. In my last company, at CloudKitchens, it came up again in my performance reviews, but under a different name: &#8220;truth seeking&#8221;. Same idea, different label. I remember reading it and thinking, sure, obviously. Isn&#8217;t that just engineering? You find the bug, you find the root cause, you fix the thing. But over time, I realized something: not everyone treats this as the default. </p><p>For many engineers, it&#8217;s one approach among many. For some engineers, it is the only approach. They need to know how things work to actually fix them. Not roughly. Not &#8220;the docs say this&#8221; Actually know. Read the source, tcpdump the traffic, go as low as possible.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.zmalik.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Optimized Infra! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Most companies have some version of this value, they just name it differently. Atlassian calls it &#8220;Open company, no bullshit.&#8221; Netflix calls it &#8220;Candor.&#8221; Amazon calls it &#8220;Dive Deep,&#8221; being skeptical when metrics and anecdotes don&#8217;t match. Bridgewater calls it &#8220;radical truth and radical transparency.&#8221; The language varies. The underlying idea is the same: </p><div class="pullquote"><p>&#8220;Have the courage to see things as they actually are, not as you wish they were&#8221;</p></div><p>Every one of these companies felt the need to put this in writing because the natural human tendency runs the other direction.</p><p>I never thought of it as a philosophy. It was just how I operated. That was the joy of work actually. But then I started sitting in incident reviews where sometimes blame was shifted sideways, where the postmortem became a performance instead of a diagnosis. Not because people were malicious, but because investigating further would mean challenging assumptions they were comfortable with. The database was fine. The config was correct. The deploy went smoothly. It always went smoothly. Nobody wanted to be the person who pulled the thread, because pulling the thread meant admitting the thing they trusted was the thing that broke.</p><p>And it wasn&#8217;t just postmortems. I sat in design reviews, architecture discussions, planning meetings where everyone nodded along and moved on. Not because they agreed, but because asking a real question carried risk. Ask &#8220;why are we doing it this way?&#8221; and you might come across as rude, as not a team player, as the person who slows things down. And the worse: your question might reveal that you also don&#8217;t fully understand the thing you&#8217;re supposed to own. So you nod. Everyone nods. The meeting ends. The assumption survives.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TkW2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TkW2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png 424w, https://substackcdn.com/image/fetch/$s_!TkW2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png 848w, https://substackcdn.com/image/fetch/$s_!TkW2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png 1272w, https://substackcdn.com/image/fetch/$s_!TkW2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TkW2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png" width="1456" height="793" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:793,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2563683,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/188944080?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TkW2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png 424w, https://substackcdn.com/image/fetch/$s_!TkW2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png 848w, https://substackcdn.com/image/fetch/$s_!TkW2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png 1272w, https://substackcdn.com/image/fetch/$s_!TkW2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F77f234db-b587-4c9a-a953-098d7b389c56_1888x1028.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Every team like this eventually produces a well-intentioned deflector. Someone who, in the middle of an outage or a heated incident review, confidently declares: &#8220;This all happened because of X.&#8221; Maybe it&#8217;s a network blip. Maybe it&#8217;s a bad deploy. Maybe it&#8217;s &#8220;the cloud provider had an issue&#8221; The explanation is plausible enough that nobody challenges it, specific enough that it sounds like a root cause, and shallow enough that it doesn&#8217;t threaten anyone&#8217;s assumptions. Leadership hears it, writes it down, moves on. The engineers are relieved.</p><div class="pullquote"><p>because now everyone is looking in the same direction and that direction isn&#8217;t at them.</p></div><p>I&#8217;ve seen this play out with noisy neighbors more times than I can count. Service is slow. Latency is spiking. Someone says &#8220;it&#8217;s noisy neighbors, we need isolated nodes.&#8221; It sounds right. It sounds like infrastructure wisdom. So the team escalates, gets dedicated capacity, and for a while things seem better, maybe because the deploy also happened to restart the pods, maybe because traffic patterns shifted. Then the latency comes back. So they ask for more isolation. More dedicated nodes. The bill goes up, the problem persists, and six weeks later someone finally profiles the application and finds a connection pool misconfiguration, or a missing index, or a goroutine leak that&#8217;s been there since day one. The noisy neighbor was never the problem. But it was a comfortable answer, and comfortable answers don&#8217;t get questioned.</p><p>The well-intentioned deflector isn&#8217;t lying. They might even be partially right. But &#8220;partially right&#8221; is the most dangerous kind of wrong in engineering, because it closes the investigation before it reaches the actual cause. And then, three months later, the same incident happens again. Maybe with a different trigger, maybe in a different service, but the same underlying pattern. Because the real root cause was never found. It was just too comfortable not to look.</p><p>The more I saw that pattern, across teams, across companies, across incidents, the more I came to believe that rigorous thinking isn&#8217;t just <em>a</em> pillar of engineering. It&#8217;s <em>the</em> pillar. Everything else, design, architecture, reliability, performance, is downstream of whether you&#8217;re building on verified reality or comfortable assumptions.</p><p>And right now, in the age of LLMs, it&#8217;s about to become the most valuable skill you can build.</p><div><hr></div><h2>The Interstellar Tax</h2><p>In space navigation, there&#8217;s a principle called the 1-in-60 rule: for every degree you&#8217;re off course, you drift one mile for every sixty you travel. At aviation distances, that&#8217;s a minor correction. At interplanetary distances, it&#8217;s annihilation.</p><p>NASA&#8217;s Mars Climate Orbiter traveled 669 million kilometers over nine and a half months. One team at Lockheed Martin generated thrust data in pound-force-seconds. The navigation team at JPL assumed it was in newton-seconds. Nobody verified. Seven small errors accumulated across the journey. When the spacecraft arrived at Mars, it was 170 kilometers closer than planned - too close. It burned up. A $327 million mission, destroyed by a unit mismatch that went unchallenged for 286 days.</p><p>Every wrong assumption in engineering works like this. It&#8217;s not a static cost - it&#8217;s a trajectory error. The longer you travel on a false heading, the further you end up from reality. And by the time you discover you&#8217;re off course, the fuel to correct may already be spent.</p><p>I think about this compounding constantly. In the infrastructure world, I&#8217;ve seen teams spend months debugging network performance because everyone assumed cloud provider provided machines were fine. The assumption felt safe: </p><div class="pullquote"><p>&#8220;it&#8217;s a managed component, it&#8217;s battle-tested, thousands of clusters run it.&#8221; </p></div><p>But assumptions don&#8217;t care about your feelings.  That&#8217;s the interstellar tax. Every day spent traveling in the wrong direction is a day you have to travel back.</p><div><hr></div><h2>A Brief History of Expensive Assumptions</h2><p>The wreckage of unchallenged assumptions is scattered across engineering history. The pattern is always the same: something was assumed, evidence was available, nobody checked.</p><p><strong>Ariane 5 (1996)</strong> - Assumed Ariane 4 flight software value ranges would hold for a faster rocket. 64-bit to 16-bit overflow. $370M rocket self-destructed 37 seconds after liftoff.</p><p><strong>Boeing 737 MAX (2018-2019)</strong> - Assumed pilots would diagnose and override a faulty sensor-driven system within three seconds. 346 people dead. ~$20B in losses.</p><p><strong>Hubble Space Telescope (1990)</strong> - Assumed the primary measuring device was calibrated correctly. Two backup instruments flagged the error. Readings dismissed. $1.5B mirror polished to exactly the wrong shape. $700M to fix.</p><p><strong>Challenger (1986)</strong> - Assumed O-rings would hold at 31&#176;F despite engineers recommending no launch below 53&#176;F. Seven dead, 73 seconds after liftoff.</p><p><strong>Therac-25 (1985-1987)</strong> - Assumed software alone could replace hardware safety interlocks. Manufacturer called overdose &#8220;impossible.&#8221; Six patients overdosed, three dead over 19 months.</p><p><strong>Knight Capital (2012)</strong> - Assumed a deployment script reporting &#8220;success&#8221; meant all eight servers were updated. One wasn&#8217;t. Dormant code from 2005 activated. $440M lost in 45 minutes.</p><p><strong>CrowdStrike (2024)</strong> - Assumed their content validator would catch a field count mismatch. Template defined 21 fields, sensor provided 20. 8.5 million Windows devices blue-screened simultaneously. ~$5.4B in losses.</p><p><strong>AWS S3 (2017)</strong> - Assumed a routine capacity removal command had safe bounds and that restart procedures matched current scale. Took down ~20% of the internet for four hours. $150M+ in losses.</p><p><strong>GitLab (2017)</strong> - Assumed their backups worked. Had five separate backup mechanisms. Zero functioned. Engineer ran <code>rm -rf</code> on production. 300GB of data lost. 18-hour outage.</p><p><strong>Cloudflare (2019)</strong> - Assumed a regex passing CI was safe for global deployment. Catastrophic backtracking spiked every edge server to 100% CPU. 27 minutes of global outage.</p><p>Same pattern. Different decade, different domain. The assumption was reasonable. The evidence was available. Nobody verified.</p><div><hr></div><h2>Now Add LLMs to the Equation</h2><p>Everything I&#8217;ve described above happened in a world where humans wrote their own code, did their own calculations, and made their own decisions about what to trust. The failure mode was always the same: humans not questioning their own assumptions. And even in that world, I watched people dodge hard questions in postmortems because the answers might be uncomfortable.</p><p>LLMs don&#8217;t create this problem. They accelerate it. And that&#8217;s exactly why the engineers who build real understanding right now will be more valuable than ever.</p><p>There&#8217;s a quieter version of this problem that nobody talks about. You make it up the ladder. You get the title, the scope, the org chart with your name near the top. And somewhere along the way, the systems you&#8217;re responsible for outgrew your hands-on understanding. You&#8217;re supposed to know, but you don&#8217;t. Not really. Not at the level where you could debug it yourself at 3 AM. So you develop instincts for when to nod confidently and when to delegate the question to someone who might actually know. This has always been part of senior engineering leadership, the gap between authority and understanding. But it used to be bounded by the fact that <em>someone</em> on the team had to write the code, had to understand it, had to make it work from scratch.</p><p>Now that gap can be closed with a prompt. And that changes what it means to be the person who actually understands.</p><p>With vibe coding and AI-assisted everything, the code actually works. That&#8217;s the interesting part. You prompt an LLM, you get a service that compiles, passes basic tests, handles the happy path. It looks like progress. It feels like velocity. But ask two questions and reality starts crumbling. Why Redis? Why can the server only handle this many requests? Why is this retry logic unbounded? The person who wrote it, who prompted it into existence, often can&#8217;t answer. Not because they&#8217;re bad engineers, but because they never had to confront those questions. The LLM absorbed them.</p><p>And here&#8217;s where it gets strange: the answers to those questions might come from the LLM itself. So now you have a human-bot interaction where the bot wrote the code and the bot explains the code, and the human in the middle is doing prompt engineering and interpreting the output. At some point you have to ask: what is the human actually contributing? If you&#8217;re not the one who understands why Redis, if you&#8217;re not the one who can reason about request capacity, you&#8217;re not engineering. You&#8217;re proxying. And you could remove the middle man entirely, just let the bot talk to itself, and the output would be roughly the same.</p><p>This is where the opportunity is. In a world where anyone can generate code, the person who can explain <em>why</em> that code is right or wrong becomes irreplaceable. The person who can ask the second question, the one the LLM can&#8217;t answer about your specific system, your specific failure modes, your specific constraints, that person is more valuable now than they&#8217;ve ever been. For the person who already felt the pressure to pretend they understood, LLMs are the perfect collaborator. They don&#8217;t judge. They don&#8217;t ask follow-ups. They give you something that looks right and let you move on. But for the person willing to actually understand, LLMs are the most powerful learning accelerator ever built. The difference is honest self-assessment about which one you&#8217;re doing.</p><p>More bugs. More confidence. That&#8217;s not a tooling problem. That&#8217;s an intellectual rigor problem.</p><div><hr></div><h2>The Assumptions Worth Questioning</h2><p>LLMs introduce a new category of assumptions into your codebase. Not maliciously. Not even incorrectly, most of the time. But silently, and at scale. Knowing what to question is the skill.</p><p><strong>Code that looks correct is not necessarily correct.</strong> LLMs produce code that reads like it was written by a competent human. It follows naming conventions, uses popular patterns, includes comments. This surface-level quality is genuinely impressive, and it&#8217;s also exactly what makes it worth scrutinizing. It passes the &#8220;does this look right?&#8221; filter, which was never a reliable filter to begin with. The engineer who reads it critically, instead of just visually, is the one who catches the issue before production does.</p><p><strong>&#8220;It worked&#8221; does not mean &#8220;it&#8217;s right.&#8221;</strong> LLM-generated code often works for the common case. But engineering has always been about the cases you didn&#8217;t think of. The Ariane 5 code worked perfectly on Ariane 4 trajectories. The MCAS system worked perfectly until the sensor failed. The engineer who asks &#8220;what input would break this?&#8221; is doing the work that separates a demo from a production system.</p><p><strong>Speed of production is not progress.</strong> When you can generate a thousand lines in minutes, it feels productive. But if you don&#8217;t understand those thousand lines, you&#8217;ve created technical debt at a speed that was never before possible. The engineer who slows down to understand what was generated, who treats the LLM output as a first draft rather than a finished product, is building something that will actually hold up.</p><p><strong>The model does not understand your system.</strong> An LLM has no knowledge of your architecture, your invariants, your failure modes, your SLAs. It generates code based on statistical patterns from millions of other systems. It picked Redis because Redis appears frequently in training data next to the word &#8220;cache&#8221; not because it analyzed your access patterns and consistency requirements. It set the connection pool to 10 because that&#8217;s a common default, not because it profiled your throughput under load. The question is never &#8220;can the LLM write this code?&#8221; The question is &#8220;does this code reflect the reality of <em>my</em> system?&#8221; The engineer who can answer that, who understands the system deeply enough to validate or reject what the model produced, is the one who will be indispensable. But if the human&#8217;s understanding also came from the LLM, you have a closed loop with no ground truth anywhere in the circuit.</p><div><hr></div><h2>Honest Engineering as a Practice</h2><p>I&#8217;ve come to see honest engineering not as a personality trait but as a set of concrete habits. Things you do before you trust, before you ship, before you declare something fixed.</p><p><strong>Measure before theorizing.</strong> The Ariane 5 team assumed Ariane 4 ranges would hold. They never measured actual values for the new flight profile. In the LLM age, this means: don&#8217;t assume generated code handles your edge cases. Instrument it. Profile it. Feed it adversarial inputs.</p><p><strong>Treat contradicting data as signal, not noise.</strong> Hubble&#8217;s backup instruments flagged the mirror flaw. The readings were dismissed. The Therac-25 operators reported burns; the manufacturer said it was impossible. When your monitoring says something different from your mental model, your mental model is wrong. This applies equally to AI-generated code that &#8220;should work&#8221; but behaves unexpectedly in staging.</p><p><strong>Verify across boundaries.</strong> The Mars Orbiter failed at the interface between Lockheed Martin and JPL, each correct in isolation, wrong together. LLMs have no concept of your system boundaries, your team interfaces, your deployment constraints. Every piece of generated code that crosses a boundary, between services, between teams, between trust zones, needs manual verification.</p><p><strong>Understand before you merge.</strong> If you can&#8217;t explain why the code works, you can&#8217;t explain why it won&#8217;t work. The 59% of developers shipping code they don&#8217;t understand are creating future incidents that nobody will be able to debug, because nobody understood the system they built. This was always true for copy-pasted Stack Overflow answers, but LLMs have industrialized the problem.</p><p><strong>Make assumptions explicit.</strong> The Ariane 5 overflow decision was &#8220;obscured from external review.&#8221; If your LLM prompt assumes a specific data format, a particular library version, a certain deployment environment, write that down. Review it. Challenge it. Because the model won&#8217;t.</p><div><hr></div><h2>The Skill That Will Matter Most</h2><p>LLMs are not going away. They&#8217;re going to get better. The code they generate will improve, the vulnerabilities will decrease, the tooling around them will mature. But none of that changes the fundamental dynamic: someone still needs to understand the system.</p><p>When you write code yourself, you encounter resistance. You hit compiler errors, test failures, logical contradictions. Each one is a micro-moment of reality testing, a forced confrontation with how things actually work. When an LLM generates code for you, those friction points disappear. The code compiles. The tests pass. Everything looks green. And the absence of friction feels like correctness.</p><p>But the absence of friction isn&#8217;t the absence of bugs. It&#8217;s the absence of <em>your understanding of where the bugs are.</em></p><p>History teaches us that the most expensive engineering failures don&#8217;t come from ignorance. They come from false confidence, from teams that believed they knew enough, that the system was well-understood, that the assumption was safe. Every disaster I&#8217;ve described was built by brilliant engineers who were wrong about one thing and didn&#8217;t know it.</p><p>So here&#8217;s my advice, especially if you&#8217;re early in your career: build the understanding. Use LLMs, use them aggressively, but use them the way you&#8217;d use a senior engineer who&#8217;s brilliant but has never seen your codebase. They can draft, they can suggest, they can teach you patterns. But they can&#8217;t tell you whether those patterns fit your system. That part is yours. And the more people outsource that part, the more valuable it becomes for the people who don&#8217;t.</p><p>The engineers who thrive in this era won&#8217;t be the ones who can prompt the fastest. They&#8217;ll be the ones who can look at what the model produced and say, with confidence, &#8220;this is wrong, and here&#8217;s why.&#8221; Or, just as importantly, &#8220;I don&#8217;t know if this is right, and I need to find out before we ship it.&#8221;</p><div class="pullquote"><p>That&#8217;s intellectual courage. It has always been the foundation of good engineering. In the age of vibes, it&#8217;s the whole building.</p></div><p>If there's one skill I'd want every engineer to build, one thing I'd want the next computer science curriculum to actually teach, it would be this: intellectual honesty. How to question what's in front of you, especially when it looks right. We're going to vibe. We're going to generate, ship, and iterate faster than ever. But the engineers who matter will be the ones who pause, question, and then vibe again, better.</p><div><hr></div><h2>References</h2><p><strong>Mars Climate Orbiter (1999)</strong> - $327M loss due to metric/imperial unit mismatch between Lockheed Martin and NASA JPL.</p><ul><li><p><a href="https://science.nasa.gov/mission/mars-climate-orbiter/">NASA Mission Page</a></p></li><li><p><a href="https://en.wikipedia.org/wiki/Mars_Climate_Orbiter">Wikipedia: Mars Climate Orbiter</a></p></li><li><p><a href="https://sma.nasa.gov/docs/default-source/safety-messages/safetymessage-2009-08-01-themarsclimateorbitermishap.pdf?sfvrsn=eaa1ef8_4">NASA System Failure Case Study (PDF)</a></p></li></ul><p><strong>Ariane 5 Flight 501 (1996)</strong> - $370M loss, 37 seconds after liftoff. Integer overflow in reused Ariane 4 software.</p><ul><li><p><a href="https://en.wikipedia.org/wiki/Ariane_flight_V88">Wikipedia: Ariane Flight V88</a></p></li><li><p><a href="https://www.esa.int/Newsroom/Press_Releases/Ariane_501_-_Presentation_of_Inquiry_Board_report">ESA Inquiry Board Report</a></p></li><li><p><a href="https://www.drisq.com/the-ariane-5-failure-how-a-huge-disaster-paved-the-way-for-better-coding">Drisq: How a Huge Disaster Paved the Way for Better Coding</a></p></li></ul><p><strong>Boeing 737 MAX / MCAS (2018&#8211;2019)</strong> - 346 deaths across Lion Air Flight 610 and Ethiopian Airlines Flight 302. Single-sensor design, flawed safety assumptions.</p><ul><li><p><a href="https://en.wikipedia.org/wiki/Boeing_737_MAX_groundings">Wikipedia: Boeing 737 MAX Groundings</a></p></li><li><p><a href="https://en.wikipedia.org/wiki/Maneuvering_Characteristics_Augmentation_System">Wikipedia: MCAS</a></p></li><li><p><a href="https://www.seattletimes.com/seattle-news/times-watchdog/the-inside-story-of-mcas-how-boeings-737-max-system-gained-power-and-lost-safeguards/">The Seattle Times: The Inside Story of MCAS</a></p></li><li><p><a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC7351545/">PMC / NIH: The Boeing 737 MAX - Lessons for Engineering Ethics</a></p></li></ul><p><strong>Hubble Space Telescope Mirror Flaw (1990)</strong> - $1.5B telescope launched with mirror ground to the wrong curvature. $700M servicing mission to correct.</p><ul><li><p><a href="https://science.nasa.gov/mission/hubble/observatory/design/optics/hubbles-mirror-flaw/">NASA: Hubble&#8217;s Mirror Flaw</a></p></li><li><p><a href="https://en.wikipedia.org/wiki/Hubble_Space_Telescope">Wikipedia: Hubble Space Telescope</a></p></li><li><p><a href="https://www.cbsnews.com/news/an-ingenius-fix-for-hubbles-famously-flawed-vision/">CBS News: How NASA Fixed Hubble&#8217;s Flawed Vision</a></p></li></ul><p><strong>Space Shuttle Challenger (1986)</strong> - 7 crew killed. O-ring failure in cold temperatures despite engineer warnings.</p><ul><li><p><a href="https://en.wikipedia.org/wiki/Space_Shuttle_Challenger_disaster">Wikipedia: Space Shuttle Challenger Disaster</a></p></li><li><p><a href="https://en.wikipedia.org/wiki/Roger_Boisjoly">Wikipedia: Roger Boisjoly</a></p></li><li><p><a href="https://www.nasa.gov/history/rogersrep/v1ch5.htm">NASA Rogers Commission Report, Chapter 5</a></p></li><li><p><a href="https://priceonomics.com/the-space-shuttle-challenger-explosion-and-the-o/">Priceonomics: The Challenger Explosion and the O-Ring</a></p></li></ul><p><strong>Therac-25 (1985&#8211;1987)</strong> - At least 6 radiation overdoses, 3 deaths. Software race conditions with no hardware safety interlocks.</p><ul><li><p><a href="https://en.wikipedia.org/wiki/Therac-25">Wikipedia: Therac-25</a></p></li><li><p><a href="https://ieeexplore.ieee.org/document/274940/">IEEE: An Investigation of the Therac-25 Accidents (Leveson &amp; Turner)</a></p></li><li><p><a href="https://ethicsunwrapped.utexas.edu/case-study/therac-25">Ethics Unwrapped: Therac-25 Case Study</a></p></li><li><p><a href="https://hackaday.com/2015/10/26/killed-by-a-machine-the-therac-25/">Hackaday: Killed By A Machine</a></p></li></ul><p><strong>Knight Capital Group ($440M loss, 2012)</strong> - Deployment error triggered dormant trading code, bankrupting the firm in 45 minutes.</p><ul><li><p><a href="https://en.wikipedia.org/wiki/Knight_Capital_Group">Wikipedia: Knight Capital Group</a></p></li><li><p><a href="https://www.sec.gov/newsroom/press-releases/2013-222">SEC Charges Knight Capital</a></p></li><li><p><a href="https://specbranch.com/posts/knight-capital/">Speculative Branches: The Knight Capital Disaster</a></p></li><li><p><a href="https://www.henricodolfing.ch/en/case-study-4-the-440-million-software-error-at-knight-capital/">Henrico Dolfing: The $440 Million Software Error</a></p></li></ul><p><strong>CrowdStrike Channel File 291 Outage (2024)</strong> - A faulty sensor configuration update crashed 8.5 million Windows devices worldwide.</p><ul><li><p><a href="https://en.wikipedia.org/wiki/2024_CrowdStrike-related_IT_outages">Wikipedia: 2024 CrowdStrike-related IT Outages</a></p></li><li><p><a href="https://www.crowdstrike.com/wp-content/uploads/2024/08/Channel-File-291-Incident-Root-Cause-Analysis-08.06.2024.pdf">CrowdStrike Root Cause Analysis (PDF)</a></p></li><li><p><a href="https://www.crowdstrike.com/en-us/blog/channel-file-291-rca-available/">CrowdStrike Blog: Channel File 291 RCA</a></p></li><li><p><a href="https://www.cisa.gov/news-events/alerts/2024/07/19/widespread-it-outage-due-crowdstrike-update">CISA Advisory: Widespread IT Outage</a></p></li></ul><p><strong>AWS S3 Outage (2017)</strong> - A mistyped command removed critical infrastructure and took down ~20% of the internet for 4 hours.</p><ul><li><p><a href="https://aws.amazon.com/message/41926/">AWS Official Postmortem</a></p></li><li><p><a href="https://www.gremlin.com/blog/the-2017-amazon-s-3-outage">Gremlin: After the Retrospective - The 2017 Amazon S3 Outage</a></p></li><li><p><a href="https://networkworld.com/article/3176595/aws-says-a-typo-caused-the-massive-s3-failure-this-week.html">Network World: AWS Says a Typo Caused the Massive S3 Failure</a></p></li></ul><p><strong>GitLab Database Outage (2017)</strong> - An engineer accidentally deleted 300GB of production data; all five backup methods had silently failed.</p><ul><li><p><a href="https://about.gitlab.com/blog/postmortem-of-database-outage-of-january-31/">GitLab Official Postmortem</a></p></li><li><p><a href="https://thehackernews.com/2024/03/4-instructive-postmortems-on-data.html">The Hacker News: 4 Instructive Postmortems</a></p></li><li><p><a href="https://downtimeproject.com/podcast/gitlabs-2017-postgres-outage/">The Downtime Project: GitLab&#8217;s 2017 Postgres Outage</a></p></li></ul><p><strong>Cloudflare WAF Outage (2019)</strong> - A single regex with catastrophic backtracking behavior spiked every edge server to 100% CPU globally.</p><ul><li><p><a href="https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/">Cloudflare Blog: Details of the Outage on July 2, 2019</a></p></li><li><p><a href="https://blog.cloudflare.com/cloudflare-outage/">Cloudflare Blog: Initial Outage Report</a></p></li></ul><p><strong>LLM Code Security Data (2025)</strong></p><ul><li><p><a href="https://clutch.co/resources/devs-use-ai-generated-code-they-dont-understand">Clutch: 59% of Devs Use AI Code They Don&#8217;t Understand</a></p></li><li><p><a href="https://shiftmag.dev/stack-overflow-survey-2025-ai-5653/">Stack Overflow 2025 Developer Survey - AI trust dropping to 33%</a></p></li><li><p><a href="https://www.itpro.com/software/development/software-developers-not-checking-ai-generated-code-verification-debt">IT Pro: 96% Don&#8217;t Trust AI Code, Fewer Than Half Review It</a></p></li><li><p><a href="https://www.infosecurity-magazine.com/news/llms-vulnerable-code-default/">Infosecurity Magazine: LLMs Produce Vulnerable Code by Default</a></p></li><li><p><a href="https://www.endorlabs.com/learn/the-most-common-security-vulnerabilities-in-ai-generated-code">Endor Labs: Most Common Vulnerabilities in AI-Generated Code</a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.zmalik.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Optimized Infra! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Lazy-Pulling Container Images: A Deep Dive Into OCI Seekability]]></title><description><![CDATA[From DEFLATE dependency chains to FUSE mounts: how few competing approaches make container layers randomly accessible, and what they all require you to change on every node.]]></description><link>https://blog.zmalik.dev/p/lazy-pulling-container-images-a-deep</link><guid isPermaLink="false">https://blog.zmalik.dev/p/lazy-pulling-container-images-a-deep</guid><dc:creator><![CDATA[Zain]]></dc:creator><pubDate>Sun, 08 Feb 2026 17:18:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!SFGL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Your container images are tar archives compressed with gzip. That single design decision, made in the Docker era and inherited by OCI, means that reading any file requires downloading and decompressing the entire layer from byte zero. For a 12GB ML image, that&#8217;s 2-8 minutes of cold-start time before a single inference request, depending on your bandwidth to the registry.</p><p>Lazy-pulling fixes this by fetching only the bytes you need, when you need them. The concept has existed since 2019. Multiple implementations are in production today. They all solve the same byte-level problem and they all require the same infrastructure change: swapping containerd&#8217;s snapshotter, deploying a FUSE daemon on every node, and accepting that your registry just became a runtime dependency.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.zmalik.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Optimized Infra! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>This post starts with why the problem is harder than it looks at the byte level, then surveys the major approaches and what they trade off. The core of the post is a hands-on experiment: I deploy an in-cluster registry, convert images to eStargz, patch containerd with a custom snapshotter, and measure something nobody benchmarks properly. Not just pull time, but <em>readiness</em>, the moment a container can actually serve its first request.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SFGL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SFGL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!SFGL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!SFGL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!SFGL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SFGL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg" width="1408" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1408,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1163788,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/187198399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SFGL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!SFGL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!SFGL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!SFGL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51dc2ede-6a8b-4b0f-8187-2346ebb2ee53_1408x768.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A note on scope. This post is about container image layers, the runtime, libraries, and application code that make up the OCI image. Model weight delivery is a different topic and won&#8217;t be covered here.</p><div><hr></div><h2>Part I: Why Container Layers Resist Random Access</h2><h3>The Tar Format: Sequential by Design</h3><p>A container image layer is a POSIX tar archive. Tar was designed for tape, literally <em>Tape ARchive</em>, and it shows. There is no central directory, no file index, no way to find a file without scanning from the beginning:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!geez!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ddb0b6-689b-4b5b-aac4-315af1e6508b_1216x1164.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!geez!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ddb0b6-689b-4b5b-aac4-315af1e6508b_1216x1164.png 424w, https://substackcdn.com/image/fetch/$s_!geez!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ddb0b6-689b-4b5b-aac4-315af1e6508b_1216x1164.png 848w, https://substackcdn.com/image/fetch/$s_!geez!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ddb0b6-689b-4b5b-aac4-315af1e6508b_1216x1164.png 1272w, https://substackcdn.com/image/fetch/$s_!geez!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ddb0b6-689b-4b5b-aac4-315af1e6508b_1216x1164.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!geez!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ddb0b6-689b-4b5b-aac4-315af1e6508b_1216x1164.png" width="1216" height="1164" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/65ddb0b6-689b-4b5b-aac4-315af1e6508b_1216x1164.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1164,&quot;width&quot;:1216,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:147966,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/187198399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ddb0b6-689b-4b5b-aac4-315af1e6508b_1216x1164.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!geez!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ddb0b6-689b-4b5b-aac4-315af1e6508b_1216x1164.png 424w, https://substackcdn.com/image/fetch/$s_!geez!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ddb0b6-689b-4b5b-aac4-315af1e6508b_1216x1164.png 848w, https://substackcdn.com/image/fetch/$s_!geez!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ddb0b6-689b-4b5b-aac4-315af1e6508b_1216x1164.png 1272w, https://substackcdn.com/image/fetch/$s_!geez!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65ddb0b6-689b-4b5b-aac4-315af1e6508b_1216x1164.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>To find <code>/usr/bin/python3</code> in a tar archive, you read the first header (512 bytes), check the filename, skip past the file data (reading the size field to know how far), read the next header, and repeat until you find your target or hit EOF. For a layer with 10,000 files, that&#8217;s potentially scanning through gigabytes of file data you don&#8217;t care about.</p><p>This is already slow. Gzip makes it worse.</p><h3>GZIP + DEFLATE: The Dependency Chain</h3><p>Container layers are <code>tar.gz</code>, the tar archive above compressed as a single gzip stream. GZIP uses DEFLATE compression (RFC 1951), which combines two techniques:</p><p><strong>LZ77 (sliding window)</strong> replaces repeated byte sequences with back-references. Instead of storing &#8220;the quick brown fox&#8221; twice, the second occurrence becomes a (distance, length) pair pointing back to the first. The window size is up to 32KB. Any byte in the output can reference up to 32,768 bytes before it.</p><p><strong>Huffman coding</strong> uses variable-length encoding where frequent symbols get shorter codes. The coding tables can be static (pre-defined) or dynamic (computed per block and embedded in the stream).</p><p>Together, these create a decompression dependency chain:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RnG8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4010a80a-16d4-42a2-b5a0-5e2753c0eb94_1656x934.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RnG8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4010a80a-16d4-42a2-b5a0-5e2753c0eb94_1656x934.png 424w, https://substackcdn.com/image/fetch/$s_!RnG8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4010a80a-16d4-42a2-b5a0-5e2753c0eb94_1656x934.png 848w, https://substackcdn.com/image/fetch/$s_!RnG8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4010a80a-16d4-42a2-b5a0-5e2753c0eb94_1656x934.png 1272w, https://substackcdn.com/image/fetch/$s_!RnG8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4010a80a-16d4-42a2-b5a0-5e2753c0eb94_1656x934.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RnG8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4010a80a-16d4-42a2-b5a0-5e2753c0eb94_1656x934.png" width="1456" height="821" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4010a80a-16d4-42a2-b5a0-5e2753c0eb94_1656x934.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:821,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:106936,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/187198399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4010a80a-16d4-42a2-b5a0-5e2753c0eb94_1656x934.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!RnG8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4010a80a-16d4-42a2-b5a0-5e2753c0eb94_1656x934.png 424w, https://substackcdn.com/image/fetch/$s_!RnG8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4010a80a-16d4-42a2-b5a0-5e2753c0eb94_1656x934.png 848w, https://substackcdn.com/image/fetch/$s_!RnG8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4010a80a-16d4-42a2-b5a0-5e2753c0eb94_1656x934.png 1272w, https://substackcdn.com/image/fetch/$s_!RnG8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4010a80a-16d4-42a2-b5a0-5e2753c0eb94_1656x934.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>To decompress Block 3, the decompressor needs the Huffman tables for Block 3 (embedded in the block header, or the static tables), up to 32KB of previously decompressed output (the sliding window) for resolving back-references, and the current bit position in the compressed stream (DEFLATE is bit-aligned, not byte-aligned).</p><p>The decompressor state at any point is a function of everything that came before. There is no way to start decompressing at Block 3 without either decompressing Blocks 0-2 first or having a saved snapshot of the decompressor&#8217;s internal state at the Block 3 boundary.</p><p><strong>This is the fundamental problem.</strong> A 5GB compressed layer containing 10,000 files is a single DEFLATE stream. To read the last file, you decompress from byte 0. There&#8217;s no shortcut within the format.</p><h3>The Combined Cost</h3><p>For a modern LLM serving image (think vLLM, SGLang, or TensorRT-LLM), excluding model weights which are typically mounted separately or pulled from object storage:</p><pre><code>Runtime layers: 12.3 GB compressed, 28.5 GB uncompressed, 5 layers
  &#9500;&#9472;&#9472; Base OS (Ubuntu)
  &#9500;&#9472;&#9472; CUDA runtime + cuDNN
  &#9500;&#9472;&#9472; Python + pip packages
  &#9500;&#9472;&#9472; PyTorch
  &#9492;&#9472;&#9472; vLLM application code

Traditional pull sequence:
  1. Download 12.3 GB                              &#8594; 2-8 min (depends on link speed)
  2. Decompress 12.3 GB &#8594; 28.5 GB                  &#8594; 2 min (CPU-bound)
  3. Write 28.5 GB to overlayfs (5 layer extracts)  &#8594; 3 min (IO-bound)
  &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
  Total: ~7-13 minutes to container Running state

What the container actually reads at startup:
  Python interpreter + stdlib + torch + vllm imports &#8776; 150 MB
  That's 1.2% of the uncompressed image.</code></pre><p>You download 12.3 GB of runtime layers to read 150 MB. The remaining 98.8% sits on disk, fully decompressed, waiting for requests that might never come. On GPU nodes costing $3-30/hr, those minutes of idle accelerator time add up, and that&#8217;s before you&#8217;ve even started loading model weights.</p><div><hr></div><h2>Part II: Many Ways to Break the Chain</h2><p>Every lazy-pulling solution addresses the same problem, which is making individual files accessible without downloading the entire layer. They differ in <em>where</em> they break the DEFLATE dependency chain and <em>what</em> they require at the format level.</p><h3>Independent Gzip Members (eStargz)</h3><p><strong>The core insight.</strong> RFC 1952 says a gzip file can contain multiple concatenated members. Standard decompressors treat them as a single stream. But each member has its own independent DEFLATE state.</p><p>eStargz recompresses each file (or chunk of a large file) as a separate gzip member:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yBnn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55c8aa31-9433-462b-9f1b-616c1883f65d_1418x640.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yBnn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55c8aa31-9433-462b-9f1b-616c1883f65d_1418x640.png 424w, https://substackcdn.com/image/fetch/$s_!yBnn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55c8aa31-9433-462b-9f1b-616c1883f65d_1418x640.png 848w, https://substackcdn.com/image/fetch/$s_!yBnn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55c8aa31-9433-462b-9f1b-616c1883f65d_1418x640.png 1272w, https://substackcdn.com/image/fetch/$s_!yBnn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55c8aa31-9433-462b-9f1b-616c1883f65d_1418x640.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yBnn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55c8aa31-9433-462b-9f1b-616c1883f65d_1418x640.png" width="1418" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55c8aa31-9433-462b-9f1b-616c1883f65d_1418x640.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:1418,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:116160,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/187198399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55c8aa31-9433-462b-9f1b-616c1883f65d_1418x640.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yBnn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55c8aa31-9433-462b-9f1b-616c1883f65d_1418x640.png 424w, https://substackcdn.com/image/fetch/$s_!yBnn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55c8aa31-9433-462b-9f1b-616c1883f65d_1418x640.png 848w, https://substackcdn.com/image/fetch/$s_!yBnn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55c8aa31-9433-462b-9f1b-616c1883f65d_1418x640.png 1272w, https://substackcdn.com/image/fetch/$s_!yBnn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55c8aa31-9433-462b-9f1b-616c1883f65d_1418x640.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>The TOC (Table of Contents)</strong> is a JSON document stored as the final gzip member. It maps every file to its compressed byte offset.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aX6O!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F511e58b7-4b09-4340-ad69-c6846bb72230_1424x632.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aX6O!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F511e58b7-4b09-4340-ad69-c6846bb72230_1424x632.png 424w, https://substackcdn.com/image/fetch/$s_!aX6O!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F511e58b7-4b09-4340-ad69-c6846bb72230_1424x632.png 848w, https://substackcdn.com/image/fetch/$s_!aX6O!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F511e58b7-4b09-4340-ad69-c6846bb72230_1424x632.png 1272w, https://substackcdn.com/image/fetch/$s_!aX6O!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F511e58b7-4b09-4340-ad69-c6846bb72230_1424x632.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aX6O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F511e58b7-4b09-4340-ad69-c6846bb72230_1424x632.png" width="1424" height="632" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/511e58b7-4b09-4340-ad69-c6846bb72230_1424x632.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:632,&quot;width&quot;:1424,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:137645,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/187198399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F511e58b7-4b09-4340-ad69-c6846bb72230_1424x632.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!aX6O!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F511e58b7-4b09-4340-ad69-c6846bb72230_1424x632.png 424w, https://substackcdn.com/image/fetch/$s_!aX6O!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F511e58b7-4b09-4340-ad69-c6846bb72230_1424x632.png 848w, https://substackcdn.com/image/fetch/$s_!aX6O!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F511e58b7-4b09-4340-ad69-c6846bb72230_1424x632.png 1272w, https://substackcdn.com/image/fetch/$s_!aX6O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F511e58b7-4b09-4340-ad69-c6846bb72230_1424x632.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>The DiffID preservation trick.</strong> eStargz changes compression boundaries but not content. When you concatenate and decompress all gzip members, you get a byte-identical tar stream to the original. The DiffID (the layer&#8217;s identity in the image config) is preserved. The blob digest changes (different compressed bytes), but the layer identity doesn&#8217;t.</p><p><strong>The cost.</strong> every image must be converted at build time. The conversion recompresses the entire layer. Compression ratio may differ slightly (independent members can&#8217;t reference data in other members, losing some LZ77 efficiency). In practice, the overhead is ~2% larger blobs.</p><h3>Other Approaches</h3><p>eStargz is the approach I use in the experiment later in this post, but it&#8217;s not the only one. Three other approaches are worth knowing about.</p><p><strong>AWS SOCI</strong> takes a different angle entirely. Instead of recompressing the image, it creates an external index (the zTOC) that stores periodic snapshots of the zlib decompressor&#8217;s internal state. These checkpoints let you resume decompression from any 4MB boundary instead of from byte zero. The index is stored as a separate OCI artifact linked via the Referrers API, so the original image stays completely unmodified. The tradeoff is that reads may need to decompress up to 4MB of unwanted data to reach the target file, and the index must be explicitly copied alongside the image when promoting across registries.</p><p><strong>Nydus</strong> replaces tar.gz entirely with RAFS (Registry Acceleration File System), a purpose-built format with separated metadata and content-addressable chunks. Its key differentiator is cross-layer chunk deduplication, which can reduce total download by 20-30% for ML images with overlapping runtime layers. Nydus also offers an EROFS kernel backend (Linux 5.19+) that eliminates FUSE from the data path, dropping per-operation latency from ~100-500&#956;s to ~10-50&#956;s. The cost is that images must be converted, and the pure Nydus format isn&#8217;t backward-compatible with standard runtimes (though a &#8220;zran&#8221; compatibility mode exists).</p><p><strong>Azure Artifact Streaming</strong> and <strong>Google Image Streaming</strong> build the seekability index server-side, transparently, with no user-visible conversion step. Azure&#8217;s implementation is based on OverlayBD, which operates at the block device level via TCMU rather than using FUSE. Google generates an opaque index automatically on push and uses a custom containerd plugin backed by FUSE with aggressive multi-level caching. Both are closed implementations that require their specific managed Kubernetes service and container registry.</p><div><hr></div><h2>Part III: The containerd Integration Point</h2><p>Every solution, open-source or proprietary, modifies the same component in containerd&#8217;s snapshotter.</p><h3>Why It Has to Be the Snapshotter</h3><p>containerd&#8217;s image pull pipeline has a clear separation of concerns.</p><pre><code><code>Image Pull Pipeline:
  1. Resolver     &#8594; Converts image reference to manifest digest
  2. Fetcher      &#8594; Downloads blobs from registry &#8594; Content Store
  3. Unpacker     &#8594; Reads blobs from Content Store &#8594; Snapshotter
  4. Snapshotter  &#8594; Materializes layer diffs into mountable filesystem
  5. Runtime      &#8594; Mounts filesystem, creates container</code></code></pre><p>Steps 1-3 operate on blobs as opaque bytes. The Snapshotter (step 4) is where bytes become <em>files</em>. This is the only point where lazy-pulling can be inserted. You intercept the moment containerd tries to materialize a complete filesystem from a layer blob and instead provide a virtual filesystem that fetches on demand.</p><h3>The Snapshotter Contract</h3><p>The default <code>overlayfs</code> snapshotter&#8217;s <code>Prepare()</code> call is synchronous and complete:</p><pre><code><code>// overlayfs snapshotter: Prepare returns after full extraction
func (o *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...Opt) ([]mount.Mount, error) {
    // ... (layer already fully extracted to disk)
    return []mount.Mount{{
        Type:    "overlay",
        Source:  "overlay",
        Options: []string{
            fmt.Sprintf("lowerdir=%s", lowerDirs),
            fmt.Sprintf("upperdir=%s", upperDir),
            fmt.Sprintf("workdir=%s", workDir),
        },
    }}, nil
}</code></code></pre><p>A lazy-pulling snapshotter can return a FUSE mount instead:</p><pre><code><code>// remote snapshotter: Prepare returns immediately, content fetched on demand
func (s *remoteSnapshotter) Prepare(ctx context.Context, key, parent string, opts ...Opt) ([]mount.Mount, error) {
    // 1. Fetch TOC/index (small metadata, fast)
    // 2. Start FUSE daemon for this layer
    // 3. Return mount point where FUSE serves content on demand
    return []mount.Mount{{
        Type:    "fuse.rawBridge",
        Source:  "stargz",
        Options: []string{"ro", fmt.Sprintf("mountpoint=%s", mountDir)},
    }}, nil
}</code></code></pre><p>From containerd&#8217;s perspective, both return <code>[]mount.Mount</code>. The OCI runtime (<code>runc</code>) mounts them identically. The container process sees a normal filesystem either way. The difference is entirely in what backs those mounts.</p><h3>What Every Solution Adds to the Node</h3><p>For FUSE-based solutions (eStargz, SOCI, Nydus default, Google), you end up with this node topology:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OEoV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55188374-a552-4d9d-a7eb-c9d09ad091d3_1452x1304.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OEoV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55188374-a552-4d9d-a7eb-c9d09ad091d3_1452x1304.png 424w, https://substackcdn.com/image/fetch/$s_!OEoV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55188374-a552-4d9d-a7eb-c9d09ad091d3_1452x1304.png 848w, https://substackcdn.com/image/fetch/$s_!OEoV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55188374-a552-4d9d-a7eb-c9d09ad091d3_1452x1304.png 1272w, https://substackcdn.com/image/fetch/$s_!OEoV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55188374-a552-4d9d-a7eb-c9d09ad091d3_1452x1304.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OEoV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55188374-a552-4d9d-a7eb-c9d09ad091d3_1452x1304.png" width="1452" height="1304" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55188374-a552-4d9d-a7eb-c9d09ad091d3_1452x1304.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1304,&quot;width&quot;:1452,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:263200,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/187198399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55188374-a552-4d9d-a7eb-c9d09ad091d3_1452x1304.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!OEoV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55188374-a552-4d9d-a7eb-c9d09ad091d3_1452x1304.png 424w, https://substackcdn.com/image/fetch/$s_!OEoV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55188374-a552-4d9d-a7eb-c9d09ad091d3_1452x1304.png 848w, https://substackcdn.com/image/fetch/$s_!OEoV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55188374-a552-4d9d-a7eb-c9d09ad091d3_1452x1304.png 1272w, https://substackcdn.com/image/fetch/$s_!OEoV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55188374-a552-4d9d-a7eb-c9d09ad091d3_1452x1304.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The <code>config.toml</code> change is simple. What it implies is not.</p><p><strong>Process dependency ordering:</strong> The snapshotter daemon must be running before containerd starts accepting CRI calls. If the daemon crashes during node boot, containerd silently falls back to <code>overlayfs</code>. Your containers start, but slowly, and the only signal is startup latency, not an error.</p><p><strong>FUSE mount lifecycle:</strong> Each lazy layer creates a FUSE mount. These mounts are tied to the snapshotter daemon&#8217;s process. If the daemon restarts, existing mounts become stale. Reads return <code>ENOTCONN</code>. Every container using those mounts breaks.</p><p><strong>Cache management:</strong> Every solution caches fetched chunks locally. Without explicit GC configuration, the cache grows until the disk fills. On GPU nodes with expensive local NVMe, that&#8217;s capacity competing with model weights, checkpoints, and scratch space.</p><div><hr></div><h2>Part IV: Readiness as the Metric That Matters</h2><p>Most lazy-pulling benchmarks report a single number: image pull time. Pull goes from minutes to sub-second, the chart looks dramatic, and the blog post ends. But pull time is a misleading metric for lazy-pulling because the cost doesn&#8217;t disappear. It shifts.</p><p>With a traditional full pull, the container starts with every file already on disk. With lazy-pulling, the container starts with <em>nothing</em> on disk. The first time the process reads a file, whether loading a binary, opening a shared library, or importing a Python module, that read blocks on a FUSE cache miss, which triggers an HTTP Range request to the registry, which adds network latency to what should be a local filesystem operation.</p><p>The metric that actually matters is <strong>readiness</strong>: the moment the container can serve its first request. We define it as the time from <code>container create</code> to a successful HTTP response on the health endpoint. This captures the full cost: pull, start, and all the on-demand file fetching that happens before the process is functional.</p><p>Readiness reveals the tradeoff that pull-time-only benchmarks hide.</p><h3>The Experiment</h3><p>We built an in-cluster test harness to compare three configurations head-to-head, measuring readiness on each. The goal: isolate what lazy-pulling actually changes by controlling for network, registry, and image variables.</p><h4>Architecture</h4><p>The experiment deploys three components into a Kubernetes cluster:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ywej!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe27c261b-0f3a-4d7a-bd46-e5a118b97c11_1686x1248.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ywej!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe27c261b-0f3a-4d7a-bd46-e5a118b97c11_1686x1248.png 424w, https://substackcdn.com/image/fetch/$s_!ywej!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe27c261b-0f3a-4d7a-bd46-e5a118b97c11_1686x1248.png 848w, https://substackcdn.com/image/fetch/$s_!ywej!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe27c261b-0f3a-4d7a-bd46-e5a118b97c11_1686x1248.png 1272w, https://substackcdn.com/image/fetch/$s_!ywej!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe27c261b-0f3a-4d7a-bd46-e5a118b97c11_1686x1248.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ywej!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe27c261b-0f3a-4d7a-bd46-e5a118b97c11_1686x1248.png" width="1456" height="1078" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e27c261b-0f3a-4d7a-bd46-e5a118b97c11_1686x1248.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1078,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:255198,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/187198399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe27c261b-0f3a-4d7a-bd46-e5a118b97c11_1686x1248.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ywej!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe27c261b-0f3a-4d7a-bd46-e5a118b97c11_1686x1248.png 424w, https://substackcdn.com/image/fetch/$s_!ywej!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe27c261b-0f3a-4d7a-bd46-e5a118b97c11_1686x1248.png 848w, https://substackcdn.com/image/fetch/$s_!ywej!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe27c261b-0f3a-4d7a-bd46-e5a118b97c11_1686x1248.png 1272w, https://substackcdn.com/image/fetch/$s_!ywej!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe27c261b-0f3a-4d7a-bd46-e5a118b97c11_1686x1248.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Local registry.</strong> A standard Docker Distribution v2 registry runs as a Deployment, backed by a PVC, exposed via NodePort on port 30500. Since kube-proxy binds NodePort on <code>0.0.0.0:&lt;port&gt;</code> on every node, the registry is reachable at <code>localhost:30500</code> from any node without DNS, ingress, or TLS. This eliminates internet variability from the measurement.</p><p><strong>eStargz conversion.</strong> A Helm post-install Job converts each source image: pulls from DockerHub, rebuilds each layer with a TOC using the <code>containerd/stargz-snapshotter/estargz</code> Go library, and pushes the result to the local registry. The converted image is backward-compatible; any OCI runtime can pull and unpack it normally. The TOC is only used by snapshotters that understand it.</p><p><strong>Node patching.</strong> A privileged DaemonSet writes the <code>hosts.toml</code> for the local registry and restarts containerd via <code>nsenter</code> into the host PID namespace. This makes containerd trust plain HTTP pulls from <code>localhost:30500</code>.</p><p><strong>stargz-snapshotter.</strong> For the FUSE lazy-pull configuration, the custom stargz-snapshotter runs as a containerd proxy plugin on the worker node. On pull, it fetches only the TOC from each layer (milliseconds). It mounts a FUSE filesystem instead of extracting layers to disk. On file access, it fetches individual files from the registry via HTTP Range requests, decompresses the specific byte range, and returns them to the process. </p><h4>Three Configurations Under Test</h4><p>We test three cold-start paths against the same image (nginx:1.25, ~70MB compressed), each starting with a clean image cache:</p><p><strong>DockerHub full pull.</strong> Standard path: containerd pulls all layers from DockerHub over the internet, decompresses them, writes them to overlayfs. Container starts with all files on disk.</p><p><strong>Local registry + overlayfs full pull.</strong> Same full-pull mechanics, but the registry is in-cluster. This isolates the network improvement: same decompression and extraction, no internet round-trip.</p><p><strong>Local registry + FUSE lazy pull.</strong> eStargz image, stargz-snapshotter active. Pull fetches only the TOC. Container starts immediately. Files are fetched on-demand through FUSE as nginx loads its binary, shared libraries, and config.</p><h4>Measurement Method</h4><p>Benchmarks run at the containerd level using <code>ctr</code> and <code>ctr-remote</code>, bypassing the Kubernetes CRI path. This eliminates kubelet scheduling, readiness probe interval, and CRI overhead from the measurement, giving us a clean view of the pull &#8594; start &#8594; ready pipeline.</p><p>For each configuration, we measure three phases: </p><ul><li><p><strong>Image Pull</strong> (time to complete the pull/lazy-pull operation)</p></li><li><p><strong>Container Start</strong> (time from pull completion to process running)</p></li><li><p><strong>HTTP Readiness</strong> (time from container start to a successful HTTP response on <code>/healthz</code>).</p></li></ul><h3>Results</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!c04n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d49034c-cd32-4430-bad5-8ff3b5379a1c_1696x1018.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c04n!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d49034c-cd32-4430-bad5-8ff3b5379a1c_1696x1018.png 424w, https://substackcdn.com/image/fetch/$s_!c04n!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d49034c-cd32-4430-bad5-8ff3b5379a1c_1696x1018.png 848w, https://substackcdn.com/image/fetch/$s_!c04n!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d49034c-cd32-4430-bad5-8ff3b5379a1c_1696x1018.png 1272w, https://substackcdn.com/image/fetch/$s_!c04n!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d49034c-cd32-4430-bad5-8ff3b5379a1c_1696x1018.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!c04n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d49034c-cd32-4430-bad5-8ff3b5379a1c_1696x1018.png" width="1456" height="874" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2d49034c-cd32-4430-bad5-8ff3b5379a1c_1696x1018.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:874,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:121852,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/187198399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d49034c-cd32-4430-bad5-8ff3b5379a1c_1696x1018.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!c04n!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d49034c-cd32-4430-bad5-8ff3b5379a1c_1696x1018.png 424w, https://substackcdn.com/image/fetch/$s_!c04n!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d49034c-cd32-4430-bad5-8ff3b5379a1c_1696x1018.png 848w, https://substackcdn.com/image/fetch/$s_!c04n!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d49034c-cd32-4430-bad5-8ff3b5379a1c_1696x1018.png 1272w, https://substackcdn.com/image/fetch/$s_!c04n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d49034c-cd32-4430-bad5-8ff3b5379a1c_1696x1018.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>What Readiness Reveals</h4><p><strong>Pull is 65x faster with FUSE</strong> (0.088s vs 5.782s). Only the TOC and image manifest are downloaded, not the actual layer data. This is the number most benchmarks report and stop.</p><p><strong>Readiness is 20x slower with FUSE</strong> (0.271s vs 0.013s). When nginx tries to serve <code>/healthz</code>, the FUSE filesystem must fetch the nginx binary, shared libraries (libc, libssl, libpcre), and config files from the registry over HTTP. Each file read becomes a network round-trip. With full-pull approaches, those files are already on disk and readiness is essentially instant.</p><p><strong>The local registry alone gets you some of the improvement</strong> (5.981s &#8594; 3.577s total) with zero containerd configuration changes, no FUSE, no snapshotter swap. It&#8217;s the boring optimization that often gets skipped in the rush toward lazy-pulling.</p><p><strong>Total readiness is still best with FUSE</strong> (0.589s vs 5.981s). For nginx, the pull savings vastly outweigh the readiness penalty. But the gap narrows as image complexity grows:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kWef!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbdc84cad-45d4-4388-8f5f-273fcfc2289f_1842x924.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kWef!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbdc84cad-45d4-4388-8f5f-273fcfc2289f_1842x924.png 424w, https://substackcdn.com/image/fetch/$s_!kWef!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbdc84cad-45d4-4388-8f5f-273fcfc2289f_1842x924.png 848w, https://substackcdn.com/image/fetch/$s_!kWef!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbdc84cad-45d4-4388-8f5f-273fcfc2289f_1842x924.png 1272w, https://substackcdn.com/image/fetch/$s_!kWef!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbdc84cad-45d4-4388-8f5f-273fcfc2289f_1842x924.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kWef!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbdc84cad-45d4-4388-8f5f-273fcfc2289f_1842x924.png" width="1456" height="730" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bdc84cad-45d4-4388-8f5f-273fcfc2289f_1842x924.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:730,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:134619,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/187198399?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbdc84cad-45d4-4388-8f5f-273fcfc2289f_1842x924.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kWef!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbdc84cad-45d4-4388-8f5f-273fcfc2289f_1842x924.png 424w, https://substackcdn.com/image/fetch/$s_!kWef!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbdc84cad-45d4-4388-8f5f-273fcfc2289f_1842x924.png 848w, https://substackcdn.com/image/fetch/$s_!kWef!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbdc84cad-45d4-4388-8f5f-273fcfc2289f_1842x924.png 1272w, https://substackcdn.com/image/fetch/$s_!kWef!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbdc84cad-45d4-4388-8f5f-273fcfc2289f_1842x924.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>For a PyTorch image where the process imports hundreds of Python modules at startup, each triggering a FUSE cache miss and a network round-trip, the readiness phase grows proportionally. The pull savings are still enormous (minutes to sub-second), but the readiness penalty can reach seconds as the FUSE daemon serializes dozens of HTTP Range requests.</p><h4>A Note on custom stargz-snapshotter</h4><p>I used a custom snapshotter built on top of stargz-snapshotter that adds several performance improvements. It implements async prefetch to proactively fetch layer data in the background, eliminating the stalling and timeouts that occur when lazy-loading multi-gigabyte container images. Without async prefetch, on-demand fetches for large layers would consistently time out. The custom snapshotter also registers the necessary unpack platform handlers for containerd v2 compatibility, ensuring lazy-pulling works through the standard CRI path. The benchmarks above use this custom snapshotter.</p><div><hr></div><h2>Part V: The Operational Reality</h2><h3>Registry as Runtime Dependency</h3><p>This is the single most important operational change that lazy-pulling introduces, and it&#8217;s consistently underemphasized in vendor documentation.</p><p>With traditional pulls, the registry interaction is bounded: download all blobs, verify digests, done. The container runs entirely from local disk. Registry outages don&#8217;t affect running workloads.</p><p>With lazy-pulling, every uncached file access is a live HTTP request to the registry:</p><pre><code><code>Timeline of a lazy-pulled container:

  t=0s     Container starts (TOC fetched, FUSE mounted)
  t=0.1s   Python interpreter loaded (prefetched, cached)
  t=0.5s   torch imported (prefetched, cached)
  t=2.0s   Model loaded, serving requests
  
  t=3600s  User uploads file triggering rare code path
           &#8594; import obscure_module
           &#8594; FUSE: cache miss for /usr/lib/python3.11/obscure_module.py
           &#8594; HTTP Range request to registry
           &#8594; Registry is in maintenance window
           &#8594; read() returns EIO
           &#8594; Python: ModuleNotFoundError
           &#8594; 500 error to user

  The container has been running for an hour.
  The failure looks like a missing file, not a network issue.
  kubectl describe pod shows nothing.
  The pod is in Running state.</code></code></pre><p>Every solution mitigates this with <strong>background fetching</strong>, downloading the complete image content in the background after the container starts. The race condition is the window between container start and background fetch completion. For a 12GB image, that window can be anywhere from 2 to 8 minutes depending on registry bandwidth. Any uncached file access during that window hits the registry live.</p><p>The deeper issue: background fetching eliminates the lazy-pulling benefit over time. Once the full image is downloaded, you have exactly the same disk usage as a traditional pull. Lazy-pulling optimizes <strong>time to first request</strong>, not steady-state resource consumption. If your containers run for hours, you&#8217;re carrying the operational complexity of lazy-pulling for a one-time startup improvement.</p><h3>FUSE at Scale</h3><p>Most open-source solutions and Google&#8217;s proprietary implementation use FUSE in the data path. Azure&#8217;s OverlayBD-based approach is a notable exception. Some characteristics of FUSE in production:</p><p><strong>Per-operation latency includes scheduling.</strong> A FUSE read is: kernel sends request to FUSE device, userspace daemon wakes up and reads from <code>/dev/fuse</code>, daemon processes the request, daemon writes response back, kernel completes the syscall. The wake-up and write-back are context switches. On CPU-saturated GPU nodes, FUSE response latency becomes a function of CPU contention.</p><p><strong>Mount count scales multiplicatively.</strong> A node running 20 containers, each with 5 image layers, creates up to 100 FUSE mounts. Each maintains kernel-side state. The aggregate matters on nodes already tracking thousands of cgroups, network interfaces, and device mappings.</p><p><strong>Observability is split.</strong> <code>strace</code> shows slow <code>read()</code> syscalls. <code>iostat</code> shows nothing unusual (FUSE isn&#8217;t a block device). The actual bottleneck, a cache miss triggering an HTTP request inside the snapshotter daemon, is visible only through the daemon&#8217;s own metrics, which may or may not be exported in your monitoring stack.</p><p><strong>Failure mode is unfamiliar.</strong> When a FUSE daemon crashes, existing mounts go stale. Reads return <code>ENOTCONN</code>, &#8220;Transport endpoint is not connected&#8221;, on what the application thinks is a local file. This error doesn&#8217;t appear in most applications&#8217; retry logic because local filesystem reads are assumed to be reliable.</p><h3>The Nydus/EROFS Exception</h3><p>Nydus with the EROFS backend (Linux 5.19+) is the only solution that eliminates FUSE from the data path entirely. The cached read path goes through kernel VFS &#8594; EROFS driver &#8594; page cache, with zero extra context switches. The on-demand fetch path uses the fscache subsystem&#8217;s userspace daemon, but this runs asynchronously. EROFS can serve other cached reads while waiting for a fetch to complete.</p><p>The result is ~10x lower per-operation latency and no stale mount failure mode. The catch: you need a kernel with <code>CONFIG_EROFS_FS_ONDEMAND</code> enabled, and many minimal cloud-native node OSes still don&#8217;t enable this config flag by default.</p><div><hr></div><h2>Part VI: Where This Stands</h2><p>Container image lazy-pulling is a solved problem at the format level. We have four proven approaches spanning backward-compatible (eStargz), non-invasive (SOCI), high-performance (Nydus/EROFS), and fully-managed (Google, Azure). The format diversity is healthy: different tradeoffs suit different environments.</p><p>What remains unsolved is the integration story. Every solution requires replacing the containerd snapshotter on every node, running a long-lived daemon with its own lifecycle and failure modes, FUSE or TCMU or a specific kernel configuration, persistent cache with GC policy, and monitoring for a new class of failures (registry-as-runtime-dependency, stale FUSE mounts, cache pressure).</p><p>The cloud provider&#8217;s answer is to absorb this complexity into managed services, which works but gates the benefit behind a specific registry and Kubernetes distribution. The open-source answer is to install and operate the snapshotter yourself, which works but adds operational surface that compounds with every containerd and kernel upgrade.</p><p>The piece that&#8217;s missing is an upstream-native lazy-pulling snapshotter in containerd. Not a proxy plugin over a Unix socket, but a built-in snapshotter that speaks the OCI distribution spec, understands SOCI-style external indexes (so images don&#8217;t need modification), and uses EROFS where available with FUSE as fallback. The OCI Referrers API provides the standard metadata attachment mechanism. EROFS is in mainline Linux. The stargz-snapshotter is already in the containerd GitHub organization. The building blocks exist. The assembly is the hard part.</p><p>In container infrastructure, the hardest problems aren&#8217;t the algorithms or the formats. They&#8217;re making the operational integration invisible enough that teams adopt it without needing a dedicated infrastructure engineer to babysit it. Our readiness experiment shows that even the simplest lazy-pulling setup (eStargz + stargz-snapshotter) delivers a 10x improvement in total cold-start time, but only if you understand that the cost shifts from pull to runtime, measure accordingly, and plan for the registry dependency.</p><p>Everything in this post addresses the <em>container runtime</em> side of cold-start, meaning the OS, libraries, interpreters, and application code baked into OCI layers. For GPU workloads, that&#8217;s often only half the story. Getting multi-gigabyte model weights to the right node at the right time is a different problem with different constraints, and outside the scope of this post.</p><div><hr></div><h3>The ROI of Complexity</h3><p>We often talk about lazy-pulling as a &#8220;startup speed&#8221; hack, but that misses the bigger picture. This is about <strong>resilience</strong> and <strong>yield</strong>.</p><p>When a spot instance vanishes or a node kernel panics, a 5-minute pull time is a 5-minute outage. Lazy-pulling turns that catastrophe into a 10-second hiccup. That difference is the margin between a seamless failover and a user-visible incident.</p><p>Simultaneously, every minute an H100 sits idle waiting for tar extraction is capital burned. I&#8217;m curious&#8212;how are you modeling this trade-off? Is the operational tax of running FUSE daemons worth the reclaimed GPU time and the slash in MTTR? Or is the risk of a runtime registry dependency too high for your SLA? bill?</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.zmalik.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Optimized Infra! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Who Will Observe the Observability? eBPF Performance at Scale]]></title><description><![CDATA[Why infrastructure teams can't afford to be passengers, even in managed Kubernetes]]></description><link>https://blog.zmalik.dev/p/who-will-observe-the-observability</link><guid isPermaLink="false">https://blog.zmalik.dev/p/who-will-observe-the-observability</guid><dc:creator><![CDATA[Zain]]></dc:creator><pubDate>Sun, 23 Nov 2025 20:31:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!q_5r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>From KubeCon Stage to Technical Deep-Dive</h2><p>After my <a href="https://www.youtube.com/watch?v=J-Zx64mJzVk">KubeCon talk</a> with <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Grzegorz G&#322;&#261;b&quot;,&quot;id&quot;:179517885,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e4795118-dd7e-40ef-857a-f2b2c6d1aa4e_144x144.png&quot;,&quot;uuid&quot;:&quot;384ca341-02db-48e9-b1d0-3ece6c77a3cf&quot;}" data-component-name="MentionToDOM"></span>, <strong>&#8220;Fix First, Investigate Later: When an eBPF Rollout Brought Down Our Network,&#8221;</strong> I received numerous questions. While many wanted to discuss the specific technicalities of the incident, a distinct pattern emerged in the &#8220;hallway.&#8221; People weren&#8217;t just asking <em>how</em> the bug happened. They were also interested in: <strong>&#8220;What kind of skills does a Infrastructure Team actually need to own this stack?&#8221;</strong></p><p>The conversation shifted from the specifics of the incident to the capability of the team solving it. It raised a fundamental question: What is the gradient of expertise required today? </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.zmalik.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Optimized Infra! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h3>The Illusion of &#8220;Managed&#8221; Systems</h3><p>There is no short answer to &#8220;what skills do we need?&#8221; but there is a mindset shift required regarding the infrastructure we rent. Too many organizations fall into the trap of treating managed Kubernetes services as black boxes. We convince ourselves that because we pay a premium for a managed control plane, the underlying compute is &#8220;someone else&#8217;s problem.&#8221;</p><p>But if your business continuity relies on these foundational systems, you cannot afford to be a passenger. Even in managed environments, you almost always have full access to the <strong>worker node</strong> VMs. There is <strong>nothing</strong> actually stopping us from building the expertise to debug them.</p><p>This depth of expertise is where <strong>an infrastructure</strong> team differentiates itself. It is the difference between the &#8220;Folklore Team&#8221; (&#8221;Just restart the node, that usually fixes it&#8221;) and the &#8220;Engineering Team&#8221; (&#8221;We identified a race condition in the CNI plugin...&#8221;).</p><p>Let&#8217;s take the technical findings from our KubeCon talk as a case study. Much like <strong>the</strong> deep dive into the <a href="https://blog.zmalik.dev/p/packet-drop">irqbalance issue</a> revealed, this investigation required us to look past the abstractions. It wasn&#8217;t just a matter of reading logs. It required digging into the kernel&#8217;s data transfer mechanisms to understand the root cause.</p><p>In fact, the deep dive I&#8217;m about to share happened much later, during personal time, driven by curiosity to reproduce and fully understand the issue. Since there were no direct business metrics attached to the specific root cause initially, limited organizational effort was allocated to it. It took that curiosity to go back and find the why.</p><div><hr></div><h2>The Hidden Challenge: Scaling eBPF on 32+ Core Systems</h2><p>While eBPF programs promise minimal overhead, the reality on high-core-count machines under production traffic patterns tells a different story. Our investigation into Microsoft Retina&#8217;s <code>packetparser</code> plugin revealed a critical scaling bottleneck: when multi-core applications generate high traffic volumes across 32, 64, or 96 cores, the choice between perf arrays and ring buffers becomes <strong>critical</strong>.</p><p>Under real-world traffic patterns, where packet processing spreads across all cores simultaneously, perf arrays can degrade performance by up to 50%, while ring buffers maintain a more consistent 7% overhead. The difference becomes especially pronounced as traffic patterns shift from single-core bursts to sustained multi-core loads, turning what should be lightweight observability into a production-impacting bottleneck.</p><div><hr></div><h2>Understanding the Architecture: Retina&#8217;s Packetparser</h2><p>Let&#8217;s examine how modern eBPF observability tools transfer data from kernel to user space, using Microsoft <a href="https://github.com/microsoft/retina">Retina</a> as our case study. Retina&#8217;s <code>packetparser</code> plugin showcases a common architecture in eBPF network monitoring:</p><h3>The eBPF Programs</h3><p>Packetparser deploys four TC-BPF programs:</p><ul><li><p><strong>endpoint_ingress/egress</strong>: Attached to pod interfaces</p></li><li><p><strong>host_ingress/egress</strong>: Monitoring host network interfaces</p></li></ul><p>These programs perform packet analysis:</p><ul><li><p>Parse TCP flags (all 9 bits)</p></li><li><p>Track TSval/TSecr for RTT calculation (RFC 7323)</p></li><li><p>Monitor sequence/ACK numbers</p></li><li><p>Calculate per-flow metrics (bytes/packets)</p></li></ul><p>But the real challenge isn&#8217;t in the kernel, it&#8217;s in getting this data to user space efficiently.</p><h2>Perf Arrays: The Traditional Approach</h2><p>Most eBPF tools default to perf arrays (<strong>BPF_MAP_TYPE_PERF_EVENT_ARRAY</strong>) for kernel-to-user communication. Here&#8217;s why this made sense initially, and why it breaks down at scale.</p><h3>How Perf Arrays Work</h3><p>Perf arrays create a per-CPU buffer architecture:</p><pre><code><code>32 CPU Cores = 32 Independent Buffers
Each buffer: 32 pages (131KB)
Total memory: ~4MB across all CPUs</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_Af4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86db0d95-5342-4bf6-83b0-1ef5a6f1d7eb_1636x886.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_Af4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86db0d95-5342-4bf6-83b0-1ef5a6f1d7eb_1636x886.png 424w, https://substackcdn.com/image/fetch/$s_!_Af4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86db0d95-5342-4bf6-83b0-1ef5a6f1d7eb_1636x886.png 848w, https://substackcdn.com/image/fetch/$s_!_Af4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86db0d95-5342-4bf6-83b0-1ef5a6f1d7eb_1636x886.png 1272w, https://substackcdn.com/image/fetch/$s_!_Af4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86db0d95-5342-4bf6-83b0-1ef5a6f1d7eb_1636x886.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_Af4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86db0d95-5342-4bf6-83b0-1ef5a6f1d7eb_1636x886.png" width="1456" height="789" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/86db0d95-5342-4bf6-83b0-1ef5a6f1d7eb_1636x886.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:789,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:715757,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/179255417?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86db0d95-5342-4bf6-83b0-1ef5a6f1d7eb_1636x886.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_Af4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86db0d95-5342-4bf6-83b0-1ef5a6f1d7eb_1636x886.png 424w, https://substackcdn.com/image/fetch/$s_!_Af4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86db0d95-5342-4bf6-83b0-1ef5a6f1d7eb_1636x886.png 848w, https://substackcdn.com/image/fetch/$s_!_Af4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86db0d95-5342-4bf6-83b0-1ef5a6f1d7eb_1636x886.png 1272w, https://substackcdn.com/image/fetch/$s_!_Af4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86db0d95-5342-4bf6-83b0-1ef5a6f1d7eb_1636x886.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The data flow follows this pattern:</p><ol><li><p>Each CPU writes events to its dedicated buffer (Kernel &#8220;Producer&#8221;)</p></li><li><p>A single <strong>reader</strong> goroutine polls all buffers via epoll (User Space &#8220;Consumer&#8221;)</p></li><li><p>The reader consumes events in FIFO order across all CPUs</p></li><li><p>Events are forwarded to consumer workers through channels</p></li></ol><p>This architecture seems reasonable. It avoids lock contention by giving each CPU its own buffer. But our benchmarks revealed a critical scaling problem.</p><h2><strong>Reproducing the Multi-Core Storm</strong></h2><p>To validate our hypothesis, we needed to simulate the traffic patterns that trigger this bottleneck. And that&#8217;s where standard tools fell short. <code>iperf3</code>, the go-to for network benchmarking, is single-threaded by default. It generates impressive throughput numbers, but all that traffic flows through a single core, completely missing the multi-core contention pattern we observed in production.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hW04!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd823e379-6cf7-4ae3-a4b4-7fe19f23f2a7_1540x694.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hW04!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd823e379-6cf7-4ae3-a4b4-7fe19f23f2a7_1540x694.png 424w, https://substackcdn.com/image/fetch/$s_!hW04!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd823e379-6cf7-4ae3-a4b4-7fe19f23f2a7_1540x694.png 848w, https://substackcdn.com/image/fetch/$s_!hW04!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd823e379-6cf7-4ae3-a4b4-7fe19f23f2a7_1540x694.png 1272w, https://substackcdn.com/image/fetch/$s_!hW04!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd823e379-6cf7-4ae3-a4b4-7fe19f23f2a7_1540x694.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hW04!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd823e379-6cf7-4ae3-a4b4-7fe19f23f2a7_1540x694.png" width="1456" height="656" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d823e379-6cf7-4ae3-a4b4-7fe19f23f2a7_1540x694.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:656,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1687514,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/179255417?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd823e379-6cf7-4ae3-a4b4-7fe19f23f2a7_1540x694.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!hW04!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd823e379-6cf7-4ae3-a4b4-7fe19f23f2a7_1540x694.png 424w, https://substackcdn.com/image/fetch/$s_!hW04!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd823e379-6cf7-4ae3-a4b4-7fe19f23f2a7_1540x694.png 848w, https://substackcdn.com/image/fetch/$s_!hW04!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd823e379-6cf7-4ae3-a4b4-7fe19f23f2a7_1540x694.png 1272w, https://substackcdn.com/image/fetch/$s_!hW04!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd823e379-6cf7-4ae3-a4b4-7fe19f23f2a7_1540x694.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>So we built a purpose-designed Go application to replicate a real-world, network-intensive workload. The architecture leverages SO_REUSEPORT to bind multiple listeners to the same port, allowing the Linux kernel to distribute incoming <code>SYN</code> packets across sockets using flow hashing on the real client pod source IPs. Each accepted connection spawns a lightweight goroutine for reading and decoding, feeding work into a buffered channel consumed by a fixed worker pool. This design ensures that when we spin up dozens of client pods hammering the receiver, we&#8217;re genuinely spreading packet processing across all available cores, exactly the scenario where eBPF&#8217;s data transfer path becomes the bottleneck rather than the application itself.</p><h3>The Benchmark Results: A Non-Linear Degradation</h3><p>We tested network throughput using this multi-threaded network receiver app on VM types with varying core counts. The results showed a disturbing trend: the more powerful the machine, the worse the relative performance overhead.</p><pre><code><code>Performance Impact by Core Count:

4-core nodes:
  - Baseline:     200 Mb/s
  - With Retina:  200 Mb/s (0% impact)
  
16-core nodes:  
  - Baseline:     5 Gb/s
  - With Retina:  4.3 Gb/s (14% reduction)

32-core nodes:
  - Baseline:     8.0 Gb/s
  - With Retina:  4.5 Gb/s (44% reduction)</code></code></pre><p>On small nodes (4 cores), the overhead was negligible. By the time we reached 64 cores, the observability tool was choking nearly <strong>50% of the available network throughput capacity</strong>.</p><h2>Deep Dive: CPU Limits &amp; Pinning</h2><p>When we started the investigation in the performance drop on 32-core nodes, our immediate hypothesis was CPU throttling. We assumed the eBPF agent was hitting its Kubernetes resource limits. To test this, we ran a series of comparative benchmarks specifically on the 32-core nodes.</p><p>The results were counter-intuitive and highlighted the &#8220;thrashing&#8221; behavior of perf arrays:</p><p><strong>Network Throughput Comparison (32-core Node)</strong></p><pre><code>Scenario&#9;Throughput&#9;Throughput
Without Retina (Baseline)&#9;8.0 Gb/s&#9;-&#9;Clean baseline
With Retina (Default)&#9;4.5 Gb/s&#9;-43%&#9;Standard deployment
Retina (No CPU Limits)&#9;3.7 Gb/s&#9;-53%&#9;Performance worsened!
Retina (CPU Pinning)&#9;5.2 Gb/s&#9;-35%&#9;Best case for Perf Arrays</code></pre><h3>Why did removing CPU limits make it worse?</h3><p>Seeing performance drop from 4.5 Gb/s to 3.7 Gb/s after <em>removing</em> CPU limits (the &#8220;Without Limits&#8221; bar) was a surprise.</p><p>This revealed that the bottleneck wasn&#8217;t a lack of CPU cycles, but <strong>scheduling contention</strong>.</p><ol><li><p><strong>Thrashing:</strong> With unlimited CPU, the reader thread spun more aggressively, polling the 32 buffers.</p></li><li><p><strong>Context Switching:</strong> This generated excessive context switches (&gt;120k/sec).</p></li></ol><h3>The Effect of CPU Pinning</h3><p>When we applied CPU pinning (isolating the Retina agent to specific cores), throughput improved to <strong>5.2 Gb/s</strong>. While this was better than the default configuration, it still represented a <strong>35% performance penalty</strong> compared to the baseline. We can do this via CPUManager static policy in kubernetes.</p><p>Even with perfect CPU isolation, the architectural overhead of polling 32 separate memory regions via Perf Arrays prevented us from reaching acceptable performance.</p><h3>Look at the CPU usage pattern</h3><p>With our reproducible pattern, we saw that the more cores we used and the more network-intensive the machines were, the higher the degradation was.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TsPf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6564186-2c5e-4275-856b-823560232d9c_2276x1208.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TsPf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6564186-2c5e-4275-856b-823560232d9c_2276x1208.png 424w, https://substackcdn.com/image/fetch/$s_!TsPf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6564186-2c5e-4275-856b-823560232d9c_2276x1208.png 848w, https://substackcdn.com/image/fetch/$s_!TsPf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6564186-2c5e-4275-856b-823560232d9c_2276x1208.png 1272w, https://substackcdn.com/image/fetch/$s_!TsPf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6564186-2c5e-4275-856b-823560232d9c_2276x1208.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TsPf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6564186-2c5e-4275-856b-823560232d9c_2276x1208.png" width="2276" height="1208" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d6564186-2c5e-4275-856b-823560232d9c_2276x1208.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1208,&quot;width&quot;:2276,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:205759,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/179255417?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0cbd76e-0620-4949-8c49-3c3e33653096_2302x1208.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TsPf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6564186-2c5e-4275-856b-823560232d9c_2276x1208.png 424w, https://substackcdn.com/image/fetch/$s_!TsPf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6564186-2c5e-4275-856b-823560232d9c_2276x1208.png 848w, https://substackcdn.com/image/fetch/$s_!TsPf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6564186-2c5e-4275-856b-823560232d9c_2276x1208.png 1272w, https://substackcdn.com/image/fetch/$s_!TsPf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6564186-2c5e-4275-856b-823560232d9c_2276x1208.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>This made us look into again which part is actually changing with CPU core count.</p><h2>Debugging eBPF related Degradation </h2><p>We just analyzed a specific CPU usage pattern using our reproducible test environment. We observed a distinct correlation: as we increased the number of cores on nodes running network-intensive workloads, the performance degradation became more severe.</p><p>This led us to investigate which components were scaling poorly with the CPU core count. Let&#8217;s go down the debugging path.</p><p>First, we checked the loaded BPF programs to identify potential overhead. We will use just one as an example.</p><pre><code><code>bpftool prog list 

...
1167: sched_cls  name endpoint_ingress_filter  tag 44b14ea77164570a  gpl
&#9;loaded_at 2025-11-23T19:06:46+0000  uid 0
&#9;xlated 10480B  jited 7222B  memlock 12288B  map_ids 268,282
&#9;btf_id 391
...</code></code></pre><p>All we needed from the command above was the map_ids. We took a closer look at map 282.</p><pre><code>bpftool map show id 282

282: perf_event_array  name retina_packetpa  flags 0x0
&#9;key 4B  value 4B  max_entries 16  memlock 512B</code></pre><p>It was a perf_event_array. To understand its impact, we decided to check the event frequency. We ran a bpftrace script to compare perf_event_output (streams) against perf_event_wakeup (wakeups).</p><pre><code>timeout 10 sudo bpftrace -e &#8216;
  BEGIN {
    printf(&#8221;Timestamp | Streams/s | Wakeups/s\n&#8221;);
    printf(&#8221;----------------------------------\n&#8221;);
  }

  kprobe:perf_event_output {
    @streams = @streams + 1;
  }

  kprobe:perf_event_wakeup {
    @wakeups = @wakeups + 1;
  }

  interval:s:2 {
    printf(&#8221;%s | %9d | %9d\n&#8221;,
           strftime(&#8221;%H:%M:%S&#8221;, nsecs),
           @streams / 2,
           @wakeups / 2);

    @streams = 0;
    @wakeups = 0;
  }
&#8216;
Attaching 4 probes...
Timestamp | Streams/s | Wakeups/s
----------------------------------
19:43:31 |     84883 |     62328
19:43:33 |     91055 |     65034
19:43:35 |     94762 |     69747
19:43:37 |     94825 |     69257

@streams: 171695
@wakeups: 115786</code></pre><p>The results showed almost 70% of events resulting in a wakeup, which is significant.</p><p>To confirm the source, we asked: What happens if we disable Retina? How much of this percentage actually belongs to retina-agent? We ran the same probe with Retina disabled.</p><pre><code>timeout 10 sudo bpftrace -e &#8216;
  BEGIN {
    printf(&#8221;Timestamp | Streams/s | Wakeups/s\n&#8221;);
    printf(&#8221;----------------------------------\n&#8221;);
  }

  kprobe:perf_event_output {
    @streams = @streams + 1;
  }

  kprobe:perf_event_wakeup {
    @wakeups = @wakeups + 1;
  }

  interval:s:2 {
    printf(&#8221;%s | %9d | %9d\n&#8221;,
           strftime(&#8221;%H:%M:%S&#8221;, nsecs),
           @streams / 2,
           @wakeups / 2);

    @streams = 0;
    @wakeups = 0;
  }
&#8216;
Attaching 4 probes...
Timestamp | Streams/s | Wakeups/s
----------------------------------
19:44:43 |         0 |         0
19:44:45 |         0 |         0
19:44:47 |         0 |         0
19:44:49 |         0 |         0


@streams: 0
@wakeups: 0</code></pre><p>The answer was clear: All of it. Every recorded event was coming due to Retina running on our node.</p><p>We were able to identify the per-cpu perf arrays as the culprit relatively quickly. By simply following the BPF program thread.</p><p>So, what does this mean for our packetparser architecture?</p><p>We were running not just one, but four TC BPF programs simultaneously. As the data demonstrates, relying heavily on per-CPU perf arrays can lead to extreme noise. The overhead incurred by these wakeups creates significant pressure on the system, explaining why the degradation worsened with increased core counts and network load.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9m1W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F823e8265-7cb4-46a3-b628-63ae7598e97d_2210x1204.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9m1W!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F823e8265-7cb4-46a3-b628-63ae7598e97d_2210x1204.png 424w, https://substackcdn.com/image/fetch/$s_!9m1W!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F823e8265-7cb4-46a3-b628-63ae7598e97d_2210x1204.png 848w, https://substackcdn.com/image/fetch/$s_!9m1W!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F823e8265-7cb4-46a3-b628-63ae7598e97d_2210x1204.png 1272w, https://substackcdn.com/image/fetch/$s_!9m1W!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F823e8265-7cb4-46a3-b628-63ae7598e97d_2210x1204.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9m1W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F823e8265-7cb4-46a3-b628-63ae7598e97d_2210x1204.png" width="1456" height="793" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/823e8265-7cb4-46a3-b628-63ae7598e97d_2210x1204.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:793,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1231291,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/179255417?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F823e8265-7cb4-46a3-b628-63ae7598e97d_2210x1204.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9m1W!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F823e8265-7cb4-46a3-b628-63ae7598e97d_2210x1204.png 424w, https://substackcdn.com/image/fetch/$s_!9m1W!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F823e8265-7cb4-46a3-b628-63ae7598e97d_2210x1204.png 848w, https://substackcdn.com/image/fetch/$s_!9m1W!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F823e8265-7cb4-46a3-b628-63ae7598e97d_2210x1204.png 1272w, https://substackcdn.com/image/fetch/$s_!9m1W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F823e8265-7cb4-46a3-b628-63ae7598e97d_2210x1204.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3>Why Perf Arrays Break at Scale</h3><p>The bottleneck emerges from a fundamental architectural constraint: <strong>buffer polling management</strong>.</p><p>Consider what happens on a 32-core system under high network load:</p><ul><li><p>The single reader thread must manage file descriptors for 32 separate buffers.</p></li><li><p>Unlike a shared buffer, this requires iterating over multiple non-contiguous memory regions.</p></li><li><p><strong>NUMA Penalties:</strong> On multi-socket systems (common in 32+ core VMs), the user-space reader typically runs on one NUMA node but must access memory pages allocated on remote NUMA nodes to drain the per-CPU buffers. This leads to cache line bouncing and expensive remote memory access.</p></li></ul><h2>Ring Buffers: A Different Approach</h2><p>In kernel 5.8, BPF ring buffers (<strong>BPF_MAP_TYPE_RINGBUF</strong>) introduced a fundamentally different architecture. Instead of per-CPU isolation, ring buffers use a single shared data structure.</p><h3>Ring Buffer Architecture</h3><pre><code><code>All CPUs &#8594; Single Shared Buffer (8MB)
          &#8595;
    [Spinlock coordination]
          &#8595;
    Single Consumer Read Point</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!I7oe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9660b6b0-2be2-4498-ba30-41126bb2329e_1358x1184.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!I7oe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9660b6b0-2be2-4498-ba30-41126bb2329e_1358x1184.png 424w, https://substackcdn.com/image/fetch/$s_!I7oe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9660b6b0-2be2-4498-ba30-41126bb2329e_1358x1184.png 848w, https://substackcdn.com/image/fetch/$s_!I7oe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9660b6b0-2be2-4498-ba30-41126bb2329e_1358x1184.png 1272w, https://substackcdn.com/image/fetch/$s_!I7oe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9660b6b0-2be2-4498-ba30-41126bb2329e_1358x1184.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!I7oe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9660b6b0-2be2-4498-ba30-41126bb2329e_1358x1184.png" width="1358" height="1184" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9660b6b0-2be2-4498-ba30-41126bb2329e_1358x1184.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1184,&quot;width&quot;:1358,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:287858,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/179255417?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9660b6b0-2be2-4498-ba30-41126bb2329e_1358x1184.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!I7oe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9660b6b0-2be2-4498-ba30-41126bb2329e_1358x1184.png 424w, https://substackcdn.com/image/fetch/$s_!I7oe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9660b6b0-2be2-4498-ba30-41126bb2329e_1358x1184.png 848w, https://substackcdn.com/image/fetch/$s_!I7oe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9660b6b0-2be2-4498-ba30-41126bb2329e_1358x1184.png 1272w, https://substackcdn.com/image/fetch/$s_!I7oe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9660b6b0-2be2-4498-ba30-41126bb2329e_1358x1184.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Key differences from perf arrays:</p><ul><li><p><strong>Multi-producer, single-consumer</strong> design</p></li><li><p><strong>Lock-free</strong> for readers (spinlock only for writers)</p></li><li><p><strong>Efficient Batching</strong> - consume events from all CPUs in one contiguous memory pass</p></li><li><p><strong>Adaptive sizing</strong> - independent of CPU count</p></li></ul><h3>Implementation and Testing</h3><p>To address this, we modified Retina to support ring buffers. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!q_5r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!q_5r!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png 424w, https://substackcdn.com/image/fetch/$s_!q_5r!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png 848w, https://substackcdn.com/image/fetch/$s_!q_5r!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png 1272w, https://substackcdn.com/image/fetch/$s_!q_5r!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!q_5r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png" width="522" height="271.125" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:482,&quot;width&quot;:928,&quot;resizeWidth&quot;:522,&quot;bytes&quot;:88664,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/179255417?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!q_5r!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png 424w, https://substackcdn.com/image/fetch/$s_!q_5r!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png 848w, https://substackcdn.com/image/fetch/$s_!q_5r!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png 1272w, https://substackcdn.com/image/fetch/$s_!q_5r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F571e8f2a-f0a2-4139-9e5c-b1fdc5eeba72_928x482.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>To verify the fix, we ran the same steps as above. First, we inspected the map again:</p><pre><code>bpftool map show id 455
455: ringbuf  name retina_packetpa  flags 0x0
&#9;key 0B  value 0B  max_entries 8388608  memlock 8434072B</code></pre><p>This time, instead of a perf_event_array, we see a ringbuf.</p><p>Next, we double-checked that perf_event_wakeup events were eliminated by running our tracing script again.</p><pre><code>Timestamp | Streams/s | Wakeups/s
----------------------------------
19:59:27 |         0 |         0
19:59:29 |         0 |         0
19:59:31 |         0 |         0
19:59:33 |         0 |         0


@streams: 0
@wakeups: 0</code></pre><p>The results confirmed it, the specific noise from the perf event array is completely gone. Let&#8217;s now run some tests.</p><h3>Benchmark Results: Ring Buffer Performance</h3><p>After implementing ring buffer support, we repeated our benchmarks on the same 32-core nodes that struggled with Perf Arrays:</p><pre><code><code>Network Throughput Comparison (32-core nodes):
- Baseline (no Retina):         8.0 Gb/s
- With Perf Arrays (Pinned):    5.2 Gb/s (35% overhead)
- With Ring Buffer:             7.4 Gb/s (7.5% overhead)</code></code></pre><p>The improvement was dramatic. We reduced overhead from 35% to 7.5%. This validated our hypothesis: on high-core-count systems under sustained multi-core load, the data structure choice fundamentally determines whether eBPF observability remains transparent or becomes a production bottleneck.</p><h3>Trade-offs and Considerations</h3><p>Ring buffers aren&#8217;t universally better. Here&#8217;s what we learned:</p><p><strong>When Perf Arrays Win:</strong></p><ul><li><p>Low core counts (&#8804;8 cores)</p></li><li><p>Strict per-CPU isolation requirements</p></li><li><p>Older kernels (pre-5.8)</p></li><li><p>NUMA-sensitive workloads (specifically where kernel-side write latency is the priority over user-side read latency)</p></li></ul><p><strong>When Ring Buffers Win:</strong></p><ul><li><p>High core counts (&#8805;16 cores)</p></li><li><p>Bursty traffic patterns</p></li><li><p>Limited consumer threads</p></li><li><p>Memory-constrained environments</p></li></ul><h2>The State of eBPF Observability: Looking Forward</h2><p>Our investigation highlights a critical gap in the eBPF ecosystem: most tools are optimized for modest systems but deployed on increasingly powerful hardware.</p><h3>Recommendations for Tool Developers</h3><ol><li><p><strong>Make buffer mechanisms configurable</strong>: Don&#8217;t hardcode perf arrays or ring buffers</p></li><li><p><strong>Test on production-representative hardware</strong>: If users run 32+ cores, test on 32+ cores</p></li><li><p><strong>Document scaling characteristics</strong>: Be transparent about performance at different scales</p></li><li><p><strong>Provide escape hatches</strong>: Quick ways to disable or tune down collection</p></li></ol><h3>The Future: Adaptive Mechanisms</h3><p>The ideal eBPF observability tool would:</p><ul><li><p>Auto-detect system characteristics (CPU count, NUMA topology)</p></li><li><p>Dynamically switch buffer mechanisms based on load</p></li><li><p>Implement backpressure when overwhelmed</p></li><li><p>Gracefully degrade (do sampling) rather than impact workload performance</p></li></ul><h2>Conclusion: Renters vs. Owners</h2><p>This investigation highlights why the choice of buffer mechanism &#8220;Perf Arrays vs. Ring Buffers&#8221; isn&#8217;t just an implementation detail. It defines the scalability of your observability stack.</p><p>But more importantly, it brings us back to the question raised in the hallway: <strong>Why does a infrastrcuture team need to know the kernel stack if they are just &#8220;renting&#8221; the cloud?</strong></p><p>Because when you run at scale, the abstraction leaks. If we had stayed in the &#8220;renter&#8221; mindset, we opened a support ticket. And to say the least, no one acknowledged this as a problem to start with. The vendor or cloud provider will look at the saturation and point the finger back at you, claiming it is <strong>your rogue workload</strong> causing the issue.</p><p>They wouldn&#8217;t be entirely wrong, your workload <em>is</em> high-traffic. But the degradation isn&#8217;t the workload&#8217;s fault. It&#8217;s the observability tool struggling to observe it. If you view yourself merely as a renter, you accept the degradation. If you view yourself as an engineer owning the stack, you investigate, you debug, and you fix.</p><div><hr></div><p><em>Questions or experiences to share? reach out on <a href="https://www.linkedin.com/in/thezainm/">LinkedIn</a>.</em></p><h2>References</h2><ul><li><p><a href="https://www.youtube.com/watch?v=J-Zx64mJzVk">Kubecon 2025 NA talk: &#8220;Fix First, Investigate Later: When an eBPF Rollout Brought Down Our Network&#8221;</a></p></li><li><p><strong><a href="https://nakryiko.com/posts/bpf-ringbuf/">Perf Arrays vs Ring Buffers Comparison</a></strong></p></li><li><p><a href="https://www.kernel.org/doc/html/next/bpf/ringbuf.html">BPF Ring buffer documentation</a></p></li></ul><h2>Acknowledgments</h2><p>Thanks to <a href="https://www.linkedin.com/in/grzesuav/">Grzegorz G&#322;&#261;b</a> (Whatnot) for co-presenting at KubeCon. Reach out to him for any questions related to the first part <strong>of our</strong> KubeCon presentation <strong>on</strong> mutation webhook magic.</p><p>Thanks to <a href="https://www.linkedin.com/in/rektide/">Matthew Fowle</a> for suggesting the title of blogpost: &#8220;Who will observe the observability?&#8221;</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.zmalik.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Optimized Infra! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[From Utilization to PSI: Rethinking Resource Starvation Monitoring in Kubernetes]]></title><description><![CDATA[From Utilization Confusion to PSI Clarity in Kubernetes]]></description><link>https://blog.zmalik.dev/p/from-utilization-to-psi-rethinking</link><guid isPermaLink="false">https://blog.zmalik.dev/p/from-utilization-to-psi-rethinking</guid><dc:creator><![CDATA[Zain]]></dc:creator><pubDate>Sun, 27 Apr 2025 13:02:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!g-Yz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<pre><code>In Kubernetes v1.33 (alpha), cAdvisor&#8217;s Pressure Stall Information (PSI) metrics can be enabled on the kubelet by passing --feature-gates=KubeletPSI=true</code></pre><h3>Introduction: The Evolution of Resource Monitoring</h3><p>In traditional VM-based environments, monitoring resource starvation was straightforward: you watched <strong>resource utilization</strong> (CPU, memory, etc.) against the machine&#8217;s capacity. If a VM&#8217;s CPU usage hit close to 100% of its allocated vCPUs or memory usage neared 100% of RAM, you knew contention was occurring. High utilization meant the workload was starved for more resources. This utilization-centric approach made sense when each VM had fixed resources.</p><p>However, Kubernetes changed the game. Kubernetes introduced the concepts of <strong>resource requests</strong> and <strong>limits</strong> for containers, enabling dynamic sharing and overcommitment of resources on a node.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.zmalik.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Optimized Infra! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Many teams initially tried to carry over the old monitoring mindset, comparing container usage to its <strong>requested resources</strong> as a proxy for stress. Unfortunately, usage vs. requests can be very misleading in Kubernetes (it may simply be borrowing idle capacity). A container using more CPU than requested isn&#8217;t necessarily a problem, and one using less than requested isn&#8217;t necessarily safe from contention. The traditional model of &#8220;utilization == starvation&#8221; doesn&#8217;t directly apply in this new world of shared resources and elastic consumption.</p><p>In this post, we&#8217;ll explore:</p><ul><li><p>Why the old metrics (like CPU utilization vs. requests) fall short in Kubernetes.</p></li><li><p>Why even monitoring usage against limits is only a slight improvement.</p></li><li><p>Why setting CPU limits in Kubernetes is often considered a bad practice, as it can hurt performance.</p></li><li><p>How Linux&#8217;s Completely Fair Scheduler (CFS) using CPU shares (weights) based on requests usually suffices to manage CPU contention.</p></li><li><p>How <strong>Pressure Stall Information (PSI)</strong> metrics provide a far more accurate picture of resource contention.</p></li></ul><p>We'll look at key scenarios that PSI highlights, such as CPU throttling events or genuine CPU pressure, and how PSI avoids the false positives of older monitoring approaches. Technical sample queries will be included to illustrate how to gather and use PSI metrics in practice.</p><p>If you&#8217;re a Kubernetes engineer or SRE still relying on outdated utilization metrics, it&#8217;s time to update your toolkit. Let&#8217;s dive in.</p><h3>The Traditional Approach: Utilization vs. Requests (and Why It Fails)</h3><p>In pre-container environments, we monitored utilization to catch resource starvation. For example, if a VM&#8217;s CPU was 95% utilized or its memory 90% full, that was a red flag.</p><p>Many teams initially applied a similar idea to Kubernetes by looking at a container&#8217;s <strong>usage relative to its resource requests</strong> (the amount of CPU or memory it &#8220;requested&#8221; when scheduled). The assumption was: if a pod&#8217;s CPU usage is near or above its request, it must be at risk of starvation, and if usage is well below request, it&#8217;s safe.</p><p>This approach, however, is flawed in Kubernetes. <strong>Resource requests are not hard allocations</strong> &#8211; they are guarantees for scheduling and baseline service, not fixed ceilings. Kubernetes uses requests to decide which node can host a pod and to ensure each pod gets its fair share when resources are contested, but a pod can use <em>more</em> CPU than requested if the node has spare capacity. Similarly, a pod might have low CPU usage relative to its request, yet still encounter contention if other pods compete for CPU.</p><p>In other words, comparing usage to requests is apples-to-oranges: requests are a scheduling construct, while usage is actual consumption.</p><p><strong>Example:</strong> Imagine a pod requests 1 CPU but at runtime it&#8217;s using 1.5 CPUs on average. In a VM world this would be &#8220;150% utilization&#8221; (impossible on a fixed 1 CPU allocation), but on Kubernetes this scenario can happen if the node has idle CPU cycles. The pod simply borrows CPU above its request since no one else is using it. Naively, an SRE might see 150% of request and panic. But if the node isn&#8217;t fully utilized, this isn&#8217;t actually a problem! The pod isn&#8217;t starved at all; it&#8217;s benefiting from extra headroom. Kubernetes explicitly allows this: "As long as the node isn't maxed out, pod B can use whatever extra CPU is free... it won't interfere with pod A's fair share. That's the whole point of CPU requests &#8211; they give you a floor (guarantee)."</p><p>On the other hand, consider a pod that requests 1 CPU, but is only using 0.5 CPU most of the time. One might think it&#8217;s safe because it&#8217;s under its request. But if the node is fully booked with other pods and this pod occasionally needs more CPU (say bursts to 1 CPU), it <em>will</em> get at least 1 CPU (its full request) if it needs it &#8211; that&#8217;s guaranteed. However, if it needed <em>more</em> than 1 CPU (beyond its request) at a time when the node is busy, it might experience delays. Traditional monitoring wouldn&#8217;t flag this at all because usage hasn&#8217;t hit any static threshold relative to request or capacity.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!g-Yz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!g-Yz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png 424w, https://substackcdn.com/image/fetch/$s_!g-Yz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png 848w, https://substackcdn.com/image/fetch/$s_!g-Yz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png 1272w, https://substackcdn.com/image/fetch/$s_!g-Yz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!g-Yz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png" width="518" height="518" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:902,&quot;width&quot;:902,&quot;resizeWidth&quot;:518,&quot;bytes&quot;:1661800,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.zmalik.dev/i/161080762?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!g-Yz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png 424w, https://substackcdn.com/image/fetch/$s_!g-Yz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png 848w, https://substackcdn.com/image/fetch/$s_!g-Yz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png 1272w, https://substackcdn.com/image/fetch/$s_!g-Yz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce21c391-e9de-4b3f-9a39-32cbc5879d94_902x902.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In short, utilization vs. request is a poor indicator of actual distress in Kubernetes. A pod can be using 200% of its requested CPU and be perfectly healthy if the node has spare capacity, or it can be well below 100% of its request and still suffer if the node CPU is fully contended (or if it&#8217;s artificially capped by other means). The old model &#8220;high utilization = bad&#8221; doesn&#8217;t directly translate when resources are elastic.</p><h3>Why It Made Sense on VMs (Fixed Quota) but Not on Kubernetes</h3><p>It&#8217;s worth highlighting why this confusion exists.</p><ul><li><p><strong>On a VM or physical machine:</strong> Your CPU and memory allocations are basically fixed. If you have 4 vCPUs, 100% usage means all 4 are busy. If you have 8 GB of RAM, using 7.5 GB means you&#8217;re about to run out. There&#8217;s a fixed ceiling, so usage as a fraction of that ceiling is a meaningful metric.</p></li><li><p><strong>In Kubernetes:</strong> A container&#8217;s &#8220;ceiling&#8221; is not always fixed at its request. If no explicit limit is set, the true ceiling is the node&#8217;s capacity (or remaining capacity), which is often much higher than the request. A container&#8217;s resource usage can go beyond what it requested (temporary boost) or can be constrained by overall node conditions even <em>before</em> hitting its request (if other pods demand their share).</p></li></ul><p>Thus, the ratio usage/request can be very misleading. High usage/request doesn&#8217;t necessarily mean trouble (could be just opportunistic usage), while low usage/request doesn&#8217;t guarantee no contention.</p><p>Many Kubernetes monitoring dashboards still show &#8220;CPU utilization vs. requests&#8221; or &#8220;Memory usage vs. requests&#8221; for pods or deployments. These can be useful for <strong>capacity planning or right-sizing</strong> (e.g., to see if requests are far too high or too low relative to actual usage over time). But they&#8217;re <strong>not reliable for real-time detection of contention or starvation.</strong> Relying on them for alerting can cause false positives (alerting on benign bursts above request) and false negatives (missing actual contention that doesn&#8217;t manifest in those ratios).</p><h3>Monitoring Against Limits: A Slightly Better Approach</h3><p>Realizing the pitfalls of using requests as the yardstick, many teams shifted to monitoring <strong>resource limits</strong> instead. Kubernetes allows setting resources.limits for CPU and memory, which are hard constraints: a container cannot exceed its CPU limit (it will be <strong>throttled</strong>) and cannot exceed its memory limit (it will be <strong>OOM-killed</strong> if it tries).</p><p>Intuitively, monitoring usage against these hard limits makes more sense:</p><ul><li><p>If a container is close to 100% of its memory limit, it&#8217;s in danger of OOM.</p></li><li><p>If a container&#8217;s CPU usage is hitting 100% of its CPU limit, it means it&#8217;s fully utilizing its allowed CPU and could be throttled.</p></li></ul><p><strong>Memory limits</strong> in particular demand close attention. Unlike CPU, memory is not a &#8220;compressible&#8221; resource &#8211; if you run out of memory, the kernel cannot just slow things down; something has to give (usually the process gets killed). "Memory is different because it is non-compressible &#8211; once you give memory you can't take it away without killing the process." For this reason, best practice is to <strong>always set memory limits</strong> on pods, and monitor if memory usage approaches those limits. A container at 95% of its memory limit is one allocation away from an OOM Kill. So monitoring memory usage vs. limits (and receiving alerts before it hits 100%) is essential.</p><p>For <strong>CPU limits</strong>, if set, a container&#8217;s CPU usage being at 100% of its limit is a sign it wants more CPU but is not allowed to have it. Hitting a CPU limit won&#8217;t kill the container &#8211; instead, the Linux kernel will <strong>throttle</strong> the container&#8217;s CPU cycles to enforce the limit. Throttling means the container&#8217;s processes are made to wait, even if the CPU is idle, until the next time slice &#8211; effectively capping its CPU usage to the limit over time. If you monitor a container and see its CPU usage flatlined at its limit (say constantly using 1 core when the limit is 1 core), that likely means the container could use more CPU if it were available. In other words, it&#8217;s potentially CPU-starved (constrained by the limit).</p><p>An even clearer indicator is to monitor the <strong>CPU throttling metrics</strong> that cAdvisor exposes when limits are in place. For example, cAdvisor tracks container_cpu_cfs_throttled_seconds_total (cumulative seconds a container was throttled) and the number of throttling occurrences. By checking the rate of increase of this metric, you can tell if the container is actively being throttled by the CPU quota. A high throttling rate means the container hit its CPU limit frequently. Monitoring throttling metrics captures scenarios where average CPU usage is low but brief bursts above the limit cause throttling.</p><p>Overall, watching memory usage vs. memory limits and CPU usage vs. CPU limits (or throttle metrics) is more aligned with real resource risks:</p><ul><li><p>If memory usage is near the limit, the pod is at risk of OOM kill &#8211; a critical condition.</p></li><li><p>If CPU usage hits the limit and throttling occurs, the pod&#8217;s performance is being artificially constrained by its quota.</p></li></ul><p>This approach reduces false alarms compared to the naive utilization-vs-request method. You won&#8217;t alert on a pod using 150% of its request if it still hasn&#8217;t hit any limit. Instead, you&#8217;d alert when it actually hits a ceiling (limit) or gets throttled. It&#8217;s a step in the right direction.</p><p>However, there are two big caveats:</p><ol><li><p>Not everyone sets CPU limits (in fact, as we&#8217;ll discuss next, setting CPU limits can be counterproductive).</p></li><li><p>Even with limits, these signals don&#8217;t tell the whole story of <em>why</em> the pod is constrained or if it&#8217;s a true contention issue or just a mis-configured limit.</p></li></ol><p>If you follow modern best practices, you might only set memory limits and not CPU limits on your pods. In that case, CPU usage has no defined hard limit to compare against &#8211; a pod can use all the CPU it can get on the node. You&#8217;re back to square one for CPU: how do you detect CPU contention without a limit? Monitoring raw CPU usage alone still isn&#8217;t sufficient, because a pod could be slowed down by competition with other pods even if it has no fixed limit.</p><p>Secondly, even when CPU limits are used, you might be interested in detecting contention <em>before</em> a pod is throttling at 100% of its limit. For example, a pod might be using 80% of its limit but the node is completely busy; it might not be throttled yet, but it could still be experiencing latency due to high CPU demand on the node. Pure usage metrics won&#8217;t flag that.</p><p>The bottom line: Monitoring limits is better than nothing &#8211; especially for memory &#8211; but it&#8217;s a reactive measure and can miss subtler forms of contention. We need a way to directly measure <strong>&#8220;how hard is the workload trying to use resources and being held back,&#8221;</strong> whether by limits or by competition with others.</p><p>Enter Linux&#8217;s CPU scheduler behavior and why many recommend removing CPU limits entirely in favor of a different approach.</p><h3>The Case Against CPU Limits (and How Kubernetes Schedules CPU Fairly Without Them)</h3><p>If monitoring CPU limits and throttling is an improvement, an even more radical improvement is to avoid CPU limits altogether. This might sound counterintuitive &#8211; if you don&#8217;t limit CPU, won&#8217;t pods just contend uncontrolled? But Kubernetes (and Linux) have a built-in mechanism to handle CPU contention: <strong>CFS CPU shares</strong> based on the pod&#8217;s CPU requests (also known as CPU weight).</p><p>Many experts argue that setting CPU limits causes more harm than good in Kubernetes, and that you can rely on requests and the kernel scheduler for fair sharing. Let&#8217;s break down why CPU limits can be harmful:</p><ul><li><p><strong>They restrict natural bursting, even when resources are idle.</strong> A container with a CPU limit cannot exceed that limit, no matter what. If the node has idle CPU cycles, a container without a limit could have used those cycles to handle a spike in work, then dropped back down. With a limit, those idle cycles go unused while the container threads sit idle waiting for the next time slice. In effect, &#8220;resources are available but you aren&#8217;t allowed to use them.&#8221; This is wasted potential and can degrade application performance. Why slow down your app just to keep CPU idle?</p></li><li><p><strong>They can cause complex throttling behavior.</strong> When a container hits its quota early in a scheduling period, the kernel will throttle it for the remainder of that period. This can introduce latency spikes. The throttling isn&#8217;t smooth; it literally pauses the container&#8217;s threads. If your application is latency-sensitive, CPU quotas can produce irregular delays that are hard to predict or tune.</p></li><li><p><strong>They are often unnecessary for fairness.</strong> The typical reason people set CPU limits is to prevent one pod from hogging the CPU and starving others (&#8220;noisy neighbor&#8221; problem). But Kubernetes already has a solution for this: CPU requests translate to CFS weights. The Linux <strong>Completely Fair Scheduler</strong> distributes CPU time according to these weights when there&#8217;s contention. If two pods contend for CPU, each gets a share proportional to its weight (derived from its CPU request). For instance, if Pod A requests 1500 millicores and Pod B requests 1000 millicores, A will get 60% and B 40% of CPU time under contention. It doesn&#8217;t matter if Pod B tries to use more; it will only get spare cycles beyond its share if A isn&#8217;t using its full request. In other words, <strong>requests give you a guaranteed floor and a fair share, without the need for hard caps.</strong> The kernel scheduler&#8217;s use of weights is well documented: &#8220;Kubernetes resources.requests.cpu translates into a weight. It&#8217;s the relative weight that matters &#8211; the ratio of one container&#8217;s request to another&#8217;s. If the node is under load, container B (with double the request of A) will get roughly twice as much CPU time as container A.&#8221; This happens automatically, no CPU limit required.</p></li><li><p><strong>CPU limits don&#8217;t affect scheduling, only runtime.</strong> A subtle point: the Kubernetes scheduler doesn&#8217;t even consider limits when placing pods, only requests. This means you could have a node where total CPU limits of pods exceed capacity; the limits aren&#8217;t used for admission control. Their only function is to throttle at runtime. If you&#8217;ve already ensured via requests that the node won&#8217;t be overloaded (scheduler won&#8217;t put more total requested CPU than the node capacity), then limits are mostly redundant for preventing overload.</p></li></ul><p>Because of these reasons, many in the Kubernetes community advocate <strong>not using CPU limits at all for most workloads.</strong> If every pod has an appropriate CPU request, then no pod can starve another of its guaranteed share. Any pod can still burst above its request if extra CPU is available, which improves utilization and performance. And if two pods both want more than their share, they&#8217;ll be limited by the CFS weighting &#8211; effectively, each is &#8220;throttled&#8221; only by the fact that the other exists and has a claim, not by an arbitrary cap. It&#8217;s a more organic form of throttling based on competition, not a static limit.</p><p>To illustrate, consider a scenario: Two pods (Pod A and Pod B) share a node. If there are no limits but each has a request (say A requests 1 CPU, B requests 1 CPU on a 2-CPU node), then if B suddenly needs more CPU and A doesn&#8217;t need all of its, B can temporarily use 1.5 CPUs while A uses 0.5. A still can get its full 1 CPU whenever it needs (it has that reserved), and B just opportunistically uses the slack. Both live. If, instead, we imposed a limit equal to their request (1 CPU each), then even if A was idle, B could not exceed 1 CPU &#8211; it would be stuck waiting while that extra CPU stays idle. That&#8217;s exactly what we want to avoid.</p><p>The modern best practice is: <strong>use CPU requests for all pods</strong> (and make them as accurate as possible), but set <strong>no CPU limits in most cases.</strong> The only exceptions might be certain workloads that internally adjust to a given CPU limit or multi-tenant clusters where you absolutely need to cap usage of untrusted workloads. But for typical microservices in a controlled cluster, CPU limits often do more harm than good.</p><p>If you adopt this approach (no CPU limits), you gain performance &#8211; pods can burst and use idle cycles &#8211; and simpler behavior. But you lose the simple signal of &#8220;CPU usage == limit&#8221; and the throttling metric for that pod, since there is no artificial throttling anymore. You need a different way to monitor when a pod is truly encountering CPU contention. After all, just because we removed the limit doesn&#8217;t mean we don&#8217;t care if the pod is getting constrained; it&#8217;s just constrained by actual contention now (other pods or node capacity), not by a configured quota.</p><p>How can we detect that scenario? This is where <strong>Pressure Stall Information (PSI)</strong> comes in as a game-changer for monitoring. It gives us direct insight into contention, regardless of whether a CPU limit is involved or not.</p><h3>The Modern Approach: Pressure Stall Information (PSI)</h3><p>Linux&#8217;s <strong>Pressure Stall Information (PSI)</strong> is a kernel feature (introduced in Linux 4.20) that provides a direct measure of resource contention. In essence, PSI metrics tell you what percentage of time tasks are <strong>stalled (waiting)</strong> due to lack of a given resource &#8211; CPU, memory, or IO.</p><p>This is exactly the signal we want for detecting resource starvation:</p><ul><li><p>If an application&#8217;s threads are frequently waiting on CPU because the CPU is busy elsewhere (or a quota throttled them), that indicates <strong>CPU pressure</strong>.</p></li><li><p>If they are waiting on memory (e.g., for memory to be freed or swapped in), that indicates <strong>memory pressure</strong>.</p></li></ul><p>PSI has been described as a &#8220;barometer&#8221; of resource pressure, providing early warning as pressure builds. Unlike raw utilization, which only shows <em>how much</em> resource is being used, <strong>PSI shows </strong><em><strong>how contended</strong></em><strong> that resource is</strong>, i.e., the cost (in wait time) of that contention.</p><p>To put it another way: high CPU utilization could be either because an app is happily consuming available CPU or because it&#8217;s struggling to get CPU time; PSI distinguishes these by measuring the delay. If an app is using a lot of CPU but not experiencing delays, PSI will remain low. If an app is getting delayed (runnable but not running), PSI will report a higher percentage.</p><p>Concretely, the Linux kernel exposes PSI data via files like /proc/pressure/cpu, /proc/pressure/memory, etc., and with cgroups v2, you can get PSI for specific cgroups (which is how Kubernetes can get per-container and per-pod PSI).</p><p>The CPU PSI metric is reported as a single metric (some pressure), since at a system level there&#8217;s always either some tasks running or waiting. For memory and IO, PSI is reported in two flavors: some (at least one task stalled) and full (all tasks stalled, meaning complete stall). But for most purposes, the &#8220;some&#8221; metric is the primary indicator of pressure.</p><p>What does &#8220;some CPU pressure = 20%&#8221; mean in plain terms? It means that over the time window, 20% of the time there was at least one task that wanted to run but couldn&#8217;t due to CPU being busy. In other words, one or more threads were ready to execute but had to wait. 0% CPU pressure means no delay. 100% CPU pressure (extreme case) would mean at all times, something was waiting for CPU.</p><p>The beauty of PSI is that it directly measures contention <strong>as experienced by the workload.</strong> It doesn&#8217;t matter whether the contention is because of a hard limit (throttling) or because other processes are competing &#8211; if your container&#8217;s tasks had to wait, PSI captures it. Conversely, if your container is blasting CPU but never actually waits (because there was no contention), PSI stays low.</p><p>As the VictoriaMetrics team put it: &#8220;PSI tracks when tasks are delayed or stalled due to resource contention &#8211; basically when the CPU is too busy to handle everything right away... These [PSI] metrics give you a pretty direct view into how much CPU pressure your containers are dealing with &#8212; something that raw CPU usage numbers don&#8217;t always show clearly.&#8221; This is a crucial point: raw usage can&#8217;t differentiate between using 80% of CPU with no interference vs. using 80% and desperately wanting 100%. PSI can.</p><h3>PSI in Kubernetes: Getting the Data</h3><p>Initially, PSI was only available by manually checking the host or cgroup files, but it has since been integrated into Kubernetes&#8217; monitoring pipeline. Recent versions of cAdvisor (and the Kubernetes summary API) now expose PSI metrics for each container, pod, and node.</p><p>As of this writing, this is typically an alpha feature &#8211; you may need to enable the KubeletPSI feature gate and be running on a Linux kernel that supports cgroup v2 and PSI (kernel 4.20+ with cgroup2). But assuming those requirements are met, you&#8217;ll have new metrics available in the kubelet&#8217;s /metrics/cadvisor endpoint.</p><p>The key PSI metrics for containers exposed via cAdvisor are typically named:</p><ul><li><p>container_pressure_cpu_waiting_seconds_total: total time tasks in the container have been delayed waiting for CPU (corresponds to the PSI &#8220;some&#8221; CPU counter). &#8220;Waiting&#8221; here means at least one task waiting.</p></li><li><p>container_pressure_cpu_stalled_seconds_total: total time <em>all</em> tasks in the container were stalled due to CPU (CPU &#8220;full&#8221;, less commonly used for CPU).</p></li></ul><p>Similarly, you&#8217;ll find:</p><ul><li><p>container_pressure_memory_waiting_seconds_total and ...memory_stalled_seconds_total for memory pressure (some vs full).</p></li><li><p>container_pressure_io_waiting_seconds_total and ...io_stalled_seconds_total for IO pressure.</p></li></ul><p>These metrics accumulate time (in seconds) that tasks were stalled. To get a <strong>current pressure percentage</strong> over a time interval, you take a rate of these counters. We&#8217;ll demonstrate that in the next section with queries.</p><p>The key thing is: we now have a direct gauge of resource contention for each container/pod. We no longer have to infer it indirectly from usage vs. requests or throttling metrics. We can literally see &#8220;this container spent X% of the last 5 minutes waiting on CPU&#8221;. That is gold from an SRE perspective &#8211; it answers &#8220;is my app suffering from lack of CPU?&#8221; with a concrete measure.</p><h3>Unlocking Insights: How PSI Reveals Real Contention</h3><p>Let&#8217;s discuss a few scenarios to illustrate how PSI shines, highlighting exactly the cases mentioned earlier:</p><ol><li><p><strong>Pod is throttled (CPU limit):</strong> Suppose you still have a CPU limit on a pod, and the pod is frequently hitting that limit. Each time it hits the limit, the kernel throttles it (makes it wait until the next period). From the pod&#8217;s perspective, its processes were ready to run but got halted &#8211; classic CPU stall. <strong>PSI will register this:</strong> during those throttle intervals, tasks were waiting for CPU even though the CPU might have been idle otherwise (it&#8217;s a forced wait). Therefore, the container&#8217;s cpu_waiting PSI goes up. If you see, say, 10% CPU pressure on a container that correlates with it running at its exact CPU limit, that indicates it spent 10% of time throttled due to the limit. In older monitoring, you might have noticed high throttle counts; with PSI, you see the <em>impact</em> of that throttling as a percentage of lost time. This is a more intuitive measure (&#8220;10% CPU starvation&#8221;) than just raw counts. The advantage is that PSI doesn&#8217;t require any special case &#8211; it doesn&#8217;t matter that the wait was self-inflicted by a limit; it will still show up.</p></li><li><p><strong>Pod exceeds its request and the node is at capacity (genuine CPU contention):</strong> Now consider a pod with no CPU limit. It has a request of 1 CPU but can use more if available. It starts using 2 CPUs because demand increased. If the node has at least 2 idle CPUs free, it will get 2 CPUs &#8211; no contention, no pressure. But if the node only had 1.5 CPUs free beyond what others are using, then the pod will be competing with others for CPU time. The Linux scheduler will give it its fair share (~1 CPU worth plus some fraction of the extra), but not the full 2 CPUs it wants. The pod will effectively be running below the level it would like to (it has threads that could run more, but they must wait their turn). In this scenario, even though there&#8217;s no explicit limit, the pod is experiencing CPU starvation due to node limits and competition. How do we detect it? CPU usage of that pod might show something like 1.5 CPUs usage (so it&#8217;s above its request of 1, which might or might not alert someone). But <strong>PSI will clearly show something like, for example, 25% CPU wait</strong>, meaning for 25% of the time, the pod had tasks waiting on CPU because the node was fully busy. That directly quantifies the contention. In other words, whenever a pod is unable to run because other pods (or overall load) are using the CPU, CPU PSI rises. This is exactly when SREs need to know &#8211; the pod could benefit from more CPU (or a higher request, or moving to a less busy node, or scaling out). Traditional metrics couldn&#8217;t isolate this condition well.</p></li><li><p><strong>Pod exceeds its request but node has available capacity (no contention):</strong> This is the flip side and addresses the false positives issue. A pod might be using more CPU than its requested (say 200% of request) but if the node has idle cores, this is not a problem &#8211; the pod isn&#8217;t depriving anyone and isn&#8217;t waiting for CPU. Old-school monitoring might wrongly flag this as an issue. But <strong>PSI will be near 0% in this case</strong>, because from the pod&#8217;s view, it got all the CPU time it wanted with no delays. No waiting, no pressure. This is a beautiful example of PSI avoiding a false alert. The SRE can confidently ignore high usage if pressure remains low &#8211; it means the high usage is simply opportunistic consumption of idle resources, not contention. By focusing on pressure, you don&#8217;t cry wolf when a pod is just efficiently using available headroom.</p></li></ol><p>To sum up, PSI aligns alerts with actual performance-impacting events. High CPU PSI means the app experienced CPU wait time (it was ready to do work but had to wait). High memory PSI means the app was stalled due to memory. If these metrics are low, it means lack of resources is not significantly slowing the app, regardless of how high the utilization numbers might be.</p><p>Memory PSI is also extremely useful. Memory contention in Kubernetes typically leads to OOM kills. Memory PSI can show that an application is spending time waiting for memory (e.g., perhaps garbage collection is hitting heavy page faults). If memory PSI for a container is significant, that&#8217;s a red flag that even if it hasn&#8217;t been OOM-killed yet, it&#8217;s suffering and could benefit from more memory or optimizations. In the past, one might only notice memory issues after an OOM kill event. PSI gives a window into the &#8220;gray zone&#8221; of memory pressure before a fatal event.</p><p>In summary, PSI metrics let you detect real resource starvation conditions in Kubernetes: whether due to CPU limits, CPU competition, or memory crunch, without getting confused by usage patterns that aren&#8217;t actually problematic. This makes them a powerful addition to the monitoring arsenal for Kubernetes SREs.</p><h3>Putting PSI to Work: Practical Monitoring Examples</h3><p>Now that we have these PSI metrics, how do we use them? In most setups, you&#8217;ll be scraping the kubelet/cAdvisor metrics with Prometheus (or another monitoring system). Assuming container_pressure_cpu_waiting_seconds_total and friends are being collected, here are some sample queries and techniques using PromQL:</p><p><strong>1. Calculate CPU pressure percent for a container or pod:</strong></p><p>Use the rate of the _waiting_seconds_total counter over a window, and multiply by 100 for percentage.</p><pre><code><code>100 * rate(container_pressure_cpu_waiting_seconds_total{namespace="my-namespace", pod="my-pod", container="my-app-container"}[5m])</code></code></pre><p>This yields the percentage of time over the last 5 minutes that at least one task in the specified container was waiting for CPU. If this value is 30, it means 30% of the time the container was CPU-starved. (Adjust labels to match your metrics setup; filter out container="POD" if needed).</p><p><strong>2. Alert on high CPU pressure:</strong></p><p>Set up an alert like: &#8220;CPU pressure &gt; X% for Y minutes&#8221;.</p><pre><code><code>rate(container_pressure_cpu_waiting_seconds_total{namespace!~"kube.*"}[5m]) &gt; 0.20</code></code></pre><p>This checks for &gt;20% CPU pressure over 5 minutes for any container not in kube-system namespaces. Choose a threshold that makes sense &#8211; even a small non-zero value consistently might be worth investigating, but 10-20% is often a good starting point to avoid noise. This alert says &#8220;this container spent more than 20% of the last 5 minutes waiting on CPU &#8211; it&#8217;s likely CPU starved.&#8221;</p><p><strong>3. Memory pressure monitoring:</strong></p><p>Similarly, use container_pressure_memory_waiting_seconds_total.</p><pre><code><code>100 * rate(container_pressure_memory_waiting_seconds_total{namespace="my-namespace", pod="my-pod", container="my-app-container"}[5m])</code></code></pre><p>This gives the percent of time the container was stalled due to memory. Ideally this is 0%. Any sustained non-zero memory pressure indicates the app is experiencing memory contention (e.g., the kernel is frequently reclaiming pages, or it&#8217;s on the verge of OOM). You might alert if this goes above, say, 5% for some time, because significant memory stall could degrade performance badly.</p><p><strong>4. Node-level pressure:</strong></p><p>Check overall node pressure by looking at the metrics for the root cgroup (usually identified by a specific label like id="/", container="", and pod="").</p><pre><code><code>100 * rate(container_pressure_cpu_waiting_seconds_total{id="/"}[5m])</code></code></pre><p>This query (adjusting labels as needed for your Prometheus setup) could give the overall CPU pressure for the entire node. If this is high, it means the node is collectively overcommitted on CPU. This can be used to drive node-level auto-scaling or just to monitor overall health.</p><p><strong>5. Identify top contended pods:</strong></p><p>Find which pods have the highest CPU pressure using topk.</p><pre><code><code>topk(5, 100 * rate(container_pressure_cpu_waiting_seconds_total{namespace="my-namespace", container!="POD"}[5m]))</code></code></pre><p>This would list the top 5 containers (excluding pause containers) in my-namespace by CPU pressure percentage over the last 5 minutes. This is great for troubleshooting: it directly surfaces &#8220;who is suffering from CPU contention the most.&#8221;</p><p><strong>6. Combine with usage for context:</strong></p><p>PSI is best used alongside traditional metrics. Create a dashboard showing:</p><ul><li><p>CPU Usage (millicores)</p></li><li><p>CPU Pressure (%)</p></li><li><p>Memory Usage (bytes)</p></li><li><p>Memory Pressure (%)</p></li></ul><p>Side by side for each pod/container . This way you can differentiate:</p><ul><li><p><strong>High usage + Low pressure:</strong> Healthy, high throughput, efficiently using resources. Good!</p></li><li><p><strong>Lower usage + High pressure:</strong> Application is likely getting throttled or contended; performance is likely degraded. Needs investigation/more resources.</p></li><li><p><strong>High usage + High pressure:</strong> Application is very busy and hitting contention. Could potentially use more resources or needs optimization.</p></li></ul><p>If request latency spikes alongside high CPU pressure, it confirms the application was delayed by CPU availability. If latency spikes but CPU pressure is zero, the cause lies elsewhere.</p><p>Remember to ensure your cluster setup provides these metrics. Check your Kubernetes version, cAdvisor configuration, and monitoring agent setup. PSI metrics are gaining adoption but might require explicit configuration depending on your environment.</p><h3>Conclusion: Out with the Old, In with the New (Monitoring)</h3><p>The world of Kubernetes resource management requires rethinking old monitoring habits. Historically, we obsessed over utilization percentages and compared usage to static allocations. In Kubernetes, that paradigm is outdated. A pod running at 95% of its requested CPU might be absolutely fine, while another at 50% could be suffering &#8211; without the right insight, you wouldn&#8217;t know.</p><p>We saw that monitoring against resource limits is a step closer to reality, especially for memory and for detecting explicit CPU throttling, but even that has limitations, particularly as best practices shift toward minimal use of CPU limits.</p><p>By leveraging <strong>PSI metrics</strong>, we align our monitoring with what actually matters: whether workloads are delayed due to resource contention. This gives SREs and engineers a much clearer signal amidst the noise. No more guessing or second-guessing based on indirect metrics &#8211; PSI tells it like it is.</p><p>To be opinionated: The traditional model of looking at utilization or usage vs. requests in Kubernetes is not just misleading, it&#8217;s antiquated. In an environment where resource allocations are fluid and &#8220;100% usage&#8221; has no fixed meaning, clinging to those old metrics can lead to bad decisions (throttling workloads unnecessarily, or not noticing when something is starving).</p><p>Modern Kubernetes operations should adopt a <strong>contention-first monitoring mindset</strong> using PSI. Here are the key takeaways:</p><ul><li><p><strong>Always set memory requests and limits</strong> and monitor usage against limits. Use <strong>memory PSI</strong> to catch pressure early.</p></li><li><p><strong>Set CPU requests</strong> for all containers to ensure fair scheduling and capacity planning.</p></li><li><p><strong>Avoid CPU limits for most workloads.</strong> Let pods burst and trust Kubernetes/Linux to share CPU via CFS weights.</p></li><li><p><strong>Monitor CPU contention directly with PSI metrics</strong> rather than naive utilization. High CPU PSI is a clear signal of starvation, low PSI indicates resource availability.</p></li><li><p>Use PSI alongside other metrics for full context (e.g., correlate with latency or traditional usage).</p></li><li><p>Monitor <strong>node-level PSI</strong> to understand overall cluster saturation.</p></li></ul><p>The Kubernetes ecosystem is recognizing the value of PSI. It&#8217;s making its way into upstream features and recommendations. By incorporating PSI into your monitoring dashboards and alerts, you&#8217;ll have a much sharper understanding of your clusters&#8217; performance. You&#8217;ll reduce noise (no more false alarms for benign high usage) and catch true issues faster (seeing actual contention as it develops).</p><p>In Kubernetes, &#8220;not all high utilization is created equal,&#8221; and PSI is the lens that shows the difference. As engineers and SREs, embracing this new approach will let us focus our optimizations and firefighting where it truly matters. It&#8217;s time to retire the old metrics (or at least deprioritize them) and adopt a contention-first monitoring mindset. Your pods will thank you, by doing their work without waiting in line (and your pager will thank you for the quieter nights!).</p><p>No single metric is a silver bullet, but in the realm of resource monitoring, PSI is a huge leap forward. Combined with good resource request hygiene and sensible limits (or lack thereof), it forms the core of a modern, accurate picture of Kubernetes performance. The old utilization metrics served us well in the VM era, but Kubernetes demands a more nuanced view &#8211; and we now have the tools to achieve it.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.zmalik.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Optimized Infra! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Are You Getting the Most Out of Your Cloud Network?]]></title><description><![CDATA[Pushing the Limits in a Managed Environment]]></description><link>https://blog.zmalik.dev/p/get-most-out-of-cloud-network</link><guid isPermaLink="false">https://blog.zmalik.dev/p/get-most-out-of-cloud-network</guid><dc:creator><![CDATA[Zain]]></dc:creator><pubDate>Tue, 09 Jul 2024 14:00:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1cb35a8-8de9-49d6-9e83-d105257b6b86_1450x1168.heic" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As users of managed Kubernetes clusters (such as AKS, EKS, or GKE), we often focus on deploying and managing our applications at a higher level, relying on the underlying infrastructure to handle the low-level details. However, there is a world of optimization opportunities beneath the surface, which sometimes is left to the mercy of default configurations. In this blog post, we'll embark on a journey to the depths of kernel tuning, shedding light on how adjusting kernel-level settings and understanding low-level concepts can significantly enhance the performance of your infrastructure.</p><p>One critical aspect of this optimization involves recognizing that transitioning to a more powerful VM or a specialized machine doesn&#8217;t automatically alter the kernel&#8217;s default settings. Though these defaults are generally optimized for smaller instances, they can limit the performance capabilities of larger nodes.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QIOt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85d92b97-6657-4f05-9623-e9f4f71b7965_1402x554.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QIOt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85d92b97-6657-4f05-9623-e9f4f71b7965_1402x554.png 424w, https://substackcdn.com/image/fetch/$s_!QIOt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85d92b97-6657-4f05-9623-e9f4f71b7965_1402x554.png 848w, https://substackcdn.com/image/fetch/$s_!QIOt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85d92b97-6657-4f05-9623-e9f4f71b7965_1402x554.png 1272w, https://substackcdn.com/image/fetch/$s_!QIOt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85d92b97-6657-4f05-9623-e9f4f71b7965_1402x554.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QIOt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85d92b97-6657-4f05-9623-e9f4f71b7965_1402x554.png" width="1402" height="554" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/85d92b97-6657-4f05-9623-e9f4f71b7965_1402x554.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:554,&quot;width&quot;:1402,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:253493,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QIOt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85d92b97-6657-4f05-9623-e9f4f71b7965_1402x554.png 424w, https://substackcdn.com/image/fetch/$s_!QIOt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85d92b97-6657-4f05-9623-e9f4f71b7965_1402x554.png 848w, https://substackcdn.com/image/fetch/$s_!QIOt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85d92b97-6657-4f05-9623-e9f4f71b7965_1402x554.png 1272w, https://substackcdn.com/image/fetch/$s_!QIOt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85d92b97-6657-4f05-9623-e9f4f71b7965_1402x554.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Although we are using Azure as an example, the concepts discussed here can be applied to most cloud providers.<br><br>Before we dive into the specifics of kernel tuning, it's important to note that we will be using Ubuntu as our example throughout this blog post. However, the concepts and techniques discussed here can be applied to other Linux distributions with similar kernel architectures.</p><h2>Journey from network device to the application</h2><p>Let's explore the optimization opportunities for the journey of a network packet from the NIC to a Linux process.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3tIl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1cb35a8-8de9-49d6-9e83-d105257b6b86_1450x1168.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3tIl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1cb35a8-8de9-49d6-9e83-d105257b6b86_1450x1168.heic 424w, https://substackcdn.com/image/fetch/$s_!3tIl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1cb35a8-8de9-49d6-9e83-d105257b6b86_1450x1168.heic 848w, https://substackcdn.com/image/fetch/$s_!3tIl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1cb35a8-8de9-49d6-9e83-d105257b6b86_1450x1168.heic 1272w, https://substackcdn.com/image/fetch/$s_!3tIl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1cb35a8-8de9-49d6-9e83-d105257b6b86_1450x1168.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3tIl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1cb35a8-8de9-49d6-9e83-d105257b6b86_1450x1168.heic" width="1450" height="1168" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c1cb35a8-8de9-49d6-9e83-d105257b6b86_1450x1168.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1168,&quot;width&quot;:1450,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:94998,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3tIl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1cb35a8-8de9-49d6-9e83-d105257b6b86_1450x1168.heic 424w, https://substackcdn.com/image/fetch/$s_!3tIl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1cb35a8-8de9-49d6-9e83-d105257b6b86_1450x1168.heic 848w, https://substackcdn.com/image/fetch/$s_!3tIl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1cb35a8-8de9-49d6-9e83-d105257b6b86_1450x1168.heic 1272w, https://substackcdn.com/image/fetch/$s_!3tIl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc1cb35a8-8de9-49d6-9e83-d105257b6b86_1450x1168.heic 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The green-highlighted steps in the flowchart illustrate the areas we will target. </p><h3><strong>Optimizing the Ring Buffer for High Network Traffic Spikes</strong></h3><p>When dealing with high network traffic spikes, one of the critical areas to focus on is the ring buffer. The ring buffer is a fixed-size buffer that temporarily holds incoming packets before they are processed by the kernel. If the buffer is too small, it can easily fill up during traffic surges, resulting in packet loss. By tuning the ring buffer size, we can mitigate this issue and improve network efficiency.</p><p>Let&#8217;s first check the defaults we have in our AKS cluster.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qfXj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55a0a30e-5362-4d46-a86d-016fb926f47f_1422x572.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qfXj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55a0a30e-5362-4d46-a86d-016fb926f47f_1422x572.png 424w, https://substackcdn.com/image/fetch/$s_!qfXj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55a0a30e-5362-4d46-a86d-016fb926f47f_1422x572.png 848w, https://substackcdn.com/image/fetch/$s_!qfXj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55a0a30e-5362-4d46-a86d-016fb926f47f_1422x572.png 1272w, https://substackcdn.com/image/fetch/$s_!qfXj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55a0a30e-5362-4d46-a86d-016fb926f47f_1422x572.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qfXj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55a0a30e-5362-4d46-a86d-016fb926f47f_1422x572.png" width="1422" height="572" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55a0a30e-5362-4d46-a86d-016fb926f47f_1422x572.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:572,&quot;width&quot;:1422,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:77946,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qfXj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55a0a30e-5362-4d46-a86d-016fb926f47f_1422x572.png 424w, https://substackcdn.com/image/fetch/$s_!qfXj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55a0a30e-5362-4d46-a86d-016fb926f47f_1422x572.png 848w, https://substackcdn.com/image/fetch/$s_!qfXj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55a0a30e-5362-4d46-a86d-016fb926f47f_1422x572.png 1272w, https://substackcdn.com/image/fetch/$s_!qfXj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55a0a30e-5362-4d46-a86d-016fb926f47f_1422x572.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>So we have a default of 1024 and maximum allowed values in our instance type is 8192</p><p>Which means depending on the nature of our network load we are not leveraging the maximum allowed values. In our case we were seeing a network device packet drop across nodes which had high network usage. Usually we just blamed it to high network usage.</p><p>After tuning the value to a higher value, we saw a dramatic change in packet drop.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aBX8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2083d985-7ef3-449a-9e1a-2acd9e583d5f_2526x826.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aBX8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2083d985-7ef3-449a-9e1a-2acd9e583d5f_2526x826.png 424w, https://substackcdn.com/image/fetch/$s_!aBX8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2083d985-7ef3-449a-9e1a-2acd9e583d5f_2526x826.png 848w, https://substackcdn.com/image/fetch/$s_!aBX8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2083d985-7ef3-449a-9e1a-2acd9e583d5f_2526x826.png 1272w, https://substackcdn.com/image/fetch/$s_!aBX8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2083d985-7ef3-449a-9e1a-2acd9e583d5f_2526x826.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aBX8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2083d985-7ef3-449a-9e1a-2acd9e583d5f_2526x826.png" width="1456" height="476" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2083d985-7ef3-449a-9e1a-2acd9e583d5f_2526x826.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:476,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1801133,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!aBX8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2083d985-7ef3-449a-9e1a-2acd9e583d5f_2526x826.png 424w, https://substackcdn.com/image/fetch/$s_!aBX8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2083d985-7ef3-449a-9e1a-2acd9e583d5f_2526x826.png 848w, https://substackcdn.com/image/fetch/$s_!aBX8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2083d985-7ef3-449a-9e1a-2acd9e583d5f_2526x826.png 1272w, https://substackcdn.com/image/fetch/$s_!aBX8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2083d985-7ef3-449a-9e1a-2acd9e583d5f_2526x826.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3>Distributing Network Processing Load Across CPU Cores</h3><p>In a high-performance environment, ensuring that network processing is efficiently distributed across multiple CPU cores is crucial. Without proper distribution, some CPU cores may become overutilized while others remain underutilized, leading to suboptimal performance and increased latency. Three key features that can help address this issue are Receive Side Scaling (RSS), Receive Packet Steering (RPS) and Receive Flow Steering (RFS)</p><p></p><h3>Receive Side Scaling (RSS)</h3><p>RSS works by distributing the network interrupt handling across multiple CPU cores. When a packet arrives, the NIC generates an interrupt that is handled by a specific CPU core, determined by the RSS hash.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dg-U!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bc852a-b74d-4089-bd9e-a4d3b61ccd08_624x532.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dg-U!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bc852a-b74d-4089-bd9e-a4d3b61ccd08_624x532.png 424w, https://substackcdn.com/image/fetch/$s_!dg-U!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bc852a-b74d-4089-bd9e-a4d3b61ccd08_624x532.png 848w, https://substackcdn.com/image/fetch/$s_!dg-U!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bc852a-b74d-4089-bd9e-a4d3b61ccd08_624x532.png 1272w, https://substackcdn.com/image/fetch/$s_!dg-U!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bc852a-b74d-4089-bd9e-a4d3b61ccd08_624x532.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dg-U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bc852a-b74d-4089-bd9e-a4d3b61ccd08_624x532.png" width="358" height="305.21794871794873" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/31bc852a-b74d-4089-bd9e-a4d3b61ccd08_624x532.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:532,&quot;width&quot;:624,&quot;resizeWidth&quot;:358,&quot;bytes&quot;:61354,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dg-U!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bc852a-b74d-4089-bd9e-a4d3b61ccd08_624x532.png 424w, https://substackcdn.com/image/fetch/$s_!dg-U!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bc852a-b74d-4089-bd9e-a4d3b61ccd08_624x532.png 848w, https://substackcdn.com/image/fetch/$s_!dg-U!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bc852a-b74d-4089-bd9e-a4d3b61ccd08_624x532.png 1272w, https://substackcdn.com/image/fetch/$s_!dg-U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31bc852a-b74d-4089-bd9e-a4d3b61ccd08_624x532.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>This means that the network traffic can be distributed across those 8 queues for both receiving and transmitting data. This results in each queue being mapped to one of the 8 cores.<br><br>We can verify this by checking the network indirection table</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kyV0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe05b589a-bd7e-4ede-bd67-d3f2527d4c82_1412x834.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kyV0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe05b589a-bd7e-4ede-bd67-d3f2527d4c82_1412x834.png 424w, https://substackcdn.com/image/fetch/$s_!kyV0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe05b589a-bd7e-4ede-bd67-d3f2527d4c82_1412x834.png 848w, https://substackcdn.com/image/fetch/$s_!kyV0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe05b589a-bd7e-4ede-bd67-d3f2527d4c82_1412x834.png 1272w, https://substackcdn.com/image/fetch/$s_!kyV0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe05b589a-bd7e-4ede-bd67-d3f2527d4c82_1412x834.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kyV0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe05b589a-bd7e-4ede-bd67-d3f2527d4c82_1412x834.png" width="1412" height="834" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e05b589a-bd7e-4ede-bd67-d3f2527d4c82_1412x834.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:834,&quot;width&quot;:1412,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:144561,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kyV0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe05b589a-bd7e-4ede-bd67-d3f2527d4c82_1412x834.png 424w, https://substackcdn.com/image/fetch/$s_!kyV0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe05b589a-bd7e-4ede-bd67-d3f2527d4c82_1412x834.png 848w, https://substackcdn.com/image/fetch/$s_!kyV0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe05b589a-bd7e-4ede-bd67-d3f2527d4c82_1412x834.png 1272w, https://substackcdn.com/image/fetch/$s_!kyV0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe05b589a-bd7e-4ede-bd67-d3f2527d4c82_1412x834.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Our NIC hardware allows about 128 entries in the indirection table and all of those are mapped to 8 queues. But we are allowed to have as many queues as our VM cores, in this case 32. </p><pre><code><code>ethtool -L eth0 combined 32</code></code></pre><p>Now we do have those 128 entries mapped to all 32 queues.</p><p>By increasing the number of RSS queues, we can achieve several benefits</p><ul><li><p><strong>Parallel Processing:</strong> With 32 queues, there are more parallel paths for processing incoming packets. This can lead to higher overall network throughput, especially beneficial for high-bandwidth applications.</p></li><li><p><strong>Reduced Queue Depth:</strong> With more queues, the depth of each queue is reduced, leading to less time packets spend waiting in the queue, thus speeding up processing.</p></li><li><p><strong>Faster Packet Processing:</strong> By distributing the workload more evenly across CPUs, each packet can be processed more quickly, reducing the overall latency.</p></li></ul><h3>Receive Packet Steering (RPS)</h3><p>RPS is designed to distribute the processing of received packets across multiple CPUs, based on the hash of the packet flow. This can help to alleviate bottlenecks on a single CPU core.<br><br>It's important to note that RPS focuses specifically on the packet processing stage, where the actual handling and manipulation of packet data occur. This is separate from the interrupt handling phase, which deals with the initial reception and queuing of incoming packets by the network interface.<br><br>Systems with a high number of CPU cores can leverage RPS to utilise their full processing potential. With a low number of CPU cores the benefits are almost null in this case.</p><p>Most modern kernels do support RPS, you can quickly verify it in the boot configuration</p><pre><code><code>grep RPS /boot/config-$(uname -r)
CONFIG_RPS=y</code></code></pre><p>lets check on any of the queues</p><pre><code><code>cat /sys/class/net/eth0/queues/rx-0/rps_cpus
0</code></code></pre><p>The default value for the <code>rps_cpus</code> file is typically <code>0</code>, indicating that no CPUs are explicitly assigned for RPS, and the packets are processed by the CPU handling the interrupt. But we can do enable it for our system.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!c_38!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F989040fa-efc6-4aa2-847d-1a58cb78ea8b_1402x242.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c_38!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F989040fa-efc6-4aa2-847d-1a58cb78ea8b_1402x242.png 424w, https://substackcdn.com/image/fetch/$s_!c_38!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F989040fa-efc6-4aa2-847d-1a58cb78ea8b_1402x242.png 848w, https://substackcdn.com/image/fetch/$s_!c_38!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F989040fa-efc6-4aa2-847d-1a58cb78ea8b_1402x242.png 1272w, https://substackcdn.com/image/fetch/$s_!c_38!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F989040fa-efc6-4aa2-847d-1a58cb78ea8b_1402x242.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!c_38!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F989040fa-efc6-4aa2-847d-1a58cb78ea8b_1402x242.png" width="1402" height="242" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/989040fa-efc6-4aa2-847d-1a58cb78ea8b_1402x242.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:242,&quot;width&quot;:1402,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:60976,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!c_38!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F989040fa-efc6-4aa2-847d-1a58cb78ea8b_1402x242.png 424w, https://substackcdn.com/image/fetch/$s_!c_38!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F989040fa-efc6-4aa2-847d-1a58cb78ea8b_1402x242.png 848w, https://substackcdn.com/image/fetch/$s_!c_38!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F989040fa-efc6-4aa2-847d-1a58cb78ea8b_1402x242.png 1272w, https://substackcdn.com/image/fetch/$s_!c_38!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F989040fa-efc6-4aa2-847d-1a58cb78ea8b_1402x242.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>This way we improve network performance by balancing the load of packet processing across all CPU cores, reducing the likelihood of any single core becoming a bottleneck.</p><p>By enabling RPS we achieve:</p><ul><li><p><strong>Enhanced Load Distribution:</strong> By distributing packet processing across multiple CPUs, RPS helps balance the workload, preventing any single CPU core from becoming a bottleneck. This ensures more efficient utilization of all available CPU resources.</p></li><li><p><strong>Improved Throughput:</strong> With RPS enabled, multiple CPU cores can handle packet processing simultaneously, leading to higher overall network throughput. This is especially beneficial for systems with high network traffic.</p></li><li><p><strong>Reduced Latency:</strong> By spreading the packet processing load across multiple CPUs, RPS reduces the time packets spend waiting in the queue, leading to faster processing and lower latency.</p></li></ul><h3>Receive Flow Steering (RFS)</h3><p>Receive Flow Steering (RFS) is an enhancement to Receive Packet Steering (RPS) that aims to improve cache locality and performance by steering packets to the CPU that is already processing the relevant flow. This ensures that the CPU cache remains "hot" with relevant data, reducing latency and improving efficiency.</p><p>RFS maintains a flow table that maps each flow to a specific CPU. This table is populated as packets are processed.</p><p>When a packet arrives at the NIC and triggers a hardware interrupt. The interrupt handler or NIC driver performs minimal processing and schedules a soft IRQ for the packet.</p><p>A hash is computed based on the 5-tuple information of the packet (source IP, destination IP, source port, destination port, and protocol). This hash uniquely identifies the flow.</p><p>If the flow is found in the table, the corresponding CPU is determined. If not, a CPU is selected based on the current load or other policies, and the flow table is updated with this new entry.</p><p>The flow table provides the CPU ID that should process the packet. The packet is then enqueued for processing by the identified CPU.</p><p>The designated CPU processes the packet, handling the protocol stack (TCP/UDP, IP) and delivering the packet to the appropriate socket buffer in the user space.</p><p>The benefits of enabling RFS include:</p><ul><li><p><strong>Improved Cache Locality:</strong> By processing packets of the same flow on the same CPU, RFS improves cache hits, reducing memory access latency.</p></li><li><p><strong>Reduced Context Switching:</strong> Minimizes the need to switch context between CPUs, leading to more efficient CPU utilization.</p></li></ul><p>If you are using <a href="https://kubernetes.io/docs/tasks/administer-cluster/cpu-management-policies/#static-policy">CPUsets</a> in your application, you might even see greater benefits in performance and efficiency due to improved CPU resource allocation and enhanced cache locality.</p><p></p><h3>SoftIRQs Processing</h3><p>We just increased the ring buffer, optimized the RSS, enabled RPS and RFS to handle scenarios where packet bursts are common. But now you might seeing packet drop in certain situations where kernel cannot process packets quickly enough.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!J0FP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde8fc853-bc15-4585-96b4-dc50badc58cc_807x1197.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!J0FP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde8fc853-bc15-4585-96b4-dc50badc58cc_807x1197.heic 424w, https://substackcdn.com/image/fetch/$s_!J0FP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde8fc853-bc15-4585-96b4-dc50badc58cc_807x1197.heic 848w, https://substackcdn.com/image/fetch/$s_!J0FP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde8fc853-bc15-4585-96b4-dc50badc58cc_807x1197.heic 1272w, https://substackcdn.com/image/fetch/$s_!J0FP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde8fc853-bc15-4585-96b4-dc50badc58cc_807x1197.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!J0FP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde8fc853-bc15-4585-96b4-dc50badc58cc_807x1197.heic" width="426" height="631.8736059479554" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/de8fc853-bc15-4585-96b4-dc50badc58cc_807x1197.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1197,&quot;width&quot;:807,&quot;resizeWidth&quot;:426,&quot;bytes&quot;:67524,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!J0FP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde8fc853-bc15-4585-96b4-dc50badc58cc_807x1197.heic 424w, https://substackcdn.com/image/fetch/$s_!J0FP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde8fc853-bc15-4585-96b4-dc50badc58cc_807x1197.heic 848w, https://substackcdn.com/image/fetch/$s_!J0FP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde8fc853-bc15-4585-96b4-dc50badc58cc_807x1197.heic 1272w, https://substackcdn.com/image/fetch/$s_!J0FP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde8fc853-bc15-4585-96b4-dc50badc58cc_807x1197.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Increasing backlog can help in these situations to match the increased ring buffer. If the default value 1000 is not sufficient, you will require to do some iterations till you find the right value for it. </p><pre><code>sysctl -w net.core.netdev_max_backlog=$new_value</code></pre><p>Setting this value too high may not be ideal, as sometimes it&#8217;s better to drop packets. Because our VM's network specs are closely tied to the number of CPU cores, we decided to base the new value on a multiple of the core count.</p><p>Also we can choose to increase the budget, this will increase the packets we process in each soft IRQ invocation. </p><pre><code>sysctl -w net.core.netdev_budget=$new_budget_value</code></pre><p>You can optimize your system for different scenarios, whether you need high throughput or low latency. Probably default values won&#8217;t cut for both extreme situations.</p><h3>Automation @ Scale</h3><p>We won&#8217;t go in details about how to set it up the automations but lets talk about the systems used to automate these issues we mentioned. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DJtu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc8bd34-1a7b-49da-a856-cadc228f06ea_1104x780.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DJtu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc8bd34-1a7b-49da-a856-cadc228f06ea_1104x780.png 424w, https://substackcdn.com/image/fetch/$s_!DJtu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc8bd34-1a7b-49da-a856-cadc228f06ea_1104x780.png 848w, https://substackcdn.com/image/fetch/$s_!DJtu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc8bd34-1a7b-49da-a856-cadc228f06ea_1104x780.png 1272w, https://substackcdn.com/image/fetch/$s_!DJtu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc8bd34-1a7b-49da-a856-cadc228f06ea_1104x780.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DJtu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc8bd34-1a7b-49da-a856-cadc228f06ea_1104x780.png" width="1104" height="780" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ccc8bd34-1a7b-49da-a856-cadc228f06ea_1104x780.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:780,&quot;width&quot;:1104,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1246658,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DJtu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc8bd34-1a7b-49da-a856-cadc228f06ea_1104x780.png 424w, https://substackcdn.com/image/fetch/$s_!DJtu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc8bd34-1a7b-49da-a856-cadc228f06ea_1104x780.png 848w, https://substackcdn.com/image/fetch/$s_!DJtu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc8bd34-1a7b-49da-a856-cadc228f06ea_1104x780.png 1272w, https://substackcdn.com/image/fetch/$s_!DJtu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccc8bd34-1a7b-49da-a856-cadc228f06ea_1104x780.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We are using Kubernetes and will leverage its tools to optimize our system management. We have two <a href="https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/">DaemonSets</a>. The first DaemonSet runs our custom Go code to check if each node has the correct version of a <a href="https://manpages.ubuntu.com/manpages/bionic/man1/systemd.1.html">systemD</a> unit. If the systemD unit is not running, it labels the node; if it is running, it removes the label. The second DaemonSet runs only on nodes labeled by the first one, ensuring the systemD unit is installed and started if it's not already. Finally, the systemD unit itself will configure kernel settings based on the number of CPU cores.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!THMr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74efe7b-6fae-492e-9530-f06517673905_826x1576.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!THMr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74efe7b-6fae-492e-9530-f06517673905_826x1576.png 424w, https://substackcdn.com/image/fetch/$s_!THMr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74efe7b-6fae-492e-9530-f06517673905_826x1576.png 848w, https://substackcdn.com/image/fetch/$s_!THMr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74efe7b-6fae-492e-9530-f06517673905_826x1576.png 1272w, https://substackcdn.com/image/fetch/$s_!THMr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74efe7b-6fae-492e-9530-f06517673905_826x1576.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!THMr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74efe7b-6fae-492e-9530-f06517673905_826x1576.png" width="826" height="1576" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d74efe7b-6fae-492e-9530-f06517673905_826x1576.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1576,&quot;width&quot;:826,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:102664,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!THMr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74efe7b-6fae-492e-9530-f06517673905_826x1576.png 424w, https://substackcdn.com/image/fetch/$s_!THMr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74efe7b-6fae-492e-9530-f06517673905_826x1576.png 848w, https://substackcdn.com/image/fetch/$s_!THMr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74efe7b-6fae-492e-9530-f06517673905_826x1576.png 1272w, https://substackcdn.com/image/fetch/$s_!THMr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd74efe7b-6fae-492e-9530-f06517673905_826x1576.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Summary</h2><p><code>In this blog post, we explored various kernel-level optimizations that can significantly enhance the performance of your infrastructure by focusing on network packet handling and processing. We discussed tuning the ring buffer, enabling RSS, RPS, and RFS, and adjusting SoftIRQs settings.</code></p><p><code>These optimizations can help mitigate packet loss, balance network processing loads, and improve overall efficiency.  While this post covered several key areas, there are still other optimizations to consider, such as fine-tuning socket buffer settings and exploring more advanced kernel parameters.  If you have any questions or would like to share your experiences, feel free to reach out to me directly.</code></p><p><code>Happy optimizing, and stay tuned for more deep dives into system performance enhancements! </code></p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://blog.zmalik.dev/p/get-most-out-of-cloud-network?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thank you for reading Optimized Infra. This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.zmalik.dev/p/get-most-out-of-cloud-network?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.zmalik.dev/p/get-most-out-of-cloud-network?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.zmalik.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Optimized Infra! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Container Network Packet Drop in AKS]]></title><description><![CDATA[Investigating and mitigating a curious case of container network packet drop in AKS (Azure Kubernetes Service)]]></description><link>https://blog.zmalik.dev/p/packet-drop</link><guid isPermaLink="false">https://blog.zmalik.dev/p/packet-drop</guid><dc:creator><![CDATA[Zain]]></dc:creator><pubDate>Mon, 25 Sep 2023 12:51:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!GG2y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a676ec0-5b0b-4658-a753-0f3d8a1103c0_2104x1150.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>During a recent system outage, our Azure Kubernetes Service (AKS) clusters experienced a peculiar issue. Specifically, some containers suffered packet drops, causing network connectivity problems.</p><p>Our AKS clusters run containerized workloads, managed by Cluster API (CAPZ). Each node pool is a Virtual Machine Scale Set (VMSS), which we manage indirectly through the AKS layer.</p><p>During the outage, certain workloads on a specific node were affected. Initially, we resolved the issue by cordoning off the node and migrating the workloads. However, the problem recurred shortly after. The primary symptom was a significant increase in network packet drops.</p><p>For container network drops, we are relying on metrics exposed by cadvisor:</p><pre><code>container_network_receive_packets_dropped_total
container_network_transmit_packets_dropped_total</code></pre><h2>Investigation</h2><p>After identifying the packet drop issue, we initiated an investigation to ascertain if network throttling was occurring at the VM level, and sought the help of Azure Support for a thorough examination.</p><p>Another common symptom for all problematic nodes was a VM Freeze event, which was observed in the node status conditions. A VM Freeze event can occur due to a variety of reasons, according to Azure documentation.</p><pre><code>The Virtual Machine is scheduled to pause for a few seconds. 
CPU and network connectivity may be suspended, but there's no 
impact on memory or open files.</code></pre><p>But we have no more visibility on the internals of an Azure VM Freeze event. The preliminary findings from Azure Support indicated no anomalies with the VM, suggesting a review of any alterations in workload behavior. Concurrently, we conducted iPerf tests and captured tcpdump data on our end to delve deeper into the nature of the packet drops and to gain more insights into the network performance hindrances we were facing.</p><h2>Root Cause Analysis</h2><p>An intriguing observation was made regarding CPU utilization on the affected node, where it was noticed that one core was being utilized at 100%, while the remaining cores exhibited significantly lower levels of utilization.</p><p>This second metric was coming from <a href="https://github.com/prometheus/node_exporter">node-exporter</a>:</p><pre><code>node_cpu_seconds_total{} by (cpu)</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GG2y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a676ec0-5b0b-4658-a753-0f3d8a1103c0_2104x1150.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GG2y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a676ec0-5b0b-4658-a753-0f3d8a1103c0_2104x1150.png 424w, https://substackcdn.com/image/fetch/$s_!GG2y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a676ec0-5b0b-4658-a753-0f3d8a1103c0_2104x1150.png 848w, https://substackcdn.com/image/fetch/$s_!GG2y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a676ec0-5b0b-4658-a753-0f3d8a1103c0_2104x1150.png 1272w, https://substackcdn.com/image/fetch/$s_!GG2y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a676ec0-5b0b-4658-a753-0f3d8a1103c0_2104x1150.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GG2y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a676ec0-5b0b-4658-a753-0f3d8a1103c0_2104x1150.png" width="1456" height="796" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7a676ec0-5b0b-4658-a753-0f3d8a1103c0_2104x1150.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1542589,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GG2y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a676ec0-5b0b-4658-a753-0f3d8a1103c0_2104x1150.png 424w, https://substackcdn.com/image/fetch/$s_!GG2y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a676ec0-5b0b-4658-a753-0f3d8a1103c0_2104x1150.png 848w, https://substackcdn.com/image/fetch/$s_!GG2y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a676ec0-5b0b-4658-a753-0f3d8a1103c0_2104x1150.png 1272w, https://substackcdn.com/image/fetch/$s_!GG2y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a676ec0-5b0b-4658-a753-0f3d8a1103c0_2104x1150.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>perf results</h2><p>The next thing I did was to run a <code>perf</code> on the node to see what was causing the CPU to spike.</p><pre><code><code>perf record -C 5 -a -g -D 99 -- sleep 60
</code></code></pre><p>results show that the CPU is being consumed by <code>ksoftirqd/5</code> process</p><pre><code><code>...
  Children      Self  Command          Shared Object 
+   99.40%     0.00%  ksoftirqd/5      [unknown]     
+   99.34%     0.00%  ksoftirqd/5      [unknown]     
+   99.18%     0.00%  ksoftirqd/5      [unknown]     
+   99.12%     0.00%  ksoftirqd/5      [unknown]     
...</code></code></pre><h2>ksoftirqd</h2><p><code>ksoftirqd</code> led me to inspect the softirqs. To do this, I had to check the interrupts on the node.</p><pre><code><code>cat /proc/interrupts

     CPU0  CPU1  CPU2  CPU3       CPU4       CPU5  CPU6  CPU7
  4:   0     0   538     0          0          0     0     0 
  8:   0     0     0     0          0          0     0     0 
  9:   0     0     0     0          0          0     0     0 
 24:   0     0     0   586          0   82868004     0     0 
 25: 728     0     0     0  869041985          0     0     0 
 26:   0   864     0     0          0  813776462     0     0 
 27:   0     0  1439     0  838852829          0     1     0 
 28:   0     0     0  1545          0  781818909     0     1 
 29:   1     0     0     0 1234309153          0     0     0 
 30:   0     1     0     0          0 1262389002     0     0 
 31:   0     0     1     0  853755079          0  1172     0 
 32:   0     0     0     1          0  812015919     0  1417 
</code></code></pre><p>We can clearly see that CPU 4 and CPU 5 are handling way more interrupts than the other CPUs.</p><h2>smp_affinity</h2><p>Next thing which I did was to check the smp_affinity of the interrupts.</p><pre><code><code>for i in {24..32} ; do cat /proc/irq/$i/smp_affinity; done
20
10
20
10
20
10
20
10
20
</code></code></pre><p>The values 20 and 10 are hexadecimal representations of the CPU core assignments. Specifically, 20 means IRQs are handled by CPU 5, and 10 means they're handled by CPU 4. So we can see that the interrupts are being handled by CPU 4 and CPU 5. This explains the CPU spike on CPU 4 and CPU 5. This also explains the packet drop on the containers running on the node.</p><p>To understand better, we need to remember how interrupts are handled in Linux.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sTN3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde71642-469c-4d30-972a-0a8213ddc5ae_2000x1286.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sTN3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde71642-469c-4d30-972a-0a8213ddc5ae_2000x1286.png 424w, https://substackcdn.com/image/fetch/$s_!sTN3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde71642-469c-4d30-972a-0a8213ddc5ae_2000x1286.png 848w, https://substackcdn.com/image/fetch/$s_!sTN3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde71642-469c-4d30-972a-0a8213ddc5ae_2000x1286.png 1272w, https://substackcdn.com/image/fetch/$s_!sTN3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde71642-469c-4d30-972a-0a8213ddc5ae_2000x1286.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sTN3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde71642-469c-4d30-972a-0a8213ddc5ae_2000x1286.png" width="1456" height="936" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dde71642-469c-4d30-972a-0a8213ddc5ae_2000x1286.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:936,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:275790,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sTN3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde71642-469c-4d30-972a-0a8213ddc5ae_2000x1286.png 424w, https://substackcdn.com/image/fetch/$s_!sTN3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde71642-469c-4d30-972a-0a8213ddc5ae_2000x1286.png 848w, https://substackcdn.com/image/fetch/$s_!sTN3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde71642-469c-4d30-972a-0a8213ddc5ae_2000x1286.png 1272w, https://substackcdn.com/image/fetch/$s_!sTN3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde71642-469c-4d30-972a-0a8213ddc5ae_2000x1286.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this scenario, the observation of <code>ksoftirqd5</code> being at 100% CPU utilization indicates a condition wherein the CPU 5 is exhaustively engaged in the handling of interrupts. This state precludes the CPU from accommodating any further interrupt requests, thereby creating a consequential situation where network packets are being discarded. The saturated utilization of the CPU 5 for interrupt handling delineates a bottleneck in the system's capability to process additional interrupts, manifesting as network packet drops.</p><p>Just to double-check if this is a common configuration on Azure VMs, I checked the smp_affinity of the interrupts on another node belonging to the same VMSS, which did not have a VM Freeze event yet.</p><pre><code><code>for i in {24..32} ; do cat /proc/irq/$i/smp_affinity; done
40
80
10
80
04
20
08
01
02</code></code></pre><h2>IRQBalance</h2><p>We can see that the interrupts are balanced across all the CPUs. So what is wrong with our node? Why is it not balanced?</p><p>Let's check the <code>irqbalance</code> service status</p><pre><code><code>service irqbalance status
&#9679; irqbalance.service - irqbalance daemon
     Loaded: loaded 
     Active: active (running) 
       Docs: man:irqbalance(1)
</code></code></pre><p><code>irqbalance</code> is running. But we are definitely not seeing the interrupts distributed across all the CPUs.</p><pre><code><code>systemctl try-restart irqbalance
</code></code></pre><p>And right after restarting <code>irqbalance</code>, I could see that the IRQs are balanced across all the CPUs. Packet drop was gone, and CPU utilization was back to normal.</p><h2>Automated Mitigation</h2><p>Now that we know what was happening with VM Freeze events and packet drops, and we have a manual mitigation of the issue. It was time for an automated mitigation.</p><p>The available metrics can allow us to dig into the number of interrupts and group them by devices or cpu for example. But there is no available metric which can tell us about <code>smp_affinity</code> of the interrupts.</p><p>We already have a <code>Daemonset</code> running on all the nodes. So we decided to leverage that to automate the mitigation of the issue. So we extended our existing compute <code>Daemonset</code> to do the following:</p><ul><li><p>emit metrics about the smp_affinity of the interrupts</p></li><li><p>if the smp_affinity is not balanced, label the node</p></li></ul><p>Now we not only have metrics about the <code>smp_affinity</code> to have observability into the issue, but also we were labelling the node to set the stage for an automated mitigation.</p><p>The mitigation was simple at this point.</p><ul><li><p>A new <code>DaemonSet</code> with <code>nodeSelector</code> configured to select the problematic nodes with the label</p></li></ul><ul><li><p>run <code>nsenter -t 1 -m -n -i systemctl try-restart irqbalance</code> in the container of the new <code>Daemonset</code></p></li></ul><p>As soon as after a VM Freeze event was leaving our nodes in a problematic state we were able to mitigate the issue automatically.</p><p>This is just a temporary mitigation. As this is as far as we can go as users of a managed AKS cluster.</p><p>Azure is still investigating this issue, and we are waiting for a permanent fix. Also for most users working with limited network interrupts capacity might not be an issue at all. You can only identify this issue if you are taking the node to substantial network usage.</p><p>For this post I used the example of an 8 cores VM. But this issue can happen on any VM size. We observed it in 16,32 and 64 cores VMs. Bigger the node, bigger the issue was due to the proportion of the interrupts capacity.</p><h2>Conclusion</h2><p>This was a very interesting case. It was a great learning experience, specially a great reminder on where we are standing as users of a managed Kubernetes cluster.</p><ul><li><p>We are not living in a world where running a managed Kubernetes cluster is a set and forget thing. It's imperative to understand the cluster's internals and possess the capability to debug issues at the cluster level.</p></li><li><p>Managed services support is great, but it's not a replacement for the knowledge of the internals of the system you are running.</p></li></ul><p>I'm 100% confident that Azure engineering will solve this problem, and we will not have to deal with this issue anymore. But meanwhile if that happens we have to be prepared to dig into the internals of the system and be able to mitigate the issue ourselves.</p><p>[update] 10/10/2023: Azure engineering has identified the issue and is rolling out a fix in the next few weeks. This was a bug in <a href="https://bugs.launchpad.net/ubuntu/+source/irqbalance/+bug/2038573">irqbalance</a></p><p>The upstream irqbalance 1.9.0+ has been fixed.</p><ul><li><p>Bug Introduction: A specific patch <a href="https://github.com/Irqbalance/irqbalance/commit/e9e28114036a198b311ee17dd542540f749e6a68">e9e2811</a> initiated the issue.</p></li><li><p>Resolution: A subsequent fix has been provided <a href="https://github.com/Irqbalance/irqbalance/commit/2a66a666d3e202dec5b1a4309447e32d5f292871">2a66a66</a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.zmalik.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Optimized Infra! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>