<?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[Joyful Programming]]></title><description><![CDATA[Helping Engineers Understand Their Ruby on Rails Apps and Reduce Time To Resolution Of Incidents.]]></description><link>https://www.joyfulprogramming.com</link><image><url>https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png</url><title>Joyful Programming</title><link>https://www.joyfulprogramming.com</link></image><generator>Substack</generator><lastBuildDate>Wed, 29 Apr 2026 11:34:08 GMT</lastBuildDate><atom:link href="https://www.joyfulprogramming.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[John Gallagher]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[joyfulprogramming@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[joyfulprogramming@substack.com]]></itunes:email><itunes:name><![CDATA[John Gallagher]]></itunes:name></itunes:owner><itunes:author><![CDATA[John Gallagher]]></itunes:author><googleplay:owner><![CDATA[joyfulprogramming@substack.com]]></googleplay:owner><googleplay:email><![CDATA[joyfulprogramming@substack.com]]></googleplay:email><googleplay:author><![CDATA[John Gallagher]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Five Principles for Joyful Observability]]></title><description><![CDATA[Real-World Lessons from Pairing on a Restaurant Rails App]]></description><link>https://www.joyfulprogramming.com/p/five-principles-for-joyful-observability</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/five-principles-for-joyful-observability</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sat, 24 Jan 2026 14:21:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today, I&#8217;ll talk about a hands-on session coaching session with Petro Yakubiv, the founder-developer of a Rails-based restaurant app called Menutaria.</p><p>Over our two-hour Zoom deep dive, we tackled the big problem of how to add observability to a Rails system just beginning to get real users&#8212;and we didn&#8217;t just talk abstract theory, but rolled up our sleeves and doggedly tracked down the most sensible ways to see inside the app as it grows.</p><p>What follows are the five underlying principles that guide my approach to observability, drawn straight from this conversation and my wider experience. Whether you&#8217;re working on a scrappy greenfield startup, a legacy codebase, or something in-between, my hope is you&#8217;ll take away concrete, repeatable strategies to make observability both useful and <strong>joyful</strong>.</p><h2>Introduction: Observability Without Overwhelm</h2><p>Trying to instrument a new Rails app - or tidy up a mess of tangled logging - is enough to leave my brain scattered in a dozen directions. Especially for those of us who are neurodivergent, it&#8217;s easy to get caught up in details that feel urgent but don&#8217;t actually make your life easier when your app is wobbling at 5pm on a Friday.</p><p>In this pairing session, I wanted to give Petro practical, sustainable ways to make his Rails app observable without falling into the trap of &#8220;let&#8217;s log everything and hope for the best.&#8221; The truth is, that&#8217;s both a waste of time and a recipe for future pain: cluttered code, slow answers, and stakeholders who are unimpressed because you can&#8217;t show concrete results.</p><p>Instead, the goal is to build up observability layer bit by bit, with intent - making every event you track pay its way by helping you answer a real question, now or down the line. Here&#8217;s how we did that, and the thinking behind each move.</p><h2>Principle 1: Start With a Clear Question</h2><p>The first and most foundational principle: <strong>Never start observability work by just adding logs or metrics everywhere.</strong></p><p>Instead, always begin by asking: <em>Which part of the app, if it broke, would cause real pain for users or the business?</em></p><p>For Petro&#8217;s restaurant app, this meant focusing on the absolute must-haves - &#8220;Can a customer view a menu after scanning a QR code? Are they able to book a table, call a waiter, or leave a review?&#8221; Working from that list, we avoid the endless project of &#8220;improve logging,&#8221; which usually fizzles into a morass of technical debt and confused developers.</p><p>This approach keeps stakeholders happy too. Rather than spending weeks &#8220;adding analytics,&#8221; you can show: &#8220;Here&#8217;s how we caught outages in payments, or saw failed menu scans instantly.&#8221; In practice, we wrote out the business flows as a numbered list, rock-bottom priorities at the top, and made sure every instrumentation change was about answering one of those core questions.</p><h2>Principle 2: Value-Driven Instrumentation</h2><p>Instrumentation is only useful if it drives action. <strong>Every metric, event, or trace you add should have a clear value</strong> - <strong>otherwise, it&#8217;s noise.</strong> If you&#8217;ve ever waded through logs only to find &#8220;something went wrong&#8221; with zero context, you know how infuriating that is.</p><p>In our session, I pushed Petro to think about this upfront. For example: why record a &#8220;menu viewed&#8221; event? Simple - if those events drop to zero, either something is wrong with the app, or with the restaurant&#8217;s business. When we found nil values in the controller (those safe navigators on <code>@menu&amp;.name</code> and <code>@table&amp;.zone</code>), it signaled a perfect opportunity to distinguish between a successful menu view and one with missing data, allowing for alerts to fire with the right context.</p><p>This discipline not only makes debugging easier, but reduces cruft for your future self and team. By asking, &#8220;If an error fires in here, will I have everything I need to actually fix it?&#8221; you end up with clean, actionable events - ones that map to how people actually experience your product.</p><h2>Principle 3: Think in Events, Not Just CRUD</h2><p>One of the biggest shifts in mindset I teach around observability: <strong>Focus on events (what happened and when), not just current state (what&#8217;s in the database right now).</strong></p><p>Too many Rails apps obsess over CRUD actions and miss the fact that when something goes wrong - an error when booking a table, or the spinner that spins forever after a failed upload - it&#8217;s the event history that explains &#8220;why.&#8221; Petro&#8217;s original design had logs tied closely to analytics tools like Ahoy, but by reframing these as domain events (e.g., <code>restaurant.menu_viewed</code>, <code>waiter.requested</code>, <code>order.placed</code>), we unlocked far richer debugging.</p><p>This also naturally blends business analytics with technical observability. Imagine being able to break down events by &#8220;physical location&#8221; (was the QR scanned indoors, outdoor, from Instagram?), or grouping menu failures by which restaurant, or which table and zone. One structure powers both real-time debugging and boardroom insights. That&#8217;s the real power of events.</p><h2>Principle 4: Make Use of the Fact Bucket</h2><p>Debugging with a brain that sniffs out oddities but can&#8217;t always hang onto them? I&#8217;m right there with you. That&#8217;s why I teach - and use daily - the <strong>Fact Bucket</strong>: a scratchpad (could be a markdown file, sticky note, whatever makes you tick) where you jot down anything interesting or weird, as soon as you spot it, without breaking your flow.</p><p>In Petro&#8217;s app, for example, it was critical to note down that menus or tables could be nil, or that a booking might succeed with a disconnected zone. The act of writing facts down, without immediately diving into rabbit holes, preserves your focus while capturing all those &#8220;Hmm, I should check that later&#8230;&#8221; moments. Over time, reviewing the fact bucket helps you connect dots and tease out hidden patterns.</p><p>I keep mine open whenever diving deep into code, code reviews, or architecture chats. It&#8217;s saved my bacon more times than I can count, especially when production is wobbling and a seemingly-unrelated detail from last week turns out to be the missing link.</p><h2>Principle 5: Design for Both Today and Tomorrow</h2><p>Shipping fast is great - but observability means designing with <strong>both today&#8217;s problems and future flexibility in mind</strong>. This means building mechanisms that let you add new events or attributes easily, without entrenching yourself in technical debt.</p><p>During our pairing, we discussed how to model key domain attributes for events, name-spacing them so you can group by restaurant, menu, zone, or access point (indoor/outdoor/Instagram/etc.) as the system grows. Bank on the fact that new business logic will emerge (&#8220;oh, now we want to track menu scans by zone, or bookings by QR code origin!&#8221;) - so create your logging/events in a consistent, extensible way: nested hashes, semantic event names, and (when you&#8217;re ready) even schema validation for observability payloads.</p><p>This isn&#8217;t about building castles in the sky - it&#8217;s about a little up-front structure that saves hours of headache when the business pivots, or users try unexpected flows. The key is not to instrument *everything*, but to make it straightforward to instrument *anything you need*, when you need it.</p><h2>Real-World Example: Debugging Menutaria&#8217;s Menu Scans</h2><p>Let&#8217;s make this concrete. In Menutaria, the most urgent business goal was: &#8220;Do menu scans work everywhere - at the table, via a QR code on the door, or from a link on the web?&#8221; By listing the business flows and mapping each to a domain event <code>menu.viewed</code>, <code>able.booked</code>, etc), we made sure observability kept pace with what actually matters.</p><p>Next, we made our event payloads explicit: If a customer tried to view a menu but saw none, we logged that as a specific failure with context - was the menu missing, was the zone disconnected, what access point did they use? For successful events, we logged the attributes needed to answer &#8220;who, where, when, and what.&#8221;</p><p>We also proactively looked for patterns: What happens if all menus are accidentally disabled? Can the error be explained just by database state, or do we need to capture the chain of events? By dividing errors into &#8220;indoor/outdoor/no menu&#8221; and always including as much context as possible, we made debugging fast, automatic, and actionable - not just a guess in the dark.</p><h2>Conclusion: Joyful Confidence in Production</h2><p>Observability is about cultivating peace of mind, so that you (and your team) can fearlessly ship, debug, and grow your product without dreading the inevitable &#8220;Friday Wobble.&#8221; By focusing on the five principles above - starting with a question, tying every event to business value, thinking in events, using the fact bucket, and designing for future flexibility - you&#8217;ll genuinely see what&#8217;s going on in production, and quickly answer the questions that really matter.</p><p>I encourage you to grab a sticky note (or spin up a <code>fact_bucket.md</code> in your repo). Start jotting down your next round of critical flows, and ask: &#8220;What would I *really* want to know if this broke?&#8221; From there, joyfully instrument with intent.</p><p>If you want to go even deeper, or get a hand instrumenting your own app, please connect and DM me COACH on LinkedIn.</p>]]></content:encoded></item><item><title><![CDATA[Stay Focussed When Debugging Production Issues - The 'Fact Bucket' Method]]></title><description><![CDATA[As a software engineer with ADHD, I've developed a technique to help me stay focussed when debugging production issues and asking questions. It's called The Fact Bucket.]]></description><link>https://www.joyfulprogramming.com/p/stay-focussed-when-debugging-production</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/stay-focussed-when-debugging-production</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sat, 14 Jun 2025 13:50:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!sQ48!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>The Challenge: Information Overload During Debugging</h3><p>Debugging a live production issue, tracing <code>nil</code>s through Rails controllers, or untangling a domain model can leave my brain scattered in twenty directions. Especially given I&#8217;m neurodivergent. I spot something odd &#8212; a controller skipping a check, a field that&#8217;s <code>nil</code> when it shouldn&#8217;t be &#8212; and I think, <em>&#8220;I&#8217;ll come back to that.&#8221;</em></p><p>But I don&#8217;t. And what&#8217;s more, it distracts me.</p><p>And later, when everything collapses, I would think:</p><blockquote><p>Oh yeah, I remember - I saw something weird.</p></blockquote><p>I was diagnosed with ADHD in adulthood. The diagnosis made a lot of things click &#8212; especially why I&#8217;d dive deep into debugging but forget half the interesting things I noticed along the way.</p><p>Debugging with ADHD means balancing hyperfocus with distractibility. If I pause every time I see something "interesting," I lose momentum. But if I don&#8217;t note it down, it's gone. That tension led me to a system I now teach every engineer I coach.</p><h3>The Solution: The "Fact Bucket"</h3><p>I call it the <em>Fact Bucket</em>. It's a simple idea:</p><ul><li><p><strong>Capture interesting observations immediately</strong></p></li><li><p><strong>Don&#8217;t interrupt your current flow</strong></p></li><li><p><strong>Review the patterns later</strong></p></li></ul><p>This isn&#8217;t a formal tool. It can be a <code>fact_bucket.md</code> in your repo. A Notion doc. A sticky note. Whatever works. The key is &#8212; when your Spidey sense tingles, you log it and keep moving.</p><p>This fact bucket can be reviewed regularly. As I move through a debugging session with a production defect, I record facts that are pertinent to the issue and facts that are not. Either way, it allows me to build a picture of the app in my head.</p><h3>Case Study: Spotting Nils &amp; Domain Inconsistencies</h3><p>Recently, <a href="https://youtu.be/nXKJb-t8eug">I gave a coaching session</a> with <a href="https://www.linkedin.com/in/petro-yakubiv-98848265/">Petro Yakubiv</a> on his restaurant app built in Rails. Customers were scanning QR codes to view menus. Sometimes they&#8217;d see... nothing. But no errors were thrown. Petro wanted to know how to get started with observability.</p><p>Inside the controller, we spotted safe navigators like <code>@menu&amp;.name</code> and <code>@table&amp;.zone</code>. That meant <code>@menu</code> or <code>@table</code> could be <code>nil</code>.</p><p>I noted this in the fact bucket: <em>&#8220;table and menu possibly nil &#8211; investigate default paths and fallbacks.&#8221;</em></p><p>Later, it turned out if a restaurant admin disabled all menus by accident, customers would land on a menu-less screen. No crashes. Just silent failure. That allowed us to come up with ways of mitigating against these kinds of failures by logging relevant events.</p><h2>Using The Fact Bucket With Steps To Observable Software</h2><p>Below is the process I use for all my debugging.</p><p>I&#8217;ve evolved it over years of exploration with real production systems.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sQ48!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sQ48!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.png 424w, https://substackcdn.com/image/fetch/$s_!sQ48!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.png 848w, https://substackcdn.com/image/fetch/$s_!sQ48!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.png 1272w, https://substackcdn.com/image/fetch/$s_!sQ48!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sQ48!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.png" width="1456" height="1165" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1165,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:204676,&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://www.joyfulprogramming.com/i/165936895?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.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_!sQ48!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.png 424w, https://substackcdn.com/image/fetch/$s_!sQ48!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.png 848w, https://substackcdn.com/image/fetch/$s_!sQ48!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.png 1272w, https://substackcdn.com/image/fetch/$s_!sQ48!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e928e4e-6aa1-424e-8ba3-1d6597acee21_3348x2680.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>A quick recap:</p><ol><li><p><strong>Question</strong> - Form a hypothesis about what might be going wrong and form a specific question you want answered.</p></li><li><p><strong>Decide</strong> - Figure out what data can you collect that answers the question.</p></li><li><p><strong>Build</strong> - Create instrumentation in code to collect the data</p></li><li><p><strong>Use</strong> - Logs, graphs, traces, use your observability tool to explore the data and ask the question</p></li><li><p><strong>Reflect</strong> - An opportunity to reflect on your progress and next steps. What have you learned? Are you still going in the right direction? What&#8217;s the next step?</p></li></ol><p>How does the fact bucket work at each step?</p><ol><li><p><strong>Question</strong> - <strong>evolve</strong> the next question or hypothesis using the fact bucket</p></li><li><p><strong>Decide</strong> - not relevant</p></li><li><p><strong>Build</strong> - not relevant</p></li><li><p><strong>Use</strong> - <strong>collect</strong> facts in the bucket</p></li><li><p><strong>Reflect</strong> - <strong>review</strong> the bucket and use it to help you decide what to do next</p></li></ol><p>The fact bucket works beautifully with the Steps To Observable Software - it keeps me on track and focussed on the job at hand.</p><h3>Broader Applications: Code Reviews, Architecture, Client Calls</h3><p>I now use the Fact Bucket method everywhere:</p><ul><li><p>During <strong>code reviews</strong>, I jot down odd patterns instead of derailing the main feedback.</p></li><li><p>In <strong>architecture discussions</strong>, I log confusing constraints to clarify later.</p></li><li><p>On <strong>client calls</strong>, I capture &#8220;that weird thing they said&#8221; without breaking the conversation flow.</p></li></ul><p>It's the perfect buffer between noticing and acting.</p><h3>Tools and Techniques: Keeping Your Fact Bucket Simple</h3><p>You don&#8217;t need a system. Just habits.</p><ul><li><p>Use <code>bin/fact-bucket</code> to open a Markdown file in your project</p></li><li><p>Create a GitHub Gist or Notion page called <code>&#129504; Fact Bucket</code></p></li><li><p>Use VSCode&#8217;s TODO extension and prefix with <code>FACT:</code> instead of <code>TODO:</code></p></li></ul><p>The best tool is the one you&#8217;ll use without friction.</p><h3>Turning Cognitive Challenges into Debugging Superpowers</h3><p>My forgetfulness isn&#8217;t a flaw &#8212; it&#8217;s a clue that my brain is noticing more than it can handle at once. The Fact Bucket turns that chaos into leverage. I start to spot patterns others miss. I connect threads across time.</p><h2>Next Steps</h2><h3>1. Sign up for free coaching</h3><p>I&#8217;m currently offering free observability coaching sessions of 90 minutes.</p><p>I&#8217;ve got 5 slots available a month for a limited time.</p><p><a href="https://linkedin.com/in/synapticmishap">DM me on LinkedIn</a> with COACH and I&#8217;ll give you more details.</p><h3>2. Join the waitlist for my course</h3><p>I&#8217;m preparing a real world, hands on practical course on observability and the first module will be FREE.</p><p><a href="https://linkedin.com/in/synapticmishap">DM me on LinkedIn</a> with COURSE and I&#8217;ll let you know when it&#8217;s out.</p>]]></content:encoded></item><item><title><![CDATA[The Essential Guide to Structured Logging: Best Practices and Benefits]]></title><description><![CDATA[Discover best practices and benefits of structured logging to understand your app. Practical insights and strategies on how to implement structured logging.]]></description><link>https://www.joyfulprogramming.com/p/structured-logging-best-practices</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/structured-logging-best-practices</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sat, 07 Dec 2024 13:31:37 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/1ebc0d7a-8191-4ba8-9713-f1319b057aad_1272x950.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What is Structured Logging?</h2><p>Structured logging is the application of a consistent format to logs, allowing them to be treating as data sets which can be easily searched and analysed, giving insights into application behaviour.</p><h2>Structured vs. Unstructured Logs</h2><p>Unstructured logs have messages in plain text containing all log data in a single string.</p><p>Structured logs record log events in a standardised, machine-readable format with key value pairs, usually in JSON format.</p><h2>Structured Logging Example</h2><h3>Unstructured Logs in Rails</h3><p>In the code:</p><pre><code><code>job = ... 
Rails.logger.info("Performed #{job.class} in #{job.duration}")
</code></code></pre><p>In the logs:</p><pre><code><code>[2022-04-01 14:35.0154] [Sidekiq] Performed Mailer::PasswordReset in 1436.451
</code></code></pre><p>Most traditional logs look like this. It stores unstructured data. Any log data in plain text can go in the message. It's not easily parsed by machines - we need regular expressions.</p><p><strong>&#128545; Unstructured logs are difficult to work with.</strong></p><h3>Structured Logs in Rails</h3><p>In the code:</p><pre><code><code>job = ...
Rails.logger.info(
  message: "Performed #{job.class} in #{job.duration}",
  event: {
    name: "app.job.performed",
  },
  code: {
    namespace: job.class,
  },
  duration: job.duration,
  messaging: {
    active_job: {
      adapter: {
        name: job.adapter
      }
    }
  }
)</code></code></pre><p>This outputs the following JSON in the logs:</p><pre><code><code>{
  "level": "info",
  "message": "Performed Mailer::PasswordReset in 1436.451",
  "event": {
    "name": "app.job.performed"
  },
  "code": {
    "namespace": "Mailer::PasswordReset",
  },
  "duration": 1436.451,
  "messaging": {
    "active_job": {
      "adapter": {
        "name": "sidekiq"
      }
    }
  }
}</code></code></pre><p>The code for structured logging looks more unfamiliar. Note how the log message is separated. We get contextual information. We can detect patterns in our logs. The log data has a consistent format in key value pairs.</p><p><strong>&#128525; Structured logs are easy to parse, search and aggregate.</strong></p><h2>What can I do with Structured Logs vs Unstructured Logs?</h2><p>Structured logs allow engineers to search and aggregate more easily.</p><p>What use cases do structured logs support?</p><h4>Search for Exact Match of a Single Field</h4><p><strong>Example</strong>: All Mailer::DeliverPasswordReset jobs  </p><p>- <strong>Structured Logs Difficulty</strong>: Easy  </p><p>- <strong>Unstructured Logs Difficulty</strong>: Easy  </p><h4>Search for Substring Match of a Single Field</h4><p><strong>Example</strong>: All `Mailer::*` jobs  </p><p>- <strong>Structured Logs Difficulty</strong>: Easy  </p><p>- <strong>Unstructured Logs Difficulty</strong>: Medium  </p><h4>Search for Exact Match of Two Fields</h4><p><strong>Example</strong>: All jobs performed for `Mailer::DeliverPasswordReset` jobs  </p><p>- <strong>Structured Logs Difficulty</strong>: Easy  </p><p>- <strong>Unstructured Logs Difficulty</strong>: Medium  </p><h4>Search for Equality Conditions</h4><p><strong>Example</strong>: Jobs performed with a duration of over 1 second  </p><p>- <strong>Structured Logs Difficulty</strong>: Easy  </p><p>- <strong>Unstructured Logs Difficulty</strong>: Impossible  </p><h4>Search for Aggregations (Average, Minimum, Maximum, P99, P95, P90)</h4><p><strong>Example</strong>: Jobs performed with a P95 over 3 seconds  </p><p>- <strong>Structured Logs Difficulty</strong>: Medium  </p><p>- <strong>Unstructured Logs Difficulty</strong>: Impossible  </p><h4>Group by a Field</h4><p><strong>Example</strong>: Graph duration of jobs grouped by job class  </p><p>- <strong>Structured Logs Difficulty</strong>: Medium  </p><p>- <strong>Unstructured Logs Difficulty</strong>: Impossible  </p><h2>Benefits of Structured Logs</h2><p>Structured logging has allowed me to:</p><ul><li><p>Analyse and understand my application behaviour</p></li><li><p>Resolve production incidents quickly</p></li><li><p>Supply critical business intelligence to other departments</p></li><li><p>Aggregate performance data - easily graph percentiles, averages, minimum and maximums</p></li><li><p>Create visuals like graphs inside my observability tool</p></li><li><p>Diagnose problems 10-100 times faster than with plain text log data</p></li></ul><h2>Structured Logging Best Practices</h2><p>Here's what's worked for me over the years:</p><ul><li><p>Select a library that supports structured logging and integrates with your web framework</p></li><li><p>Establish a consistent format for logs across all services - OpenTelemetry is valuable for this</p></li><li><p>Use unique identifiers to trace requests across multiple services</p></li><li><p>Convert unstructured logs outside the application into structured logs (apache, nginx, cloud provider)</p></li></ul><h2>Structured Logging Libraries</h2><p>Depending on the tool and framework, here are some logging frameworks that implement structured logging in your software system:</p><p><strong>Javascript</strong> - winston, pino, bunyan</p><p><strong>C#</strong> - serilog, nlog</p><p><strong>Java</strong> - log4j, tinylog</p><p><strong>Python</strong> - contextual_logger</p><p><strong>Ruby</strong> - semantic_logger</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xeoA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62ce031f-9cbe-4ebd-b2b3-44bdd13c6166_640x640.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xeoA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62ce031f-9cbe-4ebd-b2b3-44bdd13c6166_640x640.png 424w, https://substackcdn.com/image/fetch/$s_!xeoA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62ce031f-9cbe-4ebd-b2b3-44bdd13c6166_640x640.png 848w, https://substackcdn.com/image/fetch/$s_!xeoA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62ce031f-9cbe-4ebd-b2b3-44bdd13c6166_640x640.png 1272w, https://substackcdn.com/image/fetch/$s_!xeoA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62ce031f-9cbe-4ebd-b2b3-44bdd13c6166_640x640.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xeoA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62ce031f-9cbe-4ebd-b2b3-44bdd13c6166_640x640.png" width="640" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/62ce031f-9cbe-4ebd-b2b3-44bdd13c6166_640x640.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Implementing structured logging at a desk&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&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="Implementing structured logging at a desk" title="Implementing structured logging at a desk" srcset="https://substackcdn.com/image/fetch/$s_!xeoA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62ce031f-9cbe-4ebd-b2b3-44bdd13c6166_640x640.png 424w, https://substackcdn.com/image/fetch/$s_!xeoA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62ce031f-9cbe-4ebd-b2b3-44bdd13c6166_640x640.png 848w, https://substackcdn.com/image/fetch/$s_!xeoA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62ce031f-9cbe-4ebd-b2b3-44bdd13c6166_640x640.png 1272w, https://substackcdn.com/image/fetch/$s_!xeoA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62ce031f-9cbe-4ebd-b2b3-44bdd13c6166_640x640.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>Implementing Structured Logging</h2><ol><li><p>Set a specific goal - example - "why are background jobs failing?"</p></li><li><p>Initial goal - focus on migrating existing unstructured logs to a structured format</p></li><li><p>Deal with existing dependencies - do engineers use unstructured logs or are there other systems that depend on this format?</p></li><li><p>Select a library that supports structured logging</p></li><li><p>Patch framework to use the structured logging library chosen above</p></li><li><p>Test logs locally - keep existing format so as not to confuse other engineers</p></li><li><p>Integrate and test with observability tool in staging environment - are structured logs coming through? Are they being parsed correctly?</p></li><li><p>Deploy changes to the log data</p></li><li><p>Use structured logs and list defects - focus on the specific goal - what&#8217;s missing or not in the right format?</p></li><li><p>Improve structured logs and redeploy</p></li></ol><h2>Use Cases for Structured Logging</h2><p>Structured logging is great for debugging, troubleshooting, monitoring application performance, auditing security and compliance requirements, and more.</p><p>It is particularly useful in complex distributed systems where the log entry needs to be standardized and correlated.</p><p>Structured logs can be used to feed log data into a log management tool that allows for querying, correlating, and visualizing log data across all dimensions.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4a7T!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F644f6663-f3cf-45d1-bb8c-b565432ac875_640x427.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4a7T!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F644f6663-f3cf-45d1-bb8c-b565432ac875_640x427.jpeg 424w, https://substackcdn.com/image/fetch/$s_!4a7T!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F644f6663-f3cf-45d1-bb8c-b565432ac875_640x427.jpeg 848w, https://substackcdn.com/image/fetch/$s_!4a7T!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F644f6663-f3cf-45d1-bb8c-b565432ac875_640x427.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!4a7T!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F644f6663-f3cf-45d1-bb8c-b565432ac875_640x427.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4a7T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F644f6663-f3cf-45d1-bb8c-b565432ac875_640x427.jpeg" width="640" height="427" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/644f6663-f3cf-45d1-bb8c-b565432ac875_640x427.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:427,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Working with structured log data&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&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="Working with structured log data" title="Working with structured log data" srcset="https://substackcdn.com/image/fetch/$s_!4a7T!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F644f6663-f3cf-45d1-bb8c-b565432ac875_640x427.jpeg 424w, https://substackcdn.com/image/fetch/$s_!4a7T!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F644f6663-f3cf-45d1-bb8c-b565432ac875_640x427.jpeg 848w, https://substackcdn.com/image/fetch/$s_!4a7T!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F644f6663-f3cf-45d1-bb8c-b565432ac875_640x427.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!4a7T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F644f6663-f3cf-45d1-bb8c-b565432ac875_640x427.jpeg 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>Working with Structured Log Data</h2><p>Use a log management tool that allows JSON data to be sent.</p><p>For unstructured text data from other systems use log parsing tools to transform into structured data with a standard format.</p><p>Establish a unified schema for attribute names and values across all log sources to facilitate seamless correlation and cross-analysis of log data.</p><h2>Overcoming Common Challenges</h2><p>One of the main challenges of structured logging is ensuring all application logs are standardized and use the same structured format. For this I'd suggest <a href="https://opentelemetry.io/docs/specs/semconv/">OpenTelemetry's semantic conventions</a>.</p><p>Another challenge is the potential performance overhead due to the additional formatting, but modern logging libraries are highly optimized.</p><p>Existing unstructured logs from systems with no control over the logging framework can be difficult to work with. We can reformat into a consistent log format using an ingestion pipeline or custom scripts.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2FYP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98629d4-a012-4ad8-ba1a-2ae1ab99b05d_640x427.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2FYP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98629d4-a012-4ad8-ba1a-2ae1ab99b05d_640x427.jpeg 424w, https://substackcdn.com/image/fetch/$s_!2FYP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98629d4-a012-4ad8-ba1a-2ae1ab99b05d_640x427.jpeg 848w, https://substackcdn.com/image/fetch/$s_!2FYP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98629d4-a012-4ad8-ba1a-2ae1ab99b05d_640x427.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!2FYP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98629d4-a012-4ad8-ba1a-2ae1ab99b05d_640x427.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2FYP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98629d4-a012-4ad8-ba1a-2ae1ab99b05d_640x427.jpeg" width="640" height="427" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a98629d4-a012-4ad8-ba1a-2ae1ab99b05d_640x427.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:427,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Observability in charts and graphs&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&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="Observability in charts and graphs" title="Observability in charts and graphs" srcset="https://substackcdn.com/image/fetch/$s_!2FYP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98629d4-a012-4ad8-ba1a-2ae1ab99b05d_640x427.jpeg 424w, https://substackcdn.com/image/fetch/$s_!2FYP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98629d4-a012-4ad8-ba1a-2ae1ab99b05d_640x427.jpeg 848w, https://substackcdn.com/image/fetch/$s_!2FYP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98629d4-a012-4ad8-ba1a-2ae1ab99b05d_640x427.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!2FYP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98629d4-a012-4ad8-ba1a-2ae1ab99b05d_640x427.jpeg 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>Real-World Example of Structured Logging</h2><p>We received a Slack alert - "log volume is projected to breach quota". </p><p>This was a monitor we'd set up in our observability tool to stop cost overages. A lot of structured data was being sent into our logs. That's strange.</p><p><strong>Question</strong> - when are the number of structured logs spiking?</p><p>Looking into log analysis tool, we could see a huge spike in log levels at 3:17pm.</p><p><strong>Question</strong> - what's generating those logs?</p><p>Grouping the structured logs by event name, we see the majority of these events are job.performed.</p><p><strong>Question</strong> - what jobs are being performed?</p><p>Filtering by event name of job.performed, then grouping by job class we see the culprit - GeoDecodingJob</p><p><strong>Question</strong> - why are there a surge of GeoDecodingJobs?</p><p>Graphing the number of retries of GeoDecodingJobs, we can see that these jobs were repeatedly being retried and all the retries were mounting up</p><p><strong>Question</strong> - why were the jobs retrying?</p><p>The API was down for an hour and so all our jobs were retrying.</p><p><strong>Action</strong> - tweak exponential backoff algorithm of the background job to be more lazy so the same number of jobs wouldn't mount up in the same way again.</p><p>Fixed!</p><p>With unstructured log data we wouldn't have been able to answer the first question, let alone the others.</p><p>Unlike unstructured logs, any data we've recorded in our instrumentation is available to search, sort, filter and group on.</p><h2>Conclusion</h2><p>Structured logging is part of essential best practices for any team that wants to improve their log management and analysis capabilities.</p><p>By following best practices and using the right tools, companies can overcome common challenges and reap the benefits of structured logging.</p><p>With structured logs, I've gained valuable insights into the performance and health of my application, and I've made data-driven decisions to help the software development process.</p>]]></content:encoded></item><item><title><![CDATA[How Structured Logging in Rails Reduced Our Downtime by 98%]]></title><description><![CDATA[Introducing the Five Steps To Observable Software - better understand your Rails app in production whilst slashing time to resolution.]]></description><link>https://www.joyfulprogramming.com/p/how-structured-logging-in-rails-reduced</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/how-structured-logging-in-rails-reduced</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Mon, 23 Sep 2024 12:10:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!SMqp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ecc008b-a6ed-48f8-8243-1ea27ac01d8e_3000x1688.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Problem</h2><p>The following is based on true story. Names have been changed to protect the innocent.</p><p>I'm on call. It's a Friday afternoon and I get a Slack notification:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SMqp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ecc008b-a6ed-48f8-8243-1ea27ac01d8e_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SMqp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ecc008b-a6ed-48f8-8243-1ea27ac01d8e_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!SMqp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ecc008b-a6ed-48f8-8243-1ea27ac01d8e_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!SMqp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ecc008b-a6ed-48f8-8243-1ea27ac01d8e_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!SMqp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ecc008b-a6ed-48f8-8243-1ea27ac01d8e_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SMqp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ecc008b-a6ed-48f8-8243-1ea27ac01d8e_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6ecc008b-a6ed-48f8-8243-1ea27ac01d8e_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:178695,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SMqp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ecc008b-a6ed-48f8-8243-1ea27ac01d8e_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!SMqp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ecc008b-a6ed-48f8-8243-1ea27ac01d8e_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!SMqp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ecc008b-a6ed-48f8-8243-1ea27ac01d8e_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!SMqp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ecc008b-a6ed-48f8-8243-1ea27ac01d8e_3000x1688.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>Okay. Looks like password reset emails aren't working:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KVUD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75828485-13f6-4914-ae01-c644321d2062_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KVUD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75828485-13f6-4914-ae01-c644321d2062_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!KVUD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75828485-13f6-4914-ae01-c644321d2062_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!KVUD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75828485-13f6-4914-ae01-c644321d2062_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!KVUD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75828485-13f6-4914-ae01-c644321d2062_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KVUD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75828485-13f6-4914-ae01-c644321d2062_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/75828485-13f6-4914-ae01-c644321d2062_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:233011,&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;: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_!KVUD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75828485-13f6-4914-ae01-c644321d2062_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!KVUD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75828485-13f6-4914-ae01-c644321d2062_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!KVUD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75828485-13f6-4914-ae01-c644321d2062_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!KVUD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75828485-13f6-4914-ae01-c644321d2062_3000x1688.jpeg 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><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Q1Zt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b507ae4-82a9-4ba1-abb2-eb88e9af4913_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Q1Zt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b507ae4-82a9-4ba1-abb2-eb88e9af4913_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Q1Zt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b507ae4-82a9-4ba1-abb2-eb88e9af4913_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Q1Zt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b507ae4-82a9-4ba1-abb2-eb88e9af4913_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Q1Zt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b507ae4-82a9-4ba1-abb2-eb88e9af4913_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Q1Zt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b507ae4-82a9-4ba1-abb2-eb88e9af4913_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7b507ae4-82a9-4ba1-abb2-eb88e9af4913_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:279334,&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;: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_!Q1Zt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b507ae4-82a9-4ba1-abb2-eb88e9af4913_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Q1Zt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b507ae4-82a9-4ba1-abb2-eb88e9af4913_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Q1Zt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b507ae4-82a9-4ba1-abb2-eb88e9af4913_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Q1Zt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b507ae4-82a9-4ba1-abb2-eb88e9af4913_3000x1688.jpeg 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>OK fine, let's go to the logs:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BHFb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7da69668-7cb8-4410-a79d-4ac0323630fd_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BHFb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7da69668-7cb8-4410-a79d-4ac0323630fd_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BHFb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7da69668-7cb8-4410-a79d-4ac0323630fd_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BHFb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7da69668-7cb8-4410-a79d-4ac0323630fd_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BHFb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7da69668-7cb8-4410-a79d-4ac0323630fd_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BHFb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7da69668-7cb8-4410-a79d-4ac0323630fd_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7da69668-7cb8-4410-a79d-4ac0323630fd_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:298454,&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;: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_!BHFb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7da69668-7cb8-4410-a79d-4ac0323630fd_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BHFb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7da69668-7cb8-4410-a79d-4ac0323630fd_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BHFb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7da69668-7cb8-4410-a79d-4ac0323630fd_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BHFb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7da69668-7cb8-4410-a79d-4ac0323630fd_3000x1688.jpeg 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>And search them for the job performed event:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DYOL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff567b053-d55f-45c0-887d-973429c57782_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DYOL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff567b053-d55f-45c0-887d-973429c57782_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!DYOL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff567b053-d55f-45c0-887d-973429c57782_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!DYOL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff567b053-d55f-45c0-887d-973429c57782_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!DYOL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff567b053-d55f-45c0-887d-973429c57782_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DYOL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff567b053-d55f-45c0-887d-973429c57782_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f567b053-d55f-45c0-887d-973429c57782_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:119886,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!DYOL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff567b053-d55f-45c0-887d-973429c57782_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!DYOL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff567b053-d55f-45c0-887d-973429c57782_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!DYOL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff567b053-d55f-45c0-887d-973429c57782_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!DYOL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff567b053-d55f-45c0-887d-973429c57782_3000x1688.jpeg 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>Not that helpful. Well, let's check the code:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EY-p!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6678b955-f304-4b07-b9d6-b68d428c9c14_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EY-p!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6678b955-f304-4b07-b9d6-b68d428c9c14_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!EY-p!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6678b955-f304-4b07-b9d6-b68d428c9c14_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!EY-p!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6678b955-f304-4b07-b9d6-b68d428c9c14_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!EY-p!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6678b955-f304-4b07-b9d6-b68d428c9c14_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EY-p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6678b955-f304-4b07-b9d6-b68d428c9c14_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6678b955-f304-4b07-b9d6-b68d428c9c14_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:188588,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!EY-p!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6678b955-f304-4b07-b9d6-b68d428c9c14_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!EY-p!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6678b955-f304-4b07-b9d6-b68d428c9c14_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!EY-p!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6678b955-f304-4b07-b9d6-b68d428c9c14_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!EY-p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6678b955-f304-4b07-b9d6-b68d428c9c14_3000x1688.jpeg 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>Code looks fine. Which doesn&#8217;t really help.</p><p>Meanwhile, our CTO is getting anxious:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mLh-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb59ea5a7-1b18-4f82-9229-40b6cce3643b_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mLh-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb59ea5a7-1b18-4f82-9229-40b6cce3643b_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!mLh-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb59ea5a7-1b18-4f82-9229-40b6cce3643b_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!mLh-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb59ea5a7-1b18-4f82-9229-40b6cce3643b_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!mLh-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb59ea5a7-1b18-4f82-9229-40b6cce3643b_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mLh-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb59ea5a7-1b18-4f82-9229-40b6cce3643b_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b59ea5a7-1b18-4f82-9229-40b6cce3643b_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:189748,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!mLh-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb59ea5a7-1b18-4f82-9229-40b6cce3643b_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!mLh-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb59ea5a7-1b18-4f82-9229-40b6cce3643b_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!mLh-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb59ea5a7-1b18-4f82-9229-40b6cce3643b_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!mLh-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb59ea5a7-1b18-4f82-9229-40b6cce3643b_3000x1688.jpeg 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>People are taking to Twitter and complaining. Nobody can get into their accounts:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ePKd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3decebfa-1222-41af-84bb-ebbd79f8248c_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ePKd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3decebfa-1222-41af-84bb-ebbd79f8248c_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ePKd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3decebfa-1222-41af-84bb-ebbd79f8248c_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ePKd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3decebfa-1222-41af-84bb-ebbd79f8248c_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ePKd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3decebfa-1222-41af-84bb-ebbd79f8248c_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ePKd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3decebfa-1222-41af-84bb-ebbd79f8248c_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3decebfa-1222-41af-84bb-ebbd79f8248c_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:254746,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!ePKd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3decebfa-1222-41af-84bb-ebbd79f8248c_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ePKd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3decebfa-1222-41af-84bb-ebbd79f8248c_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ePKd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3decebfa-1222-41af-84bb-ebbd79f8248c_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ePKd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3decebfa-1222-41af-84bb-ebbd79f8248c_3000x1688.jpeg 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><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WsUA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F535f0b51-95a9-49ea-94c3-5c8736652bd0_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WsUA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F535f0b51-95a9-49ea-94c3-5c8736652bd0_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!WsUA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F535f0b51-95a9-49ea-94c3-5c8736652bd0_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!WsUA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F535f0b51-95a9-49ea-94c3-5c8736652bd0_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!WsUA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F535f0b51-95a9-49ea-94c3-5c8736652bd0_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WsUA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F535f0b51-95a9-49ea-94c3-5c8736652bd0_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/535f0b51-95a9-49ea-94c3-5c8736652bd0_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:266850,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!WsUA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F535f0b51-95a9-49ea-94c3-5c8736652bd0_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!WsUA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F535f0b51-95a9-49ea-94c3-5c8736652bd0_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!WsUA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F535f0b51-95a9-49ea-94c3-5c8736652bd0_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!WsUA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F535f0b51-95a9-49ea-94c3-5c8736652bd0_3000x1688.jpeg 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>I pair with a colleague and we add error logging.</p><p>Whilst waiting for the app to deploy, the issue goes away:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!m8fK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F634f29ae-3636-4565-b7e8-b82c6c2d1fcc_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!m8fK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F634f29ae-3636-4565-b7e8-b82c6c2d1fcc_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!m8fK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F634f29ae-3636-4565-b7e8-b82c6c2d1fcc_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!m8fK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F634f29ae-3636-4565-b7e8-b82c6c2d1fcc_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!m8fK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F634f29ae-3636-4565-b7e8-b82c6c2d1fcc_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!m8fK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F634f29ae-3636-4565-b7e8-b82c6c2d1fcc_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/634f29ae-3636-4565-b7e8-b82c6c2d1fcc_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:186134,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!m8fK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F634f29ae-3636-4565-b7e8-b82c6c2d1fcc_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!m8fK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F634f29ae-3636-4565-b7e8-b82c6c2d1fcc_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!m8fK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F634f29ae-3636-4565-b7e8-b82c6c2d1fcc_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!m8fK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F634f29ae-3636-4565-b7e8-b82c6c2d1fcc_3000x1688.jpeg 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'll do it later. Put it on the backlog:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CBQr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16a0c82e-c0b6-429c-bc14-9bc3068e913c_960x540.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CBQr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16a0c82e-c0b6-429c-bc14-9bc3068e913c_960x540.png 424w, https://substackcdn.com/image/fetch/$s_!CBQr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16a0c82e-c0b6-429c-bc14-9bc3068e913c_960x540.png 848w, https://substackcdn.com/image/fetch/$s_!CBQr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16a0c82e-c0b6-429c-bc14-9bc3068e913c_960x540.png 1272w, https://substackcdn.com/image/fetch/$s_!CBQr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16a0c82e-c0b6-429c-bc14-9bc3068e913c_960x540.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CBQr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16a0c82e-c0b6-429c-bc14-9bc3068e913c_960x540.png" width="960" height="540" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/16a0c82e-c0b6-429c-bc14-9bc3068e913c_960x540.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:540,&quot;width&quot;:960,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:108070,&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_!CBQr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16a0c82e-c0b6-429c-bc14-9bc3068e913c_960x540.png 424w, https://substackcdn.com/image/fetch/$s_!CBQr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16a0c82e-c0b6-429c-bc14-9bc3068e913c_960x540.png 848w, https://substackcdn.com/image/fetch/$s_!CBQr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16a0c82e-c0b6-429c-bc14-9bc3068e913c_960x540.png 1272w, https://substackcdn.com/image/fetch/$s_!CBQr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16a0c82e-c0b6-429c-bc14-9bc3068e913c_960x540.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>Might be a while until we can get to the issue. It&#8217;s just a one-off incident though. Right? Right.</p><h2>Two Months Later</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BXqY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f7047f6-194f-4685-967f-5b01e566f660_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BXqY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f7047f6-194f-4685-967f-5b01e566f660_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BXqY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f7047f6-194f-4685-967f-5b01e566f660_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BXqY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f7047f6-194f-4685-967f-5b01e566f660_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BXqY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f7047f6-194f-4685-967f-5b01e566f660_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BXqY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f7047f6-194f-4685-967f-5b01e566f660_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9f7047f6-194f-4685-967f-5b01e566f660_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:188950,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!BXqY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f7047f6-194f-4685-967f-5b01e566f660_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BXqY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f7047f6-194f-4685-967f-5b01e566f660_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BXqY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f7047f6-194f-4685-967f-5b01e566f660_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BXqY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f7047f6-194f-4685-967f-5b01e566f660_3000x1688.jpeg 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>Oh no, it's happening again:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MV_8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fbf2dd-befc-427f-b5b9-b3cb5659b136_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MV_8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fbf2dd-befc-427f-b5b9-b3cb5659b136_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!MV_8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fbf2dd-befc-427f-b5b9-b3cb5659b136_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!MV_8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fbf2dd-befc-427f-b5b9-b3cb5659b136_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!MV_8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fbf2dd-befc-427f-b5b9-b3cb5659b136_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MV_8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fbf2dd-befc-427f-b5b9-b3cb5659b136_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e3fbf2dd-befc-427f-b5b9-b3cb5659b136_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:251086,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!MV_8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fbf2dd-befc-427f-b5b9-b3cb5659b136_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!MV_8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fbf2dd-befc-427f-b5b9-b3cb5659b136_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!MV_8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fbf2dd-befc-427f-b5b9-b3cb5659b136_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!MV_8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fbf2dd-befc-427f-b5b9-b3cb5659b136_3000x1688.jpeg 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>Good job we added the extra logging! We'll search the logs:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VecD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10da6517-feba-46b7-a812-a8da512d9215_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VecD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10da6517-feba-46b7-a812-a8da512d9215_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!VecD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10da6517-feba-46b7-a812-a8da512d9215_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!VecD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10da6517-feba-46b7-a812-a8da512d9215_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!VecD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10da6517-feba-46b7-a812-a8da512d9215_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VecD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10da6517-feba-46b7-a812-a8da512d9215_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/10da6517-feba-46b7-a812-a8da512d9215_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:118180,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!VecD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10da6517-feba-46b7-a812-a8da512d9215_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!VecD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10da6517-feba-46b7-a812-a8da512d9215_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!VecD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10da6517-feba-46b7-a812-a8da512d9215_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!VecD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F10da6517-feba-46b7-a812-a8da512d9215_3000x1688.jpeg 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>Nothing. Open the Sidekiq admin dashboard:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ax2J!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F394a349e-240d-4b73-8521-170da8b75ef0_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ax2J!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F394a349e-240d-4b73-8521-170da8b75ef0_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Ax2J!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F394a349e-240d-4b73-8521-170da8b75ef0_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Ax2J!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F394a349e-240d-4b73-8521-170da8b75ef0_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Ax2J!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F394a349e-240d-4b73-8521-170da8b75ef0_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ax2J!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F394a349e-240d-4b73-8521-170da8b75ef0_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/394a349e-240d-4b73-8521-170da8b75ef0_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:297134,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!Ax2J!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F394a349e-240d-4b73-8521-170da8b75ef0_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Ax2J!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F394a349e-240d-4b73-8521-170da8b75ef0_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Ax2J!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F394a349e-240d-4b73-8521-170da8b75ef0_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Ax2J!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F394a349e-240d-4b73-8521-170da8b75ef0_3000x1688.jpeg 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 can see jobs processed&#8230; which doesn&#8217;t really help us.</p><p>At BiggerPockets, we found ourselves in this vicious cycle.</p><p>Engineers would deploy ad hoc logging, we would deploy the app and wait for the incident to happen again.</p><p>The incident happened&#8230; but we didn&#8217;t have enough data to resolve it.</p><p>We were always on the back foot - always being reactive.</p><h2>The Solution: Steps to Observable Software</h2><p>I came up with five simple steps to understand our Rails app better in production.</p><p>I call it <strong>Steps to Observable Software</strong> or <strong>SOS</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yVll!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe59953a7-27fc-42ce-9d2e-8029f38c81d0_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yVll!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe59953a7-27fc-42ce-9d2e-8029f38c81d0_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!yVll!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe59953a7-27fc-42ce-9d2e-8029f38c81d0_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!yVll!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe59953a7-27fc-42ce-9d2e-8029f38c81d0_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!yVll!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe59953a7-27fc-42ce-9d2e-8029f38c81d0_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yVll!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe59953a7-27fc-42ce-9d2e-8029f38c81d0_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e59953a7-27fc-42ce-9d2e-8029f38c81d0_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:189886,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!yVll!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe59953a7-27fc-42ce-9d2e-8029f38c81d0_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!yVll!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe59953a7-27fc-42ce-9d2e-8029f38c81d0_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!yVll!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe59953a7-27fc-42ce-9d2e-8029f38c81d0_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!yVll!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe59953a7-27fc-42ce-9d2e-8029f38c81d0_3000x1688.jpeg 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 steps are:</p><ol><li><p>Question you want answered</p></li><li><p>Decide the data</p></li><li><p>Build instrumentation</p></li><li><p>Use the graphs</p></li><li><p>Improve</p></li></ol><p>In a later post I&#8217;ll detail out these 5 steps.</p><p>We've been around the SOS cycle many times at BiggerPockets - and here&#8217;s what it gives our team.</p><h2>Same Outage&#8230; With Observability</h2><p>It's Friday morning this time, not Friday afternoon.</p><p>I'm still on call, but I now get a notification from Slack.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!28sG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9bf61f7-5d2d-4aa4-82cc-31a997b14773_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!28sG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9bf61f7-5d2d-4aa4-82cc-31a997b14773_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!28sG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9bf61f7-5d2d-4aa4-82cc-31a997b14773_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!28sG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9bf61f7-5d2d-4aa4-82cc-31a997b14773_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!28sG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9bf61f7-5d2d-4aa4-82cc-31a997b14773_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!28sG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9bf61f7-5d2d-4aa4-82cc-31a997b14773_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b9bf61f7-5d2d-4aa4-82cc-31a997b14773_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:193824,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!28sG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9bf61f7-5d2d-4aa4-82cc-31a997b14773_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!28sG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9bf61f7-5d2d-4aa4-82cc-31a997b14773_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!28sG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9bf61f7-5d2d-4aa4-82cc-31a997b14773_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!28sG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9bf61f7-5d2d-4aa4-82cc-31a997b14773_3000x1688.jpeg 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>This is not a customer, not somebody in support.</p><p>This is our monitoring software telling me the queue has breached it&#8217;s agreed queue limit.</p><p>This is before any issue has happened. No customer has complained yet.</p><p>We click the link in the notification and we see this graph:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ll3o!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47333fc6-31dc-4f41-ac31-65b0fd2a7405_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ll3o!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47333fc6-31dc-4f41-ac31-65b0fd2a7405_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ll3o!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47333fc6-31dc-4f41-ac31-65b0fd2a7405_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ll3o!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47333fc6-31dc-4f41-ac31-65b0fd2a7405_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ll3o!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47333fc6-31dc-4f41-ac31-65b0fd2a7405_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ll3o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47333fc6-31dc-4f41-ac31-65b0fd2a7405_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/47333fc6-31dc-4f41-ac31-65b0fd2a7405_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:207895,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!ll3o!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47333fc6-31dc-4f41-ac31-65b0fd2a7405_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ll3o!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47333fc6-31dc-4f41-ac31-65b0fd2a7405_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ll3o!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47333fc6-31dc-4f41-ac31-65b0fd2a7405_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ll3o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47333fc6-31dc-4f41-ac31-65b0fd2a7405_3000x1688.jpeg 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 blue line is rising fast.</p><p>Hovering over it, we can see it&#8217;s the within_five_minutes queue:</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_7F!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b10fc01-757a-49c4-bb14-ede603f98ee2_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!g_7F!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b10fc01-757a-49c4-bb14-ede603f98ee2_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!g_7F!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b10fc01-757a-49c4-bb14-ede603f98ee2_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!g_7F!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b10fc01-757a-49c4-bb14-ede603f98ee2_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!g_7F!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b10fc01-757a-49c4-bb14-ede603f98ee2_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!g_7F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b10fc01-757a-49c4-bb14-ede603f98ee2_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8b10fc01-757a-49c4-bb14-ede603f98ee2_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:225178,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!g_7F!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b10fc01-757a-49c4-bb14-ede603f98ee2_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!g_7F!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b10fc01-757a-49c4-bb14-ede603f98ee2_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!g_7F!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b10fc01-757a-49c4-bb14-ede603f98ee2_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!g_7F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b10fc01-757a-49c4-bb14-ede603f98ee2_3000x1688.jpeg 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 process starts by asking a question.</p><p><strong>Question: Which jobs are taking most time?</strong></p><p>We search for <code>job.performed</code> within the same queue, group by <code>code.namespace</code> (job class), sum up the durations and hey presto:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!krO2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5791d42e-8485-41e1-92fa-4aab04f1f57d_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!krO2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5791d42e-8485-41e1-92fa-4aab04f1f57d_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!krO2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5791d42e-8485-41e1-92fa-4aab04f1f57d_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!krO2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5791d42e-8485-41e1-92fa-4aab04f1f57d_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!krO2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5791d42e-8485-41e1-92fa-4aab04f1f57d_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!krO2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5791d42e-8485-41e1-92fa-4aab04f1f57d_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5791d42e-8485-41e1-92fa-4aab04f1f57d_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:246412,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!krO2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5791d42e-8485-41e1-92fa-4aab04f1f57d_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!krO2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5791d42e-8485-41e1-92fa-4aab04f1f57d_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!krO2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5791d42e-8485-41e1-92fa-4aab04f1f57d_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!krO2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5791d42e-8485-41e1-92fa-4aab04f1f57d_3000x1688.jpeg 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>Answer: </strong><code>Analytics::UpdateUserVisitsJob</code><strong>.</strong> </p><p><strong>Question: What's enqueuing the </strong><code>Analytics::UpdateUserVisitsJob</code><strong>?</strong></p><p>We&#8217;ll group by <code>http.resource</code> which is a combination of the controller and action:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gWfZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a60602-9732-458b-a785-82135e456dc5_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gWfZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a60602-9732-458b-a785-82135e456dc5_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!gWfZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a60602-9732-458b-a785-82135e456dc5_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!gWfZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a60602-9732-458b-a785-82135e456dc5_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!gWfZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a60602-9732-458b-a785-82135e456dc5_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gWfZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a60602-9732-458b-a785-82135e456dc5_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/93a60602-9732-458b-a785-82135e456dc5_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:227277,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!gWfZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a60602-9732-458b-a785-82135e456dc5_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!gWfZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a60602-9732-458b-a785-82135e456dc5_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!gWfZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a60602-9732-458b-a785-82135e456dc5_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!gWfZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a60602-9732-458b-a785-82135e456dc5_3000x1688.jpeg 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>Answer: </strong><code>profiles#show</code></p><p><strong>Question: What IP is hitting this controller?</strong></p><p>Let&#8217;s group by IP and show as a pie chart:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BLjK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F857ca2b7-7e97-460f-8577-385735c5482a_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BLjK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F857ca2b7-7e97-460f-8577-385735c5482a_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BLjK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F857ca2b7-7e97-460f-8577-385735c5482a_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BLjK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F857ca2b7-7e97-460f-8577-385735c5482a_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BLjK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F857ca2b7-7e97-460f-8577-385735c5482a_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BLjK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F857ca2b7-7e97-460f-8577-385735c5482a_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/857ca2b7-7e97-460f-8577-385735c5482a_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:176851,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!BLjK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F857ca2b7-7e97-460f-8577-385735c5482a_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BLjK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F857ca2b7-7e97-460f-8577-385735c5482a_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BLjK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F857ca2b7-7e97-460f-8577-385735c5482a_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BLjK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F857ca2b7-7e97-460f-8577-385735c5482a_3000x1688.jpeg 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>Answer: 15.145.1.3</strong></p><p>We&#8217;ve spent less than 5 minutes asking questions of our observability tool and we understand what&#8217;s going on.</p><p>Let's recap.</p><ul><li><p>A server at 15.145.1.3 is scraping our app</p></li><li><p>They&#8217;re hitting the show action of the profiles controller</p></li><li><p>This floods the queue with analytics jobs</p></li><li><p>That delays password reset emails.</p></li></ul><h2>The fix</h2><p>Getting all that data was so quick and easy.</p><p>Now we understand what the app is doing, we can block the IP address.</p><p>Then go back into the graphs to check on the fix:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BLBT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09ee3ba2-b2b6-400e-a644-186a5bac6fcd_3000x1688.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BLBT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09ee3ba2-b2b6-400e-a644-186a5bac6fcd_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BLBT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09ee3ba2-b2b6-400e-a644-186a5bac6fcd_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BLBT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09ee3ba2-b2b6-400e-a644-186a5bac6fcd_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BLBT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09ee3ba2-b2b6-400e-a644-186a5bac6fcd_3000x1688.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BLBT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09ee3ba2-b2b6-400e-a644-186a5bac6fcd_3000x1688.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/09ee3ba2-b2b6-400e-a644-186a5bac6fcd_3000x1688.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:207439,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&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_!BLBT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09ee3ba2-b2b6-400e-a644-186a5bac6fcd_3000x1688.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BLBT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09ee3ba2-b2b6-400e-a644-186a5bac6fcd_3000x1688.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BLBT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09ee3ba2-b2b6-400e-a644-186a5bac6fcd_3000x1688.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BLBT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09ee3ba2-b2b6-400e-a644-186a5bac6fcd_3000x1688.jpeg 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>OK! The queue is recovering. Done.</p><p>No customer impact. Minimal disruption. Easy to debug. Easy to understand.</p><h2>Feelings, Revisited</h2><p>How does this feel?</p><p>As an engineer, it feels incredible.</p><p>I have the tools to ask any question of my app and get the results in seconds.</p><p>I can truly understand my app in production.</p><p>With this way of working, I&#8217;ve seen a common pattern.</p><p>I deploy a feature and I think I know how it&#8217;s working.</p><p>Then I go to the logs, do some queries, and find out how it&#8217;s <strong>actually</strong> working.</p><p>I often find myself thinking &#8220;ah&#8230; I didn&#8217;t expect it to do that&#8230;&#8221;.</p><p>This gets addictive. It also means I start to veer off asking other unrelated questions.</p><p>&#8220;I wonder why the traffic spikes on a Wednesday at midday?&#8221;</p><p>&#8220;I wonder why the vast majority of users are on iOS at 3am?&#8221;</p><p>&#8220;I wonder why the database keeps timing out?&#8221;</p><h3>Results</h3><p>Our downtime has dropped by 98% in 18 months.</p><p>We fixed 83% of our 500 errors.</p><p>Finally, we understand our Rails app in production.</p><h2>Want Help?</h2><p>Not every team has these kind of superpowers.</p><p>I&#8217;m holding a set of group coaching workshops.</p><p>$150 for a solo engineer and $2000 for your whole team.</p><p>Designed for Senior and Lead Rails Engineers.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://courses.joyfulprogramming.com/&quot;,&quot;text&quot;:&quot;Sign Up To Workshop&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://courses.joyfulprogramming.com/"><span>Sign Up To Workshop</span></a></p>]]></content:encoded></item><item><title><![CDATA[Kill The Three Pillars]]></title><description><![CDATA[Traces, logs and metrics. The three pillars of observability? Nonsense.]]></description><link>https://www.joyfulprogramming.com/p/kill-the-three-pillars</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/kill-the-three-pillars</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Tue, 09 Jul 2024 13:09:52 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/498df1e8-5897-4263-ac6e-ecca2ce5cb4c_420x300.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>The three pillars of observability are bullshit.</strong></p><p>There, I said it.</p><p>Normally I'm a lot more nuanced about things.</p><p>Normally I don't swear.</p><p>But today?</p><p>I've got a bit more time for these things.</p><p>The idea that you'll be able to understand your app if you implement &#8220;the three pillars&#8221;?</p><p><strong>Ridiculous.</strong></p><p>It's also a fiction that plays well into the hands of certain vendors.</p><p>Three pillars?</p><p>What about three chances to charge your company for the same data?</p><p>Ever seen an invoice from an observability vendor and had that sinking feeling in your stomach?</p><p>&#8220;What? How much?"</p><p>You look down the itemised bill&#8230;</p><p>Logs&#8230; OK&#8230; expensive but&#8230; I guess&#8230;</p><p>Traces&#8230; OK&#8230; per host costs&#8230; I see&#8230; hmm&#8230;</p><p>Metrics&#8230; holy cow&#8230; OK&#8230;</p><p>Then you add it all up.</p><p>It comes to an eye popping number.</p><p>And you&#8217;re spending it month after month.</p><p>Not one time. Every. Single. Month.</p><p>Five figures. Six figures.</p><p>That's not even the worst of it.</p><p>There's not even three pillars any more.</p><p>What about the whole plethora of other "pillars" that never get mentioned?</p><p>RUM? More money.</p><p>CI? More money.</p><p>Incident management? More money.</p><p>Error reporting? More money.</p><p>And yes, these things all can <em>link</em> between one another.</p><p>But honestly, having used these links? They're not that smart.</p><p>They require you to add some obscure tag into every log and ever metric and every trace and every RUM event and...</p><p>...JUST STOP.</p><p>What the three pillars are...</p><p>...is an expensive set of SILOs.</p><p>And that makes sense. The clue is in the name. It's a pillar.</p><p>The three pillars don't really communicate with each other.</p><h2>Not pillars. Data types.</h2><p>These are three <strong>data types</strong>.</p><p>They're the raw ingredients of observability.</p><p>And that's not all - I'd argue they can all be derived from a single data type</p><p>And that's traces.</p><p>What can we do instead?</p><p>Well...</p><p>For any observability data to be useful, it has to have these following attributes:</p><ol><li><p>Event</p></li><li><p>Wide</p></li><li><p>Context aware</p></li></ol><p>Here&#8217;s more information:</p><h2>1. Event</h2><p>An event is great.</p><p>You can aggregate events in any way you would like.</p><p>Events can have a name.</p><p>Each event can have many attributes.</p><p>You can count the uniques of any attribute of an event.</p><p>You can group by attributes.</p><p>Events are awesome.</p><h2>2. Wide</h2><p>The event must have a ton of attributes. The more the better.</p><p>Because ultimately at 3am when your site is down and you have to get it back up...</p><p>...when it looks like a hacker has broken into your system and you need to assess the damage...</p><p>...when you've been on a bug for 2 hours and you still can't figure it out...</p><p>...you couldn't have anticipated what you needed 4 months ago.</p><p>The most useful attributes? They're "high cardinality".</p><p>Huh? High cardinality attributes are IDs.</p><p>Git commit SHAs.</p><p>Host IDs.</p><p>Invoice IDs, Customer IDs, Event IDs. Request IDs. User IDs.</p><p>You get the idea.</p><p>Anything that will allow you to uniquely search across events for events related to a specific object.</p><p>But also we need more than just the IDs.</p><p>We need the relevant data at the time the event was issued.</p><p>Sending a business event? Let's include all the attributes from that event, right? Right.</p><p>Is there an error you want to create an event for that details something's gone wrong with the invoice?</p><p>Is the status of the invoice worth including in the event? Yup, you betcha.</p><p>Throw it all in there.</p><h2>3. Context aware</h2><p>Unless you've spent as long as I have splunking through logs, it's difficult to overestimate the importance of context.</p><p>What was going on when this event was triggered?</p><p>If you want to understand contexts?</p><p>Think about a stack trace.</p><p>A stack trace is context.</p><p>It&#8217;s very basic context - just an array of line numbers</p><p>And that&#8217;s why a stack trace is almost never enough to go off.</p><p>In fact, stack traces nowadays look like positively ancient.</p><p>They're an observability tool from a much simpler time when codebases were much smaller and distributed apps weren't a thing.</p><p>What we really want is nested contexts.</p><h2>Nested Contexts</h2><p>Take an example.</p><p>First, an HTTP endpoint was hit.</p><p>Then we went out to an API and made a request.</p><p>That came back within 0.1 seconds.</p><p>Then we enqueued 4 background jobs.</p><p>The first one took 13 seconds to complete.</p><p>Whilst that one was running, the second was also triggered.</p><p>And the error happened within that second background job.</p><p>This allows us to join the dots - to see what the system was doing whilst this error occured.</p><p>Now let's see which data types give us all those qualities.</p><p>Metrics? They give us none of these things.</p><p>They just say "hey, this number was X at this time".</p><p>Logs? They give us an event. Cool. They give us wide events. Yup.</p><p>However&#8230; context? They're pretty weak.</p><p>Building in context is possible and that's what we do at BiggerPockets - we have attributes that store which request was going on when the event was triggered.</p><p>Problem is, the context nesting is very limited.</p><p>Since you only have one event, you either have to create multiple events (which duplicates logs) or you can only have one layer of nesting (since the previous layers would override each other).</p><p>Traces? Now we're talking. These bad boys have <strong>everything</strong> above.</p><p>And best of all? You can derive metrics from traces. You can derive logs from traces.</p><p>The weakness of traces? They're much bigger than logs. They're more costly to store.</p><p>It's also harder to get your head around tracing than logging.</p><p>It's messier - you need to make contexts which often means passing blocks around.</p><p>The code becomes pretty convoluted fast and it obscures the meaning.</p><p>But? Traces are the ultimate observability data type.</p><h2>Common Questions</h2><p><strong>So are you saying metrics aren't useful?</strong></p><p>No! I'm saying they should be <em>derived</em> from more granular sources.</p><p><strong>So are you saying that structured logging isn't valuable?</strong></p><p>No! I'm saying you need to use it to send wide events.</p><p>But it's still got its limitations - no context.</p><p><strong>Are you saying that traces don't have value?</strong></p><p>No! I'm saying that traces can be appropriated to implement the "single wide event" ethos.</p><p>Lets forget the three pillars.</p><p>Let's favour traces - the most useful of the three.</p><p>Let&#8217;s instrument our apps with Open Telemetry.</p><p>Let&#8217;s vote with our feet.</p><p>Down with the three pillars.</p><p>Context aware, wide events forever.</p><h2>Agree with this?</h2><p>Sign the manifesto at <a href="http://kill3pill.com">kill3pill.com</a>!</p>]]></content:encoded></item><item><title><![CDATA[What's a wide context aware event?]]></title><description><![CDATA[Instead of "three pillars" I'm a big fan of context aware events. But what are they?]]></description><link>https://www.joyfulprogramming.com/p/whats-a-wide-context-aware-event</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/whats-a-wide-context-aware-event</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Thu, 20 Jun 2024 21:13:18 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/cde9018a-d0f9-4eaf-bd99-33e9d5e943bb_4032x3024.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There are two elements to these events:</p><p>1. Wide</p><p>2. Context aware</p><p>Let&#8217;s go.</p><h2>1. Wide</h2><p>A wide event is one that has lots of attributes.</p><p>Think 150. 200. </p><p>What goes into a wide event?</p><p>The common rule of thumb is "whatever you can think of that you have access to that's vaguely relevant to the current operation."</p><p>Many systems work off lots of small, narrow events, events with 20 or 30 dimensions.</p><p>For example, you have an event for "http request started".</p><p>Another event for "http API request started"</p><p>Another event for "http API request returned"</p><p>Another event for "http request finished".</p><p>Where does the duration live? Well... we only know the duration once the event has been delivered back.</p><p>So the duration has to live on the "returned" or "finished" events.</p><p>Likewise with the status.</p><p>But the request attributes are on the "started" events.</p><p>This means the information about a single event - the request - is smeared between several events.</p><p>This causes big problems when using these events to understand your system.</p><p>I've tried to do this - and it's always frustrating when exploring.</p><p>You keep having to do skips between duplicate events to get attributes that should have been there to begin with!</p><p>Instead, merge attributes that live together into a single wide event.</p><p>"HTTP request handled"</p><p>"HTTP API request made"</p><p>This halves the number of events.</p><p>It also makes each event richer - it's more self contained.</p><p>Don't stop there. Throw everything you can possibly think of into that event that might be useful.</p><p>Which Heroku dyno served the request?</p><p>Which host?</p><p>What was the incoming IP address? </p><p>What were all the headers?</p><p>What's the app name?</p><p>The server name?</p><p>A full list of attributes for inspiration:</p><p>https://www.honeycomb.io/blog/event-foo-what-should-i-add-to-an-event</p><h2>2. Context aware</h2><p>This is a doozy. And it's something I see teams not even understanding, let alone getting right.</p><p>Events are never stand alone!</p><p>They almost always happen in some kind of context - in a job, in a server, in an HTTP cycle, within an API request.</p><p>So it's critical to capture this context as much as possible.</p><p>You can now say "Show me all GET request that trigger a job of class X that makes an API call that's longer than 3 seconds"</p><p>Seriously powerful! </p><p>And it's all possible because the events are "stacked".</p><p>Here's where traces can do things that logs just cannot.</p><p>You can still have context in your logs - shared tags spread between multiple logs.</p><p>At BiggerPockets we tag all log requests that happen within jobs with the job attributes.</p><p>But what happens when two contexts try to write to the same log key? Well... they get overwritten. Not ideal.</p><p>Traces are far more powerful.</p><p>Traces consist of several spans. Each span can be nested within another.</p><p>Ever seen a flame graph?</p><p>Yup, that's a trace. And each span can be within another one.</p><p>This means you can do the query we have above no problems at all!</p><p>The big issue?</p><p>There are two - cost and performance.</p><p>Traces are slower than logs.</p><p>They are also a lot more costly and your expense can easily explode.</p><p>All it takes is a few extra spans in some critical high volume requests...</p><p>...and your bill skyrockets.</p><p>This is all true with observability 1.0 tools.</p><p>There's a new breed of tools - LightStep (ServiceNow) and Honeycomb - that support wide events.</p><p>They have simple pricing models - based on X million events.</p><p>This tends to be more cost effective than with traditional three pillar vendors.</p><p>This makes sense - after all, they're not charging for three different kinds of data types, just one.</p><p>That's not all - because they only deal with wide, context aware events, there's no silos, no different parts of the app to skip between.</p><h2>Open Telemetry - the enabler</h2><p>Open Telemetry is developing fast.</p><p>Open Telemetry, for those of you who aren't aware, is finally a standard for observability.</p><p>Idea is that you can standardise the tooling across your app.</p><p>Then the vendor you choose to ship traces, logs and metrics to will be an implementation detail.</p><p>It's a matter of time until we can switch between traditional Observability 1.0 tools and Observability 2.0 tools with three lines of code.</p><p>In that new world, many will stick with what they're familiar with - the traditional three pillars.</p><p>But it&#8217;ll open up options to those who are curious about wide context aware events.</p><p>Open Telemetry allows multiple exporters and processors.</p><p>So you&#8217;ll be able to try out different observability stacks with a handful of lines of code.</p><p>Don&#8217;t like them? No worries, just stop sending data and revert to using your current system.</p><p>Like them? Just switch off the old provider.</p><p>Vendor lock in will disappear like a bad dream.</p><p>Personally? I cannot wait.</p><p>Photo by <a href="https://unsplash.com/@neonbrand?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Kenny Eliason</a> on <a href="https://unsplash.com/photos/computer-screen-displaying-47k-Ah4F6g-OmgI?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></p>]]></content:encoded></item><item><title><![CDATA[Why I Help Teams With Observability]]></title><description><![CDATA[Because I want to help eradicate poverty. Delusional? Possibly.]]></description><link>https://www.joyfulprogramming.com/p/why-i-help-teams-with-observability</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/why-i-help-teams-with-observability</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sun, 02 Jun 2024 19:19:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Working Class Upbringing</h2><p>When I was a kid, I remember asking my Mum if I could have a Mars Bar.</p><p>It cost 25p.</p><p>"I'm sorry love" she said kindly, "but we just can't afford that right now".</p><p>We never went hungry, we always had warm coats and decent shoes. We were always tidily turned out and clean.</p><p>But money was always tight.</p><p>My parents were incredibly frugal and made ends meet. They never had any debt apart from a mortgage.</p><p>This gave me an early respect for money.</p><h2>Take From The Rich and Give To The Poor</h2><p>I was fascinated by the story of Robin Hood.</p><p>I was a well behaved child and followed all the rules perfectly. I did what I was told and feared authority.</p><p>The idea that someone would <strong>steal</strong> from the rich and give to the poor seemed wrong.</p><p>On the other hand there was a big part of me that loved the idea.</p><p>I distinctly remember thinking "if only we could figure out a way of doing that lawfully, without the stealing bit, I think the world would be a better place".</p><h2>Cost of Living Crisis</h2><p>Fast forward thirty years.</p><p>I don't need to tell you that the income gap is wider than ever.</p><p>I could quote studies and statistics to persuade you there's a cost of living crisis.</p><p>That's not necessary. We all know that this is real. People the world over are hurting.</p><p>The poor have been hurting for... well, decades.</p><h2>Captialism and Technology</h2><p>Big Technology companies make a lot of money.</p><p>If this is in exchange for valuable services, that's fair.</p><p>But year after year, there are companies profiting off ignorance and miseducation.</p><p>I remember a PPC friend of mine telling me he audited the Google Ads account for a new client.</p><p>"How much are you spending?"</p><p>"Oh, about 1.2 million dollars a year."</p><p>"So, what's your ROI on that?"</p><p>They didn't even undersatnd the question.</p><p>"Well... everyone should be doing Google Ads, right? So we do it."</p><p>At my friend's suggestion, the ads were switched off.</p><p>Sales stayed exactly the same.</p><p>$100,000 a month... completely wasted.</p><h2>Observability</h2><p>This same thing happens in the observabilibity space.</p><p>Companies are profiting off ignorance.</p><p>A friend of mine recently created a metric in an observability tool with a mere 3 dimensions.</p><p>Within a day his manager messaged him - "Would you mind switching that off please? The costs are ramping up fast."</p><p>My friend didn't understand the impact of high cardinality dimensions on metrics.</p><p>And one dimension he'd picked? IP address. Whoops.</p><p>Now, the software <strong>knew</strong> that IP address was high cardinality.</p><p>It <strong>knew</strong> that this would blow up the bill.</p><p>The software didn't try to stop him.</p><p>There was no alert when costs spiked.</p><p>The software just went ahead and started billing the company.</p><p>Thank goodness his manager was diligent. It could have been a lot worse.</p><p>Still, the company had just wasted $800 on instrumentation that had <strong>zero value</strong>.</p><p>Why should the observability tool company care? They just added a bit more to their bottom line.</p><p>I've talked to one company who are spending <strong>seven figure sums on logging</strong> ... <strong>every MONTH</strong>.</p><p>How can any company short of Microsoft possibly justify this kind of outlay?</p><p>And yet, again, why should the software vendor care? They made a killing.</p><p><strong>This is bad business. And it's wrong.</strong></p><p>It would be wrong if there was no cost of living crisis.</p><p>It would be wrong if there was no income inequality.</p><p>It would be wrong if there was no-one starving because they couldn't afford to eat.</p><p>But in the light of the state of our world, it seems worse than wrong - it seems <strong>outright insane</strong>.</p><h2>What's the Plan?</h2><p>First, I don't believe that politics can have an impact.</p><p>No amount of legislation will stop this kind of bad practice.</p><p>Money underpins most politics and so big businesses will always have their thumb on the scale.</p><p>I believe the only answer is to play capitalism at it's own game.</p><p>My plan is pretty simple.</p><ol><li><p>Educate engineers and companies how to observe their apps cost effectively</p></li><li><p>Charge 10-20% of the money I save over a year</p></li><li><p>Put a percentage of profits aside to fund universal basic income for a small number of families every year</p></li></ol><p>The business makes more money.</p><p>Engineering teams can move faster.</p><p>Engineers save time and a lot of frustration.</p><p>Customers get a more reliable web app.</p><p>Families that are struggling for money are pulled out of poverty.</p><p><strong>Everyone wins.</strong></p><p>Oh, apart from large greedy software companies trying to rip off customers and hoping they don't notice.</p><p>They lose. As well they should.</p>]]></content:encoded></item><item><title><![CDATA[Visualising Sidekiq Jobs With Flame Graphs]]></title><description><![CDATA[Visualising Sidekiq Jobs using traces is essential for production apps. Flame Graphs and traces are powerful tools. Let's use them.]]></description><link>https://www.joyfulprogramming.com/p/visualising-sidekiq-jobs-with-flame</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/visualising-sidekiq-jobs-with-flame</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Thu, 09 May 2024 20:56:39 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%2F44e2510e-d121-437d-a550-ca97837491fe_800x1121.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Problem</h2><p>I&#8217;ve worked with a lot of teams who use Sidekiq in their Rails app.</p><p>Not a single one understood what the jobs were doing.</p><p>They&#8217;d disagree.</p><p>They&#8217;d say something like:</p><blockquote><p>Oh, I just look at the Sidekiq Web UI. It&#8217;s great!</p></blockquote><h2>The Sidekiq Web UI</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZXSm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cc7590-2b32-4a66-9e59-d99be8bafda7_2880x1230.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZXSm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cc7590-2b32-4a66-9e59-d99be8bafda7_2880x1230.png 424w, https://substackcdn.com/image/fetch/$s_!ZXSm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cc7590-2b32-4a66-9e59-d99be8bafda7_2880x1230.png 848w, https://substackcdn.com/image/fetch/$s_!ZXSm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cc7590-2b32-4a66-9e59-d99be8bafda7_2880x1230.png 1272w, https://substackcdn.com/image/fetch/$s_!ZXSm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cc7590-2b32-4a66-9e59-d99be8bafda7_2880x1230.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZXSm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cc7590-2b32-4a66-9e59-d99be8bafda7_2880x1230.png" width="1456" height="622" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/61cc7590-2b32-4a66-9e59-d99be8bafda7_2880x1230.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:622,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:614738,&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_!ZXSm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cc7590-2b32-4a66-9e59-d99be8bafda7_2880x1230.png 424w, https://substackcdn.com/image/fetch/$s_!ZXSm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cc7590-2b32-4a66-9e59-d99be8bafda7_2880x1230.png 848w, https://substackcdn.com/image/fetch/$s_!ZXSm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cc7590-2b32-4a66-9e59-d99be8bafda7_2880x1230.png 1272w, https://substackcdn.com/image/fetch/$s_!ZXSm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cc7590-2b32-4a66-9e59-d99be8bafda7_2880x1230.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>This ^^ is, apparently, a great monitoring tool for production systems in 2024.</p><p>Not to piss on anyone&#8217;s chips&#8230; but this is <strong>woefully inadequate</strong>.</p><p>To understand why we need to back up.</p><h2>What&#8217;s the point of observability?</h2><p>Here&#8217;s my definition:</p><p><strong>To quickly answer any question of your app running in production.</strong></p><p>The more arbitrary and rare the question, the better your observability.</p><p>Over the last 2 years, I&#8217;ve asked many questions of Rails apps I help manage.</p><p>At the start our team couldn&#8217;t answer the most basic of questions.</p><p>As I&#8217;ve improved the observability, we can ask more complex and nuanced questions.</p><h2>Important Sidekiq Questions</h2><ul><li><p>Which incoming request enqueued this job?</p></li><li><p>Which user enqueued this job?</p></li><li><p>How many times did this job get retried?</p></li><li><p>How long did each retry take?</p></li><li><p>How long did all retries take?</p></li><li><p>Is exponential backoff working as I expect?</p></li><li><p>What caused the retry? Database request? API request?</p></li><li><p>What was the error that caused the retry?</p></li><li><p>Which line in the code caused the error?</p></li><li><p>Where does each retry fall in the performance percentiles?</p></li><li><p>Did this job go to the dead queue?</p></li><li><p>How long did it take from enqueue to execute?</p></li><li><p>Is the queue for this job performing within it's SLA?</p></li><li><p>Did the failure of the job happen at the end? Or the start?</p></li></ul><p><strong>These are incredibly basic questions</strong>.</p><p>I&#8217;ve been in the position where I can only answer a handful of these.</p><p>It&#8217;s a scary place to be - thousands of jobs being executed an hour without any clue what&#8217;s really happening.</p><p>The vast majority of these cannot be answered by the Sidekiq Web UI.</p><p>So we need something more powerful.</p><h2>I Wrote The Stupidest TODO App In The World&#8230;</h2><p>I&#8217;ve created an app to demonstrate these ideas.</p><p>It couldn&#8217;t be simpler.</p><p><a href="https://github.com/JoyfulProgramming/rails_observability_playbook">Here's the code on Github</a></p><p>Here is a screenshot:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DhoV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51bd4017-c193-45c9-8eac-1ac5cb874cc5_926x648.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DhoV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51bd4017-c193-45c9-8eac-1ac5cb874cc5_926x648.png 424w, https://substackcdn.com/image/fetch/$s_!DhoV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51bd4017-c193-45c9-8eac-1ac5cb874cc5_926x648.png 848w, https://substackcdn.com/image/fetch/$s_!DhoV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51bd4017-c193-45c9-8eac-1ac5cb874cc5_926x648.png 1272w, https://substackcdn.com/image/fetch/$s_!DhoV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51bd4017-c193-45c9-8eac-1ac5cb874cc5_926x648.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DhoV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51bd4017-c193-45c9-8eac-1ac5cb874cc5_926x648.png" width="926" height="648" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/51bd4017-c193-45c9-8eac-1ac5cb874cc5_926x648.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:648,&quot;width&quot;:926,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:100681,&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_!DhoV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51bd4017-c193-45c9-8eac-1ac5cb874cc5_926x648.png 424w, https://substackcdn.com/image/fetch/$s_!DhoV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51bd4017-c193-45c9-8eac-1ac5cb874cc5_926x648.png 848w, https://substackcdn.com/image/fetch/$s_!DhoV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51bd4017-c193-45c9-8eac-1ac5cb874cc5_926x648.png 1272w, https://substackcdn.com/image/fetch/$s_!DhoV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51bd4017-c193-45c9-8eac-1ac5cb874cc5_926x648.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><figcaption class="image-caption">A screenshot of my super simple TODO app</figcaption></figure></div><p>It downloads TODOs via an API.</p><p>It stores them in the database.</p><p>It has a super ugly UI.</p><p>When you hit &#8220;Refresh&#8221; it triggers a Sidekiq job.</p><p>The job clears out the database and repopulates it.</p><p>View the code: <a href="https://github.com/JoyfulProgramming/rails_observability_playbook">https://github.com/JoyfulProgramming/rails_observability_playbook</a></p><p>And yet&#8230;</p><h2>&#8230;I Introduced A Bug</h2><p>I wrote unit tests. I designed my code. I tested it locally.</p><p>And the code <em><strong>still</strong></em> had a glaring bug!</p><p>Typical.</p><p>So how can flame graphs help us debug this?</p><h2>Introducing Flame Graphs</h2><p>Here is a small section of a flame graph in Datadog:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!n80F!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44e2510e-d121-437d-a550-ca97837491fe_800x1121.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!n80F!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44e2510e-d121-437d-a550-ca97837491fe_800x1121.png 424w, https://substackcdn.com/image/fetch/$s_!n80F!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44e2510e-d121-437d-a550-ca97837491fe_800x1121.png 848w, https://substackcdn.com/image/fetch/$s_!n80F!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44e2510e-d121-437d-a550-ca97837491fe_800x1121.png 1272w, https://substackcdn.com/image/fetch/$s_!n80F!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44e2510e-d121-437d-a550-ca97837491fe_800x1121.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!n80F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44e2510e-d121-437d-a550-ca97837491fe_800x1121.png" width="800" height="1121" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/44e2510e-d121-437d-a550-ca97837491fe_800x1121.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1121,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:409510,&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_!n80F!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44e2510e-d121-437d-a550-ca97837491fe_800x1121.png 424w, https://substackcdn.com/image/fetch/$s_!n80F!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44e2510e-d121-437d-a550-ca97837491fe_800x1121.png 848w, https://substackcdn.com/image/fetch/$s_!n80F!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44e2510e-d121-437d-a550-ca97837491fe_800x1121.png 1272w, https://substackcdn.com/image/fetch/$s_!n80F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44e2510e-d121-437d-a550-ca97837491fe_800x1121.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><figcaption class="image-caption">A flame graph of an error in my Rails app instrumented with OpenTelemetry.</figcaption></figure></div><p>Let&#8217;s examine what we get:</p><h3>1. Overview</h3><ul><li><p>Error happened on the <code>within_five_minutes</code> queue</p></li><li><p>This attempt took 768ms</p></li><li><p>In the 51st percentile of retries</p></li><li><p>The attempt took 1.23% of the overall trace</p></li><li><p>Full duration of the trace is at the top left - 1m 2s</p></li><li><p>Happened in a web request (the little green globe beside rails_observability_playbook)</p></li><li><p>Attempt was in a consumer process</p></li></ul><p>All from that teeny tiny rectangle. Phew.</p><h3>2. Error</h3><p>That teeny tiny red sliver? That's where the error happened.</p><p>It happened after many database requests (the slivers of green blocks).</p><p>That means we know it&#8217;s a database error.</p><p>Turns out, the primary key we&#8217;re inserting on has already been used.</p><p>After a little sleuthing, I realised that jobs are overlapping and causing conflicts in the database.</p><h3>3. Latency</h3><p>This one piece of information has saved me countless hours.</p><p>Latency is the time from job being enqueued to starting.</p><p>Measured in ms, it&#8217;s 1.1 seconds in this example.</p><p>Seeing the latency in a single job? Yeah, fine.</p><p>But aggregate them over a week?</p><p>You'll get a real time view of how long jobs take in your queue.</p><p>This answers questions like:</p><ul><li><p>Which queues are overloaded?</p></li><li><p>Which queues are performing well?</p></li><li><p>Can you alert me on overloaded queues?</p></li></ul><p>All easy peasy.</p><p>Chef's kiss. Mwah. &#129292;</p><h3>4. Retries</h3><p>I've never seen anyone instrument job retries in production.</p><p>This boggles my mind. Again? Basic information.</p><p>Aggregating this information gives you answers to questions like:</p><ul><li><p>What are the average retries for a specific type of job?</p></li><li><p>What are the maximum number of retries?</p></li><li><p>How often are jobs sent to the dead queue?</p></li><li><p>Which jobs are not going to the dead queue?</p></li></ul><p>And many more.</p><p>In my example, I could see that some jobs retried a couple of times then succeeded.</p><h2>The Bottom Line</h2><p>I&#8217;ve had to manage incidents with and without this information.</p><p>I&#8217;ve had to fix bugs with and without this information.</p><p>So I&#8217;m speaking from the trenches when I say this&#8230;</p><p><strong>Instrumenting Sidekiq jobs makes debugging in production a dream.</strong></p><h2>Want help?</h2><p>I&#8217;m on a mission to teach Rails engineers how to observe their Rails apps in production.</p><p>Even if you&#8217;re not using Rails, many of the principles can be adapted for your technology.</p><p>Check out the resources below.</p><p><a href="https://www.linkedin.com/in/synapticmishap/">Hit me up on LinkedIn</a> with any questions you have.</p><h2>Resources</h2><ol><li><p><a href="https://www.notion.so/Rails-Observability-Playbook-fb1d870b30b547b6b029d5c48ab300a9?pvs=21">Rails Observability Playbook in Notion</a> - Free</p></li><li><p><a href="https://github.com/JoyfulProgramming/rails_observability_playbook">Example Rails App Instrumented With OpenTelemetry</a> - Free</p></li><li><p><a href="https://gumroad.joyfulprogramming.com/">Joyful Observability for Sidekiq in Rails</a> - $1</p></li></ol>]]></content:encoded></item><item><title><![CDATA[The Impact Of Feedback Loops]]></title><description><![CDATA[Poor feedback loops lead to bad software. And bad software has real consequences.]]></description><link>https://www.joyfulprogramming.com/p/the-impact-of-feedback-loops</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/the-impact-of-feedback-loops</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Tue, 30 Apr 2024 13:11:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!DGWb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea2763be-5034-4379-9e08-bfe6306a77f6_1362x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>How many software engineering teams work</h2><p>Many teams I've worked on work from tickets on a Jira board.</p><p>The goal is framed as "get all the tickets for this sprint done".</p><p>When that's done, they celebrate with a Slack post and everyone responds with an emoji.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DGWb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea2763be-5034-4379-9e08-bfe6306a77f6_1362x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DGWb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea2763be-5034-4379-9e08-bfe6306a77f6_1362x608.png 424w, https://substackcdn.com/image/fetch/$s_!DGWb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea2763be-5034-4379-9e08-bfe6306a77f6_1362x608.png 848w, https://substackcdn.com/image/fetch/$s_!DGWb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea2763be-5034-4379-9e08-bfe6306a77f6_1362x608.png 1272w, https://substackcdn.com/image/fetch/$s_!DGWb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea2763be-5034-4379-9e08-bfe6306a77f6_1362x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DGWb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea2763be-5034-4379-9e08-bfe6306a77f6_1362x608.png" width="1362" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ea2763be-5034-4379-9e08-bfe6306a77f6_1362x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:608,&quot;width&quot;:1362,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:271701,&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_!DGWb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea2763be-5034-4379-9e08-bfe6306a77f6_1362x608.png 424w, https://substackcdn.com/image/fetch/$s_!DGWb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea2763be-5034-4379-9e08-bfe6306a77f6_1362x608.png 848w, https://substackcdn.com/image/fetch/$s_!DGWb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea2763be-5034-4379-9e08-bfe6306a77f6_1362x608.png 1272w, https://substackcdn.com/image/fetch/$s_!DGWb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea2763be-5034-4379-9e08-bfe6306a77f6_1362x608.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>The metrics reflect that they're killing it.</p><p>Many sprints later and all the tickets that comprised a project have been deployed.</p><p>They're a little behind the deadline but hey - it's software.</p><p>They high five each other, have a little zoom meeting with some self congratulatory back slapping.</p><p>Why?</p><p>Oh because "it's important to celebrate the wins".</p><p><strong>Give me a f***king break.</strong></p><h2>Meanwhile in the real world</h2><p>Software is fundamentally unpredictable.</p><p>So if there's a plan that's more than 1 week long...</p><p>...and the plan has been executed...</p><p>...and it all looks like it's gone to plan?</p><p>The reality is way messier.</p><p>The reality is way more toxic.</p><p>The reality is this.</p><p>In order to meet the arbitrary deadline, the engineers skipped some unit testing.</p><p>In the same vein, they have code that's very difficult to understand "because we took on some technical debt and that's ok"</p><p>The designers chucked pretty pictures over the wall and the engineers implemented them.</p><p>Turns out there was no time in the plan for user testing.</p><p>The API they were calling was a bit flaky and they forgot to set timeouts and implement retries.</p><p>But that's just a detail.</p><p>Testing was done at the end and a ton of issues came out of it.</p><p>After every deploy the engineers count that ticket as done.</p><p>After all it's deployed! That's the same thing as done.</p><p>There's even a column in Jira - they renamed it from "Done" to "Deployed"</p><p>So much missing work.</p><p>For all the missing work, the same lame excuses are wheeled out time and time again.</p><p>"We'll put that on the backlog and do it later"</p><p>"We can add that as a fast follow"</p><p>"We don't have time for that"</p><p>"That's not a priority"</p><p>After the project has been released and announced at the company town hall in a 16 slide presentation that sent everyone including the engineers who built the damn thing to sleep...</p><p>Turns out they're onto the next project.</p><p>The tickets stay on the backlog.</p><p>They age.</p><p>The "backlog" is over a thousand tickets strong now.</p><p>This team has been very busy releasing projects just like this one over the last year.</p><p>But so what?</p><p>Here's so what.</p><h2>Meanwhile in the real, non-techie world</h2><p>A man is visiting his mum in a care home.</p><p>It's a stressful period of his life.</p><p>He's trying to juggle the relationship with his partner, meet his own needs, make the most of his time away from home, cope with the emotional impact of his mum in slow decline, tidy her house, make sure she's well looked after, do research for his business...</p><p>He's travelling from Preston to Leyland by train.</p><p>He's in a rush.</p><p>He opens the Trainline app.</p><p>He adds a favourite journey from Preston to Leyland.</p><p>He looks at the next train time.</p><p>Oh crap! it's in 2 minutes! platform 1!</p><p>He bombs up the stairs over to the platform.</p><p>Navigates other passengers moving at a snails pace.</p><p>The train is waiting at the platform.</p><p>He jumps on.</p><p>"Oh man... I'm in such a rush. I'd better slow down and check this is the right train"</p><p>He leans out the carriage.</p><p>"Blackpool North... 11:49... yup that's right."</p><p>That's what the app said.</p><p>He sinks into the seat, relieved. He made it!</p><p>Now to buy the ticket on the app.</p><p>Press buy...</p><p>Put his CVC code in...</p><p>Bit of a pain - but at least he can remember it.</p><p>Press the buy button...</p><p>...this is taking ages...</p><p>...come on hurry up...</p><p>...yes! Finally.</p><p>There's an annoying modal that comes up every time.</p><p>He hates it. "You're going to &lt;destination&gt;&#8221;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DlQS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c6b2b3f-4246-4d7c-a21a-a013e89712ac_1080x714.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DlQS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c6b2b3f-4246-4d7c-a21a-a013e89712ac_1080x714.png 424w, https://substackcdn.com/image/fetch/$s_!DlQS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c6b2b3f-4246-4d7c-a21a-a013e89712ac_1080x714.png 848w, https://substackcdn.com/image/fetch/$s_!DlQS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c6b2b3f-4246-4d7c-a21a-a013e89712ac_1080x714.png 1272w, https://substackcdn.com/image/fetch/$s_!DlQS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c6b2b3f-4246-4d7c-a21a-a013e89712ac_1080x714.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DlQS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c6b2b3f-4246-4d7c-a21a-a013e89712ac_1080x714.png" width="1080" height="714" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0c6b2b3f-4246-4d7c-a21a-a013e89712ac_1080x714.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:714,&quot;width&quot;:1080,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:57550,&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_!DlQS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c6b2b3f-4246-4d7c-a21a-a013e89712ac_1080x714.png 424w, https://substackcdn.com/image/fetch/$s_!DlQS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c6b2b3f-4246-4d7c-a21a-a013e89712ac_1080x714.png 848w, https://substackcdn.com/image/fetch/$s_!DlQS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c6b2b3f-4246-4d7c-a21a-a013e89712ac_1080x714.png 1272w, https://substackcdn.com/image/fetch/$s_!DlQS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0c6b2b3f-4246-4d7c-a21a-a013e89712ac_1080x714.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><figcaption class="image-caption">The annoying modal that forces you to wait for 3 seconds</figcaption></figure></div><p>There's no way of cancelling it or switching it off and pressing outside the modal does nothing.</p><p>And here it comes. With the usual stupid zoomy animation.</p><p><strong>"You're going to Kirkham and Wesham!"</strong></p><p>That's ... wait what...? He wants to go to Leyland...</p><p>He feels a creeping sense of dread.</p><p>Beep beep beep!</p><p>The doors close.</p><p>Too late. He realises what's just happened.</p><p>When he added the favourite journey he <strong>assumed</strong> that the app would then show him that journey.</p><p>It didn't.</p><p>Adding a new favourite journey, it turns out, sent him back to...</p><p>...the first favourite journey.</p><p>Which is in the exact opposite direction of his desired travel.</p><p>With a deep sigh he tries to find the next train that's actually correct.</p><p>It's another &#163;8.20. Total waste of money.</p><p>Oh... better not miss the next stop.</p><p>It'd be great if the app would give him notifications when he's approaching his stop.</p><p>It does give notifications - notifications about train strikes in Birmingham.</p><p>And how is that useful to him?</p><p>Oh. That's right. It isn't.</p><p>His phone regularly pings him about some garbage Tweet from someone he followed 10 years ago...</p><p>...but it can't show him what he needs right now.</p><p>Thankfully he's not so busy booking the correct train that he misses this stop too.</p><p>He jumps off.</p><p>Now to pay...</p><p>...enter the CVC AGAIN...</p><p>...press buy...</p><p>...come on...</p><p>...and now the app just hangs.</p><p>A spinner overlays the purchase button.</p><p>He waits. And waits. And waits.</p><p>Nothing. No error message. No update.</p><p>How long exactly does he have to wait until the app gives him feedback?</p><p>It's anyone's guess.</p><p>The seconds tick by. He closes the app.</p><p>Sigh.</p><p>Fine. He'll go to the ticket office.</p><p>No-one there.</p><p>Fine. The ticket machine then.</p><p>He presses "Quick buy".</p><p>Nothing happens.</p><p>He presses again.</p><p>Still nothing.</p><p>Finally he figures out you need to press and hold your finger for nearly two solid seconds.</p><p>Despite us all having magical devices in our pockets that are super responsive and we can play farmville at 3am and be distracted from our shallow lives by incredible graphics and animations..</p><p>...the same technology somehow hasn't translated into touch screens at train stations that actually, like, WORK.</p><p>The "common journeys" screen that comes up next has destinations like "Liverpool Lime Street".</p><p>What?! How is that a common destination? From freaking Kirkham?</p><p>Sigh. He needs Leyland.</p><p>Presses L.</p><p>Waits for 5 seconds.</p><p>E.</p><p>5 seconds...</p><p>Y.</p><p>5 seconds.</p><p>It would be easier to <strong>forge</strong> the train ticket at this point.</p><p>Presses buy.</p><p>Presses again.</p><p>Swipes his card.</p><p>After a 20 second delay... it's done!</p><p>He's going to...</p><p>...erm his destination.</p><p>It&#8217;s been a stressful experience.</p><p><strong>I know it was stressful, because this man was me.</strong></p><h2>What&#8217;s the link with observability?</h2><p>Almost every dysfunction above is a <strong>failure of</strong> <strong>feedback loops</strong>.</p><p>Unit testing is a feedback loop to make sure the software is working.</p><p>User testing is a feedback loop to make sure the humans understand how to use the software.</p><p>Defects in testing are a feedback loop. Feedback that the design is wrong.</p><p>Observability is another feedback loop.</p><p>The difference?</p><p><strong>Observability is a feedback loop in the only environment that matters - production.</strong></p><p>If you have poor observability, you&#8217;re working in the dark.</p><p>How many users are having problems? No idea.</p><p>Why, exactly, are they having these issues? We&#8217;ve some theories.</p><p>How is the app being used? We think we know.</p><p>Are we leaking revenue? Probably not.</p><h2>Wait&#8230; does observability replace testing?</h2><p><strong>Of course not.</strong></p><p>Many practices like unit testing and user testing catch a ton of issues and mistakes with both the design and functionality early.</p><p>Fixing problems early on is essential to moving fast.</p><p>We can catch a ton of obvious mistakes early. And we should.</p><p>But no matter how much we try to predict how our systems will behave from our chair&#8230;</p><p>&#8230;it&#8217;s a fools errand for anything but the simplest of apps.</p><p>We can only have real, actionable insights when the app is being used by real users in real situations.</p><p>I&#8217;ve deployed pieces of work with and without watching my observability tool.</p><p>I&#8217;ve seen the difference and it&#8217;s stark.</p><p>In almost every case with observability, I learned something new, whether intentionally or by accident.</p><p>And it&#8217;s actually, kinda, (<em>whispers)</em> <em>fun.</em></p><p>So do yourself a favour.</p><p>Find a way of adding some basic observability to your app.</p><p>Look at it often.</p><p>And you&#8217;ll start to see patterns you can act on to improve.</p><h2>Want help?</h2><p>I&#8217;m on a mission to teach Rails engineers how to observe their Rails apps in production.</p><p>Even if you&#8217;re not using Rails, many of the principles can be adapted for your technology.</p><p>Check out the resources below.</p><h2>Resources</h2><ol><li><p><a href="https://www.notion.so/Rails-Observability-Playbook-fb1d870b30b547b6b029d5c48ab300a9?pvs=21">Rails Observability Playbook in Notion</a> - Free</p></li><li><p><a href="https://gumroad.joyfulprogramming.com/">Joyful Observability for Sidekiq in Rails</a> - $1</p></li></ol>]]></content:encoded></item><item><title><![CDATA[Stop sending metrics. Start deriving them.]]></title><description><![CDATA[Metrics aren't that useful. Unless they're derived from deeper sources of information.]]></description><link>https://www.joyfulprogramming.com/p/stop-sending-metrics-start-deriving</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/stop-sending-metrics-start-deriving</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Wed, 24 Apr 2024 13:36:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Stop sending metrics from your app.</p><p>Huh? But aren't metrics really useful?</p><p>Nope. And here's why.</p><h3>1. Metrics provide answers to questions you already know.</h3><p>Question: At 3am when your site is down, it's not obvious what's going wrong and you're half asleep, will metrics help?</p><p>Maybe. If you're lucky, you've encountered an issue you already thought might happen.</p><p>But maybe, just maybe, this is a non trivial issue that you've never seen before.</p><p>I've experienced this many times.</p><p>Here's how it goes:</p><p>1. Oh crap. What's going on?</p><p>2. Check all 10 metrics. All look good.</p><p>3. Now what?</p><p>If every production issue you have falls into 5-10 different repeatable scenarios, great!</p><p>But in 2024 our apps are a *lot* more complex than this.</p><h3>2. Metrics result in dead ends.</h3><p>Question: If the metric *is* showing that there's a problem, can it explain *why*?</p><p>No. Of course it can't.</p><p>It's just a number.</p><p>Imagine if you went to the doctor.</p><p>"I'm fainting all the time... what's going on?"</p><p>"OK, let me take your blood pressure. Wow - that's high. Well, thanks for coming to see me. Laters!"</p><p>I don't think you'd be very happy.</p><p>Unless you can correlate the metric with other anomalous activity, you're not much further forwards towards a solution.</p><p>I've experienced this too.</p><p>Here's how it goes:</p><p>1. Oh crap. What's going on?</p><p>2. Check all 10 metrics. Oh! Background job queue latency is massive.</p><p>3. It's something to do with the background jobs.</p><p>4. Now what?</p><p>OK, <strong>sometimes</strong> you get lucky and there are other metrics that hint at the reasons.</p><p>Maybe you've seen a flood of errors in your error tracking software.</p><p>All those other times? You're on your own.</p><h3>3. Metrics can get expensive.</h3><p>Question: If you set up a feature in your monitoring tool and 3 weeks later you realise because of one tiny misunderstanding the metric has racked up $7,000...</p><p>...is that a good feature?</p><p>Erm.... NO.</p><p>Months ago I had this exact problem.</p><p>I'd set up a metric, added a few dimensions to it. Just a handful.</p><p>A week later my manager pinged me...</p><p>"Looks like this metric is getting expensive... can we turn it off?"</p><p>Turns out that we were being charged extra for each unique value of those "few dimensions".</p><p>No worries. There was only... 220 unique values... charged at... oh wait&#8230;</p><p>...well I'm sure you can fill in the gaps.</p><p>We switching off the metric before we'd even had a chance to use it.</p><p>Metrics have their place.</p><p>But they should be <strong>derived</strong> from other tools.</p><h2>What&#8217;s the plan Mr Observability Guru?</h2><p>What should you be doing instead? Try these:</p><h3>1. Tracing.</h3><p>Tracing can give you a proper structured map of the call stack.</p><p>That's infinitely more useful than a metric.</p><p>Make sure you add all the context you care about to each span.</p><p>I&#8217;ll be discussing this in more detail in a future post.</p><h3>2. Ditch traditional tools.</h3><p>If you&#8217;re already invested in an observability tool, <strong>use it</strong>.</p><p>However - there are always exceptions.</p><p>Sometimes you want to make a break with the past.</p><p>Using a next generation tool like <a href="https://www.honeycomb.io/">Honeycomb</a> can get you an awful lot.</p><p>Clear, transparent pricing based on events.</p><p>A killer UI with instant drill down into the parts of your stack you care the most about.</p><p>Fully <a href="https://opentelemetry.io/">Open Telemetry</a> compatible.</p><p>And proper, structured, aggregateable events.</p><h3>3. Logging.</h3><blockquote><p>Sigh. Seriously? I looked at the logs the other day and they told me the square root of f**k all!</p><p>Engineer Who Hates Logs</p></blockquote><p>Yup, I know, I've experienced useless logs too.</p><p>But over the last year I've invested heavily in setup of logs.</p><p>And to be crystal clear, when I say &#8220;logs&#8221;, I mean &#8220;structured logs with 50-200 dimensions that prioritise IDs&#8221;.</p><p>Like&#8230; proper, modern logs. Not logs stuck in the 1980s.</p><p>Metrics can be derived from logs.</p><p>I&#8217;ve aggregated logs together into metrics. Totes doable.</p><p>This means you can go to the source of the data!</p><p>You can look at <strong>why</strong> that weird slowdown of the site happens on a Monday morning.</p><p>Not just that it happens.</p><p>Proper logging is paying off dividends for my current client.</p><p>Engineers can, in 30 seconds, diagnose a whole bunch of problems from first principles.</p><p>Click&#8230; type&#8230; click&#8230; &#8220;Ah! So that&#8217;s where the errors are coming from&#8230;&#8221;</p><p>However, it takes <strong>a lot of setup</strong>.</p><p>And that&#8217;s where I can help.</p><h3>Free Rails Observability Playbook</h3><p>Check out my <strong><a href="https://joyfulprogramming.notion.site/Rails-Observability-Playbook-fb1d870b30b547b6b029d5c48ab300a9?pvs=4">free playbook for observability in Rails</a>.</strong></p><p>Sign up for Notion and clone into your account.</p><p>Then play around with it and have fun.</p><p>It&#8217;s still a work in progress, so feedback is very welcome!</p><ol><li><p>What is helpful about this playbook?</p></li><li><p>What would you like to see that&#8217;s missing?</p></li><li><p>Is the survey a good idea?</p></li></ol><p>Have a great week and I&#8217;ll see you* soon!</p><p>* Not literally, I&#8217;m typing this in Belfast, Northern Ireland. Internet and all that.</p><p></p>]]></content:encoded></item><item><title><![CDATA[Using Observability To Discover Dead Code]]></title><description><![CDATA[How I discovered 27% of our Rails app was dead.]]></description><link>https://www.joyfulprogramming.com/p/using-observability-to-discover-dead</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/using-observability-to-discover-dead</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Tue, 16 Apr 2024 22:32:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>27% of our app was dead code.</strong><br><br>Nobody realised for 5 years.<br><br>Until, with help from the logs, I did some analysis.<br><br>Observability is about understanding how your app behaves in production.<br><br>This is a huge benefit, as anyone who has experienced an app that's well set up for this will tell you.<br><br>But there are related benefits that go unsung.<br><br>In our case, analysis of dead code was one of them.<br><br>27% dead. What did that mean?<br><br>* 27% of the test suite was wasted time<br>* 27% wasted slug size in Heroku<br>* 27% slower upload time for the app bundle<br>* 27% more code to grep through<br><br>And a bunch of other benefits I'm sure you can imagine.<br><br>How did I figure this out in 2 hours?<br><br>(I was using Rails, YMMV)<br><br>1. List out all the routes<br>2. Search the logs for HTTP requests in the last 90 days<br>3. Aggregate by controller and action<br>4. Download the dataset in CSV<br>5. Compare the two lists<br>6. Any route that was in the app but never hit is dead<br><br>Want to be able to ask these questions of your app in minutes?<br></p><h2>Download the playbook</h2><p>I'm putting together a playbook on how to achieve this in Rails.</p><p>Want to take an early sneak peek?</p><p><a href="https://joyfulprogramming.notion.site/Rails-Observability-Playbook-fb1d870b30b547b6b029d5c48ab300a9?pvs=4">Check out the playbook in Notion</a></p>]]></content:encoded></item><item><title><![CDATA[Observability - The New Focus]]></title><description><![CDATA[I&#8217;m changing the focus of this newsletter to be about Observability. If you don&#8217;t care about observability after reading this, please unsubscribe. &#128578;]]></description><link>https://www.joyfulprogramming.com/p/observability-the-new-focus</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/observability-the-new-focus</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sun, 07 Apr 2024 22:53:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The problem</h2><p>Imagine a company that writes a CRM.</p><p>Here&#8217;s how developing a new feature might look:</p><ol><li><p>Customers ask for a feature &#8220;Please can I have email notifications when a contact messages me?&#8221;</p></li><li><p>Business gives it a green light. Because they like the idea. &#8220;Email notifications will be great! I love it when I get notifications!&#8221;. &#8220;But will it move the needle?&#8221; &#8220;Who cares! Email notifications baby!&#8221;</p></li><li><p>Product owner makes a list of tasks for how to approach the work over many months. It looks a lot like a gantt chart, but it&#8217;s not because it&#8217;s in Google Sheets you see. The product owner has no engineering experience, no domain knowledge and has no real understanding of what&#8217;s possible. They will plan everything out because building software is just like building a house - you make the plans then build it.</p></li><li><p>User experience designers create some pretty pictures. They look good. There&#8217;s not much thought put into making it interactive. That&#8217;s a problem for the engineers. No wireframes. No information architecture. There&#8217;s little to no user testing. After all, users make things so messy and designers like staying in Figma - it&#8217;s such a cool app. When they&#8217;re done designing it, they&#8217;re really happy. The designs are copy pasted into cards that will be handed off to engineers.</p></li><li><p>Engineers break down the list of tasks and guesstimate points. After the first week of tasks is planned, they&#8217;re guessing and they know it. The points are nonsense and we&#8217;re all going to realise that soon enough.</p></li><li><p>Engineers take the tickets and work on coding up the solution. The designers love seeing engineers taking their ace Figma design and making it work. The engineers leave 14 comments on their carefully built designs pointing out problems. The designers push back and eventually the engineers just do what they ask, even though it makes the code more difficult to manage.</p></li><li><p>When it comes to the functionality, engineers go fast and break things, because that&#8217;s what Facebook do. They hammer in the features as fast as their fingers will let them. They love building! They&#8217;ve heard something about &#8220;clean code&#8221; but they don&#8217;t have time for that. Plus they can clean it up later.</p></li><li><p>They work with PRs because the need code reviews and it&#8217;s a best practice. Everyone reviews PRs in their own style at their own pace. Some are thorough, others have a shortcut made for &#8220;LGTM&#8221; because that takes too much time to type. Messages fly around Slack asking for approvals.</p></li><li><p>All the engineers work independently because that&#8217;s how you make teams go much faster. Plus, the engineers are all introverts and who wants to sit in another meeting?</p></li><li><p>No-one in the business looks at what the engineers are making. After all, the engineers don&#8217;t make the effort to talk to them. Ever. The business leaves them to do their super smart coding thing and they&#8217;ll review what they&#8217;ve done later. Email notifications will be easy - every app has them.</p></li><li><p>Every engineer has a different coding style. Some people write tests that are barely readable. Others swear by mocking. Others write the odd test that tests nothing. Still others write no tests at all. Missing tests aren&#8217;t too much of a problem. We don&#8217;t have time to write them, but they&#8217;ll do it later.</p></li><li><p>When tests fail, engineers spend hours fixing them using the debugger because it&#8217;s difficult to understand what&#8217;s going on. Engineers who don&#8217;t like writing tests think this is ridiculous. What&#8217;s the point in being slowed down? All these tests do is add extra baggage but someone on the Internet told these engineers to write them and so they do. Ah well, group think!</p></li><li><p>The code uses a lot of inheritance because that&#8217;s what&#8217;s great about object oriented programming. Class names are based on the design pattern the engineer has just learned that weekend so that other engineers can understand the code better.</p></li><li><p>Three weeks in and it&#8217;s clear someone didn&#8217;t do their planning correctly because cards that seemed easy and were given 1 point seem to be taking forever. Every card seems to breed three more. The product owner is increasingly stressed that the team is falling behind.</p></li><li><p>The existing code tricky is to change and they&#8217;re behind, but that&#8217;s OK - the engineers have a plan - copy and paste existing code then tweak it. Subclassing is a great idea to reuse existing code too.</p></li><li><p>The engineers have read on the internet that accumulating tech debt is sometimes necessary and the timescale seems impossible, so now is the perfect time to take some short cuts. They&#8217;ll can make it correct later. Even the engineers who are keen on writing tests aren&#8217;t doing so now.</p></li><li><p>When the feature is ready after 2 months, someone in the business who isn&#8217;t as smart as all those engineers takes a look. First impressions? It seems a bit weird. The email notifications seem to have overlapping rectangles in Gmail. The font is strange and the colours are garish. The settings screen looked good on paper&#8230; but when you use it, the buttons are glitchy. It wasn&#8217;t clear on the design, but the settings hide other parts of the page that looks weird and unintuitive.</p></li><li><p>The business feeds this back. The product owner is nervous. They tells the business that they&#8217;re really behind so they won&#8217;t be able to fix most of this. They promise to fix the overlapping rectangles at least as that&#8217;s the most important issue.</p></li><li><p>When the engineers see bug tickets in the backlog they&#8217;re not happy. It feels like they&#8217;re going backwards. The first issue to tackle is called - &#8220;Fix overlapping rectangles&#8221; - it has three paragraphs of waffly text in the description. When the engineer tries to reproduce the issue they can&#8217;t and they tell the product owner as much. The card is moved to blocked waiting on investigation. After 3 days of trying to fix it in their local dev environment the engineers give up.</p></li><li><p>The product owner goes back to the business with their tail between their legs. &#8220;Sorry, we worked on the overlapping rectangles and couldn&#8217;t fix it. We&#8217;ll fix it later. Both the PO and The Business knows that this won&#8217;t happen. But they both nod their heads and agree to not rock the boat.</p></li><li><p>The feature enters QA. After a few days, the testers have found 78 bugs. The testers are delighted. Everyone else is pissed.</p></li><li><p>The product owner spends 2 days putting all 78 bugs into Jira, complete with their position on an effort / impact matrix and story pointing them all. Jira already has 1425 tickets in the backlog, so those extra 70 floating around in there have lots of friends to keep them company.</p></li><li><p>Time is getting tight and the business are leaning on them heavily now - this feature is turning out to be very expensive - so they fix the most important 8 bugs and leave the rest - they&#8217;ll fix those later. After all, every bit of software has bugs.</p></li><li><p>None of the 8 bugs have a regression test written for them because they&#8217;re getting short on time. Besides, the engineers are pretty sure those have been fixed now. When the product owner points to a bug they&#8217;ve come across, the engineer defends their work - &#8220;it works on my machine&#8221;. The product owner gives up. Bigger fish to fry.</p></li><li><p>Finally the feature is released. The team merges the last PR, waits 28 minutes for the tests to pass. The moment the commit is released the team high fives. They did it! They celebrate their win by posting onto Slack and everyone chimes in adding emojis. There are 21 emojis! Wow - this feature release is a huge success. Everyone needs a 2 week holiday. But it&#8217;s done.</p></li><li><p>The team wants to do the cleanup - there are so many outstanding issues. But sadly, that time that was allocated doesn&#8217;t appear. They have 2 days to recover then it&#8217;s time for another urgent feature.</p></li><li><p>Whilst they&#8217;re developing this next feature, the team start getting support requests mentioning the email notifications. Seems like some customers are not getting notifications. More concerningly, some customers are being repeatedly spammed every hour. The app really shouldn&#8217;t be doing that.</p></li><li><p>The engineers look into it. The error shows in Sentry - it&#8217;s buried under 534 other errors so they didn&#8217;t notice it. One engineer tries to figure out what&#8217;s going on. The error contains almost no useful information. Just a cryptic message - &#8220;Email wasn&#8217;t sent&#8221; - with a stack trace. The stack trace points back to a try / catch block that swallows the error and logs a generic message.</p></li><li><p>The engineers can&#8217;t replicate this locally. They&#8217;re in trouble and they know it. There are now 132 support requests with exactly the same error. There are over 700 errors in Sentry now. The pressure mounts.</p></li><li><p>They redeploy the code, taking the try catch out. The error is hardly more useful. &#8220;SSL disconnect - retrying&#8230;&#8221;. They open New Relic as a last ditch attempt - no-one really looks in there much - and they go to the logs. Nothing of any use - just a bunch of text statements.</p></li><li><p>48 hours after the first support request, they&#8217;re still no nearer to debugging the problem. They&#8217;ve added four log statements that point to some kind of strange timeout with the email platform. The CEO is frantic and hounds the product owner for updates. The engineers are stressed out of their minds. The testers feel bad.</p></li><li><p>4 days after the initial storm, they figured out the issue. They decide to patch it and move on. They&#8217;re already late with the next feature after all and stakeholders aren&#8217;t happy. They decide to fix the other Sentry errors later.</p></li></ol><p><strong>None of this is joyful.</strong></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Joyful Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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><h2>Nice rant. What&#8217;s the point?</h2><p>In the story above there are a lot of antipatterns. I&#8217;ve never seen all these at any one workplace - this is deliberately exaggerated. But hopefully you&#8217;ve seen enough of these problems to be able to identify.</p><p>I&#8217;ve tried to challenge some of these anti-patterns throughout my career and I&#8217;ve not been massively effective.</p><p>I&#8217;ve tried to educate on the merits of software design. It&#8217;s my lifelong passion, but it seems to be an uphill battle to persuade folk that this is something worth investing in. I also find software design to be a very difficult discipline to measure the impact of.</p><h2>Observability is my new focus</h2><p>However, I&#8217;ve seen that improving observability is something I <strong>can</strong> have a measurable impact with. I&#8217;ve learned how to take a Rails app that&#8217;s a black box and turn it into an app that you can at least understand some of the basics.</p><p>I&#8217;ve seen the value of this on other engineers and on the business.</p><p>Everything I&#8217;ve learned I&#8217;ve learned on my own. As far as I&#8217;m aware there&#8217;s next to zero information on the web for how to do this in Rails properly. And there&#8217;s almost zero information for other frameworks too.</p><p>The articles I&#8217;ve found about structured logging are utterly useless. They just about explain what it is, give one example in Java then treat the problem as &#8220;fixed&#8221;. Hilarious.</p><p>Other articles on observability are also useless - they talk about the three pillars (if I read another generic explanation of logs traces and metrics, I will go actually mad) and &#8220;why observing in production is really important&#8221; but give zero real life examples. No useful advice. No details.</p><p>How do I understand my background jobs?</p><p>How do I understand the errors in my Sentry?</p><p>How do I track user behaviour?</p><p>What conventions should I use in my structured logs?</p><p>&#8220;Oh, just install New Relic / Datadog / Prometheus.&#8221;</p><p>OK&#8230; and then what?</p><p>It&#8217;s like asking &#8220;How should I invest my money?&#8221; and being told &#8220;Well, there&#8217;s stocks, bonds and Crypto. They&#8217;re volatile in different ways. Make sure you save enough for retirement. Best of luck!&#8221;</p><p>There&#8217;s now an <a href="https://www.amazon.co.uk/Observability-Engineering-Achieving-Production-Excellence/dp/1492076449">excellent book - Observability Engineering: Achieving Production Excellence - by Charity Majors, Liz Fong Jones and George Miranda</a>. This seems like a significant ray of light at the end of this very dark, very depressing tunnel of black box footling around, blindfolded by lack of accurate data&#8230; or any data at all.</p><p>I want to be crystal clear - I don&#8217;t see myself as any kind of expert. I&#8217;m an expert in observability in the same way as a 12 year old is an adult. Compared to a toddler, maybe. Compared to a real adult? Not a chance.</p><p>The Rails apps I work with are, after 2 years of part time observability improvements, are at 40% of where I want to be.</p><p>I&#8217;m obsessed with this topic and I&#8217;m always pushing the limit of what I know to deliver more value in observing an app in production.</p><p><strong>I&#8217;m on a journey to learn this too.</strong> Come along for the ride!</p><h2>I&#8217;ll share all my learnings here</h2><p>The learnings I&#8217;ll share with you in this newsletter are a <strong>tiny fraction</strong> of what I think the standard for observability should be for <strong>every app we have with any significant workload in production</strong></p><p>I&#8217;ve talked to some engineers who seem to be quite happy with what they call &#8220;the basics&#8221; in place, which really means &#8220;we installed the standard tools, added lograge and I suppose it&#8217;s good enough for now&#8221; which really translates to &#8220;we don&#8217;t have a clue how to debug anything in production but we just create a ticket to add more logging and the bug gets punted off. Plus, we&#8217;re onto the next feature now&#8221;.</p><p>I&#8217;m not sure if it&#8217;s that engineers don&#8217;t care that users are having a terrible time, don&#8217;t have bandwidth to take on more work or just don&#8217;t even have headspace to think about it.</p><p>In any case, the chaos caused by this <strong>complete lack of interest</strong> in how the app is <strong>actually</strong> working in production is incalculable. Apps crash, users lose data, the app randomly freezes on a Tuesday, DDOSes, hackers, scams, overage bills, huge P95s - and on and on.</p><p>At one point I thought I wanted to build features. Now I know different. I want to help you understand the features you already have.</p><p>Please reach out if you have any questions or comments.</p><p><strong>Thanks for reading. Onwards and upwards!</strong></p><p>PS - A HUGE thank you to <a href="https://www.linkedin.com/in/irinastanescu/overlay/about-this-profile/?lipi=urn%3Ali%3Apage%3Ad_flagship3_profile_view_base%3B8r2gQM%2BISRa2sIth0Zciww%3D%3D">Irina Stanescu</a> who writes <a href="https://www.thecaringtechie.com/">The Caring Techie</a> for inspiring me to start writing again. You rock.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Joyful Programming is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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[The SOLID Edition]]></title><description><![CDATA[SOLID is pretty old now. Are there newer alternatives that are more relevant? Let's examine three.]]></description><link>https://www.joyfulprogramming.com/p/the-solid-edition</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/the-solid-edition</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sun, 16 Jul 2023 13:18:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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>SOLID was invented by Uncle Bob Martin. It&#8217;s a <a href="https://en.wikipedia.org/wiki/Backronym">backronym</a>. </p><p>The author started with the letters and reverse engineered words to fit them.</p><p>There are many critiques out there.</p><p>Kevlin Henney&#8217;s <a href="https://www.youtube.com/watch?v=tMW08JkFrBA">The SOLID Design Principles Deconstructed</a> is a good place to start.</p><p>In this newsletter we&#8217;ll examine three alternatives.</p><ol><li><p>Four Rules of Simple Design - Kent Beck</p></li><li><p>SHOC - Jason Gorman</p></li><li><p>CUPID - Dan North</p></li></ol><h2>1. Four Rules of Simple Design</h2><p>It's shocking how few engineers have heard of this.<br><br>And yet it's one of the best ways I know of evolving a design.<br><br>Simple code adheres to all four rules.<br><br>Rule 1. The code works.<br>Rule 2. The code expresses intent<br>Rule 3. The code has no duplication (including ideas)<br>Rule 4. The code has no dead parts<br><br>We go through the rules sequentially.<br><br>We don't go onto the next rule until the previous rule has been satisfied.<br><br>Rule 1. The code works<br><br>Note it doesn't say "passes all the tests".<br><br>Tests can be misleading, depending on how they're written.<br><br>Make sure the code works and there are no edge cases that you're aware of.<br><br>Rule 2. The code expresses intent.<br><br>Put another way - good names.<br><br>Read the code literally line by line.<br><br>Does every line make sense?<br><br>Does it read like English?<br><br>Adjust the names until it does.<br><br>Rule 3. The code has no duplication.<br><br>There are no pieces of code that are duplicated.<br><br>This also applies to duplication of ideas.<br><br>If there's a similar thing expressed elsewhere but the code is different...<br><br>...that's still duplication.<br><br>Eliminate it.<br><br>Rule 4. The code has no dead parts<br><br>Debatably, this is the easiest rule to verify.<br><br>There's no unused code.<br><br>So in summary:<br><br>Rule 1. The code works.<br>Rule 2. The code expresses intent<br>Rule 3. The code has no duplication (including ideas)<br>Rule 4. The code has no dead parts<br><br>I've found following these four rules pivotal in building software that's well designed.</p><h2>2. SHOC</h2><p>Invented by Jason Gorman</p><p>See <a href="https://buff.ly/42rmUo7">this SHOC video</a> where he outlines the principles.<br><br>* Swappable<br>* Hides internal workings<br>* One job<br>* Client driven interfaces<br><br>I find this a much better alternative:<br><br>* Easier to understand<br>* Applies to modules, not just classes<br>* Plain English</p><h2>3. CUPID</h2><p>Invented by Dan North.</p><p>He introduces <a href="https://dannorth.net/2022/02/10/cupid-for-joyful-coding/">CUPID in this blog post</a>.</p><ul><li><p>Composable: plays well with others</p></li><li><p>Unix philosophy: does one thing well</p></li><li><p>Predictable: does what you expect</p></li><li><p>Idiomatic: feels natural</p></li><li><p>Domain-based: the solution domain models the problem domain in language and structure</p></li></ul><p>CUPID is about finding joy in coding again.</p><p>Instead of making the codebase &#8220;habitable&#8221;, Dan says we can and should shoot much higher.</p><p>Let&#8217;s make finding defects and adding features joyful!</p><p>He also says code that fits in your head is an important metric.</p><p>There&#8217;s been <a href="https://www.entropywins.wtf/blog/2017/02/17/why-every-single-argument-of-dan-north-is-wrong/">a lot of pushback</a> from the community.</p><p>Personally I think Dan&#8217;s really onto something.</p><p>I&#8217;ve found myself building non idiomatic castles in the sky to try to make code that&#8217;s easy to change.</p><p>Others find this kind of code difficult to work with and understand.</p><p>How can we make software that&#8217;s easy to change and joyful to work with?</p><p>Only by breaking things apart and creating the right kind of abstractions.</p><p>Summary</p><p>So there you have it. Three alternatives to SOLID.</p><p>Which do I use?</p><p>I think all three have their merits.</p><p>I&#8217;ll give the last word to Denis Cahuk based off his <a href="https://www.linkedin.com/feed/update/urn:li:activity:7063097401900716032?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7063097401900716032%2C7063135129501671424%29">excellent comment</a>:</p><blockquote><p>I don't think these acronyms are a good substitute for education if the acronym (and the underlying meanings) don't simply express principles and patterns.<br><br>The S in solid stands for SRP. Who can explain the S in SRP so that we understand a 5th of SOLID.<br><br>The L in SOLID stands for LSP. The L stands for Liskov, as in Barbara Liskov. She is still alive. Did anyone watch any of her talks on LSP?<br><br>The D in SOLID stands for DIP. That's DI for dependency inversion (principle). That doesn't mean you're using an autowire container, the emphasis is on inverting responsibilities and call-stack hierarchies.<br><br>All this information is public and easy to google. Yet after 10-15 years in the industry I still have to look them up because they simply don't make sense and never did.<br><br>SOLID was invented to sell a book about Clean Code and Architecture. It was very successful in that. However, it isn't a very successful no-brainer for developers who didn't have the opportunity to work in a great team and have real, tangible problems beyond the ability to adopt SOLID or Clean Architecture.</p></blockquote><p>If you&#8217;ve not subscribed to Denis&#8217; excellent <a href="https://craftingtechteams.substack.com/?utm_source=substack&amp;utm_medium=web&amp;utm_campaign=substack_profile">Crafting Tech Teams</a>, give it a shot. It&#8217;s chock full of interesting information!</p><h2>What Next?</h2><p>I have two options:</p><ol><li><p><a href="https://calendly.com/synapticmishap/design-help-group-coaching">Book a 1 hour coaching session</a> (normally $200, FREE for a limited time)</p></li><li><p><a href="https://softwaredesignsimplified.com/b/ruby">Buy Software Design Simplified </a>- an introduction for curious, driven engineers</p></li></ol><p>Have a great week!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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[The Unison Edition]]></title><description><![CDATA[There's a new language kid on the block - and it's got huge consequences for design.]]></description><link>https://www.joyfulprogramming.com/p/the-unison-edition</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/the-unison-edition</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sat, 24 Jun 2023 10:43:06 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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>Bit of a change of pace this week.</p><p>I'll be taking a look at a revolutionary new language.</p><p>Why? What's this got to do with software design?</p><p>I think Unison will change the game of software design.</p><p>Trust me - I'm a new language skeptic.</p><p>Any time I hear of a new language I roll my eyes skywards. "Not another one..."</p><p>However, this new language, coupled with AI, will allow us to build applications we can't dream of today.</p><p>Let's get into it.</p><p>The Big Idea</p><h2>The Big Idea</h2><p>Unison is a typed functional language.</p><p>Each Unison definition is identified by a hash of a syntax tree.</p><p>Put another way, Unison code is content-addressed.</p><p>Example:</p><p><code>increment : Nat -&gt; Nat</code></p><p><code>increment n = n + 1</code></p><p>The syntax tree that Unison sees is:</p><p><code>increment = (arg1 -&gt; #a8s6df921a8 arg1 1)</code></p><ul><li><p>The "+" function has a hash and Unison only sees that</p></li><li><p>Arguments are referenced by position</p></li></ul><p>Most importantly...</p><p>...names are *just metadata*.</p><p>Wrap your head around that for a moment.</p><ul><li><p>Every function name is metadata</p></li><li><p>Every type name is metadata</p></li><li><p>Every argument name is metadata</p></li></ul><p>The function name "increment" is just a label that's mapped to a hash.</p><p>This one, simple idea unlocks some surprising and powerful benefits.</p><p>Here are a few:</p><ul><li><p>First class refactoring</p></li><li><p>No builds </p></li><li><p>Easy distributed computing</p></li><li><p>Very fast tests</p></li></ul><p>Sounds too good to be true?</p><p>Let&#8217;s see.</p><h2>First Class Refactoring</h2><p>Names are *just metadata*.</p><p>Unison knows that #arg1 is "n".</p><p>Unison knows that #a8s6df921a8 is actually Nat.+</p><p>This has <strong>huge</strong> implications for refactoring.</p><p>Imagine renaming a function...</p><p>...and the name is instantly updated everywhere.</p><p>Yup. Instantly. No work needed.</p><p>You can even create aliases for names in Unison.</p><p>Example:</p><p>There's an Optional type:</p><p><code>structural type Optional a = Some a | None</code></p><p>It's very similar to Maybe in other languages.</p><p>Other languages have Option.</p><p>Which is it? You can never remember.</p><p>In the Unison Codebase Manager, we do:</p><p><code>.&gt; alias.term Optional Maybe</code></p><p><code>.&gt; alias.term Optional Option</code></p><p>Now Unison doesn't care which you use.</p><p>They're all referring to the exact same type.</p><p>No more head-scratching!</p><p>This means a good name (one of the hardest things in software)...</p><p>...becomes a decision you can keep revisiting.</p><p>It gets better...</p><p>Let's say you want to do a more complex refactoring.</p><p>Maybe you add an argument.</p><p>Unison knows the hash of the old function.</p><p>It knows the new one doesn't have any callers yet.</p><p>So Unison keeps your old code working until your new code compiles!</p><p>In fact, the UCM tool steps you through the refactoring.</p><p>Yup. It gives you a list of TODOs.</p><p>All the time your codebase still works with the old code.</p><p>Once you've completed all the TODOs, Unison will use your new function.</p><p>Implications:</p><ul><li><p>Your code is always in a running state</p></li><li><p>No more time wasted trawling through long obscure error messages</p></li><li><p>Predictable workflow - work through the plan Unison gives you</p></li></ul><p>Imagine the evolutionary design work you can do with this tool.</p><p>Refactoring in Unison becomes a breeze and a dream.</p><p>It can't teach you refactoring, but it certainly makes it a *lot* easier.</p><h2>No Builds</h2><p>Wait... what? No builds?</p><p>How is that possible?</p><p>First of all, what is a build?</p><p>A build wraps up all dependencies and freezes them at a point in time.</p><p>It tends to be a slow, expensive, brittle process.</p><p>But in Unison, every function is just referenced by a hash.</p><p>And every change to a function is actually a new function, referenced by a new hash.</p><p>So every function knows it's dependencies.</p><p>Let's say you want to deploy your code.</p><p>Your machine tells a remote node "Hey, run this function called a8s6df921a8"</p><p>The node says "No idea what that hash is. Can you send it me please?"</p><p>Your machine says "Sure! Here it is: ..."</p><p>Unison will serialize the structure of the function and send it to the remote node.</p><p>Then the remote node runs it.</p><p>Of course, if it doesn't know other hashes, it'll ask your machine for them too.</p><p>So you're transferring functions over the network, not data.</p><p>That means it's quick. And it means no builds.</p><p>But wait... there's more.</p><p>Every time you change a function, Unison actually creates a new function.</p><p>So functions are immutable.</p><p>And old ones are still referenced by their hashes.</p><p>So that means dependency conflicts just go away.</p><p>Still don't get it? From the excellent Unison docs:</p><blockquote><p>Dependency conflicts are, fundamentally, due to different definitions "competing" for the same names.</p><p>But why do we care if two different definitions use the same name? We shouldn't.</p><p>The limitation only arises because definitions are referenced by name.</p><p>In Unison, definitions are referenced by hash (and the names are just separately stored metadata), so dependency conflicts and the diamond dependency problem are just not a thing.</p><p><a href="https://www.unison-lang.org/learn/the-big-idea/#no-dependency-conflicts">https://www.unison-lang.org/learn/the-big-idea/#no-dependency-conflicts</a></p></blockquote><p><strong>Practical implications</strong></p><ul><li><p>Deploys are virtually instant (&lt; 0.1 seconds)</p></li><li><p>No builds</p></li><li><p>No dependency conflicts</p></li><li><p>No semantic version pinning (no versions!)</p></li><li><p>No CI tools</p></li><li><p>No package managers (goodbye Rubygems, NPM, yarn etc)</p></li><li><p>No Terraform</p></li><li><p>No CloudFormation</p></li><li><p>No bash scripts</p></li><li><p>No YAML configs</p></li><li><p>No Docker (ah, sweet relief)</p></li></ul><p>Just write your code, run it on the Cloud and live your life.</p><p>Wow.</p><h2>Easy Distributed Computing</h2><p>Here's where things get really mind-blowing.</p><p>Remember that every function is identified by a content hash.</p><p>Also Unison can serialize any function and send it from one node to another.</p><p>Every dependency of one function can be sent to any other node.</p><p>If a node doesn't have any of the dependencies it needs it asks the caller...</p><p>...hey can you send me that function?</p><p>And it will.</p><p>Then the function will be cached on that node for next time.</p><p>Example:</p><p><code>distributed : Seq k Nat -&gt;{Remote} Nat</code></p><p><code>distributed dseq =</code></p><p><code>  use Nat + ==</code></p><p><code>  dseq</code></p><p><code>  |&gt; Seq map (x -&gt; x + 1)</code></p><p><code>  |&gt; Seq filter (x -&gt; Nat mod x 7 == 0)</code></p><p><code>  |&gt; Seq reduce 0 (+)</code></p><p>Don't worry about understanding the code.</p><p>Just know that this is a distributed map reduce.</p><p>It can run on 1 node or 10000.</p><p>And it's 7 lines of Unison.</p><p>That any programmer can write.</p><p>Then it's run like so:</p><p><code>a4.distributed.Remote.pure.run</code></p><p><code>  '(distributed (fromChunkedList 10 (List.range 0 100)))</code></p><p><code>&#10728;</code></p><p><code>Right 735</code></p><p>This runs it locally.</p><p>But there will be other runners that can distribute it in parallel across nodes in the cloud.</p><p>Want to do big data processing?</p><ul><li><p>No Hadoop</p></li><li><p>No Spark</p></li><li><p>No Jar files</p></li><li><p>No Docker</p></li><li><p>No complex frameworks</p></li></ul><p>Just Unison and a few lines of business logic.</p><p>Check out the blog article <a href="https://www.unison-lang.org/articles/distributed-datasets/">Spark-like datasets in Unison in Under 100 Lines</a> for more details.</p><h2>Very Fast Tests</h2><p>Remember we're in a functional language.</p><p>So functions tend to not have any side effects.</p><p>You pass a function some arguments.</p><p>It returns an output.</p><p>No database access, no memory mutation.</p><p>Unison handles side effects using something called Abilities, but that's for another day.</p><p>Point is, if you have:</p><ul><li><p>No side effects</p></li><li><p>Functions as hashes</p></li><li><p>Values in, values out</p></li></ul><p>It has huge implications for test suites.</p><p>It means <strong>you can cache the results of any test</strong>.</p><p>Imagine a test suite that contains 100,000 tests.</p><p>You write three more unit tests and run the test suite.</p><p>The existing tests don't run.</p><p>So the test suite takes milliseconds to run.</p><p>And when it's run, the three tests you've added are cached.</p><p>What does this mean?</p><p>Huge test suites can run in fractions of a second.</p><p>No more "push tests into CI".</p><p>Just run them on your machine. It's easy. It's cheap.</p><p>This will make TDD a <strong>dream</strong> to perform.</p><p>I've spent hundreds of hours waiting for tests to pass and code to deploy.</p><p>Those days are gone with Unison.</p><h2>Wrap up</h2><p>There are so many more cool things about Unison that I've not touched on.</p><p>But hopefully I've whetted your appetite to learn more.</p><p>A few honourable mentions:</p><ul><li><p>Abilities - a single way of doing side effects without any Monads</p></li><li><p>Codebase storage - all code is stored in a database. No more text files on disk.</p></li><li><p>No Github - Unison are creating their own code sharing and PR tools that take advantage of the language</p></li><li><p>Unison Cloud - Find out more here: https://buff.ly/3BXioSL</p></li><li><p>Typed, durable storage - Imagine if rerunning computations relied on transparent caches of old data!</p></li><li><p>Instant deploys - we touched on this, but it bears repeating - sub second deploys directly from your machine.</p></li></ul><p>This language blows my mind.</p><p>If I had more time, I'd be investing all of it learning it seriously.</p><p>It&#8217;s not all sunshine and rainbows though.</p><p><strong>Drawbacks</strong></p><p>1. Small ecosystem - very few libraries, very little community - although what's there is super friendly and welcoming</p><p>2. Unconventional - if you've coded in Elm or Haskell a lot, it looks familiar. Otherwise, it's challenging to learn.</p><p>3. Slow - it'll take a long time to develop any kind of working software, but there are tools in the pipeline to help this. Rails it is not!</p><p>4. Closed - the language is open source but the cloud platform will not be as that's how they'll make their money.</p><p>Phew. So that was a whirlwind tour of what I think is the most promising language I've seen in the last 10 years.</p><p>A huge thanks to the whole Unison ecosystem and community.</p><p>You're doing some amazing things and I look forward to Unison cloud being in beta for everyone very soon!</p><h2>What Next?</h2><p>I have two options:</p><ol><li><p><a href="https://calendly.com/synapticmishap/design-help-group-coaching">Book a 1 hour coaching session</a> (normally $200, FREE for a limited time)</p></li><li><p><a href="https://clean-up-the-mess.unicornplatform.page/">Join the waitlist for my book</a></p></li></ol><p>Have a great week!</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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[The Mindset Edition]]></title><description><![CDATA[Ideas for marketing yourself, defining yourself by your language and focussing on the basics.]]></description><link>https://www.joyfulprogramming.com/p/the-mindset-edition</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/the-mindset-edition</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sat, 17 Jun 2023 11:47:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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 week we&#8217;ll be talking about mindsets around being an engineer:</p><p>Marketing yourself</p><p>You are not a &lt;language&gt; programmer</p><p>Focussing on the basics</p><p>As always, I welcome your feedback. Just reply to this newsletter. Let&#8217;s go.</p><h2>Marketing Yourself</h2><p>"Surely I want to appeal to as many companies as I can... right?"</p><p>Wrong. Let's see why...</p><p>Let's start with the most obvious:</p><p>Specialist - deep knowledge in one topic - refactoring, design, architecture, performance</p><p>Generalist - shallow knowledge of many topics</p><p>Specialist - T-shaped skills</p><p>Generalist - _ shaped skills</p><p>Specialist - fixes deep complex problems</p><p>Generalist - fixes generic surface-level problems</p><p>Specialist - well paid</p><p>Generalist - poorly paid</p><p>Specialist - marketing that appeals to a niche</p><p>Generalist - marketing that appeals to no-one</p><p>Specialist - difficult to replace</p><p>Generalist - easy to replace</p><p>Specialist - harder to find (unless marketing is really good!)</p><p>Generalist - easy to find</p><p>Specialist - unique and interesting</p><p>Generalist - generic and boring</p><p>Specialist - obsesses over a few topics</p><p>Generalist - mildly interested in hundreds of topics</p><p>Specialist - less adaptable</p><p>Generalist - more adaptable</p><p>When selling yourself to employers, specialising is a powerful sales tactic.</p><p>If they need what you're selling, you move into a category of one.</p><h2>You&#8217;re Not A Language Programmer</h2><p>Stop thinking of yourself as a &lt;language&gt; programmer.</p><p>Question.</p><p>Why are you hired?</p><p>Why are you paid money?</p><p>There's not a competent business person who wants "a JavaScript developer".</p><p>Ok they say they want that... but what they really want is the business value delivered to the customer.</p><p>And more than that they want their business to grow.</p><p>So no, you're not a "Ruby programmer" or a "python wizard".</p><p>You're an engineer.</p><p>You solve problems.</p><p>And the language is just a tool to get there.</p><p>The best engineers use the right tool for the job.</p><p>And the best engineers don't see themselves narrowly as tied to a language.</p><p>Being an engineer is so much more than that.</p><p>And yes, this is the game the job market plays.</p><p>But you don&#8217;t need to buy into that once you&#8217;re in the job.</p><h2>Focussing On The Basics</h2><p>What is software engineering all about?</p><p>Is it building features?</p><p>Is it satisfying requirements?</p><p>Is it creating business value?</p><p>Jim Rohn said "there are two ways to make more money. One is to find more time. One is to provide more value."</p><p>There's an almost infinite amount of value you can give.</p><p>This week I've been challenged by various events to provide more value.</p><p>Maybe you can relate - as an engineer it's easy to get wrapped up in the tech.</p><p>In the possibilities.</p><p>In all the options.</p><p>And yet... the business is paying us real money to solve their problems.</p><p>Not problems we THINK they have...</p><p>...but the problems they actually have.</p><p>I got lost in my own head.</p><p>Overworking. Stressing about lots of small perfectionistic details.</p><p>Focus on getting the basics right.</p><p>Focus on the problems the business has.</p><p>Not the problems you think they have.</p><h2>Want Some Help?</h2><p>I have two options:</p><ol><li><p><a href="https://calendly.com/synapticmishap/design-help-group-coaching">Book a 1 hour coaching session</a> (normally $200, FREE for a limited time)</p></li><li><p><a href="https://clean-up-the-mess.unicornplatform.page/">Join the waitlist for my book</a></p></li></ol><p>Have a great week!</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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[The Flexible Edition Part 1]]></title><description><![CDATA[Preventing a big mess, the value of use cases and how to keep objects flexible.]]></description><link>https://www.joyfulprogramming.com/p/the-flexible-edition-part-1</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/the-flexible-edition-part-1</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sat, 10 Jun 2023 11:43:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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>Welcome back to Practical Software Design.</p><p>Here&#8217;s what we&#8217;re covering:</p><p>Preventing the Big Ball Of Mud</p><p>Use Cases</p><p>Interfaces &gt; Behaviour</p><p>We all want flexible apps. Let&#8217;s see a few ways of how we can make this happen.</p><h2>Preventing The Big Ball Of Mud</h2><p>Ever worked on an app that's devolved into a big ball of mud?</p><p>Yup. Me too.</p><p>How can you start improving it?</p><p>Bounded contexts can be a great place to start.</p><p>Eric Evans has said that if he had to rewrite the DDD book he'd put bounded contexts at the start, not the end.</p><p>What are they?</p><ul><li><p>They split your app up into broad areas</p></li><li><p>Within one bounded context the language is consistent</p></li><li><p>Across bounded contexts the same word can mean something different</p></li><li><p>Example: a customer mean something slightly different between the sales bounded context and operations</p></li><li><p>Bounded contexts can talk to each other but it should be fairly minimal chat</p></li></ul><p>What are the advantages?</p><ul><li><p>Reduces bloat in God classes</p></li><li><p>Segregates the app and imposes some order</p></li><li><p>Allows different parts of the app to evolve alongside a department</p></li></ul><p>Bounded contexts are difficult to do, especially in many frameworks (I'm looking at you Rails) but they're possible.</p><p>And they make reasoning about your code way easier.</p><h2>Use Cases</h2><p>Start using use cases.</p><p>What's a use case?</p><p>Why bother?</p><p>Great questions.</p><p>A use case is an object that wraps an interaction of some kind.</p><p>Example - creating an invoice.</p><p>You'd have a class called "InvoiceCustomer"</p><p>This class is instantiated from a controller.</p><p>It would have a single method to kick off the use case.</p><p>That method accepts various parameters the class needs to do its job.</p><p>It might be the customer ID. The invoice type maybe. The amount.</p><p>Then it talks to many different classes to do its job.</p><p>It might talk to the Stripe or FreeAgent API.</p><p>It might talk to the database.</p><p>Then it delivers back a result - success or failure.</p><p>The failure contains the errors to render in the view.</p><p>The success returns the invoice.</p><p>What's the difference between this and a service object?</p><p>Well, use cases generally wrap a whole transaction whereas service objects can do... but don't need to.</p><p><strong>Advantages</strong></p><ul><li><p>Encourages ubiquitous language</p></li><li><p>Testable</p></li><li><p>Describes the actions the app takes</p></li><li><p>Can wrap complex transactions</p></li></ul><p><strong>Disadvantages</strong></p><ul><li><p>More code</p></li><li><p>A later of abstraction</p></li></ul><p>I love use cases - I find them excellent abstractions that sit between controllers and the domain.</p><h2>Focus on the interface</h2><p>Want to build flexible object oriented apps?</p><p>Ignore implementation.</p><p>Focus on the interface.</p><p>Huh?</p><p>Most engineers obsess about implementation details.</p><ul><li><p>Should I use a Map? Or a Set?</p></li><li><p>Let's use this cool framework shortcut to save 2 lines of code</p></li><li><p>Let's add a guard clause</p></li></ul><p>I'm not saying this stuff is irrelevant...</p><p>...but it's much less important than the interface to your class.</p><p>What's the secret to flexible OO code?</p><p>Well... a big one is swappability.</p><p>Imagine you want to add a new feature.</p><p>What's the usual process?</p><ul><li><p>Find the code to hook into (2 hours)</p></li><li><p>Figure out the tangled web of dependencies (2 hours)</p></li><li><p>Think about how you're going to change that code (1 hour)</p></li><li><p>Change 4 methods and 2 classes and add 2 arguments (3 hours)</p></li><li><p>Fix the 23 tests that break (4 hours)</p></li></ul><p>You've changed so much though...</p><p>...what happens if an area with low test coverage breaks in production?</p><p>Here's the alternative:</p><p>* Find the code to hook into (2 hours)</p><p>* Understand 3 interfaces (2 hours)</p><p>* Write 2 classes matching the interface with different behaviour using TDD (2 hours)</p><p>* Plug into existing code (20 minutes)</p><p>Lean back and relax knowing you've hardly changed any code.</p><h2>Want Some Help?</h2><p>I have two options:</p><ol><li><p><a href="https://calendly.com/synapticmishap/design-help-group-coaching">Book a 1 hour coaching session</a> (normally $200, FREE for a limited time)</p></li><li><p><a href="https://clean-up-the-mess.unicornplatform.page/">Join the waitlist for my book</a></p></li></ol><p>Have a great week!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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[The Messy Edition]]></title><description><![CDATA[The power of bounded contexts, making a mess and why Active Record is an anti-pattern.]]></description><link>https://www.joyfulprogramming.com/p/the-messy-edition</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/the-messy-edition</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sat, 03 Jun 2023 11:14:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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>Another week, another edition of Practical Software Design.</p><p>Here&#8217;s what we&#8217;re covering:</p><ul><li><p>The power of bounded contexts</p></li><li><p>Making a mess</p></li><li><p>Active Record is an Anti-Pattern</p></li></ul><p>It&#8217;s going to be a controversial one this week. Let&#8217;s get into it!</p><h2>The Power Of Bounded Contexts</h2><p>Small Rails apps are easy to manage.</p><p>But as the codebase grows, I've run into some big problems:</p><ul><li><p>God objects - large classes that hide huge numbers of methods</p></li><li><p>Duplication of ideas - similar but different concepts across the codebase</p></li><li><p> Conflicts - teams stepping on each other's toes</p></li></ul><p>An idea from DDD can help here - bounded contexts.</p><p>A bounded context is a self-contained area of the codebase that has it's language.</p><p>Example: An app to run a garage might have bounded contexts for sales, marketing, servicing, and accounting.</p><p>Each of these contexts will have a concept of a "customer", but it's a different model depending on the context.</p><p>Ideally, data should be separate too, but that's tricky in Rails, so in the past, I've shared one database table for all customers.</p><p>However, you can have a Sales -&gt; Customer, a Marketing -&gt; Customer etc as Rails models under namespaces.</p><p>Once this has been done, you can put sales behaviour in one customer model and marketing behaviour in another.</p><p>Bounded contexts are fantastic for breaking down a large monolith.</p><p>I'd love to hear other people's experiences. Reply to this email!</p><h2>Make a Mess</h2><p>Stop refactoring. </p><p>Instead... make a mess.</p><p>When I first heard this from my mentor I did a triple take.</p><p>"But... surely we want clean code ASAP?" </p><p>No. And here's why.</p><p>There's a temptation amongst developers like myself who love refactoring to use it whenever they get a chance.</p><p>The first chunk of code that's more than three lines... and BLAM!</p><p>We extract into a method.</p><p>The first bad name we see... and BLAM!</p><p>That sucker's been renamed throughout the whole class.</p><p>But there's a big problem with this approach.</p><p>You can be too DRY.</p><p>When you refactor too soon, you've not let enough of a pattern emerge.</p><p>As Sandi Metz says "Prefer duplication over the wrong abstraction".</p><p>Wait. Wait for that mess to build up.</p><p>Wait until it's almost intolerable. </p><p>At that point, a pattern will emerge and that pattern will tell you the right abstraction.</p><p>Then, BLAM you've refactored, not just for the sake of it, but because the code is telling you to do that very thing.</p><p>Controversial? Maybe. Let me know what you think&#8230;</p><h2>Active Record is an Anti-Pattern</h2><p>Active Record is damaging your codebase.</p><p>Repositories are one answer. </p><p>Why do I say this?</p><p>I've been in a lot of Rails codebases in the past few years.</p><p>Many folk think that going against Rails is futile.</p><p>"The Rails Way Or Nothing" they cry.</p><p>"Let's Try A Different Way" say I.</p><p>Rails is brilliant for getting a project off the ground.</p><p>Once it grows to a certain size, the Rails Way hinders you.</p><p>Rails uses a pattern called Active Record. </p><p>It encourages stuffing business logic and persistence into the same class, along with many other unrelated concerns.</p><p>One alternative is the repository pattern. </p><p>I've introduced repositories in Rails codebases recently.</p><p>It's still early days but here are the benefits I'm getting:</p><p>* Swappability - we can swap out the database for in memory repos in tests, leading to better design and flexibility (as well as some speed)</p><p>* Isolation - we can access APIs via repositories, leading to our code being less coupled to external vendors</p><p>* Less mocking - because we're now not coupled to APIs such as Stripe, we don't need complicated mocks in tests</p><p>What about you? Have you used repositories before?</p><p>Found them helpful? Annoying?</p><p>Either way please let me know in the comments or just reply...</p><h2>Want Some Help?</h2><p>I have two options:</p><ol><li><p><a href="https://calendly.com/synapticmishap/design-help-group-coaching">Book a 1 hour coaching session</a> (normally $200, FREE for a limited time)</p></li><li><p><a href="https://clean-up-the-mess.unicornplatform.page/">Join the waitlist for my book</a></p></li></ol><p>Have a great week!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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></p>]]></content:encoded></item><item><title><![CDATA[The Human Angle Edition]]></title><description><![CDATA[Readability, Being Human and Learning Things That Don't Change]]></description><link>https://www.joyfulprogramming.com/p/the-human-angle-edition</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/the-human-angle-edition</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sat, 27 May 2023 11:53:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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>Welcome to another issue of Practical Software Design.</p><p>This week we'll be talking about:</p><ul><li><p>Testing Readability</p></li><li><p>On Being Human</p></li><li><p>Learning things that don't change</p></li></ul><h2>Testing Readability</h2><p>A radical (and maybe silly) idea.</p><p>Imagine if you could test the readability of your code.</p><p>I declare "that's readable" all the time.</p><p>What evidence do I have? &#129335;&#8205;&#9794;&#65039;</p><p>Well... not much.</p><p>Apart from that fact that I was in a meeting where me, myself and I all agreed&#8230;</p><p>&#8230;that code I just wrote is <strong>awesome</strong>.</p><p>Yes, smug mode engaged.</p><p>My work here is done.</p><p>Then 6 months later I return to that "readable" code... and I'm cursing myself.</p><p>"What the heck does that do?"</p><p>"Yeah, nice job past me."</p><p>Turns out the past me was an idiot.</p><p>This has happened to me repeatedly...</p><p>...and yet I still don't have a good antidote for it apart from "more code reviews".</p><p>Which is nice and all... but hardly scientific.</p><p>There's a whole discipline devoted to user testing user interfaces.</p><p>The UX folk know how to structure the interview, how to ask questions, and how to understand the user's mental model.</p><p>Why not an equally disciplined approach to testing the subjective readability of our code?</p><p>Imagine if a PR could be run through readability tests, where 10 random developers from all over the world could see your code, and were asked questions like:</p><ul><li><p>What does this do?</p></li><li><p>How does it work?</p></li><li><p>What parts do you not understand?</p></li></ul><p>I hear what you're saying "We do have that, John, it's called Open Source!".</p><p>Sure, but this would be for private repos.</p><p>This app could compile all those responses together and maybe even give you a "readability score" - a bit like Code Climate.</p><p>It would give you a sense of which parts were most confusing.</p><p>Then you could make changes, resubmit and see if the code was easier to read or harder.</p><p>Please let me know your thoughts by replying to this email!</p><h2>On Being Human</h2><p>Human &gt; Technology</p><p>Let's be human.</p><p>Why should you care?</p><p>Obsessing about coding, engineering and technical skills is a beautiful thing.</p><p>Regularly I find myself up late at night coding.</p><p>Especially when I'm obsessed with a topic like I am at the moment. *</p><p>But after a while of this, I start to feel weird.</p><p>Disconnected.</p><p>Empty.</p><p>Did you know that 15% of men say they have zero close friends? **</p><p>Last night I went out to a meetup, connected with humans in person, and it felt great.</p><p>As engineers, we focus too much on the tech and not enough on the human.</p><p>Some ways to become more human:</p><ul><li><p>Write for humans first and computers second</p></li><li><p>Take time to get to know your colleagues</p></li><li><p>Ditch the jargon and enterprise speak</p></li><li><p>Go to meetups (yes, real-life in-person meetups</p></li><li><p>Connect with others on LinkedIn and via face-to-face conversations</p></li><li><p>Sit in on some user tests of your product. You'll learn a lot. &#129327;</p></li></ul><p>As AI becomes more and more A Thing, we'll value human connection more and more.</p><p>* The topic is nullable architecture, but that's for another post</p><p>** According to American Perspectives Survey, conducted by the Survey Center on American Life</p><h1>Learning things that don't change</h1><p>Don't learn another framework.</p><p>Don't learn another language.</p><p>&#11088; Learn things that don't change. &#11088;</p><p>Jeff Bezos' advice to the founders of BaseCamp 15 years ago?</p><p>It was the same.</p><p>"Invest in things that don't change".</p><p>What doesn't change in software?</p><p>Designing software that's easy to change.</p><p>"How ironic!" Shut up Alanis.</p><p>The majority of talks, courses and books, are on new tools, languages and frameworks.</p><p>That's nice... but where are the books on design?</p><p>On refactoring?</p><p>On making code that's easy to change?</p><p>Sure, there are the classics.</p><p>But this subject doesn't get anything like the attention it deserves.</p><p>The syntax of a language is relatively easy to learn.</p><p>The idioms of a language are more difficult and have more value.</p><p>They teach you new ways of thinking.</p><p>Software design skills last for life.</p><p>No matter the framework or language.</p><h2>What Next?</h2><p>I have two options:</p><ol><li><p><a href="https://calendly.com/synapticmishap/design-help-group-coaching">Book a 1 hour coaching session</a> (normally $200, FREE for a limited time)</p></li><li><p><a href="https://clean-up-the-mess.unicornplatform.page/">Join the waitlist for my book</a></p></li></ol><p>Have a great week!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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[The TDD Edition]]></title><description><![CDATA[Keeping builds green, the fourth step of TDD and why TDD doesn't need unit tests.]]></description><link>https://www.joyfulprogramming.com/p/lets-talk-about-tdd</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/lets-talk-about-tdd</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sat, 20 May 2023 10:04:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><p>This week we&#8217;ll be talking about TDD.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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>TDD is an important practice in making software easy to change.</p><p>More TDD topics to come, but this week we&#8217;ll be talking about:</p><ul><li><p>Keeping Builds Green</p></li><li><p>The Fourth (missing) Step Of TDD</p></li><li><p>TDD Doesn&#8217;t Need Unit Tests</p></li></ul><h2>Keep Builds Green</h2><p>"The tests are failing but I'll fix it later..."</p><p>TDD is all about fast feedback cycles.</p><p>But it's also about reducing waste. Why? &#128071;</p><p>When a test is failing, you're getting feedback that what you've done was a mistake.</p><p>When that mistake persists... for seconds... then for minutes... then maybe for hours...</p><p>It means you're getting off track.</p><p>The longer the failures last, the more work in progress builds.</p><p>The more work in progress builds, the more waste you're accruing.</p><p>And as waste builds it becomes exponentially harder to clean.</p><p>Finally, you throw your hands up "Let's break out the debugger".</p><p>That's an admission of failure!</p><p>90% of your time should be in green.</p><p>And when you're in red, get back to green as soon as you can - by whatever means necessary.</p><p>Keep your build green at all times!</p><h2>The fourth step of TDD</h2><p>TDD has three steps:</p><ol><li><p>Red - write a failing test</p></li><li><p>Green - make the test pass</p></li><li><p>Refactor - restructure the code</p></li></ol><p>But there's a fourth step that you may not be aware of.</p><p>And it's critical to long-term agility. </p><p>You write a failing test.</p><p>Now it's time to make that test pass. Right?</p><p>Well... maybe.</p><p><strong>Pay attention to how the test fails.</strong></p><p>What's the failure message?</p><p>You want that failure message to describe what went wrong clearly.</p><p>Why?</p><p>If a test suite never fails, then it's not protecting you from any failures.</p><p>A useful test suite will fail sometimes.</p><p>You'll be blocked until you fix them.</p><p>With expressive, helpful, failure messages, you can easily reason about what went wrong.</p><p>With weird, obtuse, "expected 4 got 2" failure messages you'll need to break out the debugger.</p><p>Breezing past an unhelpful error message right now means shooting someone else in the foot later.</p><p>And that someone might be the future you.</p><p>Do yourself a favour and work on making that error message useful before you move on.</p><p>The fourth step of TDD is part of step 1 - make the failure message useful.</p><h2>TDD doesn't need unit tests. &#128558;</h2><p>Say what now?</p><p>It all starts with TDD being misnamed.</p><p>The acronym stands for "Test Driven Development"</p><p>So how can tests not be an integral part of this practice?</p><p>Dan North came up with the term "BDD" or Behaviour Driven Development.</p><p>Back at the start, BDD meant something very different:</p><blockquote><p>It suddenly occurred to me that people&#8217;s misunderstandings about TDD almost always came back to the word 'test'.</p><p>I started using the word &#8220;behaviour&#8221; in place of &#8220;test&#8221; in my dealings with TDD and found that not only did it seem to fit but also that a whole category of coaching questions magically dissolved.</p><p>I found the shift from thinking in tests to thinking in behaviour so profound that I started to refer to TDD as BDD, or behaviour-driven development.</p><p>- Dan North</p></blockquote><p>TDD is first and foremost about behaviour.</p><p>Any way you have of verifying that behaviour is enough to practice TDD.</p><p>An early mentor showed me how to test drive code by only using a browser and refreshing the page.</p><p>So the next time you do TDD, whether or not you're doing it with automated tests, focus on behaviour, not the test.</p><h2>Need More?</h2><p>I have two options:</p><ol><li><p><a href="https://calendly.com/synapticmishap/design-help-group-coaching">Book a 1 hour coaching session</a> (normally $200, FREE for a limited time)</p></li><li><p><a href="https://clean-up-the-mess.unicornplatform.page/">Join the waitlist for my book</a></p></li></ol><p>Have a great week!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.joyfulprogramming.com/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">Practical Software Design is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</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[The power of mentoring]]></title><description><![CDATA[Future of end to end testing, DDD in Rails and the power of mentoring]]></description><link>https://www.joyfulprogramming.com/p/something</link><guid isPermaLink="false">https://www.joyfulprogramming.com/p/something</guid><dc:creator><![CDATA[John Gallagher]]></dc:creator><pubDate>Sun, 14 May 2023 12:41:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rh-F!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faabdd698-49fd-4538-8243-0123715fe4f1_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Future of end to end testing</h2><p>End to end testing of web software is slow and painful.</p><p>The most popular tool is Selenium. But there are much better options.</p><p>Selenium was created by Jason Huggins in 2004, so it'll be 20 years old next year.</p><p>It's served us well as a developer community, but I think it's time to talk about end to end testing tools for the next 20 years.</p><p>There are many candidates out there:</p><ul><li><p>Cypress</p></li><li><p>Nightwatch.js</p></li><li><p>WebdriverIO</p></li><li><p>Puppeteer</p></li></ul><p>Despite working in Ruby, I was heavily leaning towards Cypress.</p><p>That's until my colleague Karl Entwistle pointed me to Playwright.</p><p>Why I think it might win:</p><ul><li><p><a href="https://lnkd.in/ePJywiY7">More stable than Cypress</a></p></li><li><p><a href="https://lnkd.in/ev8miVR3">Faster</a> too</p></li><li><p>Supported by Microsoft</p></li></ul><p>I'm going to experiment with it over the coming months.</p><p>I'll report back when I know more!</p><h2>DDD In Rails</h2><p>Small Rails apps are easy to manage.</p><p>But as the codebase grows, I've run into some big problems:</p><ul><li><p>God objects - large classes that hide huge numbers of methods</p></li><li><p>Duplication of ideas - similar but different concepts across the codebase</p></li><li><p>Conflicts - teams stepping on each other's toes</p></li></ul><p>An idea from DDD can help here - bounded contexts.</p><p>A bounded context is a self-contained area of the codebase that has it's language.</p><p>Example: An app to run a garage might have bounded contexts for sales, marketing, servicing, and accounting.</p><p>Each of these contexts will have a concept of a "customer", but it's a different model depending on the context.</p><p>Ideally, data should be separate too, but that's tricky in Rails, so in the past, I've shared one database table for all customers.</p><p>However, you can have a Sales -&gt; Customer, a Marketing -&gt; Customer etc as Rails models under namespaces.</p><p>Once this has been done, you can put sales behaviour in one customer model and marketing behaviour in another.</p><p>And slowly your app will become more segregated. Order will be imposed on the chaos!</p><h2>The power of mentorship</h2><p>When I started out in Ruby I sucked.</p><p>Now I can evolve a design on my own.</p><p>Evolutionary design. As a junior engineer, I could never understand it.</p><p>But then I was mentored and my life changed...</p><p>As a junior programmer, I really struggled to write code that exhibited any properties of good design.</p><p>I knew how far short I was falling.</p><p>Time and time again, I wrote code and my mentor showed me how to improve it.</p><p>For months I just wasn't getting it.</p><p>My code was a spaghetti mess.</p><p>My code was fragile, complex, and intertwined.</p><p>And most worryingly, I didn't have a clue how to evolve towards sanity.</p><p>A good mentor sticks with the junior and encourages them.</p><ul><li><p>6 months later, I was starting to understand</p></li><li><p>1 year later, I could evolve a very basic design on my own</p></li><li><p>2 years later I was brimming with confidence</p></li><li><p>10 years later, evolutionary design is still one of my obsessions</p></li></ul><p>I want to pass along my skills and teach the next generation of developers these timeless principles.</p><p>And I want to speed up the process for you!<br><br>That's why I'm writing this newsletter.</p><h2>Need More?</h2><p>I have two options:</p><ol><li><p><a href="https://calendly.com/synapticmishap/design-help-group-coaching">Book a 1 hour coaching session</a> (normally $200, FREE for a limited time)</p></li><li><p><a href="https://clean-up-the-mess.unicornplatform.page/">Join the waitlist for my book</a></p></li></ol><p>Have a great week!</p><h2></h2>]]></content:encoded></item></channel></rss>