DrawLintDrawLint.ai

Rate Limiter — system design by AgileViper46

Hire

Reviewed by 6 specialized AI reviewers. Explore the diagram and the full per-section feedback below.

Loading diagram…

Distributed Rate Limiting System Design Overview Clients send requests through the Application Gateway. The Rate Limiter runs inside the gateway layer to avoid additional network hops and enforce limits before requests reach downstream services. The system supports multiple levels of rate limits: User level: user123 -> 1000 requests / 5 minutes API level: /payments -> 50K requests/sec Tenant level: tenantA -> 1M requests/hour Global level: entire platform limit A request is allowed only if it passes all applicable rate limit checks. Token Bucket Algorithm For user/API/tenant limits we use the Token Bucket algorithm. Each bucket maintains: { "tokens": 500, "last_refilled_timestamp": 1690000000 } For every incoming request, the rate limiter executes a Redis Lua script. The Lua script: Calculates tokens generated since the last refill. Updates the bucket size up to the maximum capacity. Checks if tokens are available. Consumes one token if allowed. Updates the timestamp. Since Redis Lua scripts execute atomically, concurrent requests cannot overspend tokens. Example: user123: capacity = 1000 refill rate = 1000 / 5 minutes If tokens are available: ALLOW Otherwise: 429 Too Many Requests The response includes: X-RateLimit-Limit X-RateLimit-Remaining Retry-After Multiple Rule Evaluation A request can match multiple rules: User Rule: user123 -> 1000/min API Rule: /payments -> 50K/sec Tenant Rule: tenantA -> 1M/hour The rate limiter evaluates all applicable buckets. The request succeeds only if all checks pass. Global Rate Limiting A global rule cannot be enforced by calling Redis for every request because the global Redis key would become a bottleneck. Instead, we use a lease-based token allocation mechanism. The global Redis maintains the global bucket: Global limit: 5M requests/sec Rate limiters request token leases: Gateway-1 -> 10000 tokens Gateway-2 -> 10000 tokens Gateway-3 -> 10000 tokens Redis atomically deducts these tokens: global_tokens -= leased_tokens The gateway keeps the leased tokens in memory and performs local rate limiting. Redis is only contacted when the lease is exhausted. Preventing Global Token Over Allocation Each lease has: lease_id region allocated_tokens expiry_time Lease allocation happens atomically in Redis. Redis ensures: available_tokens + active_leases <= global_capacity A lease has a short expiry window. If a gateway crashes with unused tokens, those tokens become invalid after expiry and are returned to the available pool. This prevents one failed region from permanently holding global quota. Handling Hot Keys A large tenant or popular API key can become a hot key. Example: Region traffic: 300K RPS tenant123: 200K RPS All requests targeting: tenant123 would hit the same Redis bucket. To avoid this: Requests for the same tenant are routed using consistent hashing to the same gateway. The gateway maintains local token state. High-volume tenants use the same lease mechanism as global limits, where quota is distributed to gateways. This removes Redis from the hot request path. Rule Management and Consistency Rules are stored in ETCD. The control plane updates ETCD, and gateways maintain watches on rule changes. Each rule contains a version: rule: { limit: 500, version: 25 } When a new version is published, gateways atomically replace their local rule snapshot. During propagation, some gateways may temporarily have an older version. To maintain consistency, every request is evaluated against the gateway's latest available snapshot, and stale gateways refresh their configuration when they detect version lag. For critical rule changes, gateways can force-refresh rules before allowing traffic. Regional Architecture and Scaling Assume: Total traffic: 1M RPS Regions: 4 Peak region traffic: 300K RPS A gateway instance handles: 20K RPS Required gateways per region: 300K / 20K = 15 gateways Each region has: API Gateway + Rate Limiter fleet Redis cluster ETCD cluster Redis capacity: Single Redis: 100K operations/sec For 300K regional traffic: 4-5 Redis nodes per region are deployed for capacity and redundancy. Global Redis Network Partition Handling The global Redis is only used for quota allocation, not every request. If a region loses connectivity to global Redis: Existing leases continue to work. The region consumes already allocated tokens. New leases cannot be acquired. To avoid unlimited usage, each region has a predefined emergency quota. After connectivity is restored: Region reports consumption. Global quota state is reconciled. New leases are adjusted. This allows availability while still maintaining approximate global enforcement. Circuit Breaker A circuit breaker exists between the rate limiter and Redis. States: Closed: Redis healthy Open: Redis failing Half-open: Send limited test requests If Redis latency increases or failures occur: Circuit opens. Rate limiter uses cached rules and fallback limits. Periodically checks Redis health. Returns to normal after recovery.

Hire SignalHire

Across NFR, API, Entities, and HLD, the design demonstrates strong architectural judgment and appropriate distributed systems trade-offs for the stated requirements and scale. It is not fully polished at the senior bar because several important contracts and failure semantics remain ambiguous, but the core architecture is sound and clearly above a merely workable design.

⭐ Excellent

NFRs are tied to concrete mechanisms

The candidate does more than list low latency and availability goals: they connect them to specific choices such as embedding the rate limiter in the gateway to avoid extra hops, using Redis Lua for atomic decisions, and using local leases for global limits to keep Redis off the hot path. That shows the non-functional targets are actually driving the design.

✅ Good

Consistency trade-off is acknowledged for cross-datacenter enforcement

For the cross-datacenter/global limit, the design explicitly chooses approximate enforcement via leased tokens, short expiries, and reconciliation after partitions. That is a reasonable consistency/availability trade-off for a globally distributed rate limiter and is better than implying strict global consistency without explaining the cost.

✅ Good

Numbers are connected to stated scale assumptions

The explanation uses the provided 1M RPS assumption, breaks it down by region, estimates per-gateway throughput, and derives regional fleet size and Redis capacity from those assumptions. The numbers are not floating in isolation.

warning

Latency target is stated but not defended end-to-end

You mention a sub-10ms decision target, but what happens when a request must evaluate multiple buckets and one of them still requires a Redis round trip under load? Without an end-to-end latency budget per step and a p99 target, it is hard to tell whether the design still meets the NFR during peak traffic, failover, or hot-key scenarios. You could improve this by explicitly budgeting gateway processing, local checks, Redis access, and config lookup behavior at p95/p99.

warning

Availability target is not mapped to failure scenarios

You state 99.99% availability and mention circuit breaking and emergency quota, but what happens when a regional Redis cluster is unavailable, ETCD watches lag, or a gateway loses both Redis and fresh config at the same time? The design says fail-open/fail-closed is dynamic, but it does not define which limits choose which mode or how that preserves the availability target without violating enforcement too badly. You could improve this by specifying failure policies per rule type and the expected behavior during Redis, config, and cross-region outages.

warning

Consistency model is only partially specified

The design explains eventual consistency for rule propagation and approximate consistency for global quotas, but what happens if a critical rule change must take effect immediately across all gateways? 'Force-refresh for critical changes' is directionally good, yet the consistency contract is still unclear: are per-user/API/tenant limits strongly enforced within a region, eventually enforced across regions, and globally approximate? Making that explicit would clarify what correctness guarantees clients and operators can rely on.

info

One listed NFR is not really a non-functional target

Proper rate-limit headers are important, but they are more of a functional/API behavior requirement than a system quality attribute like latency, availability, or consistency. You could strengthen the NFR section by replacing that item with an operational quality target such as config propagation delay, p99 decision latency, or bounded over-admission during partitions.

✅ Good

Core nouns for the main flow are identified

The design clearly names Clients and Rules as central domain concepts, which are the two primary entities needed to decide whether an incoming request should be allowed or rejected.

✅ Good

Rule scopes are modeled explicitly

The explanation distinguishes user-level, API-level, tenant-level, and global rules. That shows the candidate understands that a single request can be governed by multiple rule types rather than a single flat limit.

warning

Request identity dimensions are not modeled as entities or relationships

Have you considered how a request maps to user, API, and tenant at the domain level? The requirements are specifically per-user, per-API, and per-tenant, but the entity list only has Clients and Rules. Without explicitly modeling those dimensions, it is unclear how rules attach to the right subject and how one request resolves all applicable limits.

warning

Relationship between clients and rules is underspecified

What happens when one client is subject to multiple limits at once, such as a tenant rule plus an API rule plus a user rule? The design says all applicable rules are evaluated, but the core entities section does not define whether this is one-to-many, many-to-many, or how rule applicability is determined. Making that relationship explicit would remove ambiguity in the happy path.

info

Ephemeral rate-limit state could be called out separately from configuration

You could improve this by distinguishing static Rule configuration from runtime Bucket/Counter state. The explanation clearly uses token buckets, leases, and active consumption state, which are important domain concepts even if they are not long-lived business entities. Naming them would make the model easier to reason about.

✅ Good

Basic scale numbers are present and tied to deployment sizing

The candidate gives concrete inputs such as 1M RPS, 4 regions, 300K peak regional traffic, 20K RPS per gateway, and 100K ops/sec per Redis node, then uses them to estimate gateway and Redis fleet size. That shows the right capacity-planning instinct instead of naming infrastructure without load assumptions.

✅ Good

Hot-key mitigation is justified by scale

The design explicitly recognizes that a large tenant at 200K RPS would overload a single shared Redis bucket and introduces local leasing/consistent routing to remove Redis from the hot path. This is a scale-driven component choice rather than a generic optimization.

✅ Good

Global quota leasing reduces central bottlenecks

For cross-datacenter enforcement, the candidate avoids putting a single global store on the per-request path and instead uses lease allocation. That is a sensible capacity trade-off for the stated 1M RPS because it shifts most traffic to local memory and keeps the global coordination path much lower volume.

warning

Per-request operation count is not carried through to Redis sizing

Have you considered what happens when each request matches multiple rules? At 300K regional RPS, a request may need user + API + tenant checks, which can turn into roughly 900K bucket evaluations/sec before retries or config lookups. The Redis estimate of 4-5 nodes per region appears to assume one operation per request, so the fleet could be undersized once multi-rule evaluation is included. You could improve this by translating request mix into average bucket checks per request and sizing Redis from that derived ops/sec.

warning

No storage or memory sizing for 100K rules and active buckets

What happens when the working set of active user, tenant, and API buckets grows during peak traffic? The design names ETCD and Redis, but there is no estimate for rule storage, in-memory rule snapshots on gateways, active token-bucket cardinality, TTL behavior, or replication overhead. Without that, it is hard to tell whether the chosen clusters fit the stated 100K rules and 10M unique clients. You could improve this by estimating active bucket count per region, bytes per bucket, retention/TTL, and replicated memory footprint.

warning

Cross-datacenter capacity trade-off is not quantified

Have you considered what happens to global accuracy and quota utilization when multiple regions hold leases at the same time? Lease-based allocation is directionally correct, but the design does not quantify lease size, refill frequency, or worst-case overshoot/underutilization during failures or partitions. At 1M RPS, those parameters materially affect both correctness and the load on the global Redis. You could improve this by showing how lease size is chosen from target coordination QPS and acceptable burst error.

info

Peak versus average assumptions could be made more explicit

You could strengthen the capacity story by stating whether 1M RPS is global peak, sustained peak, or average, and by carrying a headroom factor into node counts. Right now the arithmetic is understandable, but adding a simple peak/headroom assumption would make the infrastructure sizing more defensible.

✅ Good

Core decision API is simple and usable

The evaluate(request) interface covers the main functional path for a rate limiter: the caller can ask for an allow/deny decision and receive 429 semantics plus standard rate-limit headers. For an inline gateway rate limiter, a single decision endpoint or call is an appropriate API surface.

✅ Good

Client-facing throttling headers are included

Returning X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After shows awareness of how clients consume rate-limit decisions. That makes the API more practical because callers can back off instead of treating throttling as an opaque failure.

warning

Request contract is underspecified for multi-dimensional limits

Have you considered what the client or gateway must send into evaluate(request) so the system can actually enforce per-user, per-API, and per-tenant rules? Without a clear request shape carrying identifiers like user ID, tenant ID, API key/path, and possibly method, different callers may compute different keys and the same request could be evaluated inconsistently. Define the required attributes and how missing identities are handled.

warning

Multiple-rule failures are ambiguous to clients

What happens when a request violates more than one applicable rule, such as both tenant and API limits? The current response only says OK or 429 with generic headers, but it is unclear which limit the headers refer to. Clients may see misleading remaining/reset values if several buckets were checked. You should define whether headers represent the most restrictive rule, all matched rules, or a specific canonical rule, and keep that behavior consistent.

warning

Error behavior beyond 429 is not defined

What does the client see when Redis is unavailable, rule config is missing, or the gateway cannot evaluate the request? The explanation discusses circuit breaking and fallback behavior internally, but the API contract does not say whether the caller gets fail-open allow, fail-closed deny, 503, or some structured error body. At senior level, the client-visible behavior during dependency failures should be explicit so integrators know whether to retry or surface an outage.

warning

Retry guidance is incomplete for throttled clients

Have you considered how clients should retry after a 429? Retry-After is listed, which is good, but the contract does not say whether it is seconds vs HTTP-date, how it is computed when several rules apply, or whether clients can safely retry exactly at reset time. A clearer retry contract avoids synchronized retries and makes the API easier to consume correctly.

info

Make protocol and transport explicit

You could improve this by stating whether evaluate(request) is an internal synchronous RPC, an HTTP endpoint, or a library call inside the gateway. Since this section is about API routes, naming the transport and showing one concrete example request/response would make it much easier to judge status codes, headers, and integration behavior.

⭐ Excellent

Thoughtful cross-datacenter enforcement strategy

The design recognizes that a single global counter on the hot path would collapse under load and instead uses lease-based quota allocation from global Redis with local in-memory enforcement. That is a strong trade-off for the stated requirement of cross-datacenter limiting at 1M RPS because it reduces global coordination while still keeping approximate global control.

✅ Good

Dynamic configuration propagation is wired into the architecture

Using ETCD as the source of truth and pushing updates to rate limiter instances via watches gives the system a concrete path for dynamic rule changes without requiring synchronous config lookups on every request.

✅ Good

Hot-key risk is explicitly addressed

The candidate calls out that large tenants or popular APIs can create hot Redis keys and proposes moving those cases to local leased quota on gateways. That shows awareness of the first scaling pain point rather than assuming Redis will absorb all traffic.

critical

Main request path is not connected end-to-end in the HLD

What happens when a client request reaches the API Gateway? In the diagram there is no connection from Api-Gateway to the Rate-limiter, and no path from the rate limiter back to the gateway or downstream decision point. The explanation says the limiter runs inside the gateway, but the HLD does not show that integration clearly, so the request flow does not actually complete on the drawing.

warning

Cross-datacenter correctness depends on approximate reconciliation

Have you considered what happens if multiple regions lose connectivity to global Redis at the same time and continue serving from emergency quota? The system stays available, but global limits can be materially exceeded until reconciliation. That may be an acceptable trade-off, but the HLD should make that approximation explicit and bound the overshoot.

warning

Redis-global is a likely bottleneck and failure concentration point

What happens when lease refresh traffic spikes across all gateways or Redis-global becomes slow? Even though it is off the per-request path, all regions still depend on it for replenishment of global and hot-tenant leases. Without clear sharding/replication/failover strategy for Redis-global, this becomes the first shared choke point for cross-DC enforcement.

warning

ETCD topology across regions is unclear

Have you considered what happens if each region has its own ETCD cluster and rule updates race or diverge? The diagram shows an ETCD cluster in each region, but the explanation treats rules as a single source of truth. If these are independent clusters, config consistency becomes fragile; if they are one logical cluster stretched across regions, write latency and quorum behavior need to be considered.

info

Local Redis may still sit on the hot path for non-leased keys

You could improve this by being explicit about which limits are always checked in local Redis versus cached or locally enforced. At 1M RPS, even regional Redis can become the first bottleneck if most user/API/tenant checks still require synchronous Redis Lua calls.

info

Some drawn components are underexplained in the flow

You could strengthen the HLD by showing how the control plane reaches both regions and by clarifying whether Redis-local is only for per-key buckets while in-memory state handles leased quotas. Right now several components are present, but the exact ownership of each request path is left to the explanation rather than the architecture itself.

Want this kind of feedback on your own design?

Draw your architecture for Rate Limiter and get an instant hire/no-hire signal from 6 specialized AI reviewers — free to start.