RiftRelay is a sophisticated rate-limiting proxy for the Riot Games API built in Go. It intelligently manages API rate limits to prevent throttling while maximizing throughput.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/renja-g/RiftRelay/llms.txt
Use this file to discover all available pages before exploring further.
Request Flow
Every request through RiftRelay follows this path:Path Validation
The router parses incoming requests in the format
/{region}/{riot-api-path} and validates the region and upstream path (internal/router/path.go:35).Admission Control
Requests are queued by bucket and wait for admission based on available rate limit capacity (internal/limiter/limiter.go:43).
Proxying
Once admitted, the reverse proxy forwards the request to the Riot API with the appropriate API token (internal/proxy/proxy.go:113).
Project Structure
RiftRelay is organized into focused packages with clear responsibilities:main.go
Entry point with signal handling for graceful shutdown
internal/app
Server lifecycle, HTTP routing, and middleware setup
internal/config
Environment-based configuration loading and validation
internal/router
Path parsing, validation, and bucket key generation
internal/limiter
Core admission control, queueing, and rate limit tracking
internal/proxy
Reverse proxy implementation and admission middleware
internal/transport
HTTP transport configuration and retry logic
internal/metrics
Prometheus metrics collection (optional)
Component Interaction
Here’s how the major components work together:1. Server Initialization (internal/app/server.go:24)
When RiftRelay starts:2. Request Processing (internal/proxy/admission.go:34)
Each incoming request:- Router parses the path and extracts region + bucket
- Admission middleware requests admission from the limiter
- Limiter queues the request and schedules it based on rate limits
- Proxy forwards the request with the appropriate API key
- ModifyResponse observes rate limit headers from the response
3. Rate Limit State Management (internal/limiter/state.go:82)
The limiter maintains:- Per-key state: Separate tracking for each API token
- Per-region app limits: Application-level rate limits by region
- Per-bucket method limits: Method-level rate limits by endpoint pattern
- Pacing state: Last granted time to spread requests evenly
Key Design Decisions
Why spreading instead of bursting?
Why spreading instead of bursting?
Instead of sending all requests immediately when rate limits reset, RiftRelay spreads them evenly across the window. This prevents sudden load spikes on Riot’s API and reduces the chance of hitting secondary rate limits.The pacing algorithm calculates the next available slot:See internal/limiter/state.go:46 for implementation details.
Why separate high and normal priority queues?
Why separate high and normal priority queues?
Each bucket maintains two queues (internal/limiter/heap.go:11):
- High priority: Bypasses pacing delays but still respects rate limits
- Normal priority: Uses spreading to smooth out traffic
Why track state per-key and per-region/bucket?
Why track state per-key and per-region/bucket?
Riot’s API has two levels of rate limits:
- Application limits: Apply to all requests from a key in a region
- Method limits: Apply to specific endpoint patterns
Why use a single-threaded event loop?
Why use a single-threaded event loop?
The limiter uses a single goroutine event loop (internal/limiter/limiter.go:84) that processes:
- Admission requests
- Rate limit observations from responses
- Timer events for scheduled wakeups
Concurrency Model
RiftRelay uses structured concurrency:The limiter runs a single event loop goroutine that owns all mutable state. Other components communicate via channels:
admitCh: Admission requests from the proxyobserveCh: Rate limit observations from responsescloseCh: Shutdown signal
http.Server. Each blocks on admission before proceeding to proxy the request.
Graceful Shutdown
When a SIGINT or SIGTERM is received (main.go:24):- HTTP server stops accepting new connections
- Existing requests complete within
SHUTDOWN_TIMEOUT - Limiter rejects queued requests with
shutting_downerror (internal/limiter/limiter.go:128) - Server exits cleanly
Next Steps
Rate Limiting
Learn how RiftRelay tracks and enforces rate limits
Queue System
Understand admission control and request queueing