[{"data":1,"prerenderedAt":7169},["ShallowReactive",2],{"blog-/blog/designing-a-scalable-online-chess-platform":3,"related-posts-/blog/designing-a-scalable-online-chess-platform":5450},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"image":11,"categories":12,"author":17,"readingTime":18,"body":19,"_type":5444,"_id":5445,"_source":5446,"_file":5447,"_stem":5448,"_extension":5449},"/blog/designing-a-scalable-online-chess-platform","blog",false,"","Designing a Scalable Online Chess Platform: A System Design Case Study","A comprehensive system design case study on building a server-authoritative, low-latency, real-time online chess platform at scale, handling WebSockets, matchmaking, and consistency.","2026-06-21","/images/blog/online-chess-platform-system-design.svg",[13,14,15,16],"system-design","websockets","backend","realtime","Ayush Jaipuriar","10 min read",{"type":20,"children":21,"toc":5398},"root",[22,30,35,40,54,59,63,70,75,80,135,140,143,149,154,232,247,250,256,261,355,360,378,383,386,392,397,405,433,438,443,448,453,458,466,479,484,489,523,526,532,537,546,551,556,559,565,570,575,580,593,598,603,621,626,629,635,640,645,683,688,695,707,712,745,748,754,759,764,797,802,942,947,1175,1180,1185,1224,1229,1232,1238,1243,1248,1304,1309,1512,1517,1550,1562,1567,1597,1602,1605,1611,1616,1621,1663,1668,1929,1934,2107,2110,2116,2121,2126,2169,2181,2184,2190,2195,2266,2271,2386,2391,2568,2573,2669,2674,2697,2700,2706,2711,2716,2734,2739,2744,2749,2752,2758,2763,2768,2773,2856,2861,2866,2899,2904,2907,2913,2925,2948,2960,2965,2968,2974,2979,2990,3003,3118,3123,3153,3158,3161,3167,3172,3177,3198,3203,3320,3333,3346,3349,3355,3360,3365,3370,3388,3396,3401,3419,3422,3428,3433,3437,3476,3481,3639,3644,3647,3653,3658,3702,3707,3710,3716,3721,3749,3754,3757,3763,3768,3786,3789,3795,3800,3806,3874,3880,4098,4103,4190,4193,4199,4204,4210,4279,4285,4416,4419,4425,4430,4435,4447,4708,4713,4716,4722,4727,4768,4773,4776,4782,4787,4953,4956,4962,4967,5034,5039,5042,5048,5053,5076,5094,5097,5103,5113,5118,5151,5154,5160,5230,5233,5239,5244,5285,5288,5294,5299,5305,5392],{"type":23,"tag":24,"props":25,"children":26},"element","p",{},[27],{"type":28,"value":29},"text","Online chess looks deceptively simple.",{"type":23,"tag":24,"props":31,"children":32},{},[33],{"type":28,"value":34},"At first glance, a chess app feels like a small turn-based game. Two players connect, one player makes a move, the other player responds, and the game continues until checkmate, resignation, timeout, stalemate, or draw.",{"type":23,"tag":24,"props":36,"children":37},{},[38],{"type":28,"value":39},"But once we design it at scale, it becomes a very interesting system design problem.",{"type":23,"tag":24,"props":41,"children":42},{},[43,45,52],{"type":28,"value":44},"The hard part is not the size of a chess move. A move like ",{"type":23,"tag":46,"props":47,"children":49},"code",{"className":48},[],[50],{"type":28,"value":51},"e2e4",{"type":28,"value":53}," is tiny. The hard part is the combination of real-time communication, server-authoritative game state, matchmaking, long-lived WebSocket connections, fault tolerance, reconnection handling, spectator fanout, cheating detection, rating updates, game analysis, and safe deployments without causing reconnect storms.",{"type":23,"tag":24,"props":55,"children":56},{},[57],{"type":28,"value":58},"This case study walks through the design of a scalable online chess website or app similar to Chess.com or Lichess. The goal is to make this both a blog-style explanation and a revision document for system design interviews.",{"type":23,"tag":60,"props":61,"children":62},"hr",{},[],{"type":23,"tag":64,"props":65,"children":67},"h2",{"id":66},"_1-problem-statement",[68],{"type":28,"value":69},"1. Problem Statement",{"type":23,"tag":24,"props":71,"children":72},{},[73],{"type":28,"value":74},"We want to design an online chess platform where users can come online, find opponents, play real-time chess games, chat, review games later, and receive accurate game results.",{"type":23,"tag":24,"props":76,"children":77},{},[78],{"type":28,"value":79},"The system should support:",{"type":23,"tag":81,"props":82,"children":83},"ul",{},[84,90,95,100,105,110,115,120,125,130],{"type":23,"tag":85,"props":86,"children":87},"li",{},[88],{"type":28,"value":89},"Two online players playing a chess game.",{"type":23,"tag":85,"props":91,"children":92},{},[93],{"type":28,"value":94},"A matching system that pairs players based on rating or challenge preferences.",{"type":23,"tag":85,"props":96,"children":97},{},[98],{"type":28,"value":99},"A game engine that validates moves and manages game state.",{"type":23,"tag":85,"props":101,"children":102},{},[103],{"type":28,"value":104},"A chat system between players.",{"type":23,"tag":85,"props":106,"children":107},{},[108],{"type":28,"value":109},"A move log for every game.",{"type":23,"tag":85,"props":111,"children":112},{},[113],{"type":28,"value":114},"Game termination through checkmate, timeout, resignation, stalemate, forfeit, or draw.",{"type":23,"tag":85,"props":116,"children":117},{},[118],{"type":28,"value":119},"Post-game analysis.",{"type":23,"tag":85,"props":121,"children":122},{},[123],{"type":28,"value":124},"Rating updates.",{"type":23,"tag":85,"props":126,"children":127},{},[128],{"type":28,"value":129},"Cheating detection.",{"type":23,"tag":85,"props":131,"children":132},{},[133],{"type":28,"value":134},"Optional spectator support.",{"type":23,"tag":24,"props":136,"children":137},{},[138],{"type":28,"value":139},"The system must be low-latency, low-bandwidth, fault-tolerant, and consistent.",{"type":23,"tag":60,"props":141,"children":142},{},[],{"type":23,"tag":64,"props":144,"children":146},{"id":145},"_2-functional-requirements",[147],{"type":28,"value":148},"2. Functional Requirements",{"type":23,"tag":24,"props":150,"children":151},{},[152],{"type":28,"value":153},"The core functional requirements are:",{"type":23,"tag":81,"props":155,"children":156},{},[157,162,167,172,177,182,187,192,197,202,207,212,217,222,227],{"type":23,"tag":85,"props":158,"children":159},{},[160],{"type":28,"value":161},"Users should be able to create or accept chess challenges.",{"type":23,"tag":85,"props":163,"children":164},{},[165],{"type":28,"value":166},"The system should match players against suitable opponents.",{"type":23,"tag":85,"props":168,"children":169},{},[170],{"type":28,"value":171},"Once a match is found, the system should create a game and assign one player as white and the other as black.",{"type":23,"tag":85,"props":173,"children":174},{},[175],{"type":28,"value":176},"White should make the first move.",{"type":23,"tag":85,"props":178,"children":179},{},[180],{"type":28,"value":181},"Both players should make moves one after the other.",{"type":23,"tag":85,"props":183,"children":184},{},[185],{"type":28,"value":186},"The system should validate every move.",{"type":23,"tag":85,"props":188,"children":189},{},[190],{"type":28,"value":191},"Players should not be able to cancel or roll back moves.",{"type":23,"tag":85,"props":193,"children":194},{},[195],{"type":28,"value":196},"The system should maintain a log of all moves.",{"type":23,"tag":85,"props":198,"children":199},{},[200],{"type":28,"value":201},"The system should detect game-ending states such as checkmate, stalemate, resignation, timeout, or forfeit.",{"type":23,"tag":85,"props":203,"children":204},{},[205],{"type":28,"value":206},"The system should support in-game chat.",{"type":23,"tag":85,"props":208,"children":209},{},[210],{"type":28,"value":211},"The system should allow users to retrieve game state.",{"type":23,"tag":85,"props":213,"children":214},{},[215],{"type":28,"value":216},"The system should optionally support spectators.",{"type":23,"tag":85,"props":218,"children":219},{},[220],{"type":28,"value":221},"The system should support post-game analysis.",{"type":23,"tag":85,"props":223,"children":224},{},[225],{"type":28,"value":226},"The system should support cheating detection.",{"type":23,"tag":85,"props":228,"children":229},{},[230],{"type":28,"value":231},"The system should update player ratings after the game ends.",{"type":23,"tag":233,"props":234,"children":235},"blockquote",{},[236],{"type":23,"tag":24,"props":237,"children":238},{},[239,245],{"type":23,"tag":240,"props":241,"children":242},"span",{},[243],{"type":28,"value":244},"!IMPORTANT",{"type":28,"value":246},"\nThe client cannot be trusted to validate chess moves. The browser or mobile app can be modified, hacked, or made to send illegal moves. Therefore, all important game validation must happen on the server.",{"type":23,"tag":60,"props":248,"children":249},{},[],{"type":23,"tag":64,"props":251,"children":253},{"id":252},"_3-non-functional-requirements",[254],{"type":28,"value":255},"3. Non-Functional Requirements",{"type":23,"tag":24,"props":257,"children":258},{},[259],{"type":28,"value":260},"The main non-functional requirements are:",{"type":23,"tag":81,"props":262,"children":263},{},[264,275,285,295,305,315,325,335,345],{"type":23,"tag":85,"props":265,"children":266},{},[267,273],{"type":23,"tag":268,"props":269,"children":270},"strong",{},[271],{"type":28,"value":272},"Low latency",{"type":28,"value":274},": Moves must feel real-time.",{"type":23,"tag":85,"props":276,"children":277},{},[278,283],{"type":23,"tag":268,"props":279,"children":280},{},[281],{"type":28,"value":282},"Low bandwidth",{"type":28,"value":284},": Keep message payloads small.",{"type":23,"tag":85,"props":286,"children":287},{},[288,293],{"type":23,"tag":268,"props":289,"children":290},{},[291],{"type":28,"value":292},"Fault tolerance",{"type":28,"value":294},": Server crashes should not result in lost games.",{"type":23,"tag":85,"props":296,"children":297},{},[298,303],{"type":23,"tag":268,"props":299,"children":300},{},[301],{"type":28,"value":302},"Consistency",{"type":28,"value":304},": One authoritative sequence of moves per game.",{"type":23,"tag":85,"props":306,"children":307},{},[308,313],{"type":23,"tag":268,"props":309,"children":310},{},[311],{"type":28,"value":312},"Scalability",{"type":28,"value":314},": Handle millions of connected users and active games.",{"type":23,"tag":85,"props":316,"children":317},{},[318,323],{"type":23,"tag":268,"props":319,"children":320},{},[321],{"type":28,"value":322},"High availability",{"type":28,"value":324},": The matchmaking and gameplay paths must remain operational.",{"type":23,"tag":85,"props":326,"children":327},{},[328,333],{"type":23,"tag":268,"props":329,"children":330},{},[331],{"type":28,"value":332},"Safe deployments",{"type":28,"value":334},": Deployments must not disconnect millions of active players at once.",{"type":23,"tag":85,"props":336,"children":337},{},[338,343],{"type":23,"tag":268,"props":339,"children":340},{},[341],{"type":28,"value":342},"Reconnect support",{"type":28,"value":344},": Seamless session restoration after transient network drops.",{"type":23,"tag":85,"props":346,"children":347},{},[348,353],{"type":23,"tag":268,"props":349,"children":350},{},[351],{"type":28,"value":352},"Observability",{"type":28,"value":354},": Real-time metrics on move latency, reconnect rates, and engine health.",{"type":23,"tag":24,"props":356,"children":357},{},[358],{"type":28,"value":359},"For chess, \"low latency\" does not mean the same thing as a first-person shooter game. We do not need 20 ms server reaction time. But the move should reach the opponent quickly enough that the game feels real-time. For online chess, a reasonable target would be:",{"type":23,"tag":81,"props":361,"children":362},{},[363,368,373],{"type":23,"tag":85,"props":364,"children":365},{},[366],{"type":28,"value":367},"Move acknowledgement $p_{95}$: under 100–200 ms.",{"type":23,"tag":85,"props":369,"children":370},{},[371],{"type":28,"value":372},"Opponent move delivery $p_{95}$: under 200–500 ms.",{"type":23,"tag":85,"props":374,"children":375},{},[376],{"type":28,"value":377},"Reconnect recovery: ideally within a few seconds.",{"type":23,"tag":24,"props":379,"children":380},{},[381],{"type":28,"value":382},"The system must also be consistent. For one chess game, there must be exactly one valid sequence of moves. We cannot allow two different realities where one server thinks the game is at move 24 and another server thinks it is at move 25. The game engine must be authoritative.",{"type":23,"tag":60,"props":384,"children":385},{},[],{"type":23,"tag":64,"props":387,"children":389},{"id":388},"_4-capacity-estimation",[390],{"type":28,"value":391},"4. Capacity Estimation",{"type":23,"tag":24,"props":393,"children":394},{},[395],{"type":28,"value":396},"Let us start with a simple estimate.",{"type":23,"tag":24,"props":398,"children":399},{},[400],{"type":23,"tag":268,"props":401,"children":402},{},[403],{"type":28,"value":404},"Assumptions:",{"type":23,"tag":81,"props":406,"children":407},{},[408,413,418,423,428],{"type":23,"tag":85,"props":409,"children":410},{},[411],{"type":28,"value":412},"Daily active users (DAU): 1 million.",{"type":23,"tag":85,"props":414,"children":415},{},[416],{"type":28,"value":417},"Average active users per minute: $1,000,000 / 1440 \\approx 700\\text{--}900$ users per minute.",{"type":23,"tag":85,"props":419,"children":420},{},[421],{"type":28,"value":422},"Average game duration: 5 minutes.",{"type":23,"tag":85,"props":424,"children":425},{},[426],{"type":28,"value":427},"Normal connected users: around 5,000.",{"type":23,"tag":85,"props":429,"children":430},{},[431],{"type":28,"value":432},"Peak connected users: around 20,000.",{"type":23,"tag":24,"props":434,"children":435},{},[436],{"type":28,"value":437},"Pending match requests may be much smaller than total online users because challenges expire quickly and many users are already in games.",{"type":23,"tag":24,"props":439,"children":440},{},[441],{"type":28,"value":442},"If we assume each pending match request takes 1 KB, then:\n$$20,000 \\text{ pending requests} \\times 1\\text{ KB} = 20\\text{ MB}$$",{"type":23,"tag":24,"props":444,"children":445},{},[446],{"type":28,"value":447},"This is small enough to keep in memory.",{"type":23,"tag":24,"props":449,"children":450},{},[451],{"type":28,"value":452},"If the matching engine uses a Balanced BST, TreeSet, Redis Sorted Set, or similar range-query structure, lookup complexity is $O(\\log N)$. For $N = 20,000$ pending requests, $\\log_2(20,000) \\approx 14$ operations. So the matcher is not likely to be the hardest bottleneck.",{"type":23,"tag":24,"props":454,"children":455},{},[456],{"type":28,"value":457},"The bigger challenge is maintaining long-lived WebSocket connections and safely routing messages between users. Chess is usually connection-heavy but not bandwidth-heavy. A chess move is tiny. Even if each move payload is 1 KB after metadata and overhead, the move rate is still moderate compared to video streaming or telemetry-heavy games.",{"type":23,"tag":24,"props":459,"children":460},{},[461],{"type":23,"tag":268,"props":462,"children":463},{},[464],{"type":28,"value":465},"Example:",{"type":23,"tag":81,"props":467,"children":468},{},[469,474],{"type":23,"tag":85,"props":470,"children":471},{},[472],{"type":28,"value":473},"Suppose 200,000 active games are running.",{"type":23,"tag":85,"props":475,"children":476},{},[477],{"type":28,"value":478},"If each game averages one move every 10 seconds, that is:\n$$200,000 / 10 = 20,000 \\text{ moves per second}$$",{"type":23,"tag":24,"props":480,"children":481},{},[482],{"type":28,"value":483},"This is significant, but still manageable with sharded game servers, efficient move logs, and lightweight payloads.",{"type":23,"tag":24,"props":485,"children":486},{},[487],{"type":28,"value":488},"The harder part is:",{"type":23,"tag":490,"props":491,"children":492},"ol",{},[493,498,503,508,513,518],{"type":23,"tag":85,"props":494,"children":495},{},[496],{"type":28,"value":497},"Maintaining millions of live connections.",{"type":23,"tag":85,"props":499,"children":500},{},[501],{"type":28,"value":502},"Handling reconnect storms.",{"type":23,"tag":85,"props":504,"children":505},{},[506],{"type":28,"value":507},"Routing messages to the correct gateway.",{"type":23,"tag":85,"props":509,"children":510},{},[511],{"type":28,"value":512},"Recovering game state after crashes.",{"type":23,"tag":85,"props":514,"children":515},{},[516],{"type":28,"value":517},"Avoiding spectator fanout from hurting players.",{"type":23,"tag":85,"props":519,"children":520},{},[521],{"type":28,"value":522},"Ensuring every move is processed exactly once logically.",{"type":23,"tag":60,"props":524,"children":525},{},[],{"type":23,"tag":64,"props":527,"children":529},{"id":528},"_5-high-level-architecture",[530],{"type":28,"value":531},"5. High-Level Architecture",{"type":23,"tag":24,"props":533,"children":534},{},[535],{"type":28,"value":536},"At a high level, the architecture separates the critical low-latency gameplay path from asynchronous downstream services.",{"type":23,"tag":24,"props":538,"children":539},{},[540],{"type":23,"tag":541,"props":542,"children":545},"img",{"alt":543,"src":544},"High-level architecture for a scalable online chess platform showing WebSocket gateways, connection routing, matchmaking, game engine shards, and isolated spectator fanout path.","../../images/blog/online-chess-platform-system-design.svg",[],{"type":23,"tag":24,"props":547,"children":548},{},[549],{"type":28,"value":550},"The gameplay path (move validation, clock updates, move persistence, and opponent notification) is synchronous, thin, and low-latency.",{"type":23,"tag":24,"props":552,"children":553},{},[554],{"type":28,"value":555},"Everything else—game analysis, rating updates, cheating checks, emails, and long-term analytics—should be processed asynchronously off an event stream.",{"type":23,"tag":60,"props":557,"children":558},{},[],{"type":23,"tag":64,"props":560,"children":562},{"id":561},"_6-why-websockets",[563],{"type":28,"value":564},"6. Why WebSockets?",{"type":23,"tag":24,"props":566,"children":567},{},[568],{"type":28,"value":569},"A normal HTTP request-response model is not enough for active chess gameplay.",{"type":23,"tag":24,"props":571,"children":572},{},[573],{"type":28,"value":574},"When Player A makes a move, Player B should receive it immediately. The server needs to push data to the opponent without waiting for the opponent to poll.",{"type":23,"tag":24,"props":576,"children":577},{},[578],{"type":28,"value":579},"Polling would look like this:",{"type":23,"tag":490,"props":581,"children":582},{},[583,588],{"type":23,"tag":85,"props":584,"children":585},{},[586],{"type":28,"value":587},"Player B asks the server every second, \"Did my opponent move?\"",{"type":23,"tag":85,"props":589,"children":590},{},[591],{"type":28,"value":592},"This creates unnecessary traffic and poor latency. If the polling interval is 1 second, the opponent may see moves up to 1 second late. If the polling interval is very small, the server gets hammered by repeated useless requests.",{"type":23,"tag":24,"props":594,"children":595},{},[596],{"type":28,"value":597},"WebSockets solve this by keeping a long-lived bidirectional connection between the client and server.",{"type":23,"tag":24,"props":599,"children":600},{},[601],{"type":28,"value":602},"With WebSockets:",{"type":23,"tag":81,"props":604,"children":605},{},[606,611,616],{"type":23,"tag":85,"props":607,"children":608},{},[609],{"type":28,"value":610},"Client can send a move to the server.",{"type":23,"tag":85,"props":612,"children":613},{},[614],{"type":28,"value":615},"Server can push the move to the opponent.",{"type":23,"tag":85,"props":617,"children":618},{},[619],{"type":28,"value":620},"Server can notify about resignation, timeout, draw offer, disconnect, reconnect, chat messages, and game end.",{"type":23,"tag":24,"props":622,"children":623},{},[624],{"type":28,"value":625},"The gameplay experience becomes smoother. However, WebSockets introduce new complexity. HTTP servers are mostly stateless. WebSocket servers are stateful because they hold long-lived connections. This affects load balancing, deployment, failure recovery, connection routing, and capacity planning.",{"type":23,"tag":60,"props":627,"children":628},{},[],{"type":23,"tag":64,"props":630,"children":632},{"id":631},"_7-keep-the-gateway-dumb",[633],{"type":28,"value":634},"7. Keep the Gateway Dumb",{"type":23,"tag":24,"props":636,"children":637},{},[638],{"type":28,"value":639},"One of the most important design decisions is to keep the WebSocket gateway as dumb and stable as possible.",{"type":23,"tag":24,"props":641,"children":642},{},[643],{"type":28,"value":644},"The gateway should mainly do:",{"type":23,"tag":81,"props":646,"children":647},{},[648,653,658,663,668,673,678],{"type":23,"tag":85,"props":649,"children":650},{},[651],{"type":28,"value":652},"Accept WebSocket connections.",{"type":23,"tag":85,"props":654,"children":655},{},[656],{"type":28,"value":657},"Verify authentication tokens.",{"type":23,"tag":85,"props":659,"children":660},{},[661],{"type":28,"value":662},"Maintain socket lifecycle.",{"type":23,"tag":85,"props":664,"children":665},{},[666],{"type":28,"value":667},"Handle ping/pong heartbeats.",{"type":23,"tag":85,"props":669,"children":670},{},[671],{"type":28,"value":672},"Track connection metadata.",{"type":23,"tag":85,"props":674,"children":675},{},[676],{"type":28,"value":677},"Forward inbound messages to the connection service.",{"type":23,"tag":85,"props":679,"children":680},{},[681],{"type":28,"value":682},"Send outbound messages to connected clients.",{"type":23,"tag":24,"props":684,"children":685},{},[686],{"type":28,"value":687},"It should not contain frequently changing business logic. The gateway should not deeply understand chess move validation, matchmaking rules, profile logic, rating updates, anti-cheat logic, or game analysis.",{"type":23,"tag":689,"props":690,"children":692},"h3",{"id":691},"why",[693],{"type":28,"value":694},"Why?",{"type":23,"tag":24,"props":696,"children":697},{},[698,700,705],{"type":28,"value":699},"Because WebSocket gateways hold long-lived connections. If we frequently deploy gateway changes, we risk disconnecting many users. If thousands of clients disconnect at once and reconnect immediately, we create a ",{"type":23,"tag":268,"props":701,"children":702},{},[703],{"type":28,"value":704},"thundering herd",{"type":28,"value":706},".",{"type":23,"tag":24,"props":708,"children":709},{},[710],{"type":28,"value":711},"A better design separates concerns:",{"type":23,"tag":81,"props":713,"children":714},{},[715,725,735],{"type":23,"tag":85,"props":716,"children":717},{},[718,723],{"type":23,"tag":268,"props":719,"children":720},{},[721],{"type":28,"value":722},"Dumb WebSocket Gateway",{"type":28,"value":724},": Stable and rarely changes.",{"type":23,"tag":85,"props":726,"children":727},{},[728,733],{"type":23,"tag":268,"props":729,"children":730},{},[731],{"type":28,"value":732},"Connection Service",{"type":28,"value":734},": Orchestrates the mappings.",{"type":23,"tag":85,"props":736,"children":737},{},[738,743],{"type":23,"tag":268,"props":739,"children":740},{},[741],{"type":28,"value":742},"Downstream Services (Game / Match / Profile / Chat)",{"type":28,"value":744},": Frequently changing business logic.",{"type":23,"tag":60,"props":746,"children":747},{},[],{"type":23,"tag":64,"props":749,"children":751},{"id":750},"_8-connection-service",[752],{"type":28,"value":753},"8. Connection Service",{"type":23,"tag":24,"props":755,"children":756},{},[757],{"type":28,"value":758},"The connection service is the smart layer behind the gateway.",{"type":23,"tag":24,"props":760,"children":761},{},[762],{"type":28,"value":763},"It handles:",{"type":23,"tag":81,"props":765,"children":766},{},[767,772,777,782,787,792],{"type":23,"tag":85,"props":768,"children":769},{},[770],{"type":28,"value":771},"User-to-connection mapping.",{"type":23,"tag":85,"props":773,"children":774},{},[775],{"type":28,"value":776},"Connection-to-gateway mapping.",{"type":23,"tag":85,"props":778,"children":779},{},[780],{"type":28,"value":781},"Protocol versioning.",{"type":23,"tag":85,"props":783,"children":784},{},[785],{"type":28,"value":786},"Message routing & fanout coordination.",{"type":23,"tag":85,"props":788,"children":789},{},[790],{"type":28,"value":791},"Presence & session restoration.",{"type":23,"tag":85,"props":793,"children":794},{},[795],{"type":28,"value":796},"Backpressure & reconnect handling.",{"type":23,"tag":24,"props":798,"children":799},{},[800],{"type":28,"value":801},"For example, the gateway may receive this message:",{"type":23,"tag":803,"props":804,"children":808},"pre",{"code":805,"language":806,"meta":7,"className":807,"style":7},"{\n  \"type\": \"MOVE_SUBMIT\",\n  \"gameId\": \"game-123\",\n  \"move\": \"e2e4\",\n  \"clientMoveId\": \"move-789\",\n  \"expectedMoveNumber\": 12\n}\n","json","language-json shiki shiki-themes github-light github-dark",[809],{"type":23,"tag":46,"props":810,"children":811},{"__ignoreMap":7},[812,823,849,871,893,915,933],{"type":23,"tag":240,"props":813,"children":816},{"class":814,"line":815},"line",1,[817],{"type":23,"tag":240,"props":818,"children":820},{"style":819},"--shiki-default:#24292E;--shiki-dark:#E1E4E8",[821],{"type":28,"value":822},"{\n",{"type":23,"tag":240,"props":824,"children":826},{"class":814,"line":825},2,[827,833,838,844],{"type":23,"tag":240,"props":828,"children":830},{"style":829},"--shiki-default:#005CC5;--shiki-dark:#79B8FF",[831],{"type":28,"value":832},"  \"type\"",{"type":23,"tag":240,"props":834,"children":835},{"style":819},[836],{"type":28,"value":837},": ",{"type":23,"tag":240,"props":839,"children":841},{"style":840},"--shiki-default:#032F62;--shiki-dark:#9ECBFF",[842],{"type":28,"value":843},"\"MOVE_SUBMIT\"",{"type":23,"tag":240,"props":845,"children":846},{"style":819},[847],{"type":28,"value":848},",\n",{"type":23,"tag":240,"props":850,"children":852},{"class":814,"line":851},3,[853,858,862,867],{"type":23,"tag":240,"props":854,"children":855},{"style":829},[856],{"type":28,"value":857},"  \"gameId\"",{"type":23,"tag":240,"props":859,"children":860},{"style":819},[861],{"type":28,"value":837},{"type":23,"tag":240,"props":863,"children":864},{"style":840},[865],{"type":28,"value":866},"\"game-123\"",{"type":23,"tag":240,"props":868,"children":869},{"style":819},[870],{"type":28,"value":848},{"type":23,"tag":240,"props":872,"children":874},{"class":814,"line":873},4,[875,880,884,889],{"type":23,"tag":240,"props":876,"children":877},{"style":829},[878],{"type":28,"value":879},"  \"move\"",{"type":23,"tag":240,"props":881,"children":882},{"style":819},[883],{"type":28,"value":837},{"type":23,"tag":240,"props":885,"children":886},{"style":840},[887],{"type":28,"value":888},"\"e2e4\"",{"type":23,"tag":240,"props":890,"children":891},{"style":819},[892],{"type":28,"value":848},{"type":23,"tag":240,"props":894,"children":896},{"class":814,"line":895},5,[897,902,906,911],{"type":23,"tag":240,"props":898,"children":899},{"style":829},[900],{"type":28,"value":901},"  \"clientMoveId\"",{"type":23,"tag":240,"props":903,"children":904},{"style":819},[905],{"type":28,"value":837},{"type":23,"tag":240,"props":907,"children":908},{"style":840},[909],{"type":28,"value":910},"\"move-789\"",{"type":23,"tag":240,"props":912,"children":913},{"style":819},[914],{"type":28,"value":848},{"type":23,"tag":240,"props":916,"children":918},{"class":814,"line":917},6,[919,924,928],{"type":23,"tag":240,"props":920,"children":921},{"style":829},[922],{"type":28,"value":923},"  \"expectedMoveNumber\"",{"type":23,"tag":240,"props":925,"children":926},{"style":819},[927],{"type":28,"value":837},{"type":23,"tag":240,"props":929,"children":930},{"style":829},[931],{"type":28,"value":932},"12\n",{"type":23,"tag":240,"props":934,"children":936},{"class":814,"line":935},7,[937],{"type":23,"tag":240,"props":938,"children":939},{"style":819},[940],{"type":28,"value":941},"}\n",{"type":23,"tag":24,"props":943,"children":944},{},[945],{"type":28,"value":946},"The gateway does not validate the chess move. It wraps the message with connection metadata:",{"type":23,"tag":803,"props":948,"children":950},{"code":949,"language":806,"meta":7,"className":807,"style":7},"{\n  \"connectionId\": \"conn-456\",\n  \"userId\": \"user-1\",\n  \"gatewayId\": \"gw-7\",\n  \"receivedAt\": 1710000000,\n  \"rawMessage\": {\n    \"type\": \"MOVE_SUBMIT\",\n    \"gameId\": \"game-123\",\n    \"move\": \"e2e4\",\n    \"clientMoveId\": \"move-789\",\n    \"expectedMoveNumber\": 12\n  }\n}\n",[951],{"type":23,"tag":46,"props":952,"children":953},{"__ignoreMap":7},[954,961,982,1003,1024,1045,1058,1078,1099,1120,1141,1158,1167],{"type":23,"tag":240,"props":955,"children":956},{"class":814,"line":815},[957],{"type":23,"tag":240,"props":958,"children":959},{"style":819},[960],{"type":28,"value":822},{"type":23,"tag":240,"props":962,"children":963},{"class":814,"line":825},[964,969,973,978],{"type":23,"tag":240,"props":965,"children":966},{"style":829},[967],{"type":28,"value":968},"  \"connectionId\"",{"type":23,"tag":240,"props":970,"children":971},{"style":819},[972],{"type":28,"value":837},{"type":23,"tag":240,"props":974,"children":975},{"style":840},[976],{"type":28,"value":977},"\"conn-456\"",{"type":23,"tag":240,"props":979,"children":980},{"style":819},[981],{"type":28,"value":848},{"type":23,"tag":240,"props":983,"children":984},{"class":814,"line":851},[985,990,994,999],{"type":23,"tag":240,"props":986,"children":987},{"style":829},[988],{"type":28,"value":989},"  \"userId\"",{"type":23,"tag":240,"props":991,"children":992},{"style":819},[993],{"type":28,"value":837},{"type":23,"tag":240,"props":995,"children":996},{"style":840},[997],{"type":28,"value":998},"\"user-1\"",{"type":23,"tag":240,"props":1000,"children":1001},{"style":819},[1002],{"type":28,"value":848},{"type":23,"tag":240,"props":1004,"children":1005},{"class":814,"line":873},[1006,1011,1015,1020],{"type":23,"tag":240,"props":1007,"children":1008},{"style":829},[1009],{"type":28,"value":1010},"  \"gatewayId\"",{"type":23,"tag":240,"props":1012,"children":1013},{"style":819},[1014],{"type":28,"value":837},{"type":23,"tag":240,"props":1016,"children":1017},{"style":840},[1018],{"type":28,"value":1019},"\"gw-7\"",{"type":23,"tag":240,"props":1021,"children":1022},{"style":819},[1023],{"type":28,"value":848},{"type":23,"tag":240,"props":1025,"children":1026},{"class":814,"line":895},[1027,1032,1036,1041],{"type":23,"tag":240,"props":1028,"children":1029},{"style":829},[1030],{"type":28,"value":1031},"  \"receivedAt\"",{"type":23,"tag":240,"props":1033,"children":1034},{"style":819},[1035],{"type":28,"value":837},{"type":23,"tag":240,"props":1037,"children":1038},{"style":829},[1039],{"type":28,"value":1040},"1710000000",{"type":23,"tag":240,"props":1042,"children":1043},{"style":819},[1044],{"type":28,"value":848},{"type":23,"tag":240,"props":1046,"children":1047},{"class":814,"line":917},[1048,1053],{"type":23,"tag":240,"props":1049,"children":1050},{"style":829},[1051],{"type":28,"value":1052},"  \"rawMessage\"",{"type":23,"tag":240,"props":1054,"children":1055},{"style":819},[1056],{"type":28,"value":1057},": {\n",{"type":23,"tag":240,"props":1059,"children":1060},{"class":814,"line":935},[1061,1066,1070,1074],{"type":23,"tag":240,"props":1062,"children":1063},{"style":829},[1064],{"type":28,"value":1065},"    \"type\"",{"type":23,"tag":240,"props":1067,"children":1068},{"style":819},[1069],{"type":28,"value":837},{"type":23,"tag":240,"props":1071,"children":1072},{"style":840},[1073],{"type":28,"value":843},{"type":23,"tag":240,"props":1075,"children":1076},{"style":819},[1077],{"type":28,"value":848},{"type":23,"tag":240,"props":1079,"children":1081},{"class":814,"line":1080},8,[1082,1087,1091,1095],{"type":23,"tag":240,"props":1083,"children":1084},{"style":829},[1085],{"type":28,"value":1086},"    \"gameId\"",{"type":23,"tag":240,"props":1088,"children":1089},{"style":819},[1090],{"type":28,"value":837},{"type":23,"tag":240,"props":1092,"children":1093},{"style":840},[1094],{"type":28,"value":866},{"type":23,"tag":240,"props":1096,"children":1097},{"style":819},[1098],{"type":28,"value":848},{"type":23,"tag":240,"props":1100,"children":1102},{"class":814,"line":1101},9,[1103,1108,1112,1116],{"type":23,"tag":240,"props":1104,"children":1105},{"style":829},[1106],{"type":28,"value":1107},"    \"move\"",{"type":23,"tag":240,"props":1109,"children":1110},{"style":819},[1111],{"type":28,"value":837},{"type":23,"tag":240,"props":1113,"children":1114},{"style":840},[1115],{"type":28,"value":888},{"type":23,"tag":240,"props":1117,"children":1118},{"style":819},[1119],{"type":28,"value":848},{"type":23,"tag":240,"props":1121,"children":1123},{"class":814,"line":1122},10,[1124,1129,1133,1137],{"type":23,"tag":240,"props":1125,"children":1126},{"style":829},[1127],{"type":28,"value":1128},"    \"clientMoveId\"",{"type":23,"tag":240,"props":1130,"children":1131},{"style":819},[1132],{"type":28,"value":837},{"type":23,"tag":240,"props":1134,"children":1135},{"style":840},[1136],{"type":28,"value":910},{"type":23,"tag":240,"props":1138,"children":1139},{"style":819},[1140],{"type":28,"value":848},{"type":23,"tag":240,"props":1142,"children":1144},{"class":814,"line":1143},11,[1145,1150,1154],{"type":23,"tag":240,"props":1146,"children":1147},{"style":829},[1148],{"type":28,"value":1149},"    \"expectedMoveNumber\"",{"type":23,"tag":240,"props":1151,"children":1152},{"style":819},[1153],{"type":28,"value":837},{"type":23,"tag":240,"props":1155,"children":1156},{"style":829},[1157],{"type":28,"value":932},{"type":23,"tag":240,"props":1159,"children":1161},{"class":814,"line":1160},12,[1162],{"type":23,"tag":240,"props":1163,"children":1164},{"style":819},[1165],{"type":28,"value":1166},"  }\n",{"type":23,"tag":240,"props":1168,"children":1170},{"class":814,"line":1169},13,[1171],{"type":23,"tag":240,"props":1172,"children":1173},{"style":819},[1174],{"type":28,"value":941},{"type":23,"tag":24,"props":1176,"children":1177},{},[1178],{"type":28,"value":1179},"Then the connection service routes it to the correct game engine shard.",{"type":23,"tag":24,"props":1181,"children":1182},{},[1183],{"type":28,"value":1184},"The connection service maintains mappings like:",{"type":23,"tag":81,"props":1186,"children":1187},{},[1188,1197,1206,1215],{"type":23,"tag":85,"props":1189,"children":1190},{},[1191],{"type":23,"tag":46,"props":1192,"children":1194},{"className":1193},[],[1195],{"type":28,"value":1196},"userId -> (gatewayId, connectionId)",{"type":23,"tag":85,"props":1198,"children":1199},{},[1200],{"type":23,"tag":46,"props":1201,"children":1203},{"className":1202},[],[1204],{"type":28,"value":1205},"gameId -> gameEngineShardId",{"type":23,"tag":85,"props":1207,"children":1208},{},[1209],{"type":23,"tag":46,"props":1210,"children":1212},{"className":1211},[],[1213],{"type":28,"value":1214},"connectionId -> userId",{"type":23,"tag":85,"props":1216,"children":1217},{},[1218],{"type":23,"tag":46,"props":1219,"children":1221},{"className":1220},[],[1222],{"type":28,"value":1223},"userId -> activeGameId",{"type":23,"tag":24,"props":1225,"children":1226},{},[1227],{"type":28,"value":1228},"Redis is a common choice for this type of ephemeral mapping because it supports fast reads/writes and TTLs. Mappings should expire automatically if heartbeats stop.",{"type":23,"tag":60,"props":1230,"children":1231},{},[],{"type":23,"tag":64,"props":1233,"children":1235},{"id":1234},"_9-matching-engine",[1236],{"type":28,"value":1237},"9. Matching Engine",{"type":23,"tag":24,"props":1239,"children":1240},{},[1241],{"type":28,"value":1242},"The matching engine is responsible for pairing players.",{"type":23,"tag":24,"props":1244,"children":1245},{},[1246],{"type":28,"value":1247},"A user may create a challenge with constraints like:",{"type":23,"tag":81,"props":1249,"children":1250},{},[1251,1284,1294],{"type":23,"tag":85,"props":1252,"children":1253},{},[1254,1259,1260,1266,1268,1274,1276,1282],{"type":23,"tag":268,"props":1255,"children":1256},{},[1257],{"type":28,"value":1258},"Time control",{"type":28,"value":837},{"type":23,"tag":46,"props":1261,"children":1263},{"className":1262},[],[1264],{"type":28,"value":1265},"3+0",{"type":28,"value":1267}," (Blitz), ",{"type":23,"tag":46,"props":1269,"children":1271},{"className":1270},[],[1272],{"type":28,"value":1273},"5+0",{"type":28,"value":1275},", ",{"type":23,"tag":46,"props":1277,"children":1279},{"className":1278},[],[1280],{"type":28,"value":1281},"10+0",{"type":28,"value":1283}," (Rapid), etc.",{"type":23,"tag":85,"props":1285,"children":1286},{},[1287,1292],{"type":23,"tag":268,"props":1288,"children":1289},{},[1290],{"type":28,"value":1291},"Opponent rating",{"type":28,"value":1293},": Minimum/maximum bounds.",{"type":23,"tag":85,"props":1295,"children":1296},{},[1297,1302],{"type":23,"tag":268,"props":1298,"children":1299},{},[1300],{"type":28,"value":1301},"Mode",{"type":28,"value":1303},": Rated or unrated.",{"type":23,"tag":24,"props":1305,"children":1306},{},[1307],{"type":28,"value":1308},"Example challenge:",{"type":23,"tag":803,"props":1310,"children":1312},{"code":1311,"language":806,"meta":7,"className":807,"style":7},"{\n  \"challengeId\": \"challenge-123\",\n  \"userId\": \"user-1\",\n  \"rating\": 1500,\n  \"minOpponentRating\": 1400,\n  \"maxOpponentRating\": 1600,\n  \"timeControl\": \"5+0\",\n  \"rated\": true,\n  \"createdAt\": 1710000000,\n  \"expiresAt\": 1710000030\n}\n",[1313],{"type":23,"tag":46,"props":1314,"children":1315},{"__ignoreMap":7},[1316,1323,1344,1363,1384,1405,1426,1447,1468,1488,1505],{"type":23,"tag":240,"props":1317,"children":1318},{"class":814,"line":815},[1319],{"type":23,"tag":240,"props":1320,"children":1321},{"style":819},[1322],{"type":28,"value":822},{"type":23,"tag":240,"props":1324,"children":1325},{"class":814,"line":825},[1326,1331,1335,1340],{"type":23,"tag":240,"props":1327,"children":1328},{"style":829},[1329],{"type":28,"value":1330},"  \"challengeId\"",{"type":23,"tag":240,"props":1332,"children":1333},{"style":819},[1334],{"type":28,"value":837},{"type":23,"tag":240,"props":1336,"children":1337},{"style":840},[1338],{"type":28,"value":1339},"\"challenge-123\"",{"type":23,"tag":240,"props":1341,"children":1342},{"style":819},[1343],{"type":28,"value":848},{"type":23,"tag":240,"props":1345,"children":1346},{"class":814,"line":851},[1347,1351,1355,1359],{"type":23,"tag":240,"props":1348,"children":1349},{"style":829},[1350],{"type":28,"value":989},{"type":23,"tag":240,"props":1352,"children":1353},{"style":819},[1354],{"type":28,"value":837},{"type":23,"tag":240,"props":1356,"children":1357},{"style":840},[1358],{"type":28,"value":998},{"type":23,"tag":240,"props":1360,"children":1361},{"style":819},[1362],{"type":28,"value":848},{"type":23,"tag":240,"props":1364,"children":1365},{"class":814,"line":873},[1366,1371,1375,1380],{"type":23,"tag":240,"props":1367,"children":1368},{"style":829},[1369],{"type":28,"value":1370},"  \"rating\"",{"type":23,"tag":240,"props":1372,"children":1373},{"style":819},[1374],{"type":28,"value":837},{"type":23,"tag":240,"props":1376,"children":1377},{"style":829},[1378],{"type":28,"value":1379},"1500",{"type":23,"tag":240,"props":1381,"children":1382},{"style":819},[1383],{"type":28,"value":848},{"type":23,"tag":240,"props":1385,"children":1386},{"class":814,"line":895},[1387,1392,1396,1401],{"type":23,"tag":240,"props":1388,"children":1389},{"style":829},[1390],{"type":28,"value":1391},"  \"minOpponentRating\"",{"type":23,"tag":240,"props":1393,"children":1394},{"style":819},[1395],{"type":28,"value":837},{"type":23,"tag":240,"props":1397,"children":1398},{"style":829},[1399],{"type":28,"value":1400},"1400",{"type":23,"tag":240,"props":1402,"children":1403},{"style":819},[1404],{"type":28,"value":848},{"type":23,"tag":240,"props":1406,"children":1407},{"class":814,"line":917},[1408,1413,1417,1422],{"type":23,"tag":240,"props":1409,"children":1410},{"style":829},[1411],{"type":28,"value":1412},"  \"maxOpponentRating\"",{"type":23,"tag":240,"props":1414,"children":1415},{"style":819},[1416],{"type":28,"value":837},{"type":23,"tag":240,"props":1418,"children":1419},{"style":829},[1420],{"type":28,"value":1421},"1600",{"type":23,"tag":240,"props":1423,"children":1424},{"style":819},[1425],{"type":28,"value":848},{"type":23,"tag":240,"props":1427,"children":1428},{"class":814,"line":935},[1429,1434,1438,1443],{"type":23,"tag":240,"props":1430,"children":1431},{"style":829},[1432],{"type":28,"value":1433},"  \"timeControl\"",{"type":23,"tag":240,"props":1435,"children":1436},{"style":819},[1437],{"type":28,"value":837},{"type":23,"tag":240,"props":1439,"children":1440},{"style":840},[1441],{"type":28,"value":1442},"\"5+0\"",{"type":23,"tag":240,"props":1444,"children":1445},{"style":819},[1446],{"type":28,"value":848},{"type":23,"tag":240,"props":1448,"children":1449},{"class":814,"line":1080},[1450,1455,1459,1464],{"type":23,"tag":240,"props":1451,"children":1452},{"style":829},[1453],{"type":28,"value":1454},"  \"rated\"",{"type":23,"tag":240,"props":1456,"children":1457},{"style":819},[1458],{"type":28,"value":837},{"type":23,"tag":240,"props":1460,"children":1461},{"style":829},[1462],{"type":28,"value":1463},"true",{"type":23,"tag":240,"props":1465,"children":1466},{"style":819},[1467],{"type":28,"value":848},{"type":23,"tag":240,"props":1469,"children":1470},{"class":814,"line":1101},[1471,1476,1480,1484],{"type":23,"tag":240,"props":1472,"children":1473},{"style":829},[1474],{"type":28,"value":1475},"  \"createdAt\"",{"type":23,"tag":240,"props":1477,"children":1478},{"style":819},[1479],{"type":28,"value":837},{"type":23,"tag":240,"props":1481,"children":1482},{"style":829},[1483],{"type":28,"value":1040},{"type":23,"tag":240,"props":1485,"children":1486},{"style":819},[1487],{"type":28,"value":848},{"type":23,"tag":240,"props":1489,"children":1490},{"class":814,"line":1122},[1491,1496,1500],{"type":23,"tag":240,"props":1492,"children":1493},{"style":829},[1494],{"type":28,"value":1495},"  \"expiresAt\"",{"type":23,"tag":240,"props":1497,"children":1498},{"style":819},[1499],{"type":28,"value":837},{"type":23,"tag":240,"props":1501,"children":1502},{"style":829},[1503],{"type":28,"value":1504},"1710000030\n",{"type":23,"tag":240,"props":1506,"children":1507},{"class":814,"line":1143},[1508],{"type":23,"tag":240,"props":1509,"children":1510},{"style":819},[1511],{"type":28,"value":941},{"type":23,"tag":24,"props":1513,"children":1514},{},[1515],{"type":28,"value":1516},"A simple approach:",{"type":23,"tag":490,"props":1518,"children":1519},{},[1520,1525,1530,1535,1540,1545],{"type":23,"tag":85,"props":1521,"children":1522},{},[1523],{"type":28,"value":1524},"Store pending challenges in memory.",{"type":23,"tag":85,"props":1526,"children":1527},{},[1528],{"type":28,"value":1529},"Index them by rating using a TreeSet, Balanced BST, or Redis Sorted Set.",{"type":23,"tag":85,"props":1531,"children":1532},{},[1533],{"type":28,"value":1534},"Expire challenges after a short TTL, such as 30 seconds.",{"type":23,"tag":85,"props":1536,"children":1537},{},[1538],{"type":28,"value":1539},"When a new challenge arrives, search for compatible existing challenges.",{"type":23,"tag":85,"props":1541,"children":1542},{},[1543],{"type":28,"value":1544},"If a match is found, atomically remove both challenges and create a game.",{"type":23,"tag":85,"props":1546,"children":1547},{},[1548],{"type":28,"value":1549},"If no match is found, insert the challenge into the pending pool.",{"type":23,"tag":24,"props":1551,"children":1552},{},[1553,1555,1560],{"type":28,"value":1554},"The important operation is ",{"type":23,"tag":268,"props":1556,"children":1557},{},[1558],{"type":28,"value":1559},"atomic match creation",{"type":28,"value":1561},". We must prevent a bug where User A's challenge is accepted by User B and User C at the same time. To prevent this, we need compare-and-set operations, distributed locks, database transactions, or single-threaded partition ownership inside the matching shard.",{"type":23,"tag":24,"props":1563,"children":1564},{},[1565],{"type":28,"value":1566},"A good sharding strategy partitions match requests by time control and rating bucket:",{"type":23,"tag":81,"props":1568,"children":1569},{},[1570,1579,1588],{"type":23,"tag":85,"props":1571,"children":1572},{},[1573],{"type":23,"tag":46,"props":1574,"children":1576},{"className":1575},[],[1577],{"type":28,"value":1578},"matchmaking:blitz:1400-1600",{"type":23,"tag":85,"props":1580,"children":1581},{},[1582],{"type":23,"tag":46,"props":1583,"children":1585},{"className":1584},[],[1586],{"type":28,"value":1587},"matchmaking:rapid:1600-1800",{"type":23,"tag":85,"props":1589,"children":1590},{},[1591],{"type":23,"tag":46,"props":1592,"children":1594},{"className":1593},[],[1595],{"type":28,"value":1596},"matchmaking:bullet:1200-1400",{"type":23,"tag":24,"props":1598,"children":1599},{},[1600],{"type":28,"value":1601},"As wait time increases, we can gradually widen the rating range (e.g., first 5 seconds: $\\pm 100$ rating, next 10 seconds: $\\pm 200$ rating, etc.). This improves both fairness and match availability.",{"type":23,"tag":60,"props":1603,"children":1604},{},[],{"type":23,"tag":64,"props":1606,"children":1608},{"id":1607},"_10-game-creation",[1609],{"type":28,"value":1610},"10. Game Creation",{"type":23,"tag":24,"props":1612,"children":1613},{},[1614],{"type":28,"value":1615},"Once the match engine finds two compatible users, it creates a new game.",{"type":23,"tag":24,"props":1617,"children":1618},{},[1619],{"type":28,"value":1620},"Game creation involves:",{"type":23,"tag":81,"props":1622,"children":1623},{},[1624,1636,1641,1646,1658],{"type":23,"tag":85,"props":1625,"children":1626},{},[1627,1629,1635],{"type":28,"value":1628},"Assigning a unique ",{"type":23,"tag":46,"props":1630,"children":1632},{"className":1631},[],[1633],{"type":28,"value":1634},"gameId",{"type":28,"value":706},{"type":23,"tag":85,"props":1637,"children":1638},{},[1639],{"type":28,"value":1640},"Assigning white and black colors.",{"type":23,"tag":85,"props":1642,"children":1643},{},[1644],{"type":28,"value":1645},"Initializing the board state (usually FEN format) and clocks.",{"type":23,"tag":85,"props":1647,"children":1648},{},[1649,1651,1657],{"type":28,"value":1650},"Storing game metadata and setting game status to ",{"type":23,"tag":46,"props":1652,"children":1654},{"className":1653},[],[1655],{"type":28,"value":1656},"ACTIVE",{"type":28,"value":706},{"type":23,"tag":85,"props":1659,"children":1660},{},[1661],{"type":28,"value":1662},"Notifying both players.",{"type":23,"tag":24,"props":1664,"children":1665},{},[1666],{"type":28,"value":1667},"Example game object:",{"type":23,"tag":803,"props":1669,"children":1671},{"code":1670,"language":806,"meta":7,"className":807,"style":7},"{\n  \"gameId\": \"game-123\",\n  \"whiteUserId\": \"user-1\",\n  \"blackUserId\": \"user-2\",\n  \"timeControl\": \"5+0\",\n  \"rated\": true,\n  \"status\": \"ACTIVE\",\n  \"currentFen\": \"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\",\n  \"turn\": \"WHITE\",\n  \"whiteTimeMs\": 300000,\n  \"blackTimeMs\": 300000,\n  \"moveNumber\": 0,\n  \"createdAt\": 1710000000\n}\n",[1672],{"type":23,"tag":46,"props":1673,"children":1674},{"__ignoreMap":7},[1675,1682,1701,1721,1742,1761,1780,1801,1822,1843,1864,1884,1905,1921],{"type":23,"tag":240,"props":1676,"children":1677},{"class":814,"line":815},[1678],{"type":23,"tag":240,"props":1679,"children":1680},{"style":819},[1681],{"type":28,"value":822},{"type":23,"tag":240,"props":1683,"children":1684},{"class":814,"line":825},[1685,1689,1693,1697],{"type":23,"tag":240,"props":1686,"children":1687},{"style":829},[1688],{"type":28,"value":857},{"type":23,"tag":240,"props":1690,"children":1691},{"style":819},[1692],{"type":28,"value":837},{"type":23,"tag":240,"props":1694,"children":1695},{"style":840},[1696],{"type":28,"value":866},{"type":23,"tag":240,"props":1698,"children":1699},{"style":819},[1700],{"type":28,"value":848},{"type":23,"tag":240,"props":1702,"children":1703},{"class":814,"line":851},[1704,1709,1713,1717],{"type":23,"tag":240,"props":1705,"children":1706},{"style":829},[1707],{"type":28,"value":1708},"  \"whiteUserId\"",{"type":23,"tag":240,"props":1710,"children":1711},{"style":819},[1712],{"type":28,"value":837},{"type":23,"tag":240,"props":1714,"children":1715},{"style":840},[1716],{"type":28,"value":998},{"type":23,"tag":240,"props":1718,"children":1719},{"style":819},[1720],{"type":28,"value":848},{"type":23,"tag":240,"props":1722,"children":1723},{"class":814,"line":873},[1724,1729,1733,1738],{"type":23,"tag":240,"props":1725,"children":1726},{"style":829},[1727],{"type":28,"value":1728},"  \"blackUserId\"",{"type":23,"tag":240,"props":1730,"children":1731},{"style":819},[1732],{"type":28,"value":837},{"type":23,"tag":240,"props":1734,"children":1735},{"style":840},[1736],{"type":28,"value":1737},"\"user-2\"",{"type":23,"tag":240,"props":1739,"children":1740},{"style":819},[1741],{"type":28,"value":848},{"type":23,"tag":240,"props":1743,"children":1744},{"class":814,"line":895},[1745,1749,1753,1757],{"type":23,"tag":240,"props":1746,"children":1747},{"style":829},[1748],{"type":28,"value":1433},{"type":23,"tag":240,"props":1750,"children":1751},{"style":819},[1752],{"type":28,"value":837},{"type":23,"tag":240,"props":1754,"children":1755},{"style":840},[1756],{"type":28,"value":1442},{"type":23,"tag":240,"props":1758,"children":1759},{"style":819},[1760],{"type":28,"value":848},{"type":23,"tag":240,"props":1762,"children":1763},{"class":814,"line":917},[1764,1768,1772,1776],{"type":23,"tag":240,"props":1765,"children":1766},{"style":829},[1767],{"type":28,"value":1454},{"type":23,"tag":240,"props":1769,"children":1770},{"style":819},[1771],{"type":28,"value":837},{"type":23,"tag":240,"props":1773,"children":1774},{"style":829},[1775],{"type":28,"value":1463},{"type":23,"tag":240,"props":1777,"children":1778},{"style":819},[1779],{"type":28,"value":848},{"type":23,"tag":240,"props":1781,"children":1782},{"class":814,"line":935},[1783,1788,1792,1797],{"type":23,"tag":240,"props":1784,"children":1785},{"style":829},[1786],{"type":28,"value":1787},"  \"status\"",{"type":23,"tag":240,"props":1789,"children":1790},{"style":819},[1791],{"type":28,"value":837},{"type":23,"tag":240,"props":1793,"children":1794},{"style":840},[1795],{"type":28,"value":1796},"\"ACTIVE\"",{"type":23,"tag":240,"props":1798,"children":1799},{"style":819},[1800],{"type":28,"value":848},{"type":23,"tag":240,"props":1802,"children":1803},{"class":814,"line":1080},[1804,1809,1813,1818],{"type":23,"tag":240,"props":1805,"children":1806},{"style":829},[1807],{"type":28,"value":1808},"  \"currentFen\"",{"type":23,"tag":240,"props":1810,"children":1811},{"style":819},[1812],{"type":28,"value":837},{"type":23,"tag":240,"props":1814,"children":1815},{"style":840},[1816],{"type":28,"value":1817},"\"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\"",{"type":23,"tag":240,"props":1819,"children":1820},{"style":819},[1821],{"type":28,"value":848},{"type":23,"tag":240,"props":1823,"children":1824},{"class":814,"line":1101},[1825,1830,1834,1839],{"type":23,"tag":240,"props":1826,"children":1827},{"style":829},[1828],{"type":28,"value":1829},"  \"turn\"",{"type":23,"tag":240,"props":1831,"children":1832},{"style":819},[1833],{"type":28,"value":837},{"type":23,"tag":240,"props":1835,"children":1836},{"style":840},[1837],{"type":28,"value":1838},"\"WHITE\"",{"type":23,"tag":240,"props":1840,"children":1841},{"style":819},[1842],{"type":28,"value":848},{"type":23,"tag":240,"props":1844,"children":1845},{"class":814,"line":1122},[1846,1851,1855,1860],{"type":23,"tag":240,"props":1847,"children":1848},{"style":829},[1849],{"type":28,"value":1850},"  \"whiteTimeMs\"",{"type":23,"tag":240,"props":1852,"children":1853},{"style":819},[1854],{"type":28,"value":837},{"type":23,"tag":240,"props":1856,"children":1857},{"style":829},[1858],{"type":28,"value":1859},"300000",{"type":23,"tag":240,"props":1861,"children":1862},{"style":819},[1863],{"type":28,"value":848},{"type":23,"tag":240,"props":1865,"children":1866},{"class":814,"line":1143},[1867,1872,1876,1880],{"type":23,"tag":240,"props":1868,"children":1869},{"style":829},[1870],{"type":28,"value":1871},"  \"blackTimeMs\"",{"type":23,"tag":240,"props":1873,"children":1874},{"style":819},[1875],{"type":28,"value":837},{"type":23,"tag":240,"props":1877,"children":1878},{"style":829},[1879],{"type":28,"value":1859},{"type":23,"tag":240,"props":1881,"children":1882},{"style":819},[1883],{"type":28,"value":848},{"type":23,"tag":240,"props":1885,"children":1886},{"class":814,"line":1160},[1887,1892,1896,1901],{"type":23,"tag":240,"props":1888,"children":1889},{"style":829},[1890],{"type":28,"value":1891},"  \"moveNumber\"",{"type":23,"tag":240,"props":1893,"children":1894},{"style":819},[1895],{"type":28,"value":837},{"type":23,"tag":240,"props":1897,"children":1898},{"style":829},[1899],{"type":28,"value":1900},"0",{"type":23,"tag":240,"props":1902,"children":1903},{"style":819},[1904],{"type":28,"value":848},{"type":23,"tag":240,"props":1906,"children":1907},{"class":814,"line":1169},[1908,1912,1916],{"type":23,"tag":240,"props":1909,"children":1910},{"style":829},[1911],{"type":28,"value":1475},{"type":23,"tag":240,"props":1913,"children":1914},{"style":819},[1915],{"type":28,"value":837},{"type":23,"tag":240,"props":1917,"children":1918},{"style":829},[1919],{"type":28,"value":1920},"1710000000\n",{"type":23,"tag":240,"props":1922,"children":1924},{"class":814,"line":1923},14,[1925],{"type":23,"tag":240,"props":1926,"children":1927},{"style":819},[1928],{"type":28,"value":941},{"type":23,"tag":24,"props":1930,"children":1931},{},[1932],{"type":28,"value":1933},"The system sends both players a notification:",{"type":23,"tag":803,"props":1935,"children":1937},{"code":1936,"language":806,"meta":7,"className":807,"style":7},"{\n  \"type\": \"GAME_STARTED\",\n  \"gameId\": \"game-123\",\n  \"color\": \"WHITE\",\n  \"opponent\": {\n    \"userId\": \"user-2\",\n    \"rating\": 1520\n  },\n  \"timeControl\": \"5+0\",\n  \"initialFen\": \"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\"\n}\n",[1938],{"type":23,"tag":46,"props":1939,"children":1940},{"__ignoreMap":7},[1941,1948,1968,1987,2007,2019,2039,2056,2064,2083,2100],{"type":23,"tag":240,"props":1942,"children":1943},{"class":814,"line":815},[1944],{"type":23,"tag":240,"props":1945,"children":1946},{"style":819},[1947],{"type":28,"value":822},{"type":23,"tag":240,"props":1949,"children":1950},{"class":814,"line":825},[1951,1955,1959,1964],{"type":23,"tag":240,"props":1952,"children":1953},{"style":829},[1954],{"type":28,"value":832},{"type":23,"tag":240,"props":1956,"children":1957},{"style":819},[1958],{"type":28,"value":837},{"type":23,"tag":240,"props":1960,"children":1961},{"style":840},[1962],{"type":28,"value":1963},"\"GAME_STARTED\"",{"type":23,"tag":240,"props":1965,"children":1966},{"style":819},[1967],{"type":28,"value":848},{"type":23,"tag":240,"props":1969,"children":1970},{"class":814,"line":851},[1971,1975,1979,1983],{"type":23,"tag":240,"props":1972,"children":1973},{"style":829},[1974],{"type":28,"value":857},{"type":23,"tag":240,"props":1976,"children":1977},{"style":819},[1978],{"type":28,"value":837},{"type":23,"tag":240,"props":1980,"children":1981},{"style":840},[1982],{"type":28,"value":866},{"type":23,"tag":240,"props":1984,"children":1985},{"style":819},[1986],{"type":28,"value":848},{"type":23,"tag":240,"props":1988,"children":1989},{"class":814,"line":873},[1990,1995,1999,2003],{"type":23,"tag":240,"props":1991,"children":1992},{"style":829},[1993],{"type":28,"value":1994},"  \"color\"",{"type":23,"tag":240,"props":1996,"children":1997},{"style":819},[1998],{"type":28,"value":837},{"type":23,"tag":240,"props":2000,"children":2001},{"style":840},[2002],{"type":28,"value":1838},{"type":23,"tag":240,"props":2004,"children":2005},{"style":819},[2006],{"type":28,"value":848},{"type":23,"tag":240,"props":2008,"children":2009},{"class":814,"line":895},[2010,2015],{"type":23,"tag":240,"props":2011,"children":2012},{"style":829},[2013],{"type":28,"value":2014},"  \"opponent\"",{"type":23,"tag":240,"props":2016,"children":2017},{"style":819},[2018],{"type":28,"value":1057},{"type":23,"tag":240,"props":2020,"children":2021},{"class":814,"line":917},[2022,2027,2031,2035],{"type":23,"tag":240,"props":2023,"children":2024},{"style":829},[2025],{"type":28,"value":2026},"    \"userId\"",{"type":23,"tag":240,"props":2028,"children":2029},{"style":819},[2030],{"type":28,"value":837},{"type":23,"tag":240,"props":2032,"children":2033},{"style":840},[2034],{"type":28,"value":1737},{"type":23,"tag":240,"props":2036,"children":2037},{"style":819},[2038],{"type":28,"value":848},{"type":23,"tag":240,"props":2040,"children":2041},{"class":814,"line":935},[2042,2047,2051],{"type":23,"tag":240,"props":2043,"children":2044},{"style":829},[2045],{"type":28,"value":2046},"    \"rating\"",{"type":23,"tag":240,"props":2048,"children":2049},{"style":819},[2050],{"type":28,"value":837},{"type":23,"tag":240,"props":2052,"children":2053},{"style":829},[2054],{"type":28,"value":2055},"1520\n",{"type":23,"tag":240,"props":2057,"children":2058},{"class":814,"line":1080},[2059],{"type":23,"tag":240,"props":2060,"children":2061},{"style":819},[2062],{"type":28,"value":2063},"  },\n",{"type":23,"tag":240,"props":2065,"children":2066},{"class":814,"line":1101},[2067,2071,2075,2079],{"type":23,"tag":240,"props":2068,"children":2069},{"style":829},[2070],{"type":28,"value":1433},{"type":23,"tag":240,"props":2072,"children":2073},{"style":819},[2074],{"type":28,"value":837},{"type":23,"tag":240,"props":2076,"children":2077},{"style":840},[2078],{"type":28,"value":1442},{"type":23,"tag":240,"props":2080,"children":2081},{"style":819},[2082],{"type":28,"value":848},{"type":23,"tag":240,"props":2084,"children":2085},{"class":814,"line":1122},[2086,2091,2095],{"type":23,"tag":240,"props":2087,"children":2088},{"style":829},[2089],{"type":28,"value":2090},"  \"initialFen\"",{"type":23,"tag":240,"props":2092,"children":2093},{"style":819},[2094],{"type":28,"value":837},{"type":23,"tag":240,"props":2096,"children":2097},{"style":840},[2098],{"type":28,"value":2099},"\"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\"\n",{"type":23,"tag":240,"props":2101,"children":2102},{"class":814,"line":1143},[2103],{"type":23,"tag":240,"props":2104,"children":2105},{"style":819},[2106],{"type":28,"value":941},{"type":23,"tag":60,"props":2108,"children":2109},{},[],{"type":23,"tag":64,"props":2111,"children":2113},{"id":2112},"_11-game-engine",[2114],{"type":28,"value":2115},"11. Game Engine",{"type":23,"tag":24,"props":2117,"children":2118},{},[2119],{"type":28,"value":2120},"The game engine is the core component in the system.",{"type":23,"tag":24,"props":2122,"children":2123},{},[2124],{"type":28,"value":2125},"It is responsible for:",{"type":23,"tag":81,"props":2127,"children":2128},{},[2129,2134,2139,2144,2149,2154,2159,2164],{"type":23,"tag":85,"props":2130,"children":2131},{},[2132],{"type":28,"value":2133},"Maintaining active game state.",{"type":23,"tag":85,"props":2135,"children":2136},{},[2137],{"type":28,"value":2138},"Validating moves.",{"type":23,"tag":85,"props":2140,"children":2141},{},[2142],{"type":28,"value":2143},"Enforcing turn order.",{"type":23,"tag":85,"props":2145,"children":2146},{},[2147],{"type":28,"value":2148},"Managing clocks.",{"type":23,"tag":85,"props":2150,"children":2151},{},[2152],{"type":28,"value":2153},"Detecting checkmate, stalemate, draw, resignation, and timeout.",{"type":23,"tag":85,"props":2155,"children":2156},{},[2157],{"type":28,"value":2158},"Appending moves to durable storage.",{"type":23,"tag":85,"props":2160,"children":2161},{},[2162],{"type":28,"value":2163},"Sending updates to players.",{"type":23,"tag":85,"props":2165,"children":2166},{},[2167],{"type":28,"value":2168},"Publishing events for asynchronous consumers.",{"type":23,"tag":24,"props":2170,"children":2171},{},[2172,2174,2179],{"type":28,"value":2173},"The game engine must be ",{"type":23,"tag":268,"props":2175,"children":2176},{},[2177],{"type":28,"value":2178},"authoritative",{"type":28,"value":2180},". The client may show the board and calculate legal moves for UX, but the server decides whether a move is valid. This prevents cheating where a modified client sends illegal moves or claims a different clock state.",{"type":23,"tag":60,"props":2182,"children":2183},{},[],{"type":23,"tag":64,"props":2185,"children":2187},{"id":2186},"_12-move-flow",[2188],{"type":28,"value":2189},"12. Move Flow",{"type":23,"tag":24,"props":2191,"children":2192},{},[2193],{"type":28,"value":2194},"A typical move flow:",{"type":23,"tag":490,"props":2196,"children":2197},{},[2198,2203,2208,2213,2218,2223,2228,2241,2253],{"type":23,"tag":85,"props":2199,"children":2200},{},[2201],{"type":28,"value":2202},"Player sends a move over WebSocket.",{"type":23,"tag":85,"props":2204,"children":2205},{},[2206],{"type":28,"value":2207},"Gateway receives the message.",{"type":23,"tag":85,"props":2209,"children":2210},{},[2211],{"type":28,"value":2212},"Connection service routes it to the correct game engine shard.",{"type":23,"tag":85,"props":2214,"children":2215},{},[2216],{"type":28,"value":2217},"Game engine validates the move.",{"type":23,"tag":85,"props":2219,"children":2220},{},[2221],{"type":28,"value":2222},"Game engine appends the move to the move log.",{"type":23,"tag":85,"props":2224,"children":2225},{},[2226],{"type":28,"value":2227},"Game engine updates in-memory state.",{"type":23,"tag":85,"props":2229,"children":2230},{},[2231,2233,2239],{"type":28,"value":2232},"Game engine sends ",{"type":23,"tag":46,"props":2234,"children":2236},{"className":2235},[],[2237],{"type":28,"value":2238},"MOVE_ACCEPTED",{"type":28,"value":2240}," to the active player.",{"type":23,"tag":85,"props":2242,"children":2243},{},[2244,2245,2251],{"type":28,"value":2232},{"type":23,"tag":46,"props":2246,"children":2248},{"className":2247},[],[2249],{"type":28,"value":2250},"OPPONENT_MOVED",{"type":28,"value":2252}," to the opponent.",{"type":23,"tag":85,"props":2254,"children":2255},{},[2256,2258,2264],{"type":28,"value":2257},"Game engine publishes ",{"type":23,"tag":46,"props":2259,"children":2261},{"className":2260},[],[2262],{"type":28,"value":2263},"MOVE_COMMITTED",{"type":28,"value":2265}," to the event stream.",{"type":23,"tag":24,"props":2267,"children":2268},{},[2269],{"type":28,"value":2270},"A move request:",{"type":23,"tag":803,"props":2272,"children":2274},{"code":2273,"language":806,"meta":7,"className":807,"style":7},"{\n  \"type\": \"MOVE_SUBMIT\",\n  \"gameId\": \"game-123\",\n  \"clientMoveId\": \"client-move-999\",\n  \"expectedMoveNumber\": 23,\n  \"move\": \"e2e4\"\n}\n",[2275],{"type":23,"tag":46,"props":2276,"children":2277},{"__ignoreMap":7},[2278,2285,2304,2323,2343,2363,2379],{"type":23,"tag":240,"props":2279,"children":2280},{"class":814,"line":815},[2281],{"type":23,"tag":240,"props":2282,"children":2283},{"style":819},[2284],{"type":28,"value":822},{"type":23,"tag":240,"props":2286,"children":2287},{"class":814,"line":825},[2288,2292,2296,2300],{"type":23,"tag":240,"props":2289,"children":2290},{"style":829},[2291],{"type":28,"value":832},{"type":23,"tag":240,"props":2293,"children":2294},{"style":819},[2295],{"type":28,"value":837},{"type":23,"tag":240,"props":2297,"children":2298},{"style":840},[2299],{"type":28,"value":843},{"type":23,"tag":240,"props":2301,"children":2302},{"style":819},[2303],{"type":28,"value":848},{"type":23,"tag":240,"props":2305,"children":2306},{"class":814,"line":851},[2307,2311,2315,2319],{"type":23,"tag":240,"props":2308,"children":2309},{"style":829},[2310],{"type":28,"value":857},{"type":23,"tag":240,"props":2312,"children":2313},{"style":819},[2314],{"type":28,"value":837},{"type":23,"tag":240,"props":2316,"children":2317},{"style":840},[2318],{"type":28,"value":866},{"type":23,"tag":240,"props":2320,"children":2321},{"style":819},[2322],{"type":28,"value":848},{"type":23,"tag":240,"props":2324,"children":2325},{"class":814,"line":873},[2326,2330,2334,2339],{"type":23,"tag":240,"props":2327,"children":2328},{"style":829},[2329],{"type":28,"value":901},{"type":23,"tag":240,"props":2331,"children":2332},{"style":819},[2333],{"type":28,"value":837},{"type":23,"tag":240,"props":2335,"children":2336},{"style":840},[2337],{"type":28,"value":2338},"\"client-move-999\"",{"type":23,"tag":240,"props":2340,"children":2341},{"style":819},[2342],{"type":28,"value":848},{"type":23,"tag":240,"props":2344,"children":2345},{"class":814,"line":895},[2346,2350,2354,2359],{"type":23,"tag":240,"props":2347,"children":2348},{"style":829},[2349],{"type":28,"value":923},{"type":23,"tag":240,"props":2351,"children":2352},{"style":819},[2353],{"type":28,"value":837},{"type":23,"tag":240,"props":2355,"children":2356},{"style":829},[2357],{"type":28,"value":2358},"23",{"type":23,"tag":240,"props":2360,"children":2361},{"style":819},[2362],{"type":28,"value":848},{"type":23,"tag":240,"props":2364,"children":2365},{"class":814,"line":917},[2366,2370,2374],{"type":23,"tag":240,"props":2367,"children":2368},{"style":829},[2369],{"type":28,"value":879},{"type":23,"tag":240,"props":2371,"children":2372},{"style":819},[2373],{"type":28,"value":837},{"type":23,"tag":240,"props":2375,"children":2376},{"style":840},[2377],{"type":28,"value":2378},"\"e2e4\"\n",{"type":23,"tag":240,"props":2380,"children":2381},{"class":814,"line":935},[2382],{"type":23,"tag":240,"props":2383,"children":2384},{"style":819},[2385],{"type":28,"value":941},{"type":23,"tag":24,"props":2387,"children":2388},{},[2389],{"type":28,"value":2390},"A successful response:",{"type":23,"tag":803,"props":2392,"children":2394},{"code":2393,"language":806,"meta":7,"className":807,"style":7},"{\n  \"type\": \"MOVE_ACCEPTED\",\n  \"gameId\": \"game-123\",\n  \"moveNumber\": 24,\n  \"move\": \"e2e4\",\n  \"fenAfterMove\": \"rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2\",\n  \"whiteTimeMs\": 182000,\n  \"blackTimeMs\": 210000,\n  \"serverTimestamp\": 1710000012\n}\n",[2395],{"type":23,"tag":46,"props":2396,"children":2397},{"__ignoreMap":7},[2398,2405,2425,2444,2464,2483,2504,2524,2544,2561],{"type":23,"tag":240,"props":2399,"children":2400},{"class":814,"line":815},[2401],{"type":23,"tag":240,"props":2402,"children":2403},{"style":819},[2404],{"type":28,"value":822},{"type":23,"tag":240,"props":2406,"children":2407},{"class":814,"line":825},[2408,2412,2416,2421],{"type":23,"tag":240,"props":2409,"children":2410},{"style":829},[2411],{"type":28,"value":832},{"type":23,"tag":240,"props":2413,"children":2414},{"style":819},[2415],{"type":28,"value":837},{"type":23,"tag":240,"props":2417,"children":2418},{"style":840},[2419],{"type":28,"value":2420},"\"MOVE_ACCEPTED\"",{"type":23,"tag":240,"props":2422,"children":2423},{"style":819},[2424],{"type":28,"value":848},{"type":23,"tag":240,"props":2426,"children":2427},{"class":814,"line":851},[2428,2432,2436,2440],{"type":23,"tag":240,"props":2429,"children":2430},{"style":829},[2431],{"type":28,"value":857},{"type":23,"tag":240,"props":2433,"children":2434},{"style":819},[2435],{"type":28,"value":837},{"type":23,"tag":240,"props":2437,"children":2438},{"style":840},[2439],{"type":28,"value":866},{"type":23,"tag":240,"props":2441,"children":2442},{"style":819},[2443],{"type":28,"value":848},{"type":23,"tag":240,"props":2445,"children":2446},{"class":814,"line":873},[2447,2451,2455,2460],{"type":23,"tag":240,"props":2448,"children":2449},{"style":829},[2450],{"type":28,"value":1891},{"type":23,"tag":240,"props":2452,"children":2453},{"style":819},[2454],{"type":28,"value":837},{"type":23,"tag":240,"props":2456,"children":2457},{"style":829},[2458],{"type":28,"value":2459},"24",{"type":23,"tag":240,"props":2461,"children":2462},{"style":819},[2463],{"type":28,"value":848},{"type":23,"tag":240,"props":2465,"children":2466},{"class":814,"line":895},[2467,2471,2475,2479],{"type":23,"tag":240,"props":2468,"children":2469},{"style":829},[2470],{"type":28,"value":879},{"type":23,"tag":240,"props":2472,"children":2473},{"style":819},[2474],{"type":28,"value":837},{"type":23,"tag":240,"props":2476,"children":2477},{"style":840},[2478],{"type":28,"value":888},{"type":23,"tag":240,"props":2480,"children":2481},{"style":819},[2482],{"type":28,"value":848},{"type":23,"tag":240,"props":2484,"children":2485},{"class":814,"line":917},[2486,2491,2495,2500],{"type":23,"tag":240,"props":2487,"children":2488},{"style":829},[2489],{"type":28,"value":2490},"  \"fenAfterMove\"",{"type":23,"tag":240,"props":2492,"children":2493},{"style":819},[2494],{"type":28,"value":837},{"type":23,"tag":240,"props":2496,"children":2497},{"style":840},[2498],{"type":28,"value":2499},"\"rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2\"",{"type":23,"tag":240,"props":2501,"children":2502},{"style":819},[2503],{"type":28,"value":848},{"type":23,"tag":240,"props":2505,"children":2506},{"class":814,"line":935},[2507,2511,2515,2520],{"type":23,"tag":240,"props":2508,"children":2509},{"style":829},[2510],{"type":28,"value":1850},{"type":23,"tag":240,"props":2512,"children":2513},{"style":819},[2514],{"type":28,"value":837},{"type":23,"tag":240,"props":2516,"children":2517},{"style":829},[2518],{"type":28,"value":2519},"182000",{"type":23,"tag":240,"props":2521,"children":2522},{"style":819},[2523],{"type":28,"value":848},{"type":23,"tag":240,"props":2525,"children":2526},{"class":814,"line":1080},[2527,2531,2535,2540],{"type":23,"tag":240,"props":2528,"children":2529},{"style":829},[2530],{"type":28,"value":1871},{"type":23,"tag":240,"props":2532,"children":2533},{"style":819},[2534],{"type":28,"value":837},{"type":23,"tag":240,"props":2536,"children":2537},{"style":829},[2538],{"type":28,"value":2539},"210000",{"type":23,"tag":240,"props":2541,"children":2542},{"style":819},[2543],{"type":28,"value":848},{"type":23,"tag":240,"props":2545,"children":2546},{"class":814,"line":1101},[2547,2552,2556],{"type":23,"tag":240,"props":2548,"children":2549},{"style":829},[2550],{"type":28,"value":2551},"  \"serverTimestamp\"",{"type":23,"tag":240,"props":2553,"children":2554},{"style":819},[2555],{"type":28,"value":837},{"type":23,"tag":240,"props":2557,"children":2558},{"style":829},[2559],{"type":28,"value":2560},"1710000012\n",{"type":23,"tag":240,"props":2562,"children":2563},{"class":814,"line":1122},[2564],{"type":23,"tag":240,"props":2565,"children":2566},{"style":819},[2567],{"type":28,"value":941},{"type":23,"tag":24,"props":2569,"children":2570},{},[2571],{"type":28,"value":2572},"A rejection response:",{"type":23,"tag":803,"props":2574,"children":2576},{"code":2575,"language":806,"meta":7,"className":807,"style":7},"{\n  \"type\": \"MOVE_REJECTED\",\n  \"gameId\": \"game-123\",\n  \"clientMoveId\": \"client-move-999\",\n  \"reason\": \"NOT_YOUR_TURN\"\n}\n",[2577],{"type":23,"tag":46,"props":2578,"children":2579},{"__ignoreMap":7},[2580,2587,2607,2626,2645,2662],{"type":23,"tag":240,"props":2581,"children":2582},{"class":814,"line":815},[2583],{"type":23,"tag":240,"props":2584,"children":2585},{"style":819},[2586],{"type":28,"value":822},{"type":23,"tag":240,"props":2588,"children":2589},{"class":814,"line":825},[2590,2594,2598,2603],{"type":23,"tag":240,"props":2591,"children":2592},{"style":829},[2593],{"type":28,"value":832},{"type":23,"tag":240,"props":2595,"children":2596},{"style":819},[2597],{"type":28,"value":837},{"type":23,"tag":240,"props":2599,"children":2600},{"style":840},[2601],{"type":28,"value":2602},"\"MOVE_REJECTED\"",{"type":23,"tag":240,"props":2604,"children":2605},{"style":819},[2606],{"type":28,"value":848},{"type":23,"tag":240,"props":2608,"children":2609},{"class":814,"line":851},[2610,2614,2618,2622],{"type":23,"tag":240,"props":2611,"children":2612},{"style":829},[2613],{"type":28,"value":857},{"type":23,"tag":240,"props":2615,"children":2616},{"style":819},[2617],{"type":28,"value":837},{"type":23,"tag":240,"props":2619,"children":2620},{"style":840},[2621],{"type":28,"value":866},{"type":23,"tag":240,"props":2623,"children":2624},{"style":819},[2625],{"type":28,"value":848},{"type":23,"tag":240,"props":2627,"children":2628},{"class":814,"line":873},[2629,2633,2637,2641],{"type":23,"tag":240,"props":2630,"children":2631},{"style":829},[2632],{"type":28,"value":901},{"type":23,"tag":240,"props":2634,"children":2635},{"style":819},[2636],{"type":28,"value":837},{"type":23,"tag":240,"props":2638,"children":2639},{"style":840},[2640],{"type":28,"value":2338},{"type":23,"tag":240,"props":2642,"children":2643},{"style":819},[2644],{"type":28,"value":848},{"type":23,"tag":240,"props":2646,"children":2647},{"class":814,"line":895},[2648,2653,2657],{"type":23,"tag":240,"props":2649,"children":2650},{"style":829},[2651],{"type":28,"value":2652},"  \"reason\"",{"type":23,"tag":240,"props":2654,"children":2655},{"style":819},[2656],{"type":28,"value":837},{"type":23,"tag":240,"props":2658,"children":2659},{"style":840},[2660],{"type":28,"value":2661},"\"NOT_YOUR_TURN\"\n",{"type":23,"tag":240,"props":2663,"children":2664},{"class":814,"line":917},[2665],{"type":23,"tag":240,"props":2666,"children":2667},{"style":819},[2668],{"type":28,"value":941},{"type":23,"tag":24,"props":2670,"children":2671},{},[2672],{"type":28,"value":2673},"Possible rejection reasons:",{"type":23,"tag":81,"props":2675,"children":2676},{},[2677,2682,2687,2692],{"type":23,"tag":85,"props":2678,"children":2679},{},[2680],{"type":28,"value":2681},"Invalid game or user not part of the game.",{"type":23,"tag":85,"props":2683,"children":2684},{},[2685],{"type":28,"value":2686},"Game already ended or clock expired.",{"type":23,"tag":85,"props":2688,"children":2689},{},[2690],{"type":28,"value":2691},"Not the user's turn or illegal move.",{"type":23,"tag":85,"props":2693,"children":2694},{},[2695],{"type":28,"value":2696},"Stale move number or duplicate/malformed payload.",{"type":23,"tag":60,"props":2698,"children":2699},{},[],{"type":23,"tag":64,"props":2701,"children":2703},{"id":2702},"_13-server-authoritative-clock",[2704],{"type":28,"value":2705},"13. Server-Authoritative Clock",{"type":23,"tag":24,"props":2707,"children":2708},{},[2709],{"type":28,"value":2710},"Chess clocks are highly sensitive. The client cannot be trusted to report remaining time.",{"type":23,"tag":24,"props":2712,"children":2713},{},[2714],{"type":28,"value":2715},"The server stores:",{"type":23,"tag":81,"props":2717,"children":2718},{},[2719,2724,2729],{"type":23,"tag":85,"props":2720,"children":2721},{},[2722],{"type":28,"value":2723},"Last move committed timestamp.",{"type":23,"tag":85,"props":2725,"children":2726},{},[2727],{"type":28,"value":2728},"Remaining time for white and black.",{"type":23,"tag":85,"props":2730,"children":2731},{},[2732],{"type":28,"value":2733},"Whose turn it is.",{"type":23,"tag":24,"props":2735,"children":2736},{},[2737],{"type":28,"value":2738},"When a player submits a move, the server calculates elapsed time:\n$$\\Delta t = T_{\\text{received}} - T_{\\text{last_committed}}$$",{"type":23,"tag":24,"props":2740,"children":2741},{},[2742],{"type":28,"value":2743},"The server subtracts $\\Delta t$ from the moving player's clock, applies any increment (if the time control uses increment), and commits the updated clock state.",{"type":23,"tag":24,"props":2745,"children":2746},{},[2747],{"type":28,"value":2748},"While the client displays a local countdown for smooth UX, official timeout decisions are always made by the server. This prevents cheating where a client claims it moved earlier than it actually did.",{"type":23,"tag":60,"props":2750,"children":2751},{},[],{"type":23,"tag":64,"props":2753,"children":2755},{"id":2754},"_14-move-log-and-fault-tolerance",[2756],{"type":28,"value":2757},"14. Move Log and Fault Tolerance",{"type":23,"tag":24,"props":2759,"children":2760},{},[2761],{"type":28,"value":2762},"The game engine keeps active game state in memory for low latency. However, memory is volatile. If a game server crashes, we should not lose active games.",{"type":23,"tag":24,"props":2764,"children":2765},{},[2766],{"type":28,"value":2767},"Therefore, every accepted move is appended to durable storage.",{"type":23,"tag":24,"props":2769,"children":2770},{},[2771],{"type":28,"value":2772},"A move log schema:",{"type":23,"tag":81,"props":2774,"children":2775},{},[2776,2784,2793,2802,2811,2820,2829,2838,2847],{"type":23,"tag":85,"props":2777,"children":2778},{},[2779],{"type":23,"tag":46,"props":2780,"children":2782},{"className":2781},[],[2783],{"type":28,"value":1634},{"type":23,"tag":85,"props":2785,"children":2786},{},[2787],{"type":23,"tag":46,"props":2788,"children":2790},{"className":2789},[],[2791],{"type":28,"value":2792},"moveNumber",{"type":23,"tag":85,"props":2794,"children":2795},{},[2796],{"type":23,"tag":46,"props":2797,"children":2799},{"className":2798},[],[2800],{"type":28,"value":2801},"playerId",{"type":23,"tag":85,"props":2803,"children":2804},{},[2805],{"type":23,"tag":46,"props":2806,"children":2808},{"className":2807},[],[2809],{"type":28,"value":2810},"move",{"type":23,"tag":85,"props":2812,"children":2813},{},[2814],{"type":23,"tag":46,"props":2815,"children":2817},{"className":2816},[],[2818],{"type":28,"value":2819},"fenAfterMove",{"type":23,"tag":85,"props":2821,"children":2822},{},[2823],{"type":23,"tag":46,"props":2824,"children":2826},{"className":2825},[],[2827],{"type":28,"value":2828},"serverReceivedAt",{"type":23,"tag":85,"props":2830,"children":2831},{},[2832],{"type":23,"tag":46,"props":2833,"children":2835},{"className":2834},[],[2836],{"type":28,"value":2837},"whiteTimeMs",{"type":23,"tag":85,"props":2839,"children":2840},{},[2841],{"type":23,"tag":46,"props":2842,"children":2844},{"className":2843},[],[2845],{"type":28,"value":2846},"blackTimeMs",{"type":23,"tag":85,"props":2848,"children":2849},{},[2850],{"type":23,"tag":46,"props":2851,"children":2853},{"className":2852},[],[2854],{"type":28,"value":2855},"clientMoveId",{"type":23,"tag":24,"props":2857,"children":2858},{},[2859],{"type":28,"value":2860},"The move log is append-only because players cannot roll back moves. The history of a game is naturally an ordered event log. Append-only logs are easier to recover from, audit, debug, replay, and analyze.",{"type":23,"tag":24,"props":2862,"children":2863},{},[2864],{"type":28,"value":2865},"If the game engine crashes:",{"type":23,"tag":490,"props":2867,"children":2868},{},[2869,2874,2879,2884,2889,2894],{"type":23,"tag":85,"props":2870,"children":2871},{},[2872],{"type":28,"value":2873},"Connection service detects that the shard is unavailable.",{"type":23,"tag":85,"props":2875,"children":2876},{},[2877],{"type":28,"value":2878},"The game is reassigned to another game engine shard.",{"type":23,"tag":85,"props":2880,"children":2881},{},[2882],{"type":28,"value":2883},"The new game engine loads the game metadata.",{"type":23,"tag":85,"props":2885,"children":2886},{},[2887],{"type":28,"value":2888},"The new game engine replays the move log.",{"type":23,"tag":85,"props":2890,"children":2891},{},[2892],{"type":28,"value":2893},"The new game engine reconstructs board state and clocks.",{"type":23,"tag":85,"props":2895,"children":2896},{},[2897],{"type":28,"value":2898},"Players reconnect and the game continues.",{"type":23,"tag":24,"props":2900,"children":2901},{},[2902],{"type":28,"value":2903},"This gives us the best of both worlds: in-memory state for fast move validation, and a durable move log for fault tolerance.",{"type":23,"tag":60,"props":2905,"children":2906},{},[],{"type":23,"tag":64,"props":2908,"children":2910},{"id":2909},"_15-consistency-model",[2911],{"type":28,"value":2912},"15. Consistency Model",{"type":23,"tag":24,"props":2914,"children":2915},{},[2916,2918,2923],{"type":28,"value":2917},"For each individual game, we require ",{"type":23,"tag":268,"props":2919,"children":2920},{},[2921],{"type":28,"value":2922},"strong consistency",{"type":28,"value":2924},":",{"type":23,"tag":81,"props":2926,"children":2927},{},[2928,2933,2938,2943],{"type":23,"tag":85,"props":2929,"children":2930},{},[2931],{"type":28,"value":2932},"Only one move should be accepted for a given move number.",{"type":23,"tag":85,"props":2934,"children":2935},{},[2936],{"type":28,"value":2937},"Moves must be processed in order.",{"type":23,"tag":85,"props":2939,"children":2940},{},[2941],{"type":28,"value":2942},"The same player cannot move twice in a row.",{"type":23,"tag":85,"props":2944,"children":2945},{},[2946],{"type":28,"value":2947},"Illegal moves must never be committed.",{"type":23,"tag":24,"props":2949,"children":2950},{},[2951,2953,2958],{"type":28,"value":2952},"The easiest way to achieve this is to route all moves for a given game to one game-engine owner at a time. We do this by sharding by ",{"type":23,"tag":46,"props":2954,"children":2956},{"className":2955},[],[2957],{"type":28,"value":1634},{"type":28,"value":2959},":\n$$\\text{gameId} \\xrightarrow{\\text{hash}} \\text{gameEngineShard}$$",{"type":23,"tag":24,"props":2961,"children":2962},{},[2963],{"type":28,"value":2964},"Within that shard, moves for a game are processed sequentially. If we allowed multiple servers to write moves for the same game concurrently, we would need distributed locking or complex transactions, which increases latency. Since chess games have low write throughput (at most a few writes per second), single-owner-per-game is the most reasonable model.",{"type":23,"tag":60,"props":2966,"children":2967},{},[],{"type":23,"tag":64,"props":2969,"children":2971},{"id":2970},"_16-idempotency-and-retries",[2972],{"type":28,"value":2973},"16. Idempotency and Retries",{"type":23,"tag":24,"props":2975,"children":2976},{},[2977],{"type":28,"value":2978},"Networks are unreliable. A client may send a move, the server may process it, but the response may get lost. The client will then retry the same move. Without idempotency, the server may process the same move twice.",{"type":23,"tag":24,"props":2980,"children":2981},{},[2982,2984,2989],{"type":28,"value":2983},"To avoid this, each move includes a unique ",{"type":23,"tag":46,"props":2985,"children":2987},{"className":2986},[],[2988],{"type":28,"value":2855},{"type":28,"value":706},{"type":23,"tag":24,"props":2991,"children":2992},{},[2993,2995,3001],{"type":28,"value":2994},"The server stores recently processed ",{"type":23,"tag":46,"props":2996,"children":2998},{"className":2997},[],[2999],{"type":28,"value":3000},"clientMoveIds",{"type":28,"value":3002}," per game/user. If the same move is retried, the server returns the previous cached result instead of applying it again.",{"type":23,"tag":803,"props":3004,"children":3006},{"code":3005,"language":806,"meta":7,"className":807,"style":7},"{\n  \"gameId\": \"game-123\",\n  \"userId\": \"user-1\",\n  \"clientMoveId\": \"move-abc-1\",\n  \"expectedMoveNumber\": 18,\n  \"move\": \"g1f3\"\n}\n",[3007],{"type":23,"tag":46,"props":3008,"children":3009},{"__ignoreMap":7},[3010,3017,3036,3055,3075,3095,3111],{"type":23,"tag":240,"props":3011,"children":3012},{"class":814,"line":815},[3013],{"type":23,"tag":240,"props":3014,"children":3015},{"style":819},[3016],{"type":28,"value":822},{"type":23,"tag":240,"props":3018,"children":3019},{"class":814,"line":825},[3020,3024,3028,3032],{"type":23,"tag":240,"props":3021,"children":3022},{"style":829},[3023],{"type":28,"value":857},{"type":23,"tag":240,"props":3025,"children":3026},{"style":819},[3027],{"type":28,"value":837},{"type":23,"tag":240,"props":3029,"children":3030},{"style":840},[3031],{"type":28,"value":866},{"type":23,"tag":240,"props":3033,"children":3034},{"style":819},[3035],{"type":28,"value":848},{"type":23,"tag":240,"props":3037,"children":3038},{"class":814,"line":851},[3039,3043,3047,3051],{"type":23,"tag":240,"props":3040,"children":3041},{"style":829},[3042],{"type":28,"value":989},{"type":23,"tag":240,"props":3044,"children":3045},{"style":819},[3046],{"type":28,"value":837},{"type":23,"tag":240,"props":3048,"children":3049},{"style":840},[3050],{"type":28,"value":998},{"type":23,"tag":240,"props":3052,"children":3053},{"style":819},[3054],{"type":28,"value":848},{"type":23,"tag":240,"props":3056,"children":3057},{"class":814,"line":873},[3058,3062,3066,3071],{"type":23,"tag":240,"props":3059,"children":3060},{"style":829},[3061],{"type":28,"value":901},{"type":23,"tag":240,"props":3063,"children":3064},{"style":819},[3065],{"type":28,"value":837},{"type":23,"tag":240,"props":3067,"children":3068},{"style":840},[3069],{"type":28,"value":3070},"\"move-abc-1\"",{"type":23,"tag":240,"props":3072,"children":3073},{"style":819},[3074],{"type":28,"value":848},{"type":23,"tag":240,"props":3076,"children":3077},{"class":814,"line":895},[3078,3082,3086,3091],{"type":23,"tag":240,"props":3079,"children":3080},{"style":829},[3081],{"type":28,"value":923},{"type":23,"tag":240,"props":3083,"children":3084},{"style":819},[3085],{"type":28,"value":837},{"type":23,"tag":240,"props":3087,"children":3088},{"style":829},[3089],{"type":28,"value":3090},"18",{"type":23,"tag":240,"props":3092,"children":3093},{"style":819},[3094],{"type":28,"value":848},{"type":23,"tag":240,"props":3096,"children":3097},{"class":814,"line":917},[3098,3102,3106],{"type":23,"tag":240,"props":3099,"children":3100},{"style":829},[3101],{"type":28,"value":879},{"type":23,"tag":240,"props":3103,"children":3104},{"style":819},[3105],{"type":28,"value":837},{"type":23,"tag":240,"props":3107,"children":3108},{"style":840},[3109],{"type":28,"value":3110},"\"g1f3\"\n",{"type":23,"tag":240,"props":3112,"children":3113},{"class":814,"line":935},[3114],{"type":23,"tag":240,"props":3115,"children":3116},{"style":819},[3117],{"type":28,"value":941},{"type":23,"tag":24,"props":3119,"children":3120},{},[3121],{"type":28,"value":3122},"The game engine checks:",{"type":23,"tag":81,"props":3124,"children":3125},{},[3126,3143,3148],{"type":23,"tag":85,"props":3127,"children":3128},{},[3129],{"type":23,"tag":3130,"props":3131,"children":3132},"em",{},[3133,3135,3141],{"type":28,"value":3134},"Have I already processed ",{"type":23,"tag":46,"props":3136,"children":3138},{"className":3137},[],[3139],{"type":28,"value":3140},"clientMoveId = move-abc-1",{"type":28,"value":3142}," for this game/user?",{"type":23,"tag":85,"props":3144,"children":3145},{},[3146],{"type":28,"value":3147},"If yes, return the previous acknowledgement.",{"type":23,"tag":85,"props":3149,"children":3150},{},[3151],{"type":28,"value":3152},"If no, validate normally.",{"type":23,"tag":24,"props":3154,"children":3155},{},[3156],{"type":28,"value":3157},"Making retryable operations idempotent is a standard system design technique.",{"type":23,"tag":60,"props":3159,"children":3160},{},[],{"type":23,"tag":64,"props":3162,"children":3164},{"id":3163},"_17-chat-service",[3165],{"type":28,"value":3166},"17. Chat Service",{"type":23,"tag":24,"props":3168,"children":3169},{},[3170],{"type":28,"value":3171},"The platform supports in-game chat. Chat is less consistency-critical than moves and can be eventually consistent. It should be stored separately from the game move log.",{"type":23,"tag":24,"props":3173,"children":3174},{},[3175],{"type":28,"value":3176},"Basic APIs:",{"type":23,"tag":81,"props":3178,"children":3179},{},[3180,3189],{"type":23,"tag":85,"props":3181,"children":3182},{},[3183],{"type":23,"tag":46,"props":3184,"children":3186},{"className":3185},[],[3187],{"type":28,"value":3188},"sendMessage(gameId, userId, message)",{"type":23,"tag":85,"props":3190,"children":3191},{},[3192],{"type":23,"tag":46,"props":3193,"children":3195},{"className":3194},[],[3196],{"type":28,"value":3197},"getMessages(gameId, userId)",{"type":23,"tag":24,"props":3199,"children":3200},{},[3201],{"type":28,"value":3202},"A chat message object:",{"type":23,"tag":803,"props":3204,"children":3206},{"code":3205,"language":806,"meta":7,"className":807,"style":7},"{\n  \"messageId\": \"msg-123\",\n  \"gameId\": \"game-123\",\n  \"senderId\": \"user-1\",\n  \"message\": \"Good luck!\",\n  \"createdAt\": 1710000000\n}\n",[3207],{"type":23,"tag":46,"props":3208,"children":3209},{"__ignoreMap":7},[3210,3217,3238,3257,3277,3298,3313],{"type":23,"tag":240,"props":3211,"children":3212},{"class":814,"line":815},[3213],{"type":23,"tag":240,"props":3214,"children":3215},{"style":819},[3216],{"type":28,"value":822},{"type":23,"tag":240,"props":3218,"children":3219},{"class":814,"line":825},[3220,3225,3229,3234],{"type":23,"tag":240,"props":3221,"children":3222},{"style":829},[3223],{"type":28,"value":3224},"  \"messageId\"",{"type":23,"tag":240,"props":3226,"children":3227},{"style":819},[3228],{"type":28,"value":837},{"type":23,"tag":240,"props":3230,"children":3231},{"style":840},[3232],{"type":28,"value":3233},"\"msg-123\"",{"type":23,"tag":240,"props":3235,"children":3236},{"style":819},[3237],{"type":28,"value":848},{"type":23,"tag":240,"props":3239,"children":3240},{"class":814,"line":851},[3241,3245,3249,3253],{"type":23,"tag":240,"props":3242,"children":3243},{"style":829},[3244],{"type":28,"value":857},{"type":23,"tag":240,"props":3246,"children":3247},{"style":819},[3248],{"type":28,"value":837},{"type":23,"tag":240,"props":3250,"children":3251},{"style":840},[3252],{"type":28,"value":866},{"type":23,"tag":240,"props":3254,"children":3255},{"style":819},[3256],{"type":28,"value":848},{"type":23,"tag":240,"props":3258,"children":3259},{"class":814,"line":873},[3260,3265,3269,3273],{"type":23,"tag":240,"props":3261,"children":3262},{"style":829},[3263],{"type":28,"value":3264},"  \"senderId\"",{"type":23,"tag":240,"props":3266,"children":3267},{"style":819},[3268],{"type":28,"value":837},{"type":23,"tag":240,"props":3270,"children":3271},{"style":840},[3272],{"type":28,"value":998},{"type":23,"tag":240,"props":3274,"children":3275},{"style":819},[3276],{"type":28,"value":848},{"type":23,"tag":240,"props":3278,"children":3279},{"class":814,"line":895},[3280,3285,3289,3294],{"type":23,"tag":240,"props":3281,"children":3282},{"style":829},[3283],{"type":28,"value":3284},"  \"message\"",{"type":23,"tag":240,"props":3286,"children":3287},{"style":819},[3288],{"type":28,"value":837},{"type":23,"tag":240,"props":3290,"children":3291},{"style":840},[3292],{"type":28,"value":3293},"\"Good luck!\"",{"type":23,"tag":240,"props":3295,"children":3296},{"style":819},[3297],{"type":28,"value":848},{"type":23,"tag":240,"props":3299,"children":3300},{"class":814,"line":917},[3301,3305,3309],{"type":23,"tag":240,"props":3302,"children":3303},{"style":829},[3304],{"type":28,"value":1475},{"type":23,"tag":240,"props":3306,"children":3307},{"style":819},[3308],{"type":28,"value":837},{"type":23,"tag":240,"props":3310,"children":3311},{"style":829},[3312],{"type":28,"value":1920},{"type":23,"tag":240,"props":3314,"children":3315},{"class":814,"line":935},[3316],{"type":23,"tag":240,"props":3317,"children":3318},{"style":819},[3319],{"type":28,"value":941},{"type":23,"tag":24,"props":3321,"children":3322},{},[3323,3325,3331],{"type":28,"value":3324},"For live chat, the chat service can use the same WebSocket connection but a different message type (e.g., ",{"type":23,"tag":46,"props":3326,"children":3328},{"className":3327},[],[3329],{"type":28,"value":3330},"CHAT_MESSAGE",{"type":28,"value":3332},").",{"type":23,"tag":233,"props":3334,"children":3335},{},[3336],{"type":23,"tag":24,"props":3337,"children":3338},{},[3339,3344],{"type":23,"tag":240,"props":3340,"children":3341},{},[3342],{"type":28,"value":3343},"!TIP",{"type":28,"value":3345},"\nDo not let chat processing block move processing. Chat messages should be rate-limited, filtered for abusive content, and stored asynchronously.",{"type":23,"tag":60,"props":3347,"children":3348},{},[],{"type":23,"tag":64,"props":3350,"children":3352},{"id":3351},"_18-spectator-service",[3353],{"type":28,"value":3354},"18. Spectator Service",{"type":23,"tag":24,"props":3356,"children":3357},{},[3358],{"type":28,"value":3359},"With only two players, each move needs to be delivered to two users. But with spectators, a popular game may have thousands or millions of viewers. This creates a fanout problem.",{"type":23,"tag":24,"props":3361,"children":3362},{},[3363],{"type":28,"value":3364},"We must not overload the game engine by making it push updates directly to every spectator.",{"type":23,"tag":24,"props":3366,"children":3367},{},[3368],{"type":28,"value":3369},"Instead:",{"type":23,"tag":490,"props":3371,"children":3372},{},[3373,3378,3383],{"type":23,"tag":85,"props":3374,"children":3375},{},[3376],{"type":28,"value":3377},"Game engine publishes move events to a message broker.",{"type":23,"tag":85,"props":3379,"children":3380},{},[3381],{"type":28,"value":3382},"Spectator service consumes those events.",{"type":23,"tag":85,"props":3384,"children":3385},{},[3386],{"type":28,"value":3387},"Spectator service fans out updates to viewers.",{"type":23,"tag":803,"props":3389,"children":3391},{"code":3390},"Game Engine\n   |\n   +---> Player move delivery (Priority Path)\n   |\n   +---> Event Stream\n            |\n            v\n        Spectator Service ---> Spectator Clients (Isolated Path)\n",[3392],{"type":23,"tag":46,"props":3393,"children":3394},{"__ignoreMap":7},[3395],{"type":28,"value":3390},{"type":23,"tag":24,"props":3397,"children":3398},{},[3399],{"type":28,"value":3400},"This prevents spectator traffic from harming core gameplay. For spectators, we can also optimize bandwidth:",{"type":23,"tag":81,"props":3402,"children":3403},{},[3404,3409,3414],{"type":23,"tag":85,"props":3405,"children":3406},{},[3407],{"type":28,"value":3408},"Send move deltas, not full board state.",{"type":23,"tag":85,"props":3410,"children":3411},{},[3412],{"type":28,"value":3413},"Batch spectator count updates (e.g., update every 5 seconds).",{"type":23,"tag":85,"props":3415,"children":3416},{},[3417],{"type":28,"value":3418},"Use CDN-like fanout or edge servers for popular games.",{"type":23,"tag":60,"props":3420,"children":3421},{},[],{"type":23,"tag":64,"props":3423,"children":3425},{"id":3424},"_19-analysis-engine",[3426],{"type":28,"value":3427},"19. Analysis Engine",{"type":23,"tag":24,"props":3429,"children":3430},{},[3431],{"type":28,"value":3432},"Post-game analysis is CPU-heavy. A chess engine like Stockfish analyzes positions to identify mistakes, blunders, inaccuracies, best moves, and accuracy. This should not happen in the move path.",{"type":23,"tag":24,"props":3434,"children":3435},{},[3436],{"type":28,"value":3369},{"type":23,"tag":490,"props":3438,"children":3439},{},[3440,3445,3456,3461,3466,3471],{"type":23,"tag":85,"props":3441,"children":3442},{},[3443],{"type":28,"value":3444},"Game ends.",{"type":23,"tag":85,"props":3446,"children":3447},{},[3448,3449,3455],{"type":28,"value":2257},{"type":23,"tag":46,"props":3450,"children":3452},{"className":3451},[],[3453],{"type":28,"value":3454},"GAME_ENDED",{"type":28,"value":706},{"type":23,"tag":85,"props":3457,"children":3458},{},[3459],{"type":28,"value":3460},"Analysis engine consumes the event.",{"type":23,"tag":85,"props":3462,"children":3463},{},[3464],{"type":28,"value":3465},"Analysis engine loads the game move history and runs Stockfish.",{"type":23,"tag":85,"props":3467,"children":3468},{},[3469],{"type":28,"value":3470},"Analysis result is stored.",{"type":23,"tag":85,"props":3472,"children":3473},{},[3474],{"type":28,"value":3475},"User views analysis later.",{"type":23,"tag":24,"props":3477,"children":3478},{},[3479],{"type":28,"value":3480},"Example event:",{"type":23,"tag":803,"props":3482,"children":3484},{"code":3483,"language":806,"meta":7,"className":807,"style":7},"{\n  \"eventType\": \"GAME_ENDED\",\n  \"gameId\": \"game-123\",\n  \"whiteUserId\": \"user-1\",\n  \"blackUserId\": \"user-2\",\n  \"result\": \"WHITE_WON\",\n  \"terminationReason\": \"CHECKMATE\",\n  \"endedAt\": 1710000100\n}\n",[3485],{"type":23,"tag":46,"props":3486,"children":3487},{"__ignoreMap":7},[3488,3495,3516,3535,3554,3573,3594,3615,3632],{"type":23,"tag":240,"props":3489,"children":3490},{"class":814,"line":815},[3491],{"type":23,"tag":240,"props":3492,"children":3493},{"style":819},[3494],{"type":28,"value":822},{"type":23,"tag":240,"props":3496,"children":3497},{"class":814,"line":825},[3498,3503,3507,3512],{"type":23,"tag":240,"props":3499,"children":3500},{"style":829},[3501],{"type":28,"value":3502},"  \"eventType\"",{"type":23,"tag":240,"props":3504,"children":3505},{"style":819},[3506],{"type":28,"value":837},{"type":23,"tag":240,"props":3508,"children":3509},{"style":840},[3510],{"type":28,"value":3511},"\"GAME_ENDED\"",{"type":23,"tag":240,"props":3513,"children":3514},{"style":819},[3515],{"type":28,"value":848},{"type":23,"tag":240,"props":3517,"children":3518},{"class":814,"line":851},[3519,3523,3527,3531],{"type":23,"tag":240,"props":3520,"children":3521},{"style":829},[3522],{"type":28,"value":857},{"type":23,"tag":240,"props":3524,"children":3525},{"style":819},[3526],{"type":28,"value":837},{"type":23,"tag":240,"props":3528,"children":3529},{"style":840},[3530],{"type":28,"value":866},{"type":23,"tag":240,"props":3532,"children":3533},{"style":819},[3534],{"type":28,"value":848},{"type":23,"tag":240,"props":3536,"children":3537},{"class":814,"line":873},[3538,3542,3546,3550],{"type":23,"tag":240,"props":3539,"children":3540},{"style":829},[3541],{"type":28,"value":1708},{"type":23,"tag":240,"props":3543,"children":3544},{"style":819},[3545],{"type":28,"value":837},{"type":23,"tag":240,"props":3547,"children":3548},{"style":840},[3549],{"type":28,"value":998},{"type":23,"tag":240,"props":3551,"children":3552},{"style":819},[3553],{"type":28,"value":848},{"type":23,"tag":240,"props":3555,"children":3556},{"class":814,"line":895},[3557,3561,3565,3569],{"type":23,"tag":240,"props":3558,"children":3559},{"style":829},[3560],{"type":28,"value":1728},{"type":23,"tag":240,"props":3562,"children":3563},{"style":819},[3564],{"type":28,"value":837},{"type":23,"tag":240,"props":3566,"children":3567},{"style":840},[3568],{"type":28,"value":1737},{"type":23,"tag":240,"props":3570,"children":3571},{"style":819},[3572],{"type":28,"value":848},{"type":23,"tag":240,"props":3574,"children":3575},{"class":814,"line":917},[3576,3581,3585,3590],{"type":23,"tag":240,"props":3577,"children":3578},{"style":829},[3579],{"type":28,"value":3580},"  \"result\"",{"type":23,"tag":240,"props":3582,"children":3583},{"style":819},[3584],{"type":28,"value":837},{"type":23,"tag":240,"props":3586,"children":3587},{"style":840},[3588],{"type":28,"value":3589},"\"WHITE_WON\"",{"type":23,"tag":240,"props":3591,"children":3592},{"style":819},[3593],{"type":28,"value":848},{"type":23,"tag":240,"props":3595,"children":3596},{"class":814,"line":935},[3597,3602,3606,3611],{"type":23,"tag":240,"props":3598,"children":3599},{"style":829},[3600],{"type":28,"value":3601},"  \"terminationReason\"",{"type":23,"tag":240,"props":3603,"children":3604},{"style":819},[3605],{"type":28,"value":837},{"type":23,"tag":240,"props":3607,"children":3608},{"style":840},[3609],{"type":28,"value":3610},"\"CHECKMATE\"",{"type":23,"tag":240,"props":3612,"children":3613},{"style":819},[3614],{"type":28,"value":848},{"type":23,"tag":240,"props":3616,"children":3617},{"class":814,"line":1080},[3618,3623,3627],{"type":23,"tag":240,"props":3619,"children":3620},{"style":829},[3621],{"type":28,"value":3622},"  \"endedAt\"",{"type":23,"tag":240,"props":3624,"children":3625},{"style":819},[3626],{"type":28,"value":837},{"type":23,"tag":240,"props":3628,"children":3629},{"style":829},[3630],{"type":28,"value":3631},"1710000100\n",{"type":23,"tag":240,"props":3633,"children":3634},{"class":814,"line":1101},[3635],{"type":23,"tag":240,"props":3636,"children":3637},{"style":819},[3638],{"type":28,"value":941},{"type":23,"tag":24,"props":3640,"children":3641},{},[3642],{"type":28,"value":3643},"This is an offline, asynchronous pipeline. It may be delayed by seconds or minutes under peak load, which is acceptable since it is not needed for live gameplay.",{"type":23,"tag":60,"props":3645,"children":3646},{},[],{"type":23,"tag":64,"props":3648,"children":3650},{"id":3649},"_20-rating-engine",[3651],{"type":28,"value":3652},"20. Rating Engine",{"type":23,"tag":24,"props":3654,"children":3655},{},[3656],{"type":28,"value":3657},"Player ratings should be updated after the game ends.",{"type":23,"tag":81,"props":3659,"children":3660},{},[3661,3673,3678,3690],{"type":23,"tag":85,"props":3662,"children":3663},{},[3664,3666,3671],{"type":28,"value":3665},"The rating engine consumes ",{"type":23,"tag":46,"props":3667,"children":3669},{"className":3668},[],[3670],{"type":28,"value":3454},{"type":28,"value":3672}," events.",{"type":23,"tag":85,"props":3674,"children":3675},{},[3676],{"type":28,"value":3677},"It calculates new ratings based on current ratings, game result, and the rating system (e.g., Elo or Glicko-2).",{"type":23,"tag":85,"props":3679,"children":3680},{},[3681,3683,3688],{"type":28,"value":3682},"The rating update must be reliable and idempotent. If the same ",{"type":23,"tag":46,"props":3684,"children":3686},{"className":3685},[],[3687],{"type":28,"value":3454},{"type":28,"value":3689}," event is processed twice, ratings should not be updated twice.",{"type":23,"tag":85,"props":3691,"children":3692},{},[3693,3695,3701],{"type":28,"value":3694},"We use an idempotency key: ",{"type":23,"tag":46,"props":3696,"children":3698},{"className":3697},[],[3699],{"type":28,"value":3700},"rating_update:{gameId}",{"type":28,"value":706},{"type":23,"tag":24,"props":3703,"children":3704},{},[3705],{"type":28,"value":3706},"Rating updates do not block the move path and happen asynchronously.",{"type":23,"tag":60,"props":3708,"children":3709},{},[],{"type":23,"tag":64,"props":3711,"children":3713},{"id":3712},"_21-cheating-checker",[3714],{"type":28,"value":3715},"21. Cheating Checker",{"type":23,"tag":24,"props":3717,"children":3718},{},[3719],{"type":28,"value":3720},"Cheating detection uses multiple signals:",{"type":23,"tag":81,"props":3722,"children":3723},{},[3724,3729,3734,3739,3744],{"type":23,"tag":85,"props":3725,"children":3726},{},[3727],{"type":28,"value":3728},"Move similarity to engine recommendations.",{"type":23,"tag":85,"props":3730,"children":3731},{},[3732],{"type":28,"value":3733},"Consistently high accuracy in complex positions.",{"type":23,"tag":85,"props":3735,"children":3736},{},[3737],{"type":28,"value":3738},"Suspicious timing patterns (e.g., constant 5-second delays on simple moves).",{"type":23,"tag":85,"props":3740,"children":3741},{},[3742],{"type":28,"value":3743},"Reports from opponents.",{"type":23,"tag":85,"props":3745,"children":3746},{},[3747],{"type":28,"value":3748},"Browser/device focus behavior.",{"type":23,"tag":24,"props":3750,"children":3751},{},[3752],{"type":28,"value":3753},"The cheating checker consumes move and game events asynchronously. It does not block a move from being accepted in the normal path unless there is a clear real-time abuse rule. Cheating analysis is probabilistic, expensive, and requires looking at patterns across many games.",{"type":23,"tag":60,"props":3755,"children":3756},{},[],{"type":23,"tag":64,"props":3758,"children":3760},{"id":3759},"_22-notifications-and-campaigns",[3761],{"type":28,"value":3762},"22. Notifications and Campaigns",{"type":23,"tag":24,"props":3764,"children":3765},{},[3766],{"type":28,"value":3767},"Email, SMS, and campaign systems must be fully outside the gameplay path.",{"type":23,"tag":81,"props":3769,"children":3770},{},[3771,3776,3781],{"type":23,"tag":85,"props":3772,"children":3773},{},[3774],{"type":28,"value":3775},"Examples: Tournament announcements, friend challenge notifications, game reminders.",{"type":23,"tag":85,"props":3777,"children":3778},{},[3779],{"type":28,"value":3780},"These systems are slow and failure-prone compared to gameplay. If the email service is down, chess moves should still work.",{"type":23,"tag":85,"props":3782,"children":3783},{},[3784],{"type":28,"value":3785},"This is classic system design isolation: non-critical features should fail independently.",{"type":23,"tag":60,"props":3787,"children":3788},{},[],{"type":23,"tag":64,"props":3790,"children":3792},{"id":3791},"_23-data-model",[3793],{"type":28,"value":3794},"23. Data Model",{"type":23,"tag":24,"props":3796,"children":3797},{},[3798],{"type":28,"value":3799},"Here is the proposed relational or document layout for the core entities:",{"type":23,"tag":689,"props":3801,"children":3803},{"id":3802},"user",[3804],{"type":28,"value":3805},"User",{"type":23,"tag":81,"props":3807,"children":3808},{},[3809,3820,3829,3838,3847,3856,3865],{"type":23,"tag":85,"props":3810,"children":3811},{},[3812,3818],{"type":23,"tag":46,"props":3813,"children":3815},{"className":3814},[],[3816],{"type":28,"value":3817},"userId",{"type":28,"value":3819}," (Primary Key)",{"type":23,"tag":85,"props":3821,"children":3822},{},[3823],{"type":23,"tag":46,"props":3824,"children":3826},{"className":3825},[],[3827],{"type":28,"value":3828},"username",{"type":23,"tag":85,"props":3830,"children":3831},{},[3832],{"type":23,"tag":46,"props":3833,"children":3835},{"className":3834},[],[3836],{"type":28,"value":3837},"email",{"type":23,"tag":85,"props":3839,"children":3840},{},[3841],{"type":23,"tag":46,"props":3842,"children":3844},{"className":3843},[],[3845],{"type":28,"value":3846},"ratingBlitz",{"type":23,"tag":85,"props":3848,"children":3849},{},[3850],{"type":23,"tag":46,"props":3851,"children":3853},{"className":3852},[],[3854],{"type":28,"value":3855},"ratingRapid",{"type":23,"tag":85,"props":3857,"children":3858},{},[3859],{"type":23,"tag":46,"props":3860,"children":3862},{"className":3861},[],[3863],{"type":28,"value":3864},"ratingBullet",{"type":23,"tag":85,"props":3866,"children":3867},{},[3868],{"type":23,"tag":46,"props":3869,"children":3871},{"className":3870},[],[3872],{"type":28,"value":3873},"createdAt",{"type":23,"tag":689,"props":3875,"children":3877},{"id":3876},"game",[3878],{"type":28,"value":3879},"Game",{"type":23,"tag":81,"props":3881,"children":3882},{},[3883,3892,3901,3910,3919,3930,3955,3964,3988,3996,4004,4012,4020,4029,4060],{"type":23,"tag":85,"props":3884,"children":3885},{},[3886,3891],{"type":23,"tag":46,"props":3887,"children":3889},{"className":3888},[],[3890],{"type":28,"value":1634},{"type":28,"value":3819},{"type":23,"tag":85,"props":3893,"children":3894},{},[3895],{"type":23,"tag":46,"props":3896,"children":3898},{"className":3897},[],[3899],{"type":28,"value":3900},"whiteUserId",{"type":23,"tag":85,"props":3902,"children":3903},{},[3904],{"type":23,"tag":46,"props":3905,"children":3907},{"className":3906},[],[3908],{"type":28,"value":3909},"blackUserId",{"type":23,"tag":85,"props":3911,"children":3912},{},[3913],{"type":23,"tag":46,"props":3914,"children":3916},{"className":3915},[],[3917],{"type":28,"value":3918},"timeControl",{"type":23,"tag":85,"props":3920,"children":3921},{},[3922,3928],{"type":23,"tag":46,"props":3923,"children":3925},{"className":3924},[],[3926],{"type":28,"value":3927},"rated",{"type":28,"value":3929}," (Boolean)",{"type":23,"tag":85,"props":3931,"children":3932},{},[3933,3939,3941,3946,3947,3953],{"type":23,"tag":46,"props":3934,"children":3936},{"className":3935},[],[3937],{"type":28,"value":3938},"status",{"type":28,"value":3940}," (",{"type":23,"tag":46,"props":3942,"children":3944},{"className":3943},[],[3945],{"type":28,"value":1656},{"type":28,"value":1275},{"type":23,"tag":46,"props":3948,"children":3950},{"className":3949},[],[3951],{"type":28,"value":3952},"ENDED",{"type":28,"value":3954},")",{"type":23,"tag":85,"props":3956,"children":3957},{},[3958],{"type":23,"tag":46,"props":3959,"children":3961},{"className":3960},[],[3962],{"type":28,"value":3963},"currentFen",{"type":23,"tag":85,"props":3965,"children":3966},{},[3967,3973,3974,3980,3981,3987],{"type":23,"tag":46,"props":3968,"children":3970},{"className":3969},[],[3971],{"type":28,"value":3972},"turn",{"type":28,"value":3940},{"type":23,"tag":46,"props":3975,"children":3977},{"className":3976},[],[3978],{"type":28,"value":3979},"WHITE",{"type":28,"value":1275},{"type":23,"tag":46,"props":3982,"children":3984},{"className":3983},[],[3985],{"type":28,"value":3986},"BLACK",{"type":28,"value":3954},{"type":23,"tag":85,"props":3989,"children":3990},{},[3991],{"type":23,"tag":46,"props":3992,"children":3994},{"className":3993},[],[3995],{"type":28,"value":2837},{"type":23,"tag":85,"props":3997,"children":3998},{},[3999],{"type":23,"tag":46,"props":4000,"children":4002},{"className":4001},[],[4003],{"type":28,"value":2846},{"type":23,"tag":85,"props":4005,"children":4006},{},[4007],{"type":23,"tag":46,"props":4008,"children":4010},{"className":4009},[],[4011],{"type":28,"value":2792},{"type":23,"tag":85,"props":4013,"children":4014},{},[4015],{"type":23,"tag":46,"props":4016,"children":4018},{"className":4017},[],[4019],{"type":28,"value":3873},{"type":23,"tag":85,"props":4021,"children":4022},{},[4023],{"type":23,"tag":46,"props":4024,"children":4026},{"className":4025},[],[4027],{"type":28,"value":4028},"endedAt",{"type":23,"tag":85,"props":4030,"children":4031},{},[4032,4038,4039,4045,4046,4052,4053,4059],{"type":23,"tag":46,"props":4033,"children":4035},{"className":4034},[],[4036],{"type":28,"value":4037},"result",{"type":28,"value":3940},{"type":23,"tag":46,"props":4040,"children":4042},{"className":4041},[],[4043],{"type":28,"value":4044},"WHITE_WON",{"type":28,"value":1275},{"type":23,"tag":46,"props":4047,"children":4049},{"className":4048},[],[4050],{"type":28,"value":4051},"BLACK_WON",{"type":28,"value":1275},{"type":23,"tag":46,"props":4054,"children":4056},{"className":4055},[],[4057],{"type":28,"value":4058},"DRAW",{"type":28,"value":3954},{"type":23,"tag":85,"props":4061,"children":4062},{},[4063,4069,4070,4076,4077,4083,4084,4090,4091,4097],{"type":23,"tag":46,"props":4064,"children":4066},{"className":4065},[],[4067],{"type":28,"value":4068},"terminationReason",{"type":28,"value":3940},{"type":23,"tag":46,"props":4071,"children":4073},{"className":4072},[],[4074],{"type":28,"value":4075},"CHECKMATE",{"type":28,"value":1275},{"type":23,"tag":46,"props":4078,"children":4080},{"className":4079},[],[4081],{"type":28,"value":4082},"TIMEOUT",{"type":28,"value":1275},{"type":23,"tag":46,"props":4085,"children":4087},{"className":4086},[],[4088],{"type":28,"value":4089},"RESIGNATION",{"type":28,"value":1275},{"type":23,"tag":46,"props":4092,"children":4094},{"className":4093},[],[4095],{"type":28,"value":4096},"STALEMATE",{"type":28,"value":3954},{"type":23,"tag":689,"props":4099,"children":4100},{"id":2810},[4101],{"type":28,"value":4102},"Move",{"type":23,"tag":81,"props":4104,"children":4105},{},[4106,4116,4126,4134,4150,4158,4166,4174,4182],{"type":23,"tag":85,"props":4107,"children":4108},{},[4109,4114],{"type":23,"tag":46,"props":4110,"children":4112},{"className":4111},[],[4113],{"type":28,"value":1634},{"type":28,"value":4115}," (Composite Key Part 1)",{"type":23,"tag":85,"props":4117,"children":4118},{},[4119,4124],{"type":23,"tag":46,"props":4120,"children":4122},{"className":4121},[],[4123],{"type":28,"value":2792},{"type":28,"value":4125}," (Composite Key Part 2)",{"type":23,"tag":85,"props":4127,"children":4128},{},[4129],{"type":23,"tag":46,"props":4130,"children":4132},{"className":4131},[],[4133],{"type":28,"value":2801},{"type":23,"tag":85,"props":4135,"children":4136},{},[4137,4142,4144,4149],{"type":23,"tag":46,"props":4138,"children":4140},{"className":4139},[],[4141],{"type":28,"value":2810},{"type":28,"value":4143}," (e.g., ",{"type":23,"tag":46,"props":4145,"children":4147},{"className":4146},[],[4148],{"type":28,"value":51},{"type":28,"value":3954},{"type":23,"tag":85,"props":4151,"children":4152},{},[4153],{"type":23,"tag":46,"props":4154,"children":4156},{"className":4155},[],[4157],{"type":28,"value":2819},{"type":23,"tag":85,"props":4159,"children":4160},{},[4161],{"type":23,"tag":46,"props":4162,"children":4164},{"className":4163},[],[4165],{"type":28,"value":2828},{"type":23,"tag":85,"props":4167,"children":4168},{},[4169],{"type":23,"tag":46,"props":4170,"children":4172},{"className":4171},[],[4173],{"type":28,"value":2837},{"type":23,"tag":85,"props":4175,"children":4176},{},[4177],{"type":23,"tag":46,"props":4178,"children":4180},{"className":4179},[],[4181],{"type":28,"value":2846},{"type":23,"tag":85,"props":4183,"children":4184},{},[4185],{"type":23,"tag":46,"props":4186,"children":4188},{"className":4187},[],[4189],{"type":28,"value":2855},{"type":23,"tag":60,"props":4191,"children":4192},{},[],{"type":23,"tag":64,"props":4194,"children":4196},{"id":4195},"_24-apis",[4197],{"type":28,"value":4198},"24. APIs",{"type":23,"tag":24,"props":4200,"children":4201},{},[4202],{"type":28,"value":4203},"We separate HTTP APIs (for metadata and setup) from WebSocket APIs (for active gameplay).",{"type":23,"tag":689,"props":4205,"children":4207},{"id":4206},"http-apis",[4208],{"type":28,"value":4209},"HTTP APIs",{"type":23,"tag":81,"props":4211,"children":4212},{},[4213,4224,4235,4246,4257,4268],{"type":23,"tag":85,"props":4214,"children":4215},{},[4216,4222],{"type":23,"tag":46,"props":4217,"children":4219},{"className":4218},[],[4220],{"type":28,"value":4221},"POST /challenges",{"type":28,"value":4223}," — Create a challenge",{"type":23,"tag":85,"props":4225,"children":4226},{},[4227,4233],{"type":23,"tag":46,"props":4228,"children":4230},{"className":4229},[],[4231],{"type":28,"value":4232},"DELETE /challenges/{challengeId}",{"type":28,"value":4234}," — Cancel a challenge",{"type":23,"tag":85,"props":4236,"children":4237},{},[4238,4244],{"type":23,"tag":46,"props":4239,"children":4241},{"className":4240},[],[4242],{"type":28,"value":4243},"GET /games/{gameId}",{"type":28,"value":4245}," — Retrieve game metadata",{"type":23,"tag":85,"props":4247,"children":4248},{},[4249,4255],{"type":23,"tag":46,"props":4250,"children":4252},{"className":4251},[],[4253],{"type":28,"value":4254},"GET /games/{gameId}/moves",{"type":28,"value":4256}," — Fetch move history",{"type":23,"tag":85,"props":4258,"children":4259},{},[4260,4266],{"type":23,"tag":46,"props":4261,"children":4263},{"className":4262},[],[4264],{"type":28,"value":4265},"GET /games/{gameId}/analysis",{"type":28,"value":4267}," — Get post-game analysis",{"type":23,"tag":85,"props":4269,"children":4270},{},[4271,4277],{"type":23,"tag":46,"props":4272,"children":4274},{"className":4273},[],[4275],{"type":28,"value":4276},"GET /users/{userId}/profile",{"type":28,"value":4278}," — Fetch user profile",{"type":23,"tag":689,"props":4280,"children":4282},{"id":4281},"websocket-message-types",[4283],{"type":28,"value":4284},"WebSocket Message Types",{"type":23,"tag":81,"props":4286,"children":4287},{},[4288,4299,4310,4321,4339,4349,4359,4377,4388,4398],{"type":23,"tag":85,"props":4289,"children":4290},{},[4291,4297],{"type":23,"tag":46,"props":4292,"children":4294},{"className":4293},[],[4295],{"type":28,"value":4296},"SESSION_INIT",{"type":28,"value":4298}," — Client connects and receives startup payload",{"type":23,"tag":85,"props":4300,"children":4301},{},[4302,4308],{"type":23,"tag":46,"props":4303,"children":4305},{"className":4304},[],[4306],{"type":28,"value":4307},"CHALLENGE_MATCHED",{"type":28,"value":4309}," — Match found, client redirected to game",{"type":23,"tag":85,"props":4311,"children":4312},{},[4313,4319],{"type":23,"tag":46,"props":4314,"children":4316},{"className":4315},[],[4317],{"type":28,"value":4318},"MOVE_SUBMIT",{"type":28,"value":4320}," — Client submits a move",{"type":23,"tag":85,"props":4322,"children":4323},{},[4324,4329,4331,4337],{"type":23,"tag":46,"props":4325,"children":4327},{"className":4326},[],[4328],{"type":28,"value":2238},{"type":28,"value":4330}," / ",{"type":23,"tag":46,"props":4332,"children":4334},{"className":4333},[],[4335],{"type":28,"value":4336},"MOVE_REJECTED",{"type":28,"value":4338}," — Server response to move submission",{"type":23,"tag":85,"props":4340,"children":4341},{},[4342,4347],{"type":23,"tag":46,"props":4343,"children":4345},{"className":4344},[],[4346],{"type":28,"value":2250},{"type":28,"value":4348}," — Server pushes opponent's move to client",{"type":23,"tag":85,"props":4350,"children":4351},{},[4352,4357],{"type":23,"tag":46,"props":4353,"children":4355},{"className":4354},[],[4356],{"type":28,"value":3330},{"type":28,"value":4358}," — Live in-game chat message",{"type":23,"tag":85,"props":4360,"children":4361},{},[4362,4368,4369,4375],{"type":23,"tag":46,"props":4363,"children":4365},{"className":4364},[],[4366],{"type":28,"value":4367},"DRAW_OFFERED",{"type":28,"value":4330},{"type":23,"tag":46,"props":4370,"children":4372},{"className":4371},[],[4373],{"type":28,"value":4374},"DRAW_ACCEPTED",{"type":28,"value":4376}," — Draw negotiation",{"type":23,"tag":85,"props":4378,"children":4379},{},[4380,4386],{"type":23,"tag":46,"props":4381,"children":4383},{"className":4382},[],[4384],{"type":28,"value":4385},"RESIGN",{"type":28,"value":4387}," — Resign game",{"type":23,"tag":85,"props":4389,"children":4390},{},[4391,4396],{"type":23,"tag":46,"props":4392,"children":4394},{"className":4393},[],[4395],{"type":28,"value":3454},{"type":28,"value":4397}," — Game termination notification",{"type":23,"tag":85,"props":4399,"children":4400},{},[4401,4407,4408,4414],{"type":23,"tag":46,"props":4402,"children":4404},{"className":4403},[],[4405],{"type":28,"value":4406},"PING",{"type":28,"value":4330},{"type":23,"tag":46,"props":4409,"children":4411},{"className":4410},[],[4412],{"type":28,"value":4413},"PONG",{"type":28,"value":4415}," — Heartbeat",{"type":23,"tag":60,"props":4417,"children":4418},{},[],{"type":23,"tag":64,"props":4420,"children":4422},{"id":4421},"_25-request-batching",[4423],{"type":28,"value":4424},"25. Request Batching",{"type":23,"tag":24,"props":4426,"children":4427},{},[4428],{"type":28,"value":4429},"Request batching is an important optimization. When a user connects, a naive system may make many separate calls to load profile, rating, active games, pending challenges, unread notifications, and friend status.",{"type":23,"tag":24,"props":4431,"children":4432},{},[4433],{"type":28,"value":4434},"If every small item requires a separate round trip, connection startup becomes slow and expensive. A better design batches startup data.",{"type":23,"tag":24,"props":4436,"children":4437},{},[4438,4440,4445],{"type":28,"value":4439},"Example ",{"type":23,"tag":46,"props":4441,"children":4443},{"className":4442},[],[4444],{"type":28,"value":4296},{"type":28,"value":4446}," response:",{"type":23,"tag":803,"props":4448,"children":4450},{"code":4449,"language":806,"meta":7,"className":807,"style":7},"{\n  \"type\": \"SESSION_INIT\",\n  \"user\": {\n    \"userId\": \"user-1\",\n    \"username\": \"ayush\",\n    \"rating\": 1500\n  },\n  \"activeGame\": {\n    \"gameId\": \"game-123\",\n    \"fen\": \"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\",\n    \"moveNumber\": 18,\n    \"whiteTimeMs\": 120000,\n    \"blackTimeMs\": 115000\n  },\n  \"pendingChallenges\": [],\n  \"notifications\": []\n}\n",[4451],{"type":23,"tag":46,"props":4452,"children":4453},{"__ignoreMap":7},[4454,4461,4481,4493,4512,4533,4549,4556,4568,4587,4607,4627,4648,4665,4672,4686,4700],{"type":23,"tag":240,"props":4455,"children":4456},{"class":814,"line":815},[4457],{"type":23,"tag":240,"props":4458,"children":4459},{"style":819},[4460],{"type":28,"value":822},{"type":23,"tag":240,"props":4462,"children":4463},{"class":814,"line":825},[4464,4468,4472,4477],{"type":23,"tag":240,"props":4465,"children":4466},{"style":829},[4467],{"type":28,"value":832},{"type":23,"tag":240,"props":4469,"children":4470},{"style":819},[4471],{"type":28,"value":837},{"type":23,"tag":240,"props":4473,"children":4474},{"style":840},[4475],{"type":28,"value":4476},"\"SESSION_INIT\"",{"type":23,"tag":240,"props":4478,"children":4479},{"style":819},[4480],{"type":28,"value":848},{"type":23,"tag":240,"props":4482,"children":4483},{"class":814,"line":851},[4484,4489],{"type":23,"tag":240,"props":4485,"children":4486},{"style":829},[4487],{"type":28,"value":4488},"  \"user\"",{"type":23,"tag":240,"props":4490,"children":4491},{"style":819},[4492],{"type":28,"value":1057},{"type":23,"tag":240,"props":4494,"children":4495},{"class":814,"line":873},[4496,4500,4504,4508],{"type":23,"tag":240,"props":4497,"children":4498},{"style":829},[4499],{"type":28,"value":2026},{"type":23,"tag":240,"props":4501,"children":4502},{"style":819},[4503],{"type":28,"value":837},{"type":23,"tag":240,"props":4505,"children":4506},{"style":840},[4507],{"type":28,"value":998},{"type":23,"tag":240,"props":4509,"children":4510},{"style":819},[4511],{"type":28,"value":848},{"type":23,"tag":240,"props":4513,"children":4514},{"class":814,"line":895},[4515,4520,4524,4529],{"type":23,"tag":240,"props":4516,"children":4517},{"style":829},[4518],{"type":28,"value":4519},"    \"username\"",{"type":23,"tag":240,"props":4521,"children":4522},{"style":819},[4523],{"type":28,"value":837},{"type":23,"tag":240,"props":4525,"children":4526},{"style":840},[4527],{"type":28,"value":4528},"\"ayush\"",{"type":23,"tag":240,"props":4530,"children":4531},{"style":819},[4532],{"type":28,"value":848},{"type":23,"tag":240,"props":4534,"children":4535},{"class":814,"line":917},[4536,4540,4544],{"type":23,"tag":240,"props":4537,"children":4538},{"style":829},[4539],{"type":28,"value":2046},{"type":23,"tag":240,"props":4541,"children":4542},{"style":819},[4543],{"type":28,"value":837},{"type":23,"tag":240,"props":4545,"children":4546},{"style":829},[4547],{"type":28,"value":4548},"1500\n",{"type":23,"tag":240,"props":4550,"children":4551},{"class":814,"line":935},[4552],{"type":23,"tag":240,"props":4553,"children":4554},{"style":819},[4555],{"type":28,"value":2063},{"type":23,"tag":240,"props":4557,"children":4558},{"class":814,"line":1080},[4559,4564],{"type":23,"tag":240,"props":4560,"children":4561},{"style":829},[4562],{"type":28,"value":4563},"  \"activeGame\"",{"type":23,"tag":240,"props":4565,"children":4566},{"style":819},[4567],{"type":28,"value":1057},{"type":23,"tag":240,"props":4569,"children":4570},{"class":814,"line":1101},[4571,4575,4579,4583],{"type":23,"tag":240,"props":4572,"children":4573},{"style":829},[4574],{"type":28,"value":1086},{"type":23,"tag":240,"props":4576,"children":4577},{"style":819},[4578],{"type":28,"value":837},{"type":23,"tag":240,"props":4580,"children":4581},{"style":840},[4582],{"type":28,"value":866},{"type":23,"tag":240,"props":4584,"children":4585},{"style":819},[4586],{"type":28,"value":848},{"type":23,"tag":240,"props":4588,"children":4589},{"class":814,"line":1122},[4590,4595,4599,4603],{"type":23,"tag":240,"props":4591,"children":4592},{"style":829},[4593],{"type":28,"value":4594},"    \"fen\"",{"type":23,"tag":240,"props":4596,"children":4597},{"style":819},[4598],{"type":28,"value":837},{"type":23,"tag":240,"props":4600,"children":4601},{"style":840},[4602],{"type":28,"value":1817},{"type":23,"tag":240,"props":4604,"children":4605},{"style":819},[4606],{"type":28,"value":848},{"type":23,"tag":240,"props":4608,"children":4609},{"class":814,"line":1143},[4610,4615,4619,4623],{"type":23,"tag":240,"props":4611,"children":4612},{"style":829},[4613],{"type":28,"value":4614},"    \"moveNumber\"",{"type":23,"tag":240,"props":4616,"children":4617},{"style":819},[4618],{"type":28,"value":837},{"type":23,"tag":240,"props":4620,"children":4621},{"style":829},[4622],{"type":28,"value":3090},{"type":23,"tag":240,"props":4624,"children":4625},{"style":819},[4626],{"type":28,"value":848},{"type":23,"tag":240,"props":4628,"children":4629},{"class":814,"line":1160},[4630,4635,4639,4644],{"type":23,"tag":240,"props":4631,"children":4632},{"style":829},[4633],{"type":28,"value":4634},"    \"whiteTimeMs\"",{"type":23,"tag":240,"props":4636,"children":4637},{"style":819},[4638],{"type":28,"value":837},{"type":23,"tag":240,"props":4640,"children":4641},{"style":829},[4642],{"type":28,"value":4643},"120000",{"type":23,"tag":240,"props":4645,"children":4646},{"style":819},[4647],{"type":28,"value":848},{"type":23,"tag":240,"props":4649,"children":4650},{"class":814,"line":1169},[4651,4656,4660],{"type":23,"tag":240,"props":4652,"children":4653},{"style":829},[4654],{"type":28,"value":4655},"    \"blackTimeMs\"",{"type":23,"tag":240,"props":4657,"children":4658},{"style":819},[4659],{"type":28,"value":837},{"type":23,"tag":240,"props":4661,"children":4662},{"style":829},[4663],{"type":28,"value":4664},"115000\n",{"type":23,"tag":240,"props":4666,"children":4667},{"class":814,"line":1923},[4668],{"type":23,"tag":240,"props":4669,"children":4670},{"style":819},[4671],{"type":28,"value":2063},{"type":23,"tag":240,"props":4673,"children":4675},{"class":814,"line":4674},15,[4676,4681],{"type":23,"tag":240,"props":4677,"children":4678},{"style":829},[4679],{"type":28,"value":4680},"  \"pendingChallenges\"",{"type":23,"tag":240,"props":4682,"children":4683},{"style":819},[4684],{"type":28,"value":4685},": [],\n",{"type":23,"tag":240,"props":4687,"children":4689},{"class":814,"line":4688},16,[4690,4695],{"type":23,"tag":240,"props":4691,"children":4692},{"style":829},[4693],{"type":28,"value":4694},"  \"notifications\"",{"type":23,"tag":240,"props":4696,"children":4697},{"style":819},[4698],{"type":28,"value":4699},": []\n",{"type":23,"tag":240,"props":4701,"children":4703},{"class":814,"line":4702},17,[4704],{"type":23,"tag":240,"props":4705,"children":4706},{"style":819},[4707],{"type":28,"value":941},{"type":23,"tag":24,"props":4709,"children":4710},{},[4711],{"type":28,"value":4712},"Batching reduces latency, network overhead, backend request count, and CPU serialization overhead. However, do not delay critical moves just to batch them. Critical gameplay updates should be sent immediately.",{"type":23,"tag":60,"props":4714,"children":4715},{},[],{"type":23,"tag":64,"props":4717,"children":4719},{"id":4718},"_26-low-bandwidth-design",[4720],{"type":28,"value":4721},"26. Low Bandwidth Design",{"type":23,"tag":24,"props":4723,"children":4724},{},[4725],{"type":28,"value":4726},"Chess is naturally low bandwidth, but bad design can still waste bytes.",{"type":23,"tag":81,"props":4728,"children":4729},{},[4730,4748,4758],{"type":23,"tag":85,"props":4731,"children":4732},{},[4733,4738,4740,4746],{"type":23,"tag":268,"props":4734,"children":4735},{},[4736],{"type":28,"value":4737},"Gameplay",{"type":28,"value":4739},": Use move deltas (e.g., ",{"type":23,"tag":46,"props":4741,"children":4743},{"className":4742},[],[4744],{"type":28,"value":4745},"e2e5",{"type":28,"value":4747},", clock times) for normal updates.",{"type":23,"tag":85,"props":4749,"children":4750},{},[4751,4756],{"type":23,"tag":268,"props":4752,"children":4753},{},[4754],{"type":28,"value":4755},"Reconnect/Resync",{"type":28,"value":4757},": Send full snapshots (full board FEN, entire move list) only when a client reconnects or experiences state desynchronization.",{"type":23,"tag":85,"props":4759,"children":4760},{},[4761,4766],{"type":23,"tag":268,"props":4762,"children":4763},{},[4764],{"type":28,"value":4765},"Recovery",{"type":28,"value":4767},": Replay from the move log for deep debugging.",{"type":23,"tag":24,"props":4769,"children":4770},{},[4771],{"type":28,"value":4772},"This reduces bandwidth and allows each gateway server to support more concurrent connections.",{"type":23,"tag":60,"props":4774,"children":4775},{},[],{"type":23,"tag":64,"props":4777,"children":4779},{"id":4778},"_27-scaling-strategy",[4780],{"type":28,"value":4781},"27. Scaling Strategy",{"type":23,"tag":24,"props":4783,"children":4784},{},[4785],{"type":28,"value":4786},"Different parts of the system scale on different axes:",{"type":23,"tag":4788,"props":4789,"children":4790},"table",{},[4791,4816],{"type":23,"tag":4792,"props":4793,"children":4794},"thead",{},[4795],{"type":23,"tag":4796,"props":4797,"children":4798},"tr",{},[4799,4806,4811],{"type":23,"tag":4800,"props":4801,"children":4803},"th",{"align":4802},"left",[4804],{"type":28,"value":4805},"Component",{"type":23,"tag":4800,"props":4807,"children":4808},{"align":4802},[4809],{"type":28,"value":4810},"Scaling Bottleneck",{"type":23,"tag":4800,"props":4812,"children":4813},{"align":4802},[4814],{"type":28,"value":4815},"Strategy",{"type":23,"tag":4817,"props":4818,"children":4819},"tbody",{},[4820,4842,4862,4883,4911,4932],{"type":23,"tag":4796,"props":4821,"children":4822},{},[4823,4832,4837],{"type":23,"tag":4824,"props":4825,"children":4826},"td",{"align":4802},[4827],{"type":23,"tag":268,"props":4828,"children":4829},{},[4830],{"type":28,"value":4831},"Gateway",{"type":23,"tag":4824,"props":4833,"children":4834},{"align":4802},[4835],{"type":28,"value":4836},"Concurrent connections, memory, file descriptors",{"type":23,"tag":4824,"props":4838,"children":4839},{"align":4802},[4840],{"type":28,"value":4841},"Horizontal scaling, lightweight connections",{"type":23,"tag":4796,"props":4843,"children":4844},{},[4845,4852,4857],{"type":23,"tag":4824,"props":4846,"children":4847},{"align":4802},[4848],{"type":23,"tag":268,"props":4849,"children":4850},{},[4851],{"type":28,"value":732},{"type":23,"tag":4824,"props":4853,"children":4854},{"align":4802},[4855],{"type":28,"value":4856},"Message routing QPS, registry lookups",{"type":23,"tag":4824,"props":4858,"children":4859},{"align":4802},[4860],{"type":28,"value":4861},"Redis sharding, lookup caching",{"type":23,"tag":4796,"props":4863,"children":4864},{},[4865,4873,4878],{"type":23,"tag":4824,"props":4866,"children":4867},{"align":4802},[4868],{"type":23,"tag":268,"props":4869,"children":4870},{},[4871],{"type":28,"value":4872},"Match Engine",{"type":23,"tag":4824,"props":4874,"children":4875},{"align":4802},[4876],{"type":28,"value":4877},"Pending challenge volume",{"type":23,"tag":4824,"props":4879,"children":4880},{"align":4802},[4881],{"type":28,"value":4882},"Partition by time control + rating bucket",{"type":23,"tag":4796,"props":4884,"children":4885},{},[4886,4894,4899],{"type":23,"tag":4824,"props":4887,"children":4888},{"align":4802},[4889],{"type":23,"tag":268,"props":4890,"children":4891},{},[4892],{"type":28,"value":4893},"Game Engine",{"type":23,"tag":4824,"props":4895,"children":4896},{"align":4802},[4897],{"type":28,"value":4898},"Active games, move validation QPS",{"type":23,"tag":4824,"props":4900,"children":4901},{"align":4802},[4902,4904,4909],{"type":28,"value":4903},"Shard by ",{"type":23,"tag":46,"props":4905,"children":4907},{"className":4906},[],[4908],{"type":28,"value":1634},{"type":28,"value":4910},", in-memory state",{"type":23,"tag":4796,"props":4912,"children":4913},{},[4914,4922,4927],{"type":23,"tag":4824,"props":4915,"children":4916},{"align":4802},[4917],{"type":23,"tag":268,"props":4918,"children":4919},{},[4920],{"type":28,"value":4921},"Spectator Service",{"type":23,"tag":4824,"props":4923,"children":4924},{"align":4802},[4925],{"type":28,"value":4926},"Fanout volume to viewers",{"type":23,"tag":4824,"props":4928,"children":4929},{"align":4802},[4930],{"type":28,"value":4931},"Pub/Sub event distribution, edge cache",{"type":23,"tag":4796,"props":4933,"children":4934},{},[4935,4943,4948],{"type":23,"tag":4824,"props":4936,"children":4937},{"align":4802},[4938],{"type":23,"tag":268,"props":4939,"children":4940},{},[4941],{"type":28,"value":4942},"Analysis Engine",{"type":23,"tag":4824,"props":4944,"children":4945},{"align":4802},[4946],{"type":28,"value":4947},"CPU-heavy Stockfish processing",{"type":23,"tag":4824,"props":4949,"children":4950},{"align":4802},[4951],{"type":28,"value":4952},"Async workers, priority message queues",{"type":23,"tag":60,"props":4954,"children":4955},{},[],{"type":23,"tag":64,"props":4957,"children":4959},{"id":4958},"_28-sharding",[4960],{"type":28,"value":4961},"28. Sharding",{"type":23,"tag":24,"props":4963,"children":4964},{},[4965],{"type":28,"value":4966},"For sharding:",{"type":23,"tag":81,"props":4968,"children":4969},{},[4970,4986,5001,5010],{"type":23,"tag":85,"props":4971,"children":4972},{},[4973,4977,4979,4984],{"type":23,"tag":268,"props":4974,"children":4975},{},[4976],{"type":28,"value":4893},{"type":28,"value":4978},": Shard by ",{"type":23,"tag":46,"props":4980,"children":4982},{"className":4981},[],[4983],{"type":28,"value":1634},{"type":28,"value":4985}," using consistent hashing:\n$$\\text{shard} = \\text{hash}(\\text{gameId}) \\pmod{\\text{number_of_shards}}$$",{"type":23,"tag":85,"props":4987,"children":4988},{},[4989,4994,4995,5000],{"type":23,"tag":268,"props":4990,"children":4991},{},[4992],{"type":28,"value":4993},"Connection Registry",{"type":28,"value":4978},{"type":23,"tag":46,"props":4996,"children":4998},{"className":4997},[],[4999],{"type":28,"value":3817},{"type":28,"value":706},{"type":23,"tag":85,"props":5002,"children":5003},{},[5004,5008],{"type":23,"tag":268,"props":5005,"children":5006},{},[5007],{"type":28,"value":4872},{"type":28,"value":5009},": Shard by time control and rating bucket.",{"type":23,"tag":85,"props":5011,"children":5012},{},[5013,5018,5020,5025,5027,5032],{"type":23,"tag":268,"props":5014,"children":5015},{},[5016],{"type":28,"value":5017},"Move Store",{"type":28,"value":5019},": Partition by ",{"type":23,"tag":46,"props":5021,"children":5023},{"className":5022},[],[5024],{"type":28,"value":1634},{"type":28,"value":5026}," or ",{"type":23,"tag":46,"props":5028,"children":5030},{"className":5029},[],[5031],{"type":28,"value":3873},{"type":28,"value":5033}," time.",{"type":23,"tag":24,"props":5035,"children":5036},{},[5037],{"type":28,"value":5038},"Consistent hashing reduces remapping when shards are added or removed. However, because games are active and stateful, shard movement must be done carefully. A game should not bounce between servers during active play unless there is a failure recovery or planned migration.",{"type":23,"tag":60,"props":5040,"children":5041},{},[],{"type":23,"tag":64,"props":5043,"children":5045},{"id":5044},"_29-caching",[5046],{"type":28,"value":5047},"29. Caching",{"type":23,"tag":24,"props":5049,"children":5050},{},[5051],{"type":28,"value":5052},"Caching can be used for:",{"type":23,"tag":81,"props":5054,"children":5055},{},[5056,5061,5066,5071],{"type":23,"tag":85,"props":5057,"children":5058},{},[5059],{"type":28,"value":5060},"User profiles and ratings.",{"type":23,"tag":85,"props":5062,"children":5063},{},[5064],{"type":28,"value":5065},"Active game metadata.",{"type":23,"tag":85,"props":5067,"children":5068},{},[5069],{"type":28,"value":5070},"Connection registry and pending challenges.",{"type":23,"tag":85,"props":5072,"children":5073},{},[5074],{"type":28,"value":5075},"Recent chat messages.",{"type":23,"tag":24,"props":5077,"children":5078},{},[5079,5081,5086,5088,5093],{"type":28,"value":5080},"However, the cache should not become the source of truth for critical move validation. The authoritative state is always the ",{"type":23,"tag":268,"props":5082,"children":5083},{},[5084],{"type":28,"value":5085},"in-memory game engine",{"type":28,"value":5087}," backed by the ",{"type":23,"tag":268,"props":5089,"children":5090},{},[5091],{"type":28,"value":5092},"durable move log",{"type":28,"value":706},{"type":23,"tag":60,"props":5095,"children":5096},{},[],{"type":23,"tag":64,"props":5098,"children":5100},{"id":5099},"_30-deployment-and-thundering-herds",[5101],{"type":28,"value":5102},"30. Deployment and Thundering Herds",{"type":23,"tag":24,"props":5104,"children":5105},{},[5106,5108,5112],{"type":28,"value":5107},"If a gateway process is restarted, all clients connected to it disconnect. If thousands of clients reconnect at the same time, they can overload the load balancer, authentication service, session service, connection registry, Redis, and game engine. This is a ",{"type":23,"tag":268,"props":5109,"children":5110},{},[5111],{"type":28,"value":704},{"type":28,"value":706},{"type":23,"tag":24,"props":5114,"children":5115},{},[5116],{"type":28,"value":5117},"To defend against this:",{"type":23,"tag":81,"props":5119,"children":5120},{},[5121,5131,5141],{"type":23,"tag":85,"props":5122,"children":5123},{},[5124,5129],{"type":23,"tag":268,"props":5125,"children":5126},{},[5127],{"type":28,"value":5128},"Jittered Reconnect",{"type":28,"value":5130},": Implement exponential backoff with random jitter:\n$$t_{\\text{wait}} = \\text{min}(t_{\\text{max}}, 2^{\\text{attempt}} \\times \\text{base}) \\pm \\text{random_jitter}$$",{"type":23,"tag":85,"props":5132,"children":5133},{},[5134,5139],{"type":23,"tag":268,"props":5135,"children":5136},{},[5137],{"type":28,"value":5138},"Connection Draining",{"type":28,"value":5140},": Stop sending new connections to old gateway instances. Allow existing games to finish before closing remaining connections gracefully.",{"type":23,"tag":85,"props":5142,"children":5143},{},[5144,5149],{"type":23,"tag":268,"props":5145,"children":5146},{},[5147],{"type":28,"value":5148},"Gateway Stability",{"type":28,"value":5150},": Because the gateway is dumb and stable, most feature deployments happen behind it. This reduces the frequency of risky gateway deployments.",{"type":23,"tag":60,"props":5152,"children":5153},{},[],{"type":23,"tag":64,"props":5155,"children":5157},{"id":5156},"_31-failure-modes-and-defenses",[5158],{"type":28,"value":5159},"31. Failure Modes and Defenses",{"type":23,"tag":81,"props":5161,"children":5162},{},[5163,5173,5183,5200,5210,5220],{"type":23,"tag":85,"props":5164,"children":5165},{},[5166,5171],{"type":23,"tag":268,"props":5167,"children":5168},{},[5169],{"type":28,"value":5170},"Gateway Crash",{"type":28,"value":5172},": Reconnect with jitter, session resume, connection registry TTL.",{"type":23,"tag":85,"props":5174,"children":5175},{},[5176,5181],{"type":23,"tag":268,"props":5177,"children":5178},{},[5179],{"type":28,"value":5180},"Game Engine Crash",{"type":28,"value":5182},": Durable move log, game recovery through replay, shard reassignment.",{"type":23,"tag":85,"props":5184,"children":5185},{},[5186,5191,5193,5198],{"type":23,"tag":268,"props":5187,"children":5188},{},[5189],{"type":28,"value":5190},"Duplicate Move Retry",{"type":28,"value":5192},": Use ",{"type":23,"tag":46,"props":5194,"children":5196},{"className":5195},[],[5197],{"type":28,"value":2855},{"type":28,"value":5199}," and expected move number.",{"type":23,"tag":85,"props":5201,"children":5202},{},[5203,5208],{"type":23,"tag":268,"props":5204,"children":5205},{},[5206],{"type":28,"value":5207},"Out-of-order Messages",{"type":28,"value":5209},": Validate expected move number, process moves sequentially.",{"type":23,"tag":85,"props":5211,"children":5212},{},[5213,5218],{"type":23,"tag":268,"props":5214,"children":5215},{},[5216],{"type":28,"value":5217},"Spectator Fanout Spike",{"type":28,"value":5219},": Separate spectator service, CDN fanout, prioritize players.",{"type":23,"tag":85,"props":5221,"children":5222},{},[5223,5228],{"type":23,"tag":268,"props":5224,"children":5225},{},[5226],{"type":28,"value":5227},"Rating Double Update",{"type":28,"value":5229},": Idempotency key per game, transactional database update.",{"type":23,"tag":60,"props":5231,"children":5232},{},[],{"type":23,"tag":64,"props":5234,"children":5236},{"id":5235},"_32-monitoring-and-observability",[5237],{"type":28,"value":5238},"32. Monitoring and Observability",{"type":23,"tag":24,"props":5240,"children":5241},{},[5242],{"type":28,"value":5243},"Key Metrics to track:",{"type":23,"tag":81,"props":5245,"children":5246},{},[5247,5256,5265,5275],{"type":23,"tag":85,"props":5248,"children":5249},{},[5250,5254],{"type":23,"tag":268,"props":5251,"children":5252},{},[5253],{"type":28,"value":4831},{"type":28,"value":5255},": Active connections, connect/disconnect rates, gateway CPU/memory/file descriptors.",{"type":23,"tag":85,"props":5257,"children":5258},{},[5259,5263],{"type":23,"tag":268,"props":5260,"children":5261},{},[5262],{"type":28,"value":4737},{"type":28,"value":5264},": Move submit latency ($p_{50}, p_{95}, p_{99}$), move validation latency, opponent delivery latency.",{"type":23,"tag":85,"props":5266,"children":5267},{},[5268,5273],{"type":23,"tag":268,"props":5269,"children":5270},{},[5271],{"type":28,"value":5272},"Matchmaking",{"type":28,"value":5274},": Matchmaking wait times, challenge expiration rate, game creation rate.",{"type":23,"tag":85,"props":5276,"children":5277},{},[5278,5283],{"type":23,"tag":268,"props":5279,"children":5280},{},[5281],{"type":28,"value":5282},"Downstream",{"type":28,"value":5284},": Analysis queue lag, rating update failures, cheating checker backlog.",{"type":23,"tag":60,"props":5286,"children":5287},{},[],{"type":23,"tag":64,"props":5289,"children":5291},{"id":5290},"_33-summary",[5292],{"type":28,"value":5293},"33. Summary",{"type":23,"tag":24,"props":5295,"children":5296},{},[5297],{"type":28,"value":5298},"A scalable chess platform is not difficult because chess moves are large. It is difficult because real-time correctness, connection management, fault tolerance, and safe deployments all interact.",{"type":23,"tag":689,"props":5300,"children":5302},{"id":5301},"key-takeaways",[5303],{"type":28,"value":5304},"Key Takeaways",{"type":23,"tag":490,"props":5306,"children":5307},{},[5308,5318,5328,5338,5352,5362,5372,5382],{"type":23,"tag":85,"props":5309,"children":5310},{},[5311,5316],{"type":23,"tag":268,"props":5312,"children":5313},{},[5314],{"type":28,"value":5315},"Use WebSockets",{"type":28,"value":5317}," for live gameplay, but keep gateways dumb and stable.",{"type":23,"tag":85,"props":5319,"children":5320},{},[5321,5326],{"type":23,"tag":268,"props":5322,"children":5323},{},[5324],{"type":28,"value":5325},"Move business logic",{"type":28,"value":5327}," into downstream services (Connection, Game, Match).",{"type":23,"tag":85,"props":5329,"children":5330},{},[5331,5336],{"type":23,"tag":268,"props":5332,"children":5333},{},[5334],{"type":28,"value":5335},"Use the server",{"type":28,"value":5337}," as the absolute source of truth for moves and clocks.",{"type":23,"tag":85,"props":5339,"children":5340},{},[5341,5350],{"type":23,"tag":268,"props":5342,"children":5343},{},[5344,5345],{"type":28,"value":4903},{"type":23,"tag":46,"props":5346,"children":5348},{"className":5347},[],[5349],{"type":28,"value":1634},{"type":28,"value":5351}," to process moves sequentially per game.",{"type":23,"tag":85,"props":5353,"children":5354},{},[5355,5360],{"type":23,"tag":268,"props":5356,"children":5357},{},[5358],{"type":28,"value":5359},"Log every accepted move",{"type":28,"value":5361}," to an append-only durable store for recovery.",{"type":23,"tag":85,"props":5363,"children":5364},{},[5365,5370],{"type":23,"tag":268,"props":5366,"children":5367},{},[5368],{"type":28,"value":5369},"Isolate spectators",{"type":28,"value":5371}," from players via a separate fanout service.",{"type":23,"tag":85,"props":5373,"children":5374},{},[5375,5380],{"type":23,"tag":268,"props":5376,"children":5377},{},[5378],{"type":28,"value":5379},"Process non-critical operations",{"type":28,"value":5381}," (analysis, ratings, notifications) asynchronously.",{"type":23,"tag":85,"props":5383,"children":5384},{},[5385,5390],{"type":23,"tag":268,"props":5386,"children":5387},{},[5388],{"type":28,"value":5389},"Prevent thundering herds",{"type":28,"value":5391}," using connection draining and jittered reconnects.",{"type":23,"tag":5393,"props":5394,"children":5395},"style",{},[5396],{"type":28,"value":5397},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":7,"searchDepth":825,"depth":825,"links":5399},[5400,5401,5402,5403,5404,5405,5406,5409,5410,5411,5412,5413,5414,5415,5416,5417,5418,5419,5420,5421,5422,5423,5424,5429,5433,5434,5435,5436,5437,5438,5439,5440,5441],{"id":66,"depth":825,"text":69},{"id":145,"depth":825,"text":148},{"id":252,"depth":825,"text":255},{"id":388,"depth":825,"text":391},{"id":528,"depth":825,"text":531},{"id":561,"depth":825,"text":564},{"id":631,"depth":825,"text":634,"children":5407},[5408],{"id":691,"depth":851,"text":694},{"id":750,"depth":825,"text":753},{"id":1234,"depth":825,"text":1237},{"id":1607,"depth":825,"text":1610},{"id":2112,"depth":825,"text":2115},{"id":2186,"depth":825,"text":2189},{"id":2702,"depth":825,"text":2705},{"id":2754,"depth":825,"text":2757},{"id":2909,"depth":825,"text":2912},{"id":2970,"depth":825,"text":2973},{"id":3163,"depth":825,"text":3166},{"id":3351,"depth":825,"text":3354},{"id":3424,"depth":825,"text":3427},{"id":3649,"depth":825,"text":3652},{"id":3712,"depth":825,"text":3715},{"id":3759,"depth":825,"text":3762},{"id":3791,"depth":825,"text":3794,"children":5425},[5426,5427,5428],{"id":3802,"depth":851,"text":3805},{"id":3876,"depth":851,"text":3879},{"id":2810,"depth":851,"text":4102},{"id":4195,"depth":825,"text":4198,"children":5430},[5431,5432],{"id":4206,"depth":851,"text":4209},{"id":4281,"depth":851,"text":4284},{"id":4421,"depth":825,"text":4424},{"id":4718,"depth":825,"text":4721},{"id":4778,"depth":825,"text":4781},{"id":4958,"depth":825,"text":4961},{"id":5044,"depth":825,"text":5047},{"id":5099,"depth":825,"text":5102},{"id":5156,"depth":825,"text":5159},{"id":5235,"depth":825,"text":5238},{"id":5290,"depth":825,"text":5293,"children":5442},[5443],{"id":5301,"depth":851,"text":5304},"markdown","content:blog:4.designing-a-scalable-online-chess-platform.md","content","blog/4.designing-a-scalable-online-chess-platform.md","blog/4.designing-a-scalable-online-chess-platform","md",[5451],{"_path":5452,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":5453,"description":5454,"date":5455,"image":5456,"categories":5457,"author":17,"readingTime":5459,"body":5460,"_type":5444,"_id":7166,"_source":5446,"_file":7167,"_stem":7168,"_extension":5449},"/blog/designing-a-gmail-scale-email-system","Designing a Gmail-Scale Email System: My High-Level System Design Study","A senior-level walkthrough of how I designed a Gmail-like email platform for 2 billion users, including mailbox modeling, attachments, search, spam detection, and consistency tradeoffs.","2026-04-18","/images/blog/gmail-scale-email-system-architecture.svg",[13,5458,15],"distributed-systems","14 min read",{"type":20,"children":5461,"toc":7127},[5462,5467,5472,5477,5483,5488,5516,5521,5527,5532,5560,5565,5593,5599,5604,5612,5617,5640,5645,5651,5656,5664,5669,5752,5757,5763,5768,5773,5803,5808,5813,5836,5841,5846,5879,5884,5890,5895,6020,6025,6064,6069,6075,6080,6086,6099,6127,6133,6138,6161,6167,6172,6235,6241,6246,6252,6272,6278,6283,6316,6321,6327,6332,6337,6407,6412,6417,6423,6428,6433,6482,6487,6510,6515,6520,6533,6538,6544,6549,6555,6560,6607,6613,6617,6640,6646,6650,6683,6688,6694,6699,6761,6766,6772,6777,6783,6811,6817,6848,6853,6859,6864,6870,6875,6881,6886,6892,6904,6910,6915,6921,6926,6931,6937,6942,6947,7000,7005,7011,7016,7044,7049,7055,7060,7068,7073,7079,7084,7117,7122],{"type":23,"tag":24,"props":5463,"children":5464},{},[5465],{"type":28,"value":5466},"System design interviews get much more interesting when the problem is not just \"store messages\" but \"build a mailbox product that behaves like Gmail at massive scale.\" That was the focus of this study.",{"type":23,"tag":24,"props":5468,"children":5469},{},[5470],{"type":28,"value":5471},"I wanted to design a Gmail-like email system that supports user registration, login with 2FA, profile creation, preferences, contacts and groups, sending emails with attachments, mailbox views like inbox/sent/spam/trash, tagging and labels, search, spam detection, and virus detection.",{"type":23,"tag":24,"props":5473,"children":5474},{},[5475],{"type":28,"value":5476},"The more I worked through the problem, the clearer one thing became: this is not a single-service CRUD application. It is a collection of very different systems stitched together carefully, each with different performance, storage, and consistency needs.",{"type":23,"tag":64,"props":5478,"children":5480},{"id":5479},"the-scope-i-designed-for",[5481],{"type":28,"value":5482},"The Scope I Designed For",{"type":23,"tag":24,"props":5484,"children":5485},{},[5486],{"type":28,"value":5487},"I modeled the system around these assumptions:",{"type":23,"tag":81,"props":5489,"children":5490},{},[5491,5496,5501,5506,5511],{"type":23,"tag":85,"props":5492,"children":5493},{},[5494],{"type":28,"value":5495},"2 billion users",{"type":23,"tag":85,"props":5497,"children":5498},{},[5499],{"type":28,"value":5500},"50 emails per user per day",{"type":23,"tag":85,"props":5502,"children":5503},{},[5504],{"type":28,"value":5505},"5 percent of emails include a 1 MB attachment",{"type":23,"tag":85,"props":5507,"children":5508},{},[5509],{"type":28,"value":5510},"1 percent of users are active at a given time",{"type":23,"tag":85,"props":5512,"children":5513},{},[5514],{"type":28,"value":5515},"10 percent of users opt into two-factor authentication",{"type":23,"tag":24,"props":5517,"children":5518},{},[5519],{"type":28,"value":5520},"That immediately changes the architecture. At this scale, raw email text is not the main problem. Attachments dominate storage. Search needs its own index. Spam and virus processing cannot live fully on the synchronous path. Hot data has to be cached based on active users, not total users.",{"type":23,"tag":64,"props":5522,"children":5524},{"id":5523},"capacity-estimates-that-shaped-the-design",[5525],{"type":28,"value":5526},"Capacity Estimates That Shaped the Design",{"type":23,"tag":24,"props":5528,"children":5529},{},[5530],{"type":28,"value":5531},"Here are the rough numbers I used during the study:",{"type":23,"tag":81,"props":5533,"children":5534},{},[5535,5540,5545,5550,5555],{"type":23,"tag":85,"props":5536,"children":5537},{},[5538],{"type":28,"value":5539},"Email body storage per day: about 20 TB",{"type":23,"tag":85,"props":5541,"children":5542},{},[5543],{"type":28,"value":5544},"Attachment storage per day: about 5 PB",{"type":23,"tag":85,"props":5546,"children":5547},{},[5548],{"type":28,"value":5549},"With replication, total daily storage can quickly move into the 15 PB range before optimization",{"type":23,"tag":85,"props":5551,"children":5552},{},[5553],{"type":28,"value":5554},"Virus scanning is far more compute-heavy than spam classification because attachments are much larger than message bodies",{"type":23,"tag":85,"props":5556,"children":5557},{},[5558],{"type":28,"value":5559},"Contact caching should be based on the hot active set, not the entire 2 billion-user base",{"type":23,"tag":24,"props":5561,"children":5562},{},[5563],{"type":28,"value":5564},"Those estimates led to a few strong conclusions:",{"type":23,"tag":81,"props":5566,"children":5567},{},[5568,5573,5578,5583,5588],{"type":23,"tag":85,"props":5569,"children":5570},{},[5571],{"type":28,"value":5572},"Message metadata and mailbox state belong in a fast distributed data store",{"type":23,"tag":85,"props":5574,"children":5575},{},[5576],{"type":28,"value":5577},"Attachments must live in object storage",{"type":23,"tag":85,"props":5579,"children":5580},{},[5581],{"type":28,"value":5582},"Search needs a dedicated inverted index",{"type":23,"tag":85,"props":5584,"children":5585},{},[5586],{"type":28,"value":5587},"Spam and virus processing need asynchronous pipelines",{"type":23,"tag":85,"props":5589,"children":5590},{},[5591],{"type":28,"value":5592},"Cache sizing must follow access patterns, not total dataset size",{"type":23,"tag":64,"props":5594,"children":5596},{"id":5595},"the-core-design-principle",[5597],{"type":28,"value":5598},"The Core Design Principle",{"type":23,"tag":24,"props":5600,"children":5601},{},[5602],{"type":28,"value":5603},"The most important architectural decision in the whole system is this:",{"type":23,"tag":233,"props":5605,"children":5606},{},[5607],{"type":23,"tag":24,"props":5608,"children":5609},{},[5610],{"type":28,"value":5611},"Keep the synchronous write path small, durable, and authoritative. Everything else should happen asynchronously off events.",{"type":23,"tag":24,"props":5613,"children":5614},{},[5615],{"type":28,"value":5616},"That means the send-email path should do only a few things:",{"type":23,"tag":490,"props":5618,"children":5619},{},[5620,5625,5630,5635],{"type":23,"tag":85,"props":5621,"children":5622},{},[5623],{"type":28,"value":5624},"Validate the request",{"type":23,"tag":85,"props":5626,"children":5627},{},[5628],{"type":28,"value":5629},"Persist the canonical message and per-user mailbox entries durably",{"type":23,"tag":85,"props":5631,"children":5632},{},[5633],{"type":28,"value":5634},"Emit an event for downstream systems",{"type":23,"tag":85,"props":5636,"children":5637},{},[5638],{"type":28,"value":5639},"Return success only after the durable write succeeds",{"type":23,"tag":24,"props":5641,"children":5642},{},[5643],{"type":28,"value":5644},"This prevents the mail send path from being slowed down or broken by search indexing, spam classification, notifications, analytics, or contact ranking.",{"type":23,"tag":64,"props":5646,"children":5648},{"id":5647},"the-high-level-architecture",[5649],{"type":28,"value":5650},"The High-Level Architecture",{"type":23,"tag":24,"props":5652,"children":5653},{},[5654],{"type":28,"value":5655},"Here is the architecture I converged on:",{"type":23,"tag":24,"props":5657,"children":5658},{},[5659],{"type":23,"tag":541,"props":5660,"children":5663},{"alt":5661,"src":5662},"High-level architecture for a Gmail-like email system showing the edge plane, identity plane, mail write path, mailbox state, event bus, search, and attachment processing.","../../images/blog/gmail-scale-email-system-architecture.svg",[],{"type":23,"tag":24,"props":5665,"children":5666},{},[5667],{"type":28,"value":5668},"I split the system into eight logical planes:",{"type":23,"tag":81,"props":5670,"children":5671},{},[5672,5682,5692,5702,5712,5722,5732,5742],{"type":23,"tag":85,"props":5673,"children":5674},{},[5675,5680],{"type":23,"tag":268,"props":5676,"children":5677},{},[5678],{"type":28,"value":5679},"Edge plane",{"type":28,"value":5681},": global load balancer, API gateway, WAF, auth middleware, and rate limiting",{"type":23,"tag":85,"props":5683,"children":5684},{},[5685,5690],{"type":23,"tag":268,"props":5686,"children":5687},{},[5688],{"type":28,"value":5689},"Identity plane",{"type":28,"value":5691},": auth service, MFA service, token/session service, credential store, OTP/session cache",{"type":23,"tag":85,"props":5693,"children":5694},{},[5695,5700],{"type":23,"tag":268,"props":5696,"children":5697},{},[5698],{"type":28,"value":5699},"User metadata plane",{"type":28,"value":5701},": profile service, preference service, contacts service, and contact group service",{"type":23,"tag":85,"props":5703,"children":5704},{},[5705,5710],{"type":23,"tag":268,"props":5706,"children":5707},{},[5708],{"type":28,"value":5709},"Mail write plane",{"type":28,"value":5711},": compose/send API, draft service, recipient resolution, attachment validation",{"type":23,"tag":85,"props":5713,"children":5714},{},[5715,5720],{"type":23,"tag":268,"props":5716,"children":5717},{},[5718],{"type":28,"value":5719},"Mailbox plane",{"type":28,"value":5721},": inbox/sent/spam/trash state, labels, threads, read/unread, archive",{"type":23,"tag":85,"props":5723,"children":5724},{},[5725,5730],{"type":23,"tag":268,"props":5726,"children":5727},{},[5728],{"type":28,"value":5729},"Attachment plane",{"type":28,"value":5731},": upload service, object storage, metadata store, virus scanning, signed downloads",{"type":23,"tag":85,"props":5733,"children":5734},{},[5735,5740],{"type":23,"tag":268,"props":5736,"children":5737},{},[5738],{"type":28,"value":5739},"Async enrichment plane",{"type":28,"value":5741},": event bus, search indexer, spam classifier, category classifier, notifications, analytics",{"type":23,"tag":85,"props":5743,"children":5744},{},[5745,5750],{"type":23,"tag":268,"props":5746,"children":5747},{},[5748],{"type":28,"value":5749},"Query plane",{"type":28,"value":5751},": inbox queries, search API, autocomplete, thread fetch, message fetch, cache",{"type":23,"tag":24,"props":5753,"children":5754},{},[5755],{"type":28,"value":5756},"This decomposition matters because the workload classes are completely different. Auth data is security-sensitive and low-latency. Mailbox metadata is heavily queried and updated. Attachments are large and mostly immutable. Search and spam state are derived systems that can lag slightly without breaking correctness.",{"type":23,"tag":64,"props":5758,"children":5760},{"id":5759},"why-the-mailbox-model-is-the-most-important-data-modeling-decision",[5761],{"type":28,"value":5762},"Why the Mailbox Model Is the Most Important Data Modeling Decision",{"type":23,"tag":24,"props":5764,"children":5765},{},[5766],{"type":28,"value":5767},"The biggest modeling insight in this design is that a message is not the same thing as mailbox state.",{"type":23,"tag":24,"props":5769,"children":5770},{},[5771],{"type":28,"value":5772},"A naive approach says:",{"type":23,"tag":81,"props":5774,"children":5775},{},[5776,5785,5794],{"type":23,"tag":85,"props":5777,"children":5778},{},[5779],{"type":23,"tag":46,"props":5780,"children":5782},{"className":5781},[],[5783],{"type":28,"value":5784},"message -> labels",{"type":23,"tag":85,"props":5786,"children":5787},{},[5788],{"type":23,"tag":46,"props":5789,"children":5791},{"className":5790},[],[5792],{"type":28,"value":5793},"message -> read/unread",{"type":23,"tag":85,"props":5795,"children":5796},{},[5797],{"type":23,"tag":46,"props":5798,"children":5800},{"className":5799},[],[5801],{"type":28,"value":5802},"message -> spam",{"type":23,"tag":24,"props":5804,"children":5805},{},[5806],{"type":28,"value":5807},"That falls apart immediately in a real email product.",{"type":23,"tag":24,"props":5809,"children":5810},{},[5811],{"type":28,"value":5812},"The same message can be:",{"type":23,"tag":81,"props":5814,"children":5815},{},[5816,5821,5826,5831],{"type":23,"tag":85,"props":5817,"children":5818},{},[5819],{"type":28,"value":5820},"unread for one user",{"type":23,"tag":85,"props":5822,"children":5823},{},[5824],{"type":28,"value":5825},"archived by another",{"type":23,"tag":85,"props":5827,"children":5828},{},[5829],{"type":28,"value":5830},"labeled \"Work\" by a third",{"type":23,"tag":85,"props":5832,"children":5833},{},[5834],{"type":28,"value":5835},"moved to spam by a fourth",{"type":23,"tag":24,"props":5837,"children":5838},{},[5839],{"type":28,"value":5840},"So the canonical message must be separated from the per-user mailbox view.",{"type":23,"tag":24,"props":5842,"children":5843},{},[5844],{"type":28,"value":5845},"That is why I introduced three distinct concepts:",{"type":23,"tag":81,"props":5847,"children":5848},{},[5849,5859,5869],{"type":23,"tag":85,"props":5850,"children":5851},{},[5852,5857],{"type":23,"tag":268,"props":5853,"children":5854},{},[5855],{"type":28,"value":5856},"Message",{"type":28,"value":5858},": immutable shared content like subject, body, sender, headers, and thread ID",{"type":23,"tag":85,"props":5860,"children":5861},{},[5862,5867],{"type":23,"tag":268,"props":5863,"children":5864},{},[5865],{"type":28,"value":5866},"MessageRecipient",{"type":28,"value":5868},": the recipient mapping for TO, CC, and BCC",{"type":23,"tag":85,"props":5870,"children":5871},{},[5872,5877],{"type":23,"tag":268,"props":5873,"children":5874},{},[5875],{"type":28,"value":5876},"MailboxEntry",{"type":28,"value":5878},": the per-user mailbox projection containing inbox state, read state, category, star, archive, spam, and timestamps",{"type":23,"tag":24,"props":5880,"children":5881},{},[5882],{"type":28,"value":5883},"This is the single most important correctness point in a Gmail-like design.",{"type":23,"tag":64,"props":5885,"children":5887},{"id":5886},"core-data-model",[5888],{"type":28,"value":5889},"Core Data Model",{"type":23,"tag":24,"props":5891,"children":5892},{},[5893],{"type":28,"value":5894},"The main entities in my design are:",{"type":23,"tag":81,"props":5896,"children":5897},{},[5898,5906,5915,5924,5933,5942,5951,5959,5967,5976,5984,5993,6002,6011],{"type":23,"tag":85,"props":5899,"children":5900},{},[5901],{"type":23,"tag":46,"props":5902,"children":5904},{"className":5903},[],[5905],{"type":28,"value":3805},{"type":23,"tag":85,"props":5907,"children":5908},{},[5909],{"type":23,"tag":46,"props":5910,"children":5912},{"className":5911},[],[5913],{"type":28,"value":5914},"UserProfile",{"type":23,"tag":85,"props":5916,"children":5917},{},[5918],{"type":23,"tag":46,"props":5919,"children":5921},{"className":5920},[],[5922],{"type":28,"value":5923},"UserPreference",{"type":23,"tag":85,"props":5925,"children":5926},{},[5927],{"type":23,"tag":46,"props":5928,"children":5930},{"className":5929},[],[5931],{"type":28,"value":5932},"Contact",{"type":23,"tag":85,"props":5934,"children":5935},{},[5936],{"type":23,"tag":46,"props":5937,"children":5939},{"className":5938},[],[5940],{"type":28,"value":5941},"ContactGroup",{"type":23,"tag":85,"props":5943,"children":5944},{},[5945],{"type":23,"tag":46,"props":5946,"children":5948},{"className":5947},[],[5949],{"type":28,"value":5950},"ContactGroupMember",{"type":23,"tag":85,"props":5952,"children":5953},{},[5954],{"type":23,"tag":46,"props":5955,"children":5957},{"className":5956},[],[5958],{"type":28,"value":5856},{"type":23,"tag":85,"props":5960,"children":5961},{},[5962],{"type":23,"tag":46,"props":5963,"children":5965},{"className":5964},[],[5966],{"type":28,"value":5866},{"type":23,"tag":85,"props":5968,"children":5969},{},[5970],{"type":23,"tag":46,"props":5971,"children":5973},{"className":5972},[],[5974],{"type":28,"value":5975},"Attachment",{"type":23,"tag":85,"props":5977,"children":5978},{},[5979],{"type":23,"tag":46,"props":5980,"children":5982},{"className":5981},[],[5983],{"type":28,"value":5876},{"type":23,"tag":85,"props":5985,"children":5986},{},[5987],{"type":23,"tag":46,"props":5988,"children":5990},{"className":5989},[],[5991],{"type":28,"value":5992},"Label",{"type":23,"tag":85,"props":5994,"children":5995},{},[5996],{"type":23,"tag":46,"props":5997,"children":5999},{"className":5998},[],[6000],{"type":28,"value":6001},"LabelAssignment",{"type":23,"tag":85,"props":6003,"children":6004},{},[6005],{"type":23,"tag":46,"props":6006,"children":6008},{"className":6007},[],[6009],{"type":28,"value":6010},"Draft",{"type":23,"tag":85,"props":6012,"children":6013},{},[6014],{"type":23,"tag":46,"props":6015,"children":6017},{"className":6016},[],[6018],{"type":28,"value":6019},"Thread",{"type":23,"tag":24,"props":6021,"children":6022},{},[6023],{"type":28,"value":6024},"Two details matter a lot here:",{"type":23,"tag":81,"props":6026,"children":6027},{},[6028,6046],{"type":23,"tag":85,"props":6029,"children":6030},{},[6031,6036,6038,6044],{"type":23,"tag":46,"props":6032,"children":6034},{"className":6033},[],[6035],{"type":28,"value":5876},{"type":28,"value":6037}," is partitioned by ",{"type":23,"tag":46,"props":6039,"children":6041},{"className":6040},[],[6042],{"type":28,"value":6043},"user_id",{"type":28,"value":6045}," because inbox reads and mailbox actions are user-centric",{"type":23,"tag":85,"props":6047,"children":6048},{},[6049,6054,6056,6062],{"type":23,"tag":46,"props":6050,"children":6052},{"className":6051},[],[6053],{"type":28,"value":5856},{"type":28,"value":6055}," can be partitioned by ",{"type":23,"tag":46,"props":6057,"children":6059},{"className":6058},[],[6060],{"type":28,"value":6061},"message_id",{"type":28,"value":6063}," because it is immutable shared content",{"type":23,"tag":24,"props":6065,"children":6066},{},[6067],{"type":28,"value":6068},"This split makes reads and writes much easier to scale.",{"type":23,"tag":64,"props":6070,"children":6072},{"id":6071},"the-end-to-end-send-email-flow",[6073],{"type":28,"value":6074},"The End-to-End Send Email Flow",{"type":23,"tag":24,"props":6076,"children":6077},{},[6078],{"type":28,"value":6079},"The send flow is the heart of the system.",{"type":23,"tag":689,"props":6081,"children":6083},{"id":6082},"_1-compose-submission",[6084],{"type":28,"value":6085},"1. Compose submission",{"type":23,"tag":24,"props":6087,"children":6088},{},[6089,6091,6097],{"type":28,"value":6090},"The client calls ",{"type":23,"tag":46,"props":6092,"children":6094},{"className":6093},[],[6095],{"type":28,"value":6096},"POST /emails/send",{"type":28,"value":6098}," with:",{"type":23,"tag":81,"props":6100,"children":6101},{},[6102,6107,6112,6117,6122],{"type":23,"tag":85,"props":6103,"children":6104},{},[6105],{"type":28,"value":6106},"recipients",{"type":23,"tag":85,"props":6108,"children":6109},{},[6110],{"type":28,"value":6111},"subject",{"type":23,"tag":85,"props":6113,"children":6114},{},[6115],{"type":28,"value":6116},"body",{"type":23,"tag":85,"props":6118,"children":6119},{},[6120],{"type":28,"value":6121},"attachment IDs",{"type":23,"tag":85,"props":6123,"children":6124},{},[6125],{"type":28,"value":6126},"an idempotency key",{"type":23,"tag":689,"props":6128,"children":6130},{"id":6129},"_2-validation",[6131],{"type":28,"value":6132},"2. Validation",{"type":23,"tag":24,"props":6134,"children":6135},{},[6136],{"type":28,"value":6137},"The Mail Write Service:",{"type":23,"tag":81,"props":6139,"children":6140},{},[6141,6146,6151,6156],{"type":23,"tag":85,"props":6142,"children":6143},{},[6144],{"type":28,"value":6145},"authenticates the sender",{"type":23,"tag":85,"props":6147,"children":6148},{},[6149],{"type":28,"value":6150},"validates recipients and groups",{"type":23,"tag":85,"props":6152,"children":6153},{},[6154],{"type":28,"value":6155},"checks attachment scan status",{"type":23,"tag":85,"props":6157,"children":6158},{},[6159],{"type":28,"value":6160},"applies quota and rate-limit checks",{"type":23,"tag":689,"props":6162,"children":6164},{"id":6163},"_3-canonical-durable-write",[6165],{"type":28,"value":6166},"3. Canonical durable write",{"type":23,"tag":24,"props":6168,"children":6169},{},[6170],{"type":28,"value":6171},"The system persists:",{"type":23,"tag":81,"props":6173,"children":6174},{},[6175,6185,6197,6215],{"type":23,"tag":85,"props":6176,"children":6177},{},[6178,6180],{"type":28,"value":6179},"the immutable ",{"type":23,"tag":46,"props":6181,"children":6183},{"className":6182},[],[6184],{"type":28,"value":5856},{"type":23,"tag":85,"props":6186,"children":6187},{},[6188,6190,6195],{"type":28,"value":6189},"the ",{"type":23,"tag":46,"props":6191,"children":6193},{"className":6192},[],[6194],{"type":28,"value":5866},{"type":28,"value":6196}," rows",{"type":23,"tag":85,"props":6198,"children":6199},{},[6200,6202,6207,6209],{"type":28,"value":6201},"the sender's ",{"type":23,"tag":46,"props":6203,"children":6205},{"className":6204},[],[6206],{"type":28,"value":5876},{"type":28,"value":6208}," in ",{"type":23,"tag":46,"props":6210,"children":6212},{"className":6211},[],[6213],{"type":28,"value":6214},"SENT",{"type":23,"tag":85,"props":6216,"children":6217},{},[6218,6220,6225,6227,6233],{"type":28,"value":6219},"recipient ",{"type":23,"tag":46,"props":6221,"children":6223},{"className":6222},[],[6224],{"type":28,"value":5876},{"type":28,"value":6226}," rows in ",{"type":23,"tag":46,"props":6228,"children":6230},{"className":6229},[],[6231],{"type":28,"value":6232},"INBOX",{"type":28,"value":6234}," or an initial classified state",{"type":23,"tag":689,"props":6236,"children":6238},{"id":6237},"_4-success-acknowledgement",[6239],{"type":28,"value":6240},"4. Success acknowledgement",{"type":23,"tag":24,"props":6242,"children":6243},{},[6244],{"type":28,"value":6245},"The API returns success only after the durable write succeeds. This protects against acknowledging a send and then losing the message.",{"type":23,"tag":689,"props":6247,"children":6249},{"id":6248},"_5-event-publication",[6250],{"type":28,"value":6251},"5. Event publication",{"type":23,"tag":24,"props":6253,"children":6254},{},[6255,6257,6263,6264,6270],{"type":28,"value":6256},"The system emits a ",{"type":23,"tag":46,"props":6258,"children":6260},{"className":6259},[],[6261],{"type":28,"value":6262},"MessageCreated",{"type":28,"value":5026},{"type":23,"tag":46,"props":6265,"children":6267},{"className":6266},[],[6268],{"type":28,"value":6269},"MailboxEntryCreated",{"type":28,"value":6271}," event to a durable bus.",{"type":23,"tag":689,"props":6273,"children":6275},{"id":6274},"_6-async-enrichment",[6276],{"type":28,"value":6277},"6. Async enrichment",{"type":23,"tag":24,"props":6279,"children":6280},{},[6281],{"type":28,"value":6282},"Downstream consumers then handle:",{"type":23,"tag":81,"props":6284,"children":6285},{},[6286,6291,6296,6301,6306,6311],{"type":23,"tag":85,"props":6287,"children":6288},{},[6289],{"type":28,"value":6290},"search indexing",{"type":23,"tag":85,"props":6292,"children":6293},{},[6294],{"type":28,"value":6295},"spam classification",{"type":23,"tag":85,"props":6297,"children":6298},{},[6299],{"type":28,"value":6300},"promotions/social categorization",{"type":23,"tag":85,"props":6302,"children":6303},{},[6304],{"type":28,"value":6305},"notification fanout",{"type":23,"tag":85,"props":6307,"children":6308},{},[6309],{"type":28,"value":6310},"contact interaction ranking",{"type":23,"tag":85,"props":6312,"children":6313},{},[6314],{"type":28,"value":6315},"analytics",{"type":23,"tag":24,"props":6317,"children":6318},{},[6319],{"type":28,"value":6320},"This is the right tradeoff because it keeps the critical path small while still enabling rich product behavior.",{"type":23,"tag":64,"props":6322,"children":6324},{"id":6323},"attachments-need-a-separate-architecture",[6325],{"type":28,"value":6326},"Attachments Need a Separate Architecture",{"type":23,"tag":24,"props":6328,"children":6329},{},[6330],{"type":28,"value":6331},"Attachments dominate storage and scanning cost, so they cannot be treated like regular email metadata.",{"type":23,"tag":24,"props":6333,"children":6334},{},[6335],{"type":28,"value":6336},"The upload flow I designed looks like this:",{"type":23,"tag":490,"props":6338,"children":6339},{},[6340,6345,6350,6361,6374],{"type":23,"tag":85,"props":6341,"children":6342},{},[6343],{"type":28,"value":6344},"Client requests a signed upload URL",{"type":23,"tag":85,"props":6346,"children":6347},{},[6348],{"type":28,"value":6349},"Client uploads the file directly to object storage",{"type":23,"tag":85,"props":6351,"children":6352},{},[6353,6355],{"type":28,"value":6354},"Upload service writes attachment metadata with status ",{"type":23,"tag":46,"props":6356,"children":6358},{"className":6357},[],[6359],{"type":28,"value":6360},"PENDING_SCAN",{"type":23,"tag":85,"props":6362,"children":6363},{},[6364,6366,6372],{"type":28,"value":6365},"An ",{"type":23,"tag":46,"props":6367,"children":6369},{"className":6368},[],[6370],{"type":28,"value":6371},"AttachmentUploaded",{"type":28,"value":6373}," event triggers the virus scanning pipeline",{"type":23,"tag":85,"props":6375,"children":6376},{},[6377,6379,6385,6386,6392,6393,6399,6401],{"type":28,"value":6378},"The attachment becomes ",{"type":23,"tag":46,"props":6380,"children":6382},{"className":6381},[],[6383],{"type":28,"value":6384},"SAFE",{"type":28,"value":1275},{"type":23,"tag":46,"props":6387,"children":6389},{"className":6388},[],[6390],{"type":28,"value":6391},"INFECTED",{"type":28,"value":1275},{"type":23,"tag":46,"props":6394,"children":6396},{"className":6395},[],[6397],{"type":28,"value":6398},"QUARANTINED",{"type":28,"value":6400},", or ",{"type":23,"tag":46,"props":6402,"children":6404},{"className":6403},[],[6405],{"type":28,"value":6406},"FAILED",{"type":23,"tag":24,"props":6408,"children":6409},{},[6410],{"type":28,"value":6411},"The important design choice here is to avoid proxying large uploads through the app servers whenever possible. Direct-to-object-storage uploads keep the application tier lighter and cheaper.",{"type":23,"tag":24,"props":6413,"children":6414},{},[6415],{"type":28,"value":6416},"The second important decision is quarantine: an unscanned or unsafe file should not be downloadable just because the upload succeeded.",{"type":23,"tag":64,"props":6418,"children":6420},{"id":6419},"search-spam-and-virus-processing-belong-off-the-critical-path",[6421],{"type":28,"value":6422},"Search, Spam, and Virus Processing Belong Off the Critical Path",{"type":23,"tag":24,"props":6424,"children":6425},{},[6426],{"type":28,"value":6427},"Search is not a source of truth. It is a retrieval accelerator.",{"type":23,"tag":24,"props":6429,"children":6430},{},[6431],{"type":28,"value":6432},"My search design uses a dedicated inverted index that stores:",{"type":23,"tag":81,"props":6434,"children":6435},{},[6436,6444,6452,6457,6462,6467,6472,6477],{"type":23,"tag":85,"props":6437,"children":6438},{},[6439],{"type":23,"tag":46,"props":6440,"children":6442},{"className":6441},[],[6443],{"type":28,"value":6061},{"type":23,"tag":85,"props":6445,"children":6446},{},[6447],{"type":23,"tag":46,"props":6448,"children":6450},{"className":6449},[],[6451],{"type":28,"value":6043},{"type":23,"tag":85,"props":6453,"children":6454},{},[6455],{"type":28,"value":6456},"sender and recipient fields",{"type":23,"tag":85,"props":6458,"children":6459},{},[6460],{"type":28,"value":6461},"subject tokens",{"type":23,"tag":85,"props":6463,"children":6464},{},[6465],{"type":28,"value":6466},"body tokens",{"type":23,"tag":85,"props":6468,"children":6469},{},[6470],{"type":28,"value":6471},"attachment names",{"type":23,"tag":85,"props":6473,"children":6474},{},[6475],{"type":28,"value":6476},"labels and categories",{"type":23,"tag":85,"props":6478,"children":6479},{},[6480],{"type":28,"value":6481},"timestamps",{"type":23,"tag":24,"props":6483,"children":6484},{},[6485],{"type":28,"value":6486},"The query flow is:",{"type":23,"tag":490,"props":6488,"children":6489},{},[6490,6495,6500,6505],{"type":23,"tag":85,"props":6491,"children":6492},{},[6493],{"type":28,"value":6494},"User hits the search API",{"type":23,"tag":85,"props":6496,"children":6497},{},[6498],{"type":28,"value":6499},"Search service returns candidate IDs from the index",{"type":23,"tag":85,"props":6501,"children":6502},{},[6503],{"type":28,"value":6504},"Mailbox service fetches canonical metadata from the source-of-truth store",{"type":23,"tag":85,"props":6506,"children":6507},{},[6508],{"type":28,"value":6509},"Response is assembled and returned",{"type":23,"tag":24,"props":6511,"children":6512},{},[6513],{"type":28,"value":6514},"This means search can be eventually consistent. A just-sent email may take a short time to appear in search, which is acceptable.",{"type":23,"tag":24,"props":6516,"children":6517},{},[6518],{"type":28,"value":6519},"Spam handling follows a similar hybrid model:",{"type":23,"tag":81,"props":6521,"children":6522},{},[6523,6528],{"type":23,"tag":85,"props":6524,"children":6525},{},[6526],{"type":28,"value":6527},"lightweight checks inline for sender reputation, blocklists, and policy validation",{"type":23,"tag":85,"props":6529,"children":6530},{},[6531],{"type":28,"value":6532},"heavier content and behavior analysis asynchronously",{"type":23,"tag":24,"props":6534,"children":6535},{},[6536],{"type":28,"value":6537},"Virus detection is even more expensive, so it is even more clearly an asynchronous pipeline with quarantine enforcement.",{"type":23,"tag":64,"props":6539,"children":6541},{"id":6540},"storage-strategy",[6542],{"type":28,"value":6543},"Storage Strategy",{"type":23,"tag":24,"props":6545,"children":6546},{},[6547],{"type":28,"value":6548},"I used three major storage layers:",{"type":23,"tag":689,"props":6550,"children":6552},{"id":6551},"_1-distributed-metadata-store",[6553],{"type":28,"value":6554},"1. Distributed metadata store",{"type":23,"tag":24,"props":6556,"children":6557},{},[6558],{"type":28,"value":6559},"For:",{"type":23,"tag":81,"props":6561,"children":6562},{},[6563,6568,6573,6578,6583,6588,6592,6597,6602],{"type":23,"tag":85,"props":6564,"children":6565},{},[6566],{"type":28,"value":6567},"users",{"type":23,"tag":85,"props":6569,"children":6570},{},[6571],{"type":28,"value":6572},"profiles",{"type":23,"tag":85,"props":6574,"children":6575},{},[6576],{"type":28,"value":6577},"preferences",{"type":23,"tag":85,"props":6579,"children":6580},{},[6581],{"type":28,"value":6582},"contacts",{"type":23,"tag":85,"props":6584,"children":6585},{},[6586],{"type":28,"value":6587},"messages metadata",{"type":23,"tag":85,"props":6589,"children":6590},{},[6591],{"type":28,"value":6106},{"type":23,"tag":85,"props":6593,"children":6594},{},[6595],{"type":28,"value":6596},"mailbox entries",{"type":23,"tag":85,"props":6598,"children":6599},{},[6600],{"type":28,"value":6601},"labels",{"type":23,"tag":85,"props":6603,"children":6604},{},[6605],{"type":28,"value":6606},"drafts",{"type":23,"tag":689,"props":6608,"children":6610},{"id":6609},"_2-object-storage",[6611],{"type":28,"value":6612},"2. Object storage",{"type":23,"tag":24,"props":6614,"children":6615},{},[6616],{"type":28,"value":6559},{"type":23,"tag":81,"props":6618,"children":6619},{},[6620,6625,6630,6635],{"type":23,"tag":85,"props":6621,"children":6622},{},[6623],{"type":28,"value":6624},"attachments",{"type":23,"tag":85,"props":6626,"children":6627},{},[6628],{"type":28,"value":6629},"large message bodies if needed",{"type":23,"tag":85,"props":6631,"children":6632},{},[6633],{"type":28,"value":6634},"avatars",{"type":23,"tag":85,"props":6636,"children":6637},{},[6638],{"type":28,"value":6639},"large draft bodies",{"type":23,"tag":689,"props":6641,"children":6643},{"id":6642},"_3-cache-and-search-systems",[6644],{"type":28,"value":6645},"3. Cache and search systems",{"type":23,"tag":24,"props":6647,"children":6648},{},[6649],{"type":28,"value":6559},{"type":23,"tag":81,"props":6651,"children":6652},{},[6653,6658,6663,6668,6673,6678],{"type":23,"tag":85,"props":6654,"children":6655},{},[6656],{"type":28,"value":6657},"sessions",{"type":23,"tag":85,"props":6659,"children":6660},{},[6661],{"type":28,"value":6662},"OTPs",{"type":23,"tag":85,"props":6664,"children":6665},{},[6666],{"type":28,"value":6667},"rate limits",{"type":23,"tag":85,"props":6669,"children":6670},{},[6671],{"type":28,"value":6672},"hot mailbox summaries",{"type":23,"tag":85,"props":6674,"children":6675},{},[6676],{"type":28,"value":6677},"autocomplete hotsets",{"type":23,"tag":85,"props":6679,"children":6680},{},[6681],{"type":28,"value":6682},"search indexes",{"type":23,"tag":24,"props":6684,"children":6685},{},[6686],{"type":28,"value":6687},"The reason this split works is simple: structured OLTP workloads and large immutable blobs have completely different economics and performance characteristics.",{"type":23,"tag":64,"props":6689,"children":6691},{"id":6690},"partitioning-strategy",[6692],{"type":28,"value":6693},"Partitioning Strategy",{"type":23,"tag":24,"props":6695,"children":6696},{},[6697],{"type":28,"value":6698},"Partitioning is easiest when it follows the access pattern.",{"type":23,"tag":81,"props":6700,"children":6701},{},[6702,6717,6734,6751],{"type":23,"tag":85,"props":6703,"children":6704},{},[6705,6715],{"type":23,"tag":268,"props":6706,"children":6707},{},[6708,6710],{"type":28,"value":6709},"Mailbox tables by ",{"type":23,"tag":46,"props":6711,"children":6713},{"className":6712},[],[6714],{"type":28,"value":6043},{"type":28,"value":6716}," because inbox loads, archive, read/unread, labels, and spam actions are all user-centric",{"type":23,"tag":85,"props":6718,"children":6719},{},[6720,6732],{"type":23,"tag":268,"props":6721,"children":6722},{},[6723,6725,6730],{"type":28,"value":6724},"Message table by ",{"type":23,"tag":46,"props":6726,"children":6728},{"className":6727},[],[6729],{"type":28,"value":6061},{"type":28,"value":6731}," or time-based shard",{"type":28,"value":6733}," because messages are immutable shared objects",{"type":23,"tag":85,"props":6735,"children":6736},{},[6737,6749],{"type":23,"tag":268,"props":6738,"children":6739},{},[6740,6742,6747],{"type":28,"value":6741},"Search by ",{"type":23,"tag":46,"props":6743,"children":6745},{"className":6744},[],[6746],{"type":28,"value":6043},{"type":28,"value":6748}," ownership range",{"type":28,"value":6750}," because access control and query scoping become easier",{"type":23,"tag":85,"props":6752,"children":6753},{},[6754,6759],{"type":23,"tag":268,"props":6755,"children":6756},{},[6757],{"type":28,"value":6758},"Attachments by object key",{"type":28,"value":6760}," because object storage already scales this naturally",{"type":23,"tag":24,"props":6762,"children":6763},{},[6764],{"type":28,"value":6765},"This is one of those design choices that looks small on paper but determines whether the system remains operational at scale.",{"type":23,"tag":64,"props":6767,"children":6769},{"id":6768},"consistency-tradeoffs",[6770],{"type":28,"value":6771},"Consistency Tradeoffs",{"type":23,"tag":24,"props":6773,"children":6774},{},[6775],{"type":28,"value":6776},"A strong system design answer always draws the line between what must be strongly consistent and what can be eventually consistent.",{"type":23,"tag":689,"props":6778,"children":6780},{"id":6779},"strong-consistency-required-for",[6781],{"type":28,"value":6782},"Strong consistency required for",{"type":23,"tag":81,"props":6784,"children":6785},{},[6786,6791,6796,6801,6806],{"type":23,"tag":85,"props":6787,"children":6788},{},[6789],{"type":28,"value":6790},"registration and account verification",{"type":23,"tag":85,"props":6792,"children":6793},{},[6794],{"type":28,"value":6795},"password and session correctness",{"type":23,"tag":85,"props":6797,"children":6798},{},[6799],{"type":28,"value":6800},"durable message write before send acknowledgement",{"type":23,"tag":85,"props":6802,"children":6803},{},[6804],{"type":28,"value":6805},"mailbox entry creation tied to send success",{"type":23,"tag":85,"props":6807,"children":6808},{},[6809],{"type":28,"value":6810},"label updates on source-of-truth mailbox records",{"type":23,"tag":689,"props":6812,"children":6814},{"id":6813},"eventual-consistency-acceptable-for",[6815],{"type":28,"value":6816},"Eventual consistency acceptable for",{"type":23,"tag":81,"props":6818,"children":6819},{},[6820,6824,6829,6834,6839,6844],{"type":23,"tag":85,"props":6821,"children":6822},{},[6823],{"type":28,"value":6290},{"type":23,"tag":85,"props":6825,"children":6826},{},[6827],{"type":28,"value":6828},"spam and category updates",{"type":23,"tag":85,"props":6830,"children":6831},{},[6832],{"type":28,"value":6833},"autocomplete freshness",{"type":23,"tag":85,"props":6835,"children":6836},{},[6837],{"type":28,"value":6838},"contact ranking",{"type":23,"tag":85,"props":6840,"children":6841},{},[6842],{"type":28,"value":6843},"notifications",{"type":23,"tag":85,"props":6845,"children":6846},{},[6847],{"type":28,"value":6315},{"type":23,"tag":24,"props":6849,"children":6850},{},[6851],{"type":28,"value":6852},"That boundary is what keeps the system reliable without making every subsystem part of the critical path.",{"type":23,"tag":64,"props":6854,"children":6856},{"id":6855},"failure-modes-i-explicitly-designed-around",[6857],{"type":28,"value":6858},"Failure Modes I Explicitly Designed Around",{"type":23,"tag":24,"props":6860,"children":6861},{},[6862],{"type":28,"value":6863},"The architecture is only credible if it handles partial failure well.",{"type":23,"tag":689,"props":6865,"children":6867},{"id":6866},"message-written-but-event-never-published",[6868],{"type":28,"value":6869},"Message written but event never published",{"type":23,"tag":24,"props":6871,"children":6872},{},[6873],{"type":28,"value":6874},"Fix: use the transactional outbox pattern so the message write and outbox record happen in the same transaction.",{"type":23,"tag":689,"props":6876,"children":6878},{"id":6877},"event-delivered-more-than-once",[6879],{"type":28,"value":6880},"Event delivered more than once",{"type":23,"tag":24,"props":6882,"children":6883},{},[6884],{"type":28,"value":6885},"Fix: make all consumers idempotent using event IDs, upserts, and dedupe checkpoints.",{"type":23,"tag":689,"props":6887,"children":6889},{"id":6888},"attachment-uploaded-but-never-scanned",[6890],{"type":28,"value":6891},"Attachment uploaded but never scanned",{"type":23,"tag":24,"props":6893,"children":6894},{},[6895,6897,6902],{"type":28,"value":6896},"Fix: retries, timeout handling, dead-letter queues, and reconciliation jobs for stuck ",{"type":23,"tag":46,"props":6898,"children":6900},{"className":6899},[],[6901],{"type":28,"value":6360},{"type":28,"value":6903}," files.",{"type":23,"tag":689,"props":6905,"children":6907},{"id":6906},"search-indexing-lag-grows-too-much",[6908],{"type":28,"value":6909},"Search indexing lag grows too much",{"type":23,"tag":24,"props":6911,"children":6912},{},[6913],{"type":28,"value":6914},"Fix: monitor queue lag, autoscale indexers, and optionally fall back to recent-message DB reads for fresh content.",{"type":23,"tag":689,"props":6916,"children":6918},{"id":6917},"spam-classifier-goes-down",[6919],{"type":28,"value":6920},"Spam classifier goes down",{"type":23,"tag":24,"props":6922,"children":6923},{},[6924],{"type":28,"value":6925},"Fix: keep lightweight inline defenses active and reclassify asynchronously when the classifier recovers.",{"type":23,"tag":24,"props":6927,"children":6928},{},[6929],{"type":28,"value":6930},"These are the kinds of operational details that move a design from \"diagram-level\" to \"senior-level.\"",{"type":23,"tag":64,"props":6932,"children":6934},{"id":6933},"security-architecture",[6935],{"type":28,"value":6936},"Security Architecture",{"type":23,"tag":24,"props":6938,"children":6939},{},[6940],{"type":28,"value":6941},"Because this is email, security is not a side note. It is a first-class subsystem.",{"type":23,"tag":24,"props":6943,"children":6944},{},[6945],{"type":28,"value":6946},"I included:",{"type":23,"tag":81,"props":6948,"children":6949},{},[6950,6955,6960,6965,6970,6975,6980,6985,6990,6995],{"type":23,"tag":85,"props":6951,"children":6952},{},[6953],{"type":28,"value":6954},"password hashing with Argon2 or bcrypt",{"type":23,"tag":85,"props":6956,"children":6957},{},[6958],{"type":28,"value":6959},"MFA support",{"type":23,"tag":85,"props":6961,"children":6962},{},[6963],{"type":28,"value":6964},"refresh token revocation",{"type":23,"tag":85,"props":6966,"children":6967},{},[6968],{"type":28,"value":6969},"brute-force and resend rate limiting",{"type":23,"tag":85,"props":6971,"children":6972},{},[6973],{"type":28,"value":6974},"suspicious login detection",{"type":23,"tag":85,"props":6976,"children":6977},{},[6978],{"type":28,"value":6979},"TLS in transit",{"type":23,"tag":85,"props":6981,"children":6982},{},[6983],{"type":28,"value":6984},"encryption at rest",{"type":23,"tag":85,"props":6986,"children":6987},{},[6988],{"type":28,"value":6989},"least-privilege service authentication",{"type":23,"tag":85,"props":6991,"children":6992},{},[6993],{"type":28,"value":6994},"send-rate abuse controls",{"type":23,"tag":85,"props":6996,"children":6997},{},[6998],{"type":28,"value":6999},"malware and phishing defenses",{"type":23,"tag":24,"props":7001,"children":7002},{},[7003],{"type":28,"value":7004},"For a Gmail-like product, trust and abuse prevention are inseparable from the core architecture.",{"type":23,"tag":64,"props":7006,"children":7008},{"id":7007},"the-optional-internet-email-extension",[7009],{"type":28,"value":7010},"The Optional Internet Email Extension",{"type":23,"tag":24,"props":7012,"children":7013},{},[7014],{"type":28,"value":7015},"If the interviewer means \"Gmail\" not just as a mailbox product but as a full internet email provider, I would extend the design with:",{"type":23,"tag":81,"props":7017,"children":7018},{},[7019,7024,7029,7034,7039],{"type":23,"tag":85,"props":7020,"children":7021},{},[7022],{"type":28,"value":7023},"SMTP ingress and outbound relay",{"type":23,"tag":85,"props":7025,"children":7026},{},[7027],{"type":28,"value":7028},"MX records",{"type":23,"tag":85,"props":7030,"children":7031},{},[7032],{"type":28,"value":7033},"bounce processing",{"type":23,"tag":85,"props":7035,"children":7036},{},[7037],{"type":28,"value":7038},"SPF, DKIM, and DMARC validation/signing",{"type":23,"tag":85,"props":7040,"children":7041},{},[7042],{"type":28,"value":7043},"retry queues for outbound delivery",{"type":23,"tag":24,"props":7045,"children":7046},{},[7047],{"type":28,"value":7048},"I think this is an important clarification in interviews because \"email app\" and \"global email provider\" are related but meaningfully different scopes.",{"type":23,"tag":64,"props":7050,"children":7052},{"id":7051},"what-i-would-say-in-the-interview",[7053],{"type":28,"value":7054},"What I Would Say In the Interview",{"type":23,"tag":24,"props":7056,"children":7057},{},[7058],{"type":28,"value":7059},"If I had to summarize the design in a short, interview-ready answer, I would say:",{"type":23,"tag":233,"props":7061,"children":7062},{},[7063],{"type":23,"tag":24,"props":7064,"children":7065},{},[7066],{"type":28,"value":7067},"I would design the system around a small, durable mail write path and a set of asynchronous enrichment pipelines. Identity is handled by a dedicated auth subsystem with Redis-backed OTP and session state. Mail stores immutable message content separately from per-user mailbox entries because labels, read state, spam state, and archive state are user-specific. Attachments go to object storage and are scanned asynchronously with quarantine until safe. After a message is durably written, the system emits events that drive search indexing, spam and category classification, notifications, contact-ranking updates, and analytics. The source-of-truth mailbox store is strongly consistent, while search and classification systems are eventually consistent.",{"type":23,"tag":24,"props":7069,"children":7070},{},[7071],{"type":28,"value":7072},"That captures the core idea while still showing that the design is grounded in scale, correctness, and operational realism.",{"type":23,"tag":64,"props":7074,"children":7076},{"id":7075},"final-takeaways",[7077],{"type":28,"value":7078},"Final Takeaways",{"type":23,"tag":24,"props":7080,"children":7081},{},[7082],{"type":28,"value":7083},"This study pushed me toward three big lessons:",{"type":23,"tag":490,"props":7085,"children":7086},{},[7087,7097,7107],{"type":23,"tag":85,"props":7088,"children":7089},{},[7090,7095],{"type":23,"tag":268,"props":7091,"children":7092},{},[7093],{"type":28,"value":7094},"The mailbox model matters more than most people expect.",{"type":28,"value":7096}," Shared messages and per-user mailbox state must be separated.",{"type":23,"tag":85,"props":7098,"children":7099},{},[7100,7105],{"type":23,"tag":268,"props":7101,"children":7102},{},[7103],{"type":28,"value":7104},"Attachments change everything.",{"type":28,"value":7106}," They dominate both storage and scanning cost.",{"type":23,"tag":85,"props":7108,"children":7109},{},[7110,7115],{"type":23,"tag":268,"props":7111,"children":7112},{},[7113],{"type":28,"value":7114},"Asynchronous design is not optional at this scale.",{"type":28,"value":7116}," Search, spam, virus scanning, notifications, and analytics all need to scale independently of the send path.",{"type":23,"tag":24,"props":7118,"children":7119},{},[7120],{"type":28,"value":7121},"What I like most about this problem is that it looks familiar on the surface, but underneath it forces you to think clearly about data modeling, consistency, queues, storage economics, and failure handling all at once.",{"type":23,"tag":24,"props":7123,"children":7124},{},[7125],{"type":28,"value":7126},"If you are preparing for system design interviews, this is one of the best problems to study because it tests both breadth and architectural judgment.",{"title":7,"searchDepth":825,"depth":825,"links":7128},[7129,7130,7131,7132,7133,7134,7135,7143,7144,7145,7150,7151,7155,7162,7163,7164,7165],{"id":5479,"depth":825,"text":5482},{"id":5523,"depth":825,"text":5526},{"id":5595,"depth":825,"text":5598},{"id":5647,"depth":825,"text":5650},{"id":5759,"depth":825,"text":5762},{"id":5886,"depth":825,"text":5889},{"id":6071,"depth":825,"text":6074,"children":7136},[7137,7138,7139,7140,7141,7142],{"id":6082,"depth":851,"text":6085},{"id":6129,"depth":851,"text":6132},{"id":6163,"depth":851,"text":6166},{"id":6237,"depth":851,"text":6240},{"id":6248,"depth":851,"text":6251},{"id":6274,"depth":851,"text":6277},{"id":6323,"depth":825,"text":6326},{"id":6419,"depth":825,"text":6422},{"id":6540,"depth":825,"text":6543,"children":7146},[7147,7148,7149],{"id":6551,"depth":851,"text":6554},{"id":6609,"depth":851,"text":6612},{"id":6642,"depth":851,"text":6645},{"id":6690,"depth":825,"text":6693},{"id":6768,"depth":825,"text":6771,"children":7152},[7153,7154],{"id":6779,"depth":851,"text":6782},{"id":6813,"depth":851,"text":6816},{"id":6855,"depth":825,"text":6858,"children":7156},[7157,7158,7159,7160,7161],{"id":6866,"depth":851,"text":6869},{"id":6877,"depth":851,"text":6880},{"id":6888,"depth":851,"text":6891},{"id":6906,"depth":851,"text":6909},{"id":6917,"depth":851,"text":6920},{"id":6933,"depth":825,"text":6936},{"id":7007,"depth":825,"text":7010},{"id":7051,"depth":825,"text":7054},{"id":7075,"depth":825,"text":7078},"content:blog:3.designing-a-gmail-scale-email-system.md","blog/3.designing-a-gmail-scale-email-system.md","blog/3.designing-a-gmail-scale-email-system",1782056895671]