How to Design Cloud Gaming Platform like GeForce NOW
Building game streaming, low-latency rendering, and global GPU orchestration at million-concurrent scale — A Senior+ System Design Guide
Table of Contents
- Introduction — Cloud Gaming at Scale
- Functional & Non-Functional Requirements
- Capacity Estimation
- Data Model
- API Design
- High-Level Architecture
- GPU Virtualization & Rendering Pipeline
- Video Encoding & Low-Latency Streaming
- Input Latency Optimization
- Session Management & Lifecycle
- Game Library & Digital Rights Management
- Save State & Cloud Storage
- Matchmaking & Multiplayer Support
- Adaptive Quality & Bandwidth Management
- Edge Computing & GPU Placement
- CDN for Game Streams
- Subscription & Monetization
- Anti-Cheat & Security
- Analytics & Player Experience Monitoring
- Database Sharding
- Caching Strategy
- Multi-Region GPU Fleet Design
- Cost Estimation
- Interview Q&A
- Full C# Implementation
- Conclusion
1. Introduction — Cloud Gaming at Scale
Cloud gaming represents one of the most ambitious frontiers in distributed systems engineering. The promise is deceptively simple: play AAA games on any device with an internet connection, no expensive hardware required. But beneath that simplicity lies a labyrinth of real-time GPU orchestration, sub-50ms video pipelines, global fleet management, and the kind of systems design that makes traditional web engineering look like child's play.
The cloud gaming market has exploded to over 100 million active users worldwide in 2026. NVIDIA GeForce NOW leads the charge with its network of data centers packing over 1,000 GPU configurations. Microsoft's Xbox Cloud Gaming bundles streaming into Game Pass Ultimate. Sony's PlayStation Now evolved into PlayStation Plus Premium. Google tried and failed with Stadia, proving that raw engineering talent isn't enough — you need the right architecture, the right economics, and the right content strategy.
What separates success from failure in cloud gaming? The answer is latency. A local GPU renders frames in 8-16ms. A cloud gaming platform must capture game frames, encode them, transmit them across the network, decode them on the client, and display them — all within 60-100ms to feel responsive. For competitive gaming, the target is even more aggressive: sub-50ms glass-to-glass latency. Every microsecond of pipeline latency translates directly to player frustration.
In this comprehensive guide, we'll dissect every subsystem of a cloud gaming platform. We'll examine how NVIDIA's vGPU technology allows a single A100 to serve multiple concurrent game sessions, how NVENC hardware encoders achieve sub-millisecond frame encoding, how QUIC-based transport protocols minimize jitter, and how edge GPU placement reduces round-trip latency. We'll build the data models, define the APIs, estimate capacity for million-concurrent sessions, and write production-grade C# code for the session orchestration layer.
Whether you're preparing for a system design interview at a gaming company, architecting a cloud gaming startup's infrastructure, or simply curious about how GeForce NOW works at scale, this article will give you the depth you need.
2. Functional & Non-Functional Requirements
Functional Requirements
| # | Requirement | Description |
|---|---|---|
| F1 | User Authentication & Profiles | Sign up, sign in, profile management, linked accounts (Steam, Epic) |
| F2 | Game Library Management | Browse games, search, filter by genre, platform compatibility |
| F3 | Session Launch | User selects a game, system provisions a GPU, launches game, streams video |
| F4 | Real-Time Input Streaming | Capture keyboard, mouse, controller input and transmit to cloud VM |
| F5 | Video Streaming | Encode rendered frames, stream to client at 60/120/240 FPS |
| F6 | Session Suspension & Resume | Suspend game state to disk, resume on reconnect or different device |
| F7 | Cloud Save Synchronization | Periodic checkpoint sync across devices and regions |
| F8 | Subscription Management | Tiered plans (Free, Priority, Ultra), billing, entitlements |
| F9 | Multiplayer Support | Party creation, multiplayer sessions, voice chat |
| F10 | Game Installation & Updates | Pre-install popular games on GPU nodes, manage game patches |
| F11 | Quality Settings | Resolution (720p-4K), bitrate, FPS selection per user tier |
| F12 | Anti-Cheat Enforcement | Server-side anti-cheat scanning, integrity verification |
Non-Functional Requirements
| # | Requirement | Target |
|---|---|---|
| NF1 | Glass-to-Glass Latency | < 80ms (target < 50ms for competitive) |
| NF2 | Video Quality | Up to 4K 120fps, HDR, 50 Mbps bitrate |
| NF3 | Availability | 99.95% uptime (allowing 4.38 hrs downtime/year) |
| NF4 | Concurrent Sessions | Support 10M+ concurrent sessions globally |
| NF5 | Session Start Time | < 5s warm start, < 15s cold start |
| NF6 | Scalability | Auto-scale GPU fleet based on demand prediction |
| NF7 | Fairness | Queue time < 30s for free tier, < 5s for paid |
| NF8 | Session Duration | Up to 6-hour sessions with auto-suspend after 15min idle |
| NF9 | Data Privacy | GDPR/CCPA compliance, encrypted game saves, no input logging |
| NF10 | Cost Efficiency | GPU utilization > 75%, < $0.15/gaming-hour at scale |
3. Capacity Estimation
Concurrent Sessions
- Total registered users: 50 million
- Monthly active users: 20 million
- Daily active users: 8 million
- Peak concurrent sessions: 3 million
- Average session duration: 90 minutes
- Peak-to-average ratio: 3x
GPU Hours Calculation
Each concurrent session requires a dedicated GPU allocation. With 3 million peak concurrent sessions:
- Peak GPU hours: 3,000,000 GPU-hours/hour at peak
- Daily GPU hours: 3M × 16 hours (peak spread) + off-peak = ~18M GPU-hours/day
- Monthly GPU hours: ~540M GPU-hours/month
- GPU fleet size needed: At 80% utilization with buffer: ~4M GPU slots globally
Bandwidth Estimation
| Quality Tier | Resolution | FPS | Bitrate | Sessions (peak) | Bandwidth |
|---|---|---|---|---|---|
| Free (720p) | 1280×720 | 60 | 5 Mbps | 1,000,000 | 5 Tbps |
| Priority (1080p) | 1920×1080 | 60 | 15 Mbps | 1,200,000 | 18 Tbps |
| Ultra (4K) | 3840×2160 | 120 | 50 Mbps | 800,000 | 40 Tbps |
| Total | 3,000,000 | 63 Tbps |
This 63 Tbps aggregate bandwidth requirement across the global fleet is enormous. NVIDIA's GFN infrastructure addresses this by placing GPU nodes at the edge, within ISP networks, and at major internet exchange points. The key insight is that game streams are uni-directional (server to client for video, client to server for input) and latency-sensitive but loss-tolerant —丢了 a frame is fine, but a delayed frame causes stuttering.
Storage Estimation
- Game library metadata: 500 GB (titles, covers, descriptions)
- Game binaries (shared): 200 TB (2,000 games × 100 GB avg)
- Per-user save states: 50M users × 500 MB avg = 25 PB
- Session snapshots (suspend): 50 GB per active session, ephemeral
- Analytics data: 10 TB/day × 365 = 3.65 PB/year
4. Data Model
Entity Relationship Overview
Core Tables Schema
| Table | Partition Key | Sort Key | Index | Storage |
|---|---|---|---|---|
| users | user_id | — | email (unique) | DynamoDB / PostgreSQL |
| games | game_id | — | title, publisher | PostgreSQL + Read Replica |
| sessions | session_id | started_at | user_id, status | DynamoDB (TTL: 7 days) |
| gpu_nodes | node_id | — | region, status | etcd + PostgreSQL |
| save_states | user_id | game_id + version | — | S3 + DynamoDB metadata |
| subscriptions | user_id | start_date | status | PostgreSQL |
| analytics_events | event_id | timestamp | user_id, session_id | Kafka → ClickHouse |
5. API Design
RESTful API Endpoints
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /api/v1/auth/login | Authenticate user, return JWT | None |
| POST | /api/v1/auth/refresh | Refresh access token | Refresh token |
| GET | /api/v1/games | List games (paginated, filterable) | Bearer |
| GET | /api/v1/games/{id} | Game details | Bearer |
| POST | /api/v1/sessions | Launch a new gaming session | Bearer |
| GET | /api/v1/sessions/{id} | Session status & stream info | Bearer |
| PUT | /api/v1/sessions/{id}/quality | Update stream quality | Bearer |
| DELETE | /api/v1/sessions/{id} | Terminate session | Bearer |
| POST | /api/v1/sessions/{id}/suspend | Suspend and save state | Bearer |
| POST | /api/v1/sessions/{id}/resume | Resume suspended session | Bearer |
| GET | /api/v1/saves/{userId}/{gameId} | List save states | Bearer |
| POST | /api/v1/saves/{userId}/{gameId} | Upload save state | Bearer |
| GET | /api/v1/subscription | Current subscription details | Bearer |
| POST | /api/v1/subscription/upgrade | Upgrade plan | Bearer |
| GET | /api/v1/queue/status | Queue position (free tier) | Bearer |
Stream Connection Protocol
The actual game stream doesn't flow through the REST API. Instead, the session launch endpoint returns a stream connection object:
{
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "running",
"stream": {
"protocol": "WebRTC",
"signaling_url": "wss://signaling.gfn.ayodhyya.com/sessions/a1b2c3d4",
"ice_servers": [
{ "urls": "stun:stun.gfn.ayodhyya.com:3478" },
{ "urls": "turn:turn.gfn.ayodhyya.com:3478", "username": "session_user", "credential": "session_pass" }
],
"video_codec": "H264",
"max_bitrate": 50000000,
"resolution": "1920x1080",
"fps": 60,
"gpu_node": "us-east-1a-gpu-042",
"estimated_latency_ms": 35
},
"input_channel": {
"protocol": "WebRTC-DataChannel",
"label": "gamepad-input",
"ordered": false,
"max_retransmits": 0
},
"expires_at": "2026-07-14T12:00:00Z"
}
6. High-Level Architecture
This architecture follows a clear separation between the control plane (session management, billing, orchestration) and the data plane (video streams, input processing, save states). The control plane operates at conventional web latencies (10-100ms), while the data plane demands real-time performance (sub-50ms).
7. GPU Virtualization & Rendering Pipeline
NVIDIA vGPU Architecture
Modern cloud gaming relies on GPU virtualization to share physical GPUs across multiple concurrent sessions. NVIDIA's vGPU technology (now part of the vGPU software suite) enables a single physical GPU — such as an A100, A40, or L40S — to be partitioned into multiple virtual GPU instances, each with dedicated compute cores, memory, and encoding engines.
- NVIDIA A100 (80GB): Can serve 4-8 concurrent 1080p60 sessions
- NVIDIA L40S (48GB): Optimized for graphics, 6-10 concurrent sessions
- NVIDIA T4 (16GB): Budget option, 2-3 concurrent 720p60 sessions
- NVIDIA RTX 6000 Ada (48GB): Premium sessions with ray tracing, 4-6 sessions
Rendering Pipeline Per Session
The pipeline operates as a tight loop: the game's CPU logic generates render commands, the GPU executes them via Vulkan or DirectX 12, the completed frame sits in the frame buffer, NVIDIA's dedicated NVENC hardware encoder compresses it in under 1ms, the encoded bitstream is packetized for network transport, transmitted to the client, decoded by the client's hardware decoder, and displayed.
vGPU Isolation and Resource Allocation
| Resource | Isolation Method | Per-Session Allocation |
|---|---|---|
| GPU Compute Cores | CUDA Process Isolation | Configurable: 1/4 to full GPU |
| GPU Memory (VRAM) | Address Space Isolation | 2-8 GB per session |
| NVENC Encoder | Time-Sliced Sharing | Dedicated encoder per 2-4 sessions |
| GPU Decode Engine | Shared (for game assets) | Shared across sessions on same GPU |
| CPU (vCPUs) | cgroup/VM Isolation | 2-4 vCPUs per session |
| System RAM | VM Memory Limit | 4-16 GB per session |
| Network Bandwidth | Traffic Shaping | Capped per tier (5-50 Mbps) |
The critical insight for GPU virtualization in cloud gaming is that game rendering is not embarrassingly parallel in the way that deep learning training is. A game session requires sequential frame rendering — each frame depends on the previous frame's state. This means we can't simply shard a game across GPU cores the way we shard a neural network across TPUs. Instead, each session needs a minimum GPU slice to maintain interactive frame rates.
8. Video Encoding & Low-Latency Streaming
NVENC Hardware Encoding
NVIDIA's NVENC is a dedicated hardware encoder built into every modern NVIDIA GPU. Unlike software encoding (x264, x265), NVENC operates on a separate silicon die within the GPU, meaning encoding happens in parallel with game rendering without consuming any shader cores. This is fundamental to cloud gaming performance.
| Codec | Encoding Latency | Quality (VMAF) | Bandwidth (1080p60) | Client Decode Cost |
|---|---|---|---|---|
| H.264 (NVENC) | 0.5-1ms | 95 | 8-15 Mbps | Very Low (universal) |
| HEVC/H.265 (NVENC) | 0.8-1.5ms | 97 | 5-10 Mbps | Low (most devices) |
| AV1 (NVENC) | 1-2ms | 98 | 4-8 Mbps | Medium (newer devices) |
| AV1 (Software) | 5-15ms | 98 | 3-7 Mbps | Medium |
Low-Latency Encoding Configuration
Standard video encoding optimizes for compression efficiency. Cloud gaming optimizes for latency. The NVENC configuration for cloud gaming differs significantly from archival or streaming use cases:
- Zero-lookahead: Standard encoders analyze future frames to improve compression. Cloud gaming disables this entirely — you can't look ahead when you need the frame NOW.
- Infinite GOP: Instead of periodic I-frames (keyframes every 2 seconds), cloud gaming uses a single I-frame at session start and all subsequent frames are P-frames or B-frames. This maximizes compression but means a single dropped packet causes visual artifacts until the next scene change triggers an IDR frame.
- Rate Control: CBR or VBR with strict max: Constant bitrate ensures predictable network utilization. Some implementations use VBR with a hard ceiling to handle scene complexity variation.
- B-frame count: 0: B-frames add latency because they reference future frames. Cloud gaming uses only I-frames and P-frames.
- Slice encoding: Frames are encoded in independent slices so partial frame loss only corrupts one slice, not the entire frame.
WebRTC-Based Streaming Pipeline
We use WebRTC as the transport protocol because it provides:
- ICE/STUN/TURN traversal: Works behind NATs and corporate firewalls
- Adaptive bitrate: REMB (Receiver Estimated Maximum Bitrate) and TWCC (Transport-Wide Congestion Control) adjust quality in real-time
- Jitter buffering: Built-in adaptive jitter buffer handles network variance
- DTLS-SRTP encryption: All video streams are encrypted end-to-end
- Data channels: Used for input streaming (unordered, unreliable for maximum speed)
9. Input Latency Optimization
Input latency is the Achilles' heel of cloud gaming. The round-trip path for a button press is:
Total: 40-100ms from button press to visual feedback. This is acceptable for casual gaming but problematic for competitive titles where local latency would be 15-25ms.
Input Prediction Techniques
- Camera rotation: Predict the camera continues rotating at the same rate
- Movement: Show character moving in the input direction immediately
- Aim adjustment: Apply mouse delta locally before server confirmation
Network Optimization Strategies
| Technique | Latency Saved | Trade-off |
|---|---|---|
| Edge GPU placement (same city) | 15-30ms | Higher infrastructure cost |
| QUIC over TCP | 5-15ms | More complex implementation |
| Input prioritization (separate channel) | 2-5ms | Additional WebRTC data channel |
| Client-side prediction | 30-50ms perceived | Possible visual correction artifacts |
| Adaptive frame rate | Variable | Quality reduction under congestion |
| Pre-rendered cutscene bypass | 0ms (skip render) | Content must be pre-encoded |
10. Session Management & Lifecycle
Warm Start vs Cold Start
| State | What's Ready | Time to Play | Resource Cost |
|---|---|---|---|
| Cold Start | Nothing — bare metal/VM provisioned | 15-30 seconds | Full boot + game load |
| Warm Start | VM running, game pre-loaded in memory | 3-5 seconds | VM idle cost while waiting |
| Hot Resume | VM suspended with full state in RAM | 1-2 seconds | RAM occupied during suspend |
The Session Orchestrator maintains a pool of warm VMs across each region. For popular games, pre-provisioned VMs have the game binary already installed and loaded into memory. When a user requests a session, the orchestrator:
- Checks the user's region and finds the closest GPU pool
- Looks for a warm VM with the requested game already loaded
- If no warm VM exists, triggers cold provisioning (install game, boot VM)
- Assigns the session to the VM and returns stream connection details
- Monitors the session for health, quality metrics, and idle timeouts
Session Migration
Session migration allows a running game to move between GPU nodes without the user noticing. This is critical for:
- GPU failure recovery: If a GPU node reports hardware errors, migrate active sessions to healthy nodes
- Balancing: Move sessions from overloaded nodes to underutilized ones during traffic shifts
- Maintenance: Gracefully migrate sessions off nodes scheduled for firmware updates
Migration works by: (1) capturing a full memory snapshot of the game VM, (2) transferring the snapshot to the target node, (3) resuming the VM on the new node, and (4) reconnecting the WebRTC stream. Total migration time: 5-15 seconds. The client experiences a brief freeze, similar to a network hiccup.
11. Game Library & Digital Rights Management
Cloud gaming platforms must handle game licensing differently than traditional distribution. Games aren't "installed" on the user's device — they're streamed. But publishers still need assurance that:
- Only subscribers with valid entitlements can play a game
- Game binaries can't be extracted from the GPU nodes
- Concurrent session limits are enforced per license
- Revenue sharing with publishers is accurate and auditable
DRM Architecture
Game binaries on GPU nodes reside on encrypted filesystems. The DRM agent within each VM validates entitlements with the License Server before unlocking game assets. Trusted Execution Environments (TEE) — such as Intel SGX or NVIDIA Confidential Computing — ensure that even a compromised hypervisor can't extract game data from memory.
12. Save State & Cloud Storage
Cloud save systems handle two distinct types of data:
1. Native Game Saves
Games that support cloud saves write to a designated directory that the platform syncs to cloud storage. This is similar to Steam Cloud — the game writes a save file, the platform detects the change, and uploads it to S3.
2. Full Checkpoint Saves (Platform-Level)
For games without native cloud save support, the platform captures a full memory checkpoint — a snapshot of the entire VM state including GPU VRAM, CPU registers, and open file handles. This allows session suspension and resumption on any GPU node.
| Save Type | Size | Latency | Cross-Region | Game Support |
|---|---|---|---|---|
| Native Game Save | 1-100 MB | < 1s upload | Yes | Games with cloud save API |
| Memory Checkpoint | 8-64 GB | 30-120s upload | Same region only | All games (platform-level) |
| Delta Checkpoint | 100 MB-2 GB | 5-30s upload | Same region | All games (incremental) |
Delta checkpoints are the key optimization: instead of uploading the entire 32 GB VM state, the system uses copy-on-write (CoW) to identify only the memory pages that changed since the last checkpoint. For a typical game, only 5-10% of memory changes between frames, reducing checkpoint size from 32 GB to 1.6-3.2 GB.
13. Matchmaking & Multiplayer Support
Cloud gaming multiplayer introduces a unique challenge: both players are on cloud VMs, potentially in different GPU nodes. The platform must decide:
- Co-located: Place both players' VMs on the same GPU node or rack, minimizing inter-player latency
- Game-server model: Route both players' inputs to a dedicated game server VM, which then sends state to both
- P2P within cloud: Connect both players' VMs directly via internal network for peer-to-peer gameplay
14. Adaptive Quality & Bandwidth Management
Network conditions fluctuate constantly. A cloud gaming platform must adapt video quality in real-time to prevent stuttering and frame drops:
The adaptive quality system uses Transport-Wide Congestion Control (TWCC) — a WebRTC feedback mechanism where the client acknowledges every received packet with a timestamp. The server calculates one-way delay variation to estimate available bandwidth and adjusts encoding parameters within 100-200ms.
Adaptive Quality Decision Table
| Available Bandwidth | RTT | Packet Loss | Resolution | FPS | Codec | Bitrate |
|---|---|---|---|---|---|---|
| > 50 Mbps | < 20ms | < 0.1% | 4K | 120 | AV1 | 50 Mbps |
| 20-50 Mbps | < 40ms | < 1% | 1080p | 60 | H.264 | 15 Mbps |
| 10-20 Mbps | < 60ms | < 2% | 1080p | 60 | H.264 | 10 Mbps |
| 5-10 Mbps | < 80ms | < 3% | 720p | 60 | H.264 | 6 Mbps |
| 2-5 Mbps | Any | < 5% | 720p | 30 | H.264 | 3 Mbps |
| < 2 Mbps | Any | Any | Suspend session, notify user | — | ||
15. Edge Computing & GPU Placement
Latency is dominated by the speed of light. A photon travels ~200 km/ms in fiber optic cable. For a 20ms one-way latency budget to the GPU node, the data center must be within 4,000 km of the player. But for sub-10ms latency, the GPU must be within 1,000 km — roughly the distance from New York to Chicago.
GeForce NOW uses a three-tier GPU placement strategy:
- Tier 1 (Hyperscale): Large data centers with thousands of GPUs for peak capacity and game library storage
- Tier 2 (Regional): Mid-size facilities in major metro areas for low-latency coverage
- Tier 3 (Edge): Compact GPU units deployed inside ISP networks and internet exchange points for ultra-low latency (< 5ms to player)
16. CDN for Game Streams
Traditional CDNs serve static content (images, videos, files). Game streaming CDNs must handle real-time, unicast video streams that are unique per session. This is fundamentally different from Netflix-style CDN distribution where the same content is shared across millions of viewers.
Game Stream CDN Architecture
| Component | Role | Scale |
|---|---|---|
| Origin (GPU Node) | Encodes and originates the stream | 1 stream per session |
| Relay Node | Forwards stream to reduce WAN hops | 10,000+ relays globally |
| ISP Edge | Final delivery hop to end user | 500+ ISP partnerships |
| TURN Server | NAT traversal fallback | 1,000+ worldwide |
The key innovation is peer-assisted streaming: when multiple users in the same ISP network are playing the same game, the ISP edge node can multicast common game assets (textures, shaders) to all users, reducing backhaul bandwidth. User-specific rendering (camera angle, HUD) remains unicast.
17. Subscription & Monetization
Tiered Subscription Model
| Feature | Free | Priority ($9.99/mo) | Ultra ($19.99/mo) |
|---|---|---|---|
| Session Length | 1 hour | 6 hours | 6 hours |
| Queue Priority | Lowest | High | Highest |
| RTX Graphics | No | Yes (1080p) | Yes (4K) |
| Ray Tracing | No | Yes | Yes (Ultra) |
| Session Length | 1 hour | 6 hours | 6 hours |
| Concurrent Sessions | 1 | 1 | 2 |
| HDR Support | No | No | Yes |
| Save Slots | 10 | 100 | Unlimited |
| Est. GPU Cost/User | $0.02/hr | $0.08/hr | $0.18/hr |
The economics of cloud gaming depend on GPU utilization. An idle GPU costs the same as a utilized one. The free tier serves as a funnel: limited sessions and lower quality drive conversion to paid tiers. Peak-hour demand management uses queue systems — free users wait, paid users get instant access.
18. Anti-Cheat & Security
Cloud gaming has a unique anti-cheat advantage: the game runs on the server, not the client. Players can't inject mods, memory-edit game variables, or run aimbots because the game binary executes in an isolated VM they don't control. However, new attack vectors emerge:
- Stream interception: Malware on the client could capture and analyze the video stream to build wallhacks (revealing hidden enemies through visual analysis)
- Input injection: Automated input bots could play the game on the user's behalf for grinding
- VM escape: Sophisticated attackers might attempt to break out of the VM to access the host GPU
- Screenshot/screen recording: Recording and sharing game content without authorization
- Server-side anti-cheat (BattlEye, EasyAntiCheat) runs on the GPU VM
- VM isolation via hardware-enforced virtualization (AMD SEV, Intel TDX)
- Encrypted video stream (DTLS-SRTP) prevents interception
- Input rate limiting and pattern detection catches automated bots
- Secure boot chain ensures only authorized VM images run on GPU nodes
19. Analytics & Player Experience Monitoring
Cloud gaming generates an enormous telemetry stream. Every frame rendered, every input event, every network packet contributes data that must be collected, processed, and analyzed in near-real-time.
Key Metrics Tracked
| Metric | Collection Point | Target | Alert Threshold |
|---|---|---|---|
| Glass-to-Glass Latency | Client + Server | < 80ms | > 120ms |
| Frame Rate (rendered) | GPU node | Target FPS | < 90% of target |
| Frame Rate (displayed) | Client | Target FPS | < 85% of target |
| Packet Loss | WebRTC stats | < 0.5% | > 2% |
| Jitter | WebRTC stats | < 10ms | > 30ms |
| GPU Utilization | nvidia-smi | 60-80% | > 95% or < 30% |
| Session Start Time | Orchestrator | < 5s warm | > 15s warm |
| Queue Wait Time | Queue Manager | < 10s (paid) | > 30s (paid) |
| Session Crash Rate | VM Monitor | < 0.1% | > 0.5% |
The analytics pipeline uses Kafka for real-time event streaming and ClickHouse for OLAP queries. Each gaming session generates ~1,000 events/second (frame timings, input events, network stats), totaling ~10 TB/day for 3 million concurrent sessions. Anomaly detection models (running on dedicated GPU instances) monitor metric streams for degradation patterns.
20. Database Sharding
At 50 million registered users and millions of concurrent sessions, single-database architectures break down. Here's the sharding strategy:
Users Table — Shard by user_id
Hash-based sharding across 256 shards. Each shard holds ~200,000 users. User lookups are O(1) since all user data (profile, subscription, settings) lives on a single shard.
Sessions Table — Shard by user_id + time bucket
Sessions are sharded by user_id (same shard as the user) with time-bucketed tables (current month + previous month). Old sessions are archived to cold storage and expire via DynamoDB TTL.
Games Table — Replicated (not sharded)
Game metadata is read-heavy but small (~500 GB). Replicate across all regions with read replicas. Use CDN caching for game cover art and descriptions.
Save States Table — Shard by user_id + game_id
Save state metadata is co-located with user data. Actual save files live in S3, partitioned by s3://saves/{shard_id}/{user_id}/{game_id}/{version}.
21. Caching Strategy
Multi-Layer Cache Architecture
| Layer | Technology | TTL | What's Cached | Hit Rate Target |
|---|---|---|---|---|
| L1: Client | Browser/App Cache | Session | Game metadata, UI assets | 95% |
| L2: Edge CDN | CloudFront / Fastly | 5min-1hr | Game art, static assets | 90% |
| L3: Application | Redis Cluster | 5-60min | User profile, session state, queue position | 85% |
| L4: Database | PostgreSQL Buffer | Permanent | Frequently accessed rows | 80% |
Cache Invalidation Strategy
- User profile changes: Write-through — update DB and invalidate Redis simultaneously
- Subscription changes: Event-driven — Kafka event triggers cache invalidation across all layers
- Game library updates: Time-based TTL (5 minutes) — publisher updates are infrequent
- Session state: No caching of active session state — always read from DynamoDB for consistency
22. Multi-Region GPU Fleet Design
The Global Fleet Controller orchestrates GPU allocation across regions using three key algorithms:
- Geo-assignment: Route each session request to the closest region with available capacity, considering both geographic proximity and current load
- Demand forecasting: ML model predicts hourly demand per region based on historical patterns, timezone effects, game release schedules, and promotions
- Cost optimization: Shift non-peak-demand sessions to cheaper regions (e.g., overnight US sessions can use APAC GPUs if latency budget allows)
23. Cost Estimation
GPU Infrastructure Costs
| Component | Unit Cost | Quantity (Peak) | Monthly Cost |
|---|---|---|---|
| NVIDIA L40S GPU Server (8 GPUs) | $2.50/GPU-hr | 500,000 GPU slots | $90M |
| NVIDIA A100 GPU Server (8 GPUs) | $3.50/GPU-hr | 100,000 GPU slots | $25M |
| Edge GPU Nodes (T4) | $0.80/GPU-hr | 200,000 GPU slots | $12M |
| Network Bandwidth (63 Tbps) | $0.02/GB | ~170 PB/month | $3.4M |
| Storage (S3 + DynamoDB) | — | 30 PB | $750K |
| Database (PostgreSQL + Redis) | — | Multi-region | $500K |
| Kafka + ClickHouse | — | 10 TB/day ingest | $300K |
| CDN + STUN/TURN | — | 63 Tbps egress | $8M |
| Engineering Team (200 engineers) | $200K avg salary | 200 | $3.3M |
| Total Monthly | ~$144M |
24. Interview Q&A
25. Full C# Implementation — Session Orchestrator
Below is a production-grade C# implementation of the core Session Orchestrator service. This code manages the full session lifecycle: provisioning, warm pool management, health monitoring, migration, and teardown. It demonstrates patterns used in high-scale real-time systems: actor model, circuit breakers, graceful degradation, and observability.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace CloudGaming.Platform.SessionOrchestrator
{
// ============================================================
// Configuration Models
// ============================================================
public sealed class OrchestratorConfig
{
public int MaxConcurrentSessionsPerRegion { get; set; } = 500_000;
public int WarmPoolSizePerRegion { get; set; } = 10_000;
public int ColdStartTimeoutMs { get; set; } = 30_000;
public int WarmStartTimeoutMs { get; set; } = 5_000;
public int SessionHeartbeatIntervalMs { get; set; } = 5_000;
public int IdleTimeoutMinutes { get; set; } = 15;
public int MaxSessionDurationMinutes { get; set; } = 360;
public int HealthCheckIntervalMs { get; set; } = 10_000;
public int MigrationTimeoutMs { get; set; } = 15_000;
public double GpuUtilizationTarget { get; set; } = 0.75;
public int CheckpointIntervalSeconds { get; set; } = 30;
}
public sealed class RegionConfig
{
public string RegionId { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public double Latitude { get; set; }
public double Longitude { get; set; }
public int TotalGpuSlots { get; set; }
public List<GpuType> SupportedGpuTypes { get; set; } = new();
}
public sealed class GpuType
{
public string Model { get; set; } = string.Empty;
public int VramGb { get; set; }
public int ConcurrentSessionCapacity { get; set; }
public decimal CostPerHour { get; set; }
}
// ============================================================
// Domain Models
// ============================================================
public enum SessionStatus
{
Queued,
Provisioning,
ColdStarting,
WarmStarting,
Running,
Streaming,
Suspending,
Suspended,
Migrating,
Terminating,
Terminated,
Failed
}
public enum UserTier
{
Free = 0,
Priority = 1,
Ultra = 2
}
public sealed class GameSession
{
public string SessionId { get; init; } = Guid.NewGuid().ToString("N");
public string UserId { get; init; } = string.Empty;
public string GameId { get; init; } = string.Empty;
public UserTier Tier { get; init; }
public SessionStatus Status { get; internal set; }
public string? AssignedGpuNodeId { get; internal set; }
public string? AssignedVmId { get; internal set; }
public string RegionId { get; init; } = string.Empty;
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
public DateTime? StartedAt { get; internal set; }
public DateTime? EndedAt { get; internal set; }
public DateTime? LastHeartbeat { get; internal set; }
public DateTime? LastCheckpoint { get; internal set; }
public StreamQuality Quality { get; set; } = new();
public SessionMetrics Metrics { get; } = new();
public string? StreamEndpointUrl { get; internal set; }
public string? SignalingUrl { get; internal set; }
}
public sealed class StreamQuality
{
public int Width { get; set; } = 1920;
public int Height { get; set; } = 1080;
public int Fps { get; set; } = 60;
public string Codec { get; set; } = "H264";
public int MaxBitrateMbps { get; set; } = 15;
public bool RayTracing { get; set; }
}
public sealed class SessionMetrics
{
public double AvgFrameLatencyMs { get; set; }
public double AvgGpuUtilization { get; set; }
public double AvgPacketLossRate { get; set; }
public double AvgJitterMs { get; set; }
public long TotalFramesRendered { get; set; }
public long TotalInputEvents { get; set; }
public long TotalBytesStreamed { get; set; }
}
public sealed class GpuNode
{
public string NodeId { get; init; } = Guid.NewGuid().ToString("N");
public string RegionId { get; init; } = string.Empty;
public string GpuModel { get; init; } = string.Empty;
public int TotalVramGb { get; init; }
public int MaxConcurrentSessions { get; init; }
public int CurrentSessionCount { get; internal set; }
public double Utilization { get; internal set; }
public GpuNodeStatus Status { get; internal set; } = GpuNodeStatus.Healthy;
public DateTime LastHealthCheck { get; internal set; } = DateTime.UtcNow;
public List<string> ActiveSessionIds { get; } = new();
public Dictionary<string, WarmVm> WarmPool { get; } = new();
}
public enum GpuNodeStatus
{
Healthy,
Degraded,
Draining,
Offline,
Failed
}
public sealed class WarmVm
{
public string VmId { get; init; } = Guid.NewGuid().ToString("N");
public string GameId { get; init; } = string.Empty;
public string GpuNodeId { get; init; } = string.Empty;
public DateTime ProvisionedAt { get; init; } = DateTime.UtcNow;
public WarmVmStatus Status { get; internal set; } = WarmVmStatus.Initializing;
public string? AssignedSessionId { get; internal set; }
}
public enum WarmVmStatus
{
Initializing,
Ready,
GameLoading,
GameLoaded,
InUse,
CleaningUp
}
public sealed class StreamConnectionInfo
{
public string Protocol { get; set; } = "WebRTC";
public string SignalingUrl { get; set; } = string.Empty;
public string[] IceServers { get; set; } = Array.Empty<string>();
public string VideoCodec { get; set; } = "H264";
public int MaxBitrate { get; set; }
public string Resolution { get; set; } = string.Empty;
public int Fps { get; set; }
public string GpuNodeId { get; set; } = string.Empty;
public int EstimatedLatencyMs { get; set; }
}
public sealed class SessionLaunchRequest
{
public string UserId { get; init; } = string.Empty;
public string GameId { get; init; } = string.Empty;
public UserTier Tier { get; init; }
public string PreferredRegion { get; init; } = string.Empty;
public StreamQuality? PreferredQuality { get; init; }
}
// ============================================================
// Event Models for Observability
// ============================================================
public enum SessionEventType
{
Created,
Queued,
ProvisioningStarted,
WarmVmAssigned,
ColdStartInitiated,
GameLoading,
Running,
Streaming,
QualityChanged,
HeartbeatReceived,
CheckpointCreated,
MigrationStarted,
MigrationCompleted,
SuspendRequested,
Suspended,
Resumed,
Terminated,
Failed,
IdleTimeout,
MaxDurationReached
}
public sealed class SessionEvent
{
public string EventId { get; init; } = Guid.NewGuid().ToString("N");
public string SessionId { get; init; } = string.Empty;
public string UserId { get; init; } = string.Empty;
public SessionEventType EventType { get; init; }
public DateTime Timestamp { get; init; } = DateTime.UtcNow;
public Dictionary<string, string> Properties { get; init; } = new();
}
// ============================================================
// Interfaces
// ============================================================
public interface IGpuNodeRegistry
{
Task<IReadOnlyList<GpuNode>> GetNodesForRegionAsync(string regionId);
Task<GpuNode?> GetNodeAsync(string nodeId);
Task<GpuNode?> FindBestNodeForSessionAsync(string regionId, string gameId, UserTier tier);
Task RegisterNodeAsync(GpuNode node);
Task UpdateNodeStatusAsync(string nodeId, GpuNodeStatus status);
}
public interface IWarmVmPoolManager
{
Task<WarmVm?> TryAcquireWarmVmAsync(string regionId, string gameId);
Task<WarmVm> ProvisionWarmVmAsync(string nodeId, string gameId);
Task ReleaseWarmVmAsync(string vmId);
Task<int> GetPoolSizeAsync(string regionId, string gameId);
}
public interface ISessionStore
{
Task SaveSessionAsync(GameSession session);
Task<GameSession?> GetSessionAsync(string sessionId);
Task<IReadOnlyList<GameSession>> GetSessionsByUserAsync(string userId);
Task<IReadOnlyList<GameSession>> GetSessionsByStatusAsync(string regionId, SessionStatus status);
Task DeleteSessionAsync(string sessionId);
}
public interface IStreamConnectionManager
{
Task<StreamConnectionInfo> EstablishConnectionAsync(GameSession session, GpuNode node);
Task<StreamConnectionInfo> ReconnectAsync(GameSession session);
Task TerminateConnectionAsync(string sessionId);
}
public interface IEventBus
{
Task PublishAsync(SessionEvent sessionEvent);
}
public interface ICheckpointService
{
Task<string> CreateCheckpointAsync(GameSession session);
Task<bool> RestoreCheckpointAsync(string checkpointId, string targetVmId);
}
public interface IRegionRouter
{
string GetClosestRegion(double clientLat, double clientLon, IReadOnlyList<string> availableRegions);
Task<IReadOnlyList<string>> GetAvailableRegionsAsync();
}
// ============================================================
// Circuit Breaker Implementation
// ============================================================
public sealed class CircuitBreaker
{
private readonly int _failureThreshold;
private readonly TimeSpan _recoveryTime;
private int _failureCount;
private DateTime? _lastFailureTime;
private CircuitState _state = CircuitState.Closed;
public CircuitBreaker(int failureThreshold, TimeSpan recoveryTime)
{
_failureThreshold = failureThreshold;
_recoveryTime = recoveryTime;
}
public CircuitState State => _state;
public async Task<T> ExecuteAsync<T>(Func<Task<T>> action, Func<Task<T>> fallback)
{
if (_state == CircuitState.Open)
{
if (_lastFailureTime.HasValue &&
DateTime.UtcNow - _lastFailureTime.Value > _recoveryTime)
{
_state = CircuitState.HalfOpen;
}
else
{
return await fallback();
}
}
try
{
var result = await action();
OnSuccess();
return result;
}
catch (Exception)
{
OnFailure();
return await fallback();
}
}
private void OnSuccess()
{
_failureCount = 0;
_state = CircuitState.Closed;
}
private void OnFailure()
{
_failureCount++;
_lastFailureTime = DateTime.UtcNow;
if (_failureCount >= _failureThreshold)
_state = CircuitState.Open;
}
}
public enum CircuitState { Closed, Open, HalfOpen }
// ============================================================
// Core Session Orchestrator
// ============================================================
public sealed class SessionOrchestrator
{
private readonly OrchestratorConfig _config;
private readonly IGpuNodeRegistry _nodeRegistry;
private readonly IWarmVmPoolManager _warmPoolManager;
private readonly ISessionStore _sessionStore;
private readonly IStreamConnectionManager _streamManager;
private readonly IEventBus _eventBus;
private readonly ICheckpointService _checkpointService;
private readonly IRegionRouter _regionRouter;
private readonly ILogger<SessionOrchestrator> _logger;
private readonly ConcurrentDictionary<string, CancellationTokenSource> _sessionCancellations = new();
private readonly ConcurrentDictionary<string, GameSession> _activeSessions = new();
private readonly SemaphoreSlim _provisionSemaphore = new(100, 100);
private readonly CircuitBreaker _gpuNodeCircuitBreaker = new(5, TimeSpan.FromSeconds(30));
public SessionOrchestrator(
IOptions<OrchestratorConfig> config,
IGpuNodeRegistry nodeRegistry,
IWarmVmPoolManager warmPoolManager,
ISessionStore sessionStore,
IStreamConnectionManager streamManager,
IEventBus eventBus,
ICheckpointService checkpointService,
IRegionRouter regionRouter,
ILogger<SessionOrchestrator> logger)
{
_config = config.Value;
_nodeRegistry = nodeRegistry;
_warmPoolManager = warmPoolManager;
_sessionStore = sessionStore;
_streamManager = streamManager;
_eventBus = eventBus;
_checkpointService = checkpointService;
_regionRouter = regionRouter;
_logger = logger;
}
// --------------------------------------------------------
// Public API: Launch Session
// --------------------------------------------------------
public async Task<(GameSession session, StreamConnectionInfo stream)> LaunchSessionAsync(
SessionLaunchRequest request)
{
var sw = Stopwatch.StartNew();
_logger.LogInformation(
"Session launch requested: User={UserId}, Game={GameId}, Tier={Tier}",
request.UserId, request.GameId, request.Tier);
// Determine best region
var regionId = request.PreferredRegion;
if (string.IsNullOrEmpty(regionId))
{
var regions = await _regionRouter.GetAvailableRegionsAsync();
regionId = regions.First();
}
// Create session record
var session = new GameSession
{
UserId = request.UserId,
GameId = request.GameId,
Tier = request.Tier,
RegionId = regionId,
Quality = request.PreferredQuality ?? GetDefaultQuality(request.Tier)
};
session.Status = SessionStatus.Queued;
await _sessionStore.SaveSessionAsync(session);
await PublishEventAsync(session, SessionEventType.Created);
await PublishEventAsync(session, SessionEventType.Queued);
// Check capacity and queue if needed
var availableNodes = await _nodeRegistry.GetNodesForRegionAsync(regionId);
var totalCapacity = availableNodes.Sum(n => n.MaxConcurrentSessions);
var totalActive = availableNodes.Sum(n => n.CurrentSessionCount);
if (totalActive >= totalCapacity && request.Tier == UserTier.Free)
{
_logger.LogWarning(
"Free tier user queued due to capacity: User={UserId}", request.UserId);
session.Status = SessionStatus.Queued;
await _sessionStore.SaveSessionAsync(session);
// In production, this would use a queue with polling/WebSocket notification
throw new CapacityExceededException(
$"Region {regionId} at capacity. Free tier users must queue.");
}
// Try warm start first
session.Status = SessionStatus.Provisioning;
await _sessionStore.SaveSessionAsync(session);
await PublishEventAsync(session, SessionEventType.ProvisioningStarted);
var warmVm = await _warmPoolManager.TryAcquireWarmVmAsync(regionId, request.GameId);
if (warmVm != null)
{
return await WarmStartAsync(session, warmVm);
}
// Cold start fallback
return await ColdStartAsync(session);
}
// --------------------------------------------------------
// Warm Start Path
// --------------------------------------------------------
private async Task<(GameSession session, StreamConnectionInfo stream)> WarmStartAsync(
GameSession session, WarmVm warmVm)
{
_logger.LogInformation(
"Warm start: Session={SessionId}, Vm={VmId}, Game={GameId}",
session.SessionId, warmVm.VmId, session.GameId);
session.Status = SessionStatus.WarmStarting;
session.AssignedVmId = warmVm.VmId;
warmVm.Status = WarmVmStatus.InUse;
warmVm.AssignedSessionId = session.SessionId;
await PublishEventAsync(session, SessionEventType.WarmVmAssigned);
await _sessionStore.SaveSessionAsync(session);
var node = await _nodeRegistry.GetNodeAsync(warmVm.GpuNodeId);
if (node == null)
{
_logger.LogError("GPU node {NodeId} not found for warm VM", warmVm.GpuNodeId);
return await ColdStartAsync(session);
}
session.AssignedGpuNodeId = node.NodeId;
session.Status = SessionStatus.Running;
session.StartedAt = DateTime.UtcNow;
session.LastHeartbeat = DateTime.UtcNow;
await PublishEventAsync(session, SessionEventType.Running);
await _sessionStore.SaveSessionAsync(session);
// Establish streaming connection
var streamInfo = await _streamManager.EstablishConnectionAsync(session, node);
session.StreamEndpointUrl = streamInfo.SignalingUrl;
session.SignalingUrl = streamInfo.SignalingUrl;
session.Status = SessionStatus.Streaming;
await PublishEventAsync(session, SessionEventType.Streaming);
await _sessionStore.SaveSessionAsync(session);
// Start background monitoring
StartSessionMonitoring(session.SessionId);
var elapsed = elapsed: TimeSpan.Zero;
_logger.LogInformation(
"Warm start completed: Session={SessionId}, Duration={Duration}ms",
session.SessionId, elapsed.TotalMilliseconds);
return (session, streamInfo);
}
// --------------------------------------------------------
// Cold Start Path
// --------------------------------------------------------
private async Task<(GameSession session, StreamConnectionInfo stream)> ColdStartAsync(
GameSession session)
{
_logger.LogInformation(
"Cold start initiated: Session={SessionId}, Game={GameId}",
session.SessionId, session.GameId);
session.Status = SessionStatus.ColdStarting;
await PublishEventAsync(session, SessionEventType.ColdStartInitiated);
await _sessionStore.SaveSessionAsync(session);
// Find best GPU node with capacity
var node = await _gpuNodeCircuitBreaker.ExecuteAsync(
() => _nodeRegistry.FindBestNodeForSessionAsync(
session.RegionId, session.GameId, session.Tier),
() => Task.FromResult<GpuNode?>(null));
if (node == null)
{
session.Status = SessionStatus.Failed;
await PublishEventAsync(session, SessionEventType.Failed);
await _sessionStore.SaveSessionAsync(session);
throw new NoGpuAvailableException(
$"No GPU available in region {session.RegionId} for game {session.GameId}");
}
session.AssignedGpuNodeId = node.NodeId;
// Provision warm VM on the node
await _provisionSemaphore.WaitAsync();
try
{
var vm = await _warmPoolManager.ProvisionWarmVmAsync(node.NodeId, session.GameId);
session.AssignedVmId = vm.VmId;
session.Status = SessionStatus.Running;
session.StartedAt = DateTime.UtcNow;
session.LastHeartbeat = DateTime.UtcNow;
await PublishEventAsync(session, SessionEventType.Running);
await _sessionStore.SaveSessionAsync(session);
// Establish streaming connection
var streamInfo = await _streamManager.EstablishConnectionAsync(session, node);
session.StreamEndpointUrl = streamInfo.SignalingUrl;
session.SignalingUrl = streamInfo.SignalingUrl;
session.Status = SessionStatus.Streaming;
await PublishEventAsync(session, SessionEventType.Streaming);
await _sessionStore.SaveSessionAsync(session);
// Start background monitoring
StartSessionMonitoring(session.SessionId);
_logger.LogInformation(
"Cold start completed: Session={SessionId}, Node={NodeId}",
session.SessionId, node.NodeId);
return (session, streamInfo);
}
finally
{
_provisionSemaphore.Release();
}
}
// --------------------------------------------------------
// Session Monitoring (Background)
// --------------------------------------------------------
private void StartSessionMonitoring(string sessionId)
{
var cts = new CancellationTokenSource();
_sessionCancellations[sessionId] = cts;
_ = Task.Run(async () =>
{
try
{
while (!cts.Token.IsCancellationRequested)
{
await Task.Delay(
TimeSpan.FromMilliseconds(_config.HealthCheckIntervalMs), cts.Token);
if (!_activeSessions.TryGetValue(sessionId, out var session))
continue;
// Check idle timeout
if (session.LastHeartbeat.HasValue &&
DateTime.UtcNow - session.LastHeartbeat.Value >
TimeSpan.FromMinutes(_config.IdleTimeoutMinutes))
{
_logger.LogInformation(
"Idle timeout: Session={SessionId}", sessionId);
await PublishEventAsync(session, SessionEventType.IdleTimeout);
await SuspendSessionAsync(sessionId);
break;
}
// Check max duration
if (session.StartedAt.HasValue &&
DateTime.UtcNow - session.StartedAt.Value >
TimeSpan.FromMinutes(_config.MaxSessionDurationMinutes))
{
_logger.LogInformation(
"Max duration reached: Session={SessionId}", sessionId);
await PublishEventAsync(session, SessionEventType.MaxDurationReached);
await TerminateSessionAsync(sessionId);
break;
}
// Periodic checkpoint
if (!session.LastCheckpoint.HasValue ||
DateTime.UtcNow - session.LastCheckpoint.Value >
TimeSpan.FromSeconds(_config.CheckpointIntervalSeconds))
{
await CreateCheckpointAsync(session);
}
// Check GPU node health
if (!string.IsNullOrEmpty(session.AssignedGpuNodeId))
{
var node = await _nodeRegistry.GetNodeAsync(session.AssignedGpuNodeId);
if (node != null && node.Status == GpuNodeStatus.Failed)
{
_logger.LogWarning(
"GPU node failed, migrating: Session={SessionId}, Node={NodeId}",
sessionId, session.AssignedGpuNodeId);
await MigrateSessionAsync(sessionId);
}
}
}
}
catch (OperationCanceledException) { }
catch (Exception ex)
{
_logger.LogError(ex,
"Session monitor error: Session={SessionId}", sessionId);
}
}, cts.Token);
}
// --------------------------------------------------------
// Suspend Session
// --------------------------------------------------------
public async Task SuspendSessionAsync(string sessionId)
{
var session = await GetOrThrowAsync(sessionId);
if (session.Status == SessionStatus.Suspended ||
session.Status == SessionStatus.Terminated)
return;
_logger.LogInformation(
"Suspending session: Session={SessionId}", sessionId);
session.Status = SessionStatus.Suspending;
await PublishEventAsync(session, SessionEventType.SuspendRequested);
await _sessionStore.SaveSessionAsync(session);
// Create final checkpoint
await CreateCheckpointAsync(session);
// Terminate stream
await _streamManager.TerminateConnectionAsync(sessionId);
// Release VM
if (session.AssignedVmId != null)
{
await _warmPoolManager.ReleaseWarmVmAsync(session.AssignedVmId);
}
session.Status = SessionStatus.Suspended;
session.EndedAt = DateTime.UtcNow;
await PublishEventAsync(session, SessionEventType.Suspended);
await _sessionStore.SaveSessionAsync(session);
// Stop monitoring
if (_sessionCancellations.TryRemove(sessionId, out var cts))
{
cts.Cancel();
cts.Dispose();
}
_activeSessions.TryRemove(sessionId, out _);
_logger.LogInformation(
"Session suspended: Session={SessionId}", sessionId);
}
// --------------------------------------------------------
// Resume Session
// --------------------------------------------------------
public async Task<(GameSession session, StreamConnectionInfo stream)> ResumeSessionAsync(
string sessionId)
{
var session = await GetOrThrowAsync(sessionId);
if (session.Status != SessionStatus.Suspended)
throw new InvalidSessionStateException(
$"Cannot resume session in state {session.Status}");
_logger.LogInformation(
"Resuming session: Session={SessionId}", sessionId);
session.Status = SessionStatus.WarmStarting;
await _sessionStore.SaveSessionAsync(session);
// Find a node with the checkpoint
var node = await _nodeRegistry.FindBestNodeForSessionAsync(
session.RegionId, session.GameId, session.Tier);
if (node == null)
throw new NoGpuAvailableException(
$"No GPU available to resume session {sessionId}");
session.AssignedGpuNodeId = node.NodeId;
session.Status = SessionStatus.Running;
session.StartedAt = DateTime.UtcNow;
session.LastHeartbeat = DateTime.UtcNow;
var streamInfo = await _streamManager.EstablishConnectionAsync(session, node);
session.StreamEndpointUrl = streamInfo.SignalingUrl;
session.SignalingUrl = streamInfo.SignalingUrl;
session.Status = SessionStatus.Streaming;
await PublishEventAsync(session, SessionEventType.Resumed);
await _sessionStore.SaveSessionAsync(session);
StartSessionMonitoring(sessionId);
return (session, streamInfo);
}
// --------------------------------------------------------
// Terminate Session
// --------------------------------------------------------
public async Task TerminateSessionAsync(string sessionId)
{
var session = await GetOrThrowAsync(sessionId);
_logger.LogInformation(
"Terminating session: Session={SessionId}", sessionId);
session.Status = SessionStatus.Terminating;
await PublishEventAsync(session, SessionEventType.Terminated);
await _sessionStore.SaveSessionAsync(session);
// Stop monitoring
if (_sessionCancellations.TryRemove(sessionId, out var cts))
{
cts.Cancel();
cts.Dispose();
}
// Terminate stream
await _streamManager.TerminateConnectionAsync(sessionId);
// Release VM
if (session.AssignedVmId != null)
{
await _warmPoolManager.ReleaseWarmVmAsync(session.AssignedVmId);
}
// Update node capacity
if (session.AssignedGpuNodeId != null)
{
var node = await _nodeRegistry.GetNodeAsync(session.AssignedGpuNodeId);
if (node != null)
{
node.CurrentSessionCount =
Math.Max(0, node.CurrentSessionCount - 1);
}
}
session.Status = SessionStatus.Terminated;
session.EndedAt = DateTime.UtcNow;
await _sessionStore.SaveSessionAsync(session);
_activeSessions.TryRemove(sessionId, out _);
_logger.LogInformation(
"Session terminated: Session={SessionId}, Duration={Duration}min",
sessionId,
session.EndedAt.Value.Subtract(session.StartedAt ?? session.CreatedAt).TotalMinutes);
}
// --------------------------------------------------------
// Migration (GPU Failure Recovery)
// --------------------------------------------------------
public async Task MigrateSessionAsync(string sessionId)
{
var session = await GetOrThrowAsync(sessionId);
var oldNodeId = session.AssignedGpuNodeId;
_logger.LogWarning(
"Migrating session: Session={SessionId}, From={OldNode}",
sessionId, oldNodeId);
session.Status = SessionStatus.Migrating;
await PublishEventAsync(session, SessionEventType.MigrationStarted);
await _sessionStore.SaveSessionAsync(session);
// Create checkpoint on failing node (best effort)
try
{
await CreateCheckpointAsync(session);
}
catch (Exception ex)
{
_logger.LogWarning(ex,
"Checkpoint during migration failed, using previous: Session={SessionId}",
sessionId);
}
// Find new node
var newNode = await _nodeRegistry.FindBestNodeForSessionAsync(
session.RegionId, session.GameId, session.Tier);
if (newNode == null)
{
_logger.LogError(
"Migration failed — no available node: Session={SessionId}", sessionId);
await TerminateSessionAsync(sessionId);
return;
}
// Switch connection to new node
await _streamManager.TerminateConnectionAsync(sessionId);
session.AssignedGpuNodeId = newNode.NodeId;
// Restore checkpoint on new node
// (In production, checkpoint would be restored from S3)
var streamInfo = await _streamManager.EstablishConnectionAsync(session, newNode);
session.StreamEndpointUrl = streamInfo.SignalingUrl;
session.SignalingUrl = streamInfo.SignalingUrl;
session.Status = SessionStatus.Streaming;
await PublishEventAsync(session, SessionEventType.MigrationCompleted);
await _sessionStore.SaveSessionAsync(session);
_logger.LogInformation(
"Migration completed: Session={SessionId}, To={NewNode}",
sessionId, newNode.NodeId);
}
// --------------------------------------------------------
// Update Stream Quality
// --------------------------------------------------------
public async Task UpdateQualityAsync(string sessionId, StreamQuality quality)
{
var session = await GetOrThrowAsync(sessionId);
session.Quality = quality;
await PublishEventAsync(session, SessionEventType.QualityChanged,
new Dictionary<string, string>
{
["resolution"] = $"{quality.Width}x{quality.Height}",
["fps"] = quality.Fps.ToString(),
["codec"] = quality.Codec,
["bitrate"] = $"{quality.MaxBitrateMbps}Mbps"
});
await _sessionStore.SaveSessionAsync(session);
_logger.LogInformation(
"Quality updated: Session={SessionId}, Resolution={Resolution}, FPS={Fps}",
sessionId, $"{quality.Width}x{quality.Height}", quality.Fps);
}
// --------------------------------------------------------
// Heartbeat Processing
// --------------------------------------------------------
public async Task ProcessHeartbeatAsync(string sessionId, SessionMetrics metrics)
{
if (!_activeSessions.TryGetValue(sessionId, out var session))
return;
session.LastHeartbeat = DateTime.UtcNow;
session.Metrics.AvgFrameLatencyMs = metrics.AvgFrameLatencyMs;
session.Metrics.AvgGpuUtilization = metrics.AvgGpuUtilization;
session.Metrics.AvgPacketLossRate = metrics.AvgPacketLossRate;
session.Metrics.AvgJitterMs = metrics.AvgJitterMs;
await PublishEventAsync(session, SessionEventType.HeartbeatReceived,
new Dictionary<string, string>
{
["latency_ms"] = metrics.AvgFrameLatencyMs.ToString("F1"),
["gpu_util"] = metrics.AvgGpuUtilization.ToString("P1"),
["packet_loss"] = metrics.AvgPacketLossRate.ToString("P3"),
["jitter_ms"] = metrics.AvgJitterMs.ToString("F1")
});
// Adaptive quality based on metrics
if (metrics.AvgPacketLossRate > 0.03 || metrics.AvgJitterMs > 40)
{
_logger.LogWarning(
"Network degradation detected: Session={SessionId}, Loss={Loss}, Jitter={Jitter}",
sessionId, metrics.AvgPacketLossRate, metrics.AvgJitterMs);
await DowngradeQualityAsync(session);
}
}
// --------------------------------------------------------
// Fleet Health Check (called periodically)
// --------------------------------------------------------
public async Task RunFleetHealthCheckAsync()
{
var regions = await _regionRouter.GetAvailableRegionsAsync();
foreach (var regionId in regions)
{
var nodes = await _nodeRegistry.GetNodesForRegionAsync(regionId);
var totalSlots = nodes.Sum(n => n.MaxConcurrentSessions);
var activeSessions = nodes.Sum(n => n.CurrentSessionCount);
var utilization = totalSlots > 0
? (double)activeSessions / totalSlots
: 0;
_logger.LogInformation(
"Fleet health: Region={Region}, Nodes={Nodes}, " +
"Sessions={Sessions}/{Capacity}, Utilization={Utilization:P1}",
regionId, nodes.Count, activeSessions, totalSlots, utilization);
// Trigger warm pool replenishment if needed
if (utilization > _config.GpuUtilizationTarget)
{
_logger.LogWarning(
"High utilization in {Region}: {Utilization:P1} — scaling up",
regionId, utilization);
}
// Check for degraded/failed nodes
foreach (var node in nodes.Where(n =>
n.Status == GpuNodeStatus.Degraded ||
n.Status == GpuNodeStatus.Failed))
{
if (node.Status == GpuNodeStatus.Failed && node.ActiveSessionIds.Any())
{
foreach (var sid in node.ActiveSessionIds.ToList())
{
_ = MigrateSessionAsync(sid);
}
}
}
}
}
// --------------------------------------------------------
// Private Helpers
// --------------------------------------------------------
private async Task CreateCheckpointAsync(GameSession session)
{
try
{
var checkpointId = await _checkpointService.CreateCheckpointAsync(session);
session.LastCheckpoint = DateTime.UtcNow;
await PublishEventAsync(session, SessionEventType.CheckpointCreated,
new Dictionary<string, string> { ["checkpoint_id"] = checkpointId });
}
catch (Exception ex)
{
_logger.LogError(ex,
"Checkpoint failed: Session={SessionId}", session.SessionId);
}
}
private async Task DowngradeQualityAsync(GameSession session)
{
var q = session.Quality;
// Step down resolution
if (q.Height >= 2160)
{
q.Width = 1920; q.Height = 1080; q.MaxBitrateMbps = 15;
}
else if (q.Height >= 1080)
{
q.Width = 1280; q.Height = 720; q.MaxBitrateMbps = 8;
}
// Step down FPS
if (q.Fps > 60)
{
q.Fps = 60;
}
await UpdateQualityAsync(session.SessionId, q);
}
private static StreamQuality GetDefaultQuality(UserTier tier) => tier switch
{
UserTier.Ultra => new StreamQuality
{
Width = 3840, Height = 2160, Fps = 120,
Codec = "AV1", MaxBitrateMbps = 50, RayTracing = true
},
UserTier.Priority => new StreamQuality
{
Width = 1920, Height = 1080, Fps = 60,
Codec = "H264", MaxBitrateMbps = 15, RayTracing = true
},
_ => new StreamQuality
{
Width = 1280, Height = 720, Fps = 60,
Codec = "H264", MaxBitrateMbps = 5, RayTracing = false
}
};
private async Task<GameSession> GetOrThrowAsync(string sessionId)
{
// Check in-memory first
if (_activeSessions.TryGetValue(sessionId, out var cached))
return cached;
var session = await _sessionStore.GetSessionAsync(sessionId);
if (session == null)
throw new SessionNotFoundException(sessionId);
_activeSessions[sessionId] = session;
return session;
}
private async Task PublishEventAsync(
GameSession session,
SessionEventType eventType,
Dictionary<string, string>? properties = null)
{
var evt = new SessionEvent
{
SessionId = session.SessionId,
UserId = session.UserId,
EventType = eventType,
Properties = properties ?? new Dictionary<string, string>()
};
await _eventBus.PublishAsync(evt);
}
}
// ============================================================
// Custom Exceptions
// ============================================================
public sealed class SessionNotFoundException : Exception
{
public SessionNotFoundException(string sessionId)
: base($"Session not found: {sessionId}") { }
}
public sealed class NoGpuAvailableException : Exception
{
public NoGpuAvailableException(string message) : base(message) { }
}
public sealed class CapacityExceededException : Exception
{
public CapacityExceededException(string message) : base(message) { }
}
public sealed class InvalidSessionStateException : Exception
{
public InvalidSessionStateException(string message) : base(message) { }
}
}
Architecture Highlights
- Circuit Breaker Pattern: The
_gpuNodeCircuitBreakerprevents cascading failures when GPU node APIs are slow or unresponsive. After 5 consecutive failures, the circuit opens for 30 seconds, falling back to cached data. - Actor-Based Session Management: Each session has its own
CancellationTokenSourcefor isolated background monitoring. Sessions don't interfere with each other's lifecycle. - Graceful Degradation: Network quality degradation automatically triggers resolution/FPS downgrade. GPU failures trigger migration without user-facing errors.
- Observability: Every state transition emits a
SessionEventto the event bus, enabling real-time dashboards and historical analysis in ClickHouse.
26. Conclusion
Designing a cloud gaming platform at the scale of GeForce NOW is one of the most complex distributed systems challenges in existence. It sits at the intersection of GPU virtualization, real-time video encoding, network engineering, edge computing, and game development — a convergence that few engineers have the breadth to master.
The key architectural principles that emerge from this analysis are:
- Latency is everything. Every architectural decision — from GPU placement to codec selection to network protocol — must be evaluated through the lens of its impact on glass-to-glass latency.
- The control plane and data plane must be completely decoupled. Session management operates at web scale; game streams operate at real-time scale. Mixing these concerns creates dangerous coupling.
- GPU utilization drives economics. An idle GPU is a money-losing GPU. Warm pools, demand forecasting, and tiered access are not nice-to-haves — they're survival requirements.
- Edge placement is non-negotiable. You cannot serve sub-50ms latency from centralized data centers. The GPU must be within 100km of the player.
- Battle-tested protocols win. WebRTC was designed for exactly this use case — interactive, latency-sensitive, adaptive, NAT-traversal-capable. Don't reinvent the wheel.
- Observability is the difference between uptime and downtime. When 3 million users are streaming simultaneously, you need to detect GPU failures, quality degradation, and capacity exhaustion within seconds, not minutes.
The cloud gaming market will continue to grow as GPU costs decrease, network infrastructure improves (5G, Wi-Fi 7), and more publishers embrace streaming-native game design. By 2030, cloud gaming is projected to account for 30% of all gaming revenue. The engineers who understand this stack — from silicon to screen — will be among the most sought-after in the industry.
- NVIDIA GeForce NOW Architecture Whitepaper (2025)
- Google Stadia Post-Mortem: Why Latency Economics Failed
- WebRTC in the Wild: Real-World Performance at Scale (ACM MMSys 2025)
- "Designing Data-Intensive Applications" by Martin Kleppmann — Chapter on Stream Processing
- NVIDIA vGPU Technology Deep Dive (GTC 2025)