Decision record

ADR-0006: Rate-limit counters live in RTDB

Written by

ADR-0006: Rate-limit counters live in RTDB

Status: accepted · 2026-09-04

Context

The MCP service limits each token to 120 requests a minute, and /developers says so. Until now the counter was a Map in the process, so on Cloud Run the real limit was 120 × the number of instances serving that token — an unknown number that changes with load. Phase 10 §4 called this out; Phase 11 §9 asks for a durable counter before a second integrator builds against the figure.

Bladbase does not run Redis (CLAUDE.md), so the candidates were the stores it has: Firestore, RTDB, or a Cloud Tasks/Pub/Sub contrivance.

Decision

A fixed window per token — rateLimits/{tokenId}/{windowNumber} — incremented by an RTDB transaction, on the same Admin SDK client the change signal uses. The first hit of a window removes the previous one, so a key never holds more than two small nodes. Clients can neither read nor write the path.

The limiter fails open to a per-instance count: a transaction that does not commit within 1.5 s, or throws, is answered from a local window and the decision is marked shared: false, which the service logs. Every response carries x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset; a refusal is 429 with retry-after.

Why not Firestore

A single hot document tops out near one sustained write a second, which is below the rate the limiter exists to police. Sharding the counter across documents trades that for a read fan-out on every request, for a value that is thrown away sixty seconds later. Firestore is for durable metadata; this is not that.

Why RTDB does not break its own rule

RTDB is reserved for ephemeral awareness and is never a source of truth. A rate window is ephemeral by construction and nothing durable is derived from it; when it is missing, the service degrades to the limit it had before this ADR, not to a wrong answer. The path is server-written only, like changes.

Consequences

References

Written and maintained by , who made this decision — about · GitHub.

← All decisions