System Design for 10M Users: Practical Architecture Guide
Learn system design for 10m users through concrete architecture trade-offs, failure modes, rollout controls, and production measurement practices.
Muhammad Abdul Sami
· Updated · 9 min read
- RAG
- Embeddings
- Vector Databases
- Evaluation
Table of Contents:
- What 10M Users Actually Means
- Step 1: Estimate Traffic and Storage
- Step 2: Draw the High-Level Architecture
- Step 3: Load Balancing and Stateless Services
- Step 4: Caching Strategy (CDN, Redis, Application)
- Step 5: Database Scaling (Read Replicas, Sharding)
- Step 6: Async Processing with Message Queues
- Step 7: Observability and SLOs
- Step 8: Failure Modes and Resilience
- Complete Architecture Diagram
- Frequently Asked Questions
What 10M Users Actually Means
Short answer: System design for 10M users requires architecting for roughly 500–2,000 requests per second peak, terabytes of storage, and failure of any single component without user-visible downtime — not just "add more servers."
If you searched "system design 10 million users", you are preparing for scale that breaks naive architectures: single PostgreSQL instances, synchronous email sends, unbounded session state, and missing cache layers. At HinterBuild, we walk through this exact progression with every backend API engineering client before they hit growth inflection points.
Key Takeaways:
- 10M registered users ≈ 1–2M DAU ≈ 500–2K RPS peak for typical SaaS
- Stateless API servers + Redis sessions enable horizontal scaling
- Read replicas handle 80% of database load; sharding comes at ~50M+ rows per hot table
- Async queues decouple write-heavy operations (email, analytics, search indexing)
- Design for failure — any component must be replaceable without downtime
This guide walks through system design for 10M users step by step, with concrete numbers, technology choices, and code patterns used in production 2026 architectures.
Step 1: Estimate Traffic and Storage
Before drawing boxes, quantify the problem. Interviewers and architects both start here.
User Assumptions (Typical B2B SaaS)
| Metric | Conservative | Aggressive |
|---|---|---|
| Registered users | 10M | 10M |
| Daily active users (DAU) | 10% = 1M | 20% = 2M |
| Requests per DAU per day | 50 | 200 |
| Peak/average traffic ratio | 5× | 10× |
Calculate Peak RPS
Daily requests = DAU × requests per user
= 1,000,000 × 50 = 50M requests/day
Average RPS = 50M / 86,400 ≈ 580 RPS
Peak RPS = 580 × 5 ≈ 2,900 RPS
Plan infrastructure for 3,000 RPS peak with 2× headroom → design target 6,000 RPS capacity.
Storage Estimates
| Entity | Rows (10M users) | Avg row size | Total |
|---|---|---|---|
| Users | 10M | 2 KB | 20 GB |
| Sessions/events (1yr) | 500M | 500 B | 250 GB |
| User-generated content | 50M | 5 KB | 250 GB |
| Total (year 1) | ~520 GB |
Add 3× for indexes, replicas, and backups → ~1.5 TB PostgreSQL footprint. Manageable on a well-tuned single primary with read replicas — sharding not yet required.
Compare runtime efficiency in our Go vs Python backend benchmarks when sizing API pods.
Step 2: Draw the High-Level Architecture
At 10M users, your architecture has distinct layers:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Clients │────▶│ CDN / WAF │────▶│ Load Balancer│
│ Web + Mobile │ │ (static) │ │ (ALB/NLB) │
└──────────────┘ └──────────────┘ └──────┬───────┘
│
┌─────────────────────────────┼─────────────────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ API Pod │ │ API Pod │ │ API Pod │
│ (stateless) │ (stateless) │ (stateless)
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
┌─────────┼────────────────────────────┼────────────────────────────┼─────────┐
▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌──────────┐ ┌──────────────┐
│ Redis │ │ Postgres│ │ SQS/SNS │ │ Elasticsearch│
│ (cache) │ │ primary │ │ (queues) │ │ (search) │
└─────────┘ └────┬────┘ └──────────┘ └──────────────┘
│
┌─────┴─────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ Replica 1│ │ Replica 2│
└──────────┘ └──────────┘
Deploy this on cloud infrastructure with multi-AZ redundancy. Each layer scales independently.
Step 3: Load Balancing and Stateless Services
Stateless API servers are the foundation of horizontal scaling. No session data in memory, no local file storage, no sticky requirements unless unavoidable.
Stateless API Checklist
- Sessions stored in Redis, not server memory
- File uploads go directly to S3 via presigned URLs
- No in-process caches that can't be invalidated cluster-wide
- Idempotent handlers for retry safety
- Health checks on
/healthand/ready(DB + Redis connectivity)
Load Balancer Configuration
| Setting | Value | Reason |
|---|---|---|
| Algorithm | Round robin or least connections | Even distribution |
| Health check interval | 10s | Fast unhealthy detection |
| Deregistration delay | 30s | Drain in-flight requests |
| Idle timeout | 120s | Support streaming responses |
| TLS termination | At LB | Centralized cert management |
Auto-Scaling Rules
// Example: AWS Auto Scaling policy (conceptual)
const scalingPolicy = {
metric: "CPUUtilization",
targetValue: 60,
scaleOutCooldown: 60, // seconds
scaleInCooldown: 300,
minCapacity: 3, // multi-AZ minimum
maxCapacity: 30,
};
Start with 3 API pods minimum (one per AZ). Scale out at 60% CPU, scale in slowly to avoid flapping. At 3,000 RPS with 500 RPS per pod, you need 6–8 pods at peak — size pods based on your framework benchmarks.
Step 4: Caching Strategy (CDN, Redis, Application)
Caching is the highest-ROI optimization in system design for 10M users. A three-tier cache eliminates 80%+ of database reads.
Three-Tier Cache Architecture
| Tier | Technology | TTL | Use Case |
|---|---|---|---|
| L1: CDN | CloudFront/Fastly | 1h–24h | Static assets, public API responses |
| L2: Redis | ElastiCache/MemoryDB | 1m–1h | Session, user profile, hot lists |
| L3: Application | In-process LRU | 10s–60s | Config, feature flags, reference data |
Redis Cache-Aside Pattern
import redis.asyncio as redis
import json
cache = redis.from_url("redis://cache-cluster:6379")
async def get_user(user_id: str) -> dict:
cache_key = f"user:{user_id}"
cached = await cache.get(cache_key)
if cached:
return json.loads(cached)
# Cache miss → database
user = await db.fetch_user(user_id)
await cache.setex(cache_key, 300, json.dumps(user)) # 5 min TTL
return user
Go equivalent:
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
key := "user:" + id
if val, err := s.redis.Get(ctx, key).Bytes(); err == nil {
var u User
json.Unmarshal(val, &u)
return &u, nil
}
u, err := s.repo.FindByID(ctx, id)
if err != nil {
return nil, err
}
data, _ := json.Marshal(u)
s.redis.SetEx(ctx, key, data, 5*time.Minute)
return u, nil
}
Cache Invalidation Rules
- Write-through for critical data (user permissions) — update cache on write
- TTL-based for eventually consistent data (analytics dashboards)
- Pub/sub invalidation for cluster-wide L3 bust: publish
invalidate:user:123on write
Avoid caching personalized responses at CDN unless keyed by user — use Redis instead.
Step 5: Database Scaling (Read Replicas, Sharding)
At 10M users, PostgreSQL with read replicas handles most workloads. Sharding is a future problem unless you have a single hot table exceeding 50M rows.
Read Replica Strategy
| Query Type | Route To | Example |
|---|---|---|
| Writes | Primary | INSERT, UPDATE, DELETE |
| Reads (tolerant of lag) | Replica | List pages, search, dashboards |
| Reads (must be fresh) | Primary | Post-login permissions, billing |
// Read/write routing in application layer
async function getDbConnection(mode: "read" | "write") {
const url = mode === "write"
? process.env.DATABASE_PRIMARY_URL
: process.env.DATABASE_REPLICA_URL;
return pool.connect(url);
}
Typical replica lag: 10–100ms. Acceptable for 95% of reads. Route payment and auth checks to primary.
Connection Pooling
Never connect API pods directly to PostgreSQL at scale. Use PgBouncer:
| Setting | Recommended | Why |
|---|---|---|
| Pool mode | Transaction | Best for web APIs |
| Max connections (Postgres) | 200–500 | RAM-limited |
| Pool size per pod | 20–50 | Prevent connection storms |
See PostgreSQL performance secrets for index and query tuning that matters more than replicas.
When to Shard (Not Yet at 10M)
Shard when:
- Single table exceeds 50–100M rows with hot access patterns
- Write throughput exceeds primary capacity (> 10K writes/sec)
- Replica lag consistently exceeds 500ms under normal load
At 10M users, partitioning by time (events table) often suffices before full sharding:
CREATE TABLE events (
id BIGSERIAL,
user_id UUID NOT NULL,
event_type TEXT NOT NULL,
payload JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
) PARTITION BY RANGE (created_at);
CREATE TABLE events_2026_09 PARTITION OF events
FOR VALUES FROM ('2026-09-01') TO ('2026-10-01');
Step 6: Async Processing with Message Queues
Synchronous request handlers must not perform slow operations. At 3,000 RPS, sending email, indexing search, or calling ML models inline will collapse your API.
Queue Architecture
API Handler ──▶ Write to DB ──▶ Publish event ──▶ Return 202/200
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
Email Worker Search Indexer Analytics Writer
| Operation | Sync or Async | Queue |
|---|---|---|
| User signup | Sync (create record) + Async (welcome email) | SQS/SNS |
| Search indexing | Async | Kafka/SQS |
| Analytics events | Async (batch) | Kinesis/Kafka |
| Payment webhooks | Sync (validate) + Async (fulfillment) | SQS |
| LLM inference | Async or streaming | Dedicated queue |
Idempotent Worker Pattern
from dataclasses import dataclass
import hashlib
@dataclass
class Event:
id: str
type: str
payload: dict
async def process_event(event: Event):
# Idempotency key prevents duplicate processing
idempotency_key = hashlib.sha256(
f"{event.type}:{event.id}".encode()
).hexdigest()
if await redis.setnx(f"processed:{idempotency_key}", "1"):
await redis.expire(f"processed:{idempotency_key}", 86400)
await handle_event(event)
else:
logger.info("Duplicate event skipped", event_id=event.id)
Build reliable data pipelines with dead-letter queues (DLQ) for failed messages. Monitor DLQ depth — sustained growth indicates a systemic failure.
Step 7: Observability and SLOs
You cannot scale what you cannot measure. System design for 10M users requires SLOs, not just dashboards.
Define SLOs First
| SLI | SLO Target | Measurement |
|---|---|---|
| Availability | 99.9% (8.7h downtime/yr) | Successful requests / total |
| Latency (p95) | < 200ms | API gateway metrics |
| Latency (p99) | < 500ms | API gateway metrics |
| Error rate | < 0.1% | 5xx / total requests |
| Queue processing | < 30s lag | Consumer offset lag |
Three Pillars Implementation
| Pillar | Tool (example) | What to Track |
|---|---|---|
| Metrics | Prometheus + Grafana | RPS, latency histograms, saturation |
| Logs | Structured JSON → Loki/CloudWatch | Request ID, user ID, error context |
| Traces | OpenTelemetry → Tempo/Jaeger | Cross-service latency breakdown |
// OpenTelemetry trace propagation (TypeScript API gateway)
import { trace, context } from "@opentelemetry/api";
app.use(async (req, res, next) => {
const span = trace.getTracer("api").startSpan(`${req.method} ${req.path}`);
req.span = span;
res.on("finish", () => span.end());
context.with(trace.setSpan(context.active(), span), next);
});
Our observability & monitoring practice deploys these stacks with alert runbooks tied to SLO burn rates — not just "CPU > 80%."
Alerting Hierarchy
- Page on-call: SLO burn rate > 10× (potential outage)
- Ticket: Error rate elevated but SLO intact
- Dashboard only: Capacity trending toward limits (plan scaling)
Step 8: Failure Modes and Resilience
At 10M users, failures are daily events — design for them.
Common Failure Modes
| Failure | Impact | Mitigation |
|---|---|---|
| Single API pod crash | None (LB routes around) | Multi-AZ, health checks |
| Redis cluster failover | 1–3s cache miss spike | Redis Sentinel/Cluster, graceful degradation |
| Primary DB failure | Write outage | Automated failover (Patroni/RDS Multi-AZ) |
| Replica lag spike | Stale reads | Route critical reads to primary |
| Queue backlog | Delayed emails/notifications | Auto-scale workers, DLQ alerts |
| Third-party API down | Feature degraded | Circuit breaker, cached fallback |
Circuit Breaker Pattern
import "github.com/sony/gobreaker"
var cb = gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "payment-api",
MaxRequests: 3,
Interval: 10 * time.Second,
Timeout: 30 * time.Second,
ReadyToTrip: func(counts gobreaker.Counts) bool {
return counts.ConsecutiveFailures > 5
},
})
func chargePayment(ctx context.Context, req PaymentRequest) error {
_, err := cb.Execute(func() (interface{}, error) {
return nil, paymentClient.Charge(ctx, req)
})
return err
}
Graceful Degradation
When Redis is down: skip cache, serve from database (slower but functional). When search index lags: return database results with a "results may be stale" flag. When ML service is down: disable AI features, core product still works.
Avoid API design mistakes that amplify failures — unbounded queries and missing timeouts turn partial outages into full ones.
Complete Architecture Diagram
Final system design for 10M users component list:
| Component | Technology | Count (peak) | Purpose |
|---|---|---|---|
| CDN | CloudFront | Global | Static assets, edge cache |
| WAF | AWS WAF | 1 | DDoS, bot protection |
| Load Balancer | ALB | Multi-AZ | Traffic distribution |
| API Servers | Go/FastAPI | 6–12 pods | Stateless business logic |
| Redis | ElastiCache | 3-node cluster | Cache + sessions |
| PostgreSQL | RDS/Aurora | 1 primary + 2 replicas | Persistent data |
| PgBouncer | Sidecar/container | Per AZ | Connection pooling |
| Message Queue | SQS/Kafka | Managed | Async processing |
| Workers | Python/Go | 4–8 pods | Email, search, analytics |
| Search | Elasticsearch | 3-node | Full-text search |
| Object Storage | S3 | — | Files, backups |
| Monitoring | Prometheus/Grafana | Managed | SLOs, alerting |
Estimated monthly cost (AWS, 2026): $8,000–$15,000 depending on data transfer and reserved instance commitments. Optimize with spot instances for workers and reserved capacity for database.
Capacity Planning Spreadsheet
Use this template when presenting system design for 10M users to stakeholders:
| Component | Unit capacity | Peak need | Instances | Headroom |
|---|---|---|---|---|
| API pod | 500 RPS | 3,000 RPS | 6 (+ 2 spare) | 33% |
| Redis node | 100K ops/s | 30K ops/s | 3 (cluster) | 70% |
| Postgres primary | 5K writes/s | 200 writes/s | 1 | 95% |
| Postgres replica | 20K reads/s | 2,800 reads/s | 2 | 30% |
| Worker pod | 500 events/s | 800 events/s | 4 | 25% |
Headroom below 30% on any tier triggers scale planning — not emergency firefighting. Review quarterly as user growth compounds.
Choose API runtime using framework benchmarks to calibrate "unit capacity" for your specific endpoints rather than generic hello-world numbers.
Primary references: official documentation, official documentation, official documentation, official documentation.
Frequently Asked Questions
How many servers do I need for 10M users?
For typical SaaS at 3,000 RPS peak: 6–12 API pods, 3-node Redis cluster, 1 PostgreSQL primary + 2 replicas, and 4–8 async workers. Exact sizing depends on your framework and runtime.
Do I need microservices at 10M users?
No. A well-structured modular monolith with async workers handles 10M users for most products. Split services when team size (> 20 engineers) or independent scaling requirements force it — not preemptively.
When should I shard my database?
When a single table exceeds 50–100M rows with performance degradation despite indexing, or write throughput exceeds primary capacity. At 10M users with good schema design, read replicas and partitioning usually suffice.
What is the most common scaling mistake?
Skipping caching. A Redis layer eliminates 80% of database load. The second most common: synchronous slow operations (email, search indexing) in request handlers.
How do I handle 10M WebSocket connections?
Dedicated connection servers (often Go), regional edge deployment, and Redis pub/sub for cross-node messaging. See streaming LLM production patterns for SSE/WebSocket architecture.
What SLO should I target at 10M users?
99.9% availability and p95 < 200ms are standard for B2B SaaS. Consumer apps may need 99.95%+. Define SLOs before choosing infrastructure — they drive every scaling decision.
Should I use Kubernetes at this scale?
Optional. Managed container services (ECS, Cloud Run) reduce operational overhead. Kubernetes makes sense with dedicated platform teams or multi-cloud requirements.
How do I practice system design interviews for 10M users?
Follow this eight-step framework: estimate traffic → draw architecture → load balance → cache → database → queues → observability → failure modes. Justify every component with numbers.
Conclusion
System design for 10M users is achievable with a disciplined progression: estimate load, keep API servers stateless, cache aggressively, replicate databases, async slow work, and observe everything.
- Start with numbers — RPS, storage, peak ratios
- Cache at CDN, Redis, and application layers
- Read replicas before sharding
- Queues for everything that isn't user-facing latency
- Design for failure — circuit breakers, graceful degradation, SLOs
At HinterBuild, we architect scalable systems from MVP to 10M+ users:
- Backend API Engineering
- Cloud Infrastructure & DevOps
- Observability & Monitoring
- Data Pipelines & Integrations
Schedule a consultation to review your architecture before scale breaks it.
Free consultation
Book a free consultation call on system design & scalable architecture
30-minute call with the HinterBuild team. Discuss your project, architecture questions, or next steps — no obligation.
Book a meeting
Keep reading
Related articles
Event-Driven Architecture: When to Use It and How to
Learn event-driven architecture through concrete architecture trade-offs, failure modes, rollout controls, and production measurement practices.
Read post
RAG Evaluation Without Ground Truth: Practical Guide
RAG evaluation without labeled data — LLM-as-judge, reference-free metrics, retrieval quality measurement, and production monitoring patterns.
Read post
Modular RAG: Interchangeable Components Architecture
Modular RAG guidance for engineers: compare architecture choices, avoid failure modes, and ship a measurable, reliable production implementation.
Read post
Monorepo vs Multi-Repo: Engineering Tradeoffs
Monorepo vs multi-repo comparison for repository strategy — with scaling patterns, CI/CD optimization, tooling analysis, and when to use each approach.
Read post
