Back🌐

Designing an AI Chat Service

AWAKENING0 / 100
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░]0%

Designing an AI Chat Service

High Level Design·advanced·~25 min read

  • system-design
  • ai
  • inference
  • streaming
  • gpu
  • hld

What you'll learn

  • Problem Statement

    Design a conversational AI service where users send text prompts and receive streamed responses generated by a large language model (LLM). The system must support 200 million daily active users, maintain low time-to-first-token (TTFT) la…

  • Requirements

  • Functional Requirements

    1. Send a text prompt and receive a streamed response from an LLM 2. Create, list, and resume conversations (chat sessions) 3. Persist all messages (user prompts and assistant responses) within a conversation 4. Resume a conversation wit…

  • Non-Functional Requirements

    1. Low TTFT — time-to-first-token must be under 1 second for paid tier users 2. GPU efficiency — maximize throughput per GPU dollar (continuous batching, KV-cache reuse) 3. 200M DAU scale — handle millions of concurrent generation reques…

  • Extended Requirements

    1. Multi-model routing (different model sizes for different use cases) 2. Content moderation and safety filtering 3. Plugin/tool use within conversations 4. Multi-modal inputs (images, files) ---

  • Core Entities

    | Entity | Key Fields | Notes | |--------|-----------|-------| | User | userId, tier (free/paid), dailyTokensUsed, dailyTokenBudget | Tier determines priority and limits | | Chat / Conversation | chatId, userId, title, createdAt, modelId…

  • API Design

  • Create a New Chat

  • List User's Chats

  • Get Messages in a Chat

  • Send a Message (Streaming Response)

    Why Server-Sent Events (SSE) over HTTP/2? - Unidirectional streaming (server to client) is all we need — the client sends one request, then just listens - Built-in reconnection semantics (Last-Event-ID header) - Works through proxies, lo…

  • Cancel a Generation

    ---

  • Token Streaming Architecture

  • How the Stream Flows

    1. Client sends POST with the user's message 2. API Gateway authenticates, applies rate limits, forwards to Chat Service 3. Chat Service persists the user message, assembles the context (conversation history), and submits a Generation Re…

  • Minimizing Latency in the Stream

    | Principle | Implementation | |-----------|---------------| | No buffering | Flush each token immediately at every proxy layer | | Heartbeat during prefill | Send event: heartbeat every 500ms while the model processes input (keeps conne…

  • Why TTFT Matters

    Time-to-first-token (TTFT) is the elapsed time from when the user submits a prompt to when the first output token appears on screen. It is the single most important latency metric for a chat service.

  • The Two Phases of LLM Inference

    TTFT = Queue wait time + Prefill time + First decode step

  • Optimizations to Reduce TTFT

    | Technique | How It Helps | TTFT Reduction | |-----------|-------------|----------------| | Prefix caching / KV-cache reuse | If the conversation history (system prompt + prior turns) was computed before, reuse the cached key-value tens…

  • Why KV-Cache Reuse Is Critical for Chat

    In a multi-turn conversation, the model must "see" all prior messages to generate a contextual response. Without caching: Without caching, TTFT grows linearly with conversation length. With KV-cache persistence between turns, only the ne…

  • GPU Orchestration and Scheduling

  • The Scheduling Problem

    GPU workers are the most expensive and scarce resource. A single A100 GPU costs ~$2/hour. At 200M DAU, the system may need tens of thousands of GPUs. Every millisecond of idle GPU time is wasted money.

  • Request Queue with Priority

  • Continuous Batching

    Traditional batching waits for a full batch of requests, processes them together, and waits for ALL to finish before accepting new ones. This wastes GPU cycles when some requests finish early. Continuous batching (also called iteration-l…

  • Affinity Routing

    When a user sends a follow-up message in the same conversation, the GPU worker that handled the previous turn likely still has the KV-cache in GPU memory. Routing the new request to the same worker avoids re-prefilling the entire convers…

  • Worker Health Monitoring

    | Signal | Action | |--------|--------| | No heartbeat for 10s | Mark worker unhealthy; stop routing new requests | | GPU memory > 95% | Stop accepting new requests; let current ones drain | | Inference latency spike | Reduce batch size…

  • Rate Limiting and Tiered Access

  • Multi-Level Rate Limiting

    | Layer | What It Limits | Why | |-------|---------------|-----| | API Gateway | Requests per minute per user (token bucket algorithm) | Prevents API abuse, DDoS protection | | Scheduler | One active generation per user at a time | Preve…

  • Tier Configuration

    | Parameter | Free Tier | Paid Tier | |-----------|-----------|-----------| | Queue priority | P2 (low) | P0/P1 (high) | | Max context window | 4K tokens | 128K tokens | | Daily token budget | 50K tokens | 2M tokens | | Requests per minu…

  • Weighted Fair Queuing

    To prevent heavy users from starving others even within the same tier: This ensures that even during peak load, no single user (or small group of heavy users) can monopolize GPU capacity at the expense of others in the same tier. ---

  • Context Management as Conversations Grow

    As conversations extend to dozens or hundreds of turns, the total context can exceed the model's context window. The system needs strategies to manage this.

  • Strategy Comparison

    | Strategy | How It Works | Pros | Cons | |----------|-------------|------|------| | Truncation | Drop oldest messages beyond context window | Simple, predictable | Loses early context; user notices "amnesia" | | Summarization | Periodic…

  • Data Model

  • PostgreSQL Schema

  • Why PostgreSQL?

    - Messages are relational (belong to chats, chats belong to users) - Strong consistency needed for message ordering (sequence numbers) - ACID transactions ensure a user message and its corresponding assistant response are persisted atomi…

  • Partitioning Strategy

    Partition the Messages table by chatid hash. This ensures all messages for a single conversation reside on the same shard, enabling efficient retrieval without cross-shard queries. ---

  • Cancellation

    When a user clicks "Stop generating," the system must halt inference, persist whatever was generated so far, and release resources.

  • Cancellation Flow

  • Why Check Between Decode Steps?

    Each decode step (generating one token) takes ~10-30ms. Checking a cancellation flag between steps adds negligible overhead but ensures the system responds to cancellation within one token generation cycle. This is far better than waitin…

  • Design Evolution & Trade-offs

    How this system grew from an MVP to what a production version looks like — and the choices you'd defend in an interview.

  • Stage 1 — MVP: One box, one database

    The simplest thing: single API server calling an LLM provider, storing conversation history in Postgres. What breaks first — Latency (LLM calls are 2-30s), streaming needs, DB write contention on chat history table. ---

  • Stage 2 — Scale-out: Multiple pods behind a load balancer

    - Stateless API layer, N replicas behind an L7 LB. - WebSocket / SSE for streaming tokens back to clients. - DB stays single-node. What breaks next — LLM provider rate limits, cost explosion (every call costs money), cold-start latency f…

  • Stage 3 — Bottleneck: Conversation context & memory

    The FIRST hard problem for AI chat: how to give the model conversation history without blowing token limits or latency. | Option | How it works | Latency | Cost | Complexity | Best for | Fails at | |--------|-------------|---------|-----…

  • Stage 4 — Second bottleneck: LLM rate limits, cost, and failover

    Options: | Option | How it works | Latency impact | Cost impact | Complexity | Best for | Fails at | |--------|-------------|---------------|-------------|------------|----------|----------| | A. Multi-provider routing | Route to OpenAI,…

  • Stage 5 — Mature architecture

    Full diagram: CDN → LB → API pods → (Redis session cache, vector DB for RAG, Postgres for durable history, Kafka for events, LLM router with fallback, moderation service). New components: | Component | Purpose | Why now? | |-----------|-…

  • Migration Path (MVP → Enterprise)

    Step-by-step migration in dependency order: 1. Add streaming (SSE) — Users see tokens as they generate (perceived latency drops 10x). No backend changes yet. 2. Horizontal scale API layer — Multiple pods behind LB. DB connection pooling…

  • High-Level Architecture

    ---

  • Scaling

  • Autoscaling GPU Workers

  • Separate Pools per Model Size

    | Pool | Model | Use Case | Instance Type | |------|-------|----------|---------------| | Pool A | Large (70B+ params) | Complex reasoning, paid tier | A100 80GB / H100 | | Pool B | Small (7B-13B params) | Simple tasks, free tier, high t…

  • Cost Optimization

    | Strategy | How It Saves | |----------|-------------| | Spot/preemptible for free tier | 60-70% cost reduction; free tier can tolerate occasional retries | | Warm pool instead of cold scaling | Avoids 5-10 minute GPU initialization on s…

  • Multi-Region Deployment

    ---

  • Fault Tolerance

  • GPU Worker Crash

  • Client Connection Drop

  • Database Failure

    | Failure | Impact | Recovery | |---------|--------|----------| | PostgreSQL primary down | Writes blocked (new messages can't be persisted) | Promote replica to primary; queue writes during failover | | PostgreSQL replica down | Reduced…

  • Summary: Key Design Decisions

    | Decision | What It Solves | Trade-off | |----------|---------------|-----------| | SSE over HTTP/2 for streaming | Unidirectional token delivery with reconnection support | Requires proxy configuration to avoid buffering | | Continuous…

← back to High Level Design

Designing an AI Chat Service — High Level Design | GoCrack | GoCrack