request path
Walk of GET /alice/stats with Redis on. platform="codechef". CodeChef is the sibling that also keeps an in-memory TTLCache of parsed profiles, so a Redis miss can still skip the HTML scrape.
- CORS middleware (added first, runs second).
CacheRateLimitMiddlewarewithplatform="codechef"(added last, runs first).- Skip check. Path is not
/,/docs,/redoc,/openapi.json,/favicon.ico. Method is GET. Redis is on. - Handle is the first path segment, lowercased:
alice. Deprecated aliases (/profile/alice,/heatmap/alice) still usealice. - Cache key is
cache:codechef:{sha256(GET:/alice/stats:sorted_query)}. - On HIT, body is base64-decoded and returned with
X-Cache: HIT. Done. The HTML scrape never runs. - Negative key
invalid:codechef:alice. On HIT, HTTP 404User does not exist,X-Cache: NEGATIVE-HIT. Invalid-user rate limits apply (10/IP and 5/handle per 10 minutes). - Live limits: 60 req/min per IP, 30 req/min per handle. Over: 429 with
Retry-Afterand exponential backoff 5s to 300s. - Route calls
fetch_codechef_profile. That function checksprofile_cache(in-memoryTTLCache, 300s, 256 entries) beforeGET https://www.codechef.com/users/alice. stats_from/make_envelope. HTTP 200 cached in Redis for 3600s unlessCache-Controlsays otherwise.X-Cache: MISS.
Without Redis, steps 5 to 8 disappear. The in-memory profile cache still works inside one process. Restart uvicorn and you scrape again. Rate limits disappear too.
Every data router also declares Depends(enforce_rate_limit). That dependency returns None. Live limiting is the Redis middleware. CODECHEF_RATE_LIMIT_REQUESTS is a leftover settings field.
sequenceDiagram
participant B as Browser
participant MW as CacheRateLimit
participant R as Redis
participant RT as stats route
participant M as TTLCache
participant CC as codechef.com
B->>MW: GET /alice/stats
MW->>R: GET cache:codechef:digest
alt hit
R-->>MW: body
MW-->>B: 200 X-Cache HIT
else miss
MW->>R: GET invalid:codechef:alice
MW->>R: INCR rl:ip and rl:handle
MW->>RT: call_next
RT->>M: profile_cache.get alice
alt memory hit
M-->>RT: parsed profile
else scrape
RT->>CC: GET /users/alice
RT->>M: profile_cache.set
end
RT-->>MW: envelope
MW->>R: SETEX cache 3600s
MW-->>B: 200 X-Cache MISS
endIP is X-Forwarded-For first hop, else X-Real-IP, else request.client.host.
/playground is not in SKIP_PATHS. With Redis on, the first path segment is treated as handle playground. /dashboard becomes handle dashboard the same way.
Invalid-user markers: user does not exist, user not found, not found on, invalid username. A 404 from CodeChef (CodeChef user not found) matches. Empty data with status: "error" on a miss is cached as a ghost so you do not hammer CodeChef for handles that never existed.
Tag cache is a second TTLCache (6h, 4096 entries) for practice-problem tags. Also process-local.
Redis env#
Pydantic prefix is CODECHEF_, so CODECHEF_REDIS_URL is the settings field. The field default also reads raw REDIS_URL at import, which is how a shared .env still works. Redis errors fail open.
In-memory profile cache does not need Redis. It uses cache_ttl_seconds default 300 and cache_max_entries default 256. Redis HTTP TTL is the family default 3600.
| Env | Default |
|---|---|
API_CACHE_TTL_SECONDS |
3600 (Redis HTTP body) |
INVALID_USER_CACHE_TTL_SECONDS |
300 |
RATE_LIMIT_IP_REQUESTS |
60 per 60s |
RATE_LIMIT_HANDLE_REQUESTS |
30 per 60s |
INVALID_RATE_LIMIT_IP_REQUESTS |
10 per 600s |
INVALID_RATE_LIMIT_HANDLE_REQUESTS |
5 per 600s |
RATE_LIMIT_BACKOFF_BASE_SECONDS |
5 |
RATE_LIMIT_BACKOFF_MAX_SECONDS |
300 |
Keys:
cache:codechef:{sha256}invalid:codechef:{handle}rl:ip:codechef:{ip}/rl:handle:codechef:{handle}backoff:{same}/violations:{same}
Two layers, two clocks. Redis HIT never reaches TTLCache. Redis MISS with a warm TTLCache still skips the 120s scrape timeout.