samwho.devPersonal website of Sam Rose.Zola2025-12-23T00:00:00+00:00/rss.xml20252025-12-23T00:00:00+00:002025-12-23T00:00:00+00:00Sam Rose/2025/<style>
.highlight {
width: 100%;
color: var(--text-color);
font-family: var(--heading-font);
text-align: center;
font-style: italic;
font-size: 1.5rem;
padding-top: 2rem;
padding-bottom: 3rem;
text-decoration-line: underline;
text-decoration-style: wavy;
text-decoration-skip-ink: none;
text-underline-offset: 0.3em;
}
.pink {
text-decoration-color: var(--palette-pink);
}
.red {
text-decoration-color: var(--palette-red);
}
.blue {
text-decoration-color: var(--palette-blue);
}
.green {
text-decoration-color: var(--palette-green);
}
.orange {
text-decoration-color: var(--palette-orange);
}
.dark-blue {
text-decoration-color: var(--palette-dark-blue);
}
</style>
<p>This last year marked a fundamental shift in my professional life, and I'm going
to reflect on the journey that got me to it.</p>
<p>In 2023 I wrote <a href="/load-balancing">Load Balancing</a>. I had been writing blog posts
since 2011, but Load Balancing was like nothing else I'd experienced. After a
<a rel="external" href="https://x.com/JoshWComeau/status/1648312926120337408">retweet from Josh Comeau</a>
it spread far and wide, and my phone didn't stop vibrating for 2 whole days. I
was instantly hooked and needed to experience that again.</p>
<p>So I wrote <a href="/memory-allocation">Memory Allocation</a>, which had a similar
response. I started to think I was onto something. I'd found something that was
fun, challenging, not being done by many others, and lots of people liked. It
felt like the pool of potential topics was infinite. I could keep going for
years.</p>
<p>Then another thought entered my mind...</p>
<div class="highlight pink">
Aren't you sick of chasing other peoples' dreams?
</div>
<p>I was. I really, really was. I sit in the privileged position of deeply enjoying
the work that I do. Programming computers is a joy and I love it as much today
as I did when I discovered PHP for the first time in 2007. But what to do about it.</p>
<p>When I was in high school, one parents' evening a teacher told my parents that I
would make a good teacher one day. I was 15 or 16 and thought absolutely nothing
of it, I just liked helping people. Then in my first job I got similar feedback
one performance review. I was good at explaining things to people. I thought
nothing of it, I just liked helping people. But...</p>
<div class="highlight red">
What if I actually <b>am</b> a good teacher?
</div>
<p>So I made the decision to drop down to 4 days a week in my day job, create a
limited company, and start testing this theory. Initially I spent the time
writing more blog posts, to see if I could keep the momentum going. I even
landed a sponsor, the wonderful <a rel="external" href="https://ittybit.com/">ittybit</a>, which was a
huge boost to my confidence.</p>
<p>Then I landed a client that I did some tutorial writing work for. It paid well,
enough to make me start thinking there might be a future where I could do this
full time. But... it wasn't what I wanted to be doing. The work that paid well
wasn't the work I'd set out to find. I'm sure many people before me have
independently discovered this.</p>
<div class="highlight blue">
I knew quickly that freelancing wasn't for me.
</div>
<p>Around April/May time I was diagnosed with tennis elbow after struggling with
arm pain for some months prior. I'm not new to hand and arm pain, I've had bouts
of pain in the past and made many ergonomic adjustments to my setup. But every
time it reminds me how careful I have to be with my body. I need to be able to
code for another few decades yet, and doing that in constant pain is not
appealing. I scaled back the freelancing to give myself more space to recover.</p>
<p>I still suffer with tennis elbow. It comes and goes, and when it's bad I tend to
switch away from using a mouse to controlling my computer with my voice using
<a rel="external" href="https://talonvoice.com/">Talon</a>. It's a remarkable piece of software that I'm
grateful exists. Even if you don't need it today, you might tomorrow. Consider
supporting their <a rel="external" href="https://www.patreon.com/join/lunixbochs">Patreon</a>.</p>
<div class="highlight green">
And then in July, ngrok reached out to me.
</div>
<p>They had actually been in contact earlier in the year, around March, about some
freelance work but the conversation petered out. I was a bit upset that it had,
because the work had sound fun and much more in line with what I was looking
for. In July, though, the message was different. The title of the email I got on
July 10th, 2025, was: "What if you worked at ngrok?"</p>
<p>We talked a lot. I was cautious. Freelancing had taught me what I didn't want,
and I was scared of pivoting my career into a role I didn't like. I wanted to
continue this blend of education and art I'd come to love, and be known for.</p>
<p>As it turns out, ngrok wanted that too. They had a vision that closely matched
my own, and they were willing to invest in me to see if it could play out in a
commercial setting.</p>
<p>We came to an agreement at the end of July and everything was signed and
returned in August. I was to start as a Developer Educator in September.</p>
<div class="highlight orange">
This was my first job in marketing.
</div>
<p>Every job prior to this one has been in the engineering department of companies.
I had no experience working in marketing, and the first weeks were rough. Lots
of acronyms I didn't know, KPIs that were unfamiliar to me, and tools I'd never
used before.</p>
<p>On top of that, ngrok's blog used a CMS system that wasn't going to work for the
type of posts I, and they, wanted to write. I had to be that guy and suggest
changing everything before I could get to work, but I was given a lot of agency
to make it happen.</p>
<p>After a lot of feet-finding and a short but intense project to shape the blog
into something I could work with, I got started on my first big project on
November 11th. I was worried about how long it had taken me to get to the point
where I was starting the work I'd been hired to do, but everyone was extremely
supportive.</p>
<div class="highlight dark-blue">
Beyond my wildest dreams.
</div>
<p><a rel="external" href="https://ngrok.com/blog/prompt-caching">Prompt Caching</a> was published on
December 16th and initially sank to the bottom of Hacker News and my socials
without much fanfare. I was gutted. I knew that not everything takes off first
time, plenty of my past posts haven't. But this felt different somehow. I'd
invested 5-6 weeks of full-time effort, effort ngrok had paid me for, and had
nothing much to show for it. My boss asked me in a 1:1 if I thought publishing
on a company blog is inherently more difficult than publishing on my own blog. I
think that would be a comfortable thing to believe, but I don't think it's true.</p>
<p>Then, on December 19th, I woke up to several messages from friends to say that
the post was on the front page of Hacker News. I checked and sure enough, there
it was. Around 10th place, but on the front page nonetheless. From there it took
on a life of its own. It never really got much higher than 10th on HN, but it
spread across Twitter something fierce. I got DMs from friends saying they
couldn't reload their feed without seeing it. It was surreal.</p>
<p>Simon Willison said it was <a rel="external" href="https://simonwillison.net/2025/Dec/19/sam-rose-llms/">"one of the clearest and most accessible
introductions to LLM internals I've seen
anywhere."</a> It ended up in
the <a rel="external" href="https://tldr.tech/ai/2025-12-01">tldr ai newsletter</a>. It got tweeted out by
the <a rel="external" href="https://x.com/viditchess/status/2002627862642463174?s=20">former #1 chess player in
India</a>.</p>
<div class="highlight pink">
Maybe I can do this.
</div>
<p>So I sit perched on the edge of one of the best years of my career, and I look
out on everything that might be possible in 2026 with anxious excitement. I
think about all of the people I've had the fortune of meeting through my
writing, the friends I've made and the opportunities that have come from it.</p>
<p>I don't think I'm being dramatic when I say that writing on this blog has
changed my life. Without Load Balancing, there's simply no way the last few
years would have panned out how they did.</p>
<p>To everyone who's read my work, shared it, commented on it, or reached out to
me: thank you. You have my deepest gratitude. I hope I get to keep doing this
forever.</p>
Big O2025-08-23T00:00:00+00:002025-08-23T00:00:00+00:00Sam Rose/big-o/This post
includes custom components
that
need to be viewed on my website. Sorry, I know it's not ideal.Reservoir Sampling2025-05-07T00:00:00+00:002025-05-07T00:00:00+00:00Sam Rose/reservoir-sampling/This post
includes custom components
that
need to be viewed on my website. Sorry, I know it's not ideal.Turing Machines2024-12-20T00:00:00+00:002024-12-20T00:00:00+00:00Sam Rose/turing-machines/This post
includes custom components
that
need to be viewed on my website. Sorry, I know it's not ideal.A Commitment to Art and Dogs2024-06-01T00:00:00+00:002024-06-01T00:00:00+00:00Sam Rose/dogs/<style>
.dog-line {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
width: 100%;
height: 10rem;
margin-top: 2rem;
margin-bottom: 2rem;
}
.dog-line img {
flex-grow: 1;
height: auto;
margin: 0;
padding: 0;
object-fit: contain;
}
.dog-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 1rem;
margin-top: 2rem;
margin-bottom: 2rem;
}
</style>
<p>Back in <a href="/memory-allocation">Memory Allocation</a>, I introduced Haskie.</p>
<blockquote class="haskie">
<img src="/images/haskie-triumphant-200px.png" alt="A husky puppy looking triumphant" />
<p>
Hello!
</p>
</blockquote>
<p>The idea behind Haskie was to create a character that could ask questions the
reader might have, and to "soften" the posts to make them feel less
intimidating. I got some feedback from people that Haskie was a bit too
childish, and didn't feel like he belonged in posts about serious topics.
This feedback was in the minority, though, and most people liked him. So I kept
him and used him again in <a href="/hashing">Hashing</a>.</p>
<p>Having a proxy to the reader was useful. I could anticipate areas of confusion
and clear them up without creating enormous walls of text. I don't like it
when the entire screen is filled with text, I like to break it up with images
and interactive elements. And now dogs.</p>
<p>Then in <a href="/bloom-filters">Bloom Filters</a>, I found myself needing a
character to represent the "adult in the room." If Haskie was my proxy to the
reader, this new character would serve as a proxy to all of the material I
learned from in the writing of the post. This is Sage.</p>
<blockquote class="haskie">
<img src="/images/sage-happy-200px.png" alt="A husky looking happy" />
<p>
Well met!
</p>
</blockquote>
<p>I liked the idea of having a cast of characters, each with their own personality
and purpose. But I had a few problems.</p>
<h2 id="problems"><a class="anchor" href="#problems">#</a>
Problems</h2>
<p>Both Haskie and Sage, because I have no artistic ability, were generated by AI.
Back when I made them I was making no money from this blog, and I had no idea if
I was going to keep them around. I didn't want to invest money in an idea that
could flop, so I didn't feel bad about using AI to try it out.</p>
<p>Since then, however, I have been paid twice to write posts for companies, and I
know that I'm keeping the dogs. <strong>It wasn't ethical to continue piggybacking on
AI</strong>.</p>
<p>While ethics were the primary motivation, there were some other smaller problems
with the dogs:</p>
<ol>
<li>The visual style of them, while I did like it, never felt like it fit
with the rest of my personal brand.</li>
<li>It was difficult to get AI to generate consistent dogs. You'll notice
differences in coat colouration and features between variants of the same dog.</li>
<li>The AI generated images look bad at small sizes.</li>
</ol>
<p>So I worked with the wonderful <a href="https://www.andycarolan.com/">Andy
Carolan</a> to create a new design for my dogs. A design that would be
consistent, fit with my brand, and look good at any size.</p>
<h2 id="haskie-sage-and-doe"><a class="anchor" href="#haskie-sage-and-doe">#</a>
Haskie, Sage, and Doe</h2>
<div class="dog-line">
<img src="/images/dogs/haskie/default.svg" alt="A husky puppy called Haskie" />
<img src="/images/dogs/sage/default.svg" alt="A husky called Sage" />
<img src="/images/dogs/doe/default.svg" alt="A husky puppy called Doe" />
</div>
<p>The redesigned dogs are consistent, use simple colours and shapes, and use the
SVG file format to look good at any size. Each variant clocks in at around
20kb, which is slightly larger than the small AI-generated images, but I'll be
able to use them at any size.</p>
<p>Together the dogs represent a family unit: Sage as the dad, Haskie as the
youngest child, and Doe as his older sister.</p>
<p>They also come in a variety of poses, so I can use them to represent different
emotions or actions.</p>
<div class="dog-grid">
<img src="/images/dogs/haskie/bored.svg" alt="A husky puppy called Haskie looking bored" />
<img src="/images/dogs/haskie/concerned.svg" alt="A husky puppy called Haskie looking concerned" />
<img src="/images/dogs/haskie/confused.svg" alt="A husky puppy called Haskie looking confused" />
<img src="/images/dogs/haskie/triumphant.svg" alt="A husky puppy called Haskie looking triumphant" />
<img src="/images/dogs/sage/caution2.svg" alt="A husky called Sage looking cautioning" />
<img src="/images/dogs/sage/caution.svg" alt="A husky called Sage looking cautioning" />
<img src="/images/dogs/sage/despair.svg" alt="A husky called Sage looking despaired" />
<img src="/images/dogs/sage/proud.svg" alt="A husky called Sage looking proud" />
<img src="/images/dogs/doe/amazed.svg" alt="A husky puppy called Doe looking amazed" />
<img src="/images/dogs/doe/mischief.svg" alt="A husky puppy called Doe looking mischievous" />
<img src="/images/dogs/doe/protective.svg" alt="A husky puppy called Doe looking protective" />
<img src="/images/dogs/doe/proud.svg" alt="A husky puppy called Doe looking proud" />
</div>
<p>We were careful to make the dogs recognisable apart. They differ in colour, ear
shape, tail shape, and collar tag. Sage and Doe have further distinguishing
features: Sage with his glasses, and Doe with her bandana. Doe's bandana uses
the same colours as the <a
href="https://en.wikipedia.org/wiki/Transgender_flag"> transgender flag</a>,
to show my support for the trans community and as a nod to her identity.</p>
<h2 id="going-forward"><a class="anchor" href="#going-forward">#</a>
Going forward</h2>
<p>I'm so happy with the new dogs, and plan to use them in my posts going forward.
I suspect I will, at some point, replace the dogs in my old posts as well.
I don't plan to add any more characters, and I want to be careful to avoid
overusing them. I don't want them to become a crutch, or to distract from the
content of the posts.</p>
<p>I also haven't forgotten the many people that pointed out to me that you can't
pet the dogs. I'm working on it.</p>
Bloom Filters2024-02-19T00:00:00+00:002024-02-19T00:00:00+00:00Sam Rose/bloom-filters/This post
includes custom components
that
need to be viewed on my website. Sorry, I know it's not ideal.Hashing2023-05-24T00:00:00+00:002023-05-24T00:00:00+00:00Sam Rose/hashing/This post
includes custom components
that
need to be viewed on my website. Sorry, I know it's not ideal.Memory Allocation2023-04-13T00:00:00+00:002023-04-13T00:00:00+00:00Sam Rose/memory-allocation/<script src="/js/gsap/gsap.min.js"></script>
<script src="/js/gsap/PixiPlugin.min.js"></script>
<script type="module" src="/js/memory-allocation.js"></script>
<style>
.memory {
width: 100%;
margin-bottom: 1.5em;
margin-top: 0.5em;
}
input[type=range]:focus {
outline: none;
}
a[simulation] {
cursor: pointer;
}
.size {
color: #0072B2 !important;
font-weight: bold;
}
.free {
color: #009E73 !important;
font-weight: bold;
}
.allocated {
color: #D55E00 !important;
font-weight: bold;
}
.usable-memory {
color: #E69F00 !important;
font-weight: bold;
}
</style>
<p>One thing that all programs on your computer have in common is a need for
memory. Programs need to be loaded from your hard drive into memory before they
can be run. While running, the majority of what programs do is load values from
memory, do some computation on them, and then store the result back in memory.</p>
<p>In this post I'm going to introduce you to the basics of memory allocation.
Allocators exist because it's not enough to have memory available, you need to
use it effectively. We will visually explore how simple allocators work. We'll
see some of the problems that they try to solve, and some of the techniques used
to solve them. At the end of this post, you should know everything you need to
know to write your own allocator.</p>
<h2 id="malloc-and-free"><a class="anchor" href="#malloc-and-free">#</a>
<code>malloc</code> and <code>free</code></h2>
<p>To understand the job of a memory allocator, it's essential to understand how
programs request and return memory. <code>malloc</code> and <code>free</code> are functions that were
first introduced in a recognisable form in UNIX v7 in 1979(!). Let's take a look
at a short C program demonstrating their use.</p>
<blockquote class="haskie">
<img src="/images/haskie-concerned-200px.png" />
<p>
Woah, hold on. I've never written any C code before. Will I still be able
to follow along?
</p>
</blockquote>
<p>If you have beginner-level familiarity with another language, e.g. JavaScript,
Python, or C#, you should have no problem following along. You don't need to
understand every word, as long as you get the overall idea. This is the only
C code in the article, I promise.</p>
<pre class="giallo z-code"><code data-lang="c"><span class="giallo-l"><span class="z-keyword">#include</span><span class="z-string"> <stdlib.h></span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span class="z-storage z-type">int</span><span class="z-entity z-name z-function"> main</span><span>() {</span></span>
<span class="giallo-l"><span class="z-storage z-type"> void</span><span class="z-keyword"> *</span><span>ptr </span><span class="z-keyword">=</span><span class="z-entity z-name z-function"> malloc</span><span>(</span><span class="z-constant z-numeric">4</span><span>);</span></span>
<span class="giallo-l"><span class="z-entity z-name z-function"> free</span><span>(ptr);</span></span>
<span class="giallo-l"><span class="z-keyword"> return</span><span class="z-constant z-numeric"> 0</span><span>;</span></span>
<span class="giallo-l"><span>}</span></span></code></pre>
<p>In the above program we ask for 4 bytes of memory by calling <code>malloc(4)</code>, we
store the value returned in a variable called <code>ptr</code>, then we indicate that we're
done with the memory by calling <code>free(ptr)</code>.</p>
<p>These two functions are how almost all programs manage the memory they use.
Even when you're not writing C, the code that is executing your Java, Python,
Ruby, JavaScript, and so on make use of <code>malloc</code> and <code>free</code>.</p>
<h2 id="what-is-memory"><a class="anchor" href="#what-is-memory">#</a>
What is memory?</h2>
<p>The smallest unit of memory that allocators work with is called a "byte." A byte
can store any number between 0 and 255. You can think of memory as being a long
sequence of bytes. We're going to represent this sequence as a grid of squares,
with each square representing a byte of memory.</p>
<div class="memory" bytes="32" slider=false>
</div>
<p>In the C code from before, <code>malloc(4)</code> allocates 4 bytes of memory. We're going
to represent memory that has been allocated as darker squares.</p>
<div class="memory" bytes="32" slider=false>
<malloc size="4" addr="0x0"></malloc>
</div>
<p>Then <code>free(ptr)</code> tells the allocator we're done with that memory. It is returned
back to the pool of available memory.</p>
<p>Here's what 4 <code>malloc</code> calls followed by 4 <code>free</code> calls looks like. You'll
notice there's now a slider. Dragging the slider to the right advances time
forward, and dragging it left rewinds. You can also click anywhere on the grid
and then use the arrow keys on your keyboard, or you can use the left and right
buttons. The ticks along the slider represent calls to <code>malloc</code> and <code>free</code>.</p>
<div class="memory" bytes="32">
<malloc size="4" addr="0x0"></malloc>
<malloc size="5" addr="0x4"></malloc>
<malloc size="6" addr="0x9"></malloc>
<malloc size="7" addr="0xf"></malloc>
<free addr="0x0"></free>
<free addr="0x4"></free>
<free addr="0x9"></free>
<free addr="0xf"></free>
</div>
<blockquote class="haskie">
<img src="/images/haskie-confused-200px.png">
<p>
Wait a sec... What is <code>malloc</code> actually returning as a value?
What does it mean to "give" memory to a program?
</p>
</blockquote>
<p>What <code>malloc</code> returns is called a "pointer" or a "memory address." It's a number
that identifies a byte in memory. We typically write addresses in a form called
"hexadecimal." Hexadecimal numbers are written with a <code>0x</code> prefix to distinguish
them from decimal numbers. Move the slider below to see a comparison between
decimal numbers and hexadecimal numbers.</p>
<div
id="hexadecimal-demo"
style="display: flex; width: 100%; flex-direction: column;"
>
<div style="width: 100%; font-size: 2.5rem; display: flex; justify-content: center;">
<code id="decimal" style="min-width: 4rem; text-align: right">0</code>
<code style="min-width: 5rem; text-align: center">==</code>
<code id="hexadecimal" style="min-width: 7rem; text-align: left">0x0</code>
</div>
<div>
<input
id="hexadecimal-slider"
type="range"
min="0"
max="32"
step="1"
value="0"
list="hexadecimal-demo-list"
style="width: 100%" />
<datalist id="hexadecimal-demo-list">
<option value=1></option>
<option value=2></option>
<option value=3></option>
<option value=4></option>
<option value=5></option>
<option value=6></option>
<option value=7></option>
<option value=8></option>
<option value=9></option>
<option value=10></option>
<option value=11></option>
<option value=12></option>
<option value=13></option>
<option value=14></option>
<option value=15></option>
<option value=16></option>
<option value=17></option>
<option value=18></option>
<option value=19></option>
<option value=20></option>
<option value=21></option>
<option value=22></option>
<option value=23></option>
<option value=24></option>
<option value=25></option>
<option value=26></option>
<option value=27></option>
<option value=28></option>
<option value=29></option>
<option value=30></option>
<option value=31></option>
<option value=32></option>
</datalist>
</div>
</div>
<p>Here's our familiar grid of memory. Each byte is annotated with its address in
hexadecimal form. For space reasons, I've omitted the <code>0x</code> prefix.</p>
<div class="memory" bytes=32 slider=false>
<annotate type="text" addr=0x0 text=0></annotate>
<annotate type="text" addr=0x1 text=1></annotate>
<annotate type="text" addr=0x2 text=2></annotate>
<annotate type="text" addr=0x3 text=3></annotate>
<annotate type="text" addr=0x4 text=4></annotate>
<annotate type="text" addr=0x5 text=5></annotate>
<annotate type="text" addr=0x6 text=6></annotate>
<annotate type="text" addr=0x7 text=7></annotate>
<annotate type="text" addr=0x8 text=8></annotate>
<annotate type="text" addr=0x9 text=9></annotate>
<annotate type="text" addr=0xA text=A></annotate>
<annotate type="text" addr=0xB text=B></annotate>
<annotate type="text" addr=0xC text=C></annotate>
<annotate type="text" addr=0xD text=D></annotate>
<annotate type="text" addr=0xE text=E></annotate>
<annotate type="text" addr=0xF text=F></annotate>
<annotate type="text" addr=0x10 text=10></annotate>
<annotate type="text" addr=0x11 text=11></annotate>
<annotate type="text" addr=0x12 text=12></annotate>
<annotate type="text" addr=0x13 text=13></annotate>
<annotate type="text" addr=0x14 text=14></annotate>
<annotate type="text" addr=0x15 text=15></annotate>
<annotate type="text" addr=0x16 text=16></annotate>
<annotate type="text" addr=0x17 text=17></annotate>
<annotate type="text" addr=0x18 text=18></annotate>
<annotate type="text" addr=0x19 text=19></annotate>
<annotate type="text" addr=0x1A text=1A></annotate>
<annotate type="text" addr=0x1B text=1B></annotate>
<annotate type="text" addr=0x1C text=1C></annotate>
<annotate type="text" addr=0x1D text=1D></annotate>
<annotate type="text" addr=0x1E text=1E></annotate>
<annotate type="text" addr=0x1F text=1F></annotate>
</div>
<p>The examples we use in this article pretend that your computer only has a very
small amount of memory, but in real life you have billions of bytes to work
with. Real addresses are much larger than what we're using here, but the idea is
exactly the same. Memory addresses are numbers that refer to a specific byte in
memory.</p>
<h2 id="the-simplest-malloc"><a class="anchor" href="#the-simplest-malloc">#</a>
The simplest <code>malloc</code></h2>
<p>The "hello world" of <code>malloc</code> implementations would hand out blocks of memory by
keeping track of where the previous block ended and starting the next block
right after. Below we represent where the next block should start with a grey
square.</p>
<div class="memory" bytes="32">
<allocator path="/js/allocators/stack.js"></allocator>
<malloc size="4" addr="0x0"></malloc>
<malloc size="5" addr="0x4"></malloc>
<malloc size="6" addr="0x9"></malloc>
<malloc size="7" addr="0xf"></malloc>
</div>
<p>You'll notice no memory is <code>free</code>d. If we're only keeping track of where the
next block should start, and we don't know where previous blocks start or end,
<code>free</code> doesn't have enough information to do anything. So it doesn't. This is
called a "memory leak" because, once allocated, the memory can never be used
again.</p>
<p>Believe it or not, this isn't a completely useless implementation. For programs
that use a known amount of memory, this can be a very efficient strategy. It's
extremely fast and extremely simple. As a general-purpose memory allocator,
though, we can't get away with having no <code>free</code> implementation.</p>
<h2 id="the-simplest-general-purpose-malloc"><a class="anchor" href="#the-simplest-general-purpose-malloc">#</a>
The simplest general-purpose <code>malloc</code></h2>
<p>In order to <code>free</code> memory, we need to keep better track of memory. We can do
this by saving the address and size of all allocations, and the address and size
of blocks of free memory. We'll call these an "allocation list" and a "free
list" respectively.</p>
<div class="memory" bytes="32" slider=false>
<allocator path="/js/allocators/freelist.js">
<options coalesce=false></options>
</allocator>
</div>
<p>We're representing free list entries as 2 grey squares linked together with a
line. You can imagine this entry being represented in code as <code>address=0</code> and
<code>size=32</code>. When our program starts, all of memory is marked as free. When
<code>malloc</code> is called, we loop through our free list until we find a block large
enough to accommodate it. When we find one, we save the address and size of the
allocation in our allocation list, and shrink the free list entry accordingly.</p>
<div class="memory" bytes="32" slider=false>
<allocator path="/js/allocators/freelist.js">
<options coalesce=false></options>
</allocator>
<malloc id=1 size=4></malloc>
</div>
<blockquote class="haskie">
<img src="/images/haskie-confused-200px.png">
<p>
Where do we save allocations and free list entries? Aren't we pretending our
computer only has 32 bytes of memory?
</p>
</blockquote>
<p>You caught me. One of the benefits of being a memory allocator is that you're in
charge of memory. You could store your allocation/free list in a reserved area
that's just for you. Or you could store it inline, in a few bytes immediately
preceding each allocation. For now, assume we have reserved some unseen memory
for ourselves and we're using it to store our allocation and free lists.</p>
<p>So what about <code>free</code>? Because we've saved the address and size of the allocation
in our allocation list, we can search that list and move the allocation back in
to the free list. Without the size information, we wouldn't be able to do this.</p>
<div class="memory" bytes="32">
<allocator path="/js/allocators/freelist.js">
<options coalesce=false></options>
</allocator>
<malloc id=1 size=4></malloc>
<free id=1></free>
</div>
<p>Our free list now has 2 entries. This might look harmless, but actually
represents a significant problem. Let's see that problem in action.</p>
<div class="memory" bytes="32">
<allocator path="/js/allocators/freelist.js">
<options coalesce=false></options>
</allocator>
<malloc id=1 size=4></malloc>
<malloc id=2 size=4></malloc>
<malloc id=3 size=4></malloc>
<malloc id=4 size=4></malloc>
<malloc id=5 size=4></malloc>
<malloc id=6 size=4></malloc>
<malloc id=7 size=4></malloc>
<malloc id=8 size=4></malloc>
<free id=1></free>
<free id=2></free>
<free id=3></free>
<free id=4></free>
<free id=5></free>
<free id=6></free>
<free id=7></free>
<free id=8></free>
</div>
<p>We allocated 8 blocks of memory, each 4 bytes in size. Then we <code>free</code>d them all,
resulting in 8 free list entries. The problem we have now is that if we tried
to do a <code>malloc(8)</code>, there are no items in our free list that can hold 8 bytes
and the <code>malloc(8)</code> will fail.</p>
<p>To solve this, we need to do a bit more work. When we <code>free</code> memory, we should
make sure that if the block we return to the free list is next to any other
free blocks, we combine them together. This is called "coalescing."</p>
<div class="memory" bytes="32">
<allocator path="/js/allocators/freelist.js">
<options coalesce=true>
</allocator>
<malloc id=1 size=4></malloc>
<malloc id=2 size=4></malloc>
<malloc id=3 size=4></malloc>
<malloc id=4 size=4></malloc>
<malloc id=5 size=4></malloc>
<malloc id=6 size=4></malloc>
<malloc id=7 size=4></malloc>
<malloc id=8 size=4></malloc>
<free id=1></free>
<free id=2></free>
<free id=3></free>
<free id=4></free>
<free id=5></free>
<free id=6></free>
<free id=7></free>
<free id=8></free>
</div>
<p>Much better.</p>
<h2 id="fragmentation"><a class="anchor" href="#fragmentation">#</a>
Fragmentation</h2>
<p>A perfectly coalesced free list doesn't solve all of our problems. The following
example shows a longer sequence of allocations. Have a look at the state memory
is in at the end.</p>
<div class="memory" bytes="32">
<allocator path="/js/allocators/freelist.js">
<options coalesce=true>
</allocator>
<malloc id=1 size=1></malloc>
<malloc id=2 size=2></malloc>
<free id=1></free>
<malloc id=3 size=2></malloc>
<malloc id=4 size=3></malloc>
<free id=3></free>
<malloc id=5 size=3></malloc>
<malloc id=6 size=4></malloc>
<free id=5></free>
<malloc id=7 size=4></malloc>
<malloc id=8 size=5></malloc>
<free id=7></free>
<free id=2></free>
<free id=4></free>
<malloc id=9 size=4></malloc>
<malloc id=10 size=4></malloc>
<malloc id=11 size=4></malloc>
<malloc id=12 size=5></malloc>
</div>
<p>We end this sequence with 6 of our 32 bytes free, but they're split into 2
blocks of 3 bytes. If we had to service a <code>malloc(6)</code>, while we have enough free
memory in theory, we wouldn't be able to. This is called "fragmentation."</p>
<blockquote class="haskie">
<img src="/images/haskie-confused-200px.png" />
<p>
Couldn't we rearrange the memory to get a block of 6 contiguous bytes? Some
sort of defragmentation process?
</p>
</blockquote>
<p>Sadly not. Remember earlier we talked about how the return value of <code>malloc</code> is
the address of a byte in memory? Moving allocations won't change the pointers we
have already returned from <code>malloc</code>. We would change the value those pointers
are pointed at, effectively breaking them. This is one of the downsides of the
<code>malloc</code>/<code>free</code> API.</p>
<p>If we can't move allocations after creating them, we need to be more careful
about where we put them to begin with.</p>
<p>One way to combat fragmentation is, confusingly, to overallocate. If we always
allocate a minimum of 4 bytes, even when the request is for 1 byte, watch what
happens. This is the exact same sequence of allocations as above.</p>
<div class="memory" bytes="32">
<allocator path="/js/allocators/freelist.js">
<options coalesce=true minsize=4>
</allocator>
<malloc id=1 size=1></malloc>
<malloc id=2 size=2></malloc>
<free id=1></free>
<malloc id=3 size=2></malloc>
<malloc id=4 size=3></malloc>
<free id=3></free>
<malloc id=5 size=3></malloc>
<malloc id=6 size=4></malloc>
<free id=5></free>
<malloc id=7 size=4></malloc>
<malloc id=8 size=5></malloc>
<free id=7></free>
<free id=2></free>
<free id=4></free>
<malloc id=9 size=4></malloc>
<malloc id=10 size=4></malloc>
<malloc id=11 size=4></malloc>
<malloc id=12 size=5></malloc>
</div>
<p>Now we can service a <code>malloc(6)</code>. It's worth keeping in mind that this is just
one example. Programs will call <code>malloc</code> and <code>free</code> in very different patterns
depending on what they do, which makes it challenging to design an allocator
that always performs well.</p>
<blockquote class="haskie">
<img src="/images/haskie-confused-200px.png" />
<p>
After the first <code>malloc</code>, the start of the free list seems to fall
out of sync with allocated memory. Is that a bug in the visualisation?
</p>
</blockquote>
<p>No, that's a side-effect of overallocating. The visualisation shows "true"
memory use, whereas the free list is updated from the allocator's perspective.
So when the first <code>malloc</code> happens, 1 byte of memory is allocated but the free
list entry is moved forward 4 bytes. We trade some wasted space in return for
less fragmentation.</p>
<p>It's worth noting that this unused space that results from overallocation is
another form of fragmentation. It's memory that cannot be used until the
allocation that created it is freed. As a result, we wouldn't want to go too
wild with overallocation. If our program only ever allocated 1 byte at a time,
for example, we'd be wasting 75% of all memory.</p>
<p>Another way to combat fragmentation is to segment memory into a space for small
allocations and a space for big ones. In this next visualisation we start with
two free lists. The lighter grey one is for allocations 3 bytes or smaller,
and the darker grey one is for allocations 4 bytes or larger. Again, this is
the exact same sequence of allocations as before.</p>
<div id="segmented-1" class="memory" bytes=32>
<allocator path="/js/allocators/segmented-freelist.js">
<options
coalesce=true
smallfreelistthreshold=3
smallfreelistsize=6>
</options>
</allocator>
<malloc id=1 size=1></malloc>
<malloc id=2 size=2></malloc>
<free id=1></free>
<malloc id=3 size=2></malloc>
<malloc id=4 size=3></malloc>
<free id=3></free>
<malloc id=5 size=3></malloc>
<malloc id=6 size=4></malloc>
<free id=5></free>
<malloc id=7 size=4></malloc>
<malloc id=8 size=5></malloc>
<free id=7></free>
<free id=2></free>
<free id=4></free>
<malloc id=9 size=4></malloc>
<malloc id=10 size=4></malloc>
<malloc id=11 size=4></malloc>
<malloc id=12 size=5></malloc>
</div>
<p>Nice! This also reduces fragmentation. If we're strictly only allowing
allocations of 3 bytes or less in the first segment, though, then we can't
service that <code>malloc(6)</code>. The trade-off here is that reserving a segment of
memory for smaller allocations gives you less memory to work with for bigger
ones.</p>
<blockquote class="haskie">
<img src="/images/haskie-triumphant-200px.png" />
<p>
Hey, <a simulation="segmented-1" position=4>the first allocation in the dark
grey free list</a> is 3 bytes! You said this was for allocations 4 bytes and
up. What gives?
</p>
</blockquote>
<p>Got me again. This implementation I've written will put small allocations in the
dark grey space when the light grey space is full. It will overallocate when it
does this, otherwise we'd end up with avoidable fragmentation in the dark grey
space thanks to small allocations.</p>
<p>Allocators that split memory up based on the size of allocation are called
"slab allocators." In practice they have many more size classes than the 2 in
our example.</p>
<h2 id="a-quick-malloc-puzzle"><a class="anchor" href="#a-quick-malloc-puzzle">#</a>
A quick <code>malloc</code> puzzle</h2>
<p>What happens if you <code>malloc(0)</code>? Have a think about this before playing with
the slider below.</p>
<div class="memory" bytes=32>
<allocator path="/js/allocators/freelist.js">
<options coalesce=true minsize=4></options>
</allocator>
<malloc id=1 size=0></malloc>
<malloc id=2 size=0></malloc>
<malloc id=3 size=0></malloc>
<malloc id=4 size=0></malloc>
<malloc id=5 size=0></malloc>
<malloc id=6 size=0></malloc>
<malloc id=7 size=0></malloc>
<malloc id=8 size=0></malloc>
</div>
<p>This is using our free list implementation that mandates a minimum size of 4
bytes for allocations. All memory gets allocated, but none is actually used.
Do you think this is correct behaviour?</p>
<p>It turns out that what happens when you <code>malloc(0)</code> differs between
implementations. Some of them behave as above, allocating space they probably
didn't have to. Others will return what's called a "null pointer", a special
pointer that will crash your program if you try to read or write the memory it
points to. Others pick one specific location in memory and return that same
location for all calls to <code>malloc(0)</code>, regardless how many times it is called.</p>
<p>Moral of the story? Don't <code>malloc(0)</code>.</p>
<h2 id="inline-bookkeeping"><a class="anchor" href="#inline-bookkeeping">#</a>
Inline bookkeeping</h2>
<p>Remember earlier on when you asked about where allocation list and free list
information gets stored, and I gave an unsatisfying answer about how it's
stored in some other area of memory we've reserved for ourselves?</p>
<blockquote class="haskie">
<img src="/images/haskie-concerned-200px.png" />
<p>
Yes...
</p>
</blockquote>
<p>This isn't the only way to do it. Lots of allocators store information right
next to the blocks of memory they relate to. Have a look at this.</p>
<div class="memory" bytes=32 slider=false>
<allocator path="/js/allocators/inline.js">
</allocator>
</div>
<p>What we have here is memory with no allocations, but free list information
stored inline in that memory. Each block of memory, free or used, gets 3
additional bytes of bookkeeping information. If <code>address</code> is the address of the
first byte of the allocation, here's the layout of a block:</p>
<ol>
<li><code>address + 0</code> is the <span class="size">size</span> of the block</li>
<li><code>address + 1</code> is whether the block is <span class="free">free (1)</span> or <span class="allocated">used (2)</span></li>
<li><code>address + 2</code> is where the <span class="usable-memory">usable memory</span> starts</li>
<li><code>address + 2 + size</code> -- the <span class="size">size</span> of the block again</li>
</ol>
<p>So in this above example, the byte at <code>0x0</code> is storing the value 29. This means
it's a block containing 29 bytes of memory. The value 1 at <code>0x1</code> indicates that
the block is free memory.</p>
<blockquote class="haskie">
<img src="/images/haskie-concerned-200px.png" />
<p>
We store the <span class="size">size</span> twice? Isn't that wasteful?
</p>
</blockquote>
<p>It seems wasteful at first, but it is necessary if we want to do any form of
coalescing. Let's take a look at an example.</p>
<div class="memory" bytes=32 slider=false>
<allocator path="/js/allocators/inline.js">
</allocator>
<malloc id=1 size=4></malloc>
</div>
<p>Here we've allocated 4 bytes of memory. To do this, our <code>malloc</code> implementation
starts at the beginning of memory and checks to see if the block there is used.
It knows that at <code>address + 1</code> it will find either a 1 or a 2. If it finds a
1, it can check the value at <code>address</code> for how big the block is. If it is big
enough, it can allocate into it. If it's not big enough, it knows it can add
the value it finds in <code>address</code> to <code>address</code> to get to the start of the next
block of memory.</p>
<p>This has resulted in the creation of a used block (notice the 2 stored in the
2nd byte), and it has pushed start of the free block forward by 7 bytes. Let's
do the same again and allocate another 4 bytes.</p>
<div class="memory" bytes=32 slider=false>
<allocator path="/js/allocators/inline.js">
</allocator>
<malloc id=1 size=4></malloc>
<malloc id=2 size=4></malloc>
</div>
<p>Next, let's <code>free</code> our first <code>malloc(4)</code>. The implementation of <code>free</code> is where
storing information inline starts to shine. In our previous allocators, we had
to search the allocation list to know the size of the block being <code>free</code>d. Now
we know we'll find it at <code>address</code>. What's better than that is that for this
<code>free</code>, we don't even need to know how big the allocation is. We can just set
<code>address + 1</code> to 1!</p>
<div class="memory" bytes=32 slider=false>
<allocator path="/js/allocators/inline.js">
</allocator>
<malloc id=1 size=4></malloc>
<malloc id=2 size=4></malloc>
<free id=1></free>
</div>
<p>How great is that? Simple, fast.</p>
<p>What if we wanted to free the 2nd block of used memory? We know that we want to
coalesce to avoid fragmentation, but how do we do that? This is where the
seemingly wasteful bookkeeping comes into play.</p>
<p>When we coalesce, we check to see the state of the blocks immediately before and
immediately after the block we're <code>free</code>ing. We know that we can get to the next
block by adding the value at <code>address</code> to <code>address</code>, but how do we get to the
previous block? We take the value at <code>address - 1</code> and <em>subtract</em> that from
<code>address</code>. Without this duplicated size information at the end of the block, it
would be impossible to find the previous block and impossible to coalesce
properly.</p>
<div class="memory" bytes=32>
<allocator path="/js/allocators/inline.js">
</allocator>
<malloc id=1 size=4></malloc>
<malloc id=2 size=4></malloc>
<free id=1></free>
<free id=2></free>
</div>
<p>Allocators that store bookkeeping information like this alongside allocations
are called "boundary tag allocators."</p>
<blockquote class="haskie">
<img src="/images/haskie-concerned-200px.png" />
<p>
What's stopping a program from modifying the bookkeeping information? Wouldn't
that completely break memory?
</p>
</blockquote>
<p>Surprisingly, nothing truly prevents this. We rely heavily, as an industry, on
the correctness of code. You might have heard of "buffer overrun" or "use after
free" bugs before. These are when a program modifies memory past the end of an
allocated block, or accidentally uses a block of memory after <code>free</code>ing it.
These are indeed catastrophic. They can result in your program immediately
crashing, they can result in your program crashing in several minutes, hours, or
days time. They can even result in hackers using the bug to gain access to
systems they shouldn't have access to.</p>
<p>We're seeing a rise in popularity of "memory safe" languages, for example Rust.
These languages invest a lot in making sure it's not possible to make these
types of mistake in the first place. Exactly how they do that is outside of
the scope of this article, but if this interests you I highly recommend giving
Rust a try.</p>
<p>You might have also realised that calling <code>free</code> on a pointer that's in the
middle of a block of memory could also have disastrous consequences. Depending
on what values are in memory, the allocator could be tricked into thinking it's
<code>free</code>ing something but what it's really doing is modifying memory it shouldn't
be.</p>
<p>To get around this, some allocators inject "magic" values as part of the
bookkeeping information. They store, say, <code>0x55</code> at <code>address + 2</code>. This would
waste an extra byte of memory per allocation, but would allow them to know when
a mistake has been made. To reduce the impact of this, allocators often disable
this behaviour by default and allow you to enable it only when you're debugging.</p>
<h2 id="playground"><a class="anchor" href="#playground">#</a>
Playground</h2>
<p>If you're keen to take your new found knowledge and try your hand at writing
your own allocators, you can click <a href="/allocator-playground">here</a> to go to my
allocator playground. You'll be able to write JavaScript code that implements
the <code>malloc</code>/<code>free</code> API and visualise how it works!</p>
<h2 id="conclusion"><a class="anchor" href="#conclusion">#</a>
Conclusion</h2>
<p>We've covered a lot in this post, and if it has left you yearning for more you
won't be disappointed. I've specifically avoided the topics of virtual memory,
<code>brk</code> vs <code>mmap</code>, the role of CPU caches, and the endless tricks real <code>malloc</code>
implementations pull out of their sleeves. There's no shortage of information
about memory allocators on the Internet, and if you've read this far you should
be well-placed to dive in to it.</p>
<p>Join the discussion on <a rel="external" href="https://news.ycombinator.com/item?id=36029087">Hacker News</a>!</p>
<h3 id="acknowledgments"><a class="anchor" href="#acknowledgments">#</a>
Acknowledgments</h3>
<p>Special thanks to the following people:</p>
<ul>
<li><a rel="external" href="https://chrisdown.name">Chris Down</a> for lending me his extensive knowledge of real-world
memory allocators.</li>
<li><a rel="external" href="https://zemlan.in/">Anton Verinov</a> for lending me his extensive knowledge of the web,
browser developer tools, and user experience.</li>
<li>Blake Becker, Matt Kaspar, Krista Horn, Jason Peddle, and
<a rel="external" href="https://joshwcomeau.com">Josh W. Comeau</a> for their insight and constructive
reviews.</li>
</ul>
<!--
## Real-world performance
By making use of boundary tags, we saw that `free` can be made really fast.
No list traversals required, just inspection and manipulation of a few bytes
of bookkeeping information. But `malloc` still has to traverse the list of
all blocks, free or used, to find one that can fit the current request.
How do we make `malloc` fast?
What we're asking here isn't for the fastest possible `malloc` implementation.
We've already seen that, at the very start of the article. The `malloc` that
can't `free`. That's not what we want. We want a `malloc` that gets close to
that speed, without creating a fragmented heap. We want high throughput, low
fragmentation.
There's no one-size-fits-all solution here, so I will list a few ways real-world
allocators try to achieve this.
### Segmenting/binning memory
We touched on this earlier, but a common approach to balancing throughput and
fragmentation is by splitting memory up in to segments reserved for allocations
of a specific size. In our example we had 2 segments: 1 for small allocations
and 1 for big allocations.
A `malloc` implementation called `dlmalloc` ("Doug Lea's `malloc`") splits
memory up in to 64(!) different size classes that it calls "bins." Each size
class has a linked list of free blocks of memory associated with it. These lists
begin empty, and as memory is `free`d it gets added to the appropriate list.
When there are no readily available free blocks in these bins, memory is
allocated from what `dlmalloc` refers to as "the wilderness." This is just the
free memory available to the program at the very beginning. `dlmalloc` takes
from here when it needs to, but prefers to look in the size class bins first. It
only takes from the wilderness when it can't take from anywhere else.
If memory isn't available in the appropriate bin, but is available in the next
bin up, `dlmalloc` will take a larger block, split it in 2, allocate one, and
put the other in the appropriate bin for later use. Likewise, when `free`ing,
`dlmalloc` will coalesce blocks with their neighbours and return the resulting
block to the largest bin it can. These techniques help reduce fragmentation.
### Caching
Another cool trick `dlmalloc` uses is called the "designated victim." When
`dlmalloc` takes a block from a bin larger than the current allocation, it
caches the remainder block to be used as the preferred location for the next
allocation that does not perfectly match to a bin size.
`dlmalloc` also caches whether or not bins have blocks in them using a bitmap it
calls the "binmap." This is a 32 bit value where each bit represents the state
of a given bin. If it's a 1, the bin has blocks ready to use in it. If it's 0,
it doesn't. This makes finding an appropriate bin really, really fast.
`phkmalloc`, the spiritual predecessor to `dlmalloc`, maintains a counter of how
many blocks are free in a given "arena" or memory. `phkmalloc` works on a tiered
system, putting smaller blocks inside of larger blocks, in order to be able to
return the larger blocks back to the operating system when they have been
completely freed. It calls these larger blocks "arenas."
### Locality
I've not touched on this topic at all, but it is very important especially in
more modern `malloc` implementations. Without going in to too much detail,
memory access involves a hierarchy of caches. Retrieving a value from memory is
slow, so between your CPU and your memory sit layers of caches. Each layer
closer to the CPU is faster but smaller. These caches can range in size from
100MB at the largest layer to a few dozen kilobytes at the smallest. It's common
for CPUs to have 3 layers.
When you fetch a value from memory, your CPU will actually fetch more than
needed. It does this because it is likely that memory close together is needed
at the same time. This is called "spatial locality." Hard drives do the same
thing.
`malloc` is uniquely positioned to take advantage of this fact. If your `malloc`
implementation intentionally places blocks close to blocks that were `malloc`ed
around the same time, it will increase the chance that a single fetch from
memory will hit multiple blocks of soon-needed memory.
Locality is also an argument in favour of storing your bookkeeping information
separate to allocated memory. The more tightly packed a program's in-use memory
is, the more likely it is to get cached together. Also, if you need to traverse
lists to find free blocks, and your list traversal works using boundary tags,
you will be accessing memory that is unlikely to be used. These accesses get
cached, evicting potentially useful memory in the process.
This is why `dlmalloc` maintains its "binmap" separate from the bins themselves.
You can check the status of all bins with minimal cache disturbance.
### Multithreading
The last topic we're going to talk about is multithreading. It's very common for
computers in 2023 to have many CPU cores, but this wasn't always the case. For
example, when `phkmalloc` was written there was no consideration for
multithreading in the implementation. `dlmalloc` is not thread-safe by default,
but comes with an option to make it thread-safe at a huge performance cost.
One of the first `malloc` implementations to optimise for multithreaded
use-cases was `jemalloc` ("Jason Evans `malloc`"). Written around 2006,
`jemalloc` makes the observation that is 2 CPU cores try to access the same
area of the CPU cache, they will "fight" over the "ownership" of that area of
the cache.
What does this mean?
CPU cache is split in to "lines." These lines are typically 64 bytes in size,
and represent a 64 byte chunk of memory. Each CPU core has its own dedicated set
of caches, so the same 64 byte chunk of memory could potentially be cached on
multiple cores.
When 2 threads running on 2 different cores try to access the same line of
cache, by trying to access the same area of memory and finding it already
cached, it can become a problem. If they both write to it, that will trigger the
cache to be synchronised across CPU cores, and this can be really slow. Without
this synchronisation, the same 64 byte region of memory could appear to hold
different values depending on what core you see it from.
To avoid this, `jemalloc` splits memory up in to 2MB chunks, and each chunk can
only be accessed by 1 thread. Giving threads unique ownership of parts of memory
guarantees that you will avoid this slow cache synchronisation. It also gives
your `malloc` implementation thread-safety, because you know that all threads
will be operating on memory they have sole ownership of.
-->
Load Balancing2023-04-10T00:00:00+00:002023-04-10T00:00:00+00:00Sam Rose/load-balancing/<script type="module" src="/js/load-balancers.js"></script>
<style>
.simulation {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 2.5em;
}
.load-balancer {
color: black;
font-weight: bold;
}
.request {
color: #04BF8A;
font-weight: bold;
}
.server {
color: #999999;
font-weight: bold;
}
.dropped {
color: red;
font-weight: bold;
}
.lds-dual-ring {
display: inline-block;
width: 80px;
height: 80px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 64px;
height: 64px;
margin: 8px;
border-radius: 50%;
border: 6px solid #000;
border-color: #000 transparent #000 transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<p>Past a certain point, web applications outgrow a single server deployment.
Companies either want to increase their availability, scalability, or both! To
do this, they deploy their application across multiple servers with a load
balancer in front to distribute incoming requests. Big companies may need
thousands of servers running their web application to handle the load.</p>
<p>In this post we're going to focus on the ways that a single load balancer might
distribute HTTP requests to a set of servers. We'll start from the bottom and
work our way up to modern load balancing algorithms.</p>
<h2 id="visualising-the-problem"><a class="anchor" href="#visualising-the-problem">#</a>
Visualising the problem</h2>
<p>Let's start at the beginning: a single <span class="load-balancer">load
balancer</span> sending <span class="request">requests</span> to a single <span
class="server">server</span>. <span class="request">Requests</span> are being
sent at a rate of 1 request per second (RPS), and each <span
class="request">request</span> reduces in size as the <span
class="server">server</span> processes it.</p>
<div id="1" class="simulation" style="height: 200px">
<div class="lds-dual-ring"></div>
</div>
<p>For a lot of websites, this setup works just fine. Modern <span
class="server">servers</span> are powerful and can handle a lot of <span
class="request">requests</span>. But what happens when they can't keep up?</p>
<div id="2" class="simulation" style="height: 200px">
<div class="lds-dual-ring"></div>
</div>
<p>Here we see that a rate of 3 RPS causes some <span
class="request">requests</span> to get <span class="dropped">dropped</span>. If
a <span class="request">request</span> arrives at the <span
class="server">server</span> while another <span class="request">request</span>
is being processed, the <span class="server">server</span> will <span
class="dropped">drop</span> it. This will result in an error being shown to the
user and is something we want to avoid. We can add another <span
class="server">server</span> to our <span class="load-balancer">load
balancer</span> to fix this.</p>
<div id="3" class="simulation" style="height: 200px">
<div class="lds-dual-ring"></div>
</div>
<p>No more <span class="dropped">dropped</span> <span
class="request">requests</span>! The way our <span class="load-balancer">load
balancer</span> is behaving here, sending a request to each <span
class="server">server</span> in turn, is called "round robin" load balancing.
It's one of the simplest forms of load balancing, and works well when your <span
class="server">servers</span> are all equally powerful and your <span
class="request">requests</span> are all equally expensive.</p>
<div id="4" class="simulation" style="height: 200px">
<div class="lds-dual-ring"></div>
</div>
<h2 id="when-round-robin-doesn-t-cut-it"><a class="anchor" href="#when-round-robin-doesn-t-cut-it">#</a>
When round robin doesn't cut it</h2>
<p>In the real world, it's rare for <span class="server">servers</span> to be
equally powerful and <span class="request">requests</span> to be equally
expensive. Even if you use the exact same <span class="server">server</span>
hardware, performance may differ. Applications may have to service many
different types of <span class="request">requests</span>, and these will likely
have different performance characteristics.</p>
<p>Let's see what happens when we vary <span class="request">request</span> cost.
In the following simulation, <span class="request">requests</span> aren't
equally expensive. You'll be able to see this by some <span
class="request">requests</span> taking longer to shrink than others.</p>
<div id="5" class="simulation" style="height: 200px">
<div class="lds-dual-ring"></div>
</div>
<p>While most <span class="request">requests</span> get served successfully, we do
<span class="dropped">drop</span> some. One of the ways we can mitigate this is
to have a "request queue."</p>
<div id="6" class="simulation" style="height: 250px">
<div class="lds-dual-ring"></div>
</div>
<p>Request queues help us deal with uncertainty, but it's a trade-off. We will
<span class="dropped">drop</span> fewer <span class="request">requests</span>,
but at the cost of some <span class="request">requests</span> having a higher
latency. If you watch the above simulation long enough, you might notice the
<span class="request">requests</span> subtly changing colour. The longer they go
without being served, the more their colour will change. You'll also notice that
thanks to the <span class="request">request</span> cost variance, <span
class="server">servers</span> start to exhibit an imbalance. Queues will get
backed up on <span class="server">servers</span> that get unlucky and have to
serve multiple expensive <span class="request">requests</span> in a row. If
a queue is full, we will <span class="dropped">drop</span> the <span
class="request">request</span>.</p>
<p>Everything said above applies equally to <span class="server">servers</span>
that vary in power. In the next simulation we also vary the power of each
<span class="server">server</span>, which is represented visually with a darker
shade of grey.</p>
<div id="7" class="simulation" style="height: 250px">
<div class="lds-dual-ring"></div>
</div>
<p>The <span class="server">servers</span> are given a random power value, but odds
are some are less powerful than others and quickly start to <span
class="dropped">drop</span> <span class="request">requests</span>. At the same
time, the more powerful <span class="server">servers</span> sit idle most of the
time. This scenario shows the key weakness of round robin: variance.</p>
<p>Despite its flaws, however, round robin is still the default HTTP load balancing
method for <a rel="external" href="https://nginx.org/en/docs/http/load_balancing.html">nginx</a>.</p>
<h2 id="improving-on-round-robin"><a class="anchor" href="#improving-on-round-robin">#</a>
Improving on round robin</h2>
<p>It's possible to tweak round robin to perform better with variance. There's an
algorithm called "weighted round robin" which involves getting humans
to tag each <span class="server">server</span> with a weight that dictates how
many <span class="request">requests</span> to send to it.</p>
<p>In this simulation, we use each <span class="server">server's</span> known power
value as its weight, and we give more powerful <span
class="server">servers</span> more <span class="request">requests</span> as we
loop through them.</p>
<div id="8" class="simulation" style="height: 250px">
<div class="lds-dual-ring"></div>
</div>
<p>While this handles the variance of <span class="server">server</span> power
better than vanilla round robin, we still have <span
class="request">request</span> variance to contend with. In practice, getting
humans to set the weight by hand falls apart quickly. Boiling <span
class="server">server</span> performance down to a single number is hard, and
would require careful load testing with real workloads. This is rarely done, so
another variant of weighted round robin calculates weights dynamically by using
a proxy metric: latency.</p>
<p>It stands to reason that if one <span class="server">server</span> serves
<span class="request">requests</span> 3 times faster than another <span class="server">server</span>, it's probably 3 times faster and should receive
3 times more <span class="request">requests</span> than the other <span class="server">server</span>.</p>
<div id="9" class="simulation" style="height: 250px">
<div class="lds-dual-ring"></div>
</div>
<p>I've added text to each <span class="server">server</span> this time that shows
the average latency of the last 3 <span class="request">requests</span> served.
We then decide whether to send 1, 2, or 3 <span class="request">requests</span>
to each <span class="server">server</span> based on the relative differences in
the latencies. The result is very similar to the initial weighted round robin
simulation, but there's no need to specify the weight of each <span
class="server">server</span> up front. This algorithm will also be able to adapt
to changes in <span class="server">server</span> performance over time. This is
called "dynamic weighted round robin."</p>
<p>Let's see how it handles a complex situation, with high variance in both <span
class="server">server</span> power and <span class="request">request</span>
cost. The following simulation uses randomised values, so feel free to refresh
the page a few times to see it adapt to new variants.</p>
<div id="10" class="simulation" style="height: 250px">
<div class="lds-dual-ring"></div>
</div>
<h2 id="moving-away-from-round-robin"><a class="anchor" href="#moving-away-from-round-robin">#</a>
Moving away from round robin</h2>
<p>Dynamic weighted round robin seems to account well for variance in both <span
class="server">server</span> power and <span class="request">request
</span> cost. But what if I told you we could do even better, and with a simpler
algorithm?</p>
<div id="11" class="simulation" style="height: 250px">
<div class="lds-dual-ring"></div>
</div>
<p>This is called "least connections" load balancing.</p>
<p>Because the <span class="load-balancer">load balancer</span> sits between the
<span class="server">server</span> and the user, it can accurately keep track
of how many outstanding <span class="request">requests</span> each <span
class="server">server</span> has. Then when a new <span class="request">
request</span> comes in and it's time to determine where to send it, it knows
which <span class="server">servers</span> have the least work to do and
prioritises those.</p>
<p>This algorithm performs extremely well regardless how much variance exists.
It cuts through uncertainty by maintaining an accurate understanding of what
each <span class="server">server</span> is doing. It also has the benefit of
being very simple to implement.</p>
<p>Let's see this in action in a similarly complex simulation, the same parameters
we gave the dynamic weighted round robin algorithm above. Again, these
parameters are randomised within given ranges, so refresh the page to see new
variants.</p>
<div id="12" class="simulation" style="height: 250px">
<div class="lds-dual-ring"></div>
</div>
<p>While this algorithm is a great balance between simplicity and performance, it's
not immune to <span class="dropped">dropping</span> <span
class="request">requests</span>. However, what you'll notice is that the only
time this algorithm <span class="dropped">drops</span> <span
class="request">requests</span> is when there is literally no more queue space
available. It will make sure all available resources are in use, and that makes
it a great default choice for most workloads.</p>
<h2 id="optimizing-for-latency"><a class="anchor" href="#optimizing-for-latency">#</a>
Optimizing for latency</h2>
<p>Up until now I've been avoiding a crucial part of the discussion: what we're
optimising for. Implicitly, I've been considering <span
class="dropped">dropped</span> <span class="request">requests</span> to be
really bad and seeking to avoid them. This is a nice goal, but it's not the
metric we most want to optimise for in an HTTP <span class="load-balancer">load
balancer</span>.</p>
<p>What we're often more concerned about is latency. This is measured in
milliseconds from the moment a <span class="request">request</span> is created
to the moment it has been served. When we're discussing latency in this context,
it is common to talk about different "percentiles." For example, the 50th
percentile (also called the "median") is defined as the millisecond value for
which 50% of requests are below, and 50% are above.</p>
<p>I ran 3 simulations with identical parameters for 60 seconds and took a variety
of measurements every second. Each simulation varied only by the load balancing
algorithm used. Let's compare the medians for each of the 3 simulations:</p>
<div id="graph-medians"></div>
<p>You might not have expected it, but round robin has the best median latency. If
we weren't looking at any other data points, we'd miss the full story. Let's
take a look at the 95th and 99th percentiles.</p>
<div id="graph-higher"></div>
<p>Note: there's no colour difference between the different percentiles for each
load balancing algorithm. Higher percentiles will always be higher on the graph.</p>
<p>We see that round robin doesn't perform well in the higher percentiles. How can
it be that round robin has a great median, but bad 95th and 99th percentiles?</p>
<p>In round robin, the state of each <span class="server">server</span> isn't
considered, so you'll get quite a lot of <span class="request">requests</span>
going to <span class="server">servers</span> that are idle. This is how we get
the low 50th percentile. On the flip side, we'll also happily send <span
class="request">requests</span> to <span class="server">servers</span> that are
overloaded, hence the bad 95th and 99th percentiles.</p>
<p>We can take a look at the full data in histogram form:</p>
<div id="histogram-1"></div>
<p>I chose the parameters for these simulations to avoid <span
class="dropped">dropping</span> any <span class="request">requests</span>. This
guarantees we compare the same number of data points for all 3 algorithms.
Let's run the simulations again but with an increased RPS value, designed to
push all of the algorithms past what they can handle. The following is a graph
of cumulative <span class="request">requests</span> <span
class="dropped">dropped</span> over time.</p>
<div id="graph-dropped"></div>
<p>Least connections handles overload much better, but the cost of doing that is
slightly higher 95th and 99th percentile latencies. Depending on your use-case,
this might be a worthwhile trade-off.</p>
<h2 id="one-last-algorithm"><a class="anchor" href="#one-last-algorithm">#</a>
One last algorithm</h2>
<p>If we <em>really</em> want to optimise for latency, we need an algorithm that takes
latency into account. Wouldn't it be great if we could combine the dynamic
weighted round robin algorithm with the least connections algorithm? The latency
of weighted round robin and the resilience of least connections.</p>
<p>Turns out we're not the first people to have this thought. Below is a simulation
using an algorithm called "peak exponentially weighted moving average" (or
PEWMA). It's a long and complex name but hang in there, I'll break down how it
works in a moment.</p>
<div id="13" class="simulation" style="height: 250px">
<div class="lds-dual-ring"></div>
</div>
<p>I've set specific parameters for this simulation that are guaranteed to exhibit
an expected behaviour. If you watch closely, you'll notice that the algorithm
just stops sending <span class="request">requests</span> to the leftmost <span
class="server">server</span> after a while. It does this because it figures out
that all of the other <span class="server">servers</span> are faster, and
there's no need to send <span class="request">requests</span> to the slowest
one. That will just result in <span class="request">requests</span> with a
higher latency.</p>
<p>So how does it do this? It combines techniques from dynamic weighted round robin
with techniques from least connections, and sprinkles a little bit of its own
magic on top.</p>
<p>For each <span class="server">server</span>, the algorithm keeps track of the
latency from the last N <span class="request">requests</span>. Instead of using
this to calculate an average, it sums the values but with an exponentially
decreasing scale factor. This results in a value where the older a latency is,
the less it contributes to the sum. Recent <span class="request">requests</span>
influence the calculation more than old ones.</p>
<p>That value is then taken and multiplied by the number of open connections to the
<span class="server">server</span> and the result is the value we use to choose
which <span class="server">server</span> to send the next <span
class="request">request</span> to. Lower is better.</p>
<p>So how does it compare? First let's take a look at the 50th, 95th, and 99th
percentiles when compared against the least connections data from earlier.</p>
<div id="pewma-graph"></div>
<p>We see a marked improvement across the board! It's far more pronounced at the
higher percentiles, but consistently present for the median as well. Here we
can see the same data in histogram form.</p>
<div id="pewma-histogram"></div>
<p>How about <span class="dropped">dropped</span> <span
class="requests">requests</span>?</p>
<div id="pewma-dropped"></div>
<p>It starts out performing better, but over time performs worse than least
connections. This makes sense. PEWMA is opportunistic in that it tries to get
the best latency, and this means it may sometimes leave a <span class="server">
server</span> less than fully loaded.</p>
<p>I want to add here that PEWMA has a lot of parameters that can be tweaked. The
implementation I wrote for this post uses a configuration that seemed to work
well for the situations I tested it in, but further tweaking could get you
better results vs least connections. This is one of the downsides of PEWMA vs
least connections: extra complexity.</p>
<h2 id="conclusion"><a class="anchor" href="#conclusion">#</a>
Conclusion</h2>
<p>I spent a long time on this post. It was difficult to balance realism against
ease of understanding, but I feel good about where I landed. I'm hopeful that
being able to see how these complex systems behave in practice, in ideal and
less-than-ideal scenarios, helps you grow an intuitive understanding of when
they would best apply to your workloads.</p>
<p><strong>Obligatory disclaimer</strong>: You must always benchmark your own workloads over
taking advice from the Internet as gospel. My simulations here ignore some real
life constraints (server slow start, network latency), and are set up to display
specific properties of each algorithm. They aren't realistic benchmarks to be
taken at face value.</p>
<p>To round this out, I leave you with a version of the simulation that lets you
tweak most of the parameters in real time. Have fun!</p>
<p><strong>EDIT</strong>: <em>Thanks to everyone who participated in the discussions on
<a rel="external" href="https://news.ycombinator.com/item?id=35588797">Hacker News</a>,
<a rel="external" href="https://twitter.com/samwhoo/status/1645429789107318789?s=20">Twitter</a> and
<a rel="external" href="https://lobste.rs/s/kydugs/load_balancing">Lobste.rs</a>!</em></p>
<p><em>You all had a tonne of great questions and I tried to answer all of them.
Some of the common themes were about missing things, either algorithms (like
"power of 2 choices") or downsides of algorithms covered (like how "least
connections" handles errors from servers).</em></p>
<p><em>I tried to strike a balance between post length and complexity of the
simulations. I'm quite happy with where I landed, but like you I also wish I
could have covered more. I'd love to see people taking inspiration from this
and covering more topics in this space in a visual way. Please ping me if you
do!</em></p>
<p><em>The other common theme was "how did you make this?" I used
<a rel="external" href="https://pixijs.com/">PixiJS</a> and I'm really happy with how it turned out. It's
my first time using this library and it was quite easy to get to grips with.
If writing visual explanations like this are something you're interested in,
I recommend it!</em></p>
<h2 id="playground"><a class="anchor" href="#playground">#</a>
Playground</h2>
<div id="fin" class="simulation" style="height: 450px; margin-top: 20px">
<div class="lds-dual-ring"></div>
</div>
Practical Problems with Auto-Increment2023-03-25T00:00:00+00:002023-03-25T00:00:00+00:00Sam Rose/blog/practical-problems-with-auto-increment/<p>In this post I'm going to demonstrate 2 reasons I will be avoiding
auto-increment fields in Postgres and MySQL in future. I'm going to prefer using
UUID fields unless I have a <em>very</em> good reason not to.</p>
<h2 id="mysql-8-0-auto-increment-id-re-use"><a class="anchor" href="#mysql-8-0-auto-increment-id-re-use">#</a>
MySQL <8.0 auto-increment ID re-use</h2>
<p>If you're running an older version of MySQL, it's possible for auto-incrementing
IDs to get re-used. Let's see this in action.</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>$ docker volume create mysql-data</span></span>
<span class="giallo-l"><span>$ docker run --platform linux/amd64 -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 -v mysql-data:/var/lib/mysql mysql:5.7</span></span></code></pre>
<p>This gets us a Docker container of MySQL 5.7 running, attached to a volume that
will persist the data between runs of this container. Next let's get a simple
schema we can work with:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>$ docker run -it --rm --network host --platform linux/amd64 mysql:5.7 mysql -h 127.0.0.1 -P 3306 -u root -p</span></span>
<span class="giallo-l"><span>mysql> CREATE DATABASE my_database;</span></span>
<span class="giallo-l"><span>Query OK, 1 row affected (0.01 sec)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>mysql> USE my_database;</span></span>
<span class="giallo-l"><span>Database changed</span></span>
<span class="giallo-l"><span>mysql> CREATE TABLE my_table (</span></span>
<span class="giallo-l"><span> -> ID INT AUTO_INCREMENT PRIMARY KEY</span></span>
<span class="giallo-l"><span> -> );</span></span>
<span class="giallo-l"><span>Query OK, 0 rows affected (0.02 sec)</span></span></code></pre>
<p>Now let's insert a couple of rows.</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>mysql> INSERT INTO my_table () VALUES ();</span></span>
<span class="giallo-l"><span>Query OK, 1 row affected (0.03 sec)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>mysql> INSERT INTO my_table () VALUES ();</span></span>
<span class="giallo-l"><span>Query OK, 1 row affected (0.01 sec)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>mysql> INSERT INTO my_table () VALUES ();</span></span>
<span class="giallo-l"><span>Query OK, 1 row affected (0.01 sec)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>mysql> SELECT * FROM my_table;</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>| ID |</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>| 1 |</span></span>
<span class="giallo-l"><span>| 2 |</span></span>
<span class="giallo-l"><span>| 3 |</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>3 rows in set (0.01 sec)</span></span></code></pre>
<p>So far so good. We can restart the MySQL server and run the same SELECT
statement again and get the same result.</p>
<p>Let's delete a row.</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>mysql> DELETE FROM my_table WHERE ID=3;</span></span>
<span class="giallo-l"><span>Query OK, 1 row affected (0.03 sec)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>mysql> SELECT * FROM my_table;</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>| ID |</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>| 1 |</span></span>
<span class="giallo-l"><span>| 2 |</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>2 rows in set (0.00 sec)</span></span></code></pre>
<p>Let's insert a new row to make sure the ID 3 doesn't get reused.</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>mysql> INSERT INTO my_table () VALUES ();</span></span>
<span class="giallo-l"><span>Query OK, 1 row affected (0.02 sec)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>mysql> SELECT * FROM my_table;</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>| ID |</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>| 1 |</span></span>
<span class="giallo-l"><span>| 2 |</span></span>
<span class="giallo-l"><span>| 4 |</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>3 rows in set (0.00 sec)</span></span></code></pre>
<p>Perfect. Let's delete that latest row, restart the server, and then insert
a new row.</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>mysql> DELETE FROM my_table WHERE ID=4;</span></span>
<span class="giallo-l"><span>Query OK, 1 row affected (0.01 sec)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>mysql> SELECT * FROM my_table;</span></span>
<span class="giallo-l"><span>ERROR 2013 (HY000): Lost connection to MySQL server during query</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>$ docker run -it --rm --network host --platform linux/amd64 mysql:5.7 mysql -h 127.0.0.1 -P 3306 -u root -p</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>mysql> USE my_database;</span></span>
<span class="giallo-l"><span>Database changed</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>mysql> SELECT * FROM my_table;</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>| ID |</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>| 1 |</span></span>
<span class="giallo-l"><span>| 2 |</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>2 rows in set (0.00 sec)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>mysql> INSERT INTO my_table () VALUES ();</span></span>
<span class="giallo-l"><span>Query OK, 1 row affected (0.03 sec)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>mysql> SELECT * FROM my_table;</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>| ID |</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>| 1 |</span></span>
<span class="giallo-l"><span>| 2 |</span></span>
<span class="giallo-l"><span>| 3 |</span></span>
<span class="giallo-l"><span>+----+</span></span>
<span class="giallo-l"><span>3 rows in set (0.00 sec)</span></span></code></pre>
<p>Eep. MySQL has re-used the ID 3. This is because the way that auto-increment
works in InnoDB is, on server restart, it will figure out what the next
ID to use is by effectively running this query:</p>
<pre class="giallo z-code"><code data-lang="sql"><span class="giallo-l"><span class="z-keyword">SELECT</span><span class="z-support z-function"> MAX</span><span>(ID) </span><span class="z-keyword">FROM</span><span> my_table;</span></span></code></pre>
<p>If you had deleted the most recent records from the table just before restart,
IDs that had been used will be re-used when the server comes back up.</p>
<p>In theory, this <em>shouldn't</em> cause you trouble. Best practice dictates that
you shouldn't be using IDs from database tables outside of that table unless
it's some foreign key field, and you certainly wouldn't leak that ID out of
your system, right?</p>
<p>In practice, this stuff happens and can cause devastatingly subtle bugs. MySQL
8.0 changed this behaviour by storing the auto-increment value on disk in a way
that persists across restarts.</p>
<h2 id="postgres-sequence-values-don-t-get-replicated"><a class="anchor" href="#postgres-sequence-values-don-t-get-replicated">#</a>
Postgres sequence values don't get replicated</h2>
<p>Like MySQL 8.0, Postgres stores auto-increment values on disk. It does this in
a schema object called a "sequence." When you create an auto-incrementing
field in Postgres, behind the scenes a sequence will be created to back that
field and durably keep track of what the next value should be.</p>
<p>Let's take a look at that in practice.</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>$ docker volume create postgres-14-data</span></span>
<span class="giallo-l"><span>$ docker run --network host -e POSTGRES_PASSWORD=my-secret-pw -v postgres-14-data:/var/lib/postgresql/data -p postgres:14</span></span></code></pre>
<p>With Postgres up and running, let's go ahead and create our table:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>$ docker run -it --rm --network host postgres:14 psql -h 127.0.0.1 -U postgres</span></span>
<span class="giallo-l"><span>postgres=# CREATE TABLE my_table (id SERIAL PRIMARY KEY);</span></span>
<span class="giallo-l"><span>CREATE TABLE</span></span></code></pre>
<p>And insert a few rows:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>postgres=# INSERT INTO my_table DEFAULT VALUES;</span></span>
<span class="giallo-l"><span>INSERT 0 1</span></span>
<span class="giallo-l"><span>postgres=# INSERT INTO my_table DEFAULT VALUES;</span></span>
<span class="giallo-l"><span>INSERT 0 1</span></span>
<span class="giallo-l"><span>postgres=# INSERT INTO my_table DEFAULT VALUES;</span></span>
<span class="giallo-l"><span>INSERT 0 1</span></span>
<span class="giallo-l"><span>postgres=# SELECT * FROM my_table;</span></span>
<span class="giallo-l"><span> id</span></span>
<span class="giallo-l"><span>----</span></span>
<span class="giallo-l"><span> 1</span></span>
<span class="giallo-l"><span> 2</span></span>
<span class="giallo-l"><span> 3</span></span>
<span class="giallo-l"><span>(3 rows)</span></span></code></pre>
<p>So far so good. Let's take a look at the table:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>postgres=# \d my_table</span></span>
<span class="giallo-l"><span> Table "public.my_table"</span></span>
<span class="giallo-l"><span> Column | Type | Collation | Nullable | Default</span></span>
<span class="giallo-l"><span>--------+---------+-----------+----------+--------------------------------------</span></span>
<span class="giallo-l"><span> id | integer | | not null | nextval('my_table_id_seq'::regclass)</span></span>
<span class="giallo-l"><span>Indexes:</span></span>
<span class="giallo-l"><span> "my_table_pkey" PRIMARY KEY, btree (id)</span></span></code></pre>
<p>This output tells us that the default value for our <code>id</code> field is the <code>nextval</code>
of <code>my_table_id_seq</code>. Let's take a look at <code>my_table_id_seq</code>:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>postgres=# \d my_table_id_seq</span></span>
<span class="giallo-l"><span> Sequence "public.my_table_id_seq"</span></span>
<span class="giallo-l"><span> Type | Start | Minimum | Maximum | Increment | Cycles? | Cache</span></span>
<span class="giallo-l"><span>---------+-------+---------+------------+-----------+---------+-------</span></span>
<span class="giallo-l"><span> integer | 1 | 1 | 2147483647 | 1 | no | 1</span></span>
<span class="giallo-l"><span>Owned by: public.my_table.id</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>postgres=# SELECT currval('my_table_id_seq');</span></span>
<span class="giallo-l"><span> currval</span></span>
<span class="giallo-l"><span>---------</span></span>
<span class="giallo-l"><span> 3</span></span>
<span class="giallo-l"><span>(1 row)</span></span></code></pre>
<p>Neat, we have a bonafide object in Postgres that's keeping track of the
auto-incrementing ID value. If we were to repeat what we did in MySQL, delete
some rows and restart, we wouldn't have the same problem here. <code>my_table_id_seq</code>
is saved to disk and doesn't lose its place.</p>
<p>Or does it?</p>
<p>If you want to update Postgres to a new major version, the way you typically
accomplish that is by creating a new Postgres instance on the version you want
to upgrade to, logically replicate from the old instance to the new one, and
then switch your application to talk to the new one.</p>
<p>First we need to restart our Postgres 14 with some new configuration to allow
logical replication:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>$ docker run --network host -e POSTGRES_PASSWORD=my-secret-pw -v postgres-14-data:/var/lib/postgresql/data -p postgres:14 -c wal_level=logical</span></span></code></pre>
<p>Now let's get Postgres 15 up and running:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>$ docker volume create postgres-15-data</span></span>
<span class="giallo-l"><span>$ docker run --network host -e POSTGRES_PASSWORD=my-secret-pw -v postgres-15-data:/var/lib/postgresql/data postgres:15 postgres:14 -c wal_level=logical -p 5431</span></span></code></pre>
<p>Next up, we create a "publication" on our Postgres 14 instance:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>postgres=# CREATE PUBLICATION my_publication FOR ALL TABLES;</span></span>
<span class="giallo-l"><span>CREATE PUBLICATION</span></span></code></pre>
<p>Then we create our "my_table" table and a "subscription" on our Postgres 15
instance:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>postgres=# CREATE TABLE my_table (id SERIAL PRIMARY KEY);</span></span>
<span class="giallo-l"><span>CREATE TABLE</span></span>
<span class="giallo-l"><span>postgres=# CREATE SUBSCRIPTION my_subscription CONNECTION 'host=127.0.0.1 port=5432 dbname=postgres user=postgres password=my-secret-pw' PUBLICATION my_publication;</span></span>
<span class="giallo-l"><span>NOTICE: created replication slot "my_subscription" on publisher</span></span>
<span class="giallo-l"><span>CREATE SUBSCRIPTION</span></span></code></pre>
<p>After doing this, we should see data syncing between old and new instances:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>$ docker run -it --rm --network host postgres:15 psql -h 127.0.0.1 -U postgres -p 5432 -c "SELECT * FROM my_table"</span></span>
<span class="giallo-l"><span> id</span></span>
<span class="giallo-l"><span>----</span></span>
<span class="giallo-l"><span> 1</span></span>
<span class="giallo-l"><span> 2</span></span>
<span class="giallo-l"><span> 3</span></span>
<span class="giallo-l"><span>(3 rows)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>$ docker run -it --rm --network host postgres:15 psql -h 127.0.0.1 -U postgres -p 5431 -c "SELECT * FROM my_table"</span></span>
<span class="giallo-l"><span> id</span></span>
<span class="giallo-l"><span>----</span></span>
<span class="giallo-l"><span> 1</span></span>
<span class="giallo-l"><span> 2</span></span>
<span class="giallo-l"><span> 3</span></span>
<span class="giallo-l"><span>(3 rows)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>$ docker run -it --rm --network host postgres:15 psql -h 127.0.0.1 -U postgres -p 5432 -c "INSERT INTO my_table DEFAULT VALUES"</span></span>
<span class="giallo-l"><span>INSERT 0 1</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>$ docker run -it --rm --network host postgres:15 psql -h 127.0.0.1 -U postgres -p 5431 -c "SELECT * FROM my_table"</span></span>
<span class="giallo-l"><span> id</span></span>
<span class="giallo-l"><span>----</span></span>
<span class="giallo-l"><span> 1</span></span>
<span class="giallo-l"><span> 2</span></span>
<span class="giallo-l"><span> 3</span></span>
<span class="giallo-l"><span> 4</span></span>
<span class="giallo-l"><span>(4 rows)</span></span></code></pre>
<p>So what's the problem?</p>
<p>Well...</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>$ docker run -it --rm --network host postgres:15 psql -h 127.0.0.1 -U postgres -p 5432 -c "SELECT nextval('my_table_id_seq')"</span></span>
<span class="giallo-l"><span> nextval</span></span>
<span class="giallo-l"><span>---------</span></span>
<span class="giallo-l"><span> 5</span></span>
<span class="giallo-l"><span>(1 row)</span></span>
<span class="giallo-l"></span>
<span class="giallo-l"><span>$ docker run -it --rm --network host postgres:15 psql -h 127.0.0.1 -U postgres -p 5431 -c "SELECT nextval('my_table_id_seq')"</span></span>
<span class="giallo-l"><span> nextval</span></span>
<span class="giallo-l"><span>---------</span></span>
<span class="giallo-l"><span> 1</span></span>
<span class="giallo-l"><span>(1 row)</span></span></code></pre>
<p>The sequence value is not replicated. If we tried to insert a row into Postgres
15 we get this:</p>
<pre class="giallo z-code"><code data-lang="plain"><span class="giallo-l"><span>$ docker run -it --rm --network host postgres:15 psql -h 127.0.0.1 -U postgres -p 5431 -c "INSERT INTO my_table DEFAULT VALUES"</span></span>
<span class="giallo-l"><span>ERROR: duplicate key value violates unique constraint "my_table_pkey"</span></span>
<span class="giallo-l"><span>DETAIL: Key (id)=(2) already exists.</span></span></code></pre>
<p>Note: It's tried to insert id=2 here because when we called <code>nextval</code> earlier, it
modified the sequence.</p>
<p>This can make major Postgres version updates very tricky if you rely heavily on
auto-incrementing ID fields. You need to modify the sequence values manually to
values you know for a fact won't be reached during the process of the upgrade,
and then you likely need to disable writes during the upgrade depending on
your workload.</p>
<h2 id="conclusion"><a class="anchor" href="#conclusion">#</a>
Conclusion</h2>
<p>You can avoid all of the above pain by using UUID fields instead of
auto-incrementing integers. These have the benefit of being unpredictable and
not leak information about the cardinality of the underlying table if you do end
up using them outside of the table (which you shouldn't).</p>
<p>Thanks to <a rel="external" href="https://incident.io/blog/one-two-skip-a-few">this article</a> from the
wonderful folks at <a rel="external" href="https://incident.io">Incident.io</a>, I am now aware of the <a rel="external" href="https://en.wikipedia.org/wiki/German_tank_problem">German tank problem</a>.
Well worth reading both the linked article, and the Wikipedia page, for more
reasons not to use auto-increment ID fields.</p>
Getting an Autism Diagnosis2023-03-02T00:00:00+00:002023-03-02T00:00:00+00:00Sam Rose/blog/getting-an-autism-diagnosis/<p>On the 3rd of March 2022, we received a letter informing us that our eldest son,
Max, has Autism Spectrum Disorder. The letter was the end result of a long
process. I’m going to talk about that process from start to finish, in as much
detail as I can.</p>
<p>This post would not have been possible without my wife's dedication to our 2
children, her persistence in the face of long odds, and her diligent note
taking. Sophie, I love you.</p>
<p><img src="/images/max-autism-diagnosis.jpeg" alt="A photograph of a letter explaining that our son, Max, has Autism Spectrum Disorder and Speech and Language difficulties" /></p>
<h2 id="prologue"><a class="anchor" href="#prologue">#</a>
Prologue</h2>
<p>This post spiritually follows on from <a href="/blog/having-a-baby/">this one</a>. I ended
that post by saying I’d like to write another post about the first months of
parenthood. That never happened, because the first months of parenthood are an
extreme test of patience and resolve.</p>
<p>Being new parents, we didn’t know what we were doing. Max was irritable,
difficult to get to sleep, and dropped down to the 9th percentile for weight.
This last piece of information was a shock to us, and to our health visitor, and
led to the revelation that Sophie wasn’t producing enough breastmilk. Nobody’s
fault, just the way it was.</p>
<p>To tell the truth, this was a relief. After we switched to formula feeding,
Max’s temperament changed almost overnight. He was content, he slept better, I
was able to help with feeding, and Sophie’s nipples were able to heal.</p>
<p>Max was 9 months old when the UK went into its first full lockdown of the
COVID-19 pandemic on March 26th 2020. The week before the lockdown was
announced, we had visited a local nursery and been given forms to fill in to
confirm Max’s attendance. We were excited to get him spending time with kids his
own age, and we were looking forward to getting some time off from parenting.</p>
<p>Instead, we all had to stay home 24/7 by law. I was in the fortunate position
to already be working from home, and working in an industry that was relatively
unaffected by the pandemic. But none of this helped Max’s social development.</p>
<p>I tell you all of this because it’s relevant to Max’s diagnosis. While we got
his diagnosis earlier than most, a fact we are ever grateful for, it did take
longer than it would have had there been no pandemic. We attributed a lot of his
behaviour to having lived most of his life in lockdown.</p>
<h2 id="how-we-realised-max-was-different"><a class="anchor" href="#how-we-realised-max-was-different">#</a>
How we realised Max was different</h2>
<p>Max had his first “settling in” session at nursery on February 8th 2021. These
are short sessions, often only a few hours long, designed to ease your child in
to the nursery setting. Max’s first settling in session didn’t go very well, a
fact that when taken in isolation isn't unusual. He spent the majority of the
time screaming, and we ended up cutting it short.</p>
<p>Despite this, Sophie and I settled into a good rhythm with nursery. Max went
there twice a week. It was a good balance between cost, us getting time off, and
Max getting time with his peers. But Max wasn’t taking to it, and one evening a
member of staff took Sophie to one side and said Max “isn’t where we expect him
to be developmentally.”</p>
<p>It's hard to describe how this made me feel. Is this our fault? Are we not
parenting well enough?</p>
<p>Nursery's concerns were that Max screams a lot, can’t follow instructions, and
his speech was less developed than his peers. They suggested it could be Max’s
hearing, and they recommended we talk to our GP to set up a hearing test. We
called our GP and had a frustrating conversation in which he asked us if <em>we</em>
think Max has problems hearing. We explained that we had been recommended to
get a hearing test by his nursery. But did <em>we</em> think he had problems hearing?
I didn’t think Max had any problems hearing, but I didn’t want to say that
because I am not a medical professional.</p>
<p>Nursery also recommended we contact our health visitor to ask for a
developmental review. In our area, kids used to go through a developmental
review at 2 years old. This changed in recent years, and it’s done at 3 years
old now. The review is made up of questions that gauge things your child can and
cannot do. At the end, you get a score. If you want to see what’s on it, you can
search “ASQ:3 24 months” and you’ll find loads of PDFs, all very similar.</p>
<p>Max didn’t do well at this, we had to say that he wasn’t able to do most of the
things they asked about. Because of his low score, we did another questionnaire
called the ASQ:SE:2. This one focuses on social and emotional development.
Again, Max scored low. It was the result of these two tests that led our health
visitor to refer us to our local council’s special educational needs and
disability (SEND) team. This happened on the 7th May 2021.</p>
<p>This referral included appointments with a paediatrician (which we were told
would take a few months, but actually ended up taking almost a full year),
speech and language therapist, and the SEND team themselves. The 7th of May was
the first point at which it felt “serious,” and we started to suspect he may be
autistic.</p>
<h2 id="jumping-through-all-the-hoops"><a class="anchor" href="#jumping-through-all-the-hoops">#</a>
Jumping through all the hoops</h2>
<p>On the 12th of August we had our appointment with the SEND team. We’ll call her
Janet. It took place at nursery, and the day before it Janet had been at nursery
to spend time with Max and observe his behaviour. I remember messaging my boss
saying I need to be away from work for “an hour or so.” The meeting lasted 4
hours. It was obvious that Janet had spent a lot of time with Max, and had taken
detailed notes.</p>
<p>It was in this meeting that we asked “do you think Max is autistic?” Janet said
yes, he probably is. We had asked other people this same question, because it
had been on our mind since the ASQ, but everybody had been cagey about it. “Oh I
couldn’t say”, “I’m not qualified to make that diagnosis”, etc. We appreciated
how forthcoming Janet was with us, and the risk she took personally being open
about it. She confessed in us later that a lot of parents don’t like hearing
that their child might be autistic, so she wasn’t surprised that most people
didn’t want to say.</p>
<p>After this meeting, Janet referred us to the Early Years SEND panel. It was
decided by them that Max had special needs. It’s important to note at this point
that we didn’t have an autism diagnosis. Help is based on need, not diagnosis.
Autism or not, Max needed help with his development and our local council would
give us that help regardless. Janet even went as far as to say that the
diagnosis is irrelevant to the SEND team, he’ll get the help he needs based on
what they observe.</p>
<p>On the 26th of August 2021 we had our first speech and language therapy (SLT)
appointment. This was an introductory session, Max played with some toy cars
while we spoke about his behaviours. One of the things we took away from this
appointment was to put Max's toys into clear plastic containers that he would
need to ask us to open for him. This helps to cement the need for communication.
We still do that to this day, and we do believe it has helped.</p>
<p>On the 13th of September 2021, our special needs practitioner got in touch with
us for the first time. We’ll call her Fay. She arranged “play sessions” with
Max, these happened every few weeks from 22nd of September 2021 to 23rd of
September 2022. In these sessions, Fay presents Max with toys designed to test
him in different ways. Some of them require him to match colours, some of them
shapes, some of them are things you play with with another person, and some are
toys you aren’t meant to touch at all. All of these test how he reacts to
situations, how focused he is, how well he appreciates and accepts playing with
others.</p>
<p>Something those play sessions taught me is that playing with children is a
skill. Fay was able to get Max to play in ways we would have said were
impossible without seeing it for ourselves. She was able to get him to do things
when she said so, and more crucially to not do things he obviously wanted to do.
He responded to her extremely well, and it was a joy to watch her work with him.</p>
<p>We had heard it previously from Janet, but Fay confirmed it: Max has no
difficulty learning. The way he learns is different to most other kids, though,
and will benefit from a more tailored approach. Looking back, it is probably
around this time we started forming our opinion that Max should attend a special
needs school.</p>
<p>On the 30th of September 2021 we had Max's first hearing test. The way that they
wanted to test Max's hearing was with a set of stacking cups. The doctor would
demonstrate making a noise, then putting a cup onto the stack. Then she would
make the noise again and add another cup. Then she would try and get the child
to do the same. Max, however, wasn't at the level of understanding required to
complete this test.</p>
<p>The backup test that she had involved a shelf of toys. The shelf was a grid,
like a set of IKEA Kallax shelves, and in each square was a toy that could make
a noise. The doctor would trigger each toy to make a noise and the idea was for
Max to look at the toy that made the noise. Unfortunately, Max was terrified of
the toys and screamed uncontrollably upon seeing them.</p>
<p>After this, the test was rescheduled for January 10th 2022. This time they tried
to have him listen to a cartoon on the television while they put a sensor in his
ear to take some measurements. He refused to let them put the device in his ear
long enough to get any readings. They tried the first set of tests again, but he
reacted the same way he did before. A third test was scheduled on February 3rd
2022.</p>
<p>This time they wanted to try and do the test while Max was asleep, but we
weren't able to get Max to sleep at the time of the appointment. However, to
everyone's surprise, he went in to the doctor's office and did the stacked cup
test immediately without prompting. He passed with flying colours, ruling out
that hearing was causing his language difficulties. To this day we don't know
what changed.</p>
<p>On the 17th of February 2022, 6 months after the first appointment, we had our
second SLT appointment. We had to chase them up to get this to happen, as we had
not heard back from them. In this appointment we learned about "transition
objects", objects to help children go from one activity to another. We used
bubbles to help get Max to get in the bath, and a toy game controller to help
get him in to the car. He still uses the toy controller today, though he has
grown to enjoy bath time enough to not need the bubbles.</p>
<p>On the same day, in the afternoon, we got a phone call telling us that there had
been a short-notice cancellation with the paediatrician and would we like to do
our appointment on the 20th of February? You're damn right we would! We had been
waiting for this appointment since May 2021, and had heard from friends that
waiting over a year was common. Some people wait more than 2 years. To get seen
in less than a year is rare.</p>
<p>Janet, our SEND coordinator, had compiled all of the paperwork from our other
appointments and sent them to the paediatrician. She had also let the
paediatrician know that we were receptive to a diagnosis (not all parents are).
Unfortunately, for whatever reason, this documentation didn't get to the
paediatrician ready for the appointment. The paediatrician had moved from
another area and wasn't fully set up with her email yet.</p>
<p>However, with what Sophie was able to find on her phone, and after observing Max
for about an hour, the paediatrician told us she felt comfortable giving him a
diagnosis of autism there and then, with an official letter to follow.</p>
<h2 id="wrapping-up"><a class="anchor" href="#wrapping-up">#</a>
Wrapping up</h2>
<p>Getting an autism diagnosis before the age of 3 is uncommon, and I have to
express appreciation for everyone involved in the process. While the help given
to children should be based on needs alone, we have found it helpful to have a
recognised diagnosis.</p>
<p>What I plan to write about next is the process we went through to get Max in to
a special needs school, a process which came to an end just a few weeks ago. We
are ecstatic.</p>
I finally figured out how to take notes!2022-02-14T00:00:00+00:002022-02-14T00:00:00+00:00Sam Rose/blog/note-taking/<p>I’ve never been good at taking notes. I’ve tried. Oh boy, have I tried. Name a
piece of note taking software, odds are I’ve tried it. I’ve even tried going old
school with pen and paper. Nothing sticks.</p>
<p>Until recently.</p>
<p>Some time ago, I learned about Apple’s
<a rel="external" href="https://apps.apple.com/gb/app/shortcuts/id1462947752">Shortcuts</a> app. It’s an
app on iOS, iPadOS, and MacOS that allows you to automate actions between apps.
It’s a little like <a rel="external" href="https://ifttt.com">IFTTT</a>. I played with it and made a few
fun things. I created a keyboard shortcut that could turn my lights on and off,
for example. I didn’t take it much further than that.</p>
<p>Since the start of the new year, I’ve been taking on more responsibility at
work. This has meant an increase in meetings, and an increase in me being
responsible for making sure things are moving forward. This means I often have
to follow up on things after a meeting, and I would sometimes forget to do this.
This would not do, I thought, and decided it was time to start taking meeting
notes.</p>
<p>I had some requirements in mind:</p>
<ol>
<li>I want to be able to tag notes. I’d like to track things like date, who was
there, what the key topics were, and be able to search based on these tags.</li>
<li>I need the ability to create action items, and be able to ask “what action
items have I not yet done?”</li>
<li>It has to be super easy. I want to be able to jump into a meeting and have my
meeting notes ready to go.</li>
</ol>
<p>Turns out, combining Apple Shortcuts with <a rel="external" href="https://bear.app">Bear</a> hits all of
these requirements.</p>
<h2 id="shortcuts"><a class="anchor" href="#shortcuts">#</a>
Shortcuts</h2>
<p>I have two Shortcuts I use to make my note taking life much easier:</p>
<ol>
<li>A shortcut that creates a meeting note.</li>
<li>A shortcut that opens or creates a daily “scratch” note, for note taking
outside of meetings.</li>
</ol>
<p>The meeting note shortcut does the following:</p>
<ol>
<li>Looks in my work calendar for the most recent meeting that started in the
last 30 minutes.</li>
<li>It then creates a note with the meeting title as the note title, and it adds
tags for each person who accepted the calendar invite. It also adds a tag for
the current date, my current location, and the current temperature outside. Just
a bit of fun.</li>
</ol>
<p>I trigger this shortcut by typing cmd+ctrl+m. Any meeting I go in to, the first
thing I do while I’m waiting for people to arrive is hit that shortcut, the note
pops up a few seconds later, and I’m ready to take notes.</p>
<p>The daily scratch note shortcut is much simpler. It creates a note with the
current date as the title, and all of the same non-meeting-specific tags as the
meeting note: date, location, temperature. The only difference is it first
searches for a note with the current date as the title, and if it finds it it
opens that instead of creating a new one. I trigger this shortcut with
cmd+ctrl+s.</p>
<p><a href="/images/shortcut.png"><img src="/images/shortcut.png" alt="My daily scratch note shortcut" /></a></p>
<p>After a second or two, a note that looks like this opens up on my screen:</p>
<p><a href="/images/note.png"><img src="/images/note.png" alt="A daily scratch note" /></a></p>
<h2 id="bear"><a class="anchor" href="#bear">#</a>
Bear</h2>
<p>Other than being a beautiful demonstration of not implementing every single
feature your user base asks for, the primary thing Bear excels at in my workflow
is TODO management.</p>
<p>At any point in any note, you can create a TODO. This manifests as a list item
with a checkbox, much like GitHub’s TODOs. You can have as many TODOs as you
want in a note, and Bear has a section of its navigation menu that will show you
all notes with outstanding TODOs.</p>
<p><a href="/images/note-with-todo.png"><img src="/images/note-with-todo.png" alt="A daily scratch note with TODOs" /></a></p>
<h2 id="conclusion"><a class="anchor" href="#conclusion">#</a>
Conclusion</h2>
<p>I’ve been using this new system for about a week now, which is longer than I’ve
been able to stick with any other note taking system. Nothing else has ever felt
as natural to me as this does.</p>
<p>The key outcome, though, is that I feel more on top of things now. I’m not
dropping the ball on things people ask me to do in meetings. People don’t have
to chase me for things as much, which makes me feel good and I’m sure it makes
them feel good as well.</p>