The Quest Begins (The "Why")

I was building a tiny rate‑limiter for a side‑project API. The idea was simple: every request writes a row with user_id and requested_at (a timestamp) into a rate_limit_log table, and before allowing the request we count how many rows exist for that user in the last minute. If the count exceeds the limit we reject the request.

At first it felt like a breeze. I threw together a quick migration, wrote the query, and hit “run”. The first few requests flew by—until the traffic grew. Suddenly each request started taking hundreds of milliseconds, and the API began to choke. My logs showed the same query over and over:

SELECT COUNT(*)

FROM rate_limit_log