Split Why GenServer into two slides: what it is and why we use it

Torey Heinz committed Feb 22, 2026
commit ff1a6febe11f32cb1ea8aa5044dec8ff9d85ba00
Showing 1 changed file with 26 additions and 27 deletions
slides/03-real-world.html +26 -27
@@ @@ -186,37 +186,36 @@ end
</aside>
</section>
+ <section>
+ <h2>What's a GenServer?</h2>
+ <p>A <strong>long-running process</strong> that holds state and handles messages one at a time.</p>
+ <ul>
+ <li class="fragment">Think of it as a <strong>tiny server inside your app</strong> with its own memory</li>
+ <li class="fragment">Other code sends it messages &mdash; it processes them <strong>sequentially</strong></li>
+ <li class="fragment">It stays alive for the lifetime of your application</li>
+ <li class="fragment">If it crashes, the supervisor restarts it automatically</li>
+ </ul>
+
+ <aside class="notes">
+ Before we talk about why we used a GenServer, let's explain what one is. A GenServer is a long-running process that holds state and processes messages one at a time. Think of it like a tiny server living inside your app. Other code sends it messages, it handles them sequentially — so there are no race conditions by design. It stays alive as long as your app is running, and if it crashes, the supervisor brings it right back. For the JS folks, think of a Web Worker with a built-in message queue. For Rails folks, think of a background process that's in-memory and automatically restarted.
+ </aside>
+ </section>
+
<section>
<h2>Why GenServer?</h2>
- <table>
- <thead>
- <tr>
- <th>Approach</th>
- <th>Problem</th>
- </tr>
- </thead>
- <tbody>
- <tr class="fragment">
- <td>Redis counter (Rails)</td>
- <td>Network round-trip for every email, eventual consistency</td>
- </tr>
- <tr class="fragment">
- <td>In-memory variable (Node.js)</td>
- <td>Race conditions under concurrent access</td>
- </tr>
- <tr class="fragment">
- <td>Database counter</td>
- <td>Way too slow at 500k emails</td>
- </tr>
- <tr class="fragment highlight-row">
- <td><strong>GenServer (Elixir)</strong></td>
- <td><strong>In-process, atomic, zero network overhead</strong></td>
- </tr>
- </tbody>
- </table>
+ <p class="muted small">Why not Redis, a database, or an in-memory variable?</p>
+ <ul>
+ <li class="fragment"><strong>In-process</strong> &mdash; no network round-trips like Redis</li>
+ <li class="fragment"><strong>No race conditions</strong> &mdash; messages processed one at a time, by design</li>
+ <li class="fragment"><strong>Fast</strong> &mdash; no database writes, no serialization overhead</li>
+ <li class="fragment"><strong>Supervised</strong> &mdash; crashes and restarts automatically with fresh state</li>
+ </ul>
+ <p class="fragment" style="font-size: 0.8em; margin-top: 0.8em; color: #555;">
+ Saves a network call for <strong>every single send</strong><br> at 500k emails, that's a lot of saved round-trips.
+ </p>
<aside class="notes">
- Why a GenServer instead of alternatives? Redis adds a network round-trip for every single email. An in-memory variable in Node has race conditions. A database counter is way too slow at this scale. A GenServer is in-process, atomic, and has zero network overhead. Messages are processed one at a time, so there are no race conditions by design.
+ So why a GenServer for the rate limiter? It's in-process — no network hop to Redis for every email. No race conditions because messages are sequential. It's fast — no database writes, no serialization. And it's supervised — if something goes wrong, the supervisor restarts it. At 500k emails, a Redis round-trip per email would add serious latency. The GenServer handles it all in-memory with zero overhead.
</aside>
</section>