I started learning Elixir a few months ago, mostly through hacking on Papercups. I'm ashamed to say most of my Elixir education has been through trial and error, figuring things out as I go along. So this past week I decided to take some time off from Papercups to go a bit deeper into the language.

In particular, I was itching to learn more about handling concurrency in Elixir. This, of course, led me to GenServers.

Anyone who's moderately familiar with Elixir has probably at least heard of GenServer. (If you haven't, that's ok too!) Strictly speaking, it's one of those things you can get away with avoiding for a while, and still be reasonably productive while using something like the Phoenix framework.

But it's an incredible useful feature of the language that you can add to your toolbox! There are so many nice things you can do with it. For example, with GenServers, you can:

A GenServer is a process, just like any other Elixir process. It can be used to manage state and execute code asynchronously, and includes some handy functionality around tracing and error reporting.

Another way of thinking about it — a GenServer is like an isolated little box, with a few things inside of it:

If that's confusing, hopefully a real-world example will help clarify things! 😉

While learning about GenServers, I found many of the examples to be a bit contrived... a lot of "key-value" stores, shopping lists, and things like that. I wanted to understand how GenServers are being used "out in the wild", so I turned to one of my favorite Elixir open-source repos: https://github.com/plausible/analytics

A bit of background: Plausible is an open-source analytics platform, with >1000 paying customers. Because they are a real-world production application serving many users, I figured I could learn a lot from diagnosing one of their modules. 🤓

For the sake of this post, I'm going to go through one of the simpler GenServers in their repo: Plausible.Event.WriteBuffer, which can be found at /lib/plausible/event/write_buffer.ex

At a high level, this GenServer allows Plausible to insert large quantities of events into the database in batches, rather than doing an insertion for each individual event. (This seems particularly important for a product like Plausible's, where they are potentially ingesting thousands of events per second!)

At the code level, what this process is doing is essentially:

(For the full context, let's take a look at the codebase: we can see that the Plausible.Event.WriteBuffer GenServer is used in the PlausibleWeb.Api.ExternalController.event/2 method, which handles the logic for the POST /api/event API endpoint, which in turn gets called from their JavaScript tracking snippet.)

Before we jump into it, here's the full module, with some minor stylistic tweaks and annotations I've added myself for some additional context:

Note: If you're able to read the code above and know exactly what's going on at every step, you probably don't need to read any further!

Let's start off where the core of the logic actually lives: in the GenServer callbacks.

The init/1 callback is invoked when the GenServer is started, and handles setting the initial internal state of the server process.

Here's the method definition from above:

The first line of this method (Process.flag(:trap_exit, true)) sets us up to "trap exits". This allows us to handle any "clean-up" tasks before the process terminates, in the terminate/2 callback. (We'll discuss this below!)