Module 5: Reliability at Scale
Project: Reliability Design Document
Overview
Up to here you've seen the 6 pillars of reliability for AI: load balancing with deep health checks, queue-based processing, technical and budget rate limiting, circuit breakers that detect degradation, retry with idempotency, and graceful degradation. Each one separately.
This project integrates them. You're going to produce a complete Reliability Design Document applied to a concrete case. It's the deliverable a Tech Lead would present at a design review, or that your future self would consult when something goes down in production.
It's not code — it's design. But the design must be concrete enough that a team can implement it with confidence. Diagrams, numbers, justified decisions, not empty abstractions.
By the end of the project you'll have:
- A complete Reliability Design Document in Markdown (5-10 pages)
- Diagrams showing the request flow and the reliability components
- A table of applied patterns with case-by-case justification
- A basic runbook for common incidents
The case to design
System: an AI-Powered Customer Support for a B2B SaaS.
- Users: 200 customer support agents across 5 tenant companies
- Function: agents write queries, the RAG system over the client's internal documentation returns a suggested response
- Traffic: 600 queries/hour peak (10/min), 100/hour average
- SLA: P95 < 8s, availability 99.5% monthly (allows ~3.5h/month of accumulated downtime)
- Current stack:
- API: FastAPI on 3 EC2 instances with a load balancer
- LLM: OpenAI gpt-4o-mini (primary provider)
- Vector DB: Pinecone (managed)
- Cache: Redis Cluster
- Constraints:
- Budget: $1,500/month on OpenAI tokens
- The enterprise client (one of the 5) demands that no downtime exceed 30 consecutive minutes
Document template
Your deliverable is a Markdown with this structure. Fill each section with specific decisions for the case above.
1. Executive summary (½ page)
# Reliability Design — AI Customer Support Assistant
## Summary
This document describes the reliability strategy of the AI Customer Support
Assistant. It covers the 6 pillars: load balancing, queue-based processing,
rate limiting, circuit breakers, retry and idempotency, and graceful degradation.
The system is designed to meet an SLA of P95 <8s and 99.5% monthly
availability, within a budget of $1,500/month on LLM tokens.
The decisions prioritize: (1) a consistent experience for support agents
(tolerable latency, no visible failures), (2) budget protection
(avoid bursts that drain the budget), (3) automatic recovery (less
human intervention).
2. Architecture overview (1 page)
A diagram showing the main components and the flow of a request.
Agent (browser)
│
▼
Load Balancer (L7, AWS ALB)
┌───────┼───────┐
▼ ▼ ▼
API-1 API-2 API-3 (FastAPI, 3 instances)
│ │ │
└───────┼───────┘
▼
Redis Queue
│
▼
Workers (4 instances)
│
├─→ Pinecone (vector search)
├─→ OpenAI (LLM, with circuit breaker)
└─→ Anthropic (fallback with circuit breaker)
│
▼
Result store (Redis)
│
▼
Agent (poll or WebSocket)
For each component, a one-line purpose.
3. Load balancing (½ page)
Decisions:
- Type: Layer 7 (AWS Application Load Balancer)
- Strategy: least-connections (variable latencies 2-30s across queries)
- Health check: deep (
/ready) with verification of OpenAI + Pinecone + Redis, cached 30s - Sticky sessions: NO (stateless API)
- Drain time: 60s (kubernetes terminationGracePeriodSeconds)
4. Queue-based processing (1 page)
Flow design:
| Step | Component | Target latency |
|---|---|---|
| Agent sends a query | Browser → ALB → API | <100ms |
| API validates + enqueues | API → Redis | <200ms |
| API responds 202 with job_id | API → Agent | <300ms total |
| Worker dequeues | Redis → Worker | immediate |
| Worker does RAG + LLM | Worker → Pinecone + OpenAI | 3-25s |
| Worker stores result | Worker → Redis result store | <100ms |
| Agent receives result | Polling every 1s or WebSocket | <1s after the previous step |
Worker calculation:
- Peak: 10 queries/min = 0.167 q/s
- Average duration: 12s
- Utilization target: 70%
- Workers needed:
(0.167 × 12) / 0.7 ≈ 2.9 → round up to 4
Notification to the agent: polling (simpler for this case, scales well with 200 agents).
5. Rate limiting (1 page)
Multi-level:
| Level | Limit | Algorithm | Justification |
|---|---|---|---|
| Per agent (user) | 30 queries/hour | Token bucket | Fairness between agents, prevents one agent from saturating |
| Per tenant (company) | 200 queries/hour | Token bucket | Fairness between clients, scales with the plan |
Per endpoint /search | 5 q/s global | Token bucket | Protects general capacity |
| OpenAI (vendor) | 600 RPM (of the 10K available) | Centralized token bucket | Margin for spikes, respects the OpenAI tier |
| OpenAI budget | $1500/month = $0.0006/s sustained | Cents bucket | Hard budget cap |
Behavior when the limit is reached:
- Per agent / tenant: hard reject 429 with
Retry-After: 60 - Per global endpoint: queue (defer, don't reject)
- Budget: if <10% remaining in the month, degrade to Cache-First mode before hard reject
6. Circuit breakers (1 page)
Three configured circuits:
| Circuit | Downstream | Thresholds | Cooldown |
|---|---|---|---|
openai_primary | OpenAI gpt-4o-mini | error_rate >50%, P95 >15s, 30 req minimum | 60s |
anthropic_fallback | Claude 3 Haiku | error_rate >50%, P95 >20s | 90s |
pinecone_search | Pinecone | error_rate >30%, P95 >3s | 30s |
Fallback cascade when a circuit is open:
openai_primary OPEN
↓
try anthropic_fallback
↓ (also open)
try cache (Redis)
↓ (cache miss)
canned response by category
↓ (no category matches)
degraded message
Distributed state in Redis: all instances share the circuit's state.
7. Retry and idempotency (½ page)
Retry per layer:
| Layer | Max retries | Backoff base | Jitter |
|---|---|---|---|
| OpenAI call | 3 | 2s exponential | Full jitter |
| Anthropic call | 3 | 2s exponential | Full jitter |
| Pinecone | 2 | 200ms exponential | Full jitter |
| Redis | 2 | 100ms | Full jitter |
Idempotency:
- Each job has a
job_idUUID - The API checks the client's optional
idempotency_key; maps it to ajob_id - The worker checks the job's status before processing (skip if already processed)
- DLQ after 5 total retries per job
DLQ:
- A separate queue
dlq:queries - Alert to Slack #ops when it grows
- Manual processing with a replay script
8. Graceful degradation (1 page)
4 levels:
| Level | Trigger | Behavior | UI message |
|---|---|---|---|
| N0 Normal | — | Normal flow | (nothing) |
| N1 Slow | OpenAI P95 >6s for 1min | Timeout up to 45s, UI shows "processing..." | "Processing your query..." |
| N2 Cache-first | Circuit openai_primary open OR queue >30 sustained | Cache hit if it exists → Anthropic → canned | "Fast response" badge |
| N3 Degraded | Both LLM circuits open OR budget <5% remaining | Canned response by detected category | "Limited operation, try again in more detail or later" |
| N4 Service down | Pinecone down OR Redis down | Only static info, queue everything for deferred processing | "Technical problems, status: [link]" |
Transitions:
- Going up: 30s sustained under the level's conditions
- Coming down: 5 min sustained without the level's conditions
- Manual override available for operators
9. Monitoring and alerts (½ page)
Mandatory metrics:
- Queue depth (alert if >50 sustained for 2min)
- DLQ size (alert if >0)
- Circuit state (alert if any circuit is open >5min)
- LLM P95 latency (alert if >10s sustained)
- LLM error rate (alert if >10% sustained)
- Budget consumption rate (alert if the projection is >100% of the monthly)
- Degradation level (alert when it goes up from N0→N1+)
10. Basic runbook (1 page)
| Symptom | Investigation | Action |
|---|---|---|
| High queue depth | Check workers status, down? | Restart workers, scale up |
openai_primary open | Check the OpenAI status page | Verify the fallback works; if OpenAI is prolonged, consider lowering the budget threshold |
| DLQ >0 | Inspect the jobs in the DLQ | Categorize errors, replay the recoverable ones manually |
| Budget >80% spent by mid-month | Check if there's abuse from a tenant | Notify the tenant, consider a stricter throttle |
| System enters N3 for no clear reason | Degradation manager logs | Verify the circuits aren't "stuck" open |
11. Appendices
- A: Redis configuration (ports, cluster topology)
- B: Rate limiting Lua scripts (the ones from the exercise in M5-04)
- C: pybreaker configuration / circuit state schema in Redis
- D: List of canned responses by category (initial knowledge base)
How to work on this project
I suggest this order:
- Skim the template (5 min): familiarize yourself with the sections.
- Sketch the diagram (15 min): draw the main flow by hand or with tldraw.
- Decisions per section (1.5-2 hrs): complete each section with concrete numbers for the case.
- Cross-consistency (15 min): check that the degradation levels are consistent with the defined triggers, that the rate limits respect the budget, that the circuits and queues are aligned.
- Runbook (15 min): think "if this goes down at 3am, what do I do?"
Total: ~3 hours of work.
Evaluation criteria
To ensure your document is production-ready, self-assess:
- The architecture diagram is legible and shows all the reliability components
- Each decision has a justification (not just "I use queues" but "queues because we have 5-30s of latency and a 3s UI budget")
- There are concrete numbers (how many workers, how many RPS, what thresholds) — not generic
- It covers the 6 pillars of M5 (load balancing, queues, rate limiting, circuits, retry, degradation)
- The runbook describes responses to at least 5 different scenarios
- Metrics and alerts are automatable (what to measure, what threshold, what to notify)
- A new team member could implement the system following this document
Example of a complete page: section 6 (Circuit Breakers)
To calibrate the level of detail, this is what a well-done section looks like:
## 6. Circuit Breakers
### 6.1 Configured circuits
Three independent circuits, one for each critical downstream:
| Circuit | Downstream | Tracked metrics | Thresholds | Cooldown |
|---------|-----------|------------------|----------|----------|
| `openai_primary` | OpenAI gpt-4o-mini | error rate, P95 latency, timeout rate, empty responses | error >50% OR P95 >15s OR timeouts >30%, min 30 reqs | 60s + jitter ±15s |
| `anthropic_fallback` | Claude 3 Haiku | error rate, P95 latency | error >50% OR P95 >20s, min 20 reqs | 90s + jitter ±15s |
| `pinecone_search` | Pinecone vector search | error rate, P95 latency | error >30% OR P95 >3s, min 50 reqs | 30s + jitter ±5s |
### 6.2 Distributed state
Each circuit's state is stored in Redis (`circuit:{name}:state` and
`circuit:{name}:events`). All worker instances query the same
state, avoiding each worker maintaining its own independent view.
### 6.3 Fallback cascade
When a request requires the LLM, the order of attempts is:
1. **openai_primary** — whenever the circuit is CLOSED
2. **anthropic_fallback** — if openai_primary is OPEN or fails
3. **Redis cache** (`cache:response:{hash(prompt)}`) — if both LLM circuits are open
4. **Canned response** by category — classifies the prompt with keywords, returns a template
5. **Degraded message** — last option
This cascade is implemented in `worker.py`, function `process_query()`.
### 6.4 Monitoring
Metrics exported to Prometheus:
- `circuit_state{name="X"}` — gauge (0=closed, 1=half-open, 2=open)
- `circuit_transitions_total{name="X", from="Y", to="Z"}` — counter
- `circuit_rejections_total{name="X"}` — counter (how many requests rejected by the circuit)
- `circuit_fallback_usage_total{from="X", to="Y"}` — counter
Alerts:
- Severity HIGH: any circuit OPEN >5min
- Severity MEDIUM: a circuit with >3 openings in the last hour
- Severity LOW: a circuit uses the fallback >20% of the time
That level of detail per section is what we expect.
Evidence of success by the end of M5
You'll know you finished well if you can:
- ✅ Deliver the complete document (all sections filled in)
- ✅ Defend every number you used (e.g., "why 4 workers?" → show the calculation)
- ✅ Explain the complete fallback cascade
- ✅ Show where the single points of failure are (ideally: none critical without mitigation)
- ✅ Resolve at least 3 runbook scenarios without re-thinking the architecture
Connection with the rest of the guide
This Reliability Design Document isn't discarded at the end of M5. It's the reliability section of the Capstone Architecture Design (M8). When you reach M8, you take this document, adapt it to the Knowledge Assistant system of the Capstone (Slack/Discord, Agentic RAG), and integrate it with the scaling strategy (M3), integration patterns (M4), trade-off decisions (M7).
Work done well here saves work later on.
Next module
Module 6 — Real-World Integrations with Slack/Discord. Reliability designed in the abstract. Now we land it on concrete production channels: Slack and Discord. Each one has specific constraints (timeouts, rate limits, OAuth, message format) that require adapting your generic patterns.
Resources
- Architecture Decision Records template — for documenting decisions.
- Diagrams as Code — Python lib to generate reproducible diagrams.
- tldraw — quick sketch of diagrams if you prefer by hand.
- Google SRE Workbook — for runbooks and postmortems.
- Incident Response Cheat Sheet — professional runbook format.