{"authors":[{"name":"Juno Vale"}],"description":"Notes on the BEAM, backpressure, and the long tail of distributed systems.","feed_url":"https://cherrybomb.dev/t/kage/feed.json","home_page_url":"https://cherrybomb.dev/t/kage/","items":[{"content_html":"<p>A year ago I put property tests into a codebase that already had good\nexample-based coverage, mostly to find out whether the enthusiasm was\nwarranted. Here is what they actually caught.</p>\n<h2 id=\"the-score\">The score<a href=\"#the-score\" aria-label=\"Link to heading 'The score'\" data-heading-content=\"The score\" class=\"anchor\"></a></h2>\n<p>Eleven genuine defects. Nine of them were in code that parsed, normalised, or\nmerged data — the boring seams between systems. Two were in domain logic. None\nwere in the places I expected.</p>\n<p>The single most productive property was also the dullest:</p>\n<figure class=\"code-block\"><figcaption class=\"code-head\"><span class=\"code-icon code-icon--elixir\" aria-hidden=\"true\"></span><span class=\"code-lang\">elixir</span></figcaption><pre class=\"lumis\"><code class=\"language-elixir\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-function-call\">property</span> <span class=\"l-string\">&quot;a document survives a round trip&quot;</span> <span class=\"l-keyword\">do</span>\n</div><div class=\"l-line\" data-line=\"2\">  <span class=\"l-function-call\">check</span> <span class=\"l-function-call\">all</span> <span class=\"l-variable\">document</span> <span class=\"l-operator\">&lt;-</span> <span class=\"l-function-call\">document_generator</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">)</span> <span class=\"l-keyword\">do</span>\n</div><div class=\"l-line\" data-line=\"3\">    <span class=\"l-function-call\">assert</span> <span class=\"l-variable\">document</span> <span class=\"l-operator\">==</span> <span class=\"l-variable\">document</span> <span class=\"l-operator\">|&gt;</span> <span class=\"l-module\">Codec</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">encode</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">)</span> <span class=\"l-operator\">|&gt;</span> <span class=\"l-module\">Codec</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">decode!</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"4\">  <span class=\"l-keyword\">end</span>\n</div><div class=\"l-line\" data-line=\"5\"><span class=\"l-keyword\">end</span>\n</div></code></pre></figure>\n<p>That one found four bugs on its own: an empty tag list that came back as <code>nil</code>,\na timestamp that lost microseconds, a nested map whose key order the encoder\ndepended on, and a Unicode normalisation difference that only appeared for\nstrings containing combining characters.</p>\n<p>Every one of those would have been possible to write as an example test. The\npoint is that nobody did, for years, because nobody thought of them.</p>\n<h2 id=\"the-properties-worth-writing\">The properties worth writing<a href=\"#the-properties-worth-writing\" aria-label=\"Link to heading 'The properties worth writing'\" data-heading-content=\"The properties worth writing\" class=\"anchor\"></a></h2>\n<p>Four shapes covered nearly everything useful:</p>\n<p><strong>Round trips.</strong> <code>decode(encode(x)) == x</code>. Cheap, and it finds encoder bugs\nexample tests never reach.</p>\n<p><strong>Invariants.</strong> Something that must be true of every output, regardless of\ninput. Ours: a stock projection never disagrees with a fold over the same\nevents.</p>\n<p><strong>Oracles.</strong> A second, slower, obviously correct implementation to compare\nagainst. We had a naive O(n²) deduplicator; the fast one had a bug at exactly\nthe block boundary.</p>\n<p><strong>Idempotence.</strong> <code>f(f(x)) == f(x)</code> for anything that normalises. This found a\nsanitiser that stripped one layer of nesting per call.</p>\n<h2 id=\"where-they-were-not-worth-it\">Where they were not worth it<a href=\"#where-they-were-not-worth-it\" aria-label=\"Link to heading 'Where they were not worth it'\" data-heading-content=\"Where they were not worth it\" class=\"anchor\"></a></h2>\n<p>I wrote properties for our HTTP handlers and deleted them a month later. The\ngenerators were more code than the handlers, most of the interesting behaviour\nwas in the dependencies, and every failure took twenty minutes to interpret.\nExample tests read better there and caught the same things.</p>\n<p>I also stopped trying to shrink my way to understanding. <code>stream_data</code>'s\nshrinking is good, but a minimal counterexample for a complex generator is\nstill a puzzle. Now, when a property fails, my first move is to write the\nshrunk case down as an ordinary test with a name. The property found it; the\nexample test explains it.</p>\n<p>That split — properties to search, examples to document — is the habit I would\nkeep if I had to give up everything else from this year.</p>","date_published":"2026-05-06T00:00:00Z","id":"https://cherrybomb.dev/t/kage/a-year-of-property-testing/","summary":"What a year of stream_data actually caught, and the properties that turned out to be worth writing.","tags":["testing","property-testing","elixir"],"title":"A Year of Property Testing","url":"https://cherrybomb.dev/t/kage/a-year-of-property-testing/"},{"content_html":"<p>When I join a BEAM codebase, the first file I open is <code>application.ex</code>. Not the\nrouter, not the schema — the supervision tree. It is the only place where the\nsystem states, in code, what it thinks should happen when things break.</p>\n<figure class=\"code-block\"><figcaption class=\"code-head\"><span class=\"code-icon code-icon--elixir\" aria-hidden=\"true\"></span><span class=\"code-lang\">elixir</span></figcaption><pre class=\"lumis\"><code class=\"language-elixir\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-keyword-function\">def</span> <span class=\"l-function\">start</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-comment\">_type</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-comment\">_args</span><span class=\"l-punctuation-bracket\">)</span> <span class=\"l-keyword\">do</span>\n</div><div class=\"l-line\" data-line=\"2\">  <span class=\"l-variable\">children</span> <span class=\"l-operator\">=</span> <span class=\"l-punctuation-bracket\">[</span>\n</div><div class=\"l-line\" data-line=\"3\">    <span class=\"l-module\">Ledgerbeam.Repo</span><span class=\"l-punctuation-delimiter\">,</span>\n</div><div class=\"l-line\" data-line=\"4\">    <span class=\"l-punctuation-bracket\">{</span><span class=\"l-module\">Phoenix.PubSub</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">name: </span><span class=\"l-module\">Ledgerbeam.PubSub</span><span class=\"l-punctuation-bracket\">}</span><span class=\"l-punctuation-delimiter\">,</span>\n</div><div class=\"l-line\" data-line=\"5\">    <span class=\"l-punctuation-bracket\">{</span><span class=\"l-module\">Finch</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">name: </span><span class=\"l-module\">Ledgerbeam.HTTP</span><span class=\"l-punctuation-bracket\">}</span><span class=\"l-punctuation-delimiter\">,</span>\n</div><div class=\"l-line\" data-line=\"6\">    <span class=\"l-module\">Ledgerbeam.Inventory.Supervisor</span><span class=\"l-punctuation-delimiter\">,</span>\n</div><div class=\"l-line\" data-line=\"7\">    <span class=\"l-module\">Ledgerbeam.Ingest.Pipeline</span><span class=\"l-punctuation-delimiter\">,</span>\n</div><div class=\"l-line\" data-line=\"8\">    <span class=\"l-module\">LedgerbeamWeb.Endpoint</span>\n</div><div class=\"l-line\" data-line=\"9\">  <span class=\"l-punctuation-bracket\">]</span>\n</div><div class=\"l-line\" data-line=\"10\">\n</div><div class=\"l-line\" data-line=\"11\">  <span class=\"l-module\">Supervisor</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">start_link</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">children</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">strategy: </span><span class=\"l-string-special-symbol\">:one_for_one</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">name: </span><span class=\"l-module\">Ledgerbeam.Supervisor</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"12\"><span class=\"l-keyword\">end</span>\n</div></code></pre></figure>\n<p>Six lines, and each one is a claim.</p>\n<h2 id=\"what-the-order-tells-you\">What the order tells you<a href=\"#what-the-order-tells-you\" aria-label=\"Link to heading 'What the order tells you'\" data-heading-content=\"What the order tells you\" class=\"anchor\"></a></h2>\n<p>Children start in order and stop in reverse. So this tree claims the endpoint\nshould be the last thing up and the first thing down — traffic only arrives\nonce the repo, the pipeline, and the domain supervisors are ready, and traffic\nstops before they go away.</p>\n<p>That is almost always what you want, and it is worth checking, because the\nfailure mode is subtle: put the endpoint too early and you accept requests\nagainst a half-built system for a few hundred milliseconds on every deploy.</p>\n<h2 id=\"what-the-strategy-tells-you\">What the strategy tells you<a href=\"#what-the-strategy-tells-you\" aria-label=\"Link to heading 'What the strategy tells you'\" data-heading-content=\"What the strategy tells you\" class=\"anchor\"></a></h2>\n<p><code>:one_for_one</code> says these six things are independent — if the ingest pipeline\ndies, the endpoint should carry on. Is that true? For us it was: the site stays\nup and serves reads while ingestion restarts.</p>\n<p>But look for a <code>:one_for_all</code> and ask what it is protecting. It says the\nchildren share state that cannot survive one of them restarting, which is a\nreal thing, and also a design smell worth a second look.</p>\n<p><code>:rest_for_one</code> is the interesting one: it says there is a dependency order\nhere, and everything after this child needs it. That is a map of your real\ncoupling, drawn by someone who had to think about it.</p>\n<h2 id=\"where-the-tree-lies\">Where the tree lies<a href=\"#where-the-tree-lies\" aria-label=\"Link to heading 'Where the tree lies'\" data-heading-content=\"Where the tree lies\" class=\"anchor\"></a></h2>\n<p>The tree is a set of beliefs, and outages are where the beliefs were wrong. Two\nthat catch people:</p>\n<p><strong>A process that restarts fine but reconnects slowly.</strong> The supervisor's job is\ndone in microseconds; the connection pool underneath takes eight seconds. The\ntree looks healthy and the system is not.</p>\n<p><strong><code>restart: :temporary</code> on something that matters.</strong> It will not come back, ever,\nand the supervisor will not tell you. This is correct for a one-shot task and\nquietly catastrophic on a consumer.</p>\n<p>Neither shows up in the diagram. Both show up in <code>:observer</code>, and in the first\nfive minutes of an incident, which is why I read the tree before I read\nanything else.</p>","date_published":"2026-01-20T00:00:00Z","id":"https://cherrybomb.dev/t/kage/reading-a-supervision-tree-like-a-map/","summary":"A supervision tree is a diagram of what your system believes about failure. Most outages are places where that belief was wrong.","tags":["elixir","otp","operations"],"title":"Reading a Supervision Tree Like a Map","url":"https://cherrybomb.dev/t/kage/reading-a-supervision-tree-like-a-map/"},{"content_html":"<p>Every so often I redo the string-date-parsing kata. Not because I have\nforgotten how to parse a date, but because it is the shortest exercise I know\nthat punishes vagueness.</p>\n<p>The brief: given a string, return a date. Start with <code>&quot;2025-10-15&quot;</code>.</p>\n<figure class=\"code-block\"><figcaption class=\"code-head\"><span class=\"code-icon code-icon--elixir\" aria-hidden=\"true\"></span><span class=\"code-lang\">elixir</span></figcaption><pre class=\"lumis\"><code class=\"language-elixir\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-keyword-function\">def</span> <span class=\"l-function\">parse</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">input</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">do: </span><span class=\"l-module\">Date</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">from_iso8601</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">input</span><span class=\"l-punctuation-bracket\">)</span>\n</div></code></pre></figure>\n<p>Four minutes in, you are done. Then the examples arrive.</p>\n<h2 id=\"the-examples\">The examples<a href=\"#the-examples\" aria-label=\"Link to heading 'The examples'\" data-heading-content=\"The examples\" class=\"anchor\"></a></h2>\n<pre class=\"lumis\"><code class=\"language-plaintext\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\">&quot;2025-10-15&quot;     the easy one\n</div><div class=\"l-line\" data-line=\"2\">&quot;15/10/2025&quot;     day first\n</div><div class=\"l-line\" data-line=\"3\">&quot;10/15/2025&quot;     month first\n</div><div class=\"l-line\" data-line=\"4\">&quot;15 Oct 2025&quot;    names, abbreviated\n</div><div class=\"l-line\" data-line=\"5\">&quot;October 15th&quot;   no year, ordinal suffix\n</div><div class=\"l-line\" data-line=\"6\">&quot;yesterday&quot;      relative to what?\n</div><div class=\"l-line\" data-line=\"7\">&quot;&quot; and nil       the caller has a bug\n</div></code></pre>\n<p>Nothing here is hard. What is hard is that <code>&quot;01/02/2025&quot;</code> is genuinely\nambiguous, and you cannot resolve it inside the parser. It is January 2nd or\nFebruary 1st depending on a fact the string does not contain.</p>\n<p>The kata's real lesson is that this is where the design happens. Every attempt\nI make eventually arrives at the same shape: the ambiguity becomes a parameter.</p>\n<figure class=\"code-block\"><figcaption class=\"code-head\"><span class=\"code-icon code-icon--elixir\" aria-hidden=\"true\"></span><span class=\"code-lang\">elixir</span></figcaption><pre class=\"lumis\"><code class=\"language-elixir\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-constant\">@</span><span class=\"l-constant\">type </span><span class=\"l-variable\">locale_order</span> <span class=\"l-operator\">::</span> <span class=\"l-string-special-symbol\">:day_first</span> <span class=\"l-operator\">|</span> <span class=\"l-string-special-symbol\">:month_first</span>\n</div><div class=\"l-line\" data-line=\"2\">\n</div><div class=\"l-line\" data-line=\"3\"><span class=\"l-constant\">@</span><span class=\"l-constant\">spec </span><span class=\"l-function-call\">parse</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-module\">String</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">t</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-function-call\">keyword</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-bracket\">)</span> <span class=\"l-operator\">::</span> <span class=\"l-punctuation-bracket\">{</span><span class=\"l-string-special-symbol\">:ok</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-module\">Date</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">t</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-bracket\">}</span> <span class=\"l-operator\">|</span> <span class=\"l-punctuation-bracket\">{</span><span class=\"l-string-special-symbol\">:error</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-function-call\">term</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-bracket\">}</span>\n</div><div class=\"l-line\" data-line=\"4\"><span class=\"l-keyword-function\">def</span> <span class=\"l-function\">parse</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">input</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-variable\">opts</span> <span class=\"l-operator\">\\\\</span> <span class=\"l-punctuation-bracket\">[</span><span class=\"l-punctuation-bracket\">]</span><span class=\"l-punctuation-bracket\">)</span> <span class=\"l-keyword\">do</span>\n</div><div class=\"l-line\" data-line=\"5\">  <span class=\"l-variable\">order</span> <span class=\"l-operator\">=</span> <span class=\"l-module\">Keyword</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">get</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">opts</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">:order</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">:day_first</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"6\">  <span class=\"l-variable\">today</span> <span class=\"l-operator\">=</span> <span class=\"l-module\">Keyword</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">get</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">opts</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">:today</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-module\">Date</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">utc_today</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"7\">\n</div><div class=\"l-line\" data-line=\"8\">  <span class=\"l-variable\">input</span>\n</div><div class=\"l-line\" data-line=\"9\">  <span class=\"l-operator\">|&gt;</span> <span class=\"l-module\">String</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">trim</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"10\">  <span class=\"l-operator\">|&gt;</span> <span class=\"l-function-call\">attempt</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">[</span><span class=\"l-operator\">&amp;</span><span class=\"l-function\">iso</span><span class=\"l-operator\">/</span><span class=\"l-operator\">1</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-operator\">&amp;</span><span class=\"l-function-call\">numeric</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-operator\">&amp;</span><span class=\"l-operator\">1</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-variable\">order</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-operator\">&amp;</span><span class=\"l-function\">named</span><span class=\"l-operator\">/</span><span class=\"l-operator\">1</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-operator\">&amp;</span><span class=\"l-function-call\">relative</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-operator\">&amp;</span><span class=\"l-operator\">1</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-variable\">today</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-bracket\">]</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"11\"><span class=\"l-keyword\">end</span>\n</div></code></pre></figure>\n<p>Two details that took me several attempts to arrive at:</p>\n<p><strong><code>today</code> is an argument.</strong> A parser that reads the clock cannot be tested for\n<code>&quot;yesterday&quot;</code> without either freezing time or accepting flaky tests. Passing the\ndate in makes the whole thing a pure function, and the tests get boring.</p>\n<p><strong>The strategies are a list.</strong> Each one returns <code>{:ok, date}</code> or <code>:error</code>, and\n<code>attempt/2</code> walks them in order. Adding a format is adding a function, and the\nprecedence is visible in one place rather than distributed across a <code>cond</code>.</p>\n<h2 id=\"why-it-stays-useful\">Why it stays useful<a href=\"#why-it-stays-useful\" aria-label=\"Link to heading 'Why it stays useful'\" data-heading-content=\"Why it stays useful\" class=\"anchor\"></a></h2>\n<p>Most katas train you to write code. This one trains you to notice that the\nrequirements are underspecified, and to put the missing decision somewhere the\ncaller can see it. That skill transfers to almost everything, which is more\nthan I can say for FizzBuzz.</p>","date_published":"2025-10-15T00:00:00Z","id":"https://cherrybomb.dev/t/kage/the-kata-i-keep-coming-back-to/","summary":"Parsing dates from strings looks trivial for about four minutes, which is what makes it a good exercise.","tags":["testing","kata","elixir"],"title":"The Kata I Keep Coming Back To","url":"https://cherrybomb.dev/t/kage/the-kata-i-keep-coming-back-to/"},{"content_html":"<p>I came to event sourcing expecting an audit log and got something more useful:\na reason to delete about a third of the inventory service.</p>\n<h2 id=\"the-code-that-went-away\">The code that went away<a href=\"#the-code-that-went-away\" aria-label=\"Link to heading 'The code that went away'\" data-heading-content=\"The code that went away\" class=\"anchor\"></a></h2>\n<p>The old service had a table of stock levels and a great deal of code protecting\nit. Compensating updates when a shipment was cancelled. A nightly reconciliation\njob. A <code>stock_adjustments</code> table nobody could explain, holding rows that existed\nonly to correct earlier rows.</p>\n<p>All of that was there because the current level was the only thing we stored.\nWhen it went wrong, we had no way to ask <em>how</em>, so we wrote code to guess.</p>\n<p>Storing decisions instead of state removed the guessing:</p>\n<figure class=\"code-block\"><figcaption class=\"code-head\"><span class=\"code-icon code-icon--elixir\" aria-hidden=\"true\"></span><span class=\"code-lang\">elixir</span></figcaption><pre class=\"lumis\"><code class=\"language-elixir\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-keyword-function\">defmodule</span> <span class=\"l-module\">Inventory.Events</span> <span class=\"l-keyword\">do</span>\n</div><div class=\"l-line\" data-line=\"2\">  <span class=\"l-keyword-function\">defstruct</span> <span class=\"l-module\">StockReceived</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-punctuation-bracket\">[</span><span class=\"l-string-special-symbol\">:sku</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">:quantity</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">:received_at</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">:purchase_order</span><span class=\"l-punctuation-bracket\">]</span>\n</div><div class=\"l-line\" data-line=\"3\">  <span class=\"l-keyword-function\">defstruct</span> <span class=\"l-module\">StockPicked</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-punctuation-bracket\">[</span><span class=\"l-string-special-symbol\">:sku</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">:quantity</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">:picked_at</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">:order_id</span><span class=\"l-punctuation-bracket\">]</span>\n</div><div class=\"l-line\" data-line=\"4\">  <span class=\"l-keyword-function\">defstruct</span> <span class=\"l-module\">StockWrittenOff</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-punctuation-bracket\">[</span><span class=\"l-string-special-symbol\">:sku</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">:quantity</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">:reason</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">:approved_by</span><span class=\"l-punctuation-bracket\">]</span>\n</div><div class=\"l-line\" data-line=\"5\"><span class=\"l-keyword\">end</span>\n</div></code></pre></figure>\n<p>The level is now a fold:</p>\n<figure class=\"code-block\"><figcaption class=\"code-head\"><span class=\"code-icon code-icon--elixir\" aria-hidden=\"true\"></span><span class=\"code-lang\">elixir</span></figcaption><pre class=\"lumis\"><code class=\"language-elixir\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-keyword-function\">def</span> <span class=\"l-function\">level</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">sku</span><span class=\"l-punctuation-bracket\">)</span> <span class=\"l-keyword\">do</span>\n</div><div class=\"l-line\" data-line=\"2\">  <span class=\"l-variable\">sku</span>\n</div><div class=\"l-line\" data-line=\"3\">  <span class=\"l-operator\">|&gt;</span> <span class=\"l-module\">Store</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">stream</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"4\">  <span class=\"l-operator\">|&gt;</span> <span class=\"l-module\">Enum</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">reduce</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-number\">0</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-keyword\">fn</span>\n</div><div class=\"l-line\" data-line=\"5\">    <span class=\"l-punctuation-special\">%</span><span class=\"l-module\">StockReceived</span><span class=\"l-punctuation-bracket\">{</span><span class=\"l-string-special-symbol\">quantity: </span><span class=\"l-variable\">n</span><span class=\"l-punctuation-bracket\">}</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-variable\">level</span> <span class=\"l-operator\">-&gt;</span> <span class=\"l-variable\">level</span> <span class=\"l-operator\">+</span> <span class=\"l-variable\">n</span>\n</div><div class=\"l-line\" data-line=\"6\">    <span class=\"l-punctuation-special\">%</span><span class=\"l-module\">StockPicked</span><span class=\"l-punctuation-bracket\">{</span><span class=\"l-string-special-symbol\">quantity: </span><span class=\"l-variable\">n</span><span class=\"l-punctuation-bracket\">}</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-variable\">level</span> <span class=\"l-operator\">-&gt;</span> <span class=\"l-variable\">level</span> <span class=\"l-operator\">-</span> <span class=\"l-variable\">n</span>\n</div><div class=\"l-line\" data-line=\"7\">    <span class=\"l-punctuation-special\">%</span><span class=\"l-module\">StockWrittenOff</span><span class=\"l-punctuation-bracket\">{</span><span class=\"l-string-special-symbol\">quantity: </span><span class=\"l-variable\">n</span><span class=\"l-punctuation-bracket\">}</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-variable\">level</span> <span class=\"l-operator\">-&gt;</span> <span class=\"l-variable\">level</span> <span class=\"l-operator\">-</span> <span class=\"l-variable\">n</span>\n</div><div class=\"l-line\" data-line=\"8\">  <span class=\"l-keyword\">end</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"9\"><span class=\"l-keyword\">end</span>\n</div></code></pre></figure>\n<p>The reconciliation job had nothing left to reconcile. The adjustments table\nbecame a write-off event with a <code>reason</code> and an <code>approved_by</code>, which is what\neveryone had wanted from it in the first place.</p>\n<h2 id=\"what-it-did-not-fix\">What it did not fix<a href=\"#what-it-did-not-fix\" aria-label=\"Link to heading 'What it did not fix'\" data-heading-content=\"What it did not fix\" class=\"anchor\"></a></h2>\n<p>It is worth being clear about what event sourcing cost us, because the posts\nthat skip this part are the ones that get people into trouble.</p>\n<p><strong>Versioning is forever.</strong> The moment an event is persisted, its shape is a\npublic API. We now write upcasters, and we treat an event struct with the same\ncare as a database migration.</p>\n<p><strong>Projections drift.</strong> Every read model is eventually consistent with the\nstream, and every part of the UI that pretended otherwise had to learn to say\n&quot;pending&quot;.</p>\n<p><strong>Streams grow.</strong> A fold over five years of movements for a fast-moving SKU is\nnot free. Snapshots solve it, and snapshots are cache invalidation wearing a\nhat.</p>\n<p>Those are real costs. I would pay them again for the deletions alone. The\nservice went from a thing we were afraid to touch on a Friday to one where the\nquestion &quot;how did this SKU get to negative four&quot; has a literal answer.</p>","date_published":"2025-07-09T00:00:00Z","id":"https://cherrybomb.dev/t/kage/event-sourcing-taught-me-to-delete-code/","summary":"The best thing event sourcing did for our codebase was make a whole category of defensive code obviously unnecessary.","tags":["event-sourcing","design","elixir"],"title":"Event Sourcing Taught Me to Delete Code","url":"https://cherrybomb.dev/t/kage/event-sourcing-taught-me-to-delete-code/"},{"content_html":"<p>The received wisdom about NIFs is that they can bring down the entire VM, which\nis true, and that you should therefore avoid them, which does not follow.</p>\n<p>We needed SimHash over a few million documents. In pure Elixir it was about\n40ms per document, which was not going to work. In Rust, via\n<a href=\"https://github.com/rusterlium/rustler\">Rustler</a>, it was under a millisecond.\nThat is the kind of gap worth taking a risk for — provided you know which risks\nyou are taking.</p>\n<h2 id=\"rule-one-never-block-a-scheduler\">Rule one: never block a scheduler<a href=\"#rule-one-never-block-a-scheduler\" aria-label=\"Link to heading 'Rule one: never block a scheduler'\" data-heading-content=\"Rule one: never block a scheduler\" class=\"anchor\"></a></h2>\n<p>A NIF that runs longer than about a millisecond starves the scheduler it runs\non. The whole preemption model of the BEAM assumes it can take a process off a\ncore; a native function ignores that assumption entirely.</p>\n<p>Rustler makes the fix a single annotation:</p>\n<figure class=\"code-block\"><figcaption class=\"code-head\"><span class=\"code-icon code-icon--rust\" aria-hidden=\"true\"></span><span class=\"code-lang\">rust</span></figcaption><pre class=\"lumis\"><code class=\"language-rust\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-punctuation-special\">#</span><span class=\"l-punctuation-bracket\">[</span><span class=\"l-module\">rustler</span><span class=\"l-punctuation-delimiter\">::</span><span class=\"l-function-macro\">nif</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">schedule</span> <span class=\"l-operator\">=</span> <span class=\"l-string\">&quot;DirtyCpu&quot;</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-bracket\">]</span>\n</div><div class=\"l-line\" data-line=\"2\"><span class=\"l-keyword-function\">fn</span> <span class=\"l-function\">simhash</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable-parameter\">text</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-operator\">&amp;</span><span class=\"l-type-builtin\">str</span><span class=\"l-punctuation-bracket\">)</span> <span class=\"l-punctuation-delimiter\">-&gt;</span> <span class=\"l-type-builtin\">u64</span> <span class=\"l-punctuation-bracket\">{</span>\n</div><div class=\"l-line\" data-line=\"3\">    <span class=\"l-function-call\">hash_tokens</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-function-call\">tokenize</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">text</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"4\"><span class=\"l-punctuation-bracket\">}</span>\n</div></code></pre></figure>\n<p>Dirty schedulers exist precisely for work that cannot be preempted. Use them\nfor anything you have not personally measured under a millisecond, and measure\nbefore you decide you are the exception.</p>\n<h2 id=\"rule-two-do-not-panic\">Rule two: do not panic<a href=\"#rule-two-do-not-panic\" aria-label=\"Link to heading 'Rule two: do not panic'\" data-heading-content=\"Rule two: do not panic\" class=\"anchor\"></a></h2>\n<p>A Rust panic across the NIF boundary takes the VM with it. Every fallible path\nmust return an error term instead:</p>\n<figure class=\"code-block\"><figcaption class=\"code-head\"><span class=\"code-icon code-icon--rust\" aria-hidden=\"true\"></span><span class=\"code-lang\">rust</span></figcaption><pre class=\"lumis\"><code class=\"language-rust\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-punctuation-special\">#</span><span class=\"l-punctuation-bracket\">[</span><span class=\"l-module\">rustler</span><span class=\"l-punctuation-delimiter\">::</span><span class=\"l-function-macro\">nif</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">schedule</span> <span class=\"l-operator\">=</span> <span class=\"l-string\">&quot;DirtyCpu&quot;</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-bracket\">]</span>\n</div><div class=\"l-line\" data-line=\"2\"><span class=\"l-keyword-function\">fn</span> <span class=\"l-function\">parse</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable-parameter\">input</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-operator\">&amp;</span><span class=\"l-type-builtin\">str</span><span class=\"l-punctuation-bracket\">)</span> <span class=\"l-punctuation-delimiter\">-&gt;</span> <span class=\"l-type\">Result</span><span class=\"l-punctuation-bracket\">&lt;</span><span class=\"l-type\">Document</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-type\">Error</span><span class=\"l-punctuation-bracket\">&gt;</span> <span class=\"l-punctuation-bracket\">{</span>\n</div><div class=\"l-line\" data-line=\"3\">    <span class=\"l-module\">serde_json</span><span class=\"l-punctuation-delimiter\">::</span><span class=\"l-function-call\">from_str</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">input</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-delimiter\">.</span><span class=\"l-function-call\">map_err</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">|</span><span class=\"l-variable-parameter\">e</span><span class=\"l-punctuation-bracket\">|</span> <span class=\"l-type\">Error</span><span class=\"l-punctuation-delimiter\">::</span><span class=\"l-constant\">Term</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-type\">Box</span><span class=\"l-punctuation-delimiter\">::</span><span class=\"l-function-call\">new</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">e</span><span class=\"l-punctuation-delimiter\">.</span><span class=\"l-function-call\">to_string</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"4\"><span class=\"l-punctuation-bracket\">}</span>\n</div></code></pre></figure>\n<p>Then the Elixir side gets an ordinary tagged tuple, and an ordinary supervisor\nhandles it:</p>\n<figure class=\"code-block\"><figcaption class=\"code-head\"><span class=\"code-icon code-icon--elixir\" aria-hidden=\"true\"></span><span class=\"code-lang\">elixir</span></figcaption><pre class=\"lumis\"><code class=\"language-elixir\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-keyword\">case</span> <span class=\"l-module\">Native</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">parse</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-variable\">payload</span><span class=\"l-punctuation-bracket\">)</span> <span class=\"l-keyword\">do</span>\n</div><div class=\"l-line\" data-line=\"2\">  <span class=\"l-punctuation-bracket\">{</span><span class=\"l-string-special-symbol\">:ok</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-variable\">document</span><span class=\"l-punctuation-bracket\">}</span> <span class=\"l-operator\">-&gt;</span> <span class=\"l-punctuation-bracket\">{</span><span class=\"l-string-special-symbol\">:ok</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-variable\">document</span><span class=\"l-punctuation-bracket\">}</span>\n</div><div class=\"l-line\" data-line=\"3\">  <span class=\"l-punctuation-bracket\">{</span><span class=\"l-string-special-symbol\">:error</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-variable\">reason</span><span class=\"l-punctuation-bracket\">}</span> <span class=\"l-operator\">-&gt;</span> <span class=\"l-module\">Logger</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">warning</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-string\">&quot;unparseable payload: </span><span class=\"l-string-special\">#{</span><span class=\"l-variable\">reason</span><span class=\"l-string-special\">}</span><span class=\"l-string\">&quot;</span><span class=\"l-punctuation-bracket\">)</span><span class=\"l-punctuation-delimiter\">;</span> <span class=\"l-string-special-symbol\">:skip</span>\n</div><div class=\"l-line\" data-line=\"4\"><span class=\"l-keyword\">end</span>\n</div></code></pre></figure>\n<p>Deny panics at the crate level and the compiler helps you keep the rule:</p>\n<figure class=\"code-block\"><figcaption class=\"code-head\"><span class=\"code-icon code-icon--file\" aria-hidden=\"true\"></span><span class=\"code-lang\">toml</span></figcaption><pre class=\"lumis\"><code class=\"language-toml\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-punctuation-bracket\">[</span><span class=\"l-property\">lints</span><span class=\"l-punctuation-delimiter\">.</span><span class=\"l-property\">clippy</span><span class=\"l-punctuation-bracket\">]</span>\n</div><div class=\"l-line\" data-line=\"2\"><span class=\"l-property\">unwrap_used</span> <span class=\"l-operator\">=</span> <span class=\"l-string\">&quot;deny&quot;</span>\n</div><div class=\"l-line\" data-line=\"3\"><span class=\"l-property\">expect_used</span> <span class=\"l-operator\">=</span> <span class=\"l-string\">&quot;deny&quot;</span>\n</div></code></pre></figure>\n<h2 id=\"rule-three-keep-the-boundary-boring\">Rule three: keep the boundary boring<a href=\"#rule-three-keep-the-boundary-boring\" aria-label=\"Link to heading 'Rule three: keep the boundary boring'\" data-heading-content=\"Rule three: keep the boundary boring\" class=\"anchor\"></a></h2>\n<p>The NIF should do one computational thing and hold no state. No global\nmutable structures, no threads you spawn yourself, no callbacks back into\nElixir. Everything that needs supervision, retries, or lifecycle belongs on the\nBEAM side where those things already work.</p>\n<figure>\n<img src=\"/t/kage/images/nif-boundary.svg\" alt=\"Two boxes: the BEAM side holds supervision, retries and state; the Rust side holds one stateless computation\">\n<figcaption>The whole architecture, honestly.</figcaption>\n</figure>\n<div class=\"markdown-alert markdown-alert-tip\">\n<p class=\"markdown-alert-title\">Where the fear lives</p>\n<p>Every NIF horror story is a violation of one of the three rules. Check the\nstory against the rules before checking your code against the story.</p>\n</div>\n<p>Three rules, and the fear mostly goes away. What is left is a normal\nengineering tradeoff: a build dependency and a compile step, in exchange for\ntwo orders of magnitude.</p>","date_published":"2025-04-22T00:00:00Z","id":"https://cherrybomb.dev/t/kage/a-rust-nif-without-the-fear/","summary":"A NIF can take down the whole VM. Here is the small set of habits that keep that from being your problem.","tags":["elixir","rust","nif"],"title":"A Rust NIF Without the Fear","url":"https://cherrybomb.dev/t/kage/a-rust-nif-without-the-fear/"},{"content_html":"<p>We spent a week tuning a Broadway pipeline that kept falling behind, and the\nfix turned out not to be a number at all.</p>\n<p>The pipeline ingested webhook deliveries from a payments provider. Under normal\nload it kept up comfortably. During a merchant's flash sale it did not, and the\nqueue depth climbed until the delivery provider started retrying, which added\nload, which climbed the queue further.</p>\n<p>The instinct is to add concurrency:</p>\n<figure class=\"code-block\"><figcaption class=\"code-head\"><span class=\"code-icon code-icon--elixir\" aria-hidden=\"true\"></span><span class=\"code-lang\">elixir</span></figcaption><pre class=\"lumis\"><code class=\"language-elixir\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-keyword-function\">def</span> <span class=\"l-function\">start_link</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-comment\">_opts</span><span class=\"l-punctuation-bracket\">)</span> <span class=\"l-keyword\">do</span>\n</div><div class=\"l-line\" data-line=\"2\">  <span class=\"l-module\">Broadway</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">start_link</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-constant-builtin\">__MODULE__</span><span class=\"l-punctuation-delimiter\">,</span>\n</div><div class=\"l-line\" data-line=\"3\">    <span class=\"l-string-special-symbol\">name: </span><span class=\"l-constant-builtin\">__MODULE__</span><span class=\"l-punctuation-delimiter\">,</span>\n</div><div class=\"l-line\" data-line=\"4\">    <span class=\"l-string-special-symbol\">producer: </span><span class=\"l-punctuation-bracket\">[</span><span class=\"l-string-special-symbol\">module: </span><span class=\"l-punctuation-bracket\">{</span><span class=\"l-module\">SQSProducer</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-punctuation-bracket\">[</span><span class=\"l-punctuation-bracket\">]</span><span class=\"l-punctuation-bracket\">}</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string-special-symbol\">concurrency: </span><span class=\"l-number\">4</span><span class=\"l-punctuation-bracket\">]</span><span class=\"l-punctuation-delimiter\">,</span>\n</div><div class=\"l-line\" data-line=\"5\">    <span class=\"l-string-special-symbol\">processors: </span><span class=\"l-punctuation-bracket\">[</span><span class=\"l-string-special-symbol\">default: </span><span class=\"l-punctuation-bracket\">[</span><span class=\"l-string-special-symbol\">concurrency: </span><span class=\"l-number\">80</span><span class=\"l-punctuation-bracket\">]</span><span class=\"l-punctuation-bracket\">]</span>\n</div><div class=\"l-line\" data-line=\"6\">  <span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"7\"><span class=\"l-keyword\">end</span>\n</div></code></pre></figure>\n<p>Eighty processors did make the queue drain. It also pushed the database to the\npoint where unrelated requests started timing out. We had not removed the\nbottleneck, we had moved it somewhere with worse failure behaviour.</p>\n<h2 id=\"the-question-nobody-had-asked\">The question nobody had asked<a href=\"#the-question-nobody-had-asked\" aria-label=\"Link to heading 'The question nobody had asked'\" data-heading-content=\"The question nobody had asked\" class=\"anchor\"></a></h2>\n<p>What should happen when deliveries arrive faster than we can durably record\nthem?</p>\n<p>There are only a few honest answers, and each of them is a product decision:</p>\n<ul>\n<li><strong>Slow the producer down.</strong> Correct when the upstream can wait. The payments\nprovider retried with backoff, so it could.</li>\n<li><strong>Shed load.</strong> Correct when late data is worthless. Ours was not.</li>\n<li><strong>Buffer, and accept that recovery takes time.</strong> Correct when the burst is\nbounded and you can afford the storage.</li>\n<li><strong>Degrade the work itself</strong> — record the delivery now, enrich it later.</li>\n</ul>\n<p>We picked the last one, and the pipeline stopped being a tuning problem:</p>\n<figure class=\"code-block\"><figcaption class=\"code-head\"><span class=\"code-icon code-icon--elixir\" aria-hidden=\"true\"></span><span class=\"code-lang\">elixir</span></figcaption><pre class=\"lumis\"><code class=\"language-elixir\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-constant\">@</span><span class=\"l-constant\">impl </span><span class=\"l-boolean\">true</span>\n</div><div class=\"l-line\" data-line=\"2\"><span class=\"l-keyword-function\">def</span> <span class=\"l-function\">handle_message</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-comment\">_processor</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-variable\">message</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-comment\">_context</span><span class=\"l-punctuation-bracket\">)</span> <span class=\"l-keyword\">do</span>\n</div><div class=\"l-line\" data-line=\"3\">  <span class=\"l-variable\">message</span>\n</div><div class=\"l-line\" data-line=\"4\">  <span class=\"l-operator\">|&gt;</span> <span class=\"l-module\">Message</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">update_data</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-operator\">&amp;</span><span class=\"l-module\">Delivery</span><span class=\"l-operator\">.</span><span class=\"l-function\">parse!</span><span class=\"l-operator\">/</span><span class=\"l-operator\">1</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"5\">  <span class=\"l-operator\">|&gt;</span> <span class=\"l-module\">Message</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">put_batcher</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-string-special-symbol\">:record</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"6\"><span class=\"l-keyword\">end</span>\n</div><div class=\"l-line\" data-line=\"7\">\n</div><div class=\"l-line\" data-line=\"8\"><span class=\"l-constant\">@</span><span class=\"l-constant\">impl </span><span class=\"l-boolean\">true</span>\n</div><div class=\"l-line\" data-line=\"9\"><span class=\"l-keyword-function\">def</span> <span class=\"l-function\">handle_batch</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-string-special-symbol\">:record</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-variable\">messages</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-comment\">_batch_info</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-comment\">_context</span><span class=\"l-punctuation-bracket\">)</span> <span class=\"l-keyword\">do</span>\n</div><div class=\"l-line\" data-line=\"10\">  <span class=\"l-variable\">messages</span>\n</div><div class=\"l-line\" data-line=\"11\">  <span class=\"l-operator\">|&gt;</span> <span class=\"l-module\">Enum</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">map</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-operator\">&amp;</span> <span class=\"l-operator\">&amp;</span><span class=\"l-operator\">1</span><span class=\"l-operator\">.</span><span class=\"l-variable\">data</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"12\">  <span class=\"l-operator\">|&gt;</span> <span class=\"l-module\">Deliveries</span><span class=\"l-operator\">.</span><span class=\"l-function-call\">insert_all</span><span class=\"l-punctuation-bracket\">(</span><span class=\"l-punctuation-bracket\">)</span>\n</div><div class=\"l-line\" data-line=\"13\">\n</div><div class=\"l-line\" data-line=\"14\">  <span class=\"l-variable\">messages</span>\n</div><div class=\"l-line\" data-line=\"15\"><span class=\"l-keyword\">end</span>\n</div></code></pre></figure>\n<p>Enrichment moved to a separate pipeline reading from the recorded rows, with\nits own concurrency and its own failure domain. The write path got simple\nenough that eight processors outperformed the eighty we had before.</p>\n<h2 id=\"what-the-numbers-were-hiding\">What the numbers were hiding<a href=\"#what-the-numbers-were-hiding\" aria-label=\"Link to heading 'What the numbers were hiding'\" data-heading-content=\"What the numbers were hiding\" class=\"anchor\"></a></h2>\n<p><code>concurrency:</code> looks like a performance knob and behaves like a policy. Setting\nit high says &quot;I would rather overwhelm my dependencies than fall behind.&quot;\nSetting it low says the opposite. Neither is wrong, but you should be able to\nsay which one you meant, and why, without opening the dashboard.</p>\n<p>The BEAM makes it unusually cheap to express any of these answers. It does not\ntell you which one your product needs.</p>","date_published":"2025-02-11T00:00:00Z","id":"https://cherrybomb.dev/t/kage/backpressure-is-a-product-decision/","summary":"Every queue that fills up is answering a question somebody should have answered on purpose.","tags":["elixir","broadway","backpressure"],"title":"Backpressure Is a Product Decision","url":"https://cherrybomb.dev/t/kage/backpressure-is-a-product-decision/"}],"title":"Juno Vale","version":"https://jsonfeed.org/version/1.1"}