system-design46 min read

How to Design Cloud Gaming Platform like GeForce NOW — A Senior+ Guide | Ayodhyya

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

System Design Senior+ 10000+ Words 8 Diagrams  |  Published Jul 14, 2026  |  48 min read

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.

Why This Article Exists: Most system design resources cover web-scale systems (URL shorteners, social media feeds). Cloud gaming requires deep expertise in GPU virtualization, real-time video encoding, network protocols, and edge computing. This article covers the full stack — from silicon to screen — for engineers designing systems at million-concurrent scale.

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

#RequirementDescription
F1User Authentication & ProfilesSign up, sign in, profile management, linked accounts (Steam, Epic)
F2Game Library ManagementBrowse games, search, filter by genre, platform compatibility
F3Session LaunchUser selects a game, system provisions a GPU, launches game, streams video
F4Real-Time Input StreamingCapture keyboard, mouse, controller input and transmit to cloud VM
F5Video StreamingEncode rendered frames, stream to client at 60/120/240 FPS
F6Session Suspension & ResumeSuspend game state to disk, resume on reconnect or different device
F7Cloud Save SynchronizationPeriodic checkpoint sync across devices and regions
F8Subscription ManagementTiered plans (Free, Priority, Ultra), billing, entitlements
F9Multiplayer SupportParty creation, multiplayer sessions, voice chat
F10Game Installation & UpdatesPre-install popular games on GPU nodes, manage game patches
F11Quality SettingsResolution (720p-4K), bitrate, FPS selection per user tier
F12Anti-Cheat EnforcementServer-side anti-cheat scanning, integrity verification

Non-Functional Requirements

#RequirementTarget
NF1Glass-to-Glass Latency< 80ms (target < 50ms for competitive)
NF2Video QualityUp to 4K 120fps, HDR, 50 Mbps bitrate
NF3Availability99.95% uptime (allowing 4.38 hrs downtime/year)
NF4Concurrent SessionsSupport 10M+ concurrent sessions globally
NF5Session Start Time< 5s warm start, < 15s cold start
NF6ScalabilityAuto-scale GPU fleet based on demand prediction
NF7FairnessQueue time < 30s for free tier, < 5s for paid
NF8Session DurationUp to 6-hour sessions with auto-suspend after 15min idle
NF9Data PrivacyGDPR/CCPA compliance, encrypted game saves, no input logging
NF10Cost EfficiencyGPU utilization > 75%, < $0.15/gaming-hour at scale

3. Capacity Estimation

Concurrent Sessions

Key Numbers (Year 3 projections):
  • 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 TierResolutionFPSBitrateSessions (peak)Bandwidth
Free (720p)1280×720605 Mbps1,000,0005 Tbps
Priority (1080p)1920×10806015 Mbps1,200,00018 Tbps
Ultra (4K)3840×216012050 Mbps800,00040 Tbps
Total3,000,00063 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

Storage Requirements:
  • 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

erDiagram USER { uuid user_id PK string email string display_name string password_hash string tier timestamp created_at jsonb preferences } GAME { uuid game_id PK string title string publisher jsonb system_requirements int install_size_gb boolean ray_tracing string[] compatible_tiers } SESSION { uuid session_id PK uuid user_id FK uuid game_id FK uuid gpu_node_id FK string status timestamp started_at timestamp ended_at jsonb quality_settings jsonb network_stats } GPU_NODE { uuid node_id PK string region string gpu_model int total_vcpus int total_memory_gb string status float utilization } SAVE_STATE { uuid save_id PK uuid user_id FK uuid game_id FK blob checkpoint_data int version timestamp synced_at } SUBSCRIPTION { uuid sub_id PK uuid user_id FK string plan timestamp start_date timestamp end_date string status } DEVICE { uuid device_id PK uuid user_id FK string device_type string os string browser jsonb input_capabilities } USER ||--o{ SESSION : "creates" USER ||--o{ SAVE_STATE : "owns" USER ||--o| SUBSCRIPTION : "has" USER ||--o{ DEVICE : "registers" GAME ||--o{ SESSION : "launched_in" GAME ||--o{ SAVE_STATE : "saved_in" GPU_NODE ||--o{ SESSION : "hosts"

Core Tables Schema

TablePartition KeySort KeyIndexStorage
usersuser_idemail (unique)DynamoDB / PostgreSQL
gamesgame_idtitle, publisherPostgreSQL + Read Replica
sessionssession_idstarted_atuser_id, statusDynamoDB (TTL: 7 days)
gpu_nodesnode_idregion, statusetcd + PostgreSQL
save_statesuser_idgame_id + versionS3 + DynamoDB metadata
subscriptionsuser_idstart_datestatusPostgreSQL
analytics_eventsevent_idtimestampuser_id, session_idKafka → ClickHouse
Design Note: Sessions are ephemeral — use DynamoDB with TTL to auto-expire old session records. Save states use S3 with versioning for durability. Game metadata is read-heavy and benefits from PostgreSQL with read replicas and aggressive caching via Redis.

5. API Design

RESTful API Endpoints

MethodEndpointDescriptionAuth
POST/api/v1/auth/loginAuthenticate user, return JWTNone
POST/api/v1/auth/refreshRefresh access tokenRefresh token
GET/api/v1/gamesList games (paginated, filterable)Bearer
GET/api/v1/games/{id}Game detailsBearer
POST/api/v1/sessionsLaunch a new gaming sessionBearer
GET/api/v1/sessions/{id}Session status & stream infoBearer
PUT/api/v1/sessions/{id}/qualityUpdate stream qualityBearer
DELETE/api/v1/sessions/{id}Terminate sessionBearer
POST/api/v1/sessions/{id}/suspendSuspend and save stateBearer
POST/api/v1/sessions/{id}/resumeResume suspended sessionBearer
GET/api/v1/saves/{userId}/{gameId}List save statesBearer
POST/api/v1/saves/{userId}/{gameId}Upload save stateBearer
GET/api/v1/subscriptionCurrent subscription detailsBearer
POST/api/v1/subscription/upgradeUpgrade planBearer
GET/api/v1/queue/statusQueue 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"
}
sequenceDiagram participant Client participant API as API Gateway participant Orchestrator as Session Orchestrator participant GPU as GPU Node participant Signaling as WebRTC Signaling Client->>API: POST /sessions {game_id: "cyberpunk2077"} API->>Orchestrator: Allocate GPU for session Orchestrator->>GPU: Provision VM + launch game GPU-->>Orchestrator: VM ready, stream endpoint Orchestrator-->>API: Session created API-->>Client: 201 {session_id, stream_info} Client->>Signaling: WebSocket connect (session_id) Signaling->>GPU: Forward signaling offer GPU-->>Signaling: SDP answer + ICE candidates Signaling-->>Client: WebRTC connection established loop Game Loop (16.67ms @ 60fps) GPU-->>Client: Encoded video frame (H.264/AV1) Client-->>GPU: Input events (keyboard/mouse/controller) end

6. High-Level Architecture

graph TB subgraph "Client Layer" Browser["Web Browser"] Mobile["Mobile App"] TV["Smart TV App"] PC["Desktop Client"] end subgraph "Edge Layer" CDN["Global CDN"] STUN["STUN/TURN Servers"] EdgeGPU["Edge GPU Nodes"] end subgraph "Control Plane" LB["Load Balancer"] APIGW["API Gateway"] Auth["Auth Service"] SessionOrch["Session Orchestrator"] QueueMgr["Queue Manager"] Billing["Billing Service"] Notification["Notification Service"] end subgraph "Data Plane" StreamSvc["Stream Service"] InputSvc["Input Processing"] EncodeSvc["Encoding Service"] SaveSvc["Save State Service"] DRM["DRM Service"] AntiCheat["Anti-Cheat Service"] end subgraph "GPU Fleet" GPUUS["US Region GPUs"] GPEU["EU Region GPUs"] GPAS["Asia Region GPUs"] GPUFleet["Fleet Manager"] end subgraph "Storage Layer" Redis["Redis Cluster"] DynamoDB["DynamoDB"] PostgreSQL["PostgreSQL"] S3["S3 / Object Storage"] Kafka["Kafka Streams"] ClickHouse["ClickHouse"] end Browser & Mobile & TV & PC --> CDN Browser & Mobile & TV & PC --> LB LB --> APIGW APIGW --> Auth APIGW --> SessionOrch APIGW --> QueueMgr APIGW --> Billing SessionOrch --> GPUFleet GPUFleet --> GPUUS & GPEU & GPAS GPUUS & GPEU & GPAS --> StreamSvc StreamSvc --> EncodeSvc EncodeSvc --> EdgeGPU EdgeGPU --> CDN CDN --> Browser & Mobile & TV & PC InputSvc --> SessionOrch SaveSvc --> S3 DRM --> SessionOrch AntiCheat --> SessionOrch SessionOrch --> Redis SessionOrch --> DynamoDB Auth --> PostgreSQL Billing --> PostgreSQL StreamSvc --> Kafka Kafka --> ClickHouse

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.

Key GPU Models for Cloud Gaming:
  • 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

graph LR A["Game Logic\n(CPU)"] --> B["Render Command\nGeneration"] B --> C["GPU Rendering\n(Vulkan/DX12)"] C --> D["Frame Buffer\n(RGBA)"] D --> E["NVENC Hardware\nEncoder"] E --> F["Encoded Bitstream\n(H.264/AV1)"] F --> G["Packetization\n(RTP/QUIC)"] G --> H["Network\nTransport"] H --> I["Client Decoder\n(Hardware)"] I --> J["Display"]

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

ResourceIsolation MethodPer-Session Allocation
GPU Compute CoresCUDA Process IsolationConfigurable: 1/4 to full GPU
GPU Memory (VRAM)Address Space Isolation2-8 GB per session
NVENC EncoderTime-Sliced SharingDedicated encoder per 2-4 sessions
GPU Decode EngineShared (for game assets)Shared across sessions on same GPU
CPU (vCPUs)cgroup/VM Isolation2-4 vCPUs per session
System RAMVM Memory Limit4-16 GB per session
Network BandwidthTraffic ShapingCapped 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.

CodecEncoding LatencyQuality (VMAF)Bandwidth (1080p60)Client Decode Cost
H.264 (NVENC)0.5-1ms958-15 MbpsVery Low (universal)
HEVC/H.265 (NVENC)0.8-1.5ms975-10 MbpsLow (most devices)
AV1 (NVENC)1-2ms984-8 MbpsMedium (newer devices)
AV1 (Software)5-15ms983-7 MbpsMedium

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

graph TB subgraph "Server Side" GPU["GPU Render"] NVENC["NVENC Encoder"] RTP["RTP Packetizer"] JitterBuf["Jitter Buffer\n(Server)"] ICE["ICE Agent"] DTLS["DTLS-SRTP"] end subgraph "Network" UDP["UDP/QUIC Transport"] TURN["TURN Relay\n(if needed)"] end subgraph "Client Side" DecICE["ICE Agent"] SRTP["DTLS-SRTP"] JitterDec["Jitter Buffer\n(Client)"] HWDec["Hardware Decoder\n(H.264/AV1)"] Display["Display"] end GPU --> NVENC --> RTP --> JitterBuf --> ICE --> DTLS DTLS --> UDP UDP --> TURN TURN --> DecICE DecICE --> SRTP --> JitterDec --> HWDec --> Display

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:

graph LR A["Player Presses\nButton (0ms)"] --> B["Client Captures\nInput (1ms)"] B --> C["Client Encodes\n+ Sends (2ms)"] C --> D["Network Transit\n(10-30ms)"] D --> E["Server Receives\nInput (1ms)"] E --> F["Game Processes\nInput (5-8ms)"] F --> G["GPU Renders\nFrame (8-16ms)"] G --> H["NVENC Encodes\nFrame (1ms)"] H --> I["Network Return\n(10-30ms)"] I --> J["Client Decodes\n+ Displays (2ms)"] J --> K["Player Sees\nResult (0ms)"]

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

Client-Side Prediction: The client predicts the outcome of input locally and shows a speculative result. When the server's authoritative state arrives, the client reconciles. This is especially effective for:
  • 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

TechniqueLatency SavedTrade-off
Edge GPU placement (same city)15-30msHigher infrastructure cost
QUIC over TCP5-15msMore complex implementation
Input prioritization (separate channel)2-5msAdditional WebRTC data channel
Client-side prediction30-50ms perceivedPossible visual correction artifacts
Adaptive frame rateVariableQuality reduction under congestion
Pre-rendered cutscene bypass0ms (skip render)Content must be pre-encoded

10. Session Management & Lifecycle

stateDiagram-v2 [*] --> Queued: User requests session Queued --> Provisioning: GPU available Queued --> Queued: All GPUs busy (queue) Provisioning --> ColdStarting: No warm VM ready Provisioning --> WarmStarting: Warm VM available ColdStarting --> Running: VM booted + game loaded (~15s) WarmStarting --> Running: Game resumed (~3s) Running --> Streaming: Client connected Streaming --> Running: Client disconnected (temporary) Streaming --> Suspending: User pauses / timeout Running --> Suspending: 15min idle timeout Suspending --> Suspended: State saved to S3 Suspended --> WarmStarting: User resumes Suspended --> Terminated: 30-day expiry Streaming --> Terminating: User ends session Running --> Terminating: 6-hour max duration Terminating --> [*]: Resources freed

Warm Start vs Cold Start

StateWhat's ReadyTime to PlayResource Cost
Cold StartNothing — bare metal/VM provisioned15-30 secondsFull boot + game load
Warm StartVM running, game pre-loaded in memory3-5 secondsVM idle cost while waiting
Hot ResumeVM suspended with full state in RAM1-2 secondsRAM 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:

  1. Checks the user's region and finds the closest GPU pool
  2. Looks for a warm VM with the requested game already loaded
  3. If no warm VM exists, triggers cold provisioning (install game, boot VM)
  4. Assigns the session to the VM and returns stream connection details
  5. 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

graph TB subgraph "License Server" LS["License Service"] entitlements["Entitlement DB"] publisher["Publisher API"] end subgraph "GPU Node" VM["Game VM"] TEE["Trusted Execution\nEnvironment"] DRMClient["DRM Agent"] EncFS["Encrypted\nFilesystem"] end User["Player"] -->|Entitlement Check| LS LS -->|Grant License| DRMClient DRMClient -->|Unlock Game| VM VM -->|Run in TEE| TEE TEE -->|Encrypted Assets| EncFS publisher -->|Sales Data| LS

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 TypeSizeLatencyCross-RegionGame Support
Native Game Save1-100 MB< 1s uploadYesGames with cloud save API
Memory Checkpoint8-64 GB30-120s uploadSame region onlyAll games (platform-level)
Delta Checkpoint100 MB-2 GB5-30s uploadSame regionAll 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:

  1. Co-located: Place both players' VMs on the same GPU node or rack, minimizing inter-player latency
  2. Game-server model: Route both players' inputs to a dedicated game server VM, which then sends state to both
  3. P2P within cloud: Connect both players' VMs directly via internal network for peer-to-peer gameplay
Optimal Strategy: For competitive games, use the game-server model with co-located servers. Place the authoritative game server in the same data center as both players' rendering VMs. This keeps inter-player latency under 5ms (same rack) while maintaining server authority for anti-cheat.

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:

graph TB A["Network Monitor\n(TWCC Feedback)"] --> B{Bandwidth\nEstimation} B -->|"> 30 Mbps"| C["4K 120fps\n50 Mbps target"] B -->|"15-30 Mbps"| D["1080p 60fps\n15 Mbps target"] B -->|"5-15 Mbps"| E["720p 60fps\n8 Mbps target"] B -->|"2-5 Mbps"| F["720p 30fps\n4 Mbps target"] B -->|"< 2 Mbps"| G["Queue Suspend\nSession"] C --> H["NVENC Reconfigure\nResolution + Bitrate"] D --> H E --> H F --> H H --> I["Client Decoder\nAdapts Automatically"]

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 BandwidthRTTPacket LossResolutionFPSCodecBitrate
> 50 Mbps< 20ms< 0.1%4K120AV150 Mbps
20-50 Mbps< 40ms< 1%1080p60H.26415 Mbps
10-20 Mbps< 60ms< 2%1080p60H.26410 Mbps
5-10 Mbps< 80ms< 3%720p60H.2646 Mbps
2-5 MbpsAny< 5%720p30H.2643 Mbps
< 2 MbpsAnyAnySuspend 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.

graph TB subgraph "Tier 1: Hyperscale (20+ regions)" US1["US East\nVirginia"] US2["US West\nOregon"] EU1["EU West\nIreland"] EU2["EU Central\nFrankfurt"] AP1["Asia Pacific\nTokyo"] AP2["Asia Pacific\nMumbai"] end subgraph "Tier 2: Regional (100+ locations)" US1A["NYC Metro"] US1B["Atlanta"] US2A["LA Metro"] EU1A["London"] EU1B["Paris"] AP1A["Osaka"] end subgraph "Tier 3: Edge (500+ PoPs)" ISP1["Comcast ISP\nNode"] ISP2["AT&T ISP\nNode"] ISP3["BT ISP\nNode"] IX1["DE-CIX\nFrankfurt"] IX2["AMS-IX\nAmsterdam"] end Tier1 --> Tier2 Tier2 --> Tier3

GeForce NOW uses a three-tier GPU placement strategy:

  1. Tier 1 (Hyperscale): Large data centers with thousands of GPUs for peak capacity and game library storage
  2. Tier 2 (Regional): Mid-size facilities in major metro areas for low-latency coverage
  3. 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

ComponentRoleScale
Origin (GPU Node)Encodes and originates the stream1 stream per session
Relay NodeForwards stream to reduce WAN hops10,000+ relays globally
ISP EdgeFinal delivery hop to end user500+ ISP partnerships
TURN ServerNAT traversal fallback1,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

FeatureFreePriority ($9.99/mo)Ultra ($19.99/mo)
Session Length1 hour6 hours6 hours
Queue PriorityLowestHighHighest
RTX GraphicsNoYes (1080p)Yes (4K)
Ray TracingNoYesYes (Ultra)
Session Length1 hour6 hours6 hours
Concurrent Sessions112
HDR SupportNoNoYes
Save Slots10100Unlimited
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
Defense Layers:
  1. Server-side anti-cheat (BattlEye, EasyAntiCheat) runs on the GPU VM
  2. VM isolation via hardware-enforced virtualization (AMD SEV, Intel TDX)
  3. Encrypted video stream (DTLS-SRTP) prevents interception
  4. Input rate limiting and pattern detection catches automated bots
  5. 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

MetricCollection PointTargetAlert Threshold
Glass-to-Glass LatencyClient + Server< 80ms> 120ms
Frame Rate (rendered)GPU nodeTarget FPS< 90% of target
Frame Rate (displayed)ClientTarget FPS< 85% of target
Packet LossWebRTC stats< 0.5%> 2%
JitterWebRTC stats< 10ms> 30ms
GPU Utilizationnvidia-smi60-80%> 95% or < 30%
Session Start TimeOrchestrator< 5s warm> 15s warm
Queue Wait TimeQueue Manager< 10s (paid)> 30s (paid)
Session Crash RateVM 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}.

graph TB subgraph "Shard Router" SR["Shard Router\n(Consistent Hashing)"] end subgraph "User Shards" S1["Shard 0\nUsers 0-199K"] S2["Shard 1\nUsers 200K-399K"] S3["Shard 2\nUsers 400K-599K"] S255["Shard 255\n..."] end subgraph "Storage" DDB1["DynamoDB\nShard 0"] DDB2["DynamoDB\nShard 1"] DDB3["DynamoDB\nShard 2"] DDB255["DynamoDB\nShard 255"] end SR --> S1 & S2 & S3 & S255 S1 --> DDB1 S2 --> DDB2 S3 --> DDB3 S255 --> DDB255

21. Caching Strategy

Multi-Layer Cache Architecture

LayerTechnologyTTLWhat's CachedHit Rate Target
L1: ClientBrowser/App CacheSessionGame metadata, UI assets95%
L2: Edge CDNCloudFront / Fastly5min-1hrGame art, static assets90%
L3: ApplicationRedis Cluster5-60minUser profile, session state, queue position85%
L4: DatabasePostgreSQL BufferPermanentFrequently accessed rows80%

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

graph TB subgraph "Global Fleet Controller" GFC["Global Fleet\nController"] Forecast["Demand\nForecaster"] Billing["GPU Cost\nOptimizer"] end subgraph "US Region" USM["US Master\nOrchestrator"] USR1["US-East\n500 GPUs"] USR2["US-West\n400 GPUs"] end subgraph "EU Region" EUM["EU Master\nOrchestrator"] EUR1["EU-West\n350 GPUs"] EUR2["EU-Central\n300 GPUs"] end subgraph "APAC Region" APM["APAC Master\nOrchestrator"] APR1["APAC-Tokyo\n250 GPUs"] APR2["APAC-Mumbai\n200 GPUs"] end GFC --> Forecast GFC --> Billing GFC --> USM & EUM & APM USM --> USR1 & USR2 EUM --> EUR1 & EUR2 APM --> APR1 & APR2

The Global Fleet Controller orchestrates GPU allocation across regions using three key algorithms:

  1. Geo-assignment: Route each session request to the closest region with available capacity, considering both geographic proximity and current load
  2. Demand forecasting: ML model predicts hourly demand per region based on historical patterns, timezone effects, game release schedules, and promotions
  3. 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

ComponentUnit CostQuantity (Peak)Monthly Cost
NVIDIA L40S GPU Server (8 GPUs)$2.50/GPU-hr500,000 GPU slots$90M
NVIDIA A100 GPU Server (8 GPUs)$3.50/GPU-hr100,000 GPU slots$25M
Edge GPU Nodes (T4)$0.80/GPU-hr200,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 + ClickHouse10 TB/day ingest$300K
CDN + STUN/TURN63 Tbps egress$8M
Engineering Team (200 engineers)$200K avg salary200$3.3M
Total Monthly~$144M
Revenue vs Cost: With 20M MAU and an average ARPU of $8/month (blended free + paid), monthly revenue is ~$160M. This yields a gross margin of ~11% — tight, but improving as GPU costs decrease and utilization improves. The path to profitability requires: (1) higher paid subscriber conversion (currently ~15%, target 25%), (2) GPU utilization above 80%, and (3) edge GPU deployment reducing bandwidth costs.

24. Interview Q&A

Q1: How would you handle a sudden spike in demand (e.g., a major game launch like GTA VI)?
A: Pre-provision GPU capacity 48-72 hours before the launch using demand forecasting. Implement a queue system with priority tiers — paid users get instant access, free users queue with estimated wait times. Use predictive autoscaling based on pre-registration data and historical launch patterns. Have warm standby capacity in each region at 120% of normal peak. During the spike, temporarily relax quality targets (720p for free tier) to fit more concurrent sessions per GPU.
Q2: What happens when a GPU node fails mid-session? How do you ensure players don't lose progress?
A: Three-layer defense: (1) Periodic delta checkpoints to S3 every 30-60 seconds ensure maximum data loss is one minute of gameplay. (2) The Session Orchestrator detects GPU failure via heartbeat timeout (< 5s) and immediately provisions a new GPU node. (3) The new node loads the most recent checkpoint, and the client receives updated stream connection details via the signaling server. Total recovery time: 5-10 seconds. The player sees a brief "Reconnecting..." overlay. For competitive games, the session may be forfeited with result based on the last checkpoint.
Q3: How do you minimize glass-to-glass latency? Walk through the full pipeline.
A: Each stage contributes latency: GPU render (8-16ms), NVENC encode (0.5-1ms), packetization (0.1ms), network to client (5-30ms), jitter buffer (5-20ms), client decode (1-2ms), display (1-2ms). Optimization strategies per stage: (1) GPU: pre-render next frame while encoding current, use Vulkan for low driver overhead. (2) Encode: zero-lookahead, no B-frames, slice-based encoding for resilience. (3) Network: edge GPU placement within 100km of player, QUIC transport, no TCP retransmission delays. (4) Client: hardware-accelerated decoding, display-pipeline sync (vsync-aligned decode), client-side prediction for input. Best achievable: ~40ms total, competitive local gaming: ~15-25ms.
Q4: How would you design the session queue system for 100,000 free-tier users waiting during peak hours?
A: Use a priority queue implemented as a sorted Redis Sorted Set with composite scores: (priority_tier, timestamp). Priority tiers: Ultra=0, Priority=1, Free=2. Within each tier, FIFO ordering by request timestamp. The Queue Manager polls GPU availability from the Fleet Manager and dequeues the next session when capacity opens. Notify the client via WebSocket of queue position and estimated wait time. To prevent thundering herd: use a token bucket rate limiter at the API gateway. For queue fairness: cap maximum queue wait at 30 minutes, after which the user gets a priority bump.
Q5: Compare WebRTC vs custom UDP vs RTMP for game stream delivery.
A: WebRTC is the clear winner for interactive game streaming: (1) Built-in ICE/STUN/TURN for NAT traversal — critical since players are behind consumer routers. (2) Adaptive bitrate via TWCC/REMB congestion control. (3) DTLS-SRTP encryption by default. (4) Jitter buffering with adaptive algorithms. (5) Client-side decoder ecosystem (hardware-accelerated H.264/VP8/VP9/AV1). Custom UDP gives more control but requires building NAT traversal, congestion control, and encryption from scratch. RTMP adds 2-5 seconds of latency (designed for broadcast, not interactive). The only downside of WebRTC is its complexity — the ORTC API and SDP negotiation are notoriously difficult to debug.
Q6: How do you handle the cost of storing 50 million user save states (potentially petabytes)?
A: Multi-tier storage: (1) Active saves (last 30 days) in S3 Standard — instant access, ~$0.023/GB/month. (2) Inactive saves (30-180 days) in S3 Infrequent Access — 40% cheaper. (3) Old saves (180+ days) in S3 Glacier Instant Retrieval — 70% cheaper, 1-5ms access. (4) Delta saves with deduplication: many save states share 90%+ identical data; use content-addressable storage (CAS) to deduplicate. (5) User-driven cleanup: show users their save count and let them delete old saves. With deduplication and tiering, effective storage cost is ~$0.005/GB/month, bringing 25 PB down to ~$125K/month.
Q7: How do you prevent abuse — users running multiple sessions simultaneously on a single subscription?
A: Multiple enforcement layers: (1) Server-side: the Subscription service tracks active sessions per user. Session creation validates against tier limits (Free/Priority: 1 session, Ultra: 2 sessions). (2) Device fingerprinting: track device IDs and detect account sharing across too many devices. (3) IP analysis: flag accounts with sessions from geographically impossible locations within short time windows. (4) Behavioral analysis: session input patterns can detect automated/scripted gameplay. (5) Rate limiting: API rate limits prevent rapid session cycling. Enforcement is progressive: warning → temporary suspension → permanent ban for repeated violations.
Q8: Design the real-time analytics pipeline for monitoring 3 million concurrent gaming sessions.
A: Each session generates telemetry at 1Hz (aggregated from per-frame data): latency, FPS, bitrate, packet loss, GPU utilization, input events/sec. With 3M sessions: 3M events/second = ~260 billion events/day. Pipeline: (1) Each GPU node runs a local collector that aggregates per-frame metrics into 1-second summaries. (2) summaries are published to regional Kafka topics (partitioned by session_id). (3) Kafka Streams process real-time alerts (e.g., latency spike > 200ms) and push to PagerDuty. (4) Batch inserts into ClickHouse for historical analysis via Kafka Connect. (5) Grafana dashboards query ClickHouse for real-time fleet health. (6) Anomaly detection: Isolation Forest model runs on 5-minute windows to detect unusual patterns (sudden quality degradation on a specific GPU batch).
Q9: How would you handle game updates and patches in a cloud gaming environment?
A: This is unique to cloud gaming — you control the servers. Strategy: (1) Staged rollout: push patches to 5% of warm VMs first, monitor for crashes/performance regressions. (2) Blue-green deployment: maintain two pools of warm VMs (blue and green). Patch green pool while blue serves traffic, then swap. (3) Game binary caching: store pre-installed games on shared NVMe volumes accessible by multiple VMs, reducing per-VM storage to just the writable layer (using overlay filesystems). (4) Update during off-peak: schedule major patches for 3-6 AM in each region when GPU utilization drops. (5) Publisher coordination: use publisher APIs to get update manifests in advance and pre-stage downloads. Total update time for 2,000 games: ~4 hours with staggered rolling updates.
Q10: What are the biggest failure modes and how do you mitigate them?
A: Top 5 failure modes: (1) GPU driver crash: Watchdog process monitors GPU health, auto-restarts driver, migrates sessions. MTTR: 30s. (2) Network partition between regions: Each region operates autonomously with local session management. No cross-region dependencies for active sessions. (3) Signaling server overload: WebSocket connections are stateless — horizontally scale signaling servers behind load balancers. Use sticky sessions for connection establishment only. (4) Database hot shard: Popular users (streamers with millions of followers) create hot shards. Mitigate with read replicas and user data denormalization. (5) Thundering herd on game launch: Pre-queue users before launch, stagger session creation with exponential backoff, pre-warm GPU fleet.
Q11: How does the system handle a user switching devices mid-session (e.g., from PC to phone)?
A: Device switching leverages session migration: (1) User initiates switch from new device. (2) Session Orchestrator captures current session state including stream quality settings, input configuration, and game progress. (3) The existing session continues running on the same GPU node. (4) A new WebRTC connection is established from the new device to the same GPU node. (5) The old connection is terminated. (6) Stream quality adapts to new device capabilities (phone might need 720p, lower bitrate). The GPU node doesn't change — only the client connection changes. Switch time: 2-5 seconds. The game doesn't pause; the user simply sees a brief reconnection screen.
Q12: Explain the trade-offs between running game logic on the server vs. client-side prediction.
A: Pure server-side (no prediction): Maximum security and consistency, but input latency is fully felt (40-100ms round trip). Client-side prediction: Speculatively apply inputs locally, then reconcile with server state. Benefits: perceived latency drops to 0ms for predicted actions. Costs: (1) Code complexity — every game action needs a prediction + rollback path. (2) Visual corrections — when prediction is wrong, the client must "snap back" to the authoritative state, causing visual glitches. (3) Cheating risk — sophisticated clients could manipulate prediction logic. Best practice: predict camera and movement (low risk of correction), don't predict combat outcomes (high correction cost). Use server reconciliation (like Valve's Source engine) for smooth correction.

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 _gpuNodeCircuitBreaker prevents 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 CancellationTokenSource for 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 SessionEvent to 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:

  1. 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.
  2. 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.
  3. 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.
  4. Edge placement is non-negotiable. You cannot serve sub-50ms latency from centralized data centers. The GPU must be within 100km of the player.
  5. Battle-tested protocols win. WebRTC was designed for exactly this use case — interactive, latency-sensitive, adaptive, NAT-traversal-capable. Don't reinvent the wheel.
  6. 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.

Further Reading:
  • 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)

© 2026 Ayodhyya. All rights reserved.

Cloud Gaming Platform System Design — A Senior+ Guide

Published on July 14, 2026