Rate Limiting System โœ“ Complete

Status: Production Ready | Performance: <1ms per check | Tests: 8/8 passing

๐Ÿ“‘ Table of Contents

1. Overview

Multi-tier token bucket rate limiting with Redis backend. Protects API from abuse with graceful degradation and automatic retry.

Key Features

2. Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   Client    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚         Frontend Layer              โ”‚
โ”‚  โ€ข Rate limit handler utilities     โ”‚
โ”‚  โ€ข Toast notifications              โ”‚
โ”‚  โ€ข Automatic retry (3x, backoff)    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚ HTTP/gRPC
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚      Backend gRPC Server            โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚  Rate Limit Interceptor       โ”‚  โ”‚
โ”‚  โ”‚  (runs before auth)           โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚              โ–ผ                       โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚  Multi-Tier Check             โ”‚  โ”‚
โ”‚  โ”‚  1. Global  (10k/min)         โ”‚  โ”‚
โ”‚  โ”‚  2. Tenant  (1k/min)          โ”‚  โ”‚
โ”‚  โ”‚  3. User    (200/min)         โ”‚  โ”‚
โ”‚  โ”‚  4. Method  (varies)          โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚              โ”‚                       โ”‚
โ”‚              โ–ผ                       โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚  Auth Interceptor             โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚              โ–ผ                       โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚  Business Logic Handler       โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚
       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚    Redis    โ”‚
โ”‚  (Lua script)
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

3. Backend Implementation

Core Files

File Purpose Lines
backend/internal/middleware/ratelimit.go Token bucket algorithm, Redis operations 281
backend/internal/middleware/ratelimit_test.go 8 tests + benchmark 450
backend/cmd/server/main.go gRPC server integration Modified

Token Bucket Algorithm

// Redis Lua script (atomic operation)
tokens = redis.get(key)
elapsed = now - last_update
tokens = min(capacity, tokens + elapsed * rate)

if tokens >= 1:
    tokens -= 1
    redis.set(key, tokens, last_update=now)
    return ALLOW
else:
    return DENY

Backend Code Example

// Go - Initialize rate limiter
rateLimiter := middleware.NewRateLimiter(redisClient, &middleware.RateLimitConfig{
    GlobalRequestsPerMinute: 10000,
    GlobalBurstSize:         100,
    TenantRequestsPerMinute: 1000,
    TenantBurstSize:         50,
    UserRequestsPerMinute:   200,
    UserBurstSize:           20,
})

// Add to gRPC interceptor chain
grpcSrv := grpc.NewServer(
    grpc.ChainUnaryInterceptor(
        rateLimiter.Unary(),      // Rate limit FIRST
        authInterceptor.Unary(),  // Auth second
    ),
)

4. Frontend Integration

Core Files

File Purpose
frontend/src/lib/rate-limit-handler.ts Detection, retry, error parsing utilities
frontend/src/lib/connect-rpc-browser.ts gRPC interceptor with rate limit logging
frontend/src/lib/api/imports.ts REST API wrapper with automatic retry
frontend/src/components/ui/rate-limit-toast.tsx Toast notification with countdown
frontend/src/hooks/use-imports.ts React hooks with rate limit state

Automatic Retry

// TypeScript - Wrap API call with retry
import { withRateLimitRetry } from '@/lib/rate-limit-handler'

const result = await withRateLimitRetry(async () => {
  return await fetch('/api/some-endpoint', { method: 'POST' })
})
// Retries: 1s โ†’ 2s โ†’ 4s (exponential backoff)

Error Detection

// TypeScript - Check if error is rate limit
import { isRateLimitError, parseRateLimitError } from '@/lib/rate-limit-handler'

try {
  await apiCall()
} catch (error) {
  if (isRateLimitError(error)) {
    const parsed = parseRateLimitError(error)
    console.log(parsed.message)    // "Global rate limit exceeded..."
    console.log(parsed.retryAfter) // 5 (seconds)
  }
}

5. Rate Limits & Configuration

Default Limits

Tier Requests/Min Burst Redis Key
Global 10,000 100 ratelimit:global
Tenant 1,000 50 ratelimit:tenant:{uuid}
User 200 20 ratelimit:user:{uuid}

Method-Specific Limits

Method Requests/Min Burst Reason
AI Chat 100 10 Expensive LLM calls
File Upload/Extract 200 20 Large file processing
Bulk Operations 50 5 Heavy database writes
๐Ÿ’ก Enforcement Order: Global โ†’ Tenant โ†’ User โ†’ Method-specific. All must pass.

6. Usage Examples

Example 1: React Hook with Toast

import { useImportUpload } from '@/hooks/use-imports'
import { RateLimitToast, useRateLimitToast } from '@/components/ui/rate-limit-toast'
import { getRateLimitMessage } from '@/lib/rate-limit-handler'

function UploadPage() {
  const { uploadFile, isUploading, isRateLimited, retryAfter } = useImportUpload()
  const { toast, showToast, hideToast } = useRateLimitToast()

  const handleUpload = async (file: File) => {
    try {
      await uploadFile({ tenantId, file })
      // Success!
    } catch (err) {
      if (isRateLimited) {
        showToast(getRateLimitMessage(err), retryAfter)
      }
    }
  }

  return (
    <>
      <button onClick={handleUpload}>Upload</button>
      {toast.isVisible && <RateLimitToast {...toast} onClose={hideToast} />}
    </>
  )
}

Example 2: Direct API Call

import { ImportsAPI } from '@/lib/api/imports'

// Automatic retry built-in
try {
  await ImportsAPI.executeImport(tenantId, sessionId)
} catch (error) {
  // Already retried 3x, still failed
  console.error('Import failed after retries:', error)
}

Example 3: gRPC Call with Connect-RPC

import { accountingClientBrowser } from '@/lib/connect-rpc-browser'

try {
  const response = await accountingClientBrowser.getDashboardStats({ tenantId })
} catch (error) {
  // Rate limit logged by interceptor automatically
  // Handle error or show toast
}

7. Testing

Run Backend Tests

# All tests (requires Redis running)
cd backend
go test ./internal/middleware -v

# Specific test
go test ./internal/middleware -v -run TestRateLimiter_GlobalLimit

# Benchmark
go test ./internal/middleware -bench=. -benchtime=3s

# Expected output:
# BenchmarkRateLimiter_CheckLimit-10    18302    186925 ns/op
# PASS

Manual Testing - Trigger Rate Limit

# Make rapid requests to exceed burst
for i in {1..15}; do
  curl -X POST http://localhost:3000/api/imports/upload \
    -H "Authorization: Bearer $TOKEN"
done

# Expected:
# Requests 1-10: โœ… 200 OK (burst allowed)
# Requests 11-15: โš ๏ธ 429 Too Many Requests
# Frontend: Shows toast "Rate limit reached, retrying in 3s..."

Test Results

Test Status
TestRateLimiter_GlobalLimitโœ… PASS
TestRateLimiter_TenantLimitโœ… PASS
TestRateLimiter_TenantIsolationโœ… PASS
TestRateLimiter_MethodSpecificLimitโœ… PASS
TestRateLimiter_UserLimitโœ… PASS
TestRateLimiter_ResetLimitโœ… PASS
TestRateLimiter_GetRateLimitInfoโœ… PASS
TestRateLimiter_TokenRefillโœ… PASS

8. Monitoring & Admin

Admin Functions

// Go - Reset specific limit
err := rateLimiter.ResetLimit(ctx, "ratelimit:global")

// Reset tenant limit
err := rateLimiter.ResetTenantLimit(ctx, tenantID)

// Reset user limit
err := rateLimiter.ResetUserLimit(ctx, userID)

// Check remaining tokens
tokens, err := rateLimiter.GetRateLimitInfo(ctx, "ratelimit:tenant:uuid")
fmt.Printf("Remaining tokens: %.2f\n", tokens)

Redis Commands

# Check all rate limit keys
redis-cli --scan --pattern "ratelimit:*"

# View specific key
redis-cli HGETALL ratelimit:global

# Delete all rate limit keys (reset all)
redis-cli EVAL "return redis.call('del', unpack(redis.call('keys', 'ratelimit:*')))" 0

Monitoring Metrics

Metric How to Monitor
Rate limit hits Count gRPC errors with code ResourceExhausted (8)
Redis performance Monitor Lua script execution time (<1ms expected)
Retry attempts Frontend console logs: [Rate Limit] Attempt X/3
Token bucket state Use GetRateLimitInfo() to check tokens

Performance Characteristics

๐ŸŽฏ Production Notes:

Rate Limiting System | Complete Implementation | Nov 2025