Hierarchical rate limiting with Redis sorted sets

· 8 min

Recently at work, we ran into this problem:

We needed to send Slack notifications for specific events but had to enforce rate limits to avoid overwhelming the channel. Here’s how the limits worked:

  • Global limit: Max 100 requests every 30 minutes.
  • Category limit: Each event type (e.g., errors, warnings) capped at 10 requests per 30 minutes.

Now, imagine this:

  1. There are 20 event types.
  2. Each type hits its 10-notification limit in 30 minutes.
  3. That’s 200 requests total, but the global limit only allows 100. So, 100 requests must be dropped - even if some event types still have room under their individual caps.

This created a hierarchy of limits:

Dynamic shell variables

· 4 min

I came across a weird shell syntax today - dynamic shell variables. It lets you dynamically construct and access variable names in Bash scripts, which I haven’t encountered in any of the mainstream languages I juggle for work.

In an actual programming language, you’d usually use a hashmap to achieve the same effect, but directly templating variable names is a quirky shell feature that sometimes comes in handy.

A primer

Dynamic shell variables allow shell scripts to define and access variables based on runtime conditions. Variable indirection (${!var} syntax) lets you reference the value of a variable through another variable. This can be useful for managing environment-specific configurations and function dispatch mechanisms.

Link blog in a static site

· 3 min

One of my 2025 resolutions is doing things that don’t scale and doing them faster without overthinking. The idea is to focus on doing more while worrying less about scalability and sustainability in the things I do outside of work. With that in mind, I’ve been thinking for a while about tracking some of my out-of-band activities on this blog. The goal is to:

  • List the things I do, books and articles I read, and talks I grok.
  • Add some commentary or quote something I liked from the content, verbatim, for posterity.
  • Not spam people who just want to read the regular articles.
  • Not turn into a content junkie, churning out slop I wouldn’t want to read myself.

This isn’t about getting more eyeballs on what I publish. It’s about tracking what I do so I can look back at the end of the year and enjoy a nice little lull of accomplishment. Plus, having a place to post stuff regularly nudges me to read more, explore more, and do more of the things I actually want to do.

Running only a single instance of a process

· 4 min

I’ve been having a ton of fun fiddling with Tailscale over the past few days. While setting it up on a server, I came across this ufw firewall script that configures the firewall on Linux to ensure direct communication across different nodes in my tailnet. It has the following block of code that I found interesting (added comments for clarity):

#!/usr/bin/env bash

# Define PID file path using script's name for uniqueness
PIDFILE="/tmp/$(basename "${BASH_SOURCE[0]%.*}.pid")"

# Open file descriptor 200 for the PID file
exec 200>"${PIDFILE}"

# Try to acquire a non-blocking lock; exit if the script is already running
flock -n 200 \
    || {
        echo "${BASH_SOURCE[0]} already running. Aborting..."; exit 1;
    }

# Store the current process ID (PID) in the lock file for reference
PID=$$
echo "${PID}" 1>&200

# Do work (in the original script, real work happens here)
sleep 999

Here, flock is a Linux command that ensures only one instance of the script runs at a time by locking a specified file (e.g., PIDFILE) through a file descriptor (e.g., 200). If another process already holds the lock, the script either waits or exits immediately. Above, it bails with an error message and exit code 1.

Function types and single-method interfaces in Go

· 6 min

People love single-method interfaces (SMIs) in Go. They’re simple to implement and easy to reason about. The standard library is packed with SMIs like io.Reader, io.Writer, io.Closer, io.Seeker, and more.

One cool thing about SMIs is that you don’t always need to create a full-blown struct with a method to satisfy the interface. You can define a function type, attach the interface method to it, and use it right away. This approach works well when there’s no state to maintain, so the extra struct becomes unnecessary. However, I find the syntax for this a bit abstruse. So, I’m jotting down a few examples here to reference later.

SSH saga

· 4 min

Setting up SSH access to a new VM usually follows the same routine: generate a key pair, copy it to the VM, tweak some configs, confirm the host’s identity, and maybe set up an agent to avoid typing passphrases all day. Tools like cloud-init and Ansible handle most of the setup for me now, so I rarely think about it. But I realized I don’t fully understand how all the parts work together.

Injecting Pytest fixtures without cluttering test signatures

· 2 min

Sometimes, when writing tests in Pytest, I find myself using fixtures that the test function/method doesn’t directly reference. Instead, Pytest runs the fixture, and the test function implicitly leverages its side effects. For example:

import os
from collections.abc import Iterator
from unittest.mock import Mock, patch
import pytest


# Define an implicit environment mock fixture that patches os.environ
@pytest.fixture
def mock_env() -> Iterator[None]:
    with patch.dict("os.environ", {"IMPLICIT_KEY": "IMPLICIT_VALUE"}):
        yield


# Define an explicit service mock fixture
@pytest.fixture
def mock_svc() -> Mock:
    service = Mock()
    service.process.return_value = "Explicit Mocked Response"
    return service


# IDEs tend to dim out unused parameters like mock_env
def test_stuff(mock_svc: Mock, mock_env: Mock) -> None:
    # Use the explicit mock
    response = mock_svc.process()
    assert response == "Explicit Mocked Response"
    mock_svc.process.assert_called_once()

    # Assert the environment variable patched by mock_env
    assert os.environ["IMPLICIT_KEY"] == "IMPLICIT_VALUE"

In the test_stuff function above, we directly use the mock_svc fixture but not mock_env. Instead, we expect Pytest to run mock_env, which modifies the environment variables. This works, but IDEs often mark mock_env as an unused parameter and dims it out.

Explicit method overriding with @typing.override

· 2 min

Although I’ve been using Python 3.12 in production for nearly a year, one neat feature in the typing module that escaped me was the @override decorator. Proposed in PEP 698, it’s been hanging out in typing_extensions for a while. This is one of those small features you either don’t care about or get totally psyched over. I’m definitely in the latter camp.

In languages like C#, Java, and Kotlin, explicit overriding is required. For instance, in Java, you use @Override to make it clear you’re overriding a method in a sub class. If you mess up the method name or if the method doesn’t exist in the superclass, the compiler throws an error. Now, with Python’s @override decorator, we get similar benefits - though only if you’re using a static type checker.

Quicker startup with module-level __getattr__

· 4 min

This morning, someone on Twitter pointed me to PEP 562, which introduces __getattr__ and __dir__ at the module level. While __dir__ helps control which attributes are printed when calling dir(module), __getattr__ is the more interesting addition.

The __getattr__ method in a module works similarly to how it does in a Python class. For example:

class Cat:
    def __getattr__(self, name: str) -> str:
        if name == "voice":
            return "meow!!"
        raise AttributeError(f"Attribute {name} does not exist")


# Try to access 'voice' on Cat
cat = Cat()
cat.voice  # Prints "meow!!"

# Raises AttributeError: Attribute something_else does not exist
cat.something_else

In this class, __getattr__ defines what happens when specific attributes are accessed, allowing you to manage how missing attributes behave. Since Python 3.7, you can also define __getattr__ at the module level to handle attribute access on the module itself.

Docker mount revisited

· 5 min

I always get tripped up by Docker’s different mount types and their syntax, whether I’m stringing together some CLI commands or writing a docker-compose file. Docker’s docs cover these, but for me, the confusion often comes from how “bind” is used in various contexts and how “volume” and “bind” sometimes get mixed up in the documentation.

Here’s my attempt to disentangle some of my most-used mount commands.

Volume mounts

Volume mounts let you store data outside the container in a location managed by Docker. The data persists even after the container stops. On non-Linux systems, volume mounts are faster than bind mounts because data doesn’t need to cross the virtualization boundary.