<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.7">Jekyll</generator><link href="https://neilkakkar.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://neilkakkar.com/" rel="alternate" type="text/html" /><updated>2026-03-04T20:14:29+00:00</updated><id>https://neilkakkar.com/feed.xml</id><title type="html">Neil Kakkar</title><subtitle>I want to understand how the world works. This blog tracks my growth, the things
I&apos;ve learned, and how I&apos;m leveraging them to do epic things.

</subtitle><author><name>Neil Kakkar</name></author><entry><title type="html">Agentic Debt</title><link href="https://neilkakkar.com/agentic-debt.html" rel="alternate" type="text/html" title="Agentic Debt" /><published>2026-02-24T08:00:00+00:00</published><updated>2026-02-24T08:00:00+00:00</updated><id>https://neilkakkar.com/agentic-debt</id><content type="html" xml:base="https://neilkakkar.com/agentic-debt.html">&lt;p&gt;In my second week at Tano, I tried to add a simple feature. I got stuck because Claude made changes in the wrong place. Turns out, there were three different places in the codebase using almost the same frontend code, displaying slightly different UIs. It made the change to one I wasn’t aware of, so when I tried running it locally, nothing changed where I expected.&lt;/p&gt;

&lt;p&gt;I took a step back, refactored and reconciled the three different copies to create something I could understand. I deleted about 2,000 lines of code written just a few weeks prior. More critically, the agent one-shotted the feature after.&lt;/p&gt;

&lt;p&gt;This feels like a new kind of problem. It’s not the classic technical debt - no human consciously chose to re-create three different UIs, all almost the same. It’s something more specific to how agents write code.&lt;/p&gt;

&lt;p&gt;I’ve been calling it agentic debt.&lt;/p&gt;

&lt;h2 id=&quot;the-feedback-loop&quot;&gt;The feedback loop&lt;/h2&gt;

&lt;p&gt;Unlike tech debt, agentic debt is self-reinforcing.&lt;/p&gt;

&lt;p&gt;An agent writes code. It works. You ship it. The agent writes more code. Also works. You ship that too. But each time, it’s optimising for the task at hand: the PR, the feature, the immediate ask. It doesn’t have a model of “everything else” the way you do.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Over time, you get locally optimal choices that add up to global architectural drift. Patterns get duplicated. Abstractions get half-implemented. Three different files handle the same concept in three slightly different ways.&lt;/p&gt;

&lt;p&gt;And then the next agent comes along and tries to work with this codebase. It gets confused. It makes sub-optimal choices. It picks a whacky connection to the “jungle” side of the codebase - code that’s not touched anymore. Because the first agent’s slop makes it harder for the second to reason clearly.&lt;/p&gt;

&lt;p&gt;This is the agent slop feedback loop. Decay that feeds itself.&lt;/p&gt;

&lt;h2 id=&quot;the-context-window-trap&quot;&gt;The context window trap&lt;/h2&gt;

&lt;p&gt;A potential counter-argument is that this is unnecessary when context windows are large enough to dump the entire codebase in. An agent can then figure everything out, since code is deterministic.&lt;/p&gt;

&lt;p&gt;In a year or two? Maybe. Today, I don’t think so. Even with infinite context, I expect noise and inconsistency break reasoning. Three duplicated patterns with subtle differences don’t become clearer with more context. They become three times as confusing. The model has to figure out which one is “right”, or if they’re all slightly different for good reasons, or if the last agent just copy-pasted without checking.&lt;/p&gt;

&lt;h2 id=&quot;human-understandable-is-agent-actionable&quot;&gt;Human-understandable is agent-actionable&lt;/h2&gt;

&lt;p&gt;At least for now, the counter-intuitive finding: The best way to improve agent performance is to make sure the code is simple enough for humans to model.&lt;/p&gt;

&lt;p&gt;When I reconciled those three duplicated frontends into one clean pattern, I wasn’t doing it for the agent. I was doing it because I couldn’t build a clear mental model of the codebase with all that fragmentation. But once the code was clean, the agent could reason about it better too. Future changes became faster. Future refactors became easier.&lt;/p&gt;

&lt;p&gt;This isn’t a coincidence. Agents struggle with the same things humans struggle with: inconsistency, implicit assumptions, and duplicated logic with subtle differences. A codebase that’s easy for a human to hold in their head is also easy for an agent to work with. &lt;strong&gt;The properties that make code maintainable haven’t changed just because the writer is an LLM.&lt;/strong&gt;&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h2 id=&quot;tending-the-garden&quot;&gt;Tending the garden&lt;/h2&gt;

&lt;p&gt;This changes how I think about working fast. Are we trading shipping something new tomorrow for slowing down later?&lt;/p&gt;

&lt;p&gt;Especially at startups, this can be the right trade-off. I think it was at Tano too. The point is to make it explicitly, not let it be the default. Sometimes, slower is smoother. And smoother is faster.&lt;/p&gt;

&lt;p&gt;Some refactors become much easier with agents. Deduplication, for instance, is cheap labour now. But agility depends on whether you actually do the refactoring, or just keep sprinting.&lt;/p&gt;

&lt;p&gt;A clean codebase means new people get onboarded quickly, which lets them steer agents better. We aren’t at a point yet where you can stop steering.&lt;/p&gt;

&lt;p&gt;As the codebase grows, and you go from one to many engineers, stewardship becomes more important. Sure, you can hit approve, ship it, move on. But that slows down every future change. The slop shows up in how much attention the steerer pays.&lt;/p&gt;

&lt;p&gt;You’re not necessarily writing the code anymore. But you are the maintainer. The gardener, if you will. Weeds will grow. Sometimes they’re needed to enrich the soil. Other times you need to trim them.&lt;/p&gt;

&lt;p&gt;This is a different kind of engineering than what I was doing a few years ago. I used to write the code. Since I had to do the grunt work, I’d naturally make it easier for me to continue contributing. A lot of software best practices spun out of this.&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Now I’m the person making sure the code stays coherent. It’s less about “can I implement this feature?” and more about “does this feature fit into the system in a way that won’t confuse the next agent, or the next human?”&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h2 id=&quot;open-questions&quot;&gt;Open questions&lt;/h2&gt;

&lt;p&gt;Model capabilities are improving pretty quickly. The scaffolding of today might be unnecessary tomorrow. There are a few things I’m not sure about.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Can a gardener agent work?&lt;/strong&gt; A daily monitor that audits every PR and flags duplicated logic, unnecessary API endpoints, architectural drift, etc. etc. Codex reviews Claude pretty well, so I think this is plausible. I don’t know if it will be good enough though. It’s one experiment I want to try soon.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Code reviewer agents?&lt;/strong&gt; With today’s models, I’m sure this doesn’t work well for stewardship. But in the future, I’m not sure. I think this arrives before agents that need no steering.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Does gardening scale?&lt;/strong&gt; At Tano, we’re a small team. The gardening is manageable. But what happens when you have 50 agents writing code across a large codebase? Does the gardening overhead grow linearly, or does it compound? I suspect it compounds, which means the gardener role becomes even more important as we scale up agentic development.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;When does refactoring pay for itself?&lt;/strong&gt; Taking a step back to reconcile slop feels like lost momentum. But it was necessary preparation for the next burst of agent-driven velocity. I don’t have a good heuristic yet for when to pause and garden versus when to keep sprinting.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Until I figure it out, I garden between the sprints.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/divider.jpg&quot; alt=&quot;&quot; style=&quot;width: 250px&quot; /&gt;
    
    
    
&lt;/figure&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;Even with enormous context windows. More on this later. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;I suspect this is because the models are trained on human-written code and human explanations. Their “reasoning” mirrors human reasoning patterns. What’s legible to us is legible to them. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;Which seems to suggest the best practices are changing. I think some core principles will remain, and some others will endure because humans steer the agent to such practices. What about a post-stewarding world, when agents are much smarter at coding than me? I don’t know &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;There’s a parallel here to how choosing what to build has become more important than just building. Taste is more important, and more on this in a future post! &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>Neil Kakkar</name></author><summary type="html">In my second week at Tano, I tried to add a simple feature. I got stuck because Claude made changes in the wrong place. Turns out, there were three different places in the codebase using almost the same frontend code, displaying slightly different UIs. It made the change to one I wasn’t aware of, so when I tried running it locally, nothing changed where I expected.</summary></entry><entry><title type="html">What I learned about burnout and anxiety at 30</title><link href="https://neilkakkar.com/burnout-anxiety-at-30.html" rel="alternate" type="text/html" title="What I learned about burnout and anxiety at 30" /><published>2026-02-16T00:00:00+00:00</published><updated>2026-02-16T00:00:00+00:00</updated><id>https://neilkakkar.com/burnout-anxiety-at-30</id><content type="html" xml:base="https://neilkakkar.com/burnout-anxiety-at-30.html">&lt;p&gt;If I had to choose one moment that signifies the deepest shift in the last few years, it would be mid-2024 when I had my first and only panic attack. It was my body and mind finally protesting and giving up, being like, “Neil, fuck this shit. You are not seeing the signs, so I have to take some drastic measures.”&lt;/p&gt;

&lt;p&gt;It caught me completely by surprise. I did not know what was happening to me. It felt like a serious illness, and in a way I guess it was, just more mental than physiological.&lt;/p&gt;

&lt;p&gt;Think of life like a four-legged stool. It’s super sturdy with four legs. It’s still pretty damn sturdy with three legs. Go down to two, and it’s hard to stabilise on most surfaces. At one, you’re definitely falling over, the only question is how slowly.&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; At zero, you don’t have a stool left, you’re a pancake.&lt;/p&gt;

&lt;p&gt;I think these four legs are emotional connections, physical activity, hobbies, and work.&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;And I, became a pancake.&lt;/p&gt;

&lt;h2 id=&quot;the-slow-slide&quot;&gt;The slow slide&lt;/h2&gt;

&lt;p&gt;I was deep into work. I became a manager, became responsible for high-profile client-facing features, and cared a lot about what we were building at PostHog. It was a wonderful time of growth, punching above my weight, learning a lot, but also doing too much of it. I was switched on all the time, and escaped into fantasy novels afterwards. It.. wasn’t healthy. And I didn’t realize it until it all came crashing down.&lt;/p&gt;

&lt;p&gt;Thus began my long and slow road of dealing with anxiety. It was there long before the panic attack, but I did not understand what it was. It showed up as this compulsion to go back home, read some manga, and escape into fantasy novels. That felt.. safe.&lt;/p&gt;

&lt;p&gt;I was also getting lonely. I had friends, but I rarely went out to meet them. Almost every time I did go to a social event, I adored it. Every time I had to decide to go to an event, I dreaded it. I thought I had too much work, and I wanted to relax after work by myself. I couldn’t be bothered to go out.&lt;/p&gt;

&lt;p&gt;As the 2019 me &lt;a href=&quot;/year-in-review-2019.html#thinking-in-systems&quot;&gt;thought about it&lt;/a&gt;, life is a complex adaptive system. Every part of life is an input to some other part of life. I theoretically understood it, but the visceral feeling of experiencing it is different.&lt;/p&gt;

&lt;h2 id=&quot;the-signs-i-missed&quot;&gt;The signs I missed&lt;/h2&gt;

&lt;p&gt;Observation is my superpower, but it’s a lot more retrospective than I would like. I’m not good at catching things in the moment, yet. It’s hard and it takes a lot of time.&lt;/p&gt;

&lt;p&gt;I spent so long observing and tracking things that I stopped listening to my body and instead only “heard” hard metrics I could see. It’s the classic Goodhart Law, I did not expect it to apply to my body and mind.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;One of these signs was reflux. I started having reflux problems about a year and a half &lt;em&gt;before&lt;/em&gt; the anxiety attack. It was frustrating, but not debilitating. I ran experiments to figure out what foods trigger it, broken down by time of day, and over time got a much deeper understanding of my body. However, this was treating the symptoms, not the cause. Reflux became much rarer after I quit.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; I’ve talked so much about systems but failed to see this connection in my own system.&lt;/p&gt;

&lt;p&gt;Another sign was the weight gain. This graph was going up and to the right - and this is one of those graphs you definitely do not want on a hockey stick trajectory.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/weight-graph-2026.png&quot; alt=&quot;Weight graph over time, showing steady increase after joining PostHog and drop after leaving&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;The data collection was manual here. I was inputting these numbers every day. I was seeing the graph every week. The alarm bells still failed to trigger, or when they did trigger, they were silenced. “It’s fine, I’ll start working out again when I have more time”. “Kicking myself over it doesn’t help me feel better”. “I already have enough on my plate”.&lt;/p&gt;

&lt;p&gt;Present me would treat this a lot more seriously than past me. The inner monologue above suggests things have gone very much out of balance. It’s a stool missing one of its legs.&lt;/p&gt;

&lt;p&gt;A third wedge that further complicated the above was this iron deficiency I didn’t know about. Borderline iron levels don’t get flagged on blood tests, but can cause fatigue and post-workout dizziness. Thanks to the NHS doc who noticed it after several others glossed over it.&lt;sup id=&quot;fnref:9&quot;&gt;&lt;a href=&quot;#fn:9&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;This also made my anxiety much worse - and for a while I was wrongly attributing the tiredness to anxiety, and turtled in quite a bit.&lt;/p&gt;

&lt;p&gt;But still, I’m glad it came crashing down in 2024 rather than in 2028. The faster I understand, the faster I can course correct, and that’s exactly what 2025 has been about.&lt;/p&gt;

&lt;h2 id=&quot;rebuilding&quot;&gt;Rebuilding&lt;/h2&gt;

&lt;p&gt;I quit my job in January, after 6 months of trying to figure it out while keeping the job. I couldn’t bring myself to work anymore, and it was better to stop than drag it out. It wouldn’t be fair to my colleagues, and I’d feel much worse about coasting and making excuses too.&lt;/p&gt;

&lt;p&gt;What followed was a period of lots of joy, new routines, new hobbies, and pushing my limited self to feel whole again. I discovered a love for board games, made lots of new friends, played a lot of tennis and pickleball, which made me realise how much I was missing out on.&lt;/p&gt;

&lt;p&gt;It was slow though. Anytime I got impatient and pushed too hard, the anxiety would creep back in. This was.. weird. I was so used to pushing ahead that slowing down felt awkward. I was impatient to recover. Over repeated instances, I learned to calm down.&lt;/p&gt;

&lt;p&gt;What feels like “pushing” can be true for even the smallest of things. For example, in the early days, I “pushed” myself when I went out to watch a movie with a few friends. It felt miserable. I felt trapped in my seat, watching the gore in Deadpool. This made me feel worse - “Why am I feeling like this? These are all things I used to enjoy?”. It spirals so quickly when I’m not noticing. I dipped to the bathroom to catch my breath, and went on a walk after to calm down. I decided that was enough adventure for the week, and turtled back at home.&lt;/p&gt;

&lt;p&gt;I wouldn’t have gotten out of my new shell at all, had I not pushed; but I also couldn’t push too hard. It was important to take things slow, but to also test the edges of my capabilities. It’s like running evals for an LLM - we do it because we don’t have a good intrinsic understanding of AI model capabilities. I didn’t have a good intrinsic understanding of my limited self’s capabilities, so I ran my own evals. The difference is I’m not static. The act of testing my capabilities extends them, and sometimes shrinks them.&lt;sup id=&quot;fnref:7&quot;&gt;&lt;a href=&quot;#fn:7&quot; class=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;However, my body doesn’t always align with what my mind wants. There’s always some apprehension in the body - call it anxiety, call it nervousness, call it jitters, call it excitement.&lt;sup id=&quot;fnref:5&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Over time, this apprehension doesn’t go away. I have just gotten better at dealing with it. Enough examples of going out and having fun reinforces this idea that there’s nothing scary.&lt;/p&gt;

&lt;p&gt;I still close off sometimes though, and that’s okay. Sometimes I just want to be comfy, and so I be comfy.&lt;/p&gt;

&lt;h2 id=&quot;what-ive-learned&quot;&gt;What I’ve learned&lt;/h2&gt;

&lt;p&gt;Looking back, a lot has changed in the last 5 years. The overarching theme is that theory sounds nice, but reality is messy. 2019-me was directionally correct. I would be in a much worse spot today if I didn’t think through life the way I did and lay down my principles.&lt;/p&gt;

&lt;p&gt;Reality has a surprising amount of detail, and systems need reinforcement. Knowing what was happening and why it was happening wasn’t always enough to make a change. I needed the lesson drilled into me.&lt;/p&gt;

&lt;p&gt;I felt the same about my &lt;a href=&quot;/things-I-learned-to-become-a-senior-software-engineer.html&quot;&gt;second year of work review&lt;/a&gt; coming across as overconfident.&lt;sup id=&quot;fnref:10&quot;&gt;&lt;a href=&quot;#fn:10&quot; class=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; And yeah, it was. But I still stand by it. If I don’t see myself as who I want to be, it’s much harder to reach where I want to be.&lt;/p&gt;

&lt;h3 id=&quot;environment--willpower&quot;&gt;Environment &amp;gt; willpower&lt;/h3&gt;

&lt;p&gt;Structural design wins. I’ve yet to see success with willpower based resolutions. Maybe I’m weak-willed, but I’ve learned to stick to what works best for me.&lt;/p&gt;

&lt;p&gt;My internal monologue still goes at it wrongly - “I need to go to bed early”, “Don’t eat greasy food late, it will give you sleep problems”, but what helps the most is designing my life such that this happens by default.&lt;/p&gt;

&lt;p&gt;An 8 AM alarm every day means I’m usually too tired to stay up late, and works wonders for my sleep. It’s not the whole picture though. Everything is connected, and if I’m too stressed at work, stress-eating late at night, and then revenge bedtime procrastinating&lt;sup id=&quot;fnref:6&quot;&gt;&lt;a href=&quot;#fn:6&quot; class=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt; - then I invariably end up sleeping poorly. Fixing this doesn’t mean fixing sleep, it means fixing whatever else is broken, and sleep will fall back into place.&lt;/p&gt;

&lt;p&gt;Every lasting behaviour change I’ve made has been environmental, not motivational. It was magical how many of these problems went away once I quit.&lt;/p&gt;

&lt;p&gt;No junk food at home does wonders for healthier eating. SAD lamps make winters happier. Cold, dark room leads to much better sleep. Good sleep instantly elevates the day. Moving houses made me realise how important sunlight is to me. A living room full of sunshine sparks so much joy. As do regular weekly tennis sessions.&lt;/p&gt;

&lt;p&gt;I can change things to be the way I want them. I can ask more of the world than the default.&lt;/p&gt;

&lt;h3 id=&quot;stress-reveals-itself-through-control&quot;&gt;Stress reveals itself through control&lt;/h3&gt;

&lt;p&gt;When I can’t wind down after work, when I am always on - that’s a sign of stress. That’s a sign I can’t shut off. That’s a sign I need to change things.&lt;/p&gt;

&lt;p&gt;When I’m stressed, I grip tighter. I want to fix things so they stop being a problem. I feel the stress will go away when the problem has gone away. I keep thinking about the problems no matter what I’m doing, which causes more stress. This is counterproductive.&lt;/p&gt;

&lt;p&gt;I’ve learned to trust myself, to let go. This is where high observation becomes somewhat of a burden. Tracking metrics when they’re all going poorly causes a bit more extra stress. Which in turn causes me to grip a bit tighter.&lt;/p&gt;

&lt;p&gt;Sometimes, I need to walk away from the environment that incubates this stress. That’s why I quit my job.&lt;/p&gt;

&lt;h3 id=&quot;the-same-patterns-repeat&quot;&gt;The same patterns repeat&lt;/h3&gt;

&lt;p&gt;This is surprising. My &lt;a href=&quot;/things-I-learnt-from-a-senior-dev.html&quot;&gt;first work review&lt;/a&gt; was very cerebral - things I’d learned, how to test code, frameworks to think about code design etc. etc. My second work review was a lot more.. emotional. Looking at the soft skills, how it feels to take on a big new challenge, the fear of failure, etc. etc. The loop was much faster - these posts came out over two years.&lt;/p&gt;

&lt;p&gt;Now, this same pattern is repeating over six years of life reviews. The first post was all about frameworks on how I want to design my life, the next few years were about implementing it and seeing them fail. And this post is the culmination of those patterns. Some did well, but what became more important is how I felt, how I overcame challenges, not just intellectually, but by teaching myself to listen to my body, and being comfortable with anxiety. When I embraced it, &lt;em&gt;felt&lt;/em&gt; it for what it was, and repeatedly felt that experience over months, anxiety the “bad thing” finally went away.&lt;/p&gt;

&lt;p&gt;These same patterns show up at the micro level too. I have to rediscover the same things again and again for it to stick. I’m very much like an LLM in this sense, I need to experience a lot more examples than one would expect before it sinks in.&lt;sup id=&quot;fnref:12&quot;&gt;&lt;a href=&quot;#fn:12&quot; class=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt; Especially when it comes to complicated, non-intuitive situations, or where intuition leads me astray.&lt;/p&gt;

&lt;h3 id=&quot;i-sacrifice-stability-for-growth&quot;&gt;I sacrifice stability for growth&lt;/h3&gt;

&lt;p&gt;…and I still like this a lot. Sure, it leads to problems, and instability over time, but I’d do it all over again. Would I make better decisions given I know what I know now? 100%. Would I refrain from jumping into the next big thing worrying over whether I’ll fall in the same loop again? Nope. I might still flounder in a different way, but as long as I learn from it, as long as I &lt;em&gt;grow&lt;/em&gt;, that sets me up for a more successful third loop.&lt;/p&gt;

&lt;p&gt;It’s a trade off I’m very willing to make. Compared to 2019-me, I’m more resilient, a little wiser, and most importantly, more content.&lt;/p&gt;

&lt;p&gt;Four legs on the ground again, it’s time for the next adventure!&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/divider.jpg&quot; alt=&quot;&quot; style=&quot;width: 250px&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;script async=&quot;&quot; type=&quot;text/javascript&quot; src=&quot;//cdn.carbonads.com/carbon.js?serve=CE7D427L&amp;amp;placement=neilkakkarcom&quot; id=&quot;_carbonads_js&quot;&gt;&lt;/script&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;In micro-gravity, it might take very long. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;And maybe it’s not a stool, but an N-dimensional object, which requires N+1 legs to be stable, and I don’t realise these other legs exist because they’ve never been absent in my life for long. Or maybe just like in a Principal Component Analysis, the first four dimensions account for majority of the stability and the others don’t affect things that much. Or maybe I should stop stretching this analogy so much. &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;“When a measure becomes a target, it ceases to be a good measure”. This happens in organisations all the time. Inspired by Peter Drucker’s timeless wisdom, “What gets measured gets managed”, the quest to measure metrics begins, as it did for me. However, what’s easy to measure isn’t always what determines overall health. For example, weight increase is good if I’m gaining muscle mass, but bad when I’m gaining fat. Weight is much easier to measure though than muscle mass, so 🤷‍♂️. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;Why not gone completely once the stress is gone? Again, the body is a complex adaptive system, and once the gut health is fucked, it seems like it takes a lot longer for it to go back to normal. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:9&quot;&gt;
      &lt;p&gt;And a lesson for me to run Claude / ChatGPT over my blood reports. I don’t know if the 2023 models would’ve caught it, but the early 2025 one definitely did, and managed to connect with the slightly off values in other parameters. And these models are only getting better 🤯. &lt;a href=&quot;#fnref:9&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:7&quot;&gt;
      &lt;p&gt;When I push too hard. &lt;a href=&quot;#fnref:7&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot;&gt;
      &lt;p&gt;I’m glossing over this right now, but the way I frame it makes a huuuge difference! My relationship with anxiety is a lot less negative now than it was initially, and I imagine for a lot of people anxiety is a “bad” word. So when you frame the jitters as anxiety, it seems like a bad thing is happening to you, which leads to avoiding it. I’ll maybe talk more about this in a separate post - let me know if you’d like to hear more about it. &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:10&quot;&gt;
      &lt;p&gt;and it got trashed on Hacker News for the same, as expected 😂 &lt;a href=&quot;#fnref:10&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:6&quot;&gt;
      &lt;p&gt;“Learned a very relatable term today: ‘報復性熬夜’ (revenge bedtime procrastination), a phenomenon in which people who don’t have much control over their daytime life refuse to sleep early in order to regain some sense of freedom during late night hours.” - tweet deleted now, and I don’t want to link to news slop. &lt;a href=&quot;#fnref:6&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:12&quot;&gt;
      &lt;p&gt;And this is the third time I’ve referenced this idea of repetition to make things sink in, in a different way. Co-incidence? I think not. &lt;a href=&quot;#fnref:12&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>Neil Kakkar</name></author><summary type="html">If I had to choose one moment that signifies the deepest shift in the last few years, it would be mid-2024 when I had my first and only panic attack. It was my body and mind finally protesting and giving up, being like, “Neil, fuck this shit. You are not seeing the signs, so I have to take some drastic measures.”</summary></entry><entry><title type="html">How to setup duration based profiling in Sentry</title><link href="https://neilkakkar.com/sentry-duration-span-sampling.html" rel="alternate" type="text/html" title="How to setup duration based profiling in Sentry" /><published>2023-10-04T00:00:00+00:00</published><updated>2023-10-04T00:00:00+00:00</updated><id>https://neilkakkar.com/sentry%20duration%20span%20sampling</id><content type="html" xml:base="https://neilkakkar.com/sentry-duration-span-sampling.html">&lt;p&gt;I recently came across a gnarly issue: Every morning we’d see a latency spike in our API for for about 7-8 minutes. More importantly, it happened for a small fraction of requests (0.1%), making it pretty hard to reproduce.&lt;/p&gt;

&lt;p&gt;I figured leveraging Sentry’s profiling feature would be a good way to catch this. But this has its own problems.&lt;/p&gt;

&lt;p&gt;The first problem is that it’s very expensive to profile all requests: a billion samples where most of them are useless is a waste of money.&lt;/p&gt;

&lt;p&gt;Sentry allows sampling requests, which somewhat takes care of the cost problem.&lt;/p&gt;

&lt;p&gt;The second problem is that Sentry by default samples based on the count of events. So if you have a sampling rate of 1%, you’ll get approximately 1 in 100 requests. But, since most transactions are pretty fast, these are still useless. And 10 million requests instead of a billion is still a lot of wasted money.&lt;/p&gt;

&lt;p&gt;I required a way to sample requests by duration: instead of sampling by the request ID, I want to sample by duration. Give me 50% of all requests that took more than 2 seconds, and 0.0001% of all requests that took less than 2 seconds.&lt;/p&gt;

&lt;p&gt;The magic of this kind of sampling is that it gives me the most important spans, while being much cheaper than the default sampling.&lt;/p&gt;

&lt;p&gt;The docs don’t mention this is possible at all, but combing through the source code, I found a way to do this. Here’s how it works:&lt;/p&gt;

&lt;p&gt;Sentry has a &lt;a href=&quot;https://docs.sentry.io/platforms/python/configuration/filtering/#using-platformidentifier-namebefore-send-transaction-&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;before_send_transaction&lt;/code&gt; function&lt;/a&gt; that fires before sending a transaction to Sentry. We can use this to modify the transaction before it’s sent, or add any additional logic (like sampling by duration) to drop the transaction.&lt;/p&gt;

&lt;p&gt;This is a pure function that takes in the transaction, and either returns a transaction or None. If it returns None, the transaction is dropped. So, any kind of logic depends on what’s inside the transaction. See the appendix below for a sample transaction.&lt;/p&gt;

&lt;p&gt;In my case, all I need to know is the request URL (so I can apply this only to the API request I’m interested in), and the start and end timestamp (so I can sample based on that).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The only gotcha to remember here is that you need to manually sample things, instead of using the &lt;code class=&quot;highlighter-rouge&quot;&gt;traces_sampler&lt;/code&gt; or any builtin sentry sampling, since those apply before &lt;code class=&quot;highlighter-rouge&quot;&gt;before_send_transaction&lt;/code&gt;, which means it’s too late by the time you get the transaction.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s the code I ended up using:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;before_send_transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# get the request URL
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;url_string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# only apply this to the request I&apos;m interested in
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url_string&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;decide&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# default sampling rate based on all requests.
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;DECIDE_SAMPLE_RATE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.00001&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 0.001%
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;should_sample&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DECIDE_SAMPLE_RATE&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;transaction_start_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;start_timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;transaction_end_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction_start_time&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction_end_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;parsed_start_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction_start_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;parsed_end_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction_end_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

                &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parsed_end_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parsed_start_time&lt;/span&gt;

                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timedelta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seconds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;# return all events for transactions that took more than 8 seconds
&lt;/span&gt;                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timedelta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seconds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;# very high sample rate for transactions that took more than 2 seconds
&lt;/span&gt;                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;pass&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;should_sample&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;sentry_sdk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ...
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;before_send_transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;before_send_transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And finally, here’s a real world code example of &lt;a href=&quot;https://github.com/PostHog/posthog/pull/17729&quot;&gt;how we did this at PostHog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, what ended up being the issue? I’m not sure yet, but I have these lovely spans to help me investigate&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/sentry-spans.png&quot; alt=&quot;&quot; /&gt;
    
    
    
        &lt;figcaption&gt;   
            &lt;p&gt;Slow Sentry spans&lt;/p&gt;

        &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;h3 id=&quot;appendix-sample-transaction&quot;&gt;Appendix: Sample transaction&lt;/h3&gt;

&lt;p&gt;Here’s a sample transaction, which helps us determine all possible fields we can use:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;transaction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;transaction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/decide/[#].*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;transaction_info&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;source&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;route&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;contexts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;trace&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;trace_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9c1XXXXXXXXXXXXXXXde238&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;span_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;89XXXXXXXXXXXXXcc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;parent_span_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;90XXXXXXXe99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;op&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http.server&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ok&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tracestate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sentry=&amp;lt;base64 encoding of the above info&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;dynamic_sampling_context&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;trace_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9XXXXXXXXXXXXXXXXXXXX8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;public_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;3XXXXXXXXXXXXXXXXXXXXXXXXa&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sample_rate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1e-05&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;release&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;4XXXXXXXXXXXXXXXXXXXXXXXXXXXXX14&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;environment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;production&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;transaction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/api/projects/{parent_lookup_team_id}/persons/&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;runtime&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;CPython&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;3.10.10&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;3.10.10 (main, Mar  7 2023, 14:21:53) [Clang 13.1.6 (clang-1316.0.21.2.5)]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;http.status_code&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;200&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;origin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;unknown&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;referer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;unknown&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;library.version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;unknown&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2023-10-03T09:47:17.681238Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;start_timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2023-10-03T09:47:13.542852Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;spans&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;spans&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;the&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;trace_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9XXXXXXXXXXXXXXXXXXXX8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;span_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9XXXXXXXXXXXXXXXXXXXX8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;parent_span_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9XXXXXXXXXXXXXXXXXXXX8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;same_process_as_parent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;op&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;event.django&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;django.db.close_old_connections&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;start_timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2023-10-03T09:47:13.543012Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2023-10-03T09:47:13.543020Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;signal&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;django.db.close_old_connections&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;trace_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9XXXXXXXXXXXXXXXXXXXX8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;span_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9XXXXXXXXXXXXXXXXXXXX8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;parent_span_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9XXXXXXXXXXXXXXXXXXXX8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;same_process_as_parent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;op&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;middleware.django&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;posthog.middleware.PrometheusBeforeMiddlewareWithTeamIds.__call__&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;start_timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2023-10-03T09:47:13.543162Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2023-10-03T09:47:17.681195Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;django.function_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;django.utils.deprecation.MiddlewareMixin.__call__&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;django.middleware_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;posthog.middleware.PrometheusBeforeMiddlewareWithTeamIds&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;trace_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9XXXXXXXXXXXXXXXXXXXX8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;span_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9XXXXXXXXXXXXXXXXXXXX8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;parent_span_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9XXXXXXXXXXXXXXXXXXXX8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;same_process_as_parent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;op&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;db&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SELECT &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;posthog_persondistinctid&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;person_id&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;posthog_persondistinctid&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;distinct_id&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; FROM 
        &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;posthog_persondistinctid&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; WHERE (&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;posthog_persondistinctid&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;distinct_id&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; IN (%s) AND &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;posthog_persondistinctid&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.
        &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;team_id&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; = %s)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;start_timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2023-10-03T09:47:17.573475Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2023-10-03T09:47:17.583230Z&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;event_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9XXXXXXXXXXXXXXXXXXXX8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;extra&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sys.argv&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;manage.py&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;runserver&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://localhost:8000/decide/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;query_string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v=3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;env&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;SERVER_NAME&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1.0.0.127.in-addr.arpa&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;SERVER_PORT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;8000&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;headers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Content-Length&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;282&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Host&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;localhost:8000&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Sentry-Trace&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9c121e003284df18bf9905ad6dde238-90219bf565efce99-0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;User-Agent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;posthog-python/3.0.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Accept-Encoding&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;gzip, deflate, br&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Accept&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;*/*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Connection&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;keep-alive&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;release&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e9XXXXXXXXXXXXXXXXXXXX8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;environment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;production&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;server_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;192.168.1.104&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sdk&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sentry.python.django&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1.14.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;packages&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pypi:sentry-sdk&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1.14.0&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;integrations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;aiohttp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;argv&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;atexit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;boto3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;celery&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;dedupe&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;django&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;excepthook&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;logging&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;modules&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;redis&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;stdlib&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;threading&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;platform&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;python&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;_meta&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;rem&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;!config&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/divider.jpg&quot; alt=&quot;&quot; style=&quot;width: 250px&quot; /&gt;
    
    
    
&lt;/figure&gt;</content><author><name>Neil Kakkar</name></author><summary type="html">I recently came across a gnarly issue: Every morning we’d see a latency spike in our API for for about 7-8 minutes. More importantly, it happened for a small fraction of requests (0.1%), making it pretty hard to reproduce.</summary></entry><entry><title type="html">How to simulate a broken database connection for testing in Django</title><link href="https://neilkakkar.com/test-database-connection-django.html" rel="alternate" type="text/html" title="How to simulate a broken database connection for testing in Django" /><published>2023-01-15T00:00:00+00:00</published><updated>2023-01-15T00:00:00+00:00</updated><id>https://neilkakkar.com/test%20database%20connection%20django</id><content type="html" xml:base="https://neilkakkar.com/test-database-connection-django.html">&lt;p&gt;I spent a very long amount of time trying to figure out how to simulate a broken database connection in Django. The problem is that not only do you want raw cursors to timeout, but also all models accessing the database.&lt;/p&gt;

&lt;p&gt;This means, wherever we call &lt;code class=&quot;highlighter-rouge&quot;&gt;Model.objects.filter()&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;Model.objects.all()&lt;/code&gt;, or &lt;code class=&quot;highlighter-rouge&quot;&gt;connection.cursor()&lt;/code&gt;, the operation should fail.&lt;/p&gt;

&lt;p&gt;Google and ChatGPT both produced very disappointing results, which forced me to dive into Django’s source code and think of creative solutions to the problem.&lt;/p&gt;

&lt;p&gt;As a result, I think people will find this useful, future-me will be able to google the right answers, and a future-ChatGPT will be able to give the correct answer. Let me know if you find another way!&lt;/p&gt;

&lt;h3 id=&quot;option-1-patch-raw-cursors&quot;&gt;Option 1: Patch raw cursors&lt;/h3&gt;

&lt;p&gt;If you’re directly using a raw cursor, you can do the following:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;django.db.utils&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OperationalError&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;patch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;django.db.connection&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mock_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mock_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cursor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;side_effect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OperationalError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Connection timed out&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# write test that requires a database failure
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The problem with this option is that this doesn’t work everywhere. It’s also not feasible to patch all your models, so this can be brittle.&lt;/p&gt;

&lt;h3 id=&quot;option-2-patch-django-query-internals&quot;&gt;Option 2: Patch Django query internals&lt;/h3&gt;

&lt;p&gt;An approach that works for all models is to patch the internals:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;django.db.utils&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OperationalError&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;patch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;django.db.models.sql.compiler.SQLCompiler&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mock_compiler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mock_compiler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;side_effect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OperationalError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;Connection timed out&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# write test that requires a database failure
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The SQLCompiler is instantiated whenever a model tries to access the database. This ensures that whenever you call something like &lt;code class=&quot;highlighter-rouge&quot;&gt;Model.objects.all()&lt;/code&gt;, this side effect is triggered.&lt;/p&gt;

&lt;p&gt;The disadvantage of this method is that it relies on django internals which can change over time. So, on a random upgrade, you might find that your tests now fail.&lt;/p&gt;

&lt;h3 id=&quot;greatest-option-3-leverage-database-wrappers&quot;&gt;Greatest Option 3: Leverage database wrappers&lt;/h3&gt;

&lt;p&gt;The best way I’ve found to simulate a broken database connection is to leverage database wrappers. This is at a higher level of abstraction
than the internals, and thus a lot less brittle.&lt;/p&gt;

&lt;p&gt;Django provides a context manager to wrap all database query executions. You can do anything in here. Their main recommendation is to use this for instrumentation, but anything goes, and it’s ideal for testing.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;django.db.utils&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OperationalError&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueryTimeoutWrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__call__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OperationalError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Connection timed out&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;c1&quot;&gt;# If you want the query to run, you&apos;d use the below line
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;# return execute(*args, **kwargs)
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute_wrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QueryTimeoutWrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# write test that requires a database failure
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.djangoproject.com/en/4.1/topics/db/instrumentation/&quot;&gt;Read the docs for more information&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a real world example, &lt;a href=&quot;https://github.com/PostHog/posthog/pull/13708/files#diff-c35504b79f48e02d30d41daabc1ff514751199d7bed22e80c8b85c869007411eR929-R934&quot;&gt;check out this PR in PostHog&lt;/a&gt;, where I leveraged this technique to write tests.&lt;/p&gt;

&lt;h3 id=&quot;why-is-this-useful&quot;&gt;Why is this useful?&lt;/h3&gt;

&lt;p&gt;When I find it takes me longer than an hour to find a solution, I usually try to think of different approaches to side-step the issue. In this case, I couldn’t do without simulating a broken database.&lt;/p&gt;

&lt;p&gt;I was trying to write defensive code, ensuring we don’t return 500 errors even when the database is down, by clever use of exception handling and caching.&lt;/p&gt;

&lt;p&gt;In this case, there’s several random database models that might be called, and I can’t keep track of them. If I miss even one, things will blow up. So, testing my changes work for all cases is important. Further, it’s a good test for knowing whether caching is working as intended, as thanks to the patching above, any time a function uses the database instead of the cache, it will blow up and raise the &lt;code class=&quot;highlighter-rouge&quot;&gt;OperationalError&lt;/code&gt;.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/divider.jpg&quot; alt=&quot;&quot; style=&quot;width: 250px&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Thanks to Karl for pointing me to Option 3.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name>Neil Kakkar</name></author><summary type="html">I spent a very long amount of time trying to figure out how to simulate a broken database connection in Django. The problem is that not only do you want raw cursors to timeout, but also all models accessing the database.</summary></entry><entry><title type="html">The “People fuck up because they’re not like me” Fallacy</title><link href="https://neilkakkar.com/people-fuck-up-because-theyre-different.html" rel="alternate" type="text/html" title="The &quot;People fuck up because they&apos;re not like me&quot; Fallacy" /><published>2022-11-13T00:00:00+00:00</published><updated>2022-11-13T00:00:00+00:00</updated><id>https://neilkakkar.com/people%20fuck%20up%20because%20theyre%20different</id><content type="html" xml:base="https://neilkakkar.com/people-fuck-up-because-theyre-different.html">&lt;p&gt;Recently, I’ve noticed an interesting failure mode in humans. Here are a few examples, can you tell what’s wrong?&lt;/p&gt;

&lt;p&gt;I eat meat. My family doesn’t. At our last family gathering, my family urged me to go to the dentist, because it seemed like my teeth were sharper. The only reasonable conclusion was it’s an effect of meat, like how carnivores have sharp canines.&lt;/p&gt;

&lt;p&gt;I have a friend who washes his hands regularly. Like, every three hours. He also has a severe dry hands problem. His family is convinced it’s because he washes hands too often. The thinking goes something like: “We don’t wash hands regularly, and we don’t have dry hands. He washes hands too often, and he does have dry hands!”&lt;/p&gt;

&lt;p&gt;This is an interesting case of &lt;a href=&quot;https://www.lesswrong.com/tag/privileging-the-hypothesis&quot;&gt;privileging the hypothesis&lt;/a&gt;, where you figure an obvious cause for people’s problems is doing things differently.&lt;/p&gt;

&lt;blockquote class=&quot;blockquote-extra&quot;&gt;
  &lt;p&gt;The way I see others as different from me, is the way I expect their ill-effects to present themselves.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Further, it’s not just about trivial stuff.&lt;/p&gt;

&lt;p&gt;I was debugging code with a colleague once, and the first thing I jumped at as problematic were things done differently. Regardless of what the compiler says is the problem. I didn’t feel &lt;em&gt;comfortable&lt;/em&gt; with things done in such a way, and suspected them to be the root cause, just because I don’t code in the same way.&lt;/p&gt;

&lt;blockquote class=&quot;blockquote-extra&quot;&gt;
  &lt;p&gt;The way I see things done differently from me, is the way I expect inefficiencies and problems to arise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s not only personal differences that show the same way, but cultural differences too. The same people make up a culture, so a lot of inter-personal problems apply to inter-cultural problems as well.&lt;/p&gt;

&lt;p&gt;In the 14th century, the bubonic plague struck. Perhaps surprisingly, the Jews were relatively less affected. They didn’t use common wells, washed hands regularly, and washed their dead before burials. Around the same time, anti-semitism was widespread. Other people used to drink from the well, not wash their hands, and were healthy, until they were not. Seeing the antics of the Jews, and their relative continued health convinced people that the Jews were responsible for the plague. Sadly, this lead to &lt;a href=&quot;https://en.wikipedia.org/wiki/Persecution_of_Jews_during_the_Black_Death&quot;&gt;widespread massacares&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As we know now, Jews were definitely not responsible for the plague. It was rats instead.&lt;/p&gt;

&lt;blockquote class=&quot;blockquote-extra&quot;&gt;
  &lt;p&gt;The way I see other cultures as different from mine, is the way I expect their ill effects to shine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In all examples I’ve shared above, we let the contrast between personalities and cultures govern what’s wrong with other people. It’s an interesting mish-mash of two fallacies:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Contrast_effect&quot;&gt;Contrast bias&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.lesswrong.com/posts/baTWMegR42PAsH9qJ/generalizing-from-one-example&quot;&gt;Typical mind fallacy&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Contrast bias occurs whenever you’re comparing two or more things. The differences between these two things take up all your attention, while viewing them individually you might not reach the same conclusions.&lt;/p&gt;

&lt;p&gt;For example, a person will appear more attractive than they do in isolation when simultaneously compared to a less attractive person.&lt;/p&gt;

&lt;p&gt;For the same reason, I’d jump to conclusions around dry hands and washing hands, simply because I’m contrasting the two, rather than thinking clearly. Can more wetness lead to dryness? Actually, &lt;a href=&quot;https://www.eucerin.co.uk/skin-concerns/dry-skin/dry-skin-on-hands&quot;&gt;yes, it can&lt;/a&gt;, so the heuristic isn’t always wrong. Rather, it’s about discarding all other information because the contrast pops at you.&lt;/p&gt;

&lt;p&gt;The typical mind fallacy occurs whenever you assume that every person’s brain is similar to yours. If you like rock music, everyone who doesn’t is plain weird. You compare people, and subject them to the same goals and ideals as yourself. A perverse way this shows up is thinking others are ‘lesser’, when they can’t do what you can, specially when you assume they’ve had the same opportunities as you.&lt;/p&gt;

&lt;p&gt;While most people have similar goals, their values and constraints are usually different. It’s hard to tell a recessive gene is the cause of resisting the plague, when you can’t see the gene in action. It’s easier to contrast and jump to conclusions that make sense from your mind’s point of view.&lt;/p&gt;

&lt;p&gt;With most biases, the trick is to notice what’s happening, have a name to call it, and you’re in a good spot to at least know when you’ve already fallen for it. So, to end, I’ll leave you with a name. It’s the “People fuck up because they’re not like me” Fallacy.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/divider.jpg&quot; alt=&quot;&quot; style=&quot;width: 250px&quot; /&gt;
    
    
    
&lt;/figure&gt;</content><author><name>Neil Kakkar</name></author><summary type="html">Recently, I’ve noticed an interesting failure mode in humans. Here are a few examples, can you tell what’s wrong?</summary></entry><entry><title type="html">How I Own Projects as a Software Engineer</title><link href="https://neilkakkar.com/How-I-Own-Projects-as-a-Software-Engineer.html" rel="alternate" type="text/html" title="How I Own Projects as a Software Engineer" /><published>2021-12-14T00:00:00+00:00</published><updated>2021-12-14T00:00:00+00:00</updated><id>https://neilkakkar.com/How%20I%20Own%20Projects%20as%20a%20Software%20Engineer</id><content type="html" xml:base="https://neilkakkar.com/How-I-Own-Projects-as-a-Software-Engineer.html">&lt;p&gt;When you take ownership of a project, something changes. You become responsible for everything that happens with the project.&lt;/p&gt;

&lt;p&gt;Things go wrong? That’s on you. Customers are unhappy? That’s on you. You shipped something that doesn’t solve the problem? That’s on you.&lt;/p&gt;

&lt;p&gt;Ideally, you have a process that reduces the number of ways things can go wrong.&lt;/p&gt;

&lt;p&gt;I started at &lt;a href=&quot;https://posthog.com/?utm_source=neilkakkar&quot;&gt;PostHog&lt;/a&gt; ~8 months ago. Since then, I’ve been fortunate to own 2 big projects. I’m nowhere near experienced enough to be talking about prescriptions, but that’s not the point. A big part of growing quickly is understanding what you’re doing and why you’re doing it. So, this post is my model of how to own and build new products.&lt;/p&gt;

&lt;p&gt;Something magical happens when you own the entire stack. From talking to customers, to deciding what to build, to deciding who will build what, to finally getting feedback, and then redoing this cycle. You see the entire flow, which gives you better context on what to do. Sometimes, software isn’t the way to solve problems.&lt;/p&gt;

&lt;p&gt;Broadly, I’ve noticed 5 steps. The goal behind each of these is to increase your chances of success. I’ll explain them, and follow up with metas behind these steps. Finally, since PostHog is open-source, I’ll link to precise real-world examples for what I’m talking about.&lt;/p&gt;

&lt;script async=&quot;&quot; type=&quot;text/javascript&quot; src=&quot;//cdn.carbonads.com/carbon.js?serve=CE7D427L&amp;amp;placement=neilkakkarcom&quot; id=&quot;_carbonads_js&quot;&gt;&lt;/script&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;nav&gt;

  &lt;h4&gt;Table of Contents&lt;/h4&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#gather-context&quot; id=&quot;markdown-toc-gather-context&quot;&gt;Gather Context&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#figure-out-a-solution&quot; id=&quot;markdown-toc-figure-out-a-solution&quot;&gt;Figure out a solution&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#build&quot; id=&quot;markdown-toc-build&quot;&gt;Build&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#gather-feedback&quot; id=&quot;markdown-toc-gather-feedback&quot;&gt;Gather Feedback&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#align-metrics-with-feedback&quot; id=&quot;markdown-toc-align-metrics-with-feedback&quot;&gt;Align metrics with feedback&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#meta-create-feedback-loops&quot; id=&quot;markdown-toc-meta-create-feedback-loops&quot;&gt;Meta: Create feedback loops&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#meta-manage-your-emotions&quot; id=&quot;markdown-toc-meta-manage-your-emotions&quot;&gt;Meta: Manage your Emotions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/nav&gt;

&lt;!-- works only once jQuery is loaded --&gt;
&lt;script&gt;

let banner_width = 55;

$(document).ready(() =&gt; {
    $(&apos;a[href^=&quot;#&quot;]&apos;).on(&apos;click&apos;, function(event) {
        var hash = &apos;#&apos; + $(this).attr(&apos;href&apos;).split(&apos;#&apos;)[1]
        var element = $(hash)
        if (element.length) {
            event.preventDefault();
            history.pushState(hash, undefined, hash)
            $(&apos;html, body&apos;).animate({scrollTop: element.offset().top - banner_width}, 500)
        }
    });

    window.addEventListener(&apos;popstate&apos;, function(e) {
        if(e.state &amp;&amp; e.state.startsWith(&apos;#&apos;) &amp;&amp; $(e.state).length){
        $(&apos;html, body&apos;).animate({scrollTop: $(e.state).offset().top - banner_width}, 500)
        }
    });

    $(&apos;html, body&apos;).on(&quot;scroll mousedown wheel DOMMouseScroll mousewheel keyup touchmove&quot;, function(){
        $(&apos;html, body&apos;).stop();
    });

  	if (location.hash) {
    	// $(&apos;html,body&apos;).animate({scrollTop: $(location.hash).offset().top - banner_width}, 500)
  	};
});
&lt;/script&gt;

&lt;h2 id=&quot;gather-context&quot;&gt;Gather Context&lt;/h2&gt;

&lt;p&gt;Trying to find solutions before you understand the problem will lead you around in circles. First, understand the problem. This begins with understanding the use case.&lt;/p&gt;

&lt;p&gt;An interesting project I recently worked on was Experimentation. If you think of it in terms of what you should be building, it would be: “Build an A/B testing platform”. This problem severely lacks context. When you think in terms of what to do, you lose track of what users really wanted.&lt;/p&gt;

&lt;p&gt;It also makes it harder to prioritise &amp;amp; figure out what to build. For example, say you’re just starting off with this project. What do you include in the MVP? What are the risky things you need to test quickly?  You can’t answer these questions because they’re outside the scope of the problem.&lt;/p&gt;

&lt;p&gt;Things are different when you think in terms of the use case. Begin with: “People have trouble A/B Testing because they are missing instrumentation, and the results they get are hard to understand.”  This tells you what you need to do: (1) Make instrumentation easy &amp;amp; (2) Make results easy to understand.&lt;/p&gt;

&lt;p&gt;Getting to this point is hard, and the first obstacle to building a successful product. To understand the use case, you need to talk to users. Ask questions. Go deep into what they say. &lt;a href=&quot;https://posthog.com/blog/how-to-work-out-what-users-need?utm_source=neilkakkar&quot;&gt;Here’s how PostHog does it.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you understand the use case, you can judge what problems are related to your own, and whether existing solutions work or not. In a world where similar problems have been solved before, it would be a shame to not understand how others have solved these problems.&lt;/p&gt;

&lt;p&gt;Take some time to see how others have solved these problems. With A/B testing platforms, there’s hundreds of them in the wild! What helped me lead this project better was to know how these A/B testing platforms work, which problems they focus on solving, and take inspiration from it.&lt;/p&gt;

&lt;p&gt;For example, I realised that most A/B Testing platforms are standalone: they’re unbundled, so instrumentation and deciding on target metrics happens elsewhere (say, your analytics suite), and you come here for doing analysis on the results. Since PostHog already has Feature Flags for instrumentation, and it’s main job is literally an analytics suite, there’s real opportunity for bundling here. &lt;a href=&quot;https://github.com/PostHog/posthog/issues/7418#issuecomment-982618472&quot;&gt;This is what governed our thinking about this feature.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking at solutions also gives you a better understanding of the risks you might be taking. With experimentation, since our goal was to make results easy to understand, and reduce the number of things users have to know to run a good A/B Test, we decided to test out the Bayesian analysis route. This gets rid of concepts like p-values, statistical significance, confidence intervals, and focuses on a singular value: the probability that your variant is better than the control. &lt;a href=&quot;https://github.com/PostHog/posthog/issues/7418#issuecomment-984616174&quot;&gt;For the nitty gritty details, see here&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;figure-out-a-solution&quot;&gt;Figure out a solution&lt;/h2&gt;

&lt;p&gt;Once you have context, you can start thinking of solutions.&lt;/p&gt;

&lt;p&gt;One trap I’ve noticed myself falling into is taking inspiration too heavily from existing solutions. How can you build a better version if you repeat the same mistakes they’ve made?&lt;/p&gt;

&lt;p&gt;Another trap is thinking only in terms of what’s possible technically. This is specially a problem with engineers. You know how the system works, you know the compromises you’ve made, so you limit the world of possible solutions that fit into these existing constraints. This is a problem, because it often underestimates what’s possible.&lt;/p&gt;

&lt;!-- A big reason is that you&apos;re not thinking in terms of &quot;How can I build X?&quot;, but in terms of &quot;Given constraint Y and Z, how can I build X?&quot; --&gt;

&lt;p&gt;The way I evade both these traps is to start with ideal solutions. What solution would completely solve for the usecase? Doing this allows me to &lt;a href=&quot;/A-framework-for-First-Principles-Thinking.html&quot;&gt;tease out the first principles&lt;/a&gt;. “What is it about the ideal solution that makes it ideal? What is the crux that solves the problem?”.&lt;/p&gt;

&lt;p&gt;For example, with experimentation, I came up with 3 first principles born out of an ideal solution:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The numbers have to be 100% accurate. No estimation, no rough calculation, no infrastructure affecting the A &amp;amp; B test buckets. Someone who was in bucket A can’t move to bucket B two days later.&lt;/li&gt;
  &lt;li&gt;The results should be easy to understand
    &lt;ul&gt;
      &lt;li&gt;There’s a lot more leeway here. Figuring this out well means going back to your users and asking for feedback.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;People should be able to test their changes before launching the A/B test.
    &lt;ul&gt;
      &lt;li&gt;The experiment becomes invalid if both A &amp;amp; B variant are showing the same thing when they shouldn’t be.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What’s very cool about engineers owning products is the conversation you can have with yourself. On one side are the technical constraints: what’s possible given what we have now? On the other side are the use cases: Disregarding all technical constraints, what does the ideal solution look like?&lt;/p&gt;

&lt;p&gt;When technical constraints clash against the ideal, I can go back to the first principles. Now, given I want to preserve these principles, how can I solve this problem?&lt;/p&gt;

&lt;p&gt;The above question forces me to think of levers I can pull. I often believe that the only lever I have is the technical constraints: What clever optimisations can I do that will make this possible?&lt;/p&gt;

&lt;p&gt;But this isn’t true. There’s another powerful lever I have: solving the problem differently. In practice, this means changing the UX, while preserving the first principles.&lt;/p&gt;

&lt;p&gt;For example, I clashed against a &lt;a href=&quot;https://github.com/PostHog/posthog/issues/7462#issuecomment-987868293&quot;&gt;hard technical constraint in experimentation&lt;/a&gt;. It’s not something that can’t be solved, but doing it takes way too much engineering resources. Instead, I opted to change the UX. We’re still respecting the three first principles, it’s just that the creation flow is different from how I expected the ideal one to be.&lt;/p&gt;

&lt;p&gt;Worth noting here again are the three principles for experimentation. While (1) is technical, there’s a lot more flexibility with (2) and (3). The UX lever becomes a lot more potent when you hit technical constraints you can’t overcome. That’s exactly what I did. Principle (3) doesn’t prescribe anything about existing Feature Flags. So, to overcome the technical constraint, we added a new UX constraint: People can’t reuse existing feature flags.&lt;/p&gt;

&lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&gt;
  &lt;p&gt;&lt;i class=&quot;fas fa-info-circle&quot;&gt;&lt;/i&gt;&lt;b&gt;Note:&lt;/b&gt;&lt;br /&gt;
Next time I’m leading a project, I should make the principles explicit. They stay in my head most of the time, but it ought to be very helpful for the team to know them as well: it’s valuable context that helps everyone make decisions.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;build&quot;&gt;Build&lt;/h2&gt;

&lt;p&gt;We’ve now figured out the principles thanks to the usecase we’re solving for, and have a solution in mind. I want to move the quickest when building.&lt;/p&gt;

&lt;p&gt;As the owner, my biggest responsibility here is setting priorities. Priorities help everyone move quickly and get shit done. I’ve been heavily influenced by Scott Berkun’s post on &lt;a href=&quot;https://scottberkun.com/2012/how-to-make-things-happen/&quot;&gt;making things happen.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means creating an ordered list of tasks that need to get done to achieve your goals. For huge projects, I might use a Kanban board, but for smaller ones, a single GitHub issue seems ideal. For example, here’s one &lt;a href=&quot;https://github.com/PostHog/posthog/issues/7462&quot;&gt;I created for Experimentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In most cases, I get a MVP out first. Choosing what parts are important needs the right priorities set. Does it need proper UIs or not? Can it work with mock data? What’s the smallest working expression of our first principles?&lt;/p&gt;

&lt;p&gt;The first project I led, I faced this whirlwind of tasks when building: there’s so many fucking things to do: a few people who need my help with stuff, figuring out designs, doing PR reviews etc. etc. A few days into the implementation had me juggling between all of these. I was moving around a lot, without getting much done. The core problem, I realised, was that I’d jump on whatever was dropped on my plate.&lt;/p&gt;

&lt;p&gt;If I’m coding something up, and a PR request comes in: since no one else will be reviewing this, and I’m the owner - I’d want to unblock others quickly so they can keep moving fast - I’d drop what I was doing, do the review first, and then get back on whatever I dropped. Like a stack, last in first out.&lt;/p&gt;

&lt;p&gt;This is terrible prioritisation. I had read Scott’s blogpost a while ago, but feeling it viscerally was what made it click for me: I need to get my priorities in order &amp;amp; follow through on them. This resolved things much better. I got rid of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Thrashing_(computer_science)&quot;&gt;thrashing problem&lt;/a&gt; by using a better scheduling algorithm. Whatever is the highest priority gets done first, no matter what comes in.&lt;/p&gt;

&lt;p&gt;This ties back into how important the first two steps are. If you skipped them before building, your goal would simply be “build an A/B Test platform”. You’d have a much harder time prioritising because you lack context, and don’t know principles you can’t compromise on.&lt;/p&gt;

&lt;p&gt;Being effective here also means keeping people on the same page, and ensuring that they can be productive. With more than a few people, I imagine this can get hard. I’ve only led very small teams (1-3 people), so things have been smooth so far.&lt;/p&gt;

&lt;p&gt;I also appreciate my manager’s job a lot more now. I need to think about what every person does, and how they work. Given how &lt;a href=&quot;https://neilkakkar.com/personality-loop.html&quot;&gt;the Big 5 Personality test&lt;/a&gt; accurately represents people in 5 dimensions, I’d hypothesize most people are categorizable. There’s only a few dimensions to them.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Like a Fourier Transform, instead of working with those 5 dimensions, it’s more effective to work in dimensions that are easier to measure at work. I’m yet to figure these out completely (too few experiences dealing with people), but a few dimensions that stand out:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Do they ask for help if they get stuck.&lt;/li&gt;
  &lt;li&gt;To be productive, do they need me to (a) just set context; or (b) micromanage.&lt;/li&gt;
  &lt;li&gt;What are their strengths?&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Are they gear-driven or behaviour-driven?&lt;/p&gt;

    &lt;p&gt;I’m very gearsy - which means when I propose UX designs, I tend to go for “transparent internals” approach. As I’ve come to see, this is almost always the wrong thing to do. Most users don’t care about how their problems are solved, just that there’s a button which solves them.&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;????&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I think what makes management even easier at smaller companies is that most people are around the same on these dimensions, because the interview process selects for this. For example, at PostHog, once context is set, you can &lt;a href=&quot;https://posthog.com/handbook/company/values#step-on-toes&quot;&gt;let people do what they feel is the most important.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I appreciate how I’m a complete n00b at management. But then again, I don’t think just adding years is going to make me better at it. Like I said in the introduction, if I don’t understand what I’m doing and why I’m doing it, I can’t improve quickly. Hence, my hypothesis on management.&lt;/p&gt;

&lt;p&gt;Hopefully, you’re effective enough that you and your teammates get an MVP out. The next step is gathering feedback.&lt;/p&gt;

&lt;h2 id=&quot;gather-feedback&quot;&gt;Gather Feedback&lt;/h2&gt;

&lt;p&gt;The best way I’ve found so far is doing user interviews. Nothing compares to the high fidelity one on one feedback, where you can go deep into whatever they say.&lt;/p&gt;

&lt;p&gt;When gathering feedback, there’s two important things you’re testing:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Whether the principles you arrived at are indeed the first principles.&lt;/p&gt;

    &lt;p&gt;It’s easy to misunderstand what users really want. With a concrete MVP in place, you could ask hypotheticals like: “How would you do &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;Important Thing&amp;gt;&lt;/code&gt; if this feature didn’t exist?”. You’re looking for confirmation that the information you gathered in the first step is still valid.&lt;/p&gt;

    &lt;p&gt;For example, with experimentation, I would go deep into what users mean by the results being easy to understand. Is us telling them that A has a 85% chance to beat B good enough? Do they need more information? Why?&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Whether your solution is a reasonable expression of those first principles.&lt;/p&gt;

    &lt;p&gt;Don’t just confirm you’re on the right track; ensure you actually solve the problems. Paolo, PostHog’s Product Manager, does this by giving users challenges. For example, “Say you want to create a new experiment. How will you do it?”. I often follow up with questions, like ‘What do you think this graph means?’ or ‘Why are you getting that warning?’&lt;/p&gt;

    &lt;p&gt;After these challenges, it’s a good idea to ask for general feedback: What’s missing? What do you love? Did this solve your problem?&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the end, you want to come out with two things: (1) Do users love this? and (2) If not, what’s missing? What do you need?&lt;/p&gt;

&lt;h2 id=&quot;align-metrics-with-feedback&quot;&gt;Align metrics with feedback&lt;/h2&gt;

&lt;p&gt;Just having happy users in feedback calls isn’t enough. I always need to ensure that metrics line up with what users are saying.&lt;/p&gt;

&lt;p&gt;I like to go through two cycles of &lt;code class=&quot;highlighter-rouge&quot;&gt;Gather context -&amp;gt; Figure out solution -&amp;gt; build -&amp;gt; gather feedback&lt;/code&gt;. At this point, things are usually good to go. If I’m doing things right, the second cycle is much faster: it’s adding on functionality that we skipped in the MVP, so the gathering context &amp;amp; figuring out solutions steps can mostly be skipped.&lt;/p&gt;

&lt;p&gt;At this stage I’ll add analytics support (i.e. start sending in events about how users are using the product), start releasing to a % of the world, and see how things go. You ought to be able to answer questions like: “How many people are using it?”, “Of the people using it, how many find it valuable?”, and any other relevant product questions.&lt;/p&gt;

&lt;p&gt;I build a dashboard of metrics for every product I’ve worked on, and check it every Friday. This is important in the early weeks right after you launch, since it shows you if there are bugs with event capture, and if you can answer all questions you have about the product. If I can’t tell which part of this product is most popular, I’m probably lacking event instrumentation: I should add those missing events as soon as possible. Otherwise, I’m flying blind.&lt;/p&gt;

&lt;p&gt;Usually, it takes longer for patterns to show up in metrics. At this point, I’ll put the project in background mode, wait for user patterns to show up, and then decide what I want to do about it.&lt;/p&gt;

&lt;p&gt;For example, with my first ever project, correlation analysis, our usage funnel today looks something like this:&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/correlation-funnel.jpg&quot; alt=&quot;&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;People love correlation analysis a lot, as evident by the 85% conversion rate for the second step. The problem is discovery: most people don’t know it exists. So, whenever I come back to it next, the problem I’d want to solve is making it more discoverable.&lt;/p&gt;

&lt;h2 id=&quot;meta-create-feedback-loops&quot;&gt;Meta: Create feedback loops&lt;/h2&gt;

&lt;p&gt;A common theme across all the steps I want to highlight is feedback loops. To work quickly &amp;amp; to make sure you’re doing the right things, you need several feedback loops. Both, long term and short term.&lt;/p&gt;

&lt;p&gt;Each step above has its own quick feedback loops, where you get feedback within hours.&lt;/p&gt;

&lt;p&gt;For example, when gathering context, don’t do it in a silo. Do it with your teammates (specially product owners). Explore competitor products together. Make the thread open, so everyone in the company can see and contribute to it. It’s valuable to hear from colleagues who might have more context because they were an insider.&lt;/p&gt;

&lt;p&gt;In the building phase, fast feedback loops mean small pull requests and quick reviews! Imagine how much faster you get things done and how much better your code looks when you have small PRs that get reviewed quickly, vs. a 500 line change that takes ages to get reviewed.&lt;/p&gt;

&lt;p&gt;Apart from these quick feedback loops, there are slower but still important feedback loops between consecutive steps. The stage outputs aren’t set in stone. There’s going to be times when things come up that you didn’t think about earlier.&lt;/p&gt;

&lt;p&gt;New technical constraints might show up while implementing the solution, that forces you to go back to the “figuring out the solution” stage. Don’t get married to the initial idea you came up with. Divorces are hard.&lt;/p&gt;

&lt;p&gt;While working on experimentation, we decided to allow reusing an existing Feature flag to do experiments. This made sense because people would create the feature flag, test the A &amp;amp; B versions look alright, and then use that same feature flag in an experiment, without having to do any code changes.&lt;/p&gt;

&lt;p&gt;However, during implementation I realised this clashed with one of the principles. Making things work like this meant the results would not be 100% accurate.&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; So, as I mentioned above, we came up with the extra constraint of not re-using feature flags, and generating code snippets to explain how to test with this new feature flag.&lt;/p&gt;

&lt;p&gt;Finally, the longest feedback loops come with usage over time. This means tracking your metrics properly. The final step, “aligning metrics with feedback” is precisely this: a long feedback loop that gets rid of the noise and shows you exactly how users are using this product.&lt;/p&gt;

&lt;p&gt;The meta is to always create feedback loops.&lt;/p&gt;

&lt;h2 id=&quot;meta-manage-your-emotions&quot;&gt;Meta: Manage your Emotions&lt;/h2&gt;

&lt;p&gt;When I led my first project, something switched. For the first time, I felt this responsibility of getting shit done myself - there’s no one else I can fall back on. This led to me being a lot more “switched on”. I found it hard to switch off after work. I worried about whether I’ll hit my self-imposed deadlines, about technical challenges we haven’t yet solved, and whether I’ll succeed or not.&lt;/p&gt;

&lt;p&gt;I love switching off after work. This is wonderful for my mental health, and allows me to freely enjoy my other hobbies outside of work. So, I knew that owning projects where I’m always switched on like this wasn’t feasible.&lt;/p&gt;

&lt;p&gt;This was all on me: I wasn’t used to leading projects, and that took a toll on me.&lt;/p&gt;

&lt;p&gt;The next time around, things were a lot more stable. I don’t know what changed, but I felt a lot more in control. I think familiarity with the process helped me face this anxiety better. Not surprisingly, I’ve faced something &lt;a href=&quot;https://twitter.com/neilkakkar/status/1229116977836412928&quot;&gt;like this&lt;/a&gt; &lt;a href=&quot;https://medium.com/swlh/how-to-learn-anything-quickly-leverage-the-vocabulary-c6e23ff7c56&quot;&gt;every time&lt;/a&gt; &lt;a href=&quot;/quickreps.html&quot;&gt;I’m trying something&lt;/a&gt; new. It’s a feeling I’ve come to accept.&lt;/p&gt;

&lt;p&gt;Another thing I noticed was that anxiety towards the end of a project shot up. The MVP felt easy: there’s no polishing required, it’s just the concept, get it out and you’re good. The project isn’t done with the MVP though. To finish, you actually need to tie up all the loose ends and polish the UI. The closer I got to finishing, the more I worried. I think a big part of this was that I had never built a pretty frontend before (every frontend I built before looked like shit). It was an unknown that I didn’t care about earlier, because I knew there’s atleast a few people at PostHog who are very experienced at building pretty things. Working through this unknown myself was very useful: it helped me face my “fear” and gain confidence in an area I was unfamiliar.&lt;/p&gt;

&lt;p&gt;Since I was the project owner, it would’ve been just as easy to pass this work onto someone else. But noticing what I was doing and why I was doing it helped me make better decisions.&lt;/p&gt;

&lt;p&gt;I don’t have much to offer in terms of what to do about emotions. They come, change your direction, and flitter away. What I’ve found helpful is noticing these emotions. Notice how you’re feeling and why you’re feeling that way. It gives me clarity, and just doing this is enough to make better decisions.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Each of the 5 steps above are crucial. They bring something important to the table, and I suspect are necessary for every successful product.&lt;/p&gt;

&lt;p&gt;I figured the hard part of software engineering was building things. But now, maybe it’s figuring out what to build. Knowing what to build seems rarer. That’s surprising to me, since you don’t need to know how to code to figure out what to build.&lt;/p&gt;

&lt;p&gt;There are probably more subtle things I’m doing wrong (and right), which I haven’t been noticing yet. But this post is my playbook: new things I notice get inserted into one of these steps, warnings get added, and I keep getting better and better.&lt;/p&gt;

&lt;p&gt;Wrapped in here is the third meta: To improve quickly, you need to know what you’re doing and why you’re doing it.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/divider.jpg&quot; alt=&quot;&quot; style=&quot;width: 250px&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;It’s hard for me to believe that everyone is unique, etc. etc. Most people seem to have similar lives and the same problems. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;A bit of an overgeneralisation, depends a lot on the context/industry/solution. But since I’m too gearsy, phrasing it this way ensures I take it seriously. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;Not because the feature flag is already in use, but because of the type of feature flag. &lt;a href=&quot;https://github.com/PostHog/posthog/issues/7462#issuecomment-987868293&quot;&gt;Real details here&lt;/a&gt; &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>Neil Kakkar</name></author><summary type="html">When you take ownership of a project, something changes. You become responsible for everything that happens with the project.</summary></entry><entry><title type="html">Funnels: The One Big Mental Model from Sales &amp;amp; Marketing</title><link href="https://neilkakkar.com/funnels-mental-model.html" rel="alternate" type="text/html" title="Funnels: The One Big Mental Model from Sales &amp; Marketing" /><published>2021-08-22T00:00:00+00:00</published><updated>2021-08-22T00:00:00+00:00</updated><id>https://neilkakkar.com/funnels%20mental%20model</id><content type="html" xml:base="https://neilkakkar.com/funnels-mental-model.html">&lt;p&gt;The traditional funnel is a measurement device for, say, number of people converting in your product.&lt;/p&gt;

&lt;p&gt;But funnels are a lot more universal, and you can use them to answer tough questions in Physics and Chemistry, not just for human behaviour. This post shows you how to use funnels more often, what are the general principles behind funnels, and finally, why this flexibility makes funnels the one big mental model from sales and marketing.&lt;/p&gt;

&lt;p&gt;We’ll start with 4 weird examples of funnels in the wild.&lt;/p&gt;

&lt;p&gt;Then, we’ll use the examples to decipher the general principles. We investigate what are the similarities and differences between the examples to come up with a general model for Funnels.&lt;/p&gt;

&lt;p&gt;Overall, we’ll answer what exactly are funnels, why they’re cool, and where to apply them. Let’s dig in.&lt;/p&gt;

&lt;script async=&quot;&quot; type=&quot;text/javascript&quot; src=&quot;//cdn.carbonads.com/carbon.js?serve=CE7D427L&amp;amp;placement=neilkakkarcom&quot; id=&quot;_carbonads_js&quot;&gt;&lt;/script&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;crude-oil-distillation&quot;&gt;Crude Oil Distillation&lt;/h2&gt;

&lt;p&gt;To dissolve all connotations with existing usage of funnels, let’s start with something more arcane. Let’s go to Chemistry &amp;amp; fractional distillation.&lt;/p&gt;

&lt;p&gt;Why do you think crude oil is called black gold? It’s not just because it produces petrol. It’s because it produces &lt;em&gt;several&lt;/em&gt; kinds of fuels: gasoline, diesel fuel, heating oil, jet fuel, petrochemical feedstocks, waxes, lubricating oils, and asphalt. Crude oil starts with a mixture of these, and goes through several rounds of processing which spits these fuels out one by one.&lt;/p&gt;

&lt;p&gt;At every round of processing, depending on the temperature, you get one product out of crude oil. So, for example, if you first set the temperature at 100 degrees, you get petrol/gasoline out of crude oil. Then, at 150 degrees, you get kerosene out of crude oil. And now the mixture has diesel oil, lubricating oil, and jet fuel left inside. You keep going through these steps, until you only have jet fuel left.&lt;/p&gt;

&lt;p&gt;This is a funnel&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. You have a lot of “things” which are processed over different steps, such that you lose some, until you’re left with one thing.&lt;/p&gt;

&lt;p&gt;Three more examples, and then we’re ready to draw the pattern out from it.&lt;/p&gt;

&lt;h2 id=&quot;etl-pipeline&quot;&gt;ETL Pipeline&lt;/h2&gt;

&lt;p&gt;Let’s jump to software engineering.&lt;/p&gt;

&lt;p&gt;Say you have an excel sheet of survey results. You asked people for their names, work place, salary, and favourite color. Now, it’s time to process this data: First, you want to remove all excel rows that have a column empty. You dislike people who don’t fill in the full survey, and don’t care about their data. Then, since your favourite colour is blue, you’re only interested in people whose favourite colour is blue.&lt;/p&gt;

&lt;p&gt;Finally, you have a set of people who filled the survey completely and like the colour blue, so you insert them into a new excel sheet.&lt;/p&gt;

&lt;p&gt;This process is an Extract-Transform-Load, or ETL pipeline. You begin with a data source (excel), extract data from it, then transform it (remove nulls, choose colour blue),  and then load it into another data store (excel again for our example).&lt;/p&gt;

&lt;p&gt;The ETL pipeline is a funnel. You start with some rows of data, then you go through a series of processing steps which lead to discarding some rows, and finally, you end up with a smaller set of rows.&lt;/p&gt;

&lt;p&gt;Of course, it’s possible that you don’t discard any rows. For example, if the survey happened in a diligent population who likes blue, everyone would’ve filled the entire survey, and chosen the color blue.&lt;/p&gt;

&lt;p&gt;It’s also possible that you discard every row. If the survey happened in a red state, no one would say they like the colour blue.&lt;/p&gt;

&lt;h2 id=&quot;conversion-funnels&quot;&gt;Conversion Funnels&lt;/h2&gt;

&lt;p&gt;Now comes the text-book example from sales and marketing, the conversion funnel.&lt;/p&gt;

&lt;p&gt;Say you have a website, and lots of people visit it.&lt;/p&gt;

&lt;p&gt;The conversion funnel tracks how many users came to your website, how many of those who came signed up, and finally, how many of those who signed up started paying. It tracks how well people “convert” from being just a random visitor to someone paying you money.&lt;/p&gt;

&lt;p&gt;Usually people drop off at every stage.&lt;/p&gt;

&lt;p&gt;Below is a real funnel from the analytics company I work at, &lt;a href=&quot;https://posthog.com/?utm_source=neilkakkar&quot;&gt;PostHog&lt;/a&gt;. Notice how you can get a lot of information from this. Most people do a pageview after signing up. That’s expected, and it’s quick - takes them 3 seconds to go from sign up to a pageview. The third step is a bit harder. It takes longer for people to reach, and not everyone is successful at it.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/posthog-funnel.jpg&quot; alt=&quot;&quot; /&gt;
    
    
    
        &lt;figcaption&gt;   
            &lt;p&gt;A Funnel created on &lt;a href=&quot;https://posthog.com/?utm_source=neilkakkar&quot;&gt;PostHog.com&lt;/a&gt;&lt;/p&gt;

        &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;h2 id=&quot;pachinko&quot;&gt;Pachinko&lt;/h2&gt;

&lt;p&gt;For our final example, let’s talk about the ball game Pachinko.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://i.makeagif.com/media/7-30-2021/0LSefB.gif&quot; alt=&quot;Pachinko Machine&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There’s lots of balls flowing through the machine, and only some specifc routes lead to winning. Each route for the ball is a funnel. What’s particularly interesting are the routes that lead to wins.&lt;/p&gt;

&lt;p&gt;For creating a successful Pachinko machine, we’d want the winning funnels to have a small conversion rate. While as players, we’d want to choose the Pachinko machine with the highest conversion rate. Unfortunately for players, they don’t have enough data to figure out the conversion rates: it’s a closely guarded secret by Pachinko parlour owners.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/divider.jpg&quot; alt=&quot;&quot; style=&quot;width: 250px&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;Put together, all four examples are purposefully bizzare. They don’t seem quite the same, but they all use the concept of funnels. We can now look at the similarities and differences to figure out the core ideas.&lt;/p&gt;

&lt;h2 id=&quot;where-funnels-make-sense&quot;&gt;Where Funnels make sense&lt;/h2&gt;

&lt;p&gt;Just like how the average of one value doesn’t make much sense&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, a funnel of one item doesn’t make sense. The power of averages, and funnels, come when you’re dealing with lots of things, and need a better way to summarize this data.&lt;/p&gt;

&lt;p&gt;In the crude oil example, we were aggregating over molecules.&lt;br /&gt;
In the ETL pipeline, we were aggregating over rows of data.&lt;br /&gt;
In the conversion funnel, we were aggregating over people.&lt;br /&gt;
And finally, in the Pachinko machine, we were aggregating over balls.&lt;/p&gt;

&lt;p&gt;This is a common property across all our funnel examples. Funnels only make sense with aggregates.&lt;/p&gt;

&lt;p&gt;So, whenever you’re dealing with a large number of similar things, think if you can represent them as a funnel.&lt;/p&gt;

&lt;p&gt;However, this isn’t enough. To create a funnel, you need two ingredients: the things to aggregate over, and the specific step definitions. For a good data summarization, you should choose good representative steps.&lt;/p&gt;

&lt;p&gt;Take the Pachinko machine example: I glossed over how to choose steps. The specific pegs the ball touches aka the route of the ball makes sense because it’s actionable. It allows you to tweak the specific pegs to increase or decrease the conversion rate.&lt;/p&gt;

&lt;p&gt;A bad step representation would be: ball inserted -&amp;gt; ball shot -&amp;gt; ball touches some peg -&amp;gt; ball enters a hole. This doesn’t help us manage the winning rate. However, it’s pretty useful for the person debugging the pachinko machine. If the conversion rate is not 100%, there’s something going wrong with the machine that engineers need to fix.&lt;/p&gt;

&lt;p&gt;Choosing the wrong step definitions makes a funnel useless.&lt;/p&gt;

&lt;h2 id=&quot;funnels-and-gears&quot;&gt;Funnels and Gears&lt;/h2&gt;

&lt;p&gt;We got our first principle by checking what’s common between the examples. Now, let’s reverse the question. How are the funnel examples different?&lt;/p&gt;

&lt;p&gt;With crude oil, we know the precise chemistry. So, we know beforehand precisely how much jet fuel we’ll get out of 1 barrel of crude oil. The funnel just confirms what we already expect.&lt;/p&gt;

&lt;p&gt;With the ETL pipeline, we aren’t sure beforehand, but, given a chance to inspect the raw data, we could figure out how the end funnel would look like. We can do this because we know the code: we know exactly what the funnel is going to do, and we also have a full view into the raw data.&lt;/p&gt;

&lt;p&gt;Things get a bit trickier with the Pachinko machine and conversion funnels.&lt;/p&gt;

&lt;p&gt;For conversion funnels, we need to know human psychology. But sadly, Psychology hasn’t reached a state where we can predict people’s behaviour precisely. Thus we have no way to know beforehand how the funnel would look like. We can make guesses, we can look at other people funnels and see how close we are to them, but given none of this extra information, it’s very hard to figure out.&lt;/p&gt;

&lt;p&gt;It’s similar with the Pachinko machine. This time, the culprit is Physics. We know the laws of Physics, but we can’t simulate all the forces because we don’t have enough computation capacity.&lt;/p&gt;

&lt;p&gt;If you can predict how things would look like in the funnel, the usefulness of actually building and testing the funnel decreases. This distinction is called &lt;a href=&quot;https://www.lesswrong.com/posts/gvK5QWRLk3H8iqcNy/gears-vs-behavior&quot;&gt;Gears vs Behaviour&lt;/a&gt;. The gears are the inner workings of something, while the behaviour is what you see.&lt;/p&gt;

&lt;p&gt;Funnels are great at exposing behaviour where we don’t understand the gears.&lt;/p&gt;

&lt;p&gt;Thus, the second core idea of funnels is how much of the gears do you know? When you don’t know the gears, using funnels helps make sense of things. It helps you figure out the behaviour, which is a crucial hint to figuring out the gears.&lt;/p&gt;

&lt;!-- Imagine it&apos;s the 1800s. You&apos;ve discovered crude oil, but you don&apos;t know the precise chemistry behind it. Had people known about funnels during this time, you&apos;d be able to figure out &amp; understand what&apos;s happening with crude oil better. (rephrase, expand) --&gt;

&lt;h2 id=&quot;funnels-as-measurement&quot;&gt;Funnels as Measurement&lt;/h2&gt;

&lt;p&gt;Sometimes, figuring out the gears is too hard. You want to stick in behaviour land, but you still want to measure what’s happening, and improve on it.&lt;/p&gt;

&lt;p&gt;This is why funnels are so popular in sales and marketing: You’re not trying to push the field of Psychology forward, but using measurement from funnels to drive your product forward.&lt;/p&gt;

&lt;p&gt;Once you build a conversion funnel, you see people coming in, you know the conversion rate.&lt;/p&gt;

&lt;p&gt;Then, you test changes to your flow, like the colour of buttons, size of the call-to-action, etc. and your funnel conversion rate changes. This is a proxy for human behaviour: you’re figuring out how people react to your product, and using this measurement as a guide for what changes to keep, and which ones to discard.&lt;/p&gt;

&lt;p&gt;It’s the same with the Pachinko funnel. Every time you make changes to the board, you do a test with a thousand or so balls, and see how many win the jackpot. (This is your funnel conversion rate). If the number of jackpots gets too high, you know you’re in trouble - you’ll be giving away lots of money.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Funnels are a way to make sense of aggregates: lots of things you don’t understand, each unique in their own way, but put together in a funnel, and they click into place.&lt;/p&gt;

&lt;p&gt;They’re lots more actionable than plain goals. A part of this comes from their granularity: while a goal may be “get 100 users to pay”, a funnel would be “get 10,000 users to visit the website, 2,000 to sign up, and 100 to pay”. It gives you more levers to pull, by seeing exactly in which step are people faltering.&lt;/p&gt;

&lt;p&gt;Remember though, that these steps need to make sense. Even if granularity is better, you can’t go build a 100 step funnel, since this overfits to the specific experience of a small number of people: you want steps that are important to your goal. And if you have a 100 important steps, you have 0 important steps.&lt;/p&gt;

&lt;p&gt;Finally, funnels help measure quality. If you get a shit-tonne more petroleum and not enough jet fuel, it probably means you’re dealing with a lower quality, artificially processed crude oil. We can reach this conclusion because we understand chemistry. However, if we didn’t, it would boil down to (pun intended) testing different kinds of crude oil to decide which one to use - the one which gives us the highest conversion rate.&lt;/p&gt;

&lt;p&gt;For another example, consider an expensive marketing experiment. If 100 people paid before the experiment, and a 100 people paid up after, just looking at this number doesn’t tell you much. But consider the funnel:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;Before&lt;/th&gt;
      &lt;th&gt;After&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Visit&lt;/td&gt;
      &lt;td&gt;10,000&lt;/td&gt;
      &lt;td&gt;100,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Sign Up&lt;/td&gt;
      &lt;td&gt;2,000&lt;/td&gt;
      &lt;td&gt;4,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Pay&lt;/td&gt;
      &lt;td&gt;100&lt;/td&gt;
      &lt;td&gt;100&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Your conversion rate dropped from 1% to 0.01%. That’s a lot more poor quality people entering your funnel. That’s definitely an experiment targeted at the wrong audience.&lt;/p&gt;

&lt;p&gt;To summarize: See aggregations, think funnels. See goals, think funnels. See something hard to measure, think funnels.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/divider.jpg&quot; alt=&quot;&quot; style=&quot;width: 250px&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;Thanks to Julian and Nishit for reading drafts of this.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;Funnel from sales lingo. The naming is unfortunate, since a funnel is also an instrument in Chemistry. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;It’s the value itself! &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>Neil Kakkar</name></author><summary type="html">The traditional funnel is a measurement device for, say, number of people converting in your product.</summary></entry><entry><title type="html">Building your own Hey email Feed in Gmail</title><link href="https://neilkakkar.com/gmail-hey-feed.html" rel="alternate" type="text/html" title="Building your own Hey email Feed in Gmail" /><published>2021-07-17T00:00:00+00:00</published><updated>2021-07-17T00:00:00+00:00</updated><id>https://neilkakkar.com/gmail%20hey%20feed</id><content type="html" xml:base="https://neilkakkar.com/gmail-hey-feed.html">&lt;p&gt;If there’s one epic idea &lt;a href=&quot;https://www.hey.com/index.html&quot;&gt;Hey.com&lt;/a&gt; has, it’s the Newsletter Feed. Like a social feed, you can scroll through these newsletters, read what’s interesting, skip what’s not. It feels natural. It feels unlike regular email.&lt;/p&gt;

&lt;p&gt;However, it gets annoying quickly. Some newsletters are very long, the UI is janky, some feed items are randomly skipped, and it’s a pain scrolling through the long emails I’ve lost interest in.&lt;/p&gt;

&lt;p&gt;The second epic-in-principle idea is screening people in so random emails don’t reach me. Some companies are pesky and don’t respect unsubscribe buttons. This can get annoying quickly: I have to go the extra step of approving people who I know will email me, or for OTPs coming to my mail.&lt;/p&gt;

&lt;p&gt;While I appreciate Hey’s way of thinking about email, their too constrained approach doesn’t suit me. So, I replicated their principles in Gmail, in a way that really works for me.&lt;/p&gt;

&lt;script async=&quot;&quot; type=&quot;text/javascript&quot; src=&quot;//cdn.carbonads.com/carbon.js?serve=CE7D427L&amp;amp;placement=neilkakkarcom&quot; id=&quot;_carbonads_js&quot;&gt;&lt;/script&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;It took me less than 5 minutes to set up (and more than a few hours to figure out). But, I’ve been using this system for the past 6 months, and recently cancelled my Hey subscription: this approach works so much better for me, and builds into tools I already use.&lt;/p&gt;

&lt;p&gt;This is the way: Multiple Inboxes in Gmail.&lt;/p&gt;

&lt;p&gt;Here’s how it looks:&lt;/p&gt;

&lt;div style=&quot;position: relative; padding-bottom: 62.5%; height: 0;&quot;&gt;&lt;iframe src=&quot;https://www.loom.com/embed/dff783114dbc4db68e91a952d1a0e6ef&quot; frameborder=&quot;0&quot; webkitallowfullscreen=&quot;&quot; mozallowfullscreen=&quot;&quot; allowfullscreen=&quot;&quot; style=&quot;position: absolute; top: 0; left: 0; width: 100%; height: 100%;&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;
This setup works really well for work, too. I have an extra inbox for email from GitHub, which has almost all of my work related emails, neatly separated from the rest, and I can go through them just like this newsletter feed.&lt;/p&gt;

&lt;p&gt;The sections below show you how to set it up from scratch.&lt;/p&gt;

&lt;h2 id=&quot;how-to-create-a-newsletter-feed-in-gmail&quot;&gt;How to create a Newsletter Feed in Gmail&lt;/h2&gt;

&lt;p&gt;We’ll work with labels, automatic filters and multiple inboxes. Together, these make Gmail very powerful.&lt;/p&gt;

&lt;!-- For the videos, I created a demo account to show you where to go. --&gt;

&lt;p&gt;Step 1 -&amp;gt; Enable Multiple Inboxes. Go to Settings -&amp;gt; See All Settings -&amp;gt; Inbox (3rd tab) -&amp;gt; Inbox Type: Multiple Inboxes.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/gmail-hey/multiple_inboxes.png&quot; alt=&quot;&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;Fill in Section 3 with “is:feed” and then click Save Changes at the bottom.&lt;/p&gt;

&lt;p&gt;Step 2 -&amp;gt; Set up automatic filtering for a new label called “feed”.&lt;/p&gt;

&lt;p&gt;The video below shows how to do this in depth. You can alse see the &lt;a href=&quot;https://support.google.com/mail/answer/6579?hl=en-GB&quot;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;videoWrapper&quot;&gt;
    &lt;iframe width=&quot;760&quot; height=&quot;415&quot; style=&quot;display: block; margin: auto; margin-bottom: 1.6381578947368418%&quot; src=&quot;https://www.youtube.com/embed/qEZB_CK3znk&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;
    &lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;Note that we’ll be choosing what to include in the feed, and also what not to. A good idea is to skip the Inbox as well, so these don’t show up as duplicates. If you want to see everything together in one inbox, you can go to “All Mail”.&lt;/p&gt;

&lt;p&gt;Here’s how my filter looks:&lt;/p&gt;

&lt;p&gt;{from:@substack.com from:@rootsofprogress.org} OR (from:notify@sethgodin.com) OR (“Farnam Street” OR “Idea Muse”)&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;{}&lt;/code&gt; is short form for multiple ORs. Anything without an OR is an AND. I like separating them out to distinguish domains, specific addresses and string searches.&lt;/p&gt;

&lt;p&gt;The first part filters in all emails from these domains - &lt;code class=&quot;highlighter-rouge&quot;&gt;{from:@substack.com from:@rootsofprogress.org}&lt;/code&gt;. The beauty of this is that all substack newsletters automatically go into the feedbox, instead of manually choosing them one by one.&lt;/p&gt;

&lt;p&gt;The second part filters in emails by specific sender - &lt;code class=&quot;highlighter-rouge&quot;&gt;(from:notify@sethgodin.com)&lt;/code&gt;. I do this when I don’t want mails that aren’t about blog posts in the feed box. For example, &lt;code class=&quot;highlighter-rouge&quot;&gt;notify@sethgodin.com&lt;/code&gt; sends blog posts, but &lt;code class=&quot;highlighter-rouge&quot;&gt;seth@sethgodin.com&lt;/code&gt; sends other blog updates. I only want one in the feed.&lt;/p&gt;

&lt;p&gt;The third part filters in emails by string matching - &lt;code class=&quot;highlighter-rouge&quot;&gt;(&quot;Farnam Street&quot; OR &quot;Idea Muse&quot;)&lt;/code&gt;. I use this when I’m being lazy, or the newsletter title is catchy, or if I want to see mails that reference these newsletters as well.&lt;/p&gt;

&lt;p&gt;You could also filter via just the subject line, like so: &lt;code class=&quot;highlighter-rouge&quot;&gt;OR subject:&quot;Idea Muse&quot;&lt;/code&gt;. This will only look in the subject, instead of the full email.&lt;/p&gt;

&lt;p&gt;Next, whenever you find something to add or remove from your feed, you can edit this filter, like so:&lt;/p&gt;

&lt;div class=&quot;videoWrapper&quot;&gt;
    &lt;iframe width=&quot;760&quot; height=&quot;415&quot; style=&quot;display: block; margin: auto; margin-bottom: 1.6381578947368418%&quot; src=&quot;https://www.youtube.com/embed/6q4VR77ywuQ&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;
    &lt;/iframe&gt;
&lt;/div&gt;

&lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&gt;
  &lt;p&gt;&lt;i class=&quot;fas fa-info-circle&quot;&gt;&lt;/i&gt;&lt;b&gt;Note:&lt;/b&gt;&lt;br /&gt;
It’s important to re-edit using the “Advanced Search” button, and not the search bar. Otherwise, you lose the filter.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;how-to-scroll-through-a-newsletter-feed-in-gmail&quot;&gt;How to scroll through a newsletter feed in Gmail&lt;/h2&gt;

&lt;p&gt;I do like scrolling through mails together, but I’ve settled on a low-tech, future-proof compromise here.&lt;/p&gt;

&lt;p&gt;I setup keyboard shortcuts so that I can quickly switch to the next mail, so if it’s too long I don’t have to scroll to the end.&lt;/p&gt;

&lt;div class=&quot;videoWrapper&quot;&gt;
    &lt;iframe width=&quot;760&quot; height=&quot;415&quot; style=&quot;display: block; margin: auto; margin-bottom: 1.6381578947368418%&quot; src=&quot;https://www.youtube.com/embed/WJPOsdtfWrk&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;
    &lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;I use my right hand to scroll through the email, and my left hand is on the 1 and 2 keys. Older mail is &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt;. Newer mail is &lt;code class=&quot;highlighter-rouge&quot;&gt;2&lt;/code&gt;.
Whenever I get bored, I press 1, to see the next feed email. This works surprisingly well.&lt;/p&gt;

&lt;p&gt;In normal mode, I keep the feed box collapsed, so I don’t see those mails, and check the feed out in bulk on weekends.&lt;/p&gt;

&lt;p&gt;On my real Gmail account, it looks like this:&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/gmail-hey/feedbox.png&quot; alt=&quot;&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;h2 id=&quot;how-to-set-up-screening-in-gmail&quot;&gt;How to set up screening in Gmail&lt;/h2&gt;

&lt;p&gt;This is an additional Inbox with a screened-in tag. In the end, the Inbox becomes the graveyard, or an opportunity to filter things in and out.&lt;/p&gt;

&lt;p&gt;The filter is what looks different. &lt;a href=&quot;#how-to-create-a-newsletter-feed-in-gmail&quot;&gt;Follow steps 1-2 above&lt;/a&gt;, and then modify the filter like in the video below.&lt;/p&gt;

&lt;p&gt;We don’t want to include things that are labelled as feed already, so: “-label:feed” is the first part, and then, I like to add all gmail.com addresses to the filter - this isn’t usually companies, is majorly friends, which means I don’t face headaches having to screen them in all the time.&lt;/p&gt;

&lt;p&gt;The full filter looks like this:&lt;/p&gt;

&lt;p&gt;{from:@hackernewsletter.com from:@gmail.com from:@neilkakkar.com} -from:hestonb3@gmail.com -label:feed&lt;/p&gt;

&lt;p&gt;I add my own email to the not list (hestonb3@gmail.com in this example), so that things I send don’t show up in &lt;code class=&quot;highlighter-rouge&quot;&gt;screened-in&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;videoWrapper&quot;&gt;
    &lt;iframe width=&quot;760&quot; height=&quot;415&quot; style=&quot;display: block; margin: auto; margin-bottom: 1.6381578947368418%&quot; src=&quot;https://www.youtube.com/embed/lRF6hZnvUnE&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;
    &lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;You’ll note how a “spam” mail I sent from my hey address is still in the Inbox (end of video) - I can now decide whether I want to screen it in, or archive it, or let it be. Screened-in is the main inbox now.&lt;/p&gt;

&lt;p&gt;In the end, things look something like:&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/gmail-hey/screened-in.png&quot; alt=&quot;&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;Notice the timestamps - Inbox fills up much quicker than screened-in, full of mostly crap you don’t want to waste time on.&lt;/p&gt;

&lt;p&gt;So, whenever something new comes into the Inbox, and I notice I don’t want it there, I either add it to the screened-in filter or the feed filter.&lt;/p&gt;

&lt;p&gt;I generally don’t look at the Inbox, as all important mails are in the screened-in box and the feed is in the feed box.&lt;/p&gt;

&lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&gt;
  &lt;p&gt;&lt;i class=&quot;fas fa-info-circle&quot;&gt;&lt;/i&gt;&lt;b&gt;Note:&lt;/b&gt;&lt;br /&gt;
Filters are applied in the order they’re written in the Settings, so if your filter includes a label tag, make sure the filter for that label is above this filter!&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Another important advantage here is that your Inbox still gets emails you otherwise have not labelled. So, there’s no risk of missing out on important emails from new people.&lt;/p&gt;

&lt;p&gt;Oh, and the Hey paper trail is mostly not useful. Not worth setting it up.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/divider.jpg&quot; alt=&quot;&quot; style=&quot;width: 250px&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;I can set all this up once, and then mostly forget about it. It makes things powerful, better integrated, more customizable, and finally, £99 cheaper.&lt;/p&gt;

&lt;p&gt;If you know of any other ways to do this that work better, &lt;a href=&quot;mailto:neil@neilkakkar.com?subject=A Better Way to Do Hey&quot;&gt;let me know&lt;/a&gt;!&lt;/p&gt;</content><author><name>Neil Kakkar</name></author><summary type="html">If there’s one epic idea Hey.com has, it’s the Newsletter Feed. Like a social feed, you can scroll through these newsletters, read what’s interesting, skip what’s not. It feels natural. It feels unlike regular email.</summary></entry><entry><title type="html">Debugging Interesting Bugs at PostHog</title><link href="https://neilkakkar.com/debugging-open-source.html" rel="alternate" type="text/html" title="Debugging Interesting Bugs at PostHog" /><published>2021-05-29T00:00:00+00:00</published><updated>2021-05-29T00:00:00+00:00</updated><id>https://neilkakkar.com/debugging%20open%20source</id><content type="html" xml:base="https://neilkakkar.com/debugging-open-source.html">&lt;p&gt;This week, I ran into an &lt;a href=&quot;https://github.com/PostHog/posthog-js/issues/183&quot;&gt;interesting bug&lt;/a&gt;. We would send session recording data to our client’s servers instead of our own. This is pretty annoying - we’re losing data, polluting our client’s logs, oh and it happens often, but we can’t reproduce it.&lt;/p&gt;

&lt;p&gt;I think it’ll be fun to walk through how I solved this bug. And since &lt;a href=&quot;https://posthog.com/&quot;&gt;PostHog&lt;/a&gt; is open source, we can walk through the actual code instead of a make-believe alternative.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;!-- We&apos;re going to keep going up and down the ladder of abstraction: going into the nitty gritty, then zooming out, figuring out general principles to follow, and then zooming back in to resolve and reflect. --&gt;

&lt;!-- I&apos;m using a specific example to lead through things, and I hope this makes the principles stick better - and displays the mistakes I made in all their glory. --&gt;

&lt;p&gt;There’s 4 things I hope you (and I) get out of this post:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Figure out different approaches to solving this bug.&lt;/li&gt;
  &lt;li&gt;Draw general principles that would be useful elsewhere.&lt;/li&gt;
  &lt;li&gt;Figure out how to apply these principles more effectively in the future.&lt;/li&gt;
  &lt;li&gt;Have fun solving this bug&lt;/li&gt;
&lt;/ol&gt;

&lt;!-- Reflect on how I went about things? --&gt;

&lt;script async=&quot;&quot; type=&quot;text/javascript&quot; src=&quot;//cdn.carbonads.com/carbon.js?serve=CE7D427L&amp;amp;placement=neilkakkarcom&quot; id=&quot;_carbonads_js&quot;&gt;&lt;/script&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;nav&gt;

  &lt;h4&gt;Table of Contents&lt;/h4&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#context&quot; id=&quot;markdown-toc-context&quot;&gt;Context&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#principle-1-reproduce-the-bug&quot; id=&quot;markdown-toc-principle-1-reproduce-the-bug&quot;&gt;Principle 1: Reproduce the bug&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#posthog-reproducing-the-bug&quot; id=&quot;markdown-toc-posthog-reproducing-the-bug&quot;&gt;PostHog: Reproducing the bug&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#posthog-testing-what-went-wrong&quot; id=&quot;markdown-toc-posthog-testing-what-went-wrong&quot;&gt;PostHog: Testing what went wrong&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#principle-2-make-and-test-hypotheses&quot; id=&quot;markdown-toc-principle-2-make-and-test-hypotheses&quot;&gt;Principle 2: Make and test hypotheses&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#posthog-doing-things-better&quot; id=&quot;markdown-toc-posthog-doing-things-better&quot;&gt;PostHog: Doing things better&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#principle-3-better-tools-make-things-easier&quot; id=&quot;markdown-toc-principle-3-better-tools-make-things-easier&quot;&gt;Principle 3: Better tools make things easier&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#principle-4-write-tests-that-fail&quot; id=&quot;markdown-toc-principle-4-write-tests-that-fail&quot;&gt;Principle 4: Write Tests that fail&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#end-notes&quot; id=&quot;markdown-toc-end-notes&quot;&gt;End Notes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;/nav&gt;

&lt;!-- works only once jQuery is loaded --&gt;
&lt;script&gt;

let banner_width = 55;

$(document).ready(() =&gt; {
    $(&apos;a[href^=&quot;#&quot;]&apos;).on(&apos;click&apos;, function(event) {
        var hash = &apos;#&apos; + $(this).attr(&apos;href&apos;).split(&apos;#&apos;)[1]
        var element = $(hash)
        if (element.length) {
            event.preventDefault();
            history.pushState(hash, undefined, hash)
            $(&apos;html, body&apos;).animate({scrollTop: element.offset().top - banner_width}, 500)
        }
    });

    window.addEventListener(&apos;popstate&apos;, function(e) {
        if(e.state &amp;&amp; e.state.startsWith(&apos;#&apos;) &amp;&amp; $(e.state).length){
        $(&apos;html, body&apos;).animate({scrollTop: $(e.state).offset().top - banner_width}, 500)
        }
    });

    $(&apos;html, body&apos;).on(&quot;scroll mousedown wheel DOMMouseScroll mousewheel keyup touchmove&quot;, function(){
        $(&apos;html, body&apos;).stop();
    });

  	if (location.hash) {
    	// $(&apos;html,body&apos;).animate({scrollTop: $(location.hash).offset().top - banner_width}, 500)
  	};
});
&lt;/script&gt;

&lt;h2 id=&quot;context&quot;&gt;Context&lt;/h2&gt;

&lt;p&gt;PostHog has a main app: a Django backend and React frontend, which manages analytics. This is where you’d come to see analytics for your website. Then, there’s the integration libraries: you’d run these on your website - a snippet - that manages sending requests to PostHog, auto capturing events, doing session recording, etc. etc.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/debug/posthog.jpg&quot; alt=&quot;&quot; /&gt;
    
    
    
        &lt;figcaption&gt;   
            &lt;p&gt;How PostHog looks like on this website&lt;/p&gt;

        &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;We’re dealing with the &lt;a href=&quot;https://github.com/PostHog/posthog-js&quot;&gt;posthog-js&lt;/a&gt; library.  The &lt;a href=&quot;https://github.com/PostHog/posthog-js/blob/master/src/extensions/sessionrecording.js&quot;&gt;session recording extension&lt;/a&gt; in PostHog-js manages session recordings. When things work fine, We capture these recordings and send them to the &lt;code class=&quot;highlighter-rouge&quot;&gt;/s/&lt;/code&gt; endpoint on the main app.&lt;/p&gt;

&lt;p&gt;When things don’t work fine, we send random requests to &lt;code class=&quot;highlighter-rouge&quot;&gt;/sessionRecording&lt;/code&gt;, instead of where PostHog is being hosted.&lt;/p&gt;

&lt;p&gt;What makes this bug tricky is that it’s hard to reproduce. We know it happens for &lt;a href=&quot;https://github.com/PostHog/posthog/issues/3986&quot;&gt;a LOT&lt;/a&gt; &lt;a href=&quot;https://github.com/PostHog/posthog-js/issues/183&quot;&gt;of people&lt;/a&gt;, but we don’t know exactly when or how. And this is indeed Principle #1 for any bug you’re trying to solve.&lt;/p&gt;

&lt;h2 id=&quot;principle-1-reproduce-the-bug&quot;&gt;Principle 1: Reproduce the bug&lt;/h2&gt;

&lt;p&gt;For the easy bugs, a good test is all it takes to reproduce. This makes things so much easier, since if you write the test first, see it fail, and then write the code to fix it, bam, you’re done!&lt;/p&gt;

&lt;p&gt;The hard bugs require you to set up the entire flow, imitate the user and see where they run into issues. As you can imagine, the &lt;code class=&quot;highlighter-rouge&quot;&gt;/sessionRecording&lt;/code&gt; bug is of the second kind, which is what makes it blogpost worthy.&lt;/p&gt;

&lt;h3 id=&quot;posthog-reproducing-the-bug&quot;&gt;PostHog: Reproducing the bug&lt;/h3&gt;

&lt;p&gt;Well, before I can reproduce anything, it’s a good idea to setup my development environment, so everything is local, and I can tweak things the way I want to. This bit is interesting in itself, since setting things up is non-trivial: you create a sample website to use as a playground, add the snippet that talks to PostHog, and run PostHog locally to ingest those events. There’s a bit more to it, but it’s probably not worth getting into it right now.&lt;/p&gt;

&lt;p&gt;Since we’re dealing with a local web server, I set up &lt;a href=&quot;https://www.npmjs.com/package/http-server&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;http-server&lt;/code&gt;&lt;/a&gt; - which would tell me all about the requests coming in. Now, I &lt;em&gt;somehow&lt;/em&gt; need to make the &lt;code class=&quot;highlighter-rouge&quot;&gt;/sessionRecording&lt;/code&gt; request come in. Enable session recording on the server side, enable it on the client side, some fumbling around, refresh, and sure enough, I got a few &lt;code class=&quot;highlighter-rouge&quot;&gt;/sessionRecording&lt;/code&gt; requests.&lt;/p&gt;

&lt;p&gt;I realised I’d get a new &lt;code class=&quot;highlighter-rouge&quot;&gt;/sessionRecording&lt;/code&gt; request 50% of the time after a refresh. I considered this a good enough rate to dive deeper.&lt;/p&gt;

&lt;p&gt;Next step is to figure out which one of the 30,000 lines of code is the problem. I’ve found that the best way to figure anything out is to make hypotheses, test if they’re right or wrong, and repeat. Every wrong answer gives you more insight into what to try next. If this sounds familiar, yes, it’s exactly the same as &lt;a href=&quot;/Bayes-Theorem-Framework-for-Critical-Thinking.html&quot;&gt;the framework for critical thinking&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;posthog-testing-what-went-wrong&quot;&gt;PostHog: Testing what went wrong&lt;/h3&gt;

&lt;p&gt;The first thing I’d do is grep for &lt;code class=&quot;highlighter-rouge&quot;&gt;sessionRecording&lt;/code&gt;. Find where the damn URL is being generated, and we’re done! In fact, I did this even before trying to reproduce the bug - sometimes, that’s all it takes.&lt;/p&gt;

&lt;p&gt;Well, this bug was blogpost worthy, so it obviously wasn’t that simple. You can search for &lt;a href=&quot;https://github.com/PostHog/posthog-js/search?q=sessionRecording&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;sessionRecording&lt;/code&gt; in the repo&lt;/a&gt; . The only place worth looking is in &lt;a href=&quot;https://github.com/PostHog/posthog-js/blob/635889447f213103595fdb0d1f5687dabb2e195c/src/extensions/sessionrecording.js&quot;&gt;sessionrecording.js&lt;/a&gt;, which doesn’t make sense. Maybe the string is being generated somehow? or a third party library is doing it?&lt;/p&gt;

&lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&gt;
  &lt;p&gt;&lt;i class=&quot;fas fa-info-circle&quot;&gt;&lt;/i&gt;&lt;b&gt;Note:&lt;/b&gt;&lt;br /&gt;
If you’re following along, use &lt;a href=&quot;https://github.com/PostHog/posthog-js/tree/18696ad6b4cab475e9be334d73090ab16cb8e54d&quot;&gt;this non-master branch&lt;/a&gt;, since I’ve already merged the fix into master&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;My focus areas now would be every part of &lt;code class=&quot;highlighter-rouge&quot;&gt;posthog-js&lt;/code&gt; that makes an HTTP request somewhere. If requests are being made from the client, the DevTools must have captured this request too!&lt;/p&gt;

&lt;p&gt;That’s where things start to get even weirder. I could see the requests coming to my &lt;code class=&quot;highlighter-rouge&quot;&gt;http-server&lt;/code&gt;:&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/debug/http-server.jpg&quot; alt=&quot;&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;but not on the DevTools network tab:&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/debug/devtools.jpg&quot; alt=&quot;&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;Can you guess why?&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;/sessionRecording&lt;/code&gt; request would come right after I hit refresh.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; …Maybe I was wrong about this, and it was right before refresh. Since I wasn’t preserving the logs, those requests would vanish.&lt;/p&gt;

&lt;p&gt;Sure enough, preserving the logs showed the request:&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/debug/devtools-right.jpg&quot; alt=&quot;&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;The first thing that caught my eye was how this was different from a regular XHR request. A &lt;code class=&quot;highlighter-rouge&quot;&gt;ping&lt;/code&gt; ? What, how is this happening? A few google searches led me to the &lt;code class=&quot;highlighter-rouge&quot;&gt;ping&lt;/code&gt; attribute of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a&quot;&gt;the anchor tag&lt;/a&gt;. Is there code turning HTTP requests to &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tags? Not sure. I don’t see the DOM changing. This track didn’t lead me anywhere. Maybe other things can generate a &lt;code class=&quot;highlighter-rouge&quot;&gt;ping&lt;/code&gt; type request, too?&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;So, I decided to move on. Here’s what we know so far:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;These requests are made by the client.&lt;/li&gt;
  &lt;li&gt;They’re made &lt;strong&gt;right before&lt;/strong&gt; refreshing.&lt;/li&gt;
  &lt;li&gt;They’re different from usual XHR requests.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It smells of a cleanup script running, but the codebase is huge: hard to traceback where this is coming from yet. So, it’s time to get dirty with the code to figure out the traceback.&lt;/p&gt;

&lt;p&gt;Well.. let’s console.log this shit up. (Yeah, I know now this is &lt;del&gt;a bit&lt;/del&gt; very n00by). I’m looking for the &lt;code class=&quot;highlighter-rouge&quot;&gt;url&lt;/code&gt; parameter, specifically where it equals &lt;code class=&quot;highlighter-rouge&quot;&gt;sessionRecording&lt;/code&gt;. It took a while, working my way up to the right piece of code.&lt;/p&gt;

&lt;p&gt;We’ll skip all that n00b logging and move on to the final piece. The culprit is somewhere in here:&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/debug/unload.jpg&quot; alt=&quot;&quot; /&gt;
    
    
    
        &lt;figcaption&gt;   
            &lt;p&gt;Full file &lt;a href=&quot;https://github.com/PostHog/posthog-js/blob/18696ad6b4cab475e9be334d73090ab16cb8e54d/src/request-queue.js&quot;&gt;on Github&lt;/a&gt;&lt;/p&gt;

        &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;Can you guess what’s wrong?&lt;/p&gt;

&lt;p&gt;I’ll help with some &lt;code class=&quot;highlighter-rouge&quot;&gt;console.logs&lt;/code&gt;: &lt;code class=&quot;highlighter-rouge&quot;&gt;this.formatQueue()&lt;/code&gt; returns something like:&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/debug/consolelog.jpg&quot; alt=&quot;&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;Rebuilding that myself:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{ &apos;sessionRecording&apos;: { &apos;/s/&apos;, binary_data, {transport: &apos;sendbeacon&apos;}},
  &apos;/e/&apos;: {&apos;/e/&apos;, binary_data, {transport: &apos;sendbeacon&apos;}},
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The key is &lt;code class=&quot;highlighter-rouge&quot;&gt;sessionRecording&lt;/code&gt; because of &lt;a href=&quot;https://github.com/PostHog/posthog-js/blob/18696ad6b4cab475e9be334d73090ab16cb8e54d/src/request-queue.js#L100&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;formatQueue()&lt;/code&gt;&lt;/a&gt; - the batchKey takes precedence over the url.&lt;/p&gt;

&lt;p&gt;This should make things clear. The bug was because of &lt;code class=&quot;highlighter-rouge&quot;&gt;unload()&lt;/code&gt;. Here’s the &lt;a href=&quot;https://github.com/PostHog/posthog-js/pull/234&quot;&gt;final PR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I think there’s lots to generalise from this experience, so let’s ground out the remaining principles.&lt;/p&gt;

&lt;h2 id=&quot;principle-2-make-and-test-hypotheses&quot;&gt;Principle 2: Make and test hypotheses&lt;/h2&gt;

&lt;p&gt;What distinguishes fast debuggers is how quickly they cut down the search space by making good hypotheses.&lt;/p&gt;

&lt;p&gt;The 30,000 lines of code are your search space. Through testing hypotheses, you discard lines of code that can’t possibly be the problem.&lt;/p&gt;

&lt;p&gt;Grepping is useful because if it works, it narrows the search space down to a few lines of code.&lt;/p&gt;

&lt;p&gt;Making and testing hypotheses is a bit trickier than it sounds. For example, consider when the &lt;code class=&quot;highlighter-rouge&quot;&gt;/sessionRecording&lt;/code&gt; requests happen. I’d refresh my local website, switch screens to the server logs, and sure enough, I’d find the &lt;code class=&quot;highlighter-rouge&quot;&gt;/sessionRecording&lt;/code&gt; POST request there (sometimes). This information wasn’t enough to figure out whether the request happened right before, or right after the request.&lt;/p&gt;

&lt;p&gt;Computers are much faster than humans, so our usual intuition about timings don’t work here. I inferred the test results incorrectly. I should’ve &lt;a href=&quot;/Bayes-Theorem-Framework-for-Critical-Thinking.html#seek-disconfirming-evidence&quot;&gt;seeked disconfirming evidence&lt;/a&gt;, like every hypothesis demands.&lt;/p&gt;

&lt;!-- (This is a bit trickier than it sounds, since your test may not answer your hypothesis, but an [easier question](link), which you assume is the answer for the hypothesis, which makes you incorrectly discard search space - explain w example). --&gt;

&lt;h3 id=&quot;posthog-doing-things-better&quot;&gt;PostHog: Doing things better&lt;/h3&gt;

&lt;p&gt;Coming back to our bug, I’m still not happy. I was pretty slow. This was n00b level debugging - as you can see from the 100s of &lt;code class=&quot;highlighter-rouge&quot;&gt;console.log&lt;/code&gt;s I put in the code. There has to be a better way.&lt;/p&gt;

&lt;p&gt;Making and testing hypotheses is a 2 part problem. The first part is making hypotheses, which depends on your brains and how well &lt;a href=&quot;https://www.youtube.com/watch?v=oUaOucZRlmE&quot;&gt;your tools uncover places to look&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The second part is testing those hypotheses. I was pretty slow here, because I wasn’t relying on the right tools. I figured two optimisations I could make:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Switching my development settings so instead of a minified js code for the client, I either get a source map, or non-minified code.&lt;/li&gt;
  &lt;li&gt;Using breakpoints in Chrome DevTools (it’s surprising how amazing these have gotten)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I didn’t realise until after that both of these were available options. So, I tried debugging the code again, using chrome dev tools.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/debug/debugger-tools.jpg&quot; alt=&quot;&quot; /&gt;
    
    
    
        &lt;figcaption&gt;   
            &lt;p&gt;Chrome DevTools&lt;/p&gt;

        &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;This is pretty cool: it shows me what &lt;code class=&quot;highlighter-rouge&quot;&gt;console.log&lt;/code&gt; would have, but better: I don’t have to log everything myself.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/debug/debugger-zoom.jpg&quot; alt=&quot;&quot; /&gt;
    
    
    
        &lt;figcaption&gt;   
            &lt;p&gt;zoomed in&lt;/p&gt;

        &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;… However, it didn’t help me debug this specific problem better: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event&quot;&gt;unload events&lt;/a&gt; are unreliable, and Chrome does a poor job of breaking on &lt;code class=&quot;highlighter-rouge&quot;&gt;unload&lt;/code&gt;. Stack Overflow had &lt;a href=&quot;https://stackoverflow.com/questions/39065562/breakpoint-right-before-page-refresh#49011846&quot;&gt;some answers&lt;/a&gt; but none worked as well as I’d expect. Disappointing, but I imagine this would work well for something happening not at the end of a page load.&lt;/p&gt;

&lt;!-- I also found out about the [node debugger](https://nodejs.org/en/docs/guides/debugging-getting-started/) - which I might try out for a Node related bug. --&gt;

&lt;h2 id=&quot;principle-3-better-tools-make-things-easier&quot;&gt;Principle 3: Better tools make things easier&lt;/h2&gt;

&lt;p&gt;Imagine people in a jungle shouting “it’s here!” to indentify target animals, instead of using a radar. It’s harder, unreliable, and doesn’t always give the full picture. That’s what &lt;code class=&quot;highlighter-rouge&quot;&gt;console.logging&lt;/code&gt; is, compared to a debugger.&lt;/p&gt;

&lt;p&gt;But hey, sometimes, radars don’t work either, and you have to revert to more primitive options, like in the example above. If there’s one thing, it’s that &lt;code class=&quot;highlighter-rouge&quot;&gt;console.log&lt;/code&gt; is resilient.&lt;/p&gt;

&lt;p&gt;I feel on aggregate, I’d end up doing things faster with a &lt;del&gt;radar&lt;/del&gt; debugger: it’s the difference between seeing only what I focus on - since I’d log things I want to focus on - versus seeing everything, and choosing what’s interesting and what’s not.&lt;/p&gt;

&lt;h2 id=&quot;principle-4-write-tests-that-fail&quot;&gt;Principle 4: Write Tests that fail&lt;/h2&gt;

&lt;p&gt;So, we’ve figured out the bug. Do we just fix what was wrong and call it a day? Nope, never!&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/debug/never.gif&quot; alt=&quot;&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;Once you think you’ve figured things out, you need to test your solution works, just like your hypotheses! And since we’re seeking disconfirming evidence, it’s crucial that your test fails before the fix, and works after the fix.&lt;/p&gt;

&lt;p&gt;This doesn’t mean &lt;a href=&quot;https://en.wikipedia.org/wiki/Test-driven_development&quot;&gt;TDD&lt;/a&gt;, or writing your test before your fix, but it does mean running your tests before and after the fix. If you noticed the &lt;a href=&quot;https://github.com/PostHog/posthog-js/pull/234&quot;&gt;bug fix PR above&lt;/a&gt;, you’ll see how I wrote the test afterwards. What the PR doesn’t show is my workflow that gave me confidence in the fix and the test.&lt;/p&gt;

&lt;p&gt;My workflow goes something like: I write the fix, commit to a git branch, then I write a test and see it pass. Now, before committing the test, I &lt;code class=&quot;highlighter-rouge&quot;&gt;git checkout HEAD~3&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;git checkout master&lt;/code&gt; (i.e. go back 3 commits: or however many it took to fix the bug), then run the test, and see it fail.&lt;/p&gt;

&lt;p&gt;If the test on master passes, either you’re testing the wrong thing, your test doesn’t work, or your fix didn’t actually fix the problem, since the test passed both before and after.&lt;/p&gt;

&lt;p&gt;It’s this “watching things fail –&amp;gt; writing code to fix –&amp;gt; watching things pass” loop that grants confidence in your code.&lt;/p&gt;

&lt;h2 id=&quot;end-notes&quot;&gt;End Notes&lt;/h2&gt;

&lt;p&gt;One mystery remains: where does the &lt;code class=&quot;highlighter-rouge&quot;&gt;ping&lt;/code&gt; request type come from? The hint was in &lt;code class=&quot;highlighter-rouge&quot;&gt;{transport: &apos;sendbeacon&apos;}&lt;/code&gt; above. This &lt;a href=&quot;https://w3c.github.io/beacon/#sendbeacon-method&quot;&gt;link&lt;/a&gt; is very interesting!&lt;/p&gt;

&lt;p&gt;And, that’s all for the session recording mysteries! Caught your fancy? &lt;a href=&quot;https://posthog.com/careers&quot;&gt;PostHog is hiring&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/divider.jpg&quot; alt=&quot;&quot; style=&quot;width: 250px&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;p&gt;What did you think of this post? Let me &lt;a href=&quot;https://twitter.com/neilkakkar/status/1398563283376955394&quot;&gt;know on Twitter&lt;/a&gt;. I’m super excited about all the experiments I can run, working with an open source company!&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/divider.jpg&quot; alt=&quot;&quot; style=&quot;width: 250px&quot; /&gt;
    
    
    
&lt;/figure&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;I’m probably more excited about the “actual code” bit than the “bug” bit. I’m looking forward to all the experiments I can run working at an open company. This is #2. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;This was me incorrectly inferring the test data. Since I’ll click refresh, then switch to the server logs, it made it seem like it happened after refresh, when in fact, it was &lt;em&gt;right before&lt;/em&gt;. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;Yes, they can! See the &lt;a href=&quot;#end-notes&quot;&gt;end notes&lt;/a&gt; for more information. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>Neil Kakkar</name></author><summary type="html">This week, I ran into an interesting bug. We would send session recording data to our client’s servers instead of our own. This is pretty annoying - we’re losing data, polluting our client’s logs, oh and it happens often, but we can’t reproduce it.</summary></entry><entry><title type="html">Why Is Naming Things Hard?</title><link href="https://neilkakkar.com/why-is-naming-things-hard.html" rel="alternate" type="text/html" title="Why Is Naming Things Hard?" /><published>2021-02-16T00:00:00+00:00</published><updated>2021-02-16T00:00:00+00:00</updated><id>https://neilkakkar.com/why%20is%20naming%20things%20hard</id><content type="html" xml:base="https://neilkakkar.com/why-is-naming-things-hard.html">&lt;p&gt;An interesting shift happens once you realise you’re writing code for humans to read, and not just for machines to execute.&lt;/p&gt;

&lt;p&gt;One big change is that writing clearly takes priority over correct code.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Reading code involves building up a mental model of what each thing should do, and how these things interact with each other. The best code, however, short-circuits reading everything. Instead of reading the implementation, you use variable and function names, comments, and other clues to figure things out.&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;This is chunking. You combine things together, and remember them together. For example, a Chess Opening, like the Queen’s Gambit, is a chunk. So is a &lt;code class=&quot;highlighter-rouge&quot;&gt;forEach&lt;/code&gt; loop: just by the name, you can figure out that it’s going to do something to every element of an array.&lt;/p&gt;

&lt;p&gt;Once you’ve built a chunk for a &lt;code class=&quot;highlighter-rouge&quot;&gt;forEach&lt;/code&gt; loop, you don’t have to read the implementation of &lt;code class=&quot;highlighter-rouge&quot;&gt;forEach&lt;/code&gt; to understand what it will do. This is very powerful, since it allows you to come back to the code, without having to read the implementation of everything again.&lt;/p&gt;

&lt;p&gt;Thus, a good name is a chunk that encodes important information about what the thing does.&lt;/p&gt;

&lt;p&gt;We’re compressing information into the name. This is almost always a lossy compression. Further, the more lines to compress, the more losses you incur, since you want your names to be no longer than 80 characters.&lt;/p&gt;

&lt;p&gt;The goal of good naming is to minimize this loss.&lt;/p&gt;

&lt;p&gt;And naming things is hard because of this compression. English isn’t precise like C++, so compressing from a precise to an ambiguous language increases losses. Another big reason is that choosing the most important things to include is hard. You’ll notice that most disagreements about naming fall on these two axes: either the name isn’t just right for what you want to do (does it &lt;code class=&quot;highlighter-rouge&quot;&gt;assign&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;setup&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;link&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;mangle&lt;/code&gt;?). Or the name doesn’t capture important parts of the functionality.&lt;/p&gt;

&lt;p&gt;I think this is the generating function for most best practices I’ve heard of, and thus lots more valuable than one specific guidance.&lt;/p&gt;

&lt;h2 id=&quot;generating-best-practices&quot;&gt;Generating best practices&lt;/h2&gt;

&lt;!-- Keep names short: big chunks are difficult chunks --&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Make functions do one thing&lt;/p&gt;

    &lt;p&gt;When a function does multiple things, the name needs to encode lots more information, like &lt;code class=&quot;highlighter-rouge&quot;&gt;link_user_to_trial_and_enable_downloads_and_set_charge_date()&lt;/code&gt;. That’s a long name, and a sign of losing too much information: what semantics should the name expose about the charge date, and enabling downloads?&lt;/p&gt;

    &lt;p&gt;These side effects are hard to encode for in the name.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Keep functions small&lt;/p&gt;

    &lt;p&gt;Long functions, like functions doing lots of things, need to encode lots of information into the name. And since the name length is bounded, you lose a lot more information in compressing this long name, which leads to poorer names.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Avoid meaningless names&lt;/p&gt;

    &lt;p&gt;Since we’re writing code to be read by humans, names that don’t compress any information don’t help us build chunks. This makes it harder to read, since you have to go down one layer of abstraction. You need to parse the implementation to understand what that piece of code does.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Avoid too abstract names&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;do_things()&lt;/code&gt; is a suitable name for any function, but the compression is too lossy to be useful - you lose almost all information, and need to parse the body to figure out what it does. Not great. It’s almost a meaningless name.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Be consistent&lt;/p&gt;

    &lt;p&gt;When you build similar chunks throughout your code base, it becomes easier to go up another layer of abstraction, by chunking the chunks. For example, continuing the trial management system example, you’ll have 3 things in place:&lt;/p&gt;

    &lt;ol&gt;
      &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;link_user_to_trial_agreement_given_email()&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;enable_research_downloads_for_user()&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;set_charge_date_for_user()&lt;/code&gt;&lt;/li&gt;
    &lt;/ol&gt;

    &lt;p&gt;&lt;br /&gt;
 Since they’re operating in the same domain, you can go up one level to &lt;code class=&quot;highlighter-rouge&quot;&gt;setup_user_for_trial()&lt;/code&gt; that does all three.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;disrupting-best-practices&quot;&gt;Disrupting best practices&lt;/h2&gt;

&lt;p&gt;The generator of best practices allows you to reason about cases where best practices aren’t the best way forward.&lt;/p&gt;

&lt;p&gt;For example, consider being consistent, and naming variables using camelCase. Your entire codebase uses camelCase. You’re writing a new function that’s very non-standard. It does arcane stuff that doesn’t fit into your existing model. You need it, because legacy integration/business requirements/etc. In effect, you don’t want anyone to gloss over it when they read it.&lt;/p&gt;

&lt;p&gt;So, do you stay consistent, and keep it in camelCase? Or, do you switch to snake_case?&lt;/p&gt;

&lt;p&gt;The goal is to keep things clear. I would switch to snake_case to show how it’s different from the rest, and have some comments explaining the arcane things it does.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h2 id=&quot;precision-with-words&quot;&gt;Precision with words&lt;/h2&gt;

&lt;p&gt;Spoken languages are very imprecise. There’s lots of ambiguity, double meanings, and sentences that don’t mean anything.&lt;/p&gt;

&lt;p&gt;Code, however, needs to be precise. Like we saw above, this is a source of trouble, since we’re trying to compress a precise language into an imprecise one, which makes searching for the right English words harder.&lt;/p&gt;

&lt;p&gt;These ideas apply to naming things outside of code, too! When you have a name for something, it suddenly becomes legible.&lt;/p&gt;

&lt;p&gt;Consider: You see some people around you who test if they can do something once, and drop it if they fail. Some others keep trying until they learn how to do it. The first group prizes its intelligence, while the second group prizes its hard work. Then, when you hear about the &lt;a href=&quot;/story-of-stories.html#the-story-of-growth-vs-fixed-mindset&quot;&gt;Fixed vs Growth mindset&lt;/a&gt;, things immediately click.&lt;/p&gt;

&lt;p&gt;Choosing the right name here is hard too. Every best practice that lost its nuance over time can be attributed to a name that didn’t encode the necessary information. People passed these ideas on without explaining the nuances.&lt;/p&gt;

&lt;p&gt;Or, consider equating two names. “Abortion is murder” - equating two names that aren’t the same creates an interesting dynamic between emotions and the truth. Each name has different connotations, and on combining them together, you’re passing on the connotations of one onto the other. This isn’t necessarily a good thing. You have to be very careful of the equality operator in an imprecise language.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Optimise for clear, easy to understand code. The cost of reading it is higher than the cost of executing it.&lt;/p&gt;

&lt;p&gt;To do this, leverage chunking: write names that are meaningful, and encode as much information as possible into the name.&lt;/p&gt;

&lt;p&gt;However, writing meaningful names is a lossy compression. Every naming maxim follows from minimising this loss.&lt;/p&gt;

&lt;p&gt;And finally, the same idea applies to naming things in the real world. Except, it’s even harder, since it’s encoding from an imprecise to another imprecise language. Code, atleast, was unambiguous in its execution.&lt;/p&gt;

&lt;figure&gt;
    
    &lt;img class=&quot;docimage&quot; src=&quot;/assets/images/divider.jpg&quot; alt=&quot;&quot; style=&quot;width: 250px&quot; /&gt;
    
    
    
&lt;/figure&gt;

&lt;blockquote&gt;
  &lt;p&gt;Thanks to Soumya for reading drafts of this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Have feedback? You can &lt;a href=&quot;https://twitter.com/neilkakkar/status/1361611070331305986&quot;&gt;reply to this post on Twitter&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;You might contest this, that correctness comes first, but if it’s clear to read, PR reviews will catch correctness bugs quickly. Or, if no-one catches mistakes, and then things break, it’s easier for your colleagues to fix things if they can read your code clearly. &lt;br /&gt; Footnote to footnote: This is an instance of &lt;a href=&quot;/taking-ideas-seriously.html&quot;&gt;taking ideas seriously&lt;/a&gt;. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;Sometimes, poor naming gets in the way, generating an incorrect model of how things work. Here, you have no choice but to read the implementation. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;Ideally, you’d fix things properly so there isn’t a need for something like this. But, the default is to have things like these littered over a huge codebase. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>Neil Kakkar</name></author><summary type="html">An interesting shift happens once you realise you’re writing code for humans to read, and not just for machines to execute.</summary></entry></feed>