Files
foxhunt/services/api_gateway/RATE_LIMITER_IMPLEMENTATION.md
jgrusewski 8b81138262 docs: rewrite outdated READMEs and add web-gateway docs
Rewrite 7 crate READMEs to reflect current architecture: correct
model types (DQN/PPO/TFT/Mamba2), AtomicKillSwitch, real
EnsembleConfig source from ml, actual data crate purpose,
web-dashboard project details, ml_training_service ports.

Fix 5 api_gateway/TLI docs: strip swarm agent framing, update
service endpoints to api_gateway:50050, remove deleted dashboard
references and hardcoded paths.

Add missing web-gateway/README.md documenting 24 REST endpoints,
WebSocket support, JWT auth, and 3-tier rate limiting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:39:12 +01:00

9.4 KiB

Rate Limiter Implementation

Note: HTTP-level rate limiting for the web dashboard is now handled by web-gateway (3 tiers: auth 10/min, trading 200/min, compute 30/min). This document describes the gRPC-level rate limiting in api_gateway.

Overview

High-performance token bucket rate limiting system with Redis backend and in-memory caching optimized for HFT requirements.

Architecture

Token Bucket Algorithm

The rate limiter uses the token bucket algorithm which provides:

  • Smooth rate limiting - requests consume tokens from a bucket
  • Burst handling - bucket capacity allows short bursts up to limit
  • Automatic refill - tokens refill at a constant rate over time

Two-Tier Architecture

┌─────────────────────────────────────────────────────────┐
│                   Rate Limiter                          │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  1. In-Memory Cache (LRU, 10,000 entries)              │
│     └─ HashMap<String, TokenBucket>                     │
│     └─ Target: <50ns per check (CACHE HIT)            │
│     └─ TTL: 1 second                                   │
│                                                          │
│  2. Redis Backend (Lua scripts)                        │
│     └─ Atomic token bucket operations                  │
│     └─ Target: <500μs per check (CACHE MISS)          │
│     └─ Distributed rate limiting across instances      │
│                                                          │
└─────────────────────────────────────────────────────────┘

Performance Characteristics

Measured Performance

Based on standalone benchmarks:

Operation Target Measured Status
Cache Hit <50ns 25ns ✓ EXCELLENT
Token Bucket N/A 625ns
Burst Handling N/A 41ns/req
HFT Scenario N/A 27ns/check

Performance Breakdown

  1. In-Memory Cache Hit: 25-41ns

    • HashMap lookup: ~5ns
    • Token bucket check: ~10ns
    • Refill calculation: ~10ns
    • Lock overhead: ~10ns
  2. Redis Backend: <500μs (estimated)

    • Network RTT: ~100-200μs (same AZ)
    • Lua script execution: ~50-100μs
    • Serialization: ~50μs
    • Connection pool overhead: ~50μs

Implementation Details

Rate Limit Configurations

Pre-configured limits for common endpoints:

// Trading endpoints (high frequency)
trading.submit_order:
  - Capacity: 100 tokens
  - Refill rate: 100 tokens/second
  - Burst size: 10 requests

// Configuration updates (moderate frequency)
config.update:
  - Capacity: 10 tokens
  - Refill rate: 10 tokens/second
  - Burst size: 2 requests

// Backtesting (low frequency)
backtesting.run:
  - Capacity: 5 tokens
  - Refill rate: 5 tokens/60 seconds (5/min)
  - Burst size: 1 request

// Default (for unlisted endpoints)
default:
  - Capacity: 50 tokens
  - Refill rate: 50 tokens/second
  - Burst size: 5 requests

Redis Lua Script

Atomic token bucket implementation in Redis:

local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

-- Get current bucket state
local bucket = redis.call('HGETALL', key)
local tokens = capacity
local last_refill = now

-- Parse existing state
if #bucket > 0 then
    for i = 1, #bucket, 2 do
        if bucket[i] == 'tokens' then
            tokens = tonumber(bucket[i + 1])
        elseif bucket[i] == 'last_refill' then
            last_refill = tonumber(bucket[i + 1])
        end
    end
end

-- Refill tokens based on elapsed time
local elapsed = now - last_refill
tokens = math.min(capacity, tokens + (elapsed * refill_rate))

-- Check if request is allowed
if tokens >= 1 then
    tokens = tokens - 1
    redis.call('HSET', key, 'tokens', tokens, 'last_refill', now)
    redis.call('EXPIRE', key, 300)  -- 5 minute TTL
    return 1
else
    redis.call('HSET', key, 'tokens', tokens, 'last_refill', now)
    redis.call('EXPIRE', key, 300)
    return 0
end

LRU Cache Management

// Cache configuration
max_cache_size: 10,000 entries
cache_ttl: 1 second

// Eviction policy
- When cache reaches 10,000 entries
- Evict oldest 10% (1,000 entries)
- Based on last_access timestamp
- Automatic on cache miss

Integration Points

AuthInterceptor Integration

// In services/api_gateway/src/auth/interceptor.rs
impl AuthInterceptor {
    pub async fn intercept(&self, request: Request<()>) -> Result<Request<()>, Status> {
        // ... JWT validation ...

        // Layer 6: Rate Limiting (<50ns)
        let endpoint = extract_endpoint(&request);
        if !self.rate_limiter
            .check_limit(&claims.user_id, endpoint)
            .await
            .map_err(|_| Status::internal("Rate limit check failed"))?
        {
            return Err(Status::resource_exhausted("Rate limit exceeded"));
        }

        // ... continue processing ...
    }
}

Configuration Loading

Rate limit configurations are loaded from PostgreSQL:

-- Example: Update rate limit for endpoint
UPDATE rate_limit_config
SET capacity = 200, refill_rate = 200
WHERE endpoint = 'trading.submit_order';

-- Triggers PostgreSQL NOTIFY for hot-reload
NOTIFY config_updates, 'rate_limit_config';

Testing

Unit Tests

# Run rate limiter unit tests
cargo test -p api_gateway rate_limiter

# Expected output:
# test rate_limiter::tests::test_token_bucket_basic ... ok
# test rate_limiter::tests::test_token_bucket_refill ... ok
# test rate_limiter::tests::test_rate_limit_configs ... ok

Benchmarks

# Run performance benchmarks
rustc services/api_gateway/benches/rate_limiter_bench.rs -O && ./rate_limiter_bench

# Expected output:
# Cache hit: 25 ns (target <50ns) ✓
# Token bucket: 625 ns ✓
# Burst handling: 41 ns ✓
# HFT scenario: 27 ns ✓

Integration Testing

# Start Redis for testing
docker run -d -p 6379:6379 redis:7-alpine

# Run integration tests (when implemented)
cargo test -p api_gateway --test rate_limiter_integration

Redis Setup

Development

# Docker
docker run -d -p 6379:6379 --name foxhunt-redis redis:7-alpine

# Or local Redis
redis-server --port 6379

Production

# Environment variables
export REDIS_URL="redis://redis-cluster.internal:6379"
export RATE_LIMITER_CACHE_SIZE=10000
export RATE_LIMITER_CACHE_TTL_SECONDS=1

Monitoring

Metrics to Track

  1. Cache Hit Rate

    • Target: >95% hit rate
    • Monitor: cache_hits / (cache_hits + cache_misses)
  2. Latency Distribution

    • p50: <30ns (cache hits)
    • p95: <100ns (cache hits)
    • p99: <1μs (Redis hits)
  3. Rate Limit Violations

    • Track denied requests per endpoint
    • Alert on unusual patterns

Cache Statistics

// Get current cache statistics
let stats = rate_limiter.get_cache_stats().await;
println!("Cache size: {}/{}", stats.size, stats.max_size);
println!("Cache TTL: {} seconds", stats.ttl_seconds);

Future Enhancements

  1. Dynamic Configuration

    • Load rate limits from PostgreSQL config table
    • Hot-reload on configuration changes via NOTIFY/LISTEN
  2. Advanced Metrics

    • Prometheus metrics integration
    • Per-endpoint rate limit statistics
    • User-level quota tracking
  3. Sliding Window Algorithm

    • Optional sliding window rate limiting
    • More accurate but slightly higher overhead
  4. Distributed Cache

    • Redis as primary cache (shared across instances)
    • Local in-memory as L2 cache
    • Eventual consistency acceptable for rate limiting

Files Created

services/api_gateway/src/routing/
├── mod.rs                          # Module exports
└── rate_limiter.rs                 # Token bucket implementation

services/api_gateway/benches/
└── rate_limiter_bench.rs          # Performance benchmarks

docs/
└── RATE_LIMITER_IMPLEMENTATION.md # This file

Performance Validation

Benchmark Results

Rate Limiter Performance Benchmarks
========================================

Benchmark 1: Cache Hit Performance (in-memory)
Total time: 25.167394ms
Operations: 1000000
Time per operation: 25 ns
Target: <50ns ✓

Benchmark 2: Token Bucket Refill Overhead
Total time: 62.599479ms
Operations: 100000
Time per operation: 625 ns
(includes 10μs sleeps every 100 operations)

Benchmark 3: Burst Handling (100 requests at once)
Total time: 4.177µs
Allowed requests: 100/100
Average per request: 41 ns

Benchmark 4: HFT Scenario (10,000 requests, 100 req/sec limit)
Total time: 272.411µs
Allowed: 100/10000 requests
Denied: 9900 requests
Average per check: 27 ns

========================================
Performance Summary:
  - Cache hit: 25 ns (target <50ns) ✓
  - Token bucket: 625 ns ✓
  - Burst handling: 41 ns ✓
  - HFT scenario: 27 ns ✓

Status

Implementation complete. Key files:

  • services/api_gateway/src/routing/rate_limiter.rs - Token bucket implementation
  • services/api_gateway/benches/rate_limiter_bench.rs - Performance benchmarks
  • Unit tests included in rate_limiter.rs