ฉบับหลัก: https://jungrok5.github.io/mmo-lag-anatomy/th/ หน้าสาเหตุ: https://jungrok5.github.io/mmo-lag-anatomy/th/c/[ID สาเหตุ].html # ข้อมูลความรู้ของคู่มือเกมแลค สร้างอัตโนมัติ (2026-10-03, `node tools/export.cjs`) อย่าแก้ไฟล์นี้ด้วยมือ ให้แก้ที่ src/js/ แล้วสร้างใหม่ สาเหตุ 228 ข้อ, คำศัพท์ 135 คำ อ้างอิงสาเหตุด้วย **ID** ต่อท้าย URL ของเว็บไซต์ด้วย `#c-ID` เพื่อไปยังการ์ดสาเหตุนั้น ## รหัสผู้รับผิดชอบ | รหัส | ทีม | ผู้รับผิดชอบ | ขอบเขต | |---|---|---|---| | cli | ทีมพัฒนาเกม | พัฒนาไคลเอนต์ | โค้ดไคลเอนต์ของเกม: เฟรม, GC, การโหลด, interpolation, extrapolation, prediction และการจัดการเครือข่ายฝั่งไคลเอนต์ (รวมการส่ง heartbeat และการเชื่อมต่อใหม่อัตโนมัติ) | | srv | ทีมพัฒนาเกม | พัฒนาเซิร์ฟเวอร์ | โค้ดเซิร์ฟเวอร์ของเกม: ทิก, เธรด, ล็อก, การออกแบบการซิงก์, การรับการเชื่อมต่อ (ลูป accept และอาร์กิวเมนต์ของ listen), การตอบ heartbeat และการเก็บกวาดการเชื่อมต่อที่ขาดไปแล้ว, socket option, การออกแบบคิวรีและทรานแซกชัน | | net | ทีมอินฟรา | อินฟราเครือข่าย | วงจรอินเทอร์เน็ตและอุปกรณ์เครือข่ายใน IDC (สวิตช์, เราเตอร์, ไฟร์วอลล์, โหลดบาลานเซอร์, ระบบป้องกัน DDoS), network ACL, VPC routing และโหลดบาลานเซอร์บนคลาวด์, ISP และ peering | | sys | ทีมอินฟรา | อินฟราเซิร์ฟเวอร์ | เครื่องเซิร์ฟเวอร์และอินสแตนซ์บนคลาวด์ (รวม security group และ connection tracking), การตั้งค่า OS และเคอร์เนล, NIC, สภาพแวดล้อมสำหรับ deploy และมอนิเตอร์ | | dba | ทีมอินฟรา | อินฟรา DB | เซิร์ฟเวอร์ DB และสตอเรจ, การตั้งค่า DB, replication, การสำรองข้อมูล, เซิร์ฟเวอร์แคช | | ext | ภายนอก | ภายนอก | PC และเครือข่ายในบ้านของผู้เล่น, ช่วงเครือข่ายของ ISP (อยู่นอกสัญญาของเรา), ผู้ให้บริการคลาวด์ ส่วนนี้เราแก้เองโดยตรงไม่ได้ จึงรับมือด้วยการแนะนำผู้เล่น ร้องขอไปยังผู้ให้บริการ หรือหาทางเลี่ยง | “ผู้รับผิดชอบหลัก” ของแต่ละสาเหตุคือฝ่ายที่กำจัดต้นเหตุได้ ส่วน “ร่วมกับ” คือฝ่ายที่มีงานต้องทำจริง ## อาการ - **กระตุก** (`stutter`, เรียกอีกอย่างว่า: สะดุด, ไม่ลื่น, เหมือนเฟรมดรอป): การเคลื่อนไหวไม่ลื่น หยุดสั้น ๆ แล้วขยับต่อ สลับกันไปเรื่อย ๆ ถ้าค่าปิงปกติ มักเป็นปัญหาเฟรมบน PC ของเรา (ไคลเอนต์/OS) ถ้าปิงขึ้นลงไม่นิ่ง มักเป็นจิตเตอร์จาก Wi-Fi หรือเน็ต อย่างไรก็ตาม ปิงที่เกมแสดงมักวัดในลูปของเกมที่ทำงานทุกเฟรม เมื่อเฟรมไทม์พุ่ง ตัวเลขปิงก็พุ่งตามได้ - **วาร์ป** (`teleport`, เรียกอีกอย่างว่า: เทเลพอร์ต, หายวับแล้วโผล่อีกที่, กระโดดข้ามตำแหน่ง): ตัวละครย้ายไปยังตำแหน่งที่ไกลออกไปในทีเดียว โดยไม่เห็นช่วงระหว่างทาง ส่วนใหญ่แปลว่าแพ็กเก็ตขาดหายไปช่วงหนึ่ง ให้ดูแพ็กเก็ตหาย, เน็ตขาดช่วงสั้น ๆ, เซิร์ฟเวอร์หยุด และ extrapolation ที่พลาด ถ้าคนอื่นปกติแต่มีคนเดียวที่วาร์ป ให้สงสัยเน็ตของคนนั้นก่อน - **ดีดกลับ** (`rubber`, เรียกอีกอย่างว่า: โดนดึงกลับ, rubber banding, ตำแหน่งย้อนกลับ): ตัวละครของเรากำลังเดินหน้า แล้วโดนดึงกลับไปยังจุดที่เพิ่งผ่านมา ภาพบนจอเรา (prediction) กับผลตัดสินของเซิร์ฟเวอร์ไม่ตรงกัน อาจเพราะอินพุตของเราไปไม่ถึงเซิร์ฟเวอร์ (แพ็กเก็ตหาย), เซิร์ฟเวอร์ตรวจการเคลื่อนที่แล้วตัดทิ้ง หรือสองฝั่งคำนวณการเคลื่อนที่ไม่เหมือนกัน - **กรอเร็ว** (`burst`, เรียกอีกอย่างว่า: รัว ๆ, เหมือนกดกรอ, ทุกอย่างมาพร้อมกัน): หน้าจอที่หยุดไปกลับมาขยับอีกครั้ง แล้วการเคลื่อนไหว การโจมตี และดาเมจที่สะสมไว้ก็ผ่านไปอย่างรวดเร็วในทีเดียว แพ็กเก็ตไปกองรออยู่ที่ใดที่หนึ่ง แล้วถูกปล่อยออกมาพร้อมกัน ตัวอย่างที่พบบ่อยคือการรอส่งซ้ำของ TCP, เซิร์ฟเวอร์เร่งคำนวณให้ทัน และไคลเอนต์ประมวลผลไม่ทัน - **สโลว์โมชั่น** (`slowmo`, เรียกอีกอย่างว่า: ทั้งโลกในเกมช้าลง, อืดไปหมด): ทุกอย่างเคลื่อนที่ช้าลง การร่ายสกิลและการเดินของมอนสเตอร์ดูยืดยาด ขึ้นกับการออกแบบเซิร์ฟเวอร์ บางเกมความเร็วยังเท่าเดิม และไปแสดงเป็นอาการกระตุกหรือวาร์ปแทน เซิร์ฟเวอร์ประมวลผลทิกไม่ทันเวลา เน็ตยังปกติ ปิงที่วัดจากนอกเกมจึงเท่าเดิม ส่วนปิงในเกมอาจสูงขึ้นเล็กน้อยถ้ารวมเวลารอประมวลผลของเซิร์ฟเวอร์ไว้ด้วย ให้ดูจำนวนคนที่พุ่งขึ้น, การคำนวณระยะมองเห็น, broadcast และหน่วยความจำไม่พอ - **อินพุตดีเลย์** (`delay`, เรียกอีกอย่างว่า: ตอบสนองช้า, input lag, กดแล้วไม่ได้ฟีล): กดแล้วต้องรอสักพักกว่าผลจะออก ตัวภาพบนจออาจยังลื่นตามปกติ เวลาไปกลับ (ปิง) สูง หรือมีคิวสะสมอยู่ที่ใดที่หนึ่ง ให้ดูระยะทาง, คิวในเราเตอร์, Nagle (ฟีเจอร์ของ TCP ที่รวบแพ็กเก็ตเล็ก ๆ ไว้ส่งทีเดียว) และคิวของเซิร์ฟเวอร์ ถ้าปิงต่ำแต่ยังตอบสนองช้าตลอด ให้ดูฝั่ง PC ของเรา เช่น V-Sync หรือ FPS ต่ำ หรือดูว่าเกมออกแบบให้ทุกการกระทำต้องรอเซิร์ฟเวอร์ยืนยันหรือไม่ (บทรูปแบบการซิงก์) - **ค้าง** (`freeze`, เรียกอีกอย่างว่า: ภาพนิ่ง, หยุดนิ่ง, ไม่ตอบสนอง): ทุกอย่างบนจอหยุดไปชั่วครู่ (0.5 วินาทีถึงหลายวินาที) แล้วกลับมาขยับต่อ เซิร์ฟเวอร์หยุดทั้งตัว (GC, เดดล็อก, synchronous call), เน็ตขาดไปชั่วครู่ หรือ PC ของเราหยุดทำงานชั่วขณะ - **กดไม่ติด/โรลแบ็ค** (`dropped`, เรียกอีกอย่างว่า: สกิลไม่ออก, ไอเทมย้อนกลับ, เทรดไม่สำเร็จ): การกระทำที่ทำไปแล้วแน่ ๆ กลับไม่เกิดผล หรือผลถูกพลิกกลับหลังผ่านไปพักใหญ่ คำขอหายไประหว่างทาง (แพ็กเก็ตหาย, คิวล้น), เซิร์ฟเวอร์ตัดสินต่างจากที่เห็นบนจอเรา (จังหวะเวลาตัดสินต่างกัน, เซิร์ฟเวอร์ปฏิเสธหลังแสดงผลล่วงหน้าไปแล้ว) หรือบันทึกล้มเหลวกลางทาง (DB ติดล็อกหรือล่ม, เซิร์ฟเวอร์แครช) - **หลุด** (`disconnect`, เรียกอีกอย่างว่า: เกมเด้ง, เน็ตหลุด, ขาดการเชื่อมต่อกับเซิร์ฟเวอร์): การเชื่อมต่อขาดระหว่างเล่น แล้วเด้งกลับไปหน้าล็อกอินหรือหน้าต่างเชื่อมต่อใหม่ ไม่มีแพ็กเก็ตมาถึงเลยสักตัวภายในเวลาไทม์เอาต์ ให้ดูเน็ตขาดเป็นเวลานาน, idle timeout, เซิร์ฟเวอร์แครชหรือรีสตาร์ต และเซิร์ฟเวอร์หรือ PC ของเราที่หยุดนานกว่าไทม์เอาต์ (โหลดนาน) ถ้าเกมปิดตัวไปเลยโดยไม่มีข้อความแจ้ง ให้ดูว่าไคลเอนต์ถูกปิดกะทันหัน (แครช, หน่วยความจำไม่พอ) ก่อนเรื่องการเชื่อมต่อ - **เข้าเกมไม่ได้/โหลดไม่จบ** (`noconnect`, เรียกอีกอย่างว่า: ล็อกอินไม่ได้, โหลดวนไม่จบ): เข้าไปในเกมไม่ได้ หรือติดอยู่ที่หน้าโหลดหรือหน้าเข้าเกม จุดที่รับการเชื่อมต่อใหม่ (คิวรอเชื่อมต่อของเซิร์ฟเวอร์, ไฟร์วอลล์, เซิร์ฟเวอร์ล็อกอิน, DB) เต็ม พบบ่อยเป็นพิเศษทันทีหลังปิดปรับปรุง - **มองไม่เห็น/ตัวผี** (`invisible`, เรียกอีกอย่างว่า: NPC ไม่ขึ้น, ตัวละครล่องหน, มอนที่ตายแล้วยังยืนอยู่): NPC มอนสเตอร์ หรือผู้เล่นที่ควรจะอยู่ กลับไม่มีบนจอของเราคนเดียว หรือสิ่งที่หายไปแล้วยังเหลืออยู่บนจอของเราคนเดียว เป็นสภาพที่แพ็กเก็ตบางตัวหายไปหรือวาดไม่สำเร็จ มากกว่าจะเป็นเรื่องความเร็ว ให้ดูแชนแนลหรือเฟสที่ต่างกัน, ข้อความแจ้งการปรากฏตัว/ออกไปที่หล่นหาย, การทิ้งข้อมูลระหว่างโหลด และการโหลดแอสเซ็ตล้มเหลว เบาะแสชี้ขาดคือ ถ้าเดินออกนอกระยะมองเห็นแล้วกลับมา จะมองเห็นหรือไม่ ## ปัจจัยสี่อย่าง - **ความหน่วง** (`lat`, Latency): ระยะทาง, คิว และเวลาประมวลผล ทำให้แพ็กเก็ตทุกตัวมาถึงช้าเท่า ๆ กัน วิธีที่เกมรับมือ: ใช้ prediction และการแสดงผลล่วงหน้าเพื่อให้เห็นการกระทำของเราทันที ส่วนการตัดสินผล เซิร์ฟเวอร์จะย้อนเวลากลับไปตรวจ (lag compensation) - **จิตเตอร์** (`jit`, Jitter): ค่าเฉลี่ยยังดีอยู่ แต่บางแพ็กเก็ตมาเร็ว บางแพ็กเก็ตมาช้า ต้นเหตุคือ Wi-Fi, เครือข่ายที่แออัด และ CPU ที่ทำงานหนัก วิธีที่เกมรับมือ: เก็บแพ็กเก็ตไว้ใน interpolation buffer สักเล็กน้อย แล้วดึงออกมาวาดในจังหวะที่สม่ำเสมอ ถ้าจิตเตอร์มากกว่าบัฟเฟอร์ก็กลบไม่อยู่ - **แพ็กเก็ตหาย** (`loss`, Packet loss): คิวที่ล้น, คลื่นรบกวน และอุปกรณ์ที่เสีย ทำให้แพ็กเก็ตถูกทิ้ง การที่เน็ตขาดไปชั่วครู่ก็นับเป็นแพ็กเก็ตหายต่อเนื่องเช่นกัน วิธีที่เกมรับมือ: เกมที่ใช้ UDP จะเติมช่องว่างด้วย interpolation หรือ extrapolation และส่งอินพุตของเราซ้ำทับกันไว้ ถ้าหายไปหนึ่งหรือสองแพ็กเก็ตก็ยังเติมได้ ส่วน TCP จะไม่ส่งแพ็กเก็ตถัดไปให้เกมจนกว่าจะได้แพ็กเก็ตที่หายกลับมา - **การหยุดชะงัก** (`stall`, Stall): ทิกของเซิร์ฟเวอร์ช้าลงหรือหยุด (GC, ล็อก, synchronous call, โหลดเกิน) หรือเฟรมบน PC ของเราหยุด เกิดได้แม้เน็ตจะปกติดี วิธีที่เกมรับมือ: หยุดไปนานเท่าไร งานก็สะสมมากเท่านั้น เกมจะเร่งคำนวณให้ทันในรวดเดียว ข้ามไปเลย หรือปล่อยให้เวลาในเกมเดินช้าลง ## สาเหตุ ### L1 โปรเซสเกมฝั่งไคลเอนต์ (16 สาเหตุ) #### cg-hitch · เฟรมไทม์พุ่ง · Frame hitch เฟรมหนึ่งใช้เวลาคำนวณนานกว่าปกติหลายเท่า ภาพบนจอจึงหยุดไปครู่หนึ่ง - ทำไม → ผลคือ → บนหน้าจอ: เอฟเฟกต์สกิลเยอะผิดปกติ, spawn จำนวนมาก และการอัปเดต UI ทั้งหน้าจอ มารวมอยู่ในเฟรมเดียว → ทำไม่เสร็จภายใน 16.7 ms และกินเวลา 50–300 ms → ภาพหยุดแวบ แล้วทุกตัวขยับไปพร้อมกันในเฟรมถัดไป - อาการ: กระตุก, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ตอนทำแอ็กชันบางอย่าง, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แบ่งงานหนักไปทำในหลายเฟรม, ใช้ profiler หาเฟรมที่พุ่ง, จำกัดจำนวนเอฟเฟกต์ - ตัวเลขที่ควรรู้: หนึ่งเฟรมที่ 60 FPS คือ 16.7 ms แค่มีเฟรมเดียวที่เกิน 50 ms ก็รู้สึกได้ง่ายว่า “กระตุก” - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (เฟรมไทม์) - จุดที่ต้องดู: ใช้ PresentMon อัดเฟรมไทม์ (FrameTime) และเวลาที่ CPU/GPU ใช้กับเฟรมนั้น (CPUBusy/GPUBusy) ระหว่างเล่น บนมือถือดูเมตริก slow sessions และ slow rendering ของ Android vitals - สัญญาณว่าใช่: จังหวะที่เฟรมไทม์ซึ่งปกติอยู่ราว 16.7 ms พุ่งเกิน 50 ms ตรงกับเอฟเฟกต์สกิล, spawn จำนวนมาก หรือการอัปเดต UI ทั้งหน้าจอ และขณะนั้นปิงยังเท่าเดิม - สัญญาณว่าไม่ใช่: เฟรมไทม์สม่ำเสมอ แต่มีแค่ตัวละครอื่นที่หยุดแวบ: น่าจะเป็นฝั่งเครือข่าย เช่น “ไม่มี interpolation buffer หรือสั้นเกินไป” ถ้าพุ่งเป็นรอบสม่ำเสมอ ให้ดู “GC ฝั่งไคลเอนต์” ก่อน - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [Slow rendering](https://developer.android.com/topic/performance/vitals/render) · Android (Google) · ต้องวาดหนึ่งเฟรมให้เสร็จภายใน 16 ms จึงจะได้ 60 FPS ถ้าช้ากว่านั้นเฟรมจะถูกข้ามและเห็นเป็นอาการกระตุก (jank) - [Slow Sessions (games only)](https://developer.android.com/topic/performance/issues/slow-session) · Android (Google) · Android vitals ถือว่าเฟรมของเกมที่เกิน 50 ms (20 FPS) หรือ 34 ms (30 FPS) เป็นเฟรมช้า - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · FrameTime (เวลา CPU ระหว่างเฟรม), CPUBusy/GPUBusy (เวลาที่ CPU/GPU ใช้สร้างเฟรมนั้น) #### cg-gc · GC ฝั่งไคลเอนต์ · Client GC (Unity C#, Unreal, Lua) ทั้งเกมหยุดระหว่างเก็บคืนหน่วยความจำที่ใช้แล้วทิ้ง (garbage) จุดสังเกตคือกระตุกเป็นจังหวะสม่ำเสมอ - ทำไม → ผลคือ → บนหน้าจอ: สร้าง string, array และ list ชั่วคราวแล้วทิ้งทุกเฟรม → เมื่อ garbage สะสมมากพอ GC จะหยุดเมนเธรดเพื่อเก็บคืน → กระตุกเป็นจังหวะทุกไม่กี่วินาทีถึงหลายสิบวินาที - อาการ: กระตุก, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: เป็นรอบสม่ำเสมอ, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ลดการจัดสรรหน่วยความจำ (เลี่ยงการต่อ string, LINQ และ lambda capture), ใช้ object pool, เปิด incremental GC ไว้ (เป็นค่าเริ่มต้นตั้งแต่ Unity 2020), สั่ง GC ล่วงหน้าในจังหวะที่หยุดได้ เช่น หน้าโหลด - ตัวเลขที่ควรรู้: ปกติครั้งละหลักหน่วย ms ถึง 100 ms ส่วนมือถือสเปกต่ำหรือเกมที่ใช้หน่วยความจำมากอาจนานกว่านั้น (ในการทดลองราว 150–170 ms) GC ของ Unity ตรวจทั้ง heap ทุกครั้งที่ทำงาน ยิ่งเกมใช้หน่วยความจำมากก็ยิ่งนาน - บนกราฟ: พุ่งเป็นรอบ (เฟรมไทม์, เวลาที่ GC ทำงาน) - จุดที่ต้องดู: ดู marker GC.Collect และ GC.Alloc ใน Unity Profiler บน development build ส่วน Unreal ดู stat GC และ stat Hitches (บันทึกเฟรมที่เกินเวลาที่ตั้งไว้ใน t.HitchFrameTimeThreshold ลง log) - สัญญาณว่าใช่: ทุกเฟรมที่พุ่งมีช่วง GC.Collect ยาวใกล้เคียงกับช่วงที่พุ่ง และเกิดเป็นรอบสม่ำเสมอทุกไม่กี่วินาทีถึงหลายสิบวินาที ในที่ที่คนเยอะ GC.Alloc ต่อเฟรมเพิ่มขึ้น - สัญญาณว่าไม่ใช่: เฟรมที่พุ่งไม่มีช่วง GC: น่าจะเป็น “โหลดแบบ synchronous และคอมไพล์ shader บนเมนเธรด” หรือ “ภาระการเรนเดอร์ตัวละครจำนวนมาก” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: พบบ่อยในไคลเอนต์ที่เขียนด้วย C# อย่าง Unity ตัวการหลักคือโค้ดที่สร้าง string ของ combat log, ตัวเลขดาเมจ และข้อความ UI ขึ้นใหม่ทุกเฟรม ถ้ากระตุกเฉพาะที่ที่คนเยอะ แปลว่ามีโค้ดที่สร้าง garbage เพิ่มตามจำนวนผู้เล่น incremental GC แบ่งเก็บทีละนิดในแต่ละเฟรม (ค่าเริ่มต้นของ Unity คือ 3 ms) แต่ถ้าสร้าง garbage เร็วกว่าที่เก็บทัน สุดท้ายก็ต้องหยุดเก็บรวดเดียวอยู่ดี Unreal Engine ก็มี GC ของตัวเองที่เก็บกวาด game object ที่ไม่ได้ใช้ ค่าจะต่างกันไปตามเวอร์ชันเอนจินและการตั้งค่า แต่ค่าเริ่มต้นจะทำงานราวทุก 1 นาที จึงอาจเกิดอาการหยุดแวบทุก 1 นาทีได้ ไคลเอนต์ที่เขียนกฎของเกมด้วยสคริปต์อย่าง Lua ยังมี GC ของสคริปต์นั้นทำงานแยกอีกชุดหนึ่ง - แหล่งอ้างอิง: - [Garbage collection modes](https://docs.unity3d.com/Manual/performance-incremental-garbage-collection.html) · Unity · ค่าเริ่มต้นคือ incremental GC ที่แบ่งเก็บในหลายเฟรม ถ้าปิดไว้ เมนเธรดจะหยุดระหว่างตรวจทั้ง heap และอาจนานถึงหลายร้อย ms - [Scripting.GarbageCollector.CollectIncremental](https://docs.unity3d.com/ScriptReference/Scripting.GarbageCollector.CollectIncremental.html) · Unity · เป้าหมายเริ่มต้นของเวลาที่ incremental GC ใช้ต่อครั้ง (time slice) คือ 3 ms (incrementalTimeSliceNanoseconds) - [Garbage Collection Settings in the Unreal Engine Project Settings](https://dev.epicgames.com/documentation/en-us/unreal-engine/garbage-collection-settings-in-the-unreal-engine-project-settings) · Epic Games · การตั้งค่าที่ทำให้ GC ของ Unreal ทำงานตามช่วงเวลาที่กำหนด (Time Between Purging Pending Kill Objects หน่วยเป็นวินาที) เอกสารนี้ไม่ได้ระบุตัวเลขค่าเริ่มต้น - [Profiler markers reference](https://docs.unity3d.com/Manual/profiler-markers.html) · Unity · GC.Collect: ช่วงที่โค้ดของโปรแกรมหยุดระหว่าง garbage collection (ไม่ถึง 1 ms ไปจนถึงหลายร้อย ms), GC.Alloc: การจัดสรร managed heap - [Stat Commands in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/stat-commands-in-unreal-engine) · Epic Games · stat GC (สถิติ garbage collection), stat Hitches (บันทึกเฟรมที่เกิน t.HitchFrameTimeThreshold ลง log) #### cg-sync-load · โหลดแบบ synchronous และคอมไพล์ shader บนเมนเธรด · Synchronous asset load, shader compile เกมหยุดเพราะต้องอ่านไฟล์และสร้าง shader ก่อนวาดพื้นที่, มอนสเตอร์ หรือเอฟเฟกต์ที่เพิ่งเห็นเป็นครั้งแรก - ทำไม → ผลคือ → บนหน้าจอ: เข้าพื้นที่ใหม่ หรือเจอสกิล, อุปกรณ์สวมใส่ และมอนสเตอร์ที่ไม่เคยเห็น → เมนเธรดรอการอ่านไฟล์และการคอมไพล์ shader → ค้าง 0.1–1 วินาทีแค่ครั้งแรก ตั้งแต่ครั้งที่สองก็ปกติ - อาการ: ค้าง, กระตุก / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ, ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: โหลดแบบ async, โหลดเตรียมไว้ล่วงหน้า (prewarming), คอมไพล์ shader ล่วงหน้าตอนหน้าโหลดหรือตอนเปิดเกมครั้งแรก, ใช้ประโยชน์จากหน้าโหลด - ตัวเลขที่ควรรู้: คอมไพล์ shader หนึ่งตัวใช้หลายสิบ ms ถ้านานอาจเกิน 100 ms ส่วนการโหลด texture ใช้หลายสิบถึงหลายร้อย ms ขึ้นกับความเร็วของสตอเรจ - บนกราฟ: พุ่งทันทีหลังเปิดเซิร์ฟ/ปิดปรับปรุง (จำนวนเฟรมที่พุ่ง (ทันทีหลังแพตช์หรืออัปเดตไดรเวอร์)) - จุดที่ต้องดู: อัดด้วย PresentMon บนเส้นทางเดิมสองรอบ แล้วเทียบการไปครั้งแรกกับครั้งที่สอง ใน development build ของ Unreal เปิด r.PSOPrecache.Validation แล้วดู stat PSOPrecache และ “PSO PRECACHING MISS” ใน log ส่วน Unity ดูช่วงโหลดและ shader ของเฟรมที่พุ่งใน Timeline ของ Profiler - สัญญาณว่าใช่: พุ่ง 0.1–1 วินาทีเฉพาะที่ที่ไปครั้งแรกหรือสกิลที่ใช้ครั้งแรก และหายไปในครั้งที่สอง การแจ้งปัญหาพุ่งขึ้นทันทีหลังแพตช์หรืออัปเดตไดรเวอร์กราฟิก แล้วค่อย ๆ ลดลง - สัญญาณว่าไม่ใช่: พุ่งที่จุดเดิมทุกครั้ง: ไม่ใช่ปัญหา shader cache ถ้าเกิดซ้ำทุกครั้งที่เคลื่อนที่และเป็นเฉพาะ PC ที่สตอเรจช้า น่าจะเป็น “สตอเรจช้าจนสตรีมแอสเซ็ตไม่ทัน” ถ้าหน่วยความจำ GPU เฉพาะ (dedicated) เต็ม น่าจะเป็น “หน่วยความจำกราฟิก (VRAM) ไม่พอ” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: บน PC ไดรเวอร์กราฟิกจะเก็บ shader ที่สร้างแล้วไว้ใน shader cache เพื่อใช้ซ้ำ ดังนั้นหลังอัปเดตไดรเวอร์กราฟิกหรือหลังเกมลงแพตช์ใหม่ แคชนี้จะใช้ไม่ได้ คนที่เคยเล่นได้ลื่นก็จะกลับมากระตุกอยู่ช่วงหนึ่ง การแจ้งปัญหาแบบ “หลังแพตช์ ไปที่ไหนครั้งแรกก็หยุดแวบทุกที่” เป็นรูปแบบการแจ้งปัญหาที่เจอได้บ่อย - แหล่งอ้างอิง: - [Shader loading](https://docs.unity3d.com/Manual/shader-loading.html) · Unity · ครั้งแรกที่ใช้ shader variant ไดรเวอร์กราฟิกต้องสร้างเวอร์ชันสำหรับ GPU จึงอาจหยุดจนสังเกตได้ ส่วนที่สร้างแล้วจะถูกแคชไว้และไม่หยุดอีก - [Optimizing Rendering With PSO Caches in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/optimizing-rendering-with-pso-caches-in-unreal-engine) · Epic Games · ถ้าสร้าง pipeline state (PSO) ในจังหวะที่ต้องใช้ อาจกินเวลาเกิน 100 ms จึงต้องสร้างเตรียมไว้ล่วงหน้า - [Direct3D 12 Return Codes](https://learn.microsoft.com/en-us/windows/win32/direct3d12/d3d12-graphics-reference-returnvalues) · Microsoft · D3D12_ERROR_DRIVER_VERSION_MISMATCH: PSO cache ที่สร้างจากไดรเวอร์คนละเวอร์ชันใช้ซ้ำไม่ได้ (ต้องคอมไพล์ใหม่หลังอัปเดตไดรเวอร์) - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · อัดเวลาของแต่ละเฟรมด้วย FrameTime (เวลา CPU ระหว่างเฟรม) - [PSO Precaching for Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/pso-precaching-for-unreal-engine) · Epic Games · เมื่อเปิด r.PSOPrecache.Validation จะดูสถิติ PSO ที่พลาดได้ด้วย stat PSOPrecache และมี “PSO PRECACHING MISS” ใน log ถ้าการสร้าง PSO ตอนรันเกินค่าเริ่มต้น 20 ms จะนับเป็น hitch #### cg-asset-stream · สตอเรจช้าจนสตรีมแอสเซ็ตไม่ทัน · Slow storage stalls asset streaming บนสตอเรจช้าอย่าง HDD การอ่าน texture และโมเดลของโอเพนเวิลด์ตามการเคลื่อนที่ไม่ทัน เอนทิตีจึงโผล่ช้า หรือเกมกระตุกระหว่างรออ่านข้อมูล - ทำไม → ผลคือ → บนหน้าจอ: เคลื่อนที่เร็วด้วยพาหนะหรือเทเลพอร์ต หรือเข้าไปในที่ที่คนเยอะ จนต้องใช้ texture และโมเดลใหม่จำนวนมากพร้อมกัน → สตอเรจช้าอย่าง HDD อ่านได้ไม่ทันความเร็วที่ต้องการ คำขออ่านจึงกองรอ และการโหลดบางส่วนทำให้เมนเธรดต้องรอจนเสร็จ → texture เบลออยู่พักหนึ่ง อาคารและตัวละครโผล่ช้า และกระตุกหรือค้างตอนที่รออ่าน - อาการ: มองไม่เห็น/ตัวผี, กระตุก, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: สตรีมแบบ async ที่ไม่ให้เมนเธรดรอการอ่าน, อ่านล่วงหน้าตามทิศทางและความเร็วการเคลื่อนที่, แสดงความละเอียดต่ำ (mipmap) หรือโมเดลอย่างง่ายก่อนแล้วค่อยเปลี่ยน, ถ้าสตรีมไม่ทันระหว่างเคลื่อนที่เร็วให้จำกัดความเร็วการเคลื่อนที่ชั่วคราวหรือใช้หน้าโหลด, ระบุในสเปกขั้นต่ำและสเปกแนะนำว่าต้องใช้ SSD หรือไม่ - งานฝั่งภายนอก: แนะนำให้ผู้เล่นติดตั้งเกมบน SSD, แนะนำให้ผู้เล่นตรวจว่ามีการดาวน์โหลดหรือการสแกนของแอนตี้ไวรัสทำงานอยู่บนดิสก์เดียวกันหรือไม่ - ตัวเลขที่ควรรู้: ตามคำอธิบายของ Microsoft ฮาร์ดดิสก์รุ่นเก่าอ่านได้หลายสิบ MB ใน 1 วินาที NVMe SSD อ่านได้หลาย GB ใน 1 วินาที และเกมยุคก่อนใช้แบนด์วิดท์สำหรับการสตรีมราว 50 MB ใน 1 วินาที ถ้าเอาเกมที่ออกแบบโดยถือว่ามีความเร็วระดับ SSD และอ่านมากกว่านี้มากไปเล่นบน HDD การอ่านก็มักตามการเคลื่อนที่ไม่ทัน - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนเฟรมที่พุ่ง (แยกตามชนิดสตอเรจ), ดีเลย์การอ่านดิสก์) - จุดที่ต้องดู: บันทึก PhysicalDisk\Avg. Disk sec/Read (เวลาเฉลี่ยต่อการอ่านหนึ่งครั้ง) และ Current Disk Queue Length จาก Performance Monitor ของ Windows คู่กับเฟรมไทม์จาก PresentMon ขณะเคลื่อนที่เร็ว ใน development build ดู stat Streaming และ stat AsyncLoad ของ Unreal หรือคำเตือน AssetBundle.asset/allAssets ใน Unity Profiler (ขอผลลัพธ์ก่อนโหลดเสร็จ เมนเธรดจึงต้องรอ) - สัญญาณว่าใช่: ตอนเคลื่อนที่เร็ว ดีเลย์การอ่านดิสก์และคิวพุ่งสูง และในเวลาเดียวกันเฟรมพุ่งหรือ texture และเอนทิตีโผล่ช้า เล่นฉากเดียวกันบน SSD แล้วหายไป - สัญญาณว่าไม่ใช่: ดิสก์ว่างแต่ texture เบลอ: น่าจะเป็น “หน่วยความจำกราฟิก (VRAM) ไม่พอ” ถ้าไปที่เดิมครั้งที่สองแล้วปกติ น่าจะเป็น “โหลดแบบ synchronous และคอมไพล์ shader บนเมนเธรด” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: ถ้าหยุดแค่ครั้งแรกแล้วครั้งต่อไปปกติ จะใกล้กับ “โหลดแบบ synchronous และคอมไพล์ shader บนเมนเธรด” แต่ถ้าเกิดซ้ำทุกครั้งที่เคลื่อนที่และเป็นเฉพาะ PC ที่สตอเรจช้า ก็คือสาเหตุนี้ ส่วน “หน่วยความจำกราฟิก (VRAM) ไม่พอ” ซึ่งต้องปลด texture ออกแล้วโหลดกลับเข้ามาเพราะหน่วยความจำกราฟิกไม่พอ ก็ทำให้ texture เบลอได้เหมือนกัน จึงควรดูการรออ่านดิสก์คู่กับปริมาณการใช้หน่วยความจำกราฟิก การสแกนแบบเรียลไทม์ของแอนตี้ไวรัสที่เข้ามาแทรกทุกครั้งที่เปิดไฟล์เกม ก็อาจทำให้อ่านช้าลงไปอีก - แหล่งอ้างอิง: - [DirectStorage is coming to PC](https://devblogs.microsoft.com/directx/directstorage-is-coming-to-pc/) · Microsoft · ฮาร์ดดิสก์รุ่นเก่าอ่านได้หลายสิบ MB ต่อวินาที, NVMe SSD หลาย GB ต่อวินาที, งบการสตรีมแอสเซ็ตของเกมยุคก่อนราว 50 MB ต่อวินาที, เกมโอเพนเวิลด์อ่านและทิ้งฉากที่อยู่ไกลแบบเรียลไทม์ขณะเคลื่อนที่ - [Texture and mesh loading](https://docs.unity3d.com/Manual/LoadingTextureandMeshData.html) · Unity · การอัปโหลดแบบ synchronous จะอ่านและอัปโหลดในเฟรมเดียวบนเมนเธรด ทำให้หยุดจนสังเกตได้ ส่วนการอัปโหลดแบบ async จะสตรีมกระจายไปหลายเฟรม - [Texture Streaming Overview for Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/texture-streaming-overview-for-unreal-engine) · Epic Games · ตัวสตรีมจะเพิ่มหรือลดความละเอียดของ texture (mip) ตามมุมมอง การคำนวณส่วนใหญ่ทำใน worker thread แบบ async และโหลด mip ที่เห็นบนจอก่อน - [Profiler markers reference](https://docs.unity3d.com/Manual/profiler-markers.html) · Unity · คำเตือน AssetBundle.asset/allAssets: ขอผลลัพธ์ก่อนโหลดเสร็จ เมนเธรดจึงหยุดรอ - [Stat Commands in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/stat-commands-in-unreal-engine) · Epic Games · stat Streaming (หน่วยความจำและจำนวนของ texture ที่สตรีม), stat AsyncLoad (สถิติการโหลดแบบ async) - [Windows Performance Monitor Disk Counters Explained](https://learn.microsoft.com/en-us/archive/blogs/askcore/windows-performance-monitor-disk-counters-explained) · Microsoft · Avg. Disk sec/Read คือเวลาเฉลี่ยที่การอ่านหนึ่งครั้งใช้จนเสร็จ (ดีเลย์ของ I/O), Current Disk Queue Length คือความยาวคิวดิสก์ ณ ขณะที่วัด - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · อัดเวลาของแต่ละเฟรมด้วย FrameTime (เวลา CPU ระหว่างเฟรม) - [About regular quick and full scans with Microsoft Defender Antivirus](https://learn.microsoft.com/en-us/defender-endpoint/schedule-antivirus-scans) · Microsoft · การป้องกันแบบเรียลไทม์จะสแกนทุกครั้งที่เปิดและปิดไฟล์ #### cg-crowd · ภาระการเรนเดอร์ตัวละครจำนวนมาก · Render/animation cost of crowds เมื่อคนหลายร้อยคนเข้ามาอยู่ในจอเดียว เช่น ศึกชิงปราสาทหรือเวิลด์บอส ลำพังต้นทุนการวาดภาพก็รับไม่ไหวแล้ว - ทำไม → ผลคือ → บนหน้าจอ: คนหลายร้อยคนและเอฟเฟกต์ซ้อนกันอยู่ในจอเดียว → ต้นทุนของแอนิเมชัน, เงา, ป้ายชื่อ และเอฟเฟกต์ เพิ่มขึ้นตามจำนวนคน → FPS ตกจาก 60 → 15 ทุกการเคลื่อนไหวกระตุก และอินพุตก็ช้าตาม - อาการ: กระตุก, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล, เราคนเดียว / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ลดรายละเอียดตามระยะ (LOD), จำกัดจำนวนคนที่แสดง, เพิ่มตัวเลือกลดเอฟเฟกต์, ลดความถี่การอัปเดตแอนิเมชัน - ตัวเลขที่ควรรู้: ต่อให้ใช้แค่ 0.02–0.1 ms ต่อตัวละคร 1 ตัว ถ้ามี 300 ตัวก็เป็น 6–30 ms งบเวลาต่อเฟรมที่ 60 FPS คือ 16.7 ms แค่ส่วนนี้ก็กินไปเกิน 1/3 แล้ว และถ้ามากก็เกินงบ - บนกราฟ: สูงตามจำนวนคนและโหลด (เฟรมไทม์, จำนวนตัวละครบนจอ) - จุดที่ต้องดู: เทียบเฟรมไทม์และ CPUBusy/GPUBusy จาก PresentMon ก่อนและหลังศึกชิงปราสาทหรือเวิลด์บอส ใน development build ใช้ stat Unit ของ Unreal (เวลาของเธรดเกม, เธรดเรนเดอร์ และ GPU) - สัญญาณว่าใช่: ยิ่งคนบนจอเพิ่ม เฟรมไทม์ยิ่งสูงตาม และดีขึ้นทันทีเมื่อเปิดตัวเลือกจำกัดจำนวนคนที่แสดงหรือลดเอฟเฟกต์ - สัญญาณว่าไม่ใช่: พุ่งโดยไม่เกี่ยวกับจำนวนคน: น่าจะเป็น “เฟรมไทม์พุ่ง” หรือ “GC ฝั่งไคลเอนต์” ถ้า FPS ปกติแต่มีแค่การเคลื่อนไหวของคนอื่นที่ช้า น่าจะเป็น “คอขวดการประมวลผลแพ็กเก็ตบนเมนเธรด” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [Introduction to level of detail](https://docs.unity3d.com/Manual/LevelOfDetail.html) · Unity · ถ้าไม่มี LOD วัตถุที่เห็นเล็ก ๆ บนจอก็ยังถูกวาดด้วยความซับซ้อนเท่าเดิม LOD ช่วยลดภาระการวาด - [Animation Budget Allocator in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/animation-budget-allocator-in-unreal-engine) · Epic Games · ลดการอัปเดตแอนิเมชัน (tick) ของ skeletal mesh แบบไดนามิก เพื่อคุมเวลาที่ใช้กับแอนิเมชันให้อยู่ในงบ - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · ใช้ FrameTime, CPUBusy และ GPUBusy แยกว่าฝั่ง CPU หรือ GPU ที่ทำให้เฟรมช้า - [Stat Commands in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/stat-commands-in-unreal-engine) · Epic Games · stat Unit: เฟรมไทม์รวม และเวลาของเธรดเกม, เธรดเรนเดอร์ และ GPU #### cg-net-mainthread · คอขวดการประมวลผลแพ็กเก็ตบนเมนเธรด · Network processing on the main thread ถ้าประมวลผลแพ็กเก็ตที่รับมาได้แค่จำนวนที่กำหนดในแต่ละเฟรม แพ็กเก็ตที่ทะลักเข้ามาจะถูกเลื่อนไปเฟรมถัดไปเรื่อย ๆ - ทำไม → ผลคือ → บนหน้าจอ: ในที่ที่คนเยอะ มีอัปเดตเข้ามาหลายพันรายการต่อวินาที → เมนเธรดชนเพดานปริมาณที่ประมวลผลได้ต่อเฟรม จึงอ่านได้ไม่หมด → การเคลื่อนไหวของคนอื่นแสดงช้าลงเรื่อย ๆ แล้วมาเป็นชุดรวดเดียว - อาการ: กรอเร็ว, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: บางจุด/บางแชนแนล, เราคนเดียว / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: แยกการรับและการแปลงข้อมูลไปไว้อีกเธรด, รวมอัปเดตตำแหน่งเก่าของเป้าหมายเดียวกันแล้วใช้เฉพาะอันล่าสุด เซิร์ฟเวอร์: ในที่ที่คนเยอะ ส่งอัปเดตของตัวละครที่อยู่ไกลให้ห่างขึ้นเพื่อลดปริมาณการส่ง - ตัวเลขที่ควรรู้: เมื่อแพ็กเก็ตที่ประมวลผลไม่ทันกองรอ แค่ไม่กี่วินาทีก็ตามหลังไปถึง 1 วินาทีได้แล้ว - บนกราฟ: สูงตามจำนวนคนและโหลด (จำนวนแพ็กเก็ตขาเข้าที่ประมวลผลไม่ทัน, ดีเลย์ตั้งแต่รับจนนำไปใช้) - จุดที่ต้องดู: จำนวนแพ็กเก็ตที่ไคลเอนต์ประมวลผลไม่ทันและเหลือไว้ในแต่ละเฟรม และดีเลย์ตั้งแต่แพ็กเก็ตมาถึงจนนำไปใช้ในเกม เก็บเป็น log แล้วดูคู่กับจำนวนคนรอบตัว - สัญญาณว่าใช่: ในที่ที่คนเยอะ จำนวนแพ็กเก็ตที่เหลือและดีเลย์การนำไปใช้เพิ่มขึ้นเรื่อย ๆ ขณะที่ปิงและช่วงห่างการส่งของเซิร์ฟเวอร์ในเวลาเดียวกันปกติ - สัญญาณว่าไม่ใช่: ไม่มีดีเลย์การนำไปใช้ แต่ตัวแพ็กเก็ตเองมาถึงช้า: น่าจะเป็นช่วงเครือข่าย ถ้าเฟรมไทม์สูงขึ้นมาก น่าจะเป็น “ภาระการเรนเดอร์ตัวละครจำนวนมาก” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Actor Priority in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-priority-in-unreal-engine) · Epic Games · เมื่อแบนด์วิดท์ไม่พอ จะจัดลำดับความสำคัญตามระยะห่างจากผู้ดูและเวลาตั้งแต่ replicate ครั้งล่าสุด แทนการ replicate ทุก actor ทุกครั้ง - [Replication Graph in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/replication-graph-in-unreal-engine) · Epic Games · เกมที่มีผู้เชื่อมต่อและเป้าหมายที่ต้อง replicate จำนวนมาก (เช่น MMORPG) ต้องจัดกลุ่มตามตำแหน่งแล้วส่งเฉพาะเป้าหมายที่จำเป็น จึงจะเลี่ยงคอขวด CPU ของเซิร์ฟเวอร์ได้ #### cg-no-buffer · ไม่มี interpolation buffer หรือสั้นเกินไป · Missing/short interpolation buffer ถ้าวาดทันทีที่ได้รับแพ็กเก็ตจากเซิร์ฟเวอร์ จิตเตอร์ (ช่วงเวลาที่แพ็กเก็ตมาถึงไม่สม่ำเสมอ) จะแสดงออกมาบนจอตรง ๆ - ทำไม → ผลคือ → บนหน้าจอ: วาดตำแหน่งที่ได้รับทันที หรือบัฟเฟอร์สั้นกว่าจิตเตอร์ → หยุดนานเท่าที่แพ็กเก็ตมาช้า และกระโดดไปเท่าที่แพ็กเก็ตมาพร้อมกันหลายอัน → ตัวละครอื่นขยับแบบหยุดแวบเป็นระยะ - อาการ: กระตุก / ปัจจัย: จิตเตอร์ - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: มี interpolation buffer, ปรับความยาวบัฟเฟอร์อัตโนมัติตามสภาพเน็ต เซิร์ฟเวอร์: ใช้ lag compensation (ตัดสินผลด้วยการย้อนเวลา) เพื่อให้ตัดสินได้ถูกต้องแม้ผู้เล่นจะยิงโดยดูภาพในอดีตย้อนหลังไปเท่าความยาวบัฟเฟอร์ - ตัวเลขที่ควรรู้: ปกติจะตั้งบัฟเฟอร์ไว้ราว 2 เท่าของช่วงห่างที่เซิร์ฟเวอร์ส่งแพ็กเก็ต (ถ้ารับ 20 ครั้งใน 1 วินาทีก็คือ 100 ms) - บนกราฟ: สูงตลอดตั้งแต่แรก (ช่วงห่างการมาถึงของแพ็กเก็ต, จำนวนครั้งที่ interpolation buffer ว่าง) - จุดที่ต้องดู: บันทึกการกระจายของช่วงห่างการมาถึงของแพ็กเก็ตจากเซิร์ฟเวอร์ที่ไคลเอนต์ และจำนวนเฟรมที่หยุดหรือเปลี่ยนไปใช้ extrapolation เพราะไม่มีสแนปช็อตถัดไปให้ interpolate - สัญญาณว่าใช่: ความไม่สม่ำเสมอของช่วงห่างการมาถึงเกินความยาว interpolation buffer บ่อย ๆ และทุกครั้งที่เกิน บัฟเฟอร์จะว่างจนตัวละครอื่นหยุดแวบ เพิ่มบัฟเฟอร์แล้วลดลง - สัญญาณว่าไม่ใช่: บัฟเฟอร์พอแล้วแต่ยังหยุดแวบ: ตรวจว่าช่วงห่างที่เซิร์ฟเวอร์ส่งออกมาเองไม่สม่ำเสมอหรือไม่ (ทิกล่าช้า) - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: ยิ่งเพิ่มบัฟเฟอร์ภาพก็ยิ่งลื่น แต่ก็จะเห็นอีกฝ่ายเป็นภาพในอดีตนานขึ้นเท่ากัน การตัดสินการโจมตีจึงใช้ lag compensation ควบคู่ไปด้วย โดยเซิร์ฟเวอร์ย้อนเวลากลับไปตรวจที่ “อดีตที่ผู้เล่นคนนั้นเห็น” - แหล่งอ้างอิง: - [Interpolation and extrapolation (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/interpolation.html) · Unity · buffered interpolation ที่จงใจวาดช้าลงเพื่อรอแพ็กเก็ตที่มาช้า บัฟเฟอร์ยิ่งใหญ่ยิ่งแม่นยำ แต่ดีเลย์ก็เพิ่มขึ้นตาม - [Struct ClientTickRate (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/api/Unity.NetCode.ClientTickRate.html) · Unity · ค่าเริ่มต้นของ interpolation buffer คือ InterpolationTimeNetTicks = 2 (เท่ากับการส่งของเซิร์ฟเวอร์ 2 ครั้ง) - [Physics (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/physics.html) · Unity · lag compensation: เซิร์ฟเวอร์หาสถานะการชน (collision world) ที่ไคลเอนต์เห็นในทิกนั้น แล้วตัดสินว่าโดนหรือไม่ #### cg-extrap · extrapolation มากเกินไป (dead reckoning) · Over-extrapolation / dead reckoning ระหว่างที่แพ็กเก็ตไม่มา เกมแสดงตัวละครให้เคลื่อนที่ต่อด้วยความเร็วล่าสุด แล้วดึงกลับเมื่อรู้ว่าผิด - ทำไม → ผลคือ → บนหน้าจอ: แพ็กเก็ตขาดหาย เกมจึงให้เคลื่อนที่ต่อไปตามทิศทางและความเร็วล่าสุด → ความจริงอีกฝ่ายหยุดหรือเปลี่ยนทิศไปแล้ว → ตัวละครอีกฝ่ายเดินไปไกลแล้ววืดไปอยู่ตำแหน่งจริงทันที หรือเดินทะลุกำแพง ถ้าช่วงห่างการมาถึงของแพ็กเก็ตไม่สม่ำเสมอ จะวิ่งนำไปแล้วถูกดึงกลับซ้ำ ๆ จนดูเหมือนสั่น - อาการ: วาร์ป, กระตุก / ปัจจัย: แพ็กเก็ตหาย, จิตเตอร์ - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: จำกัดเวลา extrapolation (เช่น 200–250 ms), เมื่อผิดให้ค่อย ๆ ปรับเข้าหาตำแหน่งจริงอย่างนุ่มนวล - ตัวเลขที่ควรรู้: ถ้าความเร็ว 6 เมตรต่อวินาที ผิดไปแค่ 300 ms ก็คลาดไป 1.8 เมตร - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (เวลา extrapolation, ระยะที่ต้องแก้ตำแหน่ง) - จุดที่ต้องดู: บันทึกเวลาที่วาดตัวละครอื่นด้วย extrapolation และระยะที่แก้ตำแหน่งหลังแพ็กเก็ตใหม่มาถึง - สัญญาณว่าใช่: ทุกช่วงที่แพ็กเก็ตขาด เวลา extrapolation ยาวขึ้นโดยไม่มีเพดาน และหลังจากนั้นระยะแก้ตำแหน่งกลายเป็นหลายเมตร - สัญญาณว่าไม่ใช่: extrapolation ถูกตัดสั้นแล้วแต่ยังวาร์ป: แพ็กเก็ตหายหรือความหน่วงสูงเอง ให้ดูฝั่งเน็ตและเส้นทาง - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Interpolation and extrapolation (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/interpolation.html) · Unity · extrapolation ที่ให้เคลื่อนที่ต่อในทิศทางและความเร็วเดิมเมื่อสแนปช็อตถัดไปมาไม่ทันนั้นผิดบ่อย จึงต้องมีเพดาน (ค่าเริ่มต้นของ Unity คือ 20 ทิก หรือราว 1/3 วินาทีที่ 60 Hz) - [Peeking into VALORANT's Netcode](https://technology.riotgames.com/news/peeking-valorants-netcode) · Riot Games · ถ้าเดาเพื่อเติมข้อมูลที่มาช้าหรือหายไปแล้วผิด จะคลาดจากเซิร์ฟเวอร์ ตัวละครจึงกระโดดหรือถูกแก้ตำแหน่งแบบลื่นไถล #### cg-predict · client-side prediction ไม่ตรงกับเซิร์ฟเวอร์ · Prediction mismatch / reconciliation ไคลเอนต์ของเราแสดงการเคลื่อนที่ไปก่อน แต่ถ้าเซิร์ฟเวอร์คำนวณออกมาต่างกัน ตัวละครของเราจะโดนดึงกลับ - ทำไม → ผลคือ → บนหน้าจอ: ไคลเอนต์ขยับไปก่อนที่เซิร์ฟเวอร์จะยืนยัน (prediction) → เซิร์ฟเวอร์คำนวณการชน, ความเร็วการเคลื่อนที่ หรือบัฟต่างออกไป หรือไม่ได้รับคำสั่ง → พอผลยืนยันมาถึง ตัวละครของเราโดนดึงถอยหลัง - อาการ: ดีดกลับ / ปัจจัย: แพ็กเก็ตหาย, ความหน่วง - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ใช้โค้ดการเคลื่อนที่ชุดเดียวกับเซิร์ฟเวอร์, ส่งอินพุตซ้ำ, แก้ตำแหน่งอย่างนุ่มนวล เซิร์ฟเวอร์: ใช้โค้ดการเคลื่อนที่ชุดเดียวกับไคลเอนต์, กรองอินพุตที่มาซ้ำด้วยหมายเลขอินพุตแล้วประมวลผลครั้งเดียว - ตัวเลขที่ควรรู้: ระยะที่โดนดึงคือ “เวลาที่คลาดกัน × ความเร็วการเคลื่อนที่” แค่คำสั่งหายไปไม่กี่อันก็ 1–3 เมตร - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (จำนวนครั้งที่เซิร์ฟเวอร์แก้ตำแหน่ง (prediction ผิด)) - จุดที่ต้องดู: บันทึกจำนวนครั้งและระยะที่เซิร์ฟเวอร์ส่งมาแก้ตำแหน่ง Unreal ดูการแก้ด้วย ClientAdjustPosition ของเซิร์ฟเวอร์ ส่วน Unity Netcode for Entities นับจำนวนครั้งที่ย้อนกลับไปคำนวณใหม่เพราะ prediction ผิด - สัญญาณว่าใช่: การแก้ตำแหน่งกระจุกตรงเวลาที่มีคนแจ้งอาการดีดกลับ และระยะแก้ตำแหน่งใหญ่ซ้ำ ๆ กับบัฟ, ภูมิประเทศ หรือสกิลเคลื่อนที่บางอย่าง - สัญญาณว่าไม่ใช่: การแก้ตำแหน่งกระจุกเฉพาะตอนแพ็กเก็ตหายมาก: น่าจะเป็นแพ็กเก็ตอินพุตหาย (ฝั่งเน็ต) ถ้าไม่มีการแก้ตำแหน่ง แต่เห็นแค่ตัวละครอื่นโดนดึง น่าจะเป็น “extrapolation มากเกินไป (dead reckoning)” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Introduction to prediction (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/intro-to-prediction.html) · Unity · ไคลเอนต์และเซิร์ฟเวอร์ใช้โค้ดจำลองชุดเดียวกันในการทำ prediction ถ้าต่างจากสถานะบนเซิร์ฟเวอร์ (prediction ผิด) จะย้อนกลับไปคำนวณใหม่และเห็นการแก้ตำแหน่ง - [Use the command stream to handle user inputs (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/command-stream.html) · Unity · เพื่อรับมือแพ็กเก็ตหาย จะส่งอินพุตของไม่กี่ทิกก่อนหน้าซ้ำไปพร้อมอินพุตล่าสุด - [Understanding Networked Movement in the Character Movement Component for Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/understanding-networked-movement-in-the-character-movement-component-for-unreal-engine) · Epic Games · ไคลเอนต์ขยับก่อนแล้วเซิร์ฟเวอร์จำลองการเคลื่อนที่เดียวกันซ้ำ ถ้าตำแหน่งต่างกันจะแก้ด้วย ClientAdjustPosition แล้วนำการเคลื่อนที่ที่บันทึกไว้มาใช้ใหม่ #### cg-fixed-step · fixed timestep ไล่ตามไม่ทันจนบานปลาย · Fixed-timestep catch-up / spiral of death หลังหยุดไปครั้งหนึ่ง เกมเร่งคำนวณส่วนที่ตามหลังรวดเดียว แล้วการคำนวณนั้นเองก็ทำให้ตามหลังอีก - ทำไม → ผลคือ → บนหน้าจอ: เกมรันการจำลองด้วยช่วงเวลาคงที่ แล้วเกิดหยุดไปครั้งหนึ่ง → คำนวณ step ที่ตามหลังทั้งหมดรวดเดียวในเฟรมเดียว → เฟรมยาวเกิดต่อกันเป็นพรวนจนภาพกระตุก หรือชนเพดานแล้วโลกในเกมช้าลง - อาการ: กระตุก, กรอเร็ว, สโลว์โมชั่น / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: จำกัดการไล่ตามต่อเฟรม, จัดการเวลาที่เหลือด้วย interpolation - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (เฟรมไทม์, จำนวน fixed step ต่อเฟรม) - จุดที่ต้องดู: ดูใน profiler ของ development build ว่าในหนึ่งเฟรม fixed step ทำงานกี่ครั้ง (Unity คือจำนวน marker ของขั้น FixedUpdate เช่น FixedBehaviourUpdate) คู่กับเฟรมไทม์ - สัญญาณว่าใช่: หลังเฟรมยาวหนึ่งเฟรม มีเฟรมยาวที่รัน step หลายครั้งตามมาเป็นพรวน และเมื่อชนเพดาน (Maximum Allowed Timestep ของ Unity) เวลาในเกมจะเดินช้ากว่าความจริง - สัญญาณว่าไม่ใช่: เฟรมยาวเกิดครั้งเดียวแล้วจบ: น่าจะเป็น “เฟรมไทม์พุ่ง” หรือ “GC ฝั่งไคลเอนต์” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: การคำนวณฟิสิกส์ของ Unity (FixedUpdate) คือตัวอย่างที่ชัดของ fixed step (ค่าเริ่มต้น 0.02 วินาที หรือ 50 ครั้งใน 1 วินาที) Maximum Allowed Timestep ในการตั้งค่า Time (เวลาสูงสุดที่ไล่ตามได้ในหนึ่งเฟรม ค่าเริ่มต้นราว 0.33 วินาที) คือเพดานการไล่ตาม ถ้าเฟรมหนึ่งยาวกว่านี้ เวลาส่วนที่เกินจะถูกทิ้ง นาฬิกาเกมจึงช้ากว่าความจริงไปเท่านั้น - แหล่งอ้างอิง: - [Set fixed timestep to optimize physics simulation frequency](https://docs.unity3d.com/Manual/physics-optimization-cpu-frequency.html) · Unity · ค่าเริ่มต้นของ Fixed Timestep คือ 0.02 วินาที (50 ครั้งใน 1 วินาที) ถ้าเฟรมยาว ต้องรัน physics step หลายครั้งในเฟรมเดียว ภาระจึงเพิ่มขึ้น - [Handling variation in time](https://docs.unity3d.com/Manual/time-handling-variations.html) · Unity · ค่าเริ่มต้นของ Maximum Allowed Timestep คือ 1/3 วินาที (0.3333333) ต่อให้หยุดไป 1 วินาที เวลาในเกมก็เดินไปแค่ 0.333 วินาที เป็นเพดานที่กันวงจรที่ step สำหรับไล่ตามทำให้ช้าลงไปอีก - [Profiler markers reference](https://docs.unity3d.com/Manual/profiler-markers.html) · Unity · FixedBehaviourUpdate: ช่วงที่ MonoBehaviour.FixedUpdate ทำงาน, marker ของฟิสิกส์ถูกเรียกในขั้น FixedUpdate #### cg-clock · ซิงก์นาฬิกาคลาดเคลื่อน · Clock sync error ถ้าเวลาเซิร์ฟเวอร์ที่ไคลเอนต์ประมาณไว้ผิด จังหวะ interpolation และการตัดสินคูลดาวน์จะคลาดกัน - ทำไม → ผลคือ → บนหน้าจอ: ตั้งเวลาให้ตรงกับเซิร์ฟเวอร์แค่ครั้งเดียวตอนเชื่อมต่อ แม้ปิงเปลี่ยนก็ไม่ปรับ → จังหวะที่ใช้ interpolate และเวลาที่คูลดาวน์หมดคลาดจากเซิร์ฟเวอร์ → อีกฝ่ายหยุดแวบเป็นบางครั้ง คูลดาวน์หมดแล้วแต่สกิลถูกปฏิเสธ - อาการ: กระตุก, กดไม่ติด/โรลแบ็ค / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ซิงก์เวลาเป็นระยะ (วัดเวลาไปกลับแล้วชดเชย), ค่อย ๆ ปรับเข้าหาแทนการเปลี่ยนกะทันหัน, วัดเวลาที่ผ่านไปด้วย monotonic clock แทนเวลาของ PC - บนกราฟ: ค่อย ๆ สูงขึ้น (ค่าคลาดเคลื่อนของเวลาเซิร์ฟเวอร์ที่ประมาณไว้) - จุดที่ต้องดู: บันทึกผลต่างระหว่างเวลาเซิร์ฟเวอร์ที่ไคลเอนต์ประมาณไว้กับเวลาเซิร์ฟเวอร์ (หมายเลขทิก) ที่เซิร์ฟเวอร์ใส่มาในแพ็กเก็ต เป็นระยะ - สัญญาณว่าใช่: ค่าคลาดเคลื่อนโตขึ้นเรื่อย ๆ หลังเชื่อมต่อ หรือกระโดดทีเดียวตอนที่นาฬิกา PC ถูกปรับ และช่วงนั้นมีการแจ้งสกิลถูกปฏิเสธหรือหยุดแวบเพิ่มขึ้น - สัญญาณว่าไม่ใช่: ค่าคลาดเคลื่อนยังเล็กอยู่แต่สกิลถูกปฏิเสธ: ให้ดูฝั่งการตัดสินผลของเซิร์ฟเวอร์และความหน่วง - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: ถ้าวัดเวลาที่ผ่านไปด้วยวันที่และเวลาของ PC (wall clock) ทันทีที่ Windows ปรับนาฬิกาตามเวลาอินเทอร์เน็ตหรือผู้ใช้เปลี่ยนนาฬิกา เวลาในเกมจะกระโดด เวลาที่ผ่านไปต้องวัดด้วยนาฬิกาที่ไม่ย้อนกลับ (monotonic clock เช่น Stopwatch) - แหล่งอ้างอิง: - [RFC 5905: Network Time Protocol Version 4: Protocol and Algorithms Specification](https://www.rfc-editor.org/rfc/rfc5905) · IETF · วิธีคำนวณดีเลย์ไปกลับและค่าคลาดเคลื่อนของนาฬิกาจากเวลาสี่จุดของคำขอและการตอบกลับ - [Acquiring high-resolution time stamps](https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps) · Microsoft · QueryPerformanceCounter (ที่ Stopwatch ใช้) เป็นนาฬิกาสำหรับวัดเวลาที่ผ่านไปซึ่งไม่ซิงก์กับเวลาภายนอก ใช้เวลาของระบบเฉพาะเมื่อต้องการเวลา UTC - [Time synchronization (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/time-synchronization.html) · Unity · ประมาณเวลาเซิร์ฟเวอร์จากเวลาไปกลับ แล้วปรับเข้าหาโดยค่อย ๆ ปรับความเร็วการเดินของเวลา แทนการเปลี่ยนเวลาทีเดียวมาก ๆ #### cg-float-time · เวลาแบบ float สูญเสียความแม่นยำ · Float time precision loss on long sessions ถ้าเก็บเวลาในเกมเป็นทศนิยมความแม่นยำต่ำ (float) ยิ่งเปิดเกมไว้นาน ความละเอียดของเวลา (ผลต่างของเวลาที่เล็กที่สุดที่แยกได้) ก็ยิ่งลดลง การเคลื่อนไหวและเอฟเฟกต์จึงสั่น - ทำไม → ผลคือ → บนหน้าจอ: สะสมเวลาที่ผ่านไปตั้งแต่เปิดเกมเป็น float หรือส่งค่านั้นให้ shader ตรง ๆ → ยิ่งเปิดไว้นาน ผลต่างที่เล็กที่สุดที่ float แสดงได้ก็ยิ่งใหญ่ขึ้น → เฉพาะไคลเอนต์ที่เปิดทิ้งไว้หลายวัน ตัวละคร, แอนิเมชัน และเอฟเฟกต์ที่เคลื่อนไหลสั่นระริก แต่เปิดเกมใหม่แล้วปกติ - อาการ: กระตุก / ปัจจัย: จิตเตอร์ - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เก็บเวลาที่ผ่านไปเป็น double (64 บิต) หรือจำนวนเต็ม, วนค่าเวลาที่ส่งให้ shader กลับเป็นรอบ, ทดสอบอัตโนมัติแบบรันยาวหลายวัน - ตัวเลขที่ควรรู้: float 32 บิตมีเลขนัยสำคัญราว 7 หลัก ถ้าเปิดไว้หนึ่งวัน (ราว 86,400 วินาที) ความละเอียดของเวลาจะเหลือราว 8 ms หรือราวครึ่งเฟรมที่ 60 FPS (16.7 ms) และถ้าเปิดไว้หนึ่งสัปดาห์จะราว 60 ms ซึ่งมากกว่าหนึ่งเฟรม - บนกราฟ: ค่อย ๆ สูงขึ้น (การแจ้งอาการสั่นแยกตามระยะเวลาที่เปิดเกมไว้) - จุดที่ต้องดู: ขอระยะเวลาที่เปิดไคลเอนต์ไว้มาพร้อมการแจ้งอาการสั่น แล้วเทียบก่อนและหลังรีสตาร์ต ฝั่งพัฒนาทดสอบโดยตั้งค่าเวลาเริ่มเกมเป็นค่าที่ผ่านไปแล้วหลายวัน - สัญญาณว่าใช่: สั่นเฉพาะไคลเอนต์ที่เปิดไว้หลายวัน รีสตาร์ตแล้วหาย และยิ่งเปิดไว้นานยิ่งหนัก - สัญญาณว่าไม่ใช่: สั่นตั้งแต่เพิ่งเปิด: น่าจะเป็น “ไม่มี interpolation buffer หรือสั้นเกินไป” หรือ “ความละเอียดของตัวจับเวลา” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: เจอบ่อยเป็นพิเศษในเกม MMO มือถือที่ผู้เล่นเปิดออโต้ทิ้งไว้หลายวันโดยไม่ปิด Time.time ของ Unity ก็เป็น float เช่นกัน Unity จึงมี Time.timeAsDouble ที่เป็น double แยกไว้และแนะนำให้ใช้ตัวนี้ - แหล่งอ้างอิง: - [Time.timeAsDouble](https://docs.unity3d.com/ScriptReference/Time-timeAsDouble.html) · Unity · Time.time เวอร์ชัน double ยิ่งเปิดนานยิ่งแม่นยำกว่า float จึงแนะนำให้ใช้ตัวนี้ในกรณีส่วนใหญ่ - [Floating-point numeric types (C# reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types) · Microsoft · ความแม่นยำของ float ราว 6–9 หลัก, double ราว 15–17 หลัก #### cg-vsync · V-Sync และคิวเรนเดอร์ · V-Sync, render queue ระหว่างที่ GPU เก็บเฟรมที่วาดเสร็จไว้ในคิวหลายเฟรม แล้วค่อยส่งออกตามรอบของจอ อินพุตจะช้าลง - ทำไม → ผลคือ → บนหน้าจอ: ไดรเวอร์กราฟิกเก็บเฟรมไว้ล่วงหน้าในคิว 1–3 เฟรม → อินพุตต้องใช้เวลานานขึ้นเท่านั้นกว่าจะแสดงบนจอ → ปิงต่ำแต่การควบคุมหนืดและตอบสนองช้า - อาการ: อินพุตดีเลย์, กระตุก / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: รองรับโหมด low latency, ลดคิวเฟรม, มีตัวเลือกจำกัดเฟรมเรตให้ต่ำกว่าอัตรารีเฟรชเล็กน้อย, บนมือถือเปิดฟีเจอร์ frame pacing - งานฝั่งภายนอก: แนะนำให้ผู้เล่นใช้จอ VRR คู่กับการจำกัดเฟรมเรตให้ต่ำกว่าอัตรารีเฟรชเล็กน้อย, แนะนำให้เปิดโหมด low latency ในไดรเวอร์กราฟิก - ตัวเลขที่ควรรู้: ที่ 60 Hz เฟรมละ 16.7 ms ถ้า CPU เร็วกว่า GPU หรือรอบของจอ จนคิวสามเฟรม (ค่าเริ่มต้นของ DirectX 11) เต็ม จะเพิ่มขึ้นมา 50 ms ใน V-Sync แบบ double buffer เฟรมที่ใช้ 17 ms ต้องรอจนถึงรอบรีเฟรชจอถัดไป (33.3 ms) และระหว่างนั้นเฟรมก่อนหน้าจะแสดงซ้ำอีกครั้ง - บนกราฟ: สูงตลอดตั้งแต่แรก (ดีเลย์จากอินพุตถึงจอ) - จุดที่ต้องดู: เทียบค่า MsClickToPhotonLatency, MsAllInputToPhotonLatency (ตั้งแต่อินพุตเมาส์/คีย์บอร์ดจนส่งภาพออกจอ) และ DisplayLatency ของ PresentMon ขณะสลับเปิดปิด V-Sync, โหมด low latency และการจำกัดเฟรมเรต MsPCLatency (ตั้งแต่ PC รับอินพุตจนส่งภาพออกจอ) จะถูกบันทึกเฉพาะเมื่อเกมส่ง PC Latency event ออกมา - สัญญาณว่าใช่: ตอนเปิด V-Sync หรือไม่จำกัดเฟรมเรต ดีเลย์นี้เพิ่มขึ้นหนึ่งถึงสองเฟรม (หลายสิบ ms) และลดลงเมื่อใช้โหมด low latency หรือจำกัดเฟรมเรตให้ต่ำกว่าอัตรารีเฟรชเล็กน้อย ปิงเท่าเดิม - สัญญาณว่าไม่ใช่: ดีเลย์ภายใน PC ต่ำแต่การควบคุมยังช้า: น่าจะเป็น “ดีเลย์จากจอ, อุปกรณ์อินพุต และ frame generation” ถ้าปิงสูง ให้ดูฝั่งเครือข่าย - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: V-Sync (vertical sync) คือการตั้งค่าที่ส่งเฟรมใหม่ออกไปเฉพาะจังหวะที่จอเปลี่ยนภาพ ภาพฉีก (screen tearing) จะหายไป แต่อินพุตจะช้าลงเท่าเวลาที่ต้องรอจังหวะนั้น และถ้า FPS ต่ำกว่า 60 จะสลับไปมาระหว่าง 60 กับ 30 จนภาพกระตุก จอ VRR (อัตรารีเฟรชแบบแปรผัน) จะเปลี่ยนภาพตามจังหวะที่เฟรมพร้อม จึงลดการรอนี้ได้ บนมือถือก็เกิดแบบเดียวกัน ถ้าเกม 30 FPS ส่งเฟรมออกให้สม่ำเสมอกับจอ 60 Hz ไม่ได้ ค่าเฉลี่ยจะยังเป็น 30 FPS แต่แต่ละเฟรมอยู่บนจอนานไม่เท่ากัน เช่น 49, 16, 33 ms จนภาพกระตุก (ตัวอย่างจากเอกสารนักพัฒนา Android) ลดได้ด้วยไลบรารี frame pacing (การทำให้ช่วงห่างการส่งเฟรมสม่ำเสมอ) ของ Android หรือตัวเลือกแบบเดียวกันในเอนจิน - แหล่งอ้างอิง: - [IDXGIDevice1::SetMaximumFrameLatency](https://learn.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgidevice1-setmaximumframelatency) · Microsoft · ค่าเริ่มต้นของจำนวนเฟรมที่ไดรเวอร์เก็บในคิวได้คือ 3 (1–16) - [Reduce latency with DXGI 1.3 swap chains](https://learn.microsoft.com/en-us/windows/uwp/gaming/reduce-latency-with-dxgi-1-3-swap-chains) · Microsoft · Present ถูกบล็อกจนกว่าคิวจะว่าง ทำให้ต้องรอเกือบอีกหนึ่งเฟรมตั้งแต่วาดเสร็จจนแสดงผล ลดได้ด้วย waitable swap chain - [Frame Pacing library](https://developer.android.com/games/sdk/frame-pacing) · Android (Google) · บนจอ 60 Hz ถ้าไม่มีเฟรมใหม่จะแสดงเฟรมก่อนหน้าซ้ำ ตัวอย่างเกม 30 FPS ที่เฟรมไทม์แกว่งไม่สม่ำเสมอ เช่น 49, 16, 33 ms - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · MsPCLatency (ตั้งแต่ PC รับอินพุตจนส่งภาพออกจอ), MsClickToPhotonLatency (คลิกเมาส์ถึงจอ), MsAllInputToPhotonLatency (อินพุตคีย์บอร์ด/เมาส์ถึงจอ), DisplayLatency (ตั้งแต่ submit เฟรมจนส่งออกไปที่จอ) - [PresentMon Console Application (README-ConsoleApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-ConsoleApplication.md) · Intel · MsPCLatency จะถูกบันทึกเมื่อแอปส่ง PC Latency event ออกมาเท่านั้น (--track_pc_latency), MsAllInputToPhotonLatency ยึดอินพุตคีย์บอร์ดและเมาส์ #### cg-leak · หน่วยความจำรั่วฝั่งไคลเอนต์ · Client memory leak ยิ่งเปิดไว้นาน หน่วยความจำยิ่งเพิ่ม เกมช้าลงเรื่อย ๆ แล้วสุดท้ายก็ถูกบังคับปิด - ทำไม → ผลคือ → บนหน้าจอ: texture, UI และเอฟเฟกต์ไม่ถูกปล่อยคืนเมื่อย้ายแมพไปมา → GC ทำงานถี่ขึ้น และหน่วยความจำของ OS ไม่พอจนเกิด swap → เล่นไปหลายชั่วโมงแล้วกระตุกขึ้นเรื่อย ๆ จนถูกบังคับปิด (ผู้เล่นเห็นเหมือนหลุด) - อาการ: กระตุก, หลุด / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: วัดปริมาณการใช้หน่วยความจำตอนย้ายแมพ, หาและแก้ texture, UI และเอฟเฟกต์ที่ไม่ถูกปล่อยคืน, ทดสอบอัตโนมัติแบบรันยาว (soak test) - บนกราฟ: ค่อย ๆ สูงขึ้น (หน่วยความจำของโปรเซสเกม) - จุดที่ต้องดู: บันทึก Process(เกม)\Private Bytes ด้วย Performance Monitor หลายชั่วโมง บนมือถือดูสาเหตุการปิด (REASON_LOW_MEMORY) จาก ApplicationExitInfo ของ Android และรายงาน jetsam ของ iOS - สัญญาณว่าใช่: หน่วยความจำเพิ่มขึ้นทุกครั้งที่ย้ายแมพและไม่ลดลง ยิ่งเปิดไว้นาน อาการกระตุกและการถูกบังคับปิดยิ่งเพิ่ม - สัญญาณว่าไม่ใช่: หน่วยความจำคงที่ แต่ยิ่งเปิดนานยิ่งสั่นอย่างเดียว: น่าจะเป็น “เวลาแบบ float สูญเสียความแม่นยำ” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: มือถือส่วนใหญ่ประคองไว้ด้วยการบีบอัดหน่วยความจำ ถ้ายังไม่พออีก OS จะปิดเกมทันที (เด้ง) เครื่องที่ RAM น้อยยิ่งโดนปิดก่อน - แหล่งอ้างอิง: - [Memory allocation among processes](https://developer.android.com/topic/performance/memory-management) · Android (Google) · Android ประคองไว้ด้วยการบีบอัดหน่วยความจำลง zRAM ถ้าไม่พอ low memory killer จะปิดโปรเซส ถ้าแอปที่เปิดอยู่บนหน้าจอถูกปิดจะดูเหมือนแครช - [Identifying high-memory use with jetsam event reports](https://developer.apple.com/documentation/xcode/identifying-high-memory-use-with-jetsam-event-reports) · Apple · ถ้าแรงกดดันด้านหน่วยความจำไม่ลดลง iOS จะบังคับปิดแอป (jetsam) และแอปที่ใช้หน่วยความจำเกินขีดจำกัดของตัวเองจะตกเป็นเป้าให้ถูกปิด - [Find user-mode memory leaks with Performance Monitor (PerfMon)](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/using-performance-monitor-to-find-a-user-mode-memory-leak) · Microsoft · บันทึก Process > Private Bytes (หน่วยความจำเฉพาะที่โปรเซสจัดสรร) และ Virtual Bytes เป็นเวลานาน ถ้าเพิ่มขึ้นอย่างเดียวคือหน่วยความจำรั่ว - [ApplicationExitInfo](https://developer.android.com/reference/android/app/ApplicationExitInfo) · Android (Google) · REASON_LOW_MEMORY: low memory killer ของระบบปิดโปรเซสของแอป (เครื่องที่ไม่รองรับจะรายงานเป็น REASON_SIGNALED/SIGKILL) #### cg-crash · ไคลเอนต์แครช · Client crash เกมปิดตัวลงเพราะข้อผิดพลาดที่ไม่ได้จัดการ ผู้เล่นเห็นเหมือนหลุด แต่เซิร์ฟเวอร์ปกติ - ทำไม → ผลคือ → บนหน้าจอ: null reference, หน่วยความจำไม่พอ, ไดรเวอร์กราฟิกผิดพลาด → โปรเซสเกมถูกบังคับปิด → มีคนแจ้งว่า “เกมเด้ง” ขณะที่คนอื่นในเวลาเดียวกันปกติ - อาการ: หลุด / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: เก็บ crash report, ทำสถิติแยกตามเครื่องและไดรเวอร์, แก้ข้อผิดพลาดที่เกิดบ่อยที่สุดก่อน - งานฝั่งภายนอก: ถ้ากระจุกที่ไดรเวอร์กราฟิกบางเวอร์ชัน แนะนำให้ผู้เล่นอัปเดตไดรเวอร์ - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนการแครช (แยกตามเครื่อง/ไดรเวอร์กราฟิก/บิลด์)) - จุดที่ต้องดู: ดู crash report และอัตราการแครชใน Android vitals แยกตามเครื่อง, ไดรเวอร์ และบิลด์ บน PC ของผู้เล่นดู Event ID 1000 ใน Application log ของ Event Viewer (ชื่อโมดูลที่เกิดข้อผิดพลาด) และบันทึก “Display driver stopped responding and has recovered” - สัญญาณว่าใช่: มีบันทึกการแครชตรงเวลาที่แจ้งว่าหลุด ขณะที่ผู้เล่นอื่นในเซิร์ฟเวอร์เดียวกันเวลาเดียวกันปกติ กระจุกที่บางเครื่อง, บางเวอร์ชันไดรเวอร์ หรือบางโมดูล - สัญญาณว่าไม่ใช่: ไม่มีบันทึกการแครชแต่การเชื่อมต่อหลุด: น่าจะเป็น “NAT mapping หมดอายุ” หรือฝั่งเน็ต - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Crashes](https://developer.android.com/topic/performance/vitals/crash) · Android (Google) · แครชคือการที่แอปปิดตัวโดยไม่คาดคิดเพราะ exception หรือ signal ที่ไม่ได้จัดการ (เช่น SIGSEGV) รวบรวมได้จาก Android vitals ใน Play Console - [WDDM Support for Timeout Detection and Recovery (TDR)](https://learn.microsoft.com/en-us/windows-hardware/drivers/display/timeout-detection-and-recovery) · Microsoft · ถ้า GPU ทำงานไม่เสร็จภายในค่าเริ่มต้น 2 วินาที Windows จะรีเซ็ตไดรเวอร์กราฟิกและ GPU - [The application or service crashing behavior troubleshooting guidance](https://learn.microsoft.com/en-us/troubleshoot/windows-server/performance/troubleshoot-application-service-crashing-behavior) · Microsoft · Event ID 1000 ใน Application log คือบันทึกการแครชจริง มีชื่อแอปที่ผิดพลาดและชื่อโมดูลที่ผิดพลาด (Faulting module name) #### cg-anticheat · การตรวจของโมดูลความปลอดภัยเกม (anti-cheat) · Anti-cheat scan and heartbeat โมดูลความปลอดภัยที่ทำงานคู่กับเกมเพื่อกันโปรโกงจะตรวจสอบเป็นระยะ ถ้าการตรวจหนัก หรือ heartbeat (สัญญาณยืนยันว่ายังทำงานอยู่ที่ส่งเป็นระยะ) ที่รับส่งกับเซิร์ฟเวอร์ความปลอดภัยมาช้า เกมจะกระตุกหรือหลุด - ทำไม → ผลคือ → บนหน้าจอ: โมดูลความปลอดภัยตรวจหน่วยความจำของเกม, โปรแกรมที่กำลังรัน และไดรเวอร์เป็นระยะ → ระหว่างตรวจ เธรดเกมหยุด หรือ heartbeat ส่งไปไม่ทันเวลา → หยุดแวบเป็นจังหวะสม่ำเสมอ ถ้าหนักจะหลุดพร้อมข้อความแจ้งข้อผิดพลาดด้านความปลอดภัย - อาการ: กระตุก, ค้าง, หลุด / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: เป็นรอบสม่ำเสมอ, หลังล็อกอิน/หลังปิดปรับปรุง, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ย้ายการตรวจที่หนักออกจากเธรดเกมและแบ่งรันทีละน้อย, เทียบสถิติอาการกระตุกและเกมเด้งแยกตามเวอร์ชันโมดูลความปลอดภัย (ถ้ากระจุกหลังอัปเดตให้ส่งต่อให้ผู้ผลิตโมดูล) เซิร์ฟเวอร์: ยอมให้ heartbeat มาช้าได้หนึ่งถึงสองครั้ง - ตัวเลขที่ควรรู้: การตรวจแบบเบาปกติใช้ไม่ถึง 1 ms แต่การตรวจแบบหนักที่รันบนเธรดเกมอาจกินเวลาครั้งละหลายสิบถึงหลายร้อย ms ขึ้นกับการ implement - บนกราฟ: พุ่งเป็นรอบ (เฟรมไทม์, จำนวนครั้งที่ anti-cheat เตะผู้เล่นออก) - จุดที่ต้องดู: วัดช่วงห่างของจุดที่พุ่งในเฟรมไทม์จาก PresentMon และรวมสาเหตุที่ anti-cheat เตะผู้เล่นออกที่เซิร์ฟเวอร์ได้รับ (EOS คือ AuthenticationFailed / Authentication Timed Out ฯลฯ ใน ClientActionReason) แยกตามเวอร์ชันโมดูลความปลอดภัยและสเปก - สัญญาณว่าใช่: หยุดสั้น ๆ ซ้ำเป็นจังหวะสม่ำเสมอโดยไม่เกี่ยวกับสถานการณ์ในเกม และหลังอัปเดตโมดูลความปลอดภัย บางสเปกมีอาการกระตุกและถูกเตะเพราะยืนยันตัวตนไม่ทันเวลาเพิ่มขึ้น - สัญญาณว่าไม่ใช่: จังหวะเดียวกันทุกสเปกโดยไม่เกี่ยวกับเวอร์ชันโมดูลความปลอดภัย: น่าจะเป็น “GC ฝั่งไคลเอนต์” หรือ “โปรเซสเบื้องหลังแย่ง CPU” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: โมดูลความปลอดภัยฝังลึกอยู่ใน OS ในรูปไดรเวอร์ จึงอาจชนกับแอนตี้ไวรัส, โอเวอร์เลย์ หรือโมดูลความปลอดภัยของเกมอื่นได้ ถ้าการแจ้งอาการกระตุกหรือเกมเด้งกระจุกอยู่ที่บางสเปกหลังอัปเดตโมดูลความปลอดภัย ให้สงสัยตัวนี้ก่อน - แหล่งอ้างอิง: - [Using the Anti-Cheat Interfaces](https://dev.epicgames.com/docs/epic-online-services/trust-and-safety/anti-cheat-interfaces/using-anti-cheat) · Epic Games · ถ้าเซิร์ฟเวอร์ไม่ได้รับข้อความ anti-cheat จากไคลเอนต์ภายในเวลาที่กำหนด (RegisterTimeout) จะเตะออกด้วยเหตุผลยืนยันตัวตนหมดเวลา (สาเหตุที่พบบ่อยคือไคลเอนต์ที่หยุดอยู่ระหว่างโหลด) ถ้าปัญหามาจากการอัปเดตโมดูลล่าสุดให้ย้อนกลับไปใช้โมดูลเวอร์ชันก่อน - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · อัดเวลาของแต่ละเฟรมด้วย FrameTime (เวลา CPU ระหว่างเฟรม) ### L2 OS และอุปกรณ์ฝั่งไคลเอนต์ (15 สาเหตุ) #### co-background · โปรเซสเบื้องหลังแย่ง CPU · Background CPU contention เมื่อการสแกนของแอนตี้ไวรัส, Windows Update, โปรแกรมสตรีม หรือวิดีโอในเบราว์เซอร์ยึดคอร์ไว้ เธรดเกมจะไม่ได้รับ CPU และต้องรอ - ทำไม → ผลคือ → บนหน้าจอ: โปรแกรมอื่นยึดคอร์ CPU ไว้นาน → เธรดเกมต้องรอคิว CPU → เฟรมช้า และการประมวลผลแพ็กเก็ตที่รับมาก็ช้าตาม - อาการ: กระตุก, กรอเร็ว / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, เป็นรอบสม่ำเสมอ - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ปรับ priority ของเธรดเกม, บันทึกอัตราการใช้ CPU ทั้งเครื่องไว้ใน log ตอนที่เกิดอาการกระตุกด้วย เพื่อแยกว่าเป็นเพราะโปรแกรมอื่นหรือไม่ - งานฝั่งภายนอก: แนะนำให้ผู้เล่นเปิด Game Mode ของ Windows และปิดโปรแกรมที่ไม่จำเป็นระหว่างเล่น (การสแกนของแอนตี้ไวรัส, Windows Update, โปรแกรมสตรีม และวิดีโอในเบราว์เซอร์) - ตัวเลขที่ควรรู้: ปกติ Windows จะให้แต่ละเธรดใช้คอร์ครั้งละหลักหน่วยถึงหลักสิบ ms แค่โดนเลื่อนคิว CPU ครั้งเดียวก็หายไปหนึ่งเฟรม - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (อัตราการใช้ CPU ทั้งเครื่อง, เฟรมไทม์) - จุดที่ต้องดู: บันทึกคอลัมน์ CPU ในแท็บ Processes ของ Task Manager และ Processor Information(_Total)\% Processor Time ใน Performance Monitor คู่กับเฟรมไทม์จาก PresentMon ถ้าสงสัยแอนตี้ไวรัส ให้อัดด้วย New-MpPerformanceRecording แล้วดูไฟล์และโปรเซสที่ใช้เวลาสแกนนานด้วย Get-MpPerformanceReport - สัญญาณว่าใช่: ตอนที่กระตุก การใช้ CPU ของโปรแกรมอื่น (การสแกนของแอนตี้ไวรัส, อัปเดต, โปรแกรมสตรีม) พุ่งขึ้น หรือไฟล์ในโฟลเดอร์เกมขึ้นไปอยู่อันดับต้นของเวลาสแกน ปิดโปรแกรมนั้นหรือตั้งเป็นข้อยกเว้นแล้วหายไป - สัญญาณว่าไม่ใช่: อัตราการใช้ CPU ต่ำแต่ทั้งจอหยุดแวบและเสียงแตกซ่า: น่าจะเป็น “โหมดประหยัดพลังงานของ NIC และปัญหาไดรเวอร์” (DPC latency) - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: Windows ให้ priority กับโปรแกรมของหน้าต่างที่อยู่ด้านหน้า (foreground) มากขึ้นเล็กน้อย แต่ถ้ามีงานมากกว่าจำนวนคอร์ เกมก็ต้องรอ แอนตี้ไวรัสเข้ามาแทรกผ่าน “การตรวจแบบเรียลไทม์” บ่อยกว่าการใช้ CPU เสียอีก เพราะจะสแกนทุกครั้งที่เกมเปิดไฟล์ ทำให้การหยุดตอนอ่านแอสเซ็ตนานขึ้น - แหล่งอ้างอิง: - [Multitasking](https://learn.microsoft.com/en-us/windows/win32/procthread/multitasking) · Microsoft · Windows ให้ time slice แก่แต่ละเธรด เมื่อใช้หมดจะสลับไปเธรดถัดไป time slice ยาวราว 20 ms (ต่างกันตาม OS และ CPU) - [Priority Boosts](https://learn.microsoft.com/en-us/windows/win32/procthread/priority-boosts) · Microsoft · โปรเซสของหน้าต่างที่อยู่ด้านหน้า (foreground) จะได้รับการยก priority ให้ไม่ต่ำกว่าโปรเซสเบื้องหลัง - [About regular quick and full scans with Microsoft Defender Antivirus](https://learn.microsoft.com/en-us/defender-endpoint/schedule-antivirus-scans) · Microsoft · การป้องกันแบบเรียลไทม์จะสแกนทุกครั้งที่เปิดและปิดไฟล์ และทุกครั้งที่เปิดโฟลเดอร์ - [Network-Related Performance Counters](https://learn.microsoft.com/en-us/windows-server/networking/technologies/network-subsystem/net-sub-performance-counters) · Microsoft · ตัวนับ Processor Information: % Processor Time - [Performance analyzer for Microsoft Defender Antivirus](https://learn.microsoft.com/en-us/defender-endpoint/tune-performance-defender-antivirus) · Microsoft · อัดด้วย New-MpPerformanceRecording แล้วใช้ Get-MpPerformanceReport ดูไฟล์, path และโปรเซสอันดับต้น ๆ ที่มีผลต่อเวลาสแกน - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · อัดเวลาของแต่ละเฟรมด้วย FrameTime (เวลา CPU ระหว่างเฟรม) #### co-power · โหมดประหยัดพลังงานและ thermal throttling · Power saving, thermal throttling โหมดแบตเตอรี่ของโน้ตบุ๊ก, โหมดประหยัดพลังงานของมือถือ และความร้อนของเครื่อง ทำให้ CPU และ GPU ช้าลง จุดสังเกตของความร้อนคือช่วงแรกปกติดี แล้วค่อยช้าลงหลังเล่นไปสักพัก - ทำไม → ผลคือ → บนหน้าจอ: อยู่ในโหมดแบตเตอรี่หรือประหยัดพลังงาน หรือเครื่องร้อน → ลดคล็อกของ CPU และ GPU ลง 30–50% แล้วแต่เครื่อง → โหมดประหยัดพลังงานเป็นตั้งแต่เปิดเกม ส่วนความร้อนจะเริ่มหลังเล่นไปไม่กี่นาทีถึงราว 20 นาที FPS ตกและกระตุก - อาการ: กระตุก, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น, ตลอดเวลา - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ปรับกราฟิกอัตโนมัติ, คุมความร้อนด้วยการจำกัดเฟรมเรต, ดูระดับความร้อนที่ OS แจ้ง (thermalState ของ iOS และ thermal API ของ Android) แล้วลดตัวเลือกกราฟิกล่วงหน้า, บนโน้ตบุ๊กที่มีชิปกราฟิกสองตัว ระบุในไฟล์ executable ให้ใช้การ์ดจอแยก (export NvOptimusEnablement และ AmdPowerXpressRequestHighPerformance) - งานฝั่งภายนอก: แนะนำให้ผู้เล่นปิดโหมดประหยัดพลังงาน และเสียบสายชาร์จเมื่อเล่นบนโน้ตบุ๊ก, ถ้าแจ้งมาว่า “โน้ตบุ๊กสเปกดีแต่ FPS ต่ำ” ให้ตรวจว่าเกมรันบนชิปกราฟิกตัวไหน แล้วแนะนำให้ตั้งเกมให้ใช้ GPU ประสิทธิภาพสูงในการตั้งค่ากราฟิกของ Windows - บนกราฟ: ค่อย ๆ สูงขึ้น (FPS, คล็อก CPU/GPU) - จุดที่ต้องดู: บันทึก CPUFrequency, GPUFrequency, CPUTemperature และ GPUTemperature ของ PresentMon คู่กับเฟรมไทม์ 20–30 นาที แล้วดูคอลัมน์ GPU engine ในแท็บ Processes ของ Task Manager ว่าเกมรันบนชิปกราฟิกตัวไหน บนมือถือบันทึก thermal API ของ Android (getThermalHeadroom, thermal status) และ thermalState ของ iOS คู่กับ FPS - สัญญาณว่าใช่: FPS ตกตั้งแต่จังหวะที่อุณหภูมิขึ้นแล้วคล็อกลดลง หรือคล็อกต่ำเฉพาะในโหมดแบตเตอรี่หรือประหยัดพลังงาน หรือเกมกำลังรันบนกราฟิกออนบอร์ด - สัญญาณว่าไม่ใช่: คล็อกและอุณหภูมิคงที่แต่ FPS ตก: น่าจะเป็น “โปรเซสเบื้องหลังแย่ง CPU” หรือ “หน่วยความจำรั่วฝั่งไคลเอนต์” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: โน้ตบุ๊กที่มีชิปกราฟิกสองตัว บางครั้งก็รันเกมบนกราฟิกออนบอร์ดที่ช้ากว่าเพื่อประหยัดไฟ ถ้าการแจ้งปัญหาเป็นแบบ “โน้ตบุ๊กสเปกดีแต่ FPS ต่ำ” ให้ตรวจก่อนว่าเกมรันบนชิปกราฟิกตัวไหน - แหล่งอ้างอิง: - [Thermal API](https://developer.android.com/games/optimize/adpf/thermal) · Android (Google) · อุปกรณ์รักษาประสิทธิภาพสูงไว้ได้แค่ช่วงเวลาจำกัด แล้วจะถูก throttle เพราะความร้อน แนะนำให้ดูสถานะความร้อนแล้วลดโหลดล่วงหน้า - [thermalState](https://developer.apple.com/documentation/foundation/processinfo/thermalstate-swift.property) · Apple · ระดับความร้อนปัจจุบันที่ iOS แจ้ง เมื่อระดับสูงขึ้น แอปต้องลดการใช้ทรัพยากร - [Selecting the Best Graphics Device to Run a 3D Intensive Application](https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/) · AMD · บนโน้ตบุ๊กที่มีชิปกราฟิกสองตัว ถ้ารันบนกราฟิกออนบอร์ด เกม 60 FPS อาจเหลือ 30 FPS เลือกการ์ดจอแยกได้ด้วยการ export AmdPowerXpressRequestHighPerformance - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · บันทึก CPUFrequency/GPUFrequency (คล็อก) และ CPUTemperature/GPUTemperature (อุณหภูมิ) แยกรายเฟรม - [GPUs in the task manager](https://devblogs.microsoft.com/directx/gpus-in-the-task-manager/) · Microsoft · Task Manager มีคอลัมน์แสดงอัตราการใช้ GPU ของแต่ละโปรเซส และบอกว่าค่านั้นเป็นของ GPU และ engine ตัวไหน #### co-timer · ความละเอียดของตัวจับเวลา · Timer resolution (Windows 15.6ms) ตัวจับเวลาเริ่มต้นของ Windows ทำงานเป็นหน่วย 15.6 ms การ “พักแค่ 1 ms” จึงกลายเป็นการรอจนถึงรอบตัวจับเวลาถัดไป นานสุดถึง 15.6 ms - ทำไม → ผลคือ → บนหน้าจอ: เขียนการจำกัดเฟรมและการส่งแพ็กเก็ตด้วยวิธี Sleep (รอสักครู่) → OS ปลุกให้ได้เป็นหน่วย 15.6 ms เท่านั้น → ช่วงห่างระหว่างเฟรมและช่วงห่างการส่งอินพุตไม่สม่ำเสมอ - อาการ: กระตุก / ปัจจัย: จิตเตอร์ - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ใช้ high-resolution timer, ทำ pacing ด้วย event หรือ V-Sync แทนการรอ - ตัวเลขที่ควรรู้: หน่วย 15.6 ms ทำให้ได้ช่วงห่าง 16.7 ms พอดีไม่ได้ ช่วงห่างระหว่างเฟรมจึงสลับไปมาระหว่าง 15.6 ms กับ 31.2 ms - บนกราฟ: สูงตลอดตั้งแต่แรก (การกระจายของช่วงห่างระหว่างเฟรม) - จุดที่ต้องดู: ดูการกระจายของ MsBetweenPresents (ช่วงห่างระหว่างเฟรม) จาก PresentMon และหัวข้อ “Platform Timer Resolution” ในรายงาน powercfg /energy (โปรเซสที่เปลี่ยนความละเอียดของตัวจับเวลา) - สัญญาณว่าใช่: ช่วงห่างระหว่างเฟรมกระจุกอยู่ที่ผลคูณของ 15.6 ms เช่น 15.6 ms และ 31.2 ms และเกมไม่ได้ขอความละเอียดของตัวจับเวลาที่สูงขึ้น - สัญญาณว่าไม่ใช่: ช่วงห่างกระจายไปทั่ว ไม่กระจุกที่ค่าใดค่าหนึ่ง: น่าจะเป็น “โปรเซสเบื้องหลังแย่ง CPU” หรือภาระของเฟรม มากกว่าตัวจับเวลา - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: ใน Windows รุ่นเก่า ถ้าโปรแกรมหนึ่งเปลี่ยนตัวจับเวลาเป็น 1 ms จะมีผลกับทุกโปรแกรม จึงเคยมีคำพูดว่า “เปิดเบราว์เซอร์ทิ้งไว้แล้วเกมลื่นขึ้น” ตั้งแต่ Windows 10 เวอร์ชัน 2004 จะมีผลเฉพาะโปรแกรมที่ร้องขอ และ Windows 11 อาจไม่ใช้คำขอของหน้าต่างที่ถูกย่อหรือถูกบังจนมองไม่เห็นและไม่มีเสียง - แหล่งอ้างอิง: - [_WDF_TIMER_CONFIG (wdftimer.h)](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdftimer/ns-wdftimer-_wdf_timer_config) · Microsoft · ความแม่นยำของตัวจับเวลาทั่วไปคือช่วงห่างของ system clock tick ค่าเริ่มต้น 15.6 ms ส่วน high-resolution timer คือ 1 ms - [timeBeginPeriod function (timeapi.h)](https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod) · Microsoft · ก่อน Windows 10 เวอร์ชัน 2004 เป็นการตั้งค่าทั้งระบบ หลังจากนั้นมีผลเฉพาะโปรเซสที่ร้องขอ Windows 11 ไม่รับประกันความละเอียดสูงให้โปรเซสของหน้าต่างที่ถูกบังหรือถูกย่อ - [CreateWaitableTimerExW function (synchapi.h)](https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createwaitabletimerexw) · Microsoft · แฟล็กของ high-resolution waitable timer: CREATE_WAITABLE_TIMER_HIGH_RESOLUTION - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · MsBetweenPresents: เวลาระหว่างการเรียก Present() ครั้งนี้กับครั้งก่อน (ms) - [Results for the Idle Energy Efficiency Assessment](https://learn.microsoft.com/en-us/windows-hardware/test/assessments/results-for-the-idle-energy-efficiency-assessment) · Microsoft · ความละเอียดของ system timer ค่าเริ่มต้น 15.6 ms หาโปรเซสที่เปลี่ยนความละเอียดของตัวจับเวลาได้จากหัวข้อ “Platform Timer Resolution” ในรายงานพลังงาน - [Powercfg command-line options](https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/powercfg-command-line-options) · Microsoft · powercfg /energy: วิเคราะห์ระบบแล้วสร้างรายงานพลังงาน (HTML) #### co-mobile-bg · สลับแอปมือถือไปเบื้องหลัง · App suspended in background ถ้าย่อแอปลงไปแป๊บเดียวเพื่อดูแจ้งเตือน OS จะพักการทำงาน (suspend) ของแอปภายในไม่กี่วินาที และระหว่างนั้นเซิร์ฟเวอร์จะตัดการเชื่อมต่อของเรา - ทำไม → ผลคือ → บนหน้าจอ: ย่อเกมลงเพื่อเช็กข้อความหรือรับสาย → เอนจินเกมหยุดการทำงานของเกม และอีกไม่นาน OS ก็หยุดแอปและเครือข่ายด้วย → กลับมาก็หลุดไปแล้ว ต้องเชื่อมต่อใหม่ - อาการ: หลุด / ปัจจัย: การหยุดชะงัก, แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: หลังอยู่เฉย ๆ สักพัก, ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: เมื่อกลับมาให้เชื่อมต่อใหม่อัตโนมัติด้วย session token ทันทีโดยไม่ต้องรอการเชื่อมต่อที่ขาดไป (ต่อเซสชันเดิมโดยไม่ต้องล็อกอินใหม่), รับสถานะล่าสุดครั้งเดียวแล้วปรับให้ตรงกัน เซิร์ฟเวอร์: เมื่อ heartbeat ขาด ให้ปิดการเชื่อมต่อแต่เก็บเซสชันของตัวละครไว้ในช่วงผ่อนผันสั้น ๆ (ไม่เตะออกทันที), ถ้าเชื่อมต่อใหม่ภายในช่วงนั้นให้รับช่วงต่อด้วย session token - ตัวเลขที่ควรรู้: ปกติเอนจินเกมจะหยุดทันทีที่ย่อแอป iOS จะพักการทำงานของแอปภายในไม่กี่วินาที แม้ขอเวลาเพิ่มได้ก็มักไม่เกินหลายสิบวินาที ส่วน Android 14 ขึ้นไปจะแช่แข็ง (freeze) แอปที่ออกจากหน้าจอหลังผ่านไปราว 10 วินาที - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนครั้งที่หลุด (heartbeat timeout), บันทึกการพักแอป) - จุดที่ต้องดู: จับคู่เวลาที่แอปพักและกลับมาใน log ของไคลเอนต์ (Unity คือ OnApplicationPause) กับสาเหตุและเวลาที่หลุดฝั่งเซิร์ฟเวอร์ด้วย session ID บน Android ดูสาเหตุการปิดโปรเซสที่บันทึกใน ApplicationExitInfo (REASON_LOW_MEMORY ฯลฯ) ประกอบด้วย - สัญญาณว่าใช่: ก่อนเซิร์ฟเวอร์ตัดการเชื่อมต่อเพราะ heartbeat timeout ไคลเอนต์เข้าสู่การพักการทำงาน และเชื่อมต่อใหม่ทันทีหลังกลับมา - สัญญาณว่าไม่ใช่: หลุดระหว่างที่แอปเปิดอยู่บนหน้าจอ: น่าจะเป็น “NAT mapping หมดอายุ”, “IP ที่ ISP ให้ใช้ร่วมกัน (CGNAT)” หรือ “สลับ Wi-Fi ↔ LTE/5G” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: ถ้าหน่วยความจำไม่พอ มือถือบางครั้งก็ปิดเกมที่ย่อไว้ไปเลย นี่คือเหตุผลที่สลับไปแอปกล้องหรือแอปชำระเงิน/ยืนยันตัวตนแล้วกลับมา เกมต้องเริ่มใหม่ตั้งแต่ต้น เครื่องสเปกต่ำยิ่งเจอบ่อย - แหล่งอ้างอิง: - [Extending your app’s background execution time](https://developer.apple.com/documentation/uikit/extending-your-app-s-background-execution-time) · Apple · เมื่อแอปไปอยู่เบื้องหลังจะได้เวลา 5 วินาทีใน applicationDidEnterBackground แล้วถูกพักการทำงานในไม่ช้า ถ้าต้องการเวลาเพิ่มให้ขอด้วย beginBackgroundTask (เวลาที่เหลือดูได้จาก backgroundTimeRemaining) - [Cached apps freezer](https://source.android.com/docs/core/perf/cached-apps-freezer) · Android (Google) · Android 14 ขึ้นไปจะแช่แข็งโปรเซสของแอปที่เข้าสู่สถานะ cached หลังผ่านไป 10 วินาที เมื่อถูกแช่แข็ง ทุกเธรดจะหยุด - [Application.runInBackground](https://docs.unity3d.com/ScriptReference/Application-runInBackground.html) · Unity · ค่าเริ่มต้นเป็น false จึงหยุดเมื่ออยู่เบื้องหลัง Android จะหยุดเมื่ออยู่เบื้องหลังไม่ว่าจะตั้งค่าอย่างไร ส่วน iOS ไม่สนใจการตั้งค่านี้ - [MonoBehaviour.OnApplicationPause(bool)](https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnApplicationPause.html) · Unity · เมื่อแอปถูกพักหรือกลับมาทำงาน จะส่ง OnApplicationPause(true/false) ไปยัง MonoBehaviour ทุกตัว - [ApplicationExitInfo](https://developer.android.com/reference/android/app/ApplicationExitInfo) · Android (Google) · REASON_LOW_MEMORY: low memory killer ของระบบปิดโปรเซสของแอป (เครื่องที่ไม่รองรับจะรายงานเป็น REASON_SIGNALED/SIGKILL) #### co-netswitch · สลับ Wi-Fi ↔ LTE/5G · Network switch changes IP เมื่อเดินออกจากบ้านแล้ว Wi-Fi หลุดและเปลี่ยนไปใช้ LTE หรือ 5G IP address ของเราจะเปลี่ยน การเชื่อมต่อเดิมจึงใช้ไม่ได้ - ทำไม → ผลคือ → บนหน้าจอ: สัญญาณ Wi-Fi อ่อนลงจนสลับไปใช้เครือข่ายมือถือ → IP address ของเราเปลี่ยน การเชื่อมต่อที่สร้างด้วยที่อยู่เดิมจึงรับส่งต่อไม่ได้ → ค้างไปครู่หนึ่ง แล้วหลุดหรือเชื่อมต่อใหม่ - อาการ: ค้าง, หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม), อินฟราเครือข่าย (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ใช้ session token รับช่วงเป็นผู้เล่นคนเดิมแม้ที่อยู่เปลี่ยน และปิดการเชื่อมต่อของที่อยู่เดิมทันที, พิจารณาโปรโตคอลที่การเชื่อมต่อยังอยู่ได้แม้ที่อยู่เปลี่ยน (เช่น connection migration ของ QUIC) ไคลเอนต์: เมื่อตรวจพบการสลับเครือข่าย ให้เชื่อมต่อใหม่ด้วย session token ทันทีโดยไม่ต้องรอ heartbeat timeout - งานฝั่งทีมอินฟรา: ถ้าใช้ connection migration ของ QUIC ให้ตั้งโหลดบาลานเซอร์ให้เลือกเซิร์ฟเวอร์ด้วย connection ID แทนที่อยู่และพอร์ต (ถ้าเลือกด้วยที่อยู่และพอร์ต แพ็กเก็ตที่มาจากที่อยู่ใหม่จะไปลงเซิร์ฟเวอร์อื่น) - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนครั้งที่หลุดและเชื่อมต่อใหม่, IP ที่เปลี่ยนตอนเชื่อมต่อใหม่) - จุดที่ต้องดู: หาบันทึกใน log การเชื่อมต่อของเซิร์ฟเวอร์ที่ session token เดียวกันเชื่อมต่อใหม่ด้วย IP อื่น แล้วเทียบกับเวลาของ callback การเปลี่ยน default network ฝั่งไคลเอนต์ (registerDefaultNetworkCallback) - สัญญาณว่าใช่: หลังหลุดแล้วเชื่อมต่อใหม่ IP เปลี่ยนจากช่วง IP ของ Wi-Fi (เน็ตบ้าน) ไปเป็นช่วง IP ของค่ายมือถือ หรือกลับกัน และก่อนหน้านั้นมี callback การสลับเครือข่ายเข้ามา - สัญญาณว่าไม่ใช่: IP เดิมแต่หลุด: น่าจะเป็น “handover ระหว่างเสาสัญญาณ (ขณะเดินทาง)” หรือ “สัญญาณมือถืออ่อนหรืออยู่ในจุดอับสัญญาณ” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Read network state](https://developer.android.com/training/basics/network-ops/reading-network-state) · Android (Google) · เมื่อ default network เปลี่ยน การเชื่อมต่อใหม่จะไปทางเครือข่ายใหม่ และการเชื่อมต่อบนเครือข่ายเดิมสุดท้ายจะถูกตัด ตรวจจับการสลับได้ด้วย registerDefaultNetworkCallback - [RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport](https://www.rfc-editor.org/rfc/rfc9000) · IETF · connection ID ทำให้การเชื่อมต่อคงอยู่แม้ IP address และพอร์ตเปลี่ยน (บทที่ 9) โหลดบาลานเซอร์ที่กระจายโหลดด้วยที่อยู่และพอร์ตอย่างเดียว อาจส่งแพ็กเก็ตที่มาจากที่อยู่ใหม่ไปยังเซิร์ฟเวอร์อื่น (หัวข้อ 5.2.3) - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · การเชื่อมต่อ TCP ระบุด้วยคู่ซ็อกเก็ต (ที่อยู่และพอร์ต) ของปลายทั้งสองฝั่ง #### co-security · โปรแกรมความปลอดภัยตรวจแพ็กเก็ต · Antivirus / firewall inspection ถ้าแอนตี้ไวรัสหรือไฟร์วอลล์ตรวจทุกแพ็กเก็ต ความหน่วงจะเพิ่มขึ้น และถ้าตรวจเข้มเกินไปก็อาจเข้าใจผิดว่าเกมเป็นการโจมตีแล้วบล็อก - ทำไม → ผลคือ → บนหน้าจอ: โปรแกรมความปลอดภัยตรวจแพ็กเก็ตขาเข้าและขาออกทีละแพ็กเก็ต → ทุกแพ็กเก็ตมีดีเลย์เพิ่ม และถ้าตรวจไม่ทันก็ถูกทิ้ง → ปิงพุ่งแบบไม่สม่ำเสมอ หรือการเชื่อมต่อถูกบล็อก - อาการ: กระตุก, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: จิตเตอร์, แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตลอดเวลา, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ดูแลรายการความเข้ากันได้กับโปรแกรมความปลอดภัย, ลงทะเบียนเกมเป็นข้อยกเว้นใน Windows Firewall ตอนติดตั้ง - งานฝั่งภายนอก: แนะนำให้ผู้เล่นตั้งเกมเป็นข้อยกเว้นในโปรแกรมความปลอดภัย, ถ้าโปรแกรมเข้าใจผิดว่าเกมเป็นการโจมตี ให้แจ้งผู้ผลิตโปรแกรมความปลอดภัยให้แก้ false positive - ตัวเลขที่ควรรู้: ในภาวะปกติ เวลาที่ใช้ตรวจแพ็กเก็ตมักไม่ถึง 1 ms ปัญหาอยู่ที่ตอนโมดูลตรวจทำงานไม่ทันหรือมีบั๊ก และตอนที่เข้าใจผิดว่าการสื่อสารของเกมเป็นการโจมตี - บนกราฟ: สูงเฉพาะบางกลุ่ม (RTT และการเชื่อมต่อล้มเหลว (แยกตามผู้เล่น)) - จุดที่ต้องดู: ปิดโปรแกรมความปลอดภัยชั่วคราวหรือตั้งเกมเป็นข้อยกเว้นแล้วเทียบ บน Windows ถ้าเปิด Audit Filtering Platform Connection และ Audit Filtering Platform Packet Drop ใน audit policy จะมี 5157 (บล็อกการเชื่อมต่อ) และ 5152 (บล็อกแพ็กเก็ต) ใน Security log และดูจำนวนแพ็กเก็ตที่ถูกทิ้งได้จาก WFPv4\Packets Discarded/sec ใน Performance Monitor - สัญญาณว่าใช่: มีบันทึกการบล็อกการเชื่อมต่อหรือแพ็กเก็ตที่ไปยังที่อยู่ของเซิร์ฟเวอร์เกม หรือปิดโปรแกรมความปลอดภัยแล้วอาการปิงพุ่งและเข้าเกมไม่ได้หายไป - สัญญาณว่าไม่ใช่: อุปกรณ์อื่นในบ้านเดียวกันก็เป็นเหมือนกันโดยไม่เกี่ยวกับโปรแกรมความปลอดภัย: ให้ดูเราเตอร์และเน็ต - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [About Windows Filtering Platform](https://learn.microsoft.com/en-us/windows/win32/fwp/about-windows-filtering-platform) · Microsoft · โครงสร้างที่อนุญาตหรือบล็อกแพ็กเก็ตด้วย hook และ filter engine ใน network stack ของ Windows ผู้ผลิตซอฟต์แวร์ความปลอดภัยภายนอกเสียบโมดูลกรองของตัวเอง (callout) เข้าไปได้ - [Windows Firewall Rules](https://learn.microsoft.com/en-us/windows/security/operating-system-security/network-security/windows-firewall/rules) · Microsoft · ค่าเริ่มต้นคือบล็อกการเชื่อมต่อขาเข้า แอปจึงต้องมีกฎยกเว้น และปกติโปรแกรมติดตั้งของแอปเป็นตัวสร้างกฎนี้ - [Address false positives/negatives in Microsoft Defender for Endpoint](https://learn.microsoft.com/en-us/defender-endpoint/defender-endpoint-false-positives-negatives) · Microsoft · ขั้นตอนตั้งข้อยกเว้นและส่งไฟล์ให้ Microsoft วิเคราะห์ เมื่อโปรแกรมปกติถูกเข้าใจผิดว่าเป็นภัย (false positive) - [5157(F): The Windows Filtering Platform has blocked a connection.](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-5157) · Microsoft · Event 5157: Windows Filtering Platform บล็อกการเชื่อมต่อ (Audit Filtering Platform Connection) - [Audit Filtering Platform Packet Drop](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/audit-filtering-platform-packet-drop) · Microsoft · Event 5152: Windows Filtering Platform บล็อกแพ็กเก็ต - [Network-Related Performance Counters](https://learn.microsoft.com/en-us/windows-server/networking/technologies/network-subsystem/net-sub-performance-counters) · Microsoft · ตัวนับ WFPv4/WFPv6: Packets Discarded/sec #### co-rcvbuf · receive buffer ล้น · Socket receive buffer overflow ถ้าเกมยุ่งจนดึงแพ็กเก็ตออกจากซ็อกเก็ต (อินเทอร์เฟซรับส่งข้อมูลเครือข่ายที่ OS จัดให้) ช้า บัฟเฟอร์ของ OS จะล้น - ทำไม → ผลคือ → บนหน้าจอ: เฟรมช้าจนเกมอ่านซ็อกเก็ตไม่ทัน → receive buffer ของ OS เต็ม UDP จะถูกทิ้ง ส่วน TCP จะลด receive window ให้ฝั่งส่งหยุดส่ง → วาร์ป (UDP) หรือกรอเร็ว (TCP) - อาการ: วาร์ป, กรอเร็ว / ปัจจัย: แพ็กเก็ตหาย, การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แยกเธรดสำหรับรับข้อมูลโดยเฉพาะ, ปรับขนาดบัฟเฟอร์ (SO_RCVBUF) - ตัวเลขที่ควรรู้: receive buffer เริ่มต้นมีขนาดหลายสิบถึงหลายร้อย KB ขึ้นกับ OS และการตั้งค่า อัปเดตในที่ที่คนเยอะอาจสูงถึงหลายร้อย KB ใน 1 วินาที - บนกราฟ: สูงตามจำนวนคนและโหลด (จำนวน UDP ที่ถูกทิ้งที่ receive buffer, เฟรมไทม์) - จุดที่ต้องดู: บันทึก Microsoft Winsock BSP\Dropped Datagrams (จำนวน UDP ที่ถูกทิ้งเพราะ receive buffer ของซ็อกเก็ตไม่พอ) และ UDPv4\Datagrams Received Errors ใน Performance Monitor ของ Windows คู่กับเฟรมไทม์ ฝั่งเกมนับช่องว่างของหมายเลขลำดับแพ็กเก็ตที่ได้รับ - สัญญาณว่าใช่: ในที่ที่คนเยอะหรือทันทีหลังเฟรมยาว Dropped Datagrams เพิ่มขึ้น และในจังหวะเดียวกันหมายเลขลำดับในเกมมีช่องว่าง ขณะที่ช่วงเวลาเดียวกันไม่มีแพ็กเก็ตหายฝั่งเน็ต - สัญญาณว่าไม่ใช่: Dropped Datagrams เท่าเดิมแต่หมายเลขลำดับขาดหาย: แพ็กเก็ตหายระหว่างทาง - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [socket(7) — Linux manual page](https://man7.org/linux/man-pages/man7/socket.7.html) · Linux man-pages · SO_RCVBUF คือขนาดสูงสุดของ receive buffer ของซ็อกเก็ต ค่าเริ่มต้นกำหนดโดย rmem_default และค่าสูงสุดโดย rmem_max (Android ก็ใช้เคอร์เนล Linux) - [SOL_SOCKET Socket Options (Winsock2.h)](https://learn.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options) · Microsoft · SO_RCVBUF ของ Windows: พื้นที่บัฟเฟอร์ที่จองไว้สำหรับรับข้อมูลในแต่ละซ็อกเก็ต - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · ฟิลด์ window ของ TCP คือจำนวนไบต์ที่ฝั่งรับยังรับเพิ่มได้ ถ้าเป็น 0 ฝั่งส่งจะส่งแค่ zero window probe และรอ - [Low Latency Workloads Management and Operations](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/hh997022(v=ws.11)) · Microsoft · Dropped Datagrams และ Dropped Datagrams/sec ในชุดตัวนับ Microsoft Winsock BSP: จำนวน UDP ที่ถูกทิ้งเพราะมาเร็วกว่าที่แอปประมวลผลทัน หรือ receive buffer ของซ็อกเก็ตไม่พอ - [Network-Related Performance Counters](https://learn.microsoft.com/en-us/windows-server/networking/technologies/network-subsystem/net-sub-performance-counters) · Microsoft · ตัวนับ UDPv4/UDPv6: Datagrams Received Errors และ Microsoft Winsock BSP: Dropped Datagrams #### co-swap · หน่วยความจำฝั่งไคลเอนต์ไม่พอและ swap · Paging / swap on client ถ้าเปิดแท็บเบราว์เซอร์หลายสิบแท็บพร้อมกับเกม OS จะย้ายหน่วยความจำบางส่วนของเกมไปไว้บนดิสก์ - ทำไม → ผลคือ → บนหน้าจอ: RAM ทั้งเครื่องไม่พอ → OS ย้ายหน่วยความจำของเกมที่ยังไม่ได้ใช้ตอนนี้ไปไว้บนดิสก์ → จังหวะที่กลับมาใช้ส่วนนั้นอีก เกมค้างหลายสิบถึงหลายร้อย ms ขึ้นกับสตอเรจ - อาการ: ค้าง, กระตุก / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ลดการใช้หน่วยความจำ, แสดงคำเตือนเมื่อหน่วยความจำเหลือน้อย - งานฝั่งภายนอก: แจ้งสเปกขั้นต่ำให้ผู้เล่น, แนะนำให้ปิดโปรแกรมอื่น (เช่น แท็บเบราว์เซอร์) ระหว่างเล่น - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (hard page fault, ปริมาณการใช้หน่วยความจำ) - จุดที่ต้องดู: บันทึก Memory\Pages Input/sec (จำนวนเพจที่อ่านจากดิสก์เพื่อแก้ hard page fault) ใน Performance Monitor และปริมาณการใช้หน่วยความจำและ commit ในแท็บ Performance ของ Task Manager คู่กับเฟรมไทม์ - สัญญาณว่าใช่: ตอนที่ค้าง Pages Input/sec พุ่งขึ้นและหน่วยความจำเกือบเต็ม ปิดโปรแกรมอื่นเช่นเบราว์เซอร์แล้วหายไป - สัญญาณว่าไม่ใช่: หน่วยความจำยังเหลือและ Pages Input/sec นิ่ง: น่าจะเป็น “โหลดแบบ synchronous และคอมไพล์ shader บนเมนเธรด” หรือ “สตอเรจช้าจนสตรีมแอสเซ็ตไม่ทัน” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [Introduction to the page file](https://learn.microsoft.com/en-us/troubleshoot/windows-client/performance/introduction-to-the-page-file) · Microsoft · page file คือไฟล์บนดิสก์ที่ใช้สำหรับย้ายเพจหน่วยความจำที่ถูกแก้ไขแล้วแต่ไม่ค่อยได้ใช้ออกจาก RAM - [Working Set](https://learn.microsoft.com/en-us/windows/win32/memory/working-set) · Microsoft · การเข้าถึงเพจที่ไม่อยู่ใน RAM คือ page fault ส่วน hard fault ต้องอ่านจากดิสก์ เช่น page file จึงจะแก้ได้ - [Chapter 12 - Detecting Memory Bottlenecks](https://learn.microsoft.com/en-us/previous-versions/cc749872(v=technet.10)) · Microsoft · Memory\Pages Input/sec: จำนวนเพจที่อ่านจากดิสก์เพื่อแก้ page fault (hard page fault) #### co-vram · หน่วยความจำกราฟิก (VRAM) ไม่พอ · VRAM over-commit ถ้าหน่วยความจำที่ตัวเลือกกราฟิกต้องการมากกว่าหน่วยความจำของการ์ดจอ OS จะย้าย texture ไปไว้ในหน่วยความจำของ PC แล้วดึงกลับมาใหม่ ภาพจึงกระตุก - ทำไม → ผลคือ → บนหน้าจอ: ตัวเลือก texture สูง และอุปกรณ์สวมใส่กับเอฟเฟกต์สารพัดในที่ที่คนเยอะ ทำให้หน่วยความจำการ์ดจอเต็ม → OS ย้าย texture ที่ยังไม่ได้ใช้ตอนนี้ไปไว้ในหน่วยความจำของ PC แล้วดึงกลับผ่านบัส PCIe ที่ช้ากว่าเมื่อต้องใช้ → หยุดแวบทุกครั้งที่เห็นฉากใหม่หรือตัวละครใหม่ texture เบลออยู่พักหนึ่ง - อาการ: กระตุก, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: ตั้งค่าเริ่มต้นของตัวเลือกให้เหมาะกับขนาดหน่วยความจำการ์ดจอ, ลดคุณภาพ texture อัตโนมัติเมื่อเกินงบหน่วยความจำ, ลดรายละเอียด texture ตัวละครในที่ที่คนเยอะ - งานฝั่งภายนอก: แนะนำให้ผู้เล่นลดตัวเลือก texture, ถ้าเปิดไคลเอนต์สองจอ แนะนำให้ลดตัวเลือกลงอีก - ตัวเลขที่ควรรู้: หน่วยความจำการ์ดจออ่านได้หลายร้อย GB ใน 1 วินาที แต่บัส PCIe ที่รับส่งกับหน่วยความจำของ PC ทำได้ราว 16–64 GB ใน 1 วินาทีตามรุ่น จึงช้ากว่าเกินสิบเท่า - บนกราฟ: ชนเพดานแล้วแบนราบ (หน่วยความจำ GPU เฉพาะ, หน่วยความจำ GPU ที่ใช้ร่วมกัน) - จุดที่ต้องดู: ดูกราฟหน่วยความจำ GPU เฉพาะและหน่วยความจำ GPU ที่ใช้ร่วมกันในหัวข้อ GPU ของแท็บ Performance ใน Task Manager (เพิ่มคอลัมน์รายโปรเซสในแท็บ Details ได้) คู่กับเฟรมไทม์จาก PresentMon - สัญญาณว่าใช่: ช่วงที่หน่วยความจำ GPU เฉพาะชนเพดานจนแบนราบและหน่วยความจำ GPU ที่ใช้ร่วมกันเพิ่มขึ้น มีอาการหยุดแวบบ่อย และลดตัวเลือก texture แล้วหายไป - สัญญาณว่าไม่ใช่: หน่วยความจำ GPU เฉพาะยังเหลือ: น่าจะเป็น “สตอเรจช้าจนสตรีมแอสเซ็ตไม่ทัน” หรือ “โหลดแบบ synchronous และคอมไพล์ shader บนเมนเธรด” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: ถ้าในหัวข้อ GPU ของ Task Manager บน Windows “หน่วยความจำ GPU เฉพาะ (Dedicated GPU memory)” เต็มและ “หน่วยความจำ GPU ที่ใช้ร่วมกัน (Shared GPU memory)” เพิ่มขึ้น คือภาวะนี้ ถ้าเปิดไคลเอนต์สองจอบน PC เครื่องเดียวกันจะเต็มเร็วขึ้น (ดูรายการ “หน่วยความจำ/VRAM ไม่พอจนสตรีมล้มเหลว”) - แหล่งอ้างอิง: - [Residency](https://learn.microsoft.com/en-us/windows/win32/direct3d12/residency) · Microsoft · แต่ละโปรเซสมีงบหน่วยความจำกราฟิกที่ใช้ได้ ถ้าเกิน เคอร์เนลจะย้าย heap บางส่วนของ GPU แยกไปไว้ในหน่วยความจำของ PC (เป็นทางเลือกสุดท้าย จึงแนะนำให้จัดการงบเอง) - [GPUs in the task manager](https://devblogs.microsoft.com/directx/gpus-in-the-task-manager/) · Microsoft · หน่วยความจำ GPU เฉพาะใน Task Manager คือ VRAM ของการ์ดจอ ส่วนหน่วยความจำ GPU ที่ใช้ร่วมกันคือหน่วยความจำของ PC ที่ GPU และ CPU ใช้ร่วมกัน - [CUDA C++ Best Practices Guide](https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html) · NVIDIA · แบนด์วิดท์หน่วยความจำกราฟิก (V100 898 GB/s) สูงกว่า PCIe x16 รุ่นที่ 3 (16 GB/s) มาก จึงแนะนำให้ลดการรับส่งกับหน่วยความจำของ PC - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · อัดเวลาของแต่ละเฟรมด้วย FrameTime (เวลา CPU ระหว่างเฟรม) #### co-wifi-scan · การสแกน Wi-Fi เบื้องหลัง · Periodic Wi-Fi background scan ระหว่างที่ OS สลับช่องสัญญาณไปมาเป็นระยะเพื่อหา Wi-Fi รอบตัว การรับส่งข้อมูลจะหยุดไปครู่หนึ่ง - ทำไม → ผลคือ → บนหน้าจอ: OS หรือไดรเวอร์ค้นหา Wi-Fi รอบตัวเป็นรอบ → ระหว่างค้นหา การรับส่งข้อมูลหยุดไปครู่หนึ่ง → ปิงพุ่งเป็นจังหวะที่ห่างเท่ากันเป๊ะ (เช่น ทุก 60 วินาที) - อาการ: กระตุก, วาร์ป / ปัจจัย: จิตเตอร์ - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: เป็นรอบสม่ำเสมอ - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ขอโหมดที่ลดการค้นหาไร้สายระหว่างเล่น (Android คือโหมด Wi-Fi low latency WIFI_MODE_FULL_LOW_LATENCY, Windows คือโหมด media streaming ของ WlanSetInterface ซึ่งบางเครื่องหรือบางไดรเวอร์อาจไม่ได้ผล) - งานฝั่งภายนอก: แนะนำให้ผู้เล่นใช้สาย LAN, ปรับการตั้งค่าบริการตำแหน่งและการค้นหา Wi-Fi อัตโนมัติ, อัปเดตไดรเวอร์ไร้สาย - ตัวเลขที่ควรรู้: ปกติครั้งละหลายสิบถึงหลายร้อย ms ถ้าเป็นจังหวะสม่ำเสมอเกินไป ให้สงสัยสาเหตุนี้ก่อน - บนกราฟ: พุ่งเป็นรอบ (RTT ถึงเราเตอร์) - จุดที่ต้องดู: ระหว่างเล่น ใช้ ping /t วัดไปที่อยู่ของเราเตอร์ (default gateway จาก ipconfig) สักหลายนาที แล้ววัดช่วงห่างของจุดที่พุ่ง จากนั้นเปลี่ยนเป็นสาย LAN แล้ววัดแบบเดียวกัน - สัญญาณว่าใช่: ปิงถึงเราเตอร์พุ่งหลายสิบถึงหลายร้อย ms เป็นจังหวะห่างเท่ากันเป๊ะ (เช่น 60 วินาที) และหายไปเมื่อใช้สาย LAN - สัญญาณว่าไม่ใช่: ช่วงห่างของจุดที่พุ่งไม่สม่ำเสมอ: น่าจะเป็น “Wi-Fi ถูกรบกวนหรือสัญญาณอ่อน” ถ้าถึงเราเตอร์ปกติแต่พุ่งเฉพาะช่วงที่เลยออกไป ให้ดูเน็ตและช่วงของ ISP - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [WDI low latency connection quality](https://learn.microsoft.com/en-us/windows-hardware/drivers/network/wdi-low-latency-connection-quality) · Microsoft · การค้นหาและ roaming ย้ายชิปไร้สายออกนอกช่องสัญญาณที่เชื่อมต่ออยู่ โหมด low latency จึงจำกัดเวลาที่ออกนอกช่องสัญญาณและการค้นหา - [WlanSetInterface function (wlanapi.h)](https://learn.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlansetinterface) · Microsoft · API สำหรับเปิดปิดการค้นหาเบื้องหลัง (wlan_intf_opcode_background_scan_enabled) และโหมด media streaming บน Windows - [Wi-Fi low-latency mode](https://source.android.com/docs/core/connect/wifi-low-latency) · Android (Google) · โหมด low latency จะปิดการประหยัดพลังงานของ Wi-Fi ส่วนการปรับแต่งการค้นหาและ roaming ขึ้นกับการ implement ของผู้ผลิตเครื่อง - [ping](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ping) · Microsoft · /t: ส่ง echo request ต่อไปเรื่อย ๆ จนกว่าจะสั่งหยุด - [ipconfig](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ipconfig) · Microsoft · ถ้ารันโดยไม่ใส่พารามิเตอร์ จะแสดงที่อยู่ IPv4/IPv6 และ default gateway ของแต่ละอะแดปเตอร์ #### co-driver · โหมดประหยัดพลังงานของ NIC และปัญหาไดรเวอร์ · NIC power saving, driver bugs ถ้าการ์ด LAN หรือชิป Wi-Fi เข้าสู่โหมดประหยัดพลังงานระหว่างแพ็กเก็ต จะต้องใช้เวลากว่าจะกลับมาทำงาน - ทำไม → ผลคือ → บนหน้าจอ: เปิดฟีเจอร์ประหยัดพลังงานของอุปกรณ์เครือข่ายไว้ หรือไดรเวอร์เก่า → ดีเลย์จากการตื่นจากโหมดประหยัดพลังงาน (wake-up) บางครั้งอุปกรณ์รีสตาร์ต → ดีเลย์ไม่สม่ำเสมอ นาน ๆ ครั้งค้างไปหลายวินาที - อาการ: กระตุก, ค้าง / ปัจจัย: จิตเตอร์, แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: หลังอยู่เฉย ๆ สักพัก, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์ Android ขอโหมด Wi-Fi low latency (WIFI_MODE_FULL_LOW_LATENCY) ระหว่างเล่นเพื่อปิดการประหยัดพลังงานของ Wi-Fi - งานฝั่งภายนอก: แนะนำให้ผู้เล่นอัปเดตไดรเวอร์เครือข่าย และปิดการประหยัดพลังงานของอุปกรณ์เครือข่ายใน Device Manager, ถ้าทั้งจอหยุดแวบและเสียงแตกซ่า แนะนำให้ใช้ LatencyMon หาไดรเวอร์ต้นเหตุ - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (เวลา DPC/ISR, RTT ถึงเราเตอร์) - จุดที่ต้องดู: อัดด้วย Windows Performance Recorder (WPR) แล้วหาไดรเวอร์ที่ทำงานนาน (คอลัมน์ Module) ในกราฟ DPC/ISR ของ Windows Performance Analyzer (WPA) และตรวจการตั้งค่า power management (ประหยัดพลังงาน) ของ network adapter ใน Device Manager - สัญญาณว่าใช่: ตอนที่หยุดแวบ DPC/ISR ของไดรเวอร์เครือข่ายทำงานต่อเนื่องครั้งละหลาย ms หรือปิดการประหยัดพลังงานแล้วดีเลย์ที่ไม่สม่ำเสมอหายไป - สัญญาณว่าไม่ใช่: DPC สั้น และปิดการประหยัดพลังงานแล้วก็ยังเหมือนเดิม: น่าจะเป็น “Wi-Fi ถูกรบกวนหรือสัญญาณอ่อน” หรือ “การสแกน Wi-Fi เบื้องหลัง” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: ถ้าไดรเวอร์ใช้ CPU นานเพื่อจัดการอินเทอร์รัปต์ (บน Windows เรียกว่า DPC latency) ระหว่างนั้นเธรดเกมก็ใช้คอร์นั้นไม่ได้ อาการคือทั้งจอหยุดแวบและเสียงแตกซ่าทั้งที่อัตราการใช้ CPU ต่ำ หาได้ว่าเป็นไดรเวอร์ตัวไหนด้วยเครื่องมืออย่าง LatencyMon ต้นเหตุที่พบบ่อยคือไดรเวอร์ Wi-Fi และ LAN - แหล่งอ้างอิง: - [Introduction to NDIS Selective Suspend](https://learn.microsoft.com/en-us/windows-hardware/drivers/network/ndis-selective-suspend) · Microsoft · Windows ส่ง network adapter ที่ว่างอยู่เข้าสู่สถานะพลังงานต่ำได้ (selective suspend) - [Guidelines for Writing DPC Routines](https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/guidelines-for-writing-dpc-routines) · Microsoft · ระหว่างที่ DPC ทำงาน ทุกเธรดบนคอร์นั้นจะหยุด จึงแนะนำว่าไม่ควรเกิน 100 µs ต่อครั้ง - [Wi-Fi low-latency mode](https://source.android.com/docs/core/connect/wifi-low-latency) · Android (Google) · โหมด Wi-Fi low latency ของ Android ให้ framework ปิดการประหยัดพลังงานของ Wi-Fi อย่างชัดแจ้ง - [CPU Analysis](https://learn.microsoft.com/en-us/windows-hardware/test/wpt/cpu-analysis) · Microsoft · กราฟ DPC/ISR ของ WPA: เวลาของแต่ละช่วงที่ DPC/ISR ทำงานต่อเนื่อง และโมดูล (Module) ที่ฟังก์ชันนั้นอยู่ #### co-other-apps · แอปอื่นในเครื่องเดียวกันแย่งแบนด์วิดท์ · Other apps saturating the link ถ้าการซิงก์ไฟล์ขึ้นคลาวด์, การดาวน์โหลดไฟล์ใหญ่ หรือแพตช์เกมกำลังทำงานบน PC เครื่องเดียวกัน แพ็กเก็ตเกมจะต้องรอในคิว - ทำไม → ผลคือ → บนหน้าจอ: แอปอื่นใช้อัปโหลดหรือดาวน์โหลดเต็มที่ → แพ็กเก็ตเกมกองรอในคิวของ PC และเราเตอร์ → ปิงพุ่งสูงมาก, อินพุตดีเลย์, กรอเร็ว - อาการ: อินพุตดีเลย์, กรอเร็ว / ปัจจัย: ความหน่วง, จิตเตอร์ - ใครเจอ: เราคนเดียว, คนในบ้านเดียวกัน / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ให้ launcher และ patcher ของเราหยุดหรือจำกัดความเร็วการดาวน์โหลดเบื้องหลังระหว่างเล่น - งานฝั่งภายนอก: แนะนำให้ผู้เล่นจำกัดความเร็วดาวน์โหลด และปิดการอัปเดตอัตโนมัติระหว่างเล่น - บนกราฟ: สูงตามจำนวนคนและโหลด (RTT, ปริมาณรับส่งของ PC) - จุดที่ต้องดู: บันทึก Network Interface\Bytes Sent/sec และ Bytes Received/sec ใน Performance Monitor คู่กับปิง วิธีเดียวกับการทดสอบ bufferbloat ที่เปิดปิงทิ้งไว้แล้วจงใจส่งข้อมูลจำนวนมาก - สัญญาณว่าใช่: ระหว่างที่ดาวน์โหลดหรืออัปโหลดเกือบเต็มความเร็วเน็ต ปิงขึ้นไปหลายสิบถึงหลายร้อย ms และกลับมาทันทีเมื่อหยุดส่ง - สัญญาณว่าไม่ใช่: ปริมาณรับส่งของ PC ต่ำแต่ปิงขึ้น: น่าจะเป็น “bufferbloat (คิวในเราเตอร์)” จากอุปกรณ์อื่นในบ้านเดียวกัน หรือช่วงของ ISP - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [Introduction](https://www.bufferbloat.net/projects/bloat/wiki/Introduction/) · Bufferbloat.net · ถ้าอุปกรณ์เครือข่ายอย่างเราเตอร์เก็บข้อมูลไว้มากเกินไป ดีเลย์จะพุ่งสูงมาก (bufferbloat) - [Delivery Optimization reference](https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization-reference) · Microsoft · ค่าเริ่มต้นของการดาวน์โหลด Windows Update (Delivery Optimization) จะปรับตามแบนด์วิดท์ที่ใช้ได้แบบไดนามิก และตั้งเพดานแบนด์วิดท์การดาวน์โหลดเบื้องหลังและเบื้องหน้าได้ - [Network-Related Performance Counters](https://learn.microsoft.com/en-us/windows-server/networking/technologies/network-subsystem/net-sub-performance-counters) · Microsoft · ตัวนับ Network Interface: Bytes Received/sec และ Bytes Sent/sec - [Tests for Bufferbloat](https://www.bufferbloat.net/projects/bloat/wiki/Tests_for_Bufferbloat/) · Bufferbloat.net · ถ้าเปิดปิงทิ้งไว้แล้วใช้การทดสอบความเร็วทำให้เน็ตเต็ม แล้วปิงขึ้น คือ bufferbloat #### co-unfocused · จำกัดการประมวลผลเมื่อย่อหรือสลับหน้าต่าง · Minimized / unfocused window throttling เมื่อไปดูหน้าต่างอื่นหรือย่อเกม ตัวเกมและ Windows จะลดความเร็วการทำงานของเกมเพื่อประหยัดไฟ พอกลับมา แพ็กเก็ตที่กองรอจะทะลักเข้ามา หรือหลุดไปแล้ว - ทำไม → ผลคือ → บนหน้าจอ: กด Alt+Tab ไปดูหน้าต่างอื่น หรือย่อเกม → ระหว่างที่มองไม่เห็นเกม เกมลด FPS ลงมากหรือหยุด และ Windows ก็ลด priority ของโปรแกรมที่มองไม่เห็นด้วย → ทันทีที่กลับมาเกิดอาการกรอเร็ว ถ้าย่อไว้นานจะหลุด - อาการ: กรอเร็ว, กระตุก, หลุด / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง, หลังอยู่เฉย ๆ สักพัก - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ให้การรับแพ็กเก็ตและ heartbeat ทำงานต่อในเธรดแยกแม้มองไม่เห็นหน้าต่าง, ตรวจการตั้งค่า “Run In Background” ของเอนจิน, ตอนกลับมาให้ปรับเป็นสถานะล่าสุดในครั้งเดียว - ตัวเลขที่ควรรู้: ถ้าลด FPS เหลือ 5–10 ตอนที่มองไม่เห็นหน้าต่าง หนึ่งเฟรมจะยาว 100–200 ms เกมที่ประมวลผลแพ็กเก็ตทุกเฟรมก็จะอ่านแพ็กเก็ตช้าลงเท่านั้น - บนกราฟ: ขาดช่วงแล้วมารวดเดียว (ช่วงห่างระหว่างเฟรม (ก่อนและหลังสลับหน้าต่าง), จำนวนแพ็กเก็ตที่ประมวลผล) - จุดที่ต้องดู: เปิด PresentMon ไว้แล้วลองกด Alt+Tab หรือย่อหน้าต่าง ดูช่วงห่างระหว่างเฟรมตอนที่มองไม่เห็นหน้าต่าง บันทึกเวลาที่โฟกัสหน้าต่างเปลี่ยนลง log ของเกม แล้วเทียบกับสาเหตุที่หลุด - สัญญาณว่าใช่: ระหว่างที่มองไม่เห็นหน้าต่าง ช่วงห่างระหว่างเฟรมยาวขึ้นเกิน 100 ms หรือบันทึกขาดหาย และทันทีที่กลับมา เกมประมวลผลแพ็กเก็ตที่กองรอรวดเดียวจนเกิดอาการกรอเร็ว ถ้าย่อไว้นานจะหลุดเพราะ heartbeat timeout - สัญญาณว่าไม่ใช่: เปิดหน้าต่างไว้ก็ยังเป็นเหมือนกัน: น่าจะเป็น “โปรเซสเบื้องหลังแย่ง CPU” หรือฝั่งเครือข่าย - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: Windows 11 ไม่รับประกันตัวจับเวลา 1 ms ให้โปรแกรมของหน้าต่างที่ถูกย่อหรือถูกบังมิดและไม่มีเสียง บนโน้ตบุ๊กที่ใช้แบตเตอรี่ Windows จะลดความเร็วของโปรแกรมแบบนี้ลงไปที่ระดับที่ใช้ไฟน้อยที่สุด และถ้าเป็น CPU ที่มีคอร์หลายแบบผสมกัน ก็อาจย้ายไปรันบน efficiency core ที่ช้ากว่า ถ้าไคลเอนต์สองตัวใน PC เครื่องเดียวกันผิดปกติเฉพาะตัวที่อยู่เบื้องหลัง ให้ดูรายการ “จำกัดการประมวลผลของหน้าต่างเบื้องหลัง” ประกอบด้วย - แหล่งอ้างอิง: - [Quality of Service](https://learn.microsoft.com/en-us/windows/win32/procthread/quality-of-service) · Microsoft · โปรแกรมของหน้าต่างที่มองไม่เห็นและไม่มีเสียงจะได้ Low QoS เมื่อใช้แบตเตอรี่จะถูกจัดให้รันที่ความเร็ว CPU ที่ประหยัดที่สุดและบน efficiency core - [timeBeginPeriod function (timeapi.h)](https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod) · Microsoft · Windows 11 ไม่รับประกันความละเอียดของตัวจับเวลาที่สูงกว่าค่าเริ่มต้นให้โปรเซสของหน้าต่างที่ถูกบังหรือถูกย่อ - [Application.runInBackground](https://docs.unity3d.com/ScriptReference/Application-runInBackground.html) · Unity · ค่าเริ่มต้นของ Unity คือ false เมื่อหน้าต่างไปอยู่เบื้องหลัง ลูปของเกมจะหยุด - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · MsBetweenPresents: เวลาระหว่างการเรียก Present() ครั้งนี้กับครั้งก่อน (ms) #### co-overlay · โปรแกรมโอเวอร์เลย์รบกวน · Overlays and screen hooks โปรแกรมแชต, launcher, โปรแกรมอัดจอ และโปรแกรมแสดง FPS จะแทรกเข้าไปในขั้นตอนการเรนเดอร์ของเกม (hooking) เพื่อวาด UI ของตัวเองทับบนจอเกม งานในแต่ละเฟรมจึงเพิ่มขึ้น และบางครั้งก็ชนกับเกมจนภาพหยุดแวบหรือเกมถูกบังคับปิด - ทำไม → ผลคือ → บนหน้าจอ: เปิดโอเวอร์เลย์ของโปรแกรมแชต, game launcher, เครื่องมือของการ์ดจอ หรือโปรแกรมอัดจอไว้ → ทุกครั้งที่ส่งเฟรมออกจอ โอเวอร์เลย์จะแทรกเข้ามาวาด UI ของตัวเองทับ → เฟรมช้าลงทีละนิด และตอนที่แจ้งเตือนโผล่ขึ้นมา ภาพหยุดแวบ กราฟิกเพี้ยน หรือเกมถูกบังคับปิด (ผู้เล่นเห็นเหมือนหลุด) - อาการ: กระตุก, ค้าง, หลุด / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตลอดเวลา, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: รวบรวมรายการโอเวอร์เลย์ที่กำลังทำงานไปพร้อมกับรายงานการแครชและ log อาการกระตุก - งานฝั่งภายนอก: เมื่อได้รับการแจ้งปัญหา แนะนำให้ผู้เล่นปิดโอเวอร์เลย์ทั้งหมดแล้วลองใหม่ - บนกราฟ: สูงเฉพาะบางกลุ่ม (เฟรมไทม์และจำนวนการแครช (ผู้เล่นที่เปิดโอเวอร์เลย์)) - จุดที่ต้องดู: ปิดโอเวอร์เลย์ทั้งหมดแล้วเทียบเฟรมไทม์จาก PresentMon ในฉากเดียวกัน ถ้ามีการแครช ดูชื่อโมดูลที่ผิดพลาด (Faulting module name) ใน Event ID 1000 ของ Event Viewer - สัญญาณว่าใช่: ปิดโอเวอร์เลย์แล้วอาการหยุดแวบหรือกราฟิกเพี้ยนหายไป หรือโมดูลที่ผิดพลาดในการแครชคือ DLL ของโปรแกรมโอเวอร์เลย์ - สัญญาณว่าไม่ใช่: ปิดโอเวอร์เลย์ทั้งหมดแล้วยังเหมือนเดิม: น่าจะเป็นไดรเวอร์กราฟิก หรือ “ไคลเอนต์แครช” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: ถ้ากระตุกหรือเกมปิดเองเฉพาะบางคน และอธิบายด้วยสเปกไม่ได้ ให้สงสัยการชนกันระหว่างโอเวอร์เลย์กับโมดูลความปลอดภัยของเกมก่อน - แหล่งอ้างอิง: - [Steam Overlay (Steamworks Documentation)](https://partner.steamgames.com/doc/features/overlay) · Valve · Steam Overlay จะ hook เข้าไปในเกมที่เปิดผ่าน Steam โดยอัตโนมัติ และด้วยวิธีนี้เองจึงอาจเผยข้อผิดพลาดด้านหน่วยความจำในการใช้ rendering API ของเกมจนแครชได้ - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · อัดเวลาของแต่ละเฟรมด้วย FrameTime (เวลา CPU ระหว่างเฟรม) - [The application or service crashing behavior troubleshooting guidance](https://learn.microsoft.com/en-us/troubleshoot/windows-server/performance/troubleshoot-application-service-crashing-behavior) · Microsoft · Event ID 1000 ใน Application log มีชื่อโมดูลที่ผิดพลาด (Faulting module name) และบางครั้งโมดูลของ Windows ก็ถูกบันทึกเป็นโมดูลที่ผิดพลาดเพราะโมดูลอื่นทำหน่วยความจำเสียหาย #### co-display-input · ดีเลย์จากจอ, อุปกรณ์อินพุต และ frame generation · Display, input device and frame generation latency ถ้าปิงปกติแต่การควบคุมรู้สึกหนืด อาจเป็นเพราะการประมวลผลภาพของทีวี, คอนโทรลเลอร์ไร้สาย หรือ frame generation กำลังเพิ่มดีเลย์ระหว่างอินพุตกับภาพบนจอ - ทำไม → ผลคือ → บนหน้าจอ: ปิด Game Mode ของทีวีไว้, ใช้คอนโทรลเลอร์ Bluetooth หรือไร้สาย หรือเปิด frame generation (DLSS/FSR Frame Generation) → ทีวีส่งเฟรมออกช้าลงระหว่างประมวลผลคุณภาพภาพ, อินพุตไร้สายมาถึงช้าตามรอบการส่งและสัญญาณรบกวน และ frame generation ต้องรอเฟรมถัดไปก่อนจึงสร้างเฟรมคั่นกลางได้ → ปิงและตัวเลข FPS ดี แต่กดแล้วกว่าจะเห็นผลบนจอช้า เป็นอาการอินพุตดีเลย์ - อาการ: อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ให้ frame generation เป็นตัวเลือก และแจ้งว่าเปิดแล้วอินพุตดีเลย์อาจเพิ่มขึ้น, เมื่อใช้ frame generation ให้เชื่อมกับฟีเจอร์ลดดีเลย์ของผู้ผลิตการ์ดจอ (NVIDIA Reflex, AMD Anti-Lag 2) ด้วย, แสดงดีเลย์จากอินพุตถึงจอฝั่ง PC ในเกม, บิลด์สำหรับ Android TV และ set-top box ใช้ Window.setPreferMinimalPostProcessing(true) เพื่อขอโหมด low latency (ALLM) จากทีวี - งานฝั่งภายนอก: แนะนำให้ผู้เล่นเปิด Game Mode (ALLM) ของทีวีหรือจอ, ใช้คอนโทรลเลอร์แบบมีสายและปิด frame generation ในคอนเทนต์ที่แข่งขัน, วางอุปกรณ์ Bluetooth ไว้ใกล้ และใช้ Wi-Fi 5 GHz - ตัวเลขที่ควรรู้: จอ 60 Hz ใช้เวลาส่งหนึ่งเฟรมอย่างเดียวก็ 16.7 ms ส่วน 120 Hz ใช้ 8.3 ms คอนโทรลเลอร์ Xbox รุ่นเก่าอ่านและส่งอินพุตทุก 8 ms ดีเลย์ที่การประมวลผลภาพของทีวีเพิ่มขึ้นมาต่างกันไปตามรุ่นจนบอกเป็นตัวเลขเดียวได้ยาก และ Game Mode คือการตั้งค่าที่ลดการประมวลผลนี้ สำหรับ frame generation ทาง AMD แนะนำให้ใช้เมื่อเฟรมเรตก่อนสร้างเฟรมอยู่ที่ 60 FPS ขึ้นไป - บนกราฟ: สูงตลอดตั้งแต่แรก (ดีเลย์จากอินพุตถึงจอ) - จุดที่ต้องดู: เทียบ MsAllInputToPhotonLatency (ตั้งแต่อินพุตคีย์บอร์ด/เมาส์จนส่งภาพออกจอ) ของ PresentMon ขณะเปิดและปิด frame generation และดูจาก FrameType (บันทึกเฉพาะเมื่อไดรเวอร์หรือ SDK แจ้งมา) ว่ามีเฟรมคั่นกลางที่สร้างขึ้นปนอยู่หรือไม่ ค่านี้ไม่รวมช่วงไร้สายของคอนโทรลเลอร์และการประมวลผลในทีวี ส่วนนั้นให้เทียบโดยสลับไปใช้ Game Mode ของทีวีหรือคอนโทรลเลอร์แบบมีสาย - สัญญาณว่าใช่: ปิงปกติ แต่ปิด frame generation แล้วดีเลย์จากอินพุตถึงจอลดลง หรือเปลี่ยนเป็น Game Mode ของทีวีหรือคอนโทรลเลอร์แบบมีสายแล้วรู้สึกว่าดีเลย์หายไป - สัญญาณว่าไม่ใช่: เปลี่ยนการตั้งค่าเหล่านี้ทั้งหมดแล้วยังเหมือนเดิม และปิงสูงหรือพุ่ง: ให้ดูฝั่งเครือข่าย ถ้าดีเลย์ฝั่ง PC สูงเพราะ V-Sync หรือคิวเฟรม น่าจะเป็น “V-Sync และคิวเรนเดอร์” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: ความหน่วงของเครือข่ายเห็นได้จากปิง แต่ดีเลย์นี้ไม่ปรากฏในปิง จึงเป็นจุดแรกที่ควรตรวจเมื่อมีการแจ้งว่า “ปิงต่ำแต่แลค” frame generation ทำให้ตัวเลข FPS บนจอสูงขึ้นราวสองเท่า แต่การสร้างเฟรมคั่นกลางต้องรอเฟรมจริงถัดไปก่อน เวลาที่อินพุตจะแสดงบนจอจึงยาวขึ้น (AMD ระบุว่าโดยการออกแบบแล้วดีเลย์จะเพิ่มขึ้น) อุปกรณ์ Bluetooth ใช้ย่าน 2.4 GHz เดียวกับ Wi-Fi ถ้าถูกรบกวน อินพุตอาจขาดหายหรือกระโดดได้ V-Sync และคิวเรนเดอร์ที่ทำให้ดีเลย์ใน PC เพิ่มขึ้น ดูได้ในรายการ “V-Sync และคิวเรนเดอร์” - แหล่งอ้างอิง: - [Auto Low Latency Mode (ALLM)](https://www.hdmi.org/spec21sub/autolowlatencymode) · HDMI Licensing Administrator · ALLM ทำให้อุปกรณ์สลับจอเป็นโหมด low latency (ที่มักเรียกว่า Game Mode) โดยอัตโนมัติ ในโหมดนี้ทีวีจะหยุดการประมวลผลภาพบางอย่างเพื่อลดดีเลย์ - [Xbox Series X: What’s the Deal with Latency?](https://news.xbox.com/en-us/2020/03/16/xbox-series-x-latency/) · Microsoft · อินพุตดีเลย์คือผลรวมของเส้นทางคอนโทรลเลอร์→คอนโซล→HDMI→ทีวี, คอนโทรลเลอร์รุ่นเก่าอ่านและส่งอินพุตทุก 8 ms, เวลาส่งหนึ่งเฟรมผ่าน HDMI คือ 60 Hz 16.6 ms และ 120 Hz 8.3 ms, ALLM สลับทีวีเป็น Game Mode อัตโนมัติ - [AMD FSR 3 game integrations out now + more details for developers](https://gpuopen.com/news/fsr3-in-games-technical-details/) · AMD · frame interpolation เพิ่มดีเลย์โดยการออกแบบ, แนะนำให้ใช้เมื่อเฟรมเรตก่อน interpolate อยู่ที่ 60 ขึ้นไป, อินพุต 60 FPS ส่งออกได้สูงสุด 120 FPS - [AMD FSR Frame Generation](https://gpuopen.com/amd-fsr-framegeneration/) · AMD · frame generation แนะนำให้ใช้ที่ 60 FPS ขึ้นไปก่อน interpolate (ควรเลี่ยงต่ำกว่า 30 FPS), AMD Radeon Anti-Lag 2 จัดจังหวะงานของ CPU และ GPU ให้สอดคล้องกันเพื่อลดดีเลย์ของระบบ - [NVIDIA DLSS](https://developer.nvidia.com/rtx/dlss) · NVIDIA · DLSS Frame Generation ออกแบบมาให้ใช้คู่กับ NVIDIA Reflex (ฟีเจอร์ลดดีเลย์) เพื่อรักษาการตอบสนอง - [Resolve Wi-Fi and Bluetooth issues caused by wireless interference](https://support.apple.com/en-us/102319) · Apple · สัญญาณรบกวนไร้สายทำให้การเชื่อมต่อของอุปกรณ์ Wi-Fi และ Bluetooth ขาดเป็นช่วง ๆ และประสิทธิภาพลดลง Bluetooth กับ Wi-Fi ใช้ย่าน 2.4 GHz เดียวกัน - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · MsAllInputToPhotonLatency (ดีเลย์จากอินพุตถึงจอ), DisplayLatency (ตั้งแต่ submit เฟรมจนส่งออกไปที่จอ), FrameType (แยกเฟรมที่แอปวาดกับเฟรมที่ไดรเวอร์หรือ SDK interpolate) - [PresentMon Console Application (README-ConsoleApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-ConsoleApplication.md) · Intel · MsAllInputToPhotonLatency ยึดอินพุตคีย์บอร์ดและเมาส์, FrameType จะถูกบันทึกเมื่อแอปหรือไดรเวอร์ส่ง event ของ Intel-PresentMon ออกมาเท่านั้น (--track_frame_type) - [Window.setPreferMinimalPostProcessing](https://developer.android.com/reference/android/view/Window#setPreferMinimalPostProcessing(boolean)) · Android (Google) · หน้าต่างที่ดีเลย์สำคัญอย่างเกมจะขอให้จอประมวลผลภาพน้อยที่สุด ถ้าเชื่อมต่อผ่าน HDMI จะส่งสัญญาณ ALLM และ Game Content Type เพื่อสลับทีวีเป็นโหมด low latency ### L3 เครือข่ายในบ้าน (10 สาเหตุ) #### hn-wifi · Wi-Fi ถูกรบกวนหรือสัญญาณอ่อน · Wi-Fi interference, weak signal ถ้าสัญญาณอ่อนหรือถูกรบกวน ช่วงไร้สายต้องส่งซ้ำหลายรอบ แพ็กเก็ตจึงมาถึงไม่สม่ำเสมอ - ทำไม → ผลคือ → บนหน้าจอ: คุณภาพสัญญาณแย่ลงเพราะผนัง, ระยะทาง, ไมโครเวฟ, Bluetooth และเราเตอร์ของเพื่อนบ้าน → ส่งในช่วงไร้สายไม่สำเร็จ → ส่งซ้ำหลายรอบ → แพ็กเก็ตมาถึงไม่สม่ำเสมอ (จิตเตอร์) ตัวละครจึงหยุดแวบเป็นระยะ ถ้าหนักจะแพ็กเก็ตหายจนวาร์ป - อาการ: กระตุก, วาร์ป, ดีดกลับ / ปัจจัย: จิตเตอร์, แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว, คนในบ้านเดียวกัน / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ตลอดเวลา - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ปรับความยาว interpolation buffer อัตโนมัติตามสภาพเน็ต, ถ้าจิตเตอร์หรือแพ็กเก็ตหายสูงให้แสดงสถานะเครือข่ายบนจอ - งานฝั่งภายนอก: แนะนำให้ผู้เล่นใช้สาย LAN, ใช้ 5 GHz หรือ 6 GHz, ย้ายตำแหน่งเราเตอร์ - ตัวเลขที่ควรรู้: การส่งซ้ำหนึ่งครั้งเพิ่มเวลาราว 1–4 ms ถ้าสัญญาณอ่อน จะส่งซ้ำหลายรอบด้วยความเร็วต่ำ และต้องรอจนช่องสัญญาณว่างด้วย จึงอาจพุ่งครั้งละ 50–200 ms กับดักคือปิงเฉลี่ยดูปกติ - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (RTT ถึงเราเตอร์) - จุดที่ต้องดู: วัด ping /t ไปที่อยู่ของเราเตอร์ (default gateway จาก ipconfig) สักหลายนาที และดูความแรงสัญญาณกับช่องสัญญาณของเราเตอร์เราด้วย netsh wlan show networks mode=bssid แล้วเปลี่ยนเป็นสาย LAN ที่จุดเดิมเพื่อเทียบ - สัญญาณว่าใช่: ตั้งแต่ปิงถึงเราเตอร์ก็พุ่งหลายสิบถึงหลายร้อย ms อย่างไม่สม่ำเสมอ มีแพ็กเก็ตหายบางครั้ง และสัญญาณอ่อน หายไปเมื่อใช้สาย LAN หรืออยู่ใกล้เราเตอร์ - สัญญาณว่าไม่ใช่: ถึงเราเตอร์สม่ำเสมอแต่พุ่งเฉพาะช่วงที่เลยออกไป: ให้ดูเน็ตและช่วงของ ISP ถ้าพุ่งเป็นจังหวะเท่ากันอย่างเดียว น่าจะเป็น “การสแกน Wi-Fi เบื้องหลัง” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: ใน mesh Wi-Fi ถ้าเราเตอร์ (node) เชื่อมกันเองแบบไร้สาย (wireless backhaul) node ที่ทำหน้าที่ส่งต่อจะส่งไม่ได้ระหว่างที่กำลังรับ และต้องแบ่งโอกาสการส่งกับช่วงก่อนหน้าและถัดไปที่ใช้ช่องสัญญาณเดียวกัน เมื่อแออัด throughput จึงลดลงและดีเลย์เพิ่มขึ้นได้ ผลิตภัณฑ์ที่มีย่านไร้สายเฉพาะสำหรับ backhaul อาจเป็นน้อยกว่า และถ้าเชื่อม node ด้วยสาย (Ethernet) ช่วงนี้ก็จะไม่ผ่านไร้สายอีก อะแดปเตอร์ Powerline (PLC) ก็ใช้วิธีตรวจว่าตัวกลางว่างก่อนส่ง (CSMA/CA) เหมือน Wi-Fi และคุณภาพเปลี่ยนไปตลอดตามสัญญาณรบกวนจากเครื่องใช้ไฟฟ้าและการเปิดปิดเครื่องใช้ไฟฟ้า จึงเกิดการส่งซ้ำและจิตเตอร์ได้ - กรณีจริง: ffxiv-2021 - แหล่งอ้างอิง: - [Resolve Wi-Fi and Bluetooth issues caused by wireless interference](https://support.apple.com/en-us/102319) · Apple · แหล่งสัญญาณรบกวนอย่างไมโครเวฟและโทรศัพท์ไร้สาย, Wi-Fi และ Bluetooth ใช้ย่าน 2.4 GHz เดียวกัน, แนะนำให้ย้ายไป 5 GHz และเลือกช่องสัญญาณที่มีสัญญาณรบกวนน้อย - [RFC 8325: Mapping Diffserv to IEEE 802.11](https://www.rfc-editor.org/rfc/rfc8325) · IETF · CSMA/CA ของ 802.11: ส่งเฉพาะตอนช่องสัญญาณว่าง ถ้าไม่ว่างจะเลื่อนไปจนว่าง แล้วรอเพิ่มอีกตามช่วง backoff แบบสุ่ม - [Ending the Anomaly: Achieving Low Latency and Airtime Fairness in WiFi (USENIX ATC 2017)](https://www.usenix.org/system/files/conference/atc17/atc17-hoiland-jorgensen.pdf) · USENIX · คิวเริ่มต้นของ Wi-Fi ที่มีโหลดทำให้ดีเลย์หลายร้อย ms อุปกรณ์ที่เชื่อมด้วยความเร็วต่ำ (สัญญาณอ่อน) ต่อให้ใช้ FQ-CoDel ค่ามัธยฐานก็เกิน 200 ms - [ping](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ping) · Microsoft · /t: ส่ง echo request ต่อไปเรื่อย ๆ จนกว่าจะสั่งหยุด - [ipconfig](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ipconfig) · Microsoft · ถ้ารันโดยไม่ใส่พารามิเตอร์ จะแสดงที่อยู่ IPv4/IPv6 และ default gateway ของแต่ละอะแดปเตอร์ - [Wireless network connectivity issues troubleshooting](https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/wireless-network-connectivity-issues-troubleshooting) · Microsoft · netsh wlan show networks mode=bssid: แสดง BSSID, ความแรงสัญญาณ, ช่องสัญญาณ และมาตรฐานไร้สายของ Wi-Fi แต่ละตัวที่มองเห็น - [Capacity of Ad Hoc Wireless Networks (MobiCom 2001)](https://pdos.csail.mit.edu/papers/grid:mobicom01/paper.pdf) · ACM · การส่งต่อหลายทอดผ่านไร้สาย 802.11 ทำให้ node ส่งไม่ได้ระหว่างรับ และช่วงก่อนหน้ากับถัดไปรบกวนกันเอง throughput ของเส้นทางที่ส่งต่อเป็นแถวเดียวจึงลดลงได้ถึง 1/3 ตามทฤษฎี (ในการจำลองราว 1/7) - [Electri-Fi Your Data: Measuring and Combining Power-Line Communications with WiFi (IMC 2015)](https://conferences.sigcomm.org/imc/2015/papers/p325.pdf) · ACM · อุปกรณ์สื่อสารผ่านสายไฟ (IEEE 1901, HomePlug AV) ที่ขายในท้องตลาดส่งด้วย CSMA/CA คล้าย Wi-Fi ความไม่เป็นธรรมในช่วงเวลาสั้น ๆ อาจทำให้จิตเตอร์สูงขึ้น และคุณภาพช่องสัญญาณเปลี่ยนไปตามสัญญาณรบกวนจากเครื่องใช้ไฟฟ้าและการเปิดปิดเครื่องใช้ไฟฟ้า (ระดับไม่กี่นาทีถึงหลายชั่วโมง) #### hn-channel · ช่องสัญญาณ Wi-Fi แออัด · Crowded Wi-Fi channel ในที่ที่มีเราเตอร์หลายสิบตัวอย่างคอนโดหรืออพาร์ตเมนต์ ต้องแบ่งกันใช้ช่องสัญญาณเดียวกัน จึงต้องรอโอกาสส่ง - ทำไม → ผลคือ → บนหน้าจอ: เราเตอร์หลายสิบตัวใช้ช่องสัญญาณ 2.4 GHz เดียวกัน → จะส่งได้ต้องรอให้อุปกรณ์อื่นส่งเสร็จจนช่องสัญญาณว่าง → ช่วงหัวค่ำที่คนกลับบ้าน จิตเตอร์ (ช่วงเวลาที่แพ็กเก็ตมาถึงไม่สม่ำเสมอ) เพิ่มขึ้นจนกระตุก - อาการ: กระตุก, อินพุตดีเลย์ / ปัจจัย: จิตเตอร์, ความหน่วง - ใครเจอ: คนในบ้านเดียวกัน / เกิดเมื่อไร: ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เมื่อจิตเตอร์เพิ่มขึ้น ให้ยืดความยาว interpolation buffer อัตโนมัติ - งานฝั่งภายนอก: แนะนำให้ผู้เล่นใช้ 5 GHz หรือ 6 GHz, ช่องสัญญาณที่ไม่แออัด, สาย LAN - บนกราฟ: สูงเฉพาะบางช่วงเวลา (RTT และจิตเตอร์ถึงเราเตอร์) - จุดที่ต้องดู: ดูช่องสัญญาณและความแรงสัญญาณของ Wi-Fi รอบตัวด้วย netsh wlan show networks mode=bssid แล้ววัดปิงถึงเราเตอร์ตอนหัวค่ำเทียบกับตอนกลางวัน - สัญญาณว่าใช่: เห็นเราเตอร์รอบตัวจำนวนมากบนช่องสัญญาณ 2.4 GHz เดียวกัน และจิตเตอร์ถึงเราเตอร์สูงเฉพาะช่วงหัวค่ำ ย้ายไป 5 GHz/6 GHz หรือช่องสัญญาณที่ไม่แออัดแล้วลดลง - สัญญาณว่าไม่ใช่: พุ่งโดยไม่เกี่ยวกับช่วงเวลา: น่าจะเป็น “Wi-Fi ถูกรบกวนหรือสัญญาณอ่อน” ถ้าถึงเราเตอร์ปกติแต่ตอนหัวค่ำช่วงที่เลยออกไปแย่ น่าจะเป็น “จุด peering แออัดช่วงพีค” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [Recommended settings for Wi-Fi routers and access points](https://support.apple.com/en-us/102766) · Apple · เราเตอร์และอุปกรณ์อื่นที่ใช้ช่องสัญญาณเดียวกันคือแหล่งสัญญาณรบกวน, 2.4 GHz แนะนำให้ใช้ความกว้าง 20 MHz, 5 GHz/6 GHz กังวลเรื่องสัญญาณรบกวนน้อยกว่า - [RFC 8325: Mapping Diffserv to IEEE 802.11](https://www.rfc-editor.org/rfc/rfc8325) · IETF · 802.11 จะเลื่อนการส่งเมื่อช่องสัญญาณไม่ว่างจนกว่าจะว่าง แล้วส่งหลัง backoff แบบสุ่ม (CSMA/CA) - [Wireless network connectivity issues troubleshooting](https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/wireless-network-connectivity-issues-troubleshooting) · Microsoft · netsh wlan show networks mode=bssid: แสดง BSSID, ความแรงสัญญาณ, ช่องสัญญาณ และมาตรฐานไร้สายของ Wi-Fi แต่ละตัวที่มองเห็น - [ping](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ping) · Microsoft · /t: ส่ง echo request ต่อไปเรื่อย ๆ จนกว่าจะสั่งหยุด #### hn-bufferbloat · bufferbloat (คิวในเราเตอร์) · Bufferbloat เมื่อมีคนในบ้านอัปโหลดวิดีโอหรือดาวน์โหลดไฟล์ใหญ่ แพ็กเก็ตจะกองอยู่ในคิวของเราเตอร์ยาวเป็นหลายร้อย ms และแพ็กเก็ตเกมก็ต้องรอต่อท้าย - ทำไม → ผลคือ → บนหน้าจอ: คนในบ้านอัปโหลดวิดีโอหรือสำรองข้อมูลขึ้นคลาวด์, เราสตรีมออกอากาศ หรือดาวน์โหลดไฟล์ใหญ่ จนเน็ตเต็ม → เราเตอร์หรือโมเด็มเก็บแพ็กเก็ตที่ล้นไว้ในคิวขนาดใหญ่ → แพ็กเก็ตเกมก็ต้องรอท้ายคิว ปิงพุ่งขึ้นไปหลายร้อย ms - อาการ: อินพุตดีเลย์, กรอเร็ว, วาร์ป / ปัจจัย: ความหน่วง, จิตเตอร์ - ใครเจอ: คนในบ้านเดียวกัน, เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ถ้าปิงขึ้นไปหลายร้อย ms กะทันหัน ให้แสดงสถานะเครือข่ายบนจอ (บอกว่าอาจมีการรับส่งข้อมูลจำนวนมากบนเน็ตเดียวกัน) - งานฝั่งภายนอก: แนะนำให้ผู้เล่นใช้เราเตอร์ที่มี SQM (fq_codel, CAKE) หรือ QoS, ตั้งความเร็ว SQM ไว้ที่ 90–95% ของความเร็วเน็ต (คิวจึงจะไปเกิดในเราเตอร์และได้ผล), จำกัดความเร็วอัปโหลด - ตัวเลขที่ควรรู้: เน็ตอัปโหลด 10 Mbps กับบัฟเฟอร์ 1 MB ทำให้คิวยาวได้ถึง 800 ms - บนกราฟ: สูงตามจำนวนคนและโหลด (RTT, ปริมาณการใช้อัปโหลด/ดาวน์โหลดของเน็ต) - จุดที่ต้องดู: เปิดปิงทิ้งไว้แล้วใช้การทดสอบความเร็วทำให้เน็ตเต็ม หรือใช้เว็บทดสอบที่วัดดีเลย์ขณะมีโหลด (ตามคำแนะนำของ Bufferbloat.net) แล้วดูคู่กับปริมาณการใช้อัปโหลด/ดาวน์โหลดในหน้าจอของเราเตอร์ - สัญญาณว่าใช่: ระหว่างที่อัปโหลดหรือดาวน์โหลดเต็มเน็ต ปิงขึ้นไปหลายร้อย ms และกลับมาเมื่อส่งเสร็จ (ถ้าดีเลย์ขณะมีโหลดเกิน 50 ms ให้สงสัย) เปิด SQM แล้วหายไป - สัญญาณว่าไม่ใช่: เน็ตว่างแต่ปิงยังพุ่ง: น่าจะเป็น “Wi-Fi ถูกรบกวนหรือสัญญาณอ่อน” หรือ “คุณภาพสายเน็ตแย่” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: ฝั่งอัปโหลดตันง่ายเป็นพิเศษ เพราะเน็ตเคเบิลและเครือข่ายมือถือมักมีอัปโหลดแคบกว่าดาวน์โหลดมาก บ้านที่ใช้ไฟเบอร์ความเร็วเหลือเฟือ ช่วง Wi-Fi จะกลายเป็นคอขวดแทน และเกิดเรื่องเดียวกันในคิวไร้สายของเราเตอร์ แพ็กเก็ตเกมมีขนาดเล็กจึงแทบไม่ใช้แบนด์วิดท์ แต่ก็ต้องรอในคิวเหมือนกัน ถ้าตันเฉพาะทิศอัปโหลด อินพุตของเราจะช้าอย่างเดียว ส่วนการเคลื่อนไหวของคนอื่นปกติ บนมือถือ การสำรองรูปภาพหรืออัปเดตแอปในเครื่องเดียวกันจะทำให้คิวของโมเด็มในมือถือและเสาสัญญาณเต็มจนเกิดเรื่องเดียวกัน - แหล่งอ้างอิง: - [Setting up SQM for CeroWrt 3.10](https://www.bufferbloat.net/projects/cerowrt/wiki/Setting_up_SQM_for_CeroWrt_310/) · Bufferbloat.net · ต้องลดความเร็ว SQM ลงเป็น 95% ของความเร็วที่วัดได้จริง (ถ้าอิงความเร็วที่โฆษณาคือ 85%) เพื่อย้ายคอขวดจากอุปกรณ์ของ ISP มาไว้ในเราเตอร์ จึงจะได้ผล - [SQM (Smart Queue Management)](https://openwrt.org/docs/guide-user/network/traffic-shaping/sqm) · OpenWrt · ใส่ความเร็วดาวน์โหลดและอัปโหลดเป็น 90% ของค่าที่วัดได้จริง แนะนำ queue discipline แบบ cake (ถ้า CPU อ่อนให้ใช้ fq_codel) - [Ending the Anomaly: Achieving Low Latency and Airtime Fairness in WiFi (USENIX ATC 2017)](https://www.usenix.org/system/files/conference/atc17/atc17-hoiland-jorgensen.pdf) · USENIX · เมื่อช่วง Wi-Fi เต็ม จะเกิดดีเลย์หลายร้อย ms ในคิวไร้สายของเราเตอร์ ถ้าแก้คิวไร้สาย ดีเลย์ขณะมีโหลดจะลดลงเหลือราว 1/10 - [Tests for Bufferbloat](https://www.bufferbloat.net/projects/bloat/wiki/Tests_for_Bufferbloat/) · Bufferbloat.net · ถ้าเปิดปิงทิ้งไว้แล้วใช้การทดสอบความเร็วทำให้เน็ตเต็ม แล้วปิงขึ้น คือ bufferbloat ถ้าดีเลย์ขณะมีโหลดเกิน 50 ms (หรือต่ำกว่าเกรด B) แนะนำให้แก้ไข #### hn-nat · NAT mapping หมดอายุ · NAT mapping timeout เราเตอร์จะลบการเชื่อมต่อที่ idle (ไม่มีแพ็กเก็ตวิ่งมาสักพัก) ออกจากตาราง NAT นี่คือสาเหตุที่พบบ่อยของอาการหลุดตอนที่เพิ่งขยับหลังจากอยู่เฉย ๆ - ทำไม → ผลคือ → บนหน้าจอ: เราเตอร์บันทึกการเชื่อมต่อ “อุปกรณ์ข้างใน ↔ เซิร์ฟเวอร์ข้างนอก” ไว้ในตาราง NAT (ตารางแปลงที่อยู่) → ถ้าไม่มีแพ็กเก็ตสักพัก จะลบออกจากตาราง (UDP มักอยู่ที่ 30–120 วินาที) → แพ็กเก็ตจากเซิร์ฟเวอร์เข้ามาในบ้านไม่ได้ จึงหลุด - อาการ: หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว, คนในบ้านเดียวกัน / เกิดเมื่อไร: หลังอยู่เฉย ๆ สักพัก - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ส่ง heartbeat ทุกช่วงไม่เกินครึ่งหนึ่งของ idle timeout ที่สั้นที่สุด (UDP mapping จะต่ออายุได้แน่นอนก็ต่อเมื่อมีแพ็กเก็ตขาออกจากในบ้าน ไคลเอนต์จึงต้องเป็นฝ่ายส่ง), เชื่อมต่อใหม่อัตโนมัติเมื่อหลุด เซิร์ฟเวอร์: ตอบ heartbeat และถ้าไม่ได้รับนานเกินกำหนดให้ปิดการเชื่อมต่อก่อน, ถ้า mapping ถูกลบจนที่อยู่และพอร์ตภายนอกเปลี่ยน ให้ยืนยันว่าเป็นผู้เล่นคนเดิมด้วย session token (รหัสยืนยันที่ได้ตอนเชื่อมต่อ) แล้วรับช่วงต่อ - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนครั้งที่หลุด (heartbeat timeout), เวลา idle ก่อนหลุด) - จุดที่ต้องดู: รวบรวมสาเหตุการหลุดที่เซิร์ฟเวอร์บันทึกไว้ และเวลาที่ผ่านไปตั้งแต่แพ็กเก็ตสุดท้ายบนการเชื่อมต่อนั้นจนหลุด (เวลา idle) แล้วดูเป็นการกระจาย สำหรับการทดสอบ ให้ยืดช่วงห่างของแพ็กเก็ต UDP เป็น 30, 60 และ 120 วินาที แล้ววัดว่าช่วงห่างเท่าไรที่การตอบกลับขาดไป - สัญญาณว่าใช่: หลุดเฉพาะการเชื่อมต่อที่อยู่เฉย ๆ และเวลา idle กระจุกอยู่ถัดจากค่าหนึ่ง เช่น 30–120 วินาที ถ้าตั้งช่วงห่าง heartbeat ให้สั้นกว่านั้นแล้วหายไป - สัญญาณว่าไม่ใช่: หลุดแม้ระหว่างกำลังขยับ: ให้ดูเน็ตและเส้นทาง ถ้ากระจุกที่ค่าสั้น ๆ เฉพาะบางค่ายมือถือ น่าจะเป็น “IP ที่ ISP ให้ใช้ร่วมกัน (CGNAT)” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [RFC 4787: Network Address Translation (NAT) Behavioral Requirements for Unicast UDP](https://www.rfc-editor.org/rfc/rfc4787) · IETF · ตัวจับเวลาของ UDP mapping ต้องไม่หมดก่อน 2 นาที และแนะนำค่าเริ่มต้นตั้งแต่ 5 นาทีขึ้นไป การต่ออายุด้วยแพ็กเก็ตขาออกจากข้างในเป็นข้อบังคับ ส่วนการต่ออายุด้วยแพ็กเก็ตขาเข้าจากข้างนอกเป็นทางเลือก - [An Experimental Study of Home Gateway Characteristics (IMC 2010)](https://conferences.sigcomm.org/imc/2010/papers/p260.pdf) · ACM · ผลวัดเราเตอร์บ้าน 34 รุ่น: UDP mapping อยู่ได้ 30–691 วินาที ค่ามัธยฐาน 90 วินาที เกินครึ่งต่ำกว่า 2 นาที ส่วน TCP ค่ามัธยฐานราว 60 นาที - [RFC 9308: Applicability of the QUIC Transport Protocol](https://www.rfc-editor.org/rfc/rfc9308) · IETF · บนอินเทอร์เน็ตที่มี NAT อยู่ในเส้นทาง keep-alive ทุกราว 30 วินาทีกำลังพอดี ถี่กว่านั้นจะเปลืองทราฟฟิกและพลังงาน #### hn-router · เราเตอร์สเปกไม่พอหรือร้อนเกิน · Router CPU / session table exhaustion เมื่ออุปกรณ์หลายสิบเครื่องและการเชื่อมต่อหลายพันรายการมารวมที่เราเตอร์ราคาถูก ตัวเราเตอร์เองจะประมวลผลไม่ไหว - ทำไม → ผลคือ → บนหน้าจอ: อุปกรณ์หลายสิบเครื่อง และ P2P/ทอร์เรนต์เปิดการเชื่อมต่อหลายพันรายการ → CPU และตารางเซสชันของเราเตอร์เต็ม → ประมวลผลแพ็กเก็ตช้าหรือแพ็กเก็ตหาย, เชื่อมต่อใหม่ไม่สำเร็จ - อาการ: กระตุก, เข้าเกมไม่ได้/โหลดไม่จบ, หลุด / ปัจจัย: แพ็กเก็ตหาย, จิตเตอร์ - ใครเจอ: คนในบ้านเดียวกัน / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) - งานฝั่งภายนอก: แนะนำให้ผู้เล่นรีบูตเราเตอร์ (แก้ชั่วคราว), เปลี่ยนเราเตอร์, ปิดโปรแกรมที่เปิดการเชื่อมต่อจำนวนมาก (P2P, ทอร์เรนต์) - บนกราฟ: ชนเพดานแล้วแบนราบ (CPU และจำนวนการเชื่อมต่อของเราเตอร์, RTT ถึงเราเตอร์) - จุดที่ต้องดู: ดูอัตราการใช้ CPU, จำนวนการเชื่อมต่อ (เซสชัน) และจำนวนอุปกรณ์ที่เชื่อมอยู่ในหน้าจัดการเราเตอร์ (ถ้ารุ่นนั้นรองรับ) แล้วเทียบปิงถึงตัวเราเตอร์ก่อนและหลังรีบูต - สัญญาณว่าใช่: เมื่อการเชื่อมต่อมาก ปิงถึงเราเตอร์ก็เริ่มพุ่งหรือแพ็กเก็ตหาย และเชื่อมต่อใหม่ไม่สำเร็จ รีบูตแล้วดีอยู่พักหนึ่งก่อนกลับมาแย่อีก - สัญญาณว่าไม่ใช่: ถึงเราเตอร์ปกติแต่ช่วงที่เลยออกไปแย่: ให้ดูเน็ตและช่วงของ ISP - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [An Experimental Study of Home Gateway Characteristics (IMC 2010)](https://conferences.sigcomm.org/imc/2010/papers/p260.pdf) · ACM · จำนวนการเชื่อมต่อ TCP ที่เราเตอร์บ้านยอมให้ต่อไปยังพอร์ตเซิร์ฟเวอร์เดียวคือ 16 ถึงราว 1,024 (ค่ามัธยฐาน 135) และอุปกรณ์ราคาถูกบางรุ่นมี throughput แค่ไม่กี่ Mbps - [Netfilter Conntrack Sysfs variables](https://docs.kernel.org/networking/nf_conntrack-sysctl.html) · Linux kernel · จำนวนรายการสูงสุดในตาราง connection tracking ของ Linux (nf_conntrack_max) และค่าเริ่มต้นของระยะเวลาที่เก็บไว้ในแต่ละสถานะ #### hn-handover · handover ระหว่างเสาสัญญาณ (ขณะเดินทาง) · Cellular handover เมื่อเดินทางด้วยรถเมล์หรือรถไฟใต้ดิน การสื่อสารจะขาดช่วงระหว่างที่เปลี่ยนเสาสัญญาณ - ทำไม → ผลคือ → บนหน้าจอ: เสาสัญญาณที่เชื่อมต่อเปลี่ยนไประหว่างเดินทาง → ปกติขาดไปหลายสิบ ms แต่ถ้าสัญญาณแย่จนสลับไม่สำเร็จ อาจขาดไปหลายร้อย ms ถึงหลายวินาที → ค้างแล้ววาร์ป ถ้านานจะหลุด - อาการ: ค้าง, วาร์ป, หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ตั้งไทม์เอาต์ให้ทนการขาดช่วงสั้น ๆ ได้, เชื่อมต่อใหม่ให้เร็ว เซิร์ฟเวอร์: ตั้งไทม์เอาต์ที่ไม่เตะออกทันทีแม้ขาดไปไม่กี่วินาที, เมื่อเชื่อมต่อใหม่ให้ต่อเข้าเซสชันเดิม - งานฝั่งภายนอก: แจ้งผู้เล่นว่าการหลุดระหว่างเดินทาง (รถเมล์, รถไฟใต้ดิน) เกิดจากการสลับเสาสัญญาณ - บนกราฟ: ขาดช่วงแล้วมารวดเดียว (จำนวนแพ็กเก็ตที่ได้รับ, RTT) - จุดที่ต้องดู: ตรวจว่าตอนที่แจ้งว่าหลุดกำลังเดินทางอยู่หรือไม่ (รถเมล์, รถไฟใต้ดิน) และดูเวลาที่การรับข้อมูลขาดช่วงใน log ของไคลเอนต์คู่กับการเปลี่ยนชนิดเครือข่ายและสัญญาณ - สัญญาณว่าใช่: เฉพาะระหว่างเดินทาง การรับข้อมูลว่างไปหลายร้อย ms ถึงหลายวินาทีแล้วมารวดเดียว และทำซ้ำไม่ได้ตอนอยู่กับที่ - สัญญาณว่าไม่ใช่: อยู่กับที่ก็เป็นเหมือนกัน: น่าจะเป็น “สัญญาณมือถืออ่อนหรืออยู่ในจุดอับสัญญาณ” หรือ “สลับ 5G↔LTE บ่อย (บริเวณขอบพื้นที่ 5G)” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [Report ITU-R M.2134: Requirements related to technical performance for IMT-Advanced radio interface(s)](https://www.itu.int/pub/R-REP-M.2134-2008) · ITU · ข้อกำหนดของเวลาที่รับส่งข้อมูลไม่ได้ระหว่าง handover: ความถี่เดียวกัน 27.5 ms, ต่างความถี่ 40–60 ms - [Understanding Operational 5G: A First Measurement Study on Its Coverage, Performance and Energy Consumption (SIGCOMM 2020)](https://xyzhang.ucsd.edu/papers/DXu_SIGCOMM20_5Gmeasure.pdf) · ACM · ดีเลย์ handover ที่วัดจริงบนเครือข่ายเชิงพาณิชย์: 4G↔4G เฉลี่ย 30 ms, ระหว่างเซลล์ 5G (NSA) เฉลี่ย 108 ms #### hn-rrc · ดีเลย์จากการเปลี่ยนสถานะ RRC (โหมดประหยัดพลังงานของวิทยุมือถือ) · Radio state promotion (RRC) ถ้าไม่มีการสื่อสารสักพัก มือถือจะลดการเชื่อมต่อวิทยุลงเป็นสถานะพลังงานต่ำ และเมื่อมีแพ็กเก็ตถัดไปต้องยกกลับขึ้นมาใหม่ จึงช้าลง - ทำไม → ผลคือ → บนหน้าจอ: ไม่มีการสื่อสารสักพัก มือถือจึงสลับการเชื่อมต่อวิทยุเป็นสถานะประหยัดพลังงาน → จะส่งแพ็กเก็ตถัดไปได้ต้องยกการเชื่อมต่อกลับขึ้นมาก่อน → เฉพาะแอ็กชันแรกหลังอยู่เฉย ๆ ช้าเป็นพิเศษ - อาการ: อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: หลังอยู่เฉย ๆ สักพัก - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ส่งข้อมูลเบา ๆ เป็นระยะเพื่อคงสถานะ active (แลกกับแบตเตอรี่) - ตัวเลขที่ควรรู้: ปกติ LTE จะลงไปสถานะประหยัดพลังงานเมื่อไม่มีการสื่อสารราว 10 วินาที และใช้เวลาหลายสิบถึงหลายร้อย ms กว่าจะยกกลับขึ้นมา (ตัวอย่างที่วัดจริง: ราว 0.3–0.6 วินาที) ส่วน 3G นานกว่า 1 วินาที - บนกราฟ: สูงเฉพาะบางกลุ่ม (RTT ของคำขอแรกหลัง idle (มือถือ)) - จุดที่ต้องดู: แยก RTT ในเกมตามช่วงห่างจากการสื่อสารครั้งก่อน เทียบ RTT ของแพ็กเก็ตแรกที่ส่งหลังพักเกิน 10 วินาทีบนมือถือ กับแพ็กเก็ตที่ส่งต่อเนื่องกัน - สัญญาณว่าใช่: บนเครือข่ายมือถือ เฉพาะแพ็กเก็ตแรกหลังพักช้าไปหลายร้อย ms ส่วนแพ็กเก็ตที่ส่งตามติดกันปกติ บน Wi-Fi ไม่มีความต่าง - สัญญาณว่าไม่ใช่: ส่งต่อเนื่องก็ยังช้า: ให้ดูสัญญาณ, เน็ต และเส้นทาง - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [An In-depth Study of LTE: Effect of Network Protocol and Application Behavior on Performance (SIGCOMM 2013)](https://conferences.sigcomm.org/sigcomm/2013/papers/sigcomm/p363.pdf) · ACM · ตัวจับเวลาเปลี่ยนเข้าสถานะประหยัดพลังงาน (tail) ของเครือข่าย LTE ที่วัดคือ 10 วินาที ดีเลย์ในการยกกลับจากสถานะประหยัดพลังงานมีค่ามัธยฐาน 435 ms (25–75%: 319–558 ms) ส่วน 3G ราว 1.5–2 วินาที - [Optimize network access](https://developer.android.com/develop/connectivity/network-ops/network-access-optimization) · Android (Google) · ดีเลย์การเปลี่ยนสถานะวิทยุและเวลา tail ต่างกันไปตามเทคโนโลยีไร้สาย (3G, LTE, 5G) และการตั้งค่าของค่ายมือถือ ตัวอย่าง 3G: จากพลังงานต่ำไปพลังงานสูงสุดราว 1.5 วินาที จาก idle ไปพลังงานสูงสุดเกิน 2 วินาที - [Report ITU-R M.2134: Requirements related to technical performance for IMT-Advanced radio interface(s)](https://www.itu.int/pub/R-REP-M.2134-2008) · ITU · ข้อกำหนดดีเลย์ของ control plane ในการเปลี่ยนจากสถานะ idle เป็น active ต้องต่ำกว่า 100 ms (ไม่รวม paging และช่วงสาย) #### hn-weak-cell · สัญญาณมือถืออ่อนหรืออยู่ในจุดอับสัญญาณ · Weak cellular signal ในลิฟต์, ชั้นใต้ดิน หรือด้านในอาคาร การส่งซ้ำจะเพิ่มขึ้น ความเร็วลดลง และสุดท้ายก็หลุด - ทำไม → ผลคือ → บนหน้าจอ: เคลื่อนที่ไปยังจุดที่สัญญาณอ่อน → การส่งซ้ำทางวิทยุเพิ่มขึ้น, ความเร็วลดลง, การเชื่อมต่อขาดชั่วขณะ → จิตเตอร์และแพ็กเก็ตหายทำให้กระตุกหรือวาร์ป สุดท้ายก็หลุด - อาการ: กระตุก, วาร์ป, หลุด / ปัจจัย: จิตเตอร์, แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ปรับปรุงขั้นตอนการเชื่อมต่อใหม่, แสดงคุณภาพเครือข่าย - งานฝั่งภายนอก: แจ้งผู้เล่นว่าเป็นปัญหาที่เกิดในจุดที่สัญญาณอ่อน (ลิฟต์, ชั้นใต้ดิน, ด้านในอาคาร) - บนกราฟ: สูงเฉพาะบางกลุ่ม (RTT และแพ็กเก็ตหาย (แยกตามผู้เล่นมือถือ)) - จุดที่ต้องดู: ตรวจสถานที่ตอนที่แจ้งว่าหลุด (ลิฟต์, ชั้นใต้ดิน, ในอาคาร) และขีดสัญญาณบนมือถือ แล้วลองทำแบบเดิมซ้ำในจุดที่สัญญาณดีเพื่อเทียบ - สัญญาณว่าใช่: RTT และแพ็กเก็ตหายเพิ่มขึ้นจนหลุดเฉพาะในจุดที่สัญญาณอ่อน และหายไปเมื่อย้ายไปที่สัญญาณดี - สัญญาณว่าไม่ใช่: สัญญาณดีแต่ยังเป็นเหมือนกัน: ให้ดูช่วงของ ISP หรือฝั่งเซิร์ฟเวอร์ - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [An In-depth Study of LTE: Effect of Network Protocol and Application Behavior on Performance (SIGCOMM 2013)](https://conferences.sigcomm.org/sigcomm/2013/papers/sigcomm/p363.pdf) · ACM · LTE ซ่อนการหายในช่วงวิทยุด้วยการส่งซ้ำที่ชั้น physical และ MAC แบนด์วิดท์ที่ใช้ได้เปลี่ยนแปลงมากในระดับวินาทีตามความแรงสัญญาณและปัจจัยอื่น #### hn-5g-flip · สลับ 5G↔LTE บ่อย (บริเวณขอบพื้นที่ 5G) · 5G NSA / LTE switching ในอาคารที่สัญญาณ 5G อ่อน หรือบริเวณขอบพื้นที่ 5G มือถือจะสลับไปมาระหว่าง 5G กับ LTE บ่อย และทุกครั้งที่สลับ ปิงจะพุ่งหรือการสื่อสารขาดไปครู่หนึ่ง - ทำไม → ผลคือ → บนหน้าจอ: อยู่ในจุดที่สัญญาณ 5G ไม่สม่ำเสมอ (ในอาคาร, ขอบพื้นที่ 5G) → มือถือสลับระหว่าง 5G กับ LTE อยู่เรื่อย ๆ และทุกครั้งเกิดช่องว่างสั้น ๆ → แม้อยู่เฉย ๆ ปิงก็พุ่งแบบไม่มีแบบแผน บางครั้งค้างหรือวาร์ป - อาการ: กระตุก, วาร์ป, ค้าง / ปัจจัย: จิตเตอร์, แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เมื่อจิตเตอร์เพิ่มขึ้น ให้ยืดความยาว interpolation buffer อัตโนมัติ, บันทึกการเปลี่ยนชนิดเครือข่าย (5G/LTE) ไว้ใน log ตอนที่เกิดแลคด้วยเพื่อแยกสาเหตุ - งานฝั่งภายนอก: แนะนำให้ผู้เล่นลองเปลี่ยนเป็นโหมดใช้ LTE เป็นหลักในการตั้งค่าแล้วเทียบ, แนะนำให้ใช้ Wi-Fi - ตัวเลขที่ควรรู้: สลับหนึ่งครั้งใช้เวลาหลายสิบถึงหลายร้อย ms 5G ในเกาหลีส่วนใหญ่เป็นแบบที่ใช้ร่วมกับ LTE (NSA) ฝั่ง 5G จึงเชื่อมต่อแล้วขาดสลับไปมาได้ง่าย - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (RTT, การเปลี่ยนชนิดเครือข่าย (5G/LTE)) - จุดที่ต้องดู: ตั้งมือถือเป็นโหมดใช้ LTE เป็นหลักแล้วเทียบที่จุดเดิม ถ้าไคลเอนต์บันทึกการเปลี่ยนสัญลักษณ์เครือข่ายจาก TelephonyDisplayInfo ของ Android (OVERRIDE_NETWORK_TYPE_NR_NSA ฯลฯ) คู่กับ RTT จะยืนยันได้ชัดขึ้น - สัญญาณว่าใช่: เวลาที่ RTT พุ่งตรงกับเวลาที่สัญลักษณ์ 5G↔LTE เปลี่ยน และในโหมดใช้ LTE เป็นหลักไม่พุ่งอีก - สัญญาณว่าไม่ใช่: สัญลักษณ์เครือข่ายไม่เปลี่ยนแต่ยังพุ่ง: น่าจะเป็น “สัญญาณมือถืออ่อนหรืออยู่ในจุดอับสัญญาณ” หรือฝั่งเน็ต - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [Understanding Operational 5G: A First Measurement Study on Its Coverage, Performance and Energy Consumption (SIGCOMM 2020)](https://xyzhang.ucsd.edu/papers/DXu_SIGCOMM20_5Gmeasure.pdf) · ACM · 5G แบบ NSA ฝากการควบคุมไว้กับ LTE เวลาเปลี่ยนเซลล์ 5G จึงต้องปล่อย 5G แล้วผ่าน LTE ก่อนกลับมาต่อใหม่: เฉลี่ย 108 ms (4G→5G 80 ms) และ TCP throughput ลดลง 73–83% ทันทีหลังการสลับที่มี 5G เกี่ยวข้อง - [5G 통신서비스 품질평가 결과 발표](https://www.korea.kr/briefing/policyBriefingView.do?newsId=156404679) · 과학기술정보통신부 · ตามประกาศปี 2020 5G ในเกาหลีให้บริการแบบ NSA และอยู่ในขั้นวางแผนเปลี่ยนเป็น SA - [TelephonyDisplayInfo](https://developer.android.com/reference/android/telephony/TelephonyDisplayInfo) · Android (Google) · OVERRIDE_NETWORK_TYPE_NR_NSA: สัญลักษณ์เครือข่ายเมื่อเชื่อมต่อ LTE อยู่และสามารถเชื่อมต่อคู่ (EN-DC) กับ 5G (NR) ได้หรือเชื่อมต่ออยู่ #### hn-captive · ข้อจำกัดของ Wi-Fi สาธารณะและเครือข่ายบริษัท · Captive portal, restrictive network หน้าล็อกอิน Wi-Fi ของร้านกาแฟหรือไฟร์วอลล์ของบริษัทบล็อกการเชื่อมต่อของเกม - ทำไม → ผลคือ → บนหน้าจอ: ยังไม่ได้ยืนยันตัวตนในหน้าล็อกอิน หรือไฟร์วอลล์บล็อกพอร์ตเกมหรือ UDP → ความพยายามเชื่อมต่อถูกบล็อกทั้งหมด หรือผ่านได้แค่บางส่วน → เข้าเกมไม่ได้ หรือล็อกอินได้แต่เข้าไปในเกมไม่สำเร็จ - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: แสดงเหตุผลเมื่อถูกบล็อก (ยังไม่ได้ยืนยันตัวตนในหน้าล็อกอิน, UDP ถูกบล็อก ฯลฯ), ถ้า UDP ถูกบล็อกให้สลับไปใช้เส้นทางสำรองอัตโนมัติ เซิร์ฟเวอร์: มีเส้นทางสำรอง เช่น TCP 443 - งานฝั่งภายนอก: แนะนำผู้เล่นว่า Wi-Fi สาธารณะต้องยืนยันตัวตนในหน้าล็อกอินก่อน และในเครือข่ายที่ถูกบล็อกอย่างเครือข่ายบริษัทให้ใช้เครือข่ายอื่น - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนการเชื่อมต่อล้มเหลว (แยกตามเครือข่าย)) - จุดที่ต้องดู: ให้ผู้เล่นที่เชื่อมต่อไม่สำเร็จลองเปลี่ยนไปใช้เครือข่ายอื่น เช่น เน็ตมือถือ แล้วดูใน log การเชื่อมต่อของเซิร์ฟเวอร์ว่าแพ็กเก็ต UDP แรกมาถึงหรือไม่ และเชื่อมต่อผ่านเส้นทางสำรอง TCP 443 ได้หรือไม่ - สัญญาณว่าใช่: ล้มเหลวเฉพาะบาง Wi-Fi (ร้านกาแฟ, บริษัท) และเชื่อมต่อได้ทันทีบนเครือข่ายอื่น ยังไม่ได้ยืนยันตัวตนในหน้าล็อกอิน หรือมีแต่ UDP ที่มาไม่ถึงเซิร์ฟเวอร์ - สัญญาณว่าไม่ใช่: ล้มเหลวทุกเครือข่าย: ให้ดูบัญชี, เซิร์ฟเวอร์ หรือ “DNS ขัดข้อง/ช้า” ถ้าล้มเหลวทั้งประเทศหรือทั้ง ISP น่าจะเป็น “การจำกัด UDP และตรวจแพ็กเก็ตระดับประเทศหรือ ISP” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [RFC 8952: Captive Portal Architecture](https://www.rfc-editor.org/rfc/rfc8952) · IETF · captive portal: เครือข่ายที่จำกัดการเชื่อมต่อจนกว่าจะทำตามเงื่อนไข เช่น ยอมรับข้อตกลงหรือยืนยันตัวตน - [RFC 9308: Applicability of the QUIC Transport Protocol](https://www.rfc-editor.org/rfc/rfc9308) · IETF · ตามงานวิจัยที่วัดผล 3–5% ของเครือข่ายบล็อก UDP ทั้งหมด แอปที่ใช้ UDP จึงต้องเตรียมเส้นทางสำรองผ่าน TCP (TLS) ### L4 เส้นทางอินเทอร์เน็ต (14 สาเหตุ) #### isp-distance · ความหน่วงในการแพร่สัญญาณ (ระยะทางจริง) · Propagation delay แม้แต่แสงในสายไฟเบอร์ก็เดินทางได้แค่ประมาณ 200,000 กิโลเมตรใน 1 วินาที เซิร์ฟเวอร์ที่อยู่ไกลจะดีแค่ไหนก็ยังช้า - ทำไม → ผลคือ → บนหน้าจอ: เซิร์ฟเวอร์อยู่ไกล (เซิร์ฟเวอร์ต่างประเทศ, อีกทวีป) → เวลาไปกลับเพิ่มขึ้นตามระยะทาง (อย่างน้อย 10 ms ต่อ 1,000 กิโลเมตร) → ทุกการกระทำมีอินพุตดีเลย์คงที่ และเสียเปรียบในการตัดสินผล - อาการ: อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: อินฟราเครือข่าย (ทีมอินฟรา), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แก้กฎฟิสิกส์ด้วยโค้ดไม่ได้ จึงทำได้แค่บรรเทา, ทำระบบเลือกภูมิภาคให้ผู้เล่นเลือกเซิร์ฟเวอร์ที่ใกล้, ใช้ lag compensation (การย้อนเวลา) ลดความเสียเปรียบในการตัดสินผล - งานฝั่งทีมอินฟรา: เครื่องเซิร์ฟเวอร์/OS: ตั้งเซิร์ฟเวอร์แยกตามภูมิภาคในพื้นที่ที่มีผู้เล่นมาก เครือข่าย: ตั้งจุดเชื่อมต่อ (edge) ไว้ใกล้ผู้เล่น, เลือกวงจรและเส้นทางที่อ้อมน้อย - ตัวเลขที่ควรรู้: โซล–โตเกียว ประมาณ 30 ms, โซล–สิงคโปร์ ประมาณ 75 ms, โซล–สหรัฐฯ ฝั่งตะวันตก ประมาณ 140 ms, โซล–ยุโรป ประมาณ 230–270 ms (ไปกลับ ตามเส้นทางจริง) เส้นทางไปยุโรปแทบไม่มีเคเบิลใหญ่ที่วิ่งตามแนวเส้นตรง จึงต้องอ้อมผ่านเอเชียตะวันออกเฉียงใต้และสุเอซ หรืออ้อมผ่านสหรัฐฯ ทำให้นานกว่าที่ระยะทางบอกไว้มาก - บนกราฟ: สูงตลอดตั้งแต่แรก (RTT (แยกตามประเทศ/ภูมิภาค)) - จุดที่ต้องดู: ระบุประเทศจาก IP ที่เชื่อมต่อแล้วดูการกระจายของ RTT แยกตามประเทศ และวัดด้วย ping กับ traceroute ไปยังเซิร์ฟเวอร์จาก VM ในคลาวด์รีเจียนของพื้นที่นั้น หรือจาก probe ของ RIPE Atlas (เลือกตามประเทศหรือ ASN) - สัญญาณว่าใช่: RTT ของประเทศที่อยู่ไกลสูงตลอดไม่ว่าช่วงเวลาไหน และใกล้กับความหน่วงขั้นต่ำที่คำนวณจากระยะทาง (ไปกลับ 10 ms ต่อ 1,000 กิโลเมตร) และสถิติความหน่วงที่เปิดเผยต่อสาธารณะ - สัญญาณว่าไม่ใช่: ถ้าสูงกว่าที่ระยะทางอธิบายได้มาก น่าจะเป็น “เส้นทางวิ่งอ้อม” ถ้าสูงเฉพาะหัวค่ำ น่าจะเป็น “จุด peering แออัดช่วงพีค” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - กรณีจริง: riot-direct-2015 - แหล่งอ้างอิง: - [ITU-T G.114: One-way transmission time](https://www.itu.int/rec/T-REC-G.114-200305-I/en) · ITU · ค่าที่ใช้วางแผนสำหรับความหน่วงในการแพร่สัญญาณในสายไฟเบอร์ 5 µs/km (ประมาณ 200,000 กิโลเมตรต่อวินาที, ไปกลับ 1,000 กิโลเมตรใช้ 10 ms) - [Azure network round-trip latency statistics](https://learn.microsoft.com/en-us/azure/networking/azure-network-latency) · Microsoft Azure · ค่ามัธยฐานเวลาไปกลับที่วัดจริงจากโซล (Korea Central): โตเกียว 30 ms, สิงคโปร์ 68 ms, สหรัฐฯ ฝั่งตะวันตก 124–136 ms, ยุโรป 234–244 ms - [AAE-1 & SMW5 cable cuts impact millions of users across multiple countries](https://blog.cloudflare.com/aae-1-smw5-cable-cuts/) · Cloudflare · ทราฟฟิกระหว่างยุโรปกับเอเชียส่วนใหญ่วิ่งผ่านเคเบิลใต้น้ำที่ผ่านอียิปต์ (สุเอซ) - [Probe Selection (RIPE Atlas REST API)](https://atlas.ripe.net/docs/apis/rest-api-manual/measurements/creating-measurements/probe-selection/) · RIPE NCC · เลือก probe ของ RIPE Atlas ตามประเทศ, ภูมิภาค, ASN หรือช่วง IP แล้วรัน ping และ traceroute #### isp-satellite · อินเทอร์เน็ตดาวเทียม (วงโคจรต่ำ/ค้างฟ้า) · Satellite internet (LEO, GEO) อินเทอร์เน็ตดาวเทียมต้องส่งสัญญาณขึ้นไปในอวกาศแล้วกลับลงมา ดาวเทียมวงโคจรค้างฟ้าแค่ช่วงขึ้นลงนี้ก็ใช้เวลาไปกลับเกิน 0.5 วินาทีแล้ว ส่วนดาวเทียมวงโคจรต่ำอย่าง Starlink ปกติเร็ว แต่ตอนที่ระบบจัดเส้นทางใหม่ ความหน่วงจะแกว่งและบางครั้งขาดไปชั่วครู่ - ทำไม → ผลคือ → บนหน้าจอ: เชื่อมต่อจากบ้าน เรือ หรือเครื่องบิน ผ่านอินเทอร์เน็ตดาวเทียมวงโคจรค้างฟ้าหรือวงโคจรต่ำ หรือผ่าน Wi-Fi บนเครื่องบินที่ใช้ดาวเทียม → วงโคจรค้างฟ้าอยู่สูงประมาณ 36,000 กิโลเมตร ระยะทางขึ้นลงจึงยาวอยู่แล้ว ส่วนวงโคจรต่ำจัดเส้นทางระหว่างอุปกรณ์ผู้ใช้ ดาวเทียม และสถานีภาคพื้นดินใหม่เป็นรอบสั้น ๆ และตอนนั้นจะเกิดความหน่วงหรือแพ็กเก็ตหายชั่วครู่ → วงโคจรค้างฟ้ามีอินพุตดีเลย์มากในทุกการกระทำ ส่วนวงโคจรต่ำปกติดี แต่กระตุกหรือวาร์ปเป็นช่วงห่างเท่า ๆ กัน - อาการ: อินพุตดีเลย์, กระตุก, วาร์ป / ปัจจัย: ความหน่วง, จิตเตอร์, แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว, คนในบ้านเดียวกัน, บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตลอดเวลา, เป็นรอบสม่ำเสมอ - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ขยาย interpolation buffer อัตโนมัติตามจิตเตอร์, ส่งอินพุตซ้ำเพื่อให้ทนแพ็กเก็ตหายช่วงสั้น, แสดงคุณภาพการเชื่อมต่อ เซิร์ฟเวอร์: คำนึงถึงความหน่วงของเน็ตดาวเทียมเมื่อกำหนดช่วงเวลาตัดสินและเพดานของ lag compensation, ตั้งไทม์เอาต์ที่ไม่ตัดผู้เล่นออกทันทีเมื่อขาดช่วงราว 1 วินาที - งานฝั่งภายนอก: แนะนำผู้เล่นว่าอินเทอร์เน็ตดาวเทียมอาจมีความหน่วงสูงหรือพุ่งเป็นรอบ, แนะนำให้ใช้เน็ตแบบมีสายภาคพื้นดินกับคอนเทนต์แข่งขันถ้าทำได้ - ตัวเลขที่ควรรู้: วงโคจรค้างฟ้า (สูง 36,000 กิโลเมตร) สัญญาณใช้เวลาผ่านอวกาศทางเดียว 260 ms ไปกลับจึงเกิน 520 ms (ITU-T G.114) ส่วน Starlink ซึ่งเป็นวงโคจรต่ำ ตามข้อมูลทางการ (ค่าเฉลี่ยทุก 15 วินาที) มีค่ามัธยฐานช่วงพีคในสหรัฐฯ 33 ms และแม้ 1% ที่แย่ที่สุด (p99) ก็ต่ำกว่า 65 ms (ปี 2024) งานวิจัยที่วัดจริงพบว่าความหน่วงเปลี่ยนทุกครั้งที่จัดเส้นทางใหม่ทุก 15 วินาที และมีช่วงขาดสั้น ๆ ไม่ถึง 1 วินาที การวัดอินเทอร์เน็ตบนเครื่องบินในปี 2018 พบว่าแบบดาวเทียมมีเวลาไปกลับเฉลี่ย 750 ms - บนกราฟ: สูงเฉพาะบางกลุ่ม (RTT/จิตเตอร์ (แยกตาม ASN ของผู้ให้บริการอินเทอร์เน็ตดาวเทียม)) - จุดที่ต้องดู: ดูว่า ASN ของ IP ที่เชื่อมต่อเป็นผู้ให้บริการอินเทอร์เน็ตดาวเทียมหรือไม่ แล้วพล็อตการกระจายและอนุกรมเวลาของ RTT ของผู้เล่นค่ายนั้นแยกไว้ วัด ping ต่อเนื่องหลายนาทีจาก probe ของ RIPE Atlas ใน ASN นั้นไปยังเซิร์ฟเวอร์ หรือให้ผู้เล่นเปิด ping ทิ้งไว้แล้ววัดระยะห่างระหว่างครั้งที่พุ่ง - สัญญาณว่าใช่: ผู้ให้บริการวงโคจรค้างฟ้ามี RTT เกิน 500 ms ตลอด ส่วนผู้ให้บริการวงโคจรต่ำปกติอยู่ที่หลายสิบ ms แต่ RTT เปลี่ยนหรือขาดสั้น ๆ ทุกประมาณ 15 วินาที - สัญญาณว่าไม่ใช่: ถ้าไม่ใช่ผู้ให้บริการดาวเทียมแต่ RTT สูงตลอด น่าจะเป็น “ความหน่วงในการแพร่สัญญาณ (ระยะทางจริง)” หรือ “เส้นทางวิ่งอ้อม” ถ้าพุ่งแบบไม่เป็นจังหวะ ให้ดูสัญญาณ Wi-Fi หรือมือถือ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ดาวเทียมวงโคจรต่ำอยู่ไม่ไกล (Starlink ช่วงเดียว 1.8–3.6 ms) ความหน่วงตามปกติจึงอาจใกล้เคียงกับเน็ตภาคพื้นดิน แต่ถ้าจุดที่สถานีภาคพื้นดินออกสู่อินเทอร์เน็ต (PoP) อยู่ไกลจากเซิร์ฟเวอร์เกม เส้นทางก็ยาวขึ้นตามนั้น และถ้าอ้อมผ่านลิงก์เลเซอร์ระหว่างดาวเทียม ความหน่วงก็เพิ่มอีก งานวิจัยที่วัดจริงมองว่าการแกว่งทุก 15 วินาทีมาจากการจัดเส้นทางใหม่ที่เกิดพร้อมกันทั่วโลกในเวลาเดียวกัน และไม่เกี่ยวกับการสลับดาวเทียม Wi-Fi บนเครื่องบินมีความหน่วงต่างกันมากตามวิธีเชื่อมต่อ (ดาวเทียมหรือสถานีฐานภาคพื้นดิน) ถ้าเป็นแบบที่ใช้ดาวเทียมวงโคจรค้างฟ้า ก็จะมีเวลาไปกลับยาวแบบข้างต้น - แหล่งอ้างอิง: - [ITU-T G.114: One-way transmission time](https://www.itu.int/rec/T-REC-G.114-200305-I/en) · ITU · ค่าที่ใช้วางแผนสำหรับความหน่วงทางเดียวในช่วงดาวเทียม: ความสูง 400 กิโลเมตร 12 ms, 14,000 กิโลเมตร 110 ms, 36,000 กิโลเมตร (วงโคจรค้างฟ้า) 260 ms - [Improving Starlink’s Latency](https://starlink.com/public-files/StarlinkLatency.pdf) · Starlink · ค่ามัธยฐานช่วงพีคในสหรัฐฯ 48.5 ms→33 ms, 1% ที่ช้าที่สุด (p99) จากเกิน 150 ms→ต่ำกว่า 65 ms (ปี 2024), สัญญาณเดินทางช่วงดาวเทียมหนึ่งช่วง 1.8–3.6 ms, ถ้าอ้อมผ่านลิงก์เลเซอร์ความหน่วงจะเพิ่ม และระยะจากสถานีภาคพื้นดินถึงจุดเชื่อมต่ออินเทอร์เน็ต (PoP) ก็เป็นปัจจัยของความหน่วงด้วย - [A Multifaceted Look at Starlink Performance (WWW 2024)](https://www.nitindermohan.com/documents/2024/pubs/starlinkWWW2024.pdf) · ACM · Starlink จัดเส้นทางใหม่พร้อมกันทั่วโลกทุก 15 วินาที ช่วงรอยต่อนั้นความหน่วงและ throughput แกว่ง และมีช่วงขาดสั้น ๆ ไม่ถึง 1 วินาที (ไม่ได้เกิดจากการสลับดาวเทียม), ความหน่วงช่วงอุปกรณ์ผู้ใช้↔ดาวเทียม↔สถานีภาคพื้นดิน ประมาณ 40 ms - [Mile High WiFi: A First Look at In-Flight Internet Connectivity (WWW 2018)](https://aqualab.cs.northwestern.edu/publication/2018/jrula-www18/) · ACM · วัดอินเทอร์เน็ตบนเครื่องบิน 45 ชั่วโมง: เวลาไปกลับเฉลี่ยแบบสถานีฐานภาคพื้นดิน 200 ms แบบดาวเทียม 750 ms, ค่ามัธยฐานอัตราแพ็กเก็ตหายของแบบดาวเทียม 7% - [Probe Selection (RIPE Atlas REST API)](https://atlas.ripe.net/docs/apis/rest-api-manual/measurements/creating-measurements/probe-selection/) · RIPE NCC · เลือก probe ของ RIPE Atlas ตามประเทศ, ภูมิภาค, ASN หรือช่วง IP แล้วรัน ping และ traceroute #### isp-routing · เส้นทางวิ่งอ้อม · Suboptimal routing เพราะสัญญาการเชื่อมต่อระหว่าง ISP แม้เซิร์ฟเวอร์จะอยู่ใกล้ แพ็กเก็ตก็ยังวิ่งอ้อมไปทางไกล - ทำไม → ผลคือ → บนหน้าจอ: ISP ของเรากับ ISP ฝั่งเซิร์ฟเวอร์ไม่ได้เชื่อมต่อกันโดยตรง → วิ่งผ่านประเทศอื่นหรือเมืองอื่น ระยะทางและจำนวนอุปกรณ์ที่ผ่านจึงเพิ่มขึ้น → เฉพาะผู้เล่นของ ISP บางค่ายที่ปิงสูงผิดปกติ - อาการ: อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: ภายนอก (ภายนอก) - งานฝั่งทีมอินฟรา: เชื่อมต่อกับ ISP หลายค่าย (multihoming), มอนิเตอร์ปิงแยกตาม ISP เพื่อหาค่ายที่วิ่งอ้อม, เจรจาปรับเส้นทางกับ ISP - งานฝั่งภายนอก: ร้องขอให้ ISP ค่ายนั้นปรับเส้นทาง - ตัวเลขที่ควรรู้: แม้อยู่ในประเทศเดียวกัน ปิงก็อาจต่างกันสองสามเท่าตามเส้นทาง - บนกราฟ: สูงตลอดตั้งแต่แรก (RTT (แยกตาม ISP/ASN)) - จุดที่ต้องดู: เทียบ RTT แยกตาม ISP (ASN) แล้วดูว่าเส้นทางผ่านประเทศหรือเมืองใด จาก probe ของ RIPE Atlas ใน ISP ที่ช้า หรือจาก traceroute หรือ mtr ที่ได้จากผู้เล่น วัด IPv4 และ IPv6 แยกกัน (mtr -4, -6) - สัญญาณว่าใช่: ภูมิภาคเดียวกันแต่ ISP บางค่ายสูงตลอด และเส้นทางมีช่วงที่ผ่านประเทศอื่นหรือเมืองที่อยู่ไกล หรือสูงเฉพาะ IP เวอร์ชันเดียว (IPv4 หรือ IPv6) - สัญญาณว่าไม่ใช่: ถ้าทุก ISP สูงพอ ๆ กัน น่าจะเป็น “ความหน่วงในการแพร่สัญญาณ (ระยะทางจริง)” ถ้าสูงเฉพาะหัวค่ำ น่าจะเป็น “จุด peering แออัดช่วงพีค” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: IPv4 และ IPv6 กำหนดเส้นทางแยกกัน แม้เป็นเซิร์ฟเวอร์เดียวกันก็อาจมีฝั่งเดียวที่อ้อมไกลจนช้า (การวัดของ APNIC ปี 2016: ภายใน ISP เดียวกันพบกลุ่มผู้ใช้ที่ IPv6 ช้ากว่า IPv4 อยู่ 15 ms, 25 ms และ 75 ms แยกกันเป็นกลุ่ม) แอปที่ใช้วิธี Happy Eyeballs (RFC 8305) ซึ่งเลือกใช้ฝั่งที่เชื่อมต่อได้ก่อนระหว่าง IPv6 กับ IPv4 จะลอง IPv6 ก่อน และถ้า IPv6 เชื่อมต่อได้ภายในค่าแนะนำ 250 ms ก็จะไม่ลอง IPv4 ดังนั้นแม้ฝั่ง IPv6 จะช้ากว่านิดหน่อย ก็มักจะเชื่อมต่อผ่านเส้นทางนั้น ถ้าปิงสูงเฉพาะบาง ISP ให้ลองวัด IPv4 และ IPv6 แยกกัน - กรณีจริง: riot-direct-2015 - แหล่งอ้างอิง: - [Quantifying the Causes of Path Inflation (SIGCOMM 2003)](https://conferences.sigcomm.org/sigcomm/2003/papers/p113-spring.pdf) · ACM · วิเคราะห์ ISP 65 ราย: นโยบาย peering ระหว่าง ISP และ routing ระหว่างโดเมน (inter-domain) ทำให้เส้นทางยาวขึ้นมาก - [The Internet at the Speed of Light (HotNets 2014)](https://conferences.sigcomm.org/hotnets/2014/papers/hotnets-XIII-final111.pdf) · ACM · เส้นทางจริงที่ผ่านเราเตอร์ยาวกว่าไฟเบอร์ที่ลากเป็นเส้นตรงประมาณ 1.5 เท่า (ค่ามัธยฐาน) และมีกรณีที่แพ็กเก็ตระหว่างสองจุดที่อยู่ใกล้กันวิ่งอ้อมไปอีกฟากโลก (hairpinning) - [Probe Selection (RIPE Atlas REST API)](https://atlas.ripe.net/docs/apis/rest-api-manual/measurements/creating-measurements/probe-selection/) · RIPE NCC · เลือก probe ของ RIPE Atlas ตามประเทศ, ภูมิภาค, ASN หรือช่วง IP แล้วรัน ping และ traceroute - [mtr(8) manual page source](https://raw.githubusercontent.com/traviscross/mtr/master/man/mtr.8.in) · mtr · ใช้ -4 หรือ -6 เพื่อวัดเส้นทางด้วย IPv4 หรือ IPv6 อย่างเดียว - [RFC 8305: Happy Eyeballs Version 2: Better Connectivity Using Concurrency](https://www.rfc-editor.org/rfc/rfc8305) · IETF · IP หรือเวอร์ชันของ IP (IPv4/IPv6) อาจถูกบล็อก เสีย หรือช้า ขึ้นอยู่กับเครือข่าย, ลอง IPv6 ก่อน แล้วรอค่าแนะนำ 250 ms ก่อนเริ่มเชื่อมต่อครั้งถัดไป - [IPv6 Performance – Revisited](https://blog.apnic.net/2016/08/22/ipv6-performance-revisited/) · APNIC · เทียบเวลาไปกลับของ IPv6 กับ IPv4 จากผู้ใช้ dual-stack คนเดียวกัน: บางเครือข่ายปลายทาง (access network) จัดการแพ็กเก็ต IPv6 ต่างไปโดยสิ้นเชิง จึงพบกลุ่มที่ IPv6 ช้ากว่า 15 ms, 25 ms และ 75 ms ภายใน ISP เดียวกัน #### isp-peak · จุด peering แออัดช่วงพีค · Peak-hour congestion at peering ช่วงหัวค่ำราว 21:00–23:00 ทราฟฟิกวิดีโอพุ่งขึ้นมาก จุดเชื่อมต่อระหว่าง ISP (peering) จึงมักแออัด - ทำไม → ผลคือ → บนหน้าจอ: ช่วงหัวค่ำมีการสตรีมและดาวน์โหลดพร้อมกันจำนวนมาก → เกิดคิวและแพ็กเก็ตหายที่จุด peering → เฉพาะช่วงหัวค่ำที่ผู้เล่นของ ISP บางค่ายกระตุกหรือวาร์ป - อาการ: กระตุก, วาร์ป, ดีดกลับ / ปัจจัย: จิตเตอร์, แพ็กเก็ตหาย, ความหน่วง - ใครเจอ: บางพื้นที่/บาง ISP / เกิดเมื่อไร: ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: ภายนอก (ภายนอก) - งานฝั่งทีมอินฟรา: เพิ่มการเชื่อมต่อตรงกับ ISP ค่ายนั้น, เลี่ยงเส้นทางที่แออัด, มอนิเตอร์แพ็กเก็ตหายและปิงช่วงหัวค่ำแยกตาม ISP - งานฝั่งภายนอก: ร้องขอให้ ISP ค่ายนั้นขยายความจุของจุด peering - บนกราฟ: สูงเฉพาะบางช่วงเวลา (RTT/แพ็กเก็ตหาย (แยกตาม ISP)) - จุดที่ต้องดู: พล็อต RTT และแพ็กเก็ตหายแยกตาม ISP (ASN) ตามช่วงเวลา แล้วเก็บ mtr ทั้งช่วงหัวค่ำและกลางวันจาก probe ของ RIPE Atlas ใน ISP นั้นหรือจากผู้เล่น เพื่อดูว่าแพ็กเก็ตเริ่มหายที่ช่วงไหน - สัญญาณว่าใช่: เฉพาะ ISP บางค่ายที่ RTT และแพ็กเก็ตหายสูงขึ้นทุกวันช่วงราว 21:00–23:00 และใน mtr แพ็กเก็ตหายและความหน่วงต่อเนื่องตั้งแต่จุดเชื่อมต่อระหว่าง ISP ไปจนถึงปลายทาง - สัญญาณว่าไม่ใช่: ถ้าทุก ISP สูงขึ้นพร้อมกัน น่าจะเป็นฝั่งวงจรหรือเซิร์ฟเวอร์ของเรา ถ้าแย่เฉพาะคนในบ้านเดียวกันช่วงหัวค่ำ น่าจะเป็น “ช่องสัญญาณ Wi-Fi แออัด” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Inferring Persistent Interdomain Congestion (SIGCOMM 2018)](https://www.caida.org/catalog/papers/2018_inferring_persistent_interdomain_congestion/inferring_persistent_interdomain_congestion.pdf) · ACM · ความแออัดที่เกิดซ้ำทุกวัน โดยความหน่วงสูงขึ้นทุกช่วงพีคในบางจุดเชื่อมต่อระหว่าง ISP และอัตราแพ็กเก็ตหายก็สูงขึ้นในช่วงแออัด - [Probe Selection (RIPE Atlas REST API)](https://atlas.ripe.net/docs/apis/rest-api-manual/measurements/creating-measurements/probe-selection/) · RIPE NCC · เลือก probe ของ RIPE Atlas ตามประเทศ, ภูมิภาค, ASN หรือช่วง IP แล้วรัน ping และ traceroute #### isp-cable · เคเบิลใต้น้ำ/วงจรต่างประเทศขัดข้อง · Submarine cable fault เมื่อเคเบิลใต้น้ำขาด ทราฟฟิกต้องอ้อมไปเส้นทางไกลหลายสัปดาห์ (นานสุดหลายเดือน) จนกว่าจะซ่อมเสร็จ และวงจรที่เหลืออยู่ก็แออัด - ทำไม → ผลคือ → บนหน้าจอ: เคเบิลขาดหรืออุปกรณ์เสีย → ทราฟฟิกไปกองที่เส้นทางอ้อมที่ไกลและวงจรที่เหลืออยู่ → ผู้เล่นที่เชื่อมต่อจากต่างประเทศปิงพุ่งและแพ็กเก็ตหาย ต่อเนื่องหลายวันถึงหลายสัปดาห์ - อาการ: อินพุตดีเลย์, วาร์ป / ปัจจัย: ความหน่วง, แพ็กเก็ตหาย - ใครเจอ: บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: อินฟราเครือข่าย (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: จัดหาวงจรที่ใช้เส้นทางอื่นไว้, ย้ายทราฟฟิกไปเส้นทางนั้นเมื่อเกิดเหตุขัดข้อง - งานฝั่งภายนอก: แจ้งผู้เล่นต่างประเทศถึงสาเหตุและเวลาที่คาดว่าจะกลับมาปกติ, ขอให้ผู้ให้บริการวงจรยืนยันกำหนดการซ่อม - บนกราฟ: ขึ้นเป็นขั้นบันไดจากจุดหนึ่ง (RTT (แยกตามประเทศของผู้เล่นต่างประเทศ)) - จุดที่ต้องดู: หาเวลาที่เริ่มสูงขึ้นในกราฟ RTT และแพ็กเก็ตหายแยกตามประเทศ แล้วเทียบกับสรุปเหตุขัดข้องอินเทอร์เน็ตของ Cloudflare Radar และประกาศของผู้ให้บริการเคเบิลใต้น้ำ และใช้ traceroute ดูว่าเส้นทางอ้อมไปอีกทวีปหรือไม่ - สัญญาณว่าใช่: ตั้งแต่จุดหนึ่ง RTT ของบางภูมิภาคในต่างประเทศขึ้นไปหนึ่งขั้นและอยู่ระดับนั้นหลายวันถึงหลายสัปดาห์ และช่วงเดียวกันมีรายงานเคเบิลขัดข้อง เส้นทางเปลี่ยนไปเป็นทางอ้อมที่ไกลกว่าปกติ - สัญญาณว่าไม่ใช่: ถ้ากลับมาปกติภายในไม่กี่วันและไม่มีรายงานเหตุขัดข้อง น่าจะเป็น “เส้นทาง BGP เปลี่ยนและ converge ใหม่” หรือช่วงเครือข่ายของ ISP - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [AAE-1 & SMW5 cable cuts impact millions of users across multiple countries](https://blog.cloudflare.com/aae-1-smw5-cable-cuts/) · Cloudflare · การซ่อมเคเบิลใต้น้ำต้องส่งเรือซ่อมออกไป ปกติใช้เวลาหลายวันถึงหลายสัปดาห์ (กรณีตองกา 38 วัน), เมื่อเคเบิลขาด ความหน่วงและแพ็กเก็ตหายช่วงยุโรป–เอเชียเพิ่มขึ้น - [Q2 2024 Internet disruption summary](https://blog.cloudflare.com/q2-2024-internet-disruption-summary/) · Cloudflare · เคเบิลในทะเลแดงที่เสียหายเมื่อกุมภาพันธ์ 2024 ยังซ่อมอยู่ในเดือนกรกฎาคม (พื้นที่ขัดแย้ง), เคเบิล EASSy และ Seacom ที่ขาดในเดือนพฤษภาคมกลับมาใช้ได้ใน 19 วัน - [Q1 2024 Internet disruption summary](https://blog.cloudflare.com/q1-2024-internet-disruption-summary/) · Cloudflare · เคเบิลแอฟริกาตะวันตกขาด (14 มีนาคม) กลับมาใช้ได้หลังผ่านไป 3–6 สัปดาห์ ระหว่างนั้นย้ายทราฟฟิกไปเคเบิลอื่น #### isp-bgp · เส้นทาง BGP เปลี่ยนและ converge ใหม่ · Route change / BGP convergence เมื่อข้อมูลเส้นทางของอินเทอร์เน็ตเปลี่ยน แพ็กเก็ตจะหายในช่วงไม่กี่วินาทีถึงหลายสิบวินาที (บางกรณีหลายนาที) ที่เส้นทางกำลัง converge ใหม่ - ทำไม → ผลคือ → บนหน้าจอ: ข้อมูลเส้นทางในช่วงเครือข่ายของ ISP รายใดรายหนึ่งเปลี่ยน → แพ็กเก็ตหายไปไม่กี่วินาทีถึงหลายสิบวินาที หรือสลับไปใช้เส้นทางใหม่ → จู่ ๆ ก็ค้างไปไม่กี่วินาที แล้วค่าปิงเปลี่ยนไป (เช่น 40 → 70 ms) - อาการ: ค้าง, วาร์ป / ปัจจัย: แพ็กเก็ตหาย, ความหน่วง - ใครเจอ: บางพื้นที่/บาง ISP / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: ตั้งไทม์เอาต์ให้ทนการขาดช่วงสั้น ๆ ได้ (ไม่ตัดการเชื่อมต่อที่หยุดไปไม่กี่วินาทีทันที) - งานฝั่งทีมอินฟรา: มอนิเตอร์เส้นทาง (เฝ้าดูการเปลี่ยนแปลงของเส้นทางและปิงของช่วง IP ของเรา), ใช้ BFD ตรวจจับวงจรของเราที่ขัดข้องภายใน 1 วินาทีแล้วสลับเส้นทาง (hold time ค่าเริ่มต้นของ BGP คือ 90–180 วินาที), ถ้าเส้นทางเปลี่ยนไปทางไกลแล้วไม่กลับมา ให้ย้ายทราฟฟิกไปวงจรอื่น - งานฝั่งภายนอก: ขอให้ ISP ที่เส้นทางในช่วงเครือข่ายของตนเปลี่ยนบ่อยตรวจหาสาเหตุ - บนกราฟ: ขึ้นเป็นขั้นบันไดจากจุดหนึ่ง (RTT, เส้นทางจาก traceroute) - จุดที่ต้องดู: เทียบเส้นทางจาก traceroute และ mtr ก่อนและหลังเวลาที่ RTT เปลี่ยน และดูประวัติการเปลี่ยนเส้นทาง BGP ของช่วง IP ของเรา (prefix) ด้วย RIPEstat BGPlay - สัญญาณว่าใช่: หยุดไปไม่กี่วินาทีพร้อมกับที่ RTT ย้ายไปอยู่อีกค่าหนึ่ง และในเวลาเดียวกันมี BGP update และ AS path เปลี่ยน - สัญญาณว่าไม่ใช่: ถ้าไม่มีประวัติการเปลี่ยนเส้นทางแต่สูงเฉพาะหัวค่ำ น่าจะเป็น “จุด peering แออัดช่วงพีค” ถ้าแย่เฉพาะบางการเชื่อมต่อ น่าจะเป็น “เส้นทาง ECMP เส้นหนึ่งเสีย” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - กรณีจริง: cloudflare-2020, meta-2021, cloudflare-dns-2025 - แหล่งอ้างอิง: - [RFC 4271: A Border Gateway Protocol 4 (BGP-4)](https://www.rfc-editor.org/rfc/rfc4271) · IETF · ค่าเริ่มต้นที่แนะนำของ BGP hold time คือ 90 วินาที (ถ้าไม่ได้รับข้อความจากอีกฝั่งในช่วงนี้ จะตัดเซสชัน) - [BGP updates in 2024](https://blog.apnic.net/2025/01/07/bgp-updates-in-2024/) · APNIC · เส้นทางที่ไม่เสถียรใช้เวลากว่าจะกลับมาเสถียรโดยเฉลี่ยรายวัน 25–35 วินาที (IPv4) และ 40–50 วินาที (IPv6) - [Delayed Internet Routing Convergence (SIGCOMM 2000)](https://conferences.sigcomm.org/sigcomm/2000/conf/paper/sigcomm2000-5-2.pdf) · ACM · หลังเส้นทางขัดข้อง การ converge ใช้เวลาได้ถึงหลายนาที และระหว่างนั้นแพ็กเก็ตหายและความหน่วงเพิ่มขึ้น (การวัดในปี 2000) - [BGPlay (RIPEstat Data API)](https://stat.ripe.net/docs/data-api/api-endpoints/bgplay) · RIPE NCC · แสดงเส้นทาง BGP ของช่วง IP (prefix) ณ เวลาเริ่มต้น, BGP update ที่พบในช่วงนั้น และข้อมูล AS ในเส้นทาง #### isp-ecmp · เส้นทาง ECMP เส้นหนึ่งเสีย · ECMP / link bundle member fault ISP และดาต้าเซ็นเตอร์มีหลายเส้นทางไปยังปลายทางเดียวกัน และกำหนดเส้นทางหนึ่งเส้นให้แต่ละการเชื่อมต่อ ถ้าเสียแค่เส้นเดียว คนที่ถูกจัดให้ใช้เส้นนั้นจะแลคอยู่ตลอด - ทำไม → ผลคือ → บนหน้าจอ: ในช่วงที่รวมหลายลิงก์เข้าด้วยกัน มีลิงก์หรืออุปกรณ์ตัวหนึ่งเสียหรือแออัด → เส้นทางถูกกำหนดจากค่า hash ของ IP และพอร์ต การเชื่อมต่อที่ถูกจัดให้ใช้เส้นนั้นเท่านั้นที่เจอแพ็กเก็ตหายและความหน่วง → ภูมิภาคเดียวกัน ISP เดียวกัน แต่บางคนวาร์ปอยู่เรื่อย ๆ บางครั้งเชื่อมต่อใหม่แล้วกลับมาปกติ - อาการ: วาร์ป, ดีดกลับ, กระตุก / ปัจจัย: แพ็กเก็ตหาย, จิตเตอร์ - ใครเจอ: เราคนเดียว, บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: เก็บสถิติแพ็กเก็ตหายและการส่งซ้ำแยกตามการเชื่อมต่อ ให้ดึง IP, พอร์ต และเวลาของคนที่เจอปัญหาได้ (TCP ใช้จำนวนการส่งซ้ำจาก TCP_INFO, UDP คำนวณจาก sequence number ที่ขาดหาย) - งานฝั่งทีมอินฟรา: รวบรวม IP, พอร์ต และเวลาของคนที่เจอปัญหาส่งให้ ISP หรือดาต้าเซ็นเตอร์, มอนิเตอร์แพ็กเก็ตหายแยกตามเส้นทาง, วัดเส้นทางด้วยโปรโตคอลและพอร์ตเดียวกับเกม (mtr --tcp หรือ --udp กับ --port), ถ้าเป็นเส้นทางในอุปกรณ์ของเรา ให้ถอดลิงก์หรืออุปกรณ์ที่เสียออกจากกลุ่ม - งานฝั่งภายนอก: ขอให้ ISP ตรวจและเปลี่ยนเส้นทางที่เสีย, แนะนำผู้เล่นให้เชื่อมต่อใหม่เพื่อเลี่ยงไปก่อน (กรณีที่เชื่อมต่อใหม่แล้วพอร์ตเปลี่ยน) - ตัวเลขที่ควรรู้: ถ้ามี 4 เส้นทาง จะมีผู้เล่นประมาณ 1 ใน 4 ที่เจอปัญหา แพ็กเก็ตที่ใช้วัดปิงบางครั้งวิ่งคนละเส้นกับเกม ผลจึงออกมาปกติ - บนกราฟ: สูงเฉพาะบางกลุ่ม (แพ็กเก็ตหาย/การส่งซ้ำแยกตามการเชื่อมต่อ (แยกตาม IP/พอร์ต)) - จุดที่ต้องดู: ดูแพ็กเก็ตหายและการส่งซ้ำแยกตามการเชื่อมต่อโดยแบ่งตาม IP และพอร์ตต้นทาง แล้ววัดด้วย mtr แบบ UDP (-u) ไปที่พอร์ตเกม (-P) โดยกำหนดพอร์ตต้นทาง (-L) และทำซ้ำหลายรอบโดยเปลี่ยนพอร์ตต้นทาง ถ้าใส่แค่ -P โดยไม่มี -L พอร์ตต้นทางจะเปลี่ยนทุกคำขอ ทำให้หลายเส้นทางปนกัน - สัญญาณว่าใช่: ในภูมิภาคและ ISP เดียวกัน เฉพาะชุดพอร์ตต้นทาง (หรือ IP) บางชุดที่แพ็กเก็ตหายตลอด และเมื่อเชื่อมต่อใหม่จนพอร์ตเปลี่ยนก็กลับมาปกติ - สัญญาณว่าไม่ใช่: ถ้าเปลี่ยนพอร์ตแล้วยังแย่ทั้งหมด น่าจะเป็นความแออัดหรือเหตุขัดข้องของทั้งช่วงเครือข่าย - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: อุปกรณ์ (ECMP, LAG) จะล็อกเส้นทางให้แต่ละการเชื่อมต่อตามค่าที่คำนวณจาก IP และพอร์ต (บางอุปกรณ์ตั้งให้ใช้แค่ IP) เพื่อไม่ให้ลำดับแพ็กเก็ตในการเชื่อมต่อเดียวกันสลับกัน ในที่ที่ใช้แค่ IP แม้เชื่อมต่อใหม่ก็ยังได้เส้นทางเดิม อาการจึงไม่ดีขึ้น ดังนั้นถ้ามีรายงานอย่าง “ปิงปกติแต่เกมแลค” หรือ “เชื่อมต่อใหม่แล้วดีขึ้น” เข้ามาพร้อมกัน ให้สงสัยสาเหตุนี้ - แหล่งอ้างอิง: - [RFC 7424: Mechanisms for Optimizing Link Aggregation Group (LAG) and Equal-Cost Multipath (ECMP) Component Link Utilization in Networks](https://www.rfc-editor.org/rfc/rfc7424) · IETF · LAG และ ECMP เลือกลิงก์หนึ่งเส้นให้แต่ละ flow ด้วย hash ของฟิลด์ในเฮดเดอร์เพื่อรักษาลำดับแพ็กเก็ต (flow→ลิงก์ แบบหลายต่อหนึ่ง) - [RFC 2991: Multipath Issues in Unicast and Multicast Next-Hop Selection](https://www.rfc-editor.org/rfc/rfc2991) · IETF · เกณฑ์แยก flow ต่างกันตามการ implement (แค่ IP ปลายทาง, คู่ IP หรือรวมถึงพอร์ต), ในเครือข่ายหลายเส้นทาง ผล ping และ traceroute เชื่อถือได้ยาก - [mtr(8) manual page source](https://raw.githubusercontent.com/traviscross/mtr/master/man/mtr.8.in) · mtr · ออปชัน -u (UDP), -P (พอร์ตปลายทาง), -L (พอร์ตต้นทางของ UDP), ถ้าใส่แค่ -P จะใช้ลำดับของคำขอเป็นพอร์ตต้นทาง ทำให้พอร์ตเปลี่ยนทุกคำขอ #### isp-shaping · ISP จำกัดความเร็วและจัดการทราฟฟิก · Traffic shaping, data caps ในแพ็กเกจที่ใช้ดาต้าเกินโควตาแล้ว หรือแพ็กเกจที่มีการจัดการทราฟฟิกบางประเภท แพ็กเก็ตจะถูกหน่วงหรือถูกทิ้ง - ทำไม → ผลคือ → บนหน้าจอ: ถูกจำกัดความเร็วหลังดาต้าในแพ็กเกจหมด หรือถูกจำกัดทราฟฟิกบางประเภท → แพ็กเก็ตต้องรอคิวหรือถูกทิ้ง → แลคหลังใช้งานไปถึงปริมาณหนึ่ง โดยเฉพาะบนมือถือ - อาการ: อินพุตดีเลย์, วาร์ป / ปัจจัย: ความหน่วง, แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว, บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตลอดเวลา, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), อินฟราเครือข่าย (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ลดทราฟฟิกของเกม (บีบอัด, ส่งเฉพาะที่จำเป็น) - งานฝั่งทีมอินฟรา: ถ้าทราฟฟิกเกมถูกหน่วงหรือถูกทิ้งเฉพาะบาง ISP ให้รวบรวมข้อมูลแล้ว escalate ไปยัง ISP - งานฝั่งภายนอก: แนะนำผู้เล่นให้ตรวจว่าดาต้าในแพ็กเกจหมดหรือถูกจำกัดความเร็วหรือไม่ และมีแอปอื่นในมือถือเครื่องเดียวกันใช้เน็ตอยู่หรือไม่, ขอให้ ISP ยืนยันว่ามีการจำกัดทราฟฟิกเกมหรือไม่ - ตัวเลขที่ควรรู้: แพ็กเกจมือถือในเกาหลี เมื่อใช้ดาต้าหมดมักถูกจำกัดเหลือ 1–5 Mbps ส่วนแพ็กเกจราคาถูกเหลือหลายร้อย kbps แม้เกมจะใช้ดาต้าน้อย แต่ถ้าแอปอื่นในมือถือเครื่องเดียวกันใช้เน็ตไปด้วย ก็จะเกิดคิวหน้าอุปกรณ์จำกัดความเร็ว - บนกราฟ: ชนเพดานแล้วแบนราบ (throughput, RTT) - จุดที่ต้องดู: ให้ผู้เล่นเช็กดาต้าที่เหลือและสถานะการจำกัดความเร็วในแอปของค่ายมือถือ แล้วทดสอบความเร็วเพื่อดูความเร็วสูงสุด ฝั่งเซิร์ฟเวอร์ให้เทียบแพ็กเก็ตหายและ RTT แยกตาม ISP - สัญญาณว่าใช่: throughput ไม่ขึ้นเกินค่าหนึ่ง เช่น 1–5 Mbps หรือหลายร้อย kbps และตั้งแต่นั้น ถ้าแอปอื่นในมือถือเครื่องเดียวกันใช้เน็ต RTT และแพ็กเก็ตหายจะเพิ่มขึ้น อาการหายไปเมื่อเติมดาต้าหรือเปลี่ยนไปใช้ Wi-Fi - สัญญาณว่าไม่ใช่: ถ้าไม่ถูกจำกัดความเร็วแต่แย่เฉพาะบาง ISP น่าจะเป็น “จุด peering แออัดช่วงพีค” หรือ “เส้นทางวิ่งอ้อม” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [SKT, 고객 선택권과 혜택 강화한 신규 5G 요금제 출시](https://news.sktelecom.com/180213) · SK텔레콤 · ตัวอย่างการควบคุมความเร็วหลังดาต้าพื้นฐานของแพ็กเกจ 5G หมด: สูงสุด 400 kbps, 1 Mbps, 3 Mbps - [SKT, 요금제 개편](https://news.sktelecom.com/225487) · SK텔레콤 · ใช้งานต่อได้ที่ความเร็วสูงสุด 400 kbps แม้ใช้ดาต้าพื้นฐานหมดแล้ว (โครงการดาต้าอุ่นใจสำหรับคนทั้งประเทศ) #### isp-udp-block · การจำกัด UDP และตรวจแพ็กเก็ตระดับประเทศหรือ ISP · UDP blocking, throttling and inspection by networks บางเครือข่ายบล็อก IP หรือพอร์ต UDP บางตัว หรือจำกัดความเร็ว UDP และอุปกรณ์ตรวจแพ็กเก็ตจะกรองโปรโตคอลที่ไม่รู้จักทิ้ง เกมที่สื่อสารด้วย UDP จึงเชื่อมต่อในเครือข่ายนั้นไม่ได้หรือหลุดบ่อย - ทำไม → ผลคือ → บนหน้าจอ: เชื่อมต่อจากเครือข่าย ISP บางรายที่จำกัดความเร็ว UDP หรือจากเครือข่ายที่มีอุปกรณ์ตรวจทราฟฟิก (เพื่อเซ็นเซอร์) ระดับประเทศหรือ ISP → บล็อก IP หรือพอร์ต UDP บางตัว, จำกัดความเร็ว UDP ในช่วงที่แออัด, กรองพอร์ตหรือโปรโตคอลที่ไม่อยู่ในรายการอนุญาตทิ้ง หรือปล่อยผ่านแค่ไม่กี่แพ็กเก็ตแรกแล้วบล็อก → เฉพาะผู้เล่นในบางประเทศหรือบาง ISP ที่เข้าเกมไม่ได้หรือโหลดไม่จบ, เข้าได้แล้วหลุดในไม่ช้า, วาร์ปเพราะแพ็กเก็ตหายในช่วงที่แออัด - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, หลุด, วาร์ป / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: บางพื้นที่/บาง ISP / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ตลอดเวลา, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), อินฟราเครือข่าย (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ถ้า UDP เชื่อมต่อไม่ได้ภายในไม่กี่วินาที ให้สลับไปเส้นทางสำรอง TCP/TLS 443 อัตโนมัติ, ตรวจจับกรณีที่เชื่อมต่อได้ตอนแรกแล้วหลุดในไม่ช้าด้วย แล้วลองใหม่ผ่านเส้นทางสำรอง, บันทึกลง log ว่าเชื่อมต่อผ่านเส้นทางไหน เซิร์ฟเวอร์: รับโปรโตคอลเกมเดียวกันผ่าน TCP 443 (TLS) ด้วย, ปรับไทม์เอาต์เพราะเส้นทางสำรองอาจมีความหน่วงเพิ่มขึ้น - งานฝั่งทีมอินฟรา: ก่อนขยายบริการไปประเทศใหม่ ให้วัดในเครือข่าย ISP ท้องถิ่นว่า UDP ไปถึงได้หรือไม่และแพ็กเก็ตหายช่วงพีคเท่าไร, ตั้ง relay หรือเกตเวย์ที่รับเส้นทางสำรอง TCP 443 ไว้ใกล้ประเทศนั้น, เฝ้าดูอัตราการเชื่อมต่อสำเร็จของ UDP และ TCP แยกตามประเทศและ ASN, ถ้ายืนยันได้ว่า ISP ใดจำกัดความเร็ว UDP ให้รวบรวมข้อมูลแล้ว escalate - งานฝั่งภายนอก: สอบถาม ISP หรือหน่วยงานนั้นถึงเกณฑ์การจำกัด UDP และขอให้ผ่อนปรน, แนะนำผู้เล่นให้ลองเชื่อมต่อจากเครือข่ายอื่นเพื่อเปรียบเทียบ - ตัวเลขที่ควรรู้: ตามการวัดที่เอกสาร IETF อ้างถึง 3–5% ของเครือข่ายบล็อก UDP ทั้งหมด เมื่อ Google ดูผลการใช้ QUIC (ที่ทำงานบน UDP) ในปี 2016 พบว่าไคลเอนต์ 4.4% ใช้ไม่ได้เพราะ UDP หรือ QUIC ถูกบล็อก หรือ path MTU เล็กเกินไป ส่วนใหญ่อยู่หลังไฟร์วอลล์ขององค์กร และไม่พบกรณีที่ ISP บล็อกทั้งเครือข่าย อีก 0.3% อยู่ในเครือข่ายที่แพ็กเก็ตหายเพิ่มขึ้นมากในช่วงพีค ซึ่งดูเหมือนจำกัดความเร็ว UDP และหลังจากขอให้ ISP แก้ไข สัดส่วนนี้ก็ลดลงจาก 1% ในปี 2015 - บนกราฟ: สูงเฉพาะบางกลุ่ม (อัตราการเชื่อมต่อ UDP สำเร็จ (แยกตามประเทศ/ASN)) - จุดที่ต้องดู: ดูอัตราการเชื่อมต่อสำเร็จของ UDP และของเส้นทางสำรอง TCP 443 แยกกันตามประเทศและ ASN ทดสอบเชื่อมต่อไปที่พอร์ต UDP ของเกมและ TCP 443 แยกกันจาก VM บนคลาวด์ในเครือข่าย ISP นั้นหรือจาก PC ของผู้เล่น แล้วใช้ mtr -u -P (พอร์ตเกม) และ mtr -T -P 443 เทียบว่าการตอบกลับหายไปตั้งแต่ช่วงไหน - สัญญาณว่าใช่: เฉพาะบางประเทศหรือบาง ASN ที่ UDP ไม่ได้รับการตอบกลับครั้งแรก หรือหลุดภายในไม่กี่วินาที ขณะที่ TCP 443 จากที่เดียวกันปกติ ถ้าเป็นการจำกัดความเร็ว แพ็กเก็ตหายของ UDP จะเพิ่มชัดเจนเฉพาะช่วงพีค และ TCP ได้รับผลกระทบน้อยกว่า - สัญญาณว่าไม่ใช่: ถ้า TCP ล้มเหลวด้วย น่าจะเป็นเส้นทางขัดข้อง, IP ถูกบล็อก หรือ “DNS ขัดข้อง/ช้า” ถ้าเป็นเหมือนกันทุกประเทศ น่าจะเป็นการตั้งค่าเซิร์ฟเวอร์หรือไฟร์วอลล์ของเรา ถ้าแพ็กเก็ตหายเฉพาะตอนที่ส่งข้อมูลปริมาณมากในชั่วขณะ ไม่ว่าจะเป็น UDP หรือ TCP น่าจะเป็น “policer ทิ้งส่วนที่เกิน” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: เอกสารสำรวจของ IRTF ระบุว่าอุปกรณ์ตรวจแพ็กเก็ตอาจเลือกบล็อก flow ของ UDP ตาม IP, พอร์ต และโปรโตคอล หรือบล็อกทุกอย่างที่ไม่ใช่โปรโตคอลที่อนุญาต (allowlist) ถ้าอุปกรณ์ตัดสินจากฟิลด์บางส่วนของแพ็กเก็ตเท่านั้น แค่เปลี่ยนโปรโตคอลเล็กน้อยก็อาจถูกบล็อกได้ ในช่วงแรกของ QUIC มีไฟร์วอลล์ตัวหนึ่งที่หลังจาก 1 บิตในเฮดเดอร์เปลี่ยน ก็ปล่อยให้ไม่กี่แพ็กเก็ตแรกผ่านแล้วบล็อกแพ็กเก็ตหลังจากนั้น ทำให้ลอจิกที่ให้ไคลเอนต์สลับไปเชื่อมต่อด้วย TCP ทำงานไม่ได้ ปัญหานี้อาจโผล่มาตอนขยายบริการไปประเทศใหม่ ในรูปของรายงานว่า “ในเกาหลีปกติดี แต่ ISP บางรายในประเทศนั้นเข้าเกมไม่ได้” ถ้าถูกบล็อกเฉพาะในเครือข่ายของสถานที่หนึ่ง เช่น ร้านกาแฟหรือบริษัท ให้ดูสาเหตุ “ข้อจำกัดของ Wi-Fi สาธารณะและเครือข่ายบริษัท” - แหล่งอ้างอิง: - [RFC 9308: Applicability of the QUIC Transport Protocol](https://www.rfc-editor.org/rfc/rfc9308) · IETF · ตามงานวิจัยที่วัดจริง 3–5% ของเครือข่ายบล็อก UDP ทั้งหมด แอปที่ใช้ UDP จึงต้องยอมรับว่าอาจเชื่อมต่อไม่สำเร็จ หรือมีเส้นทางสำรองผ่าน TCP (TLS), ไฟร์วอลล์อาจบล็อกพอร์ตที่ไม่ได้ผูกกับบริการที่ลงทะเบียนไว้ - [The QUIC Transport Protocol: Design and Internet-Scale Deployment (SIGCOMM 2017)](https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/46403.pdf) · ACM · ปี 2016: ไคลเอนต์ 4.4% ใช้ QUIC (UDP) ไม่ได้ (UDP หรือ QUIC ถูกบล็อก หรือ path MTU เล็ก ส่วนใหญ่อยู่หลังไฟร์วอลล์ขององค์กร และไม่พบ ISP ที่บล็อกทั้งเครือข่าย), 0.3% อยู่ในเครือข่ายที่ดูเหมือนจำกัดความเร็ว UDP (แพ็กเก็ตหายเพิ่มในช่วงพีค หลังขอให้ ISP แก้ไขก็ลดลงจาก 1% ในปี 2015), กรณีไฟร์วอลล์ที่หลัง 1 บิตในเฮดเดอร์เปลี่ยน ก็ปล่อยผ่านแค่ไม่กี่แพ็กเก็ตแรกแล้วบล็อกที่เหลือ ทำให้ลอจิกสำรองไปใช้ TCP ใช้การไม่ได้ - [RFC 9505: A Survey of Worldwide Censorship Techniques](https://www.rfc-editor.org/rfc/rfc9505) · IRTF · อุปกรณ์ตรวจในเครือข่ายอาจเลือกบล็อก flow ของ TCP หรือ UDP ตาม IP, พอร์ต และโปรโตคอล (พบการบล็อก endpoint UDP ของ QUIC), วิธีบล็อกทุกอย่างที่ไม่ใช่โปรโตคอลที่อนุญาตทำให้บล็อกเกินจำเป็น และยังมีวิธีจำกัดความเร็วของทราฟฟิกบางประเภทด้วย - [mtr(8) manual page source](https://raw.githubusercontent.com/traviscross/mtr/master/man/mtr.8.in) · mtr · ใช้ -u ส่ง UDP, -T ส่ง TCP SYN และ -P กำหนดพอร์ตปลายทาง เพื่อวัดเส้นทางด้วยโปรโตคอลและพอร์ตเดียวกับเกม #### isp-line · คุณภาพสายเน็ตแย่ · Faulty last-mile line / modem ขั้วต่อหลวม สายเก่า หรือโมเด็มผิดปกติ ทำให้แพ็กเก็ตหายอย่างต่อเนื่องและเน็ตขาดเป็นระยะ - ทำไม → ผลคือ → บนหน้าจอ: สายเคเบิลเสียหาย, ขั้วต่อหลวม, โมเด็มหรืออุปกรณ์ไฟเบอร์ (ONU) ผิดปกติ → แพ็กเก็ตถูกทิ้งเพราะบิตผิดพลาด และบางครั้งเน็ตขาดไปหลายวินาทีถึงราว 1 นาทีระหว่างเชื่อมต่อใหม่ → แพ็กเก็ตหายเล็กน้อยต่อเนื่อง บางครั้งค้างไปหลายวินาทีหรือหลุด - อาการ: วาร์ป, ค้าง, หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: คนในบ้านเดียวกัน / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) - งานฝั่งภายนอก: แนะนำผู้เล่นให้ดูว่าเกมอื่นหรือวิดีโอคอลก็ขาดด้วยหรือไม่ ถ้าใช่ ให้แจ้ง ISP ให้มาตรวจสาย - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (อัตราแพ็กเก็ตหาย, ประวัติการเชื่อมต่อใหม่ของเน็ต) - จุดที่ต้องดู: ใช้ pathping (หรือ mtr) วัดแพ็กเก็ตหายถึง hop แรกของ ISP ต่อเนื่องหลายนาที และดูเวลาที่เชื่อมต่อใหม่จากประวัติการเชื่อมต่ออินเทอร์เน็ต (WAN) ในหน้าตั้งค่าเราเตอร์ - สัญญาณว่าใช่: แม้เน็ตว่างก็มีแพ็กเก็ตหายต่อเนื่องตั้งแต่ hop แรกของ ISP และเวลาที่เน็ตเชื่อมต่อใหม่ในประวัติเราเตอร์ตรงกับเวลาที่เกมค้างหรือหลุด เกมอื่นและวิดีโอคอลก็ขาดไปด้วย - สัญญาณว่าไม่ใช่: ถ้าแพ็กเก็ตเริ่มหายตั้งแต่ช่วงไร้สายระหว่างเครื่องกับเราเตอร์ น่าจะเป็น “Wi-Fi ถูกรบกวนหรือสัญญาณอ่อน” ถ้าเริ่มหายที่ช่วงไกลในเครือข่าย ISP ให้ดูเส้นทางของ ISP - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [RFC 3635: Definitions of Managed Objects for the Ethernet-like Interface Types](https://www.rfc-editor.org/rfc/rfc3635) · IETF · เฟรมที่ไม่ผ่านการตรวจเฟรม (FCS) จะนับเป็น FCS error (dot3StatsFCSErrors) และรวมไว้ใน input error (ifInErrors) - [Understanding and Mitigating Packet Corruption in Data Center Networks (SIGCOMM 2017)](https://www.microsoft.com/en-us/research/wp-content/uploads/2017/06/CorrOpt_SIGCOMM2017.pdf) · ACM · สาเหตุหลักของแพ็กเก็ตเสียหายคือโมดูลออปติกเสีย, ไฟเบอร์เสียหาย, ขั้วต่อสกปรก และการติดตั้งผิด อัตราแพ็กเก็ตหายจากความเสียหายคงที่ไม่ว่าปริมาณการใช้งานจะเป็นเท่าไร - [pathping](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/pathping) · Microsoft · ส่ง ping ไปแต่ละ hop เป็นช่วงเวลาหนึ่งเพื่อคำนวณอัตราแพ็กเก็ตหายของแต่ละเราเตอร์และลิงก์ แล้วแสดงว่าแพ็กเก็ตหายเกิดที่ช่วงไหน #### isp-dns · DNS ขัดข้อง/ช้า · DNS failure / slowness ถ้า DNS ซึ่งแปลงชื่อเซิร์ฟเวอร์เป็น IP ช้าหรือล้มเหลว เกมจะหาเซิร์ฟเวอร์ล็อกอินหรือเซิร์ฟเวอร์แพตช์ไม่เจอ - ทำไม → ผลคือ → บนหน้าจอ: DNS ของ ISP ขัดข้องหรือตั้งค่าผิด → หา IP ของเซิร์ฟเวอร์ล็อกอินหรือเซิร์ฟเวอร์แพตช์ไม่เจอ → กดเชื่อมต่อแล้วรอนาน หรือเข้าเกมไม่ได้ คนที่เข้าเกมอยู่แล้วไม่เป็นอะไร - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: ความหน่วง, แพ็กเก็ตหาย - ใครเจอ: บางพื้นที่/บาง ISP, เราคนเดียว / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แคช IP (จำ IP เซิร์ฟเวอร์ที่เชื่อมต่อสำเร็จครั้งล่าสุด), เตรียม DNS หลายตัว (ถ้าตัวหนึ่งล้มเหลวให้ถาม DNS ตัวอื่นใหม่) - งานฝั่งภายนอก: แนะนำผู้เล่นให้ลองเปลี่ยน DNS ไปใช้ตัวอื่น เช่น DNS สาธารณะ - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนล็อกอินล้มเหลว (แยกตาม ISP), เวลาตอบกลับของ DNS) - จุดที่ต้องดู: ใช้ Resolve-DnsName -Server (หรือ nslookup) ถามชื่อเซิร์ฟเวอร์ล็อกอินกับ DNS ของ ISP และ DNS สาธารณะแยกกัน แล้วเทียบเวลาตอบกลับและผลลัพธ์ - สัญญาณว่าใช่: เฉพาะ DNS ของ ISP ที่ไม่ตอบหรือตอบช้า และเมื่อเปลี่ยนไปใช้ DNS สาธารณะก็เข้าเกมได้ทันที ผู้เล่นที่เข้าเกมอยู่แล้วไม่เป็นอะไร - สัญญาณว่าไม่ใช่: ถ้าได้ IP ทันทีไม่ว่าจะถาม DNS ตัวไหน แต่ยังเข้าเกมไม่ได้ ให้ดูเส้นทาง ไฟร์วอลล์ หรือเซิร์ฟเวอร์ - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - กรณีจริง: meta-2021, cloudflare-dns-2025, aws-2025 - แหล่งอ้างอิง: - [Cloudflare 1.1.1.1 Incident on July 14, 2025](https://blog.cloudflare.com/cloudflare-1-1-1-1-incident-on-july-14-2025/) · Cloudflare · เมื่อ DNS resolver สาธารณะหยุดทำงาน 62 นาที ผู้ใช้ที่แปลงชื่อเป็น IP ไม่ได้ก็แทบใช้บริการอินเทอร์เน็ตอะไรไม่ได้เลย - [RFC 8767: Serving Stale Data to Improve DNS Resiliency](https://www.rfc-editor.org/rfc/rfc8767) · IETF · วิธีใช้ระเบียนในแคชที่หมดอายุแล้วต่อไปเมื่อติดต่อเซิร์ฟเวอร์ authoritative ไม่ได้ เพื่อให้ผ่านช่วงขัดข้องไปได้ (serve-stale) - [Resolve-DnsName](https://learn.microsoft.com/en-us/powershell/module/dnsclient/resolve-dnsname) · Microsoft · ใช้ -Server กำหนดเซิร์ฟเวอร์ DNS ที่จะถาม แล้วค้นหาชื่อ - [nslookup](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/nslookup) · Microsoft · คำสั่งสำหรับถามชื่อจากเซิร์ฟเวอร์ DNS โดยตรง #### isp-ddos-path · ลิงก์ที่ใช้ร่วมกันเต็มเพราะ DDoS · DDoS saturating shared links การโจมตีปริมาณมหาศาลที่พุ่งเป้ามาที่บริษัทเกม หรือที่อื่นในเครือข่ายเดียวกัน ทำให้ลิงก์ที่ใช้ร่วมกันเต็ม - ทำไม → ผลคือ → บนหน้าจอ: มีทราฟฟิกโจมตีปริมาณมหาศาล → ทราฟฟิกปกติที่ใช้ลิงก์เดียวกันก็ถูกเบียดและถูกทิ้งไปด้วย → หลายคนวาร์ป หลุด หรือเข้าเกมไม่ได้พร้อมกัน - อาการ: วาร์ป, หลุด, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: แพ็กเก็ตหาย, ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางพื้นที่/บาง ISP / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: ภายนอก (ภายนอก) - งานฝั่งทีมอินฟรา: ใช้บริการป้องกัน DDoS, เบี่ยงทราฟฟิกเมื่อถูกโจมตี, ซ่อน IP ของเซิร์ฟเวอร์ (วางไว้หลังอุปกรณ์ป้องกันและไม่เปิดเผย IP จริง) - งานฝั่งภายนอก: ถ้าเป็นการโจมตีที่พุ่งเป้าไปที่อื่นในเครือข่ายเดียวกัน ให้ขอ ISP บล็อกที่ช่วงเครือข่ายต้นทาง (upstream) - บนกราฟ: ชนเพดานแล้วแบนราบ (ปริมาณรับเข้าของลิงก์ (bps/pps), แพ็กเก็ตที่อินเทอร์เฟซทิ้ง (drop)) - จุดที่ต้องดู: ดูปริมาณรับเข้าและจำนวนแพ็กเก็ตที่ทิ้งบนอินเทอร์เฟซของวงจรและอุปกรณ์ของเรา และประวัติการตรวจพบการโจมตีของบริการป้องกัน DDoS เทียบกับช่วงที่มีคนหลุดจำนวนมาก - สัญญาณว่าใช่: ปริมาณรับเข้าของลิงก์ชนความจุของลิงก์จนแบนราบและการ drop เพิ่มขึ้น ในเวลาเดียวกันผู้เล่นหลายภูมิภาคและหลาย ISP วาร์ปหรือหลุดพร้อมกัน - สัญญาณว่าไม่ใช่: ถ้าลิงก์ยังมีที่ว่างแต่แย่เฉพาะบาง ISP น่าจะเป็นความแออัดหรือปัญหาเส้นทางในช่วงเครือข่ายของ ISP - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Infrastructure layer attacks](https://docs.aws.amazon.com/whitepapers/latest/aws-best-practices-ddos-resiliency/infrastructure-layer-attacks.html) · AWS · การโจมตีปริมาณมาก เช่น UDP reflection และ SYN flood ทำให้ความจุเครือข่ายล้น หรือจับทรัพยากรของไฟร์วอลล์และโหลดบาลานเซอร์ไว้ - [Obfuscating AWS resources (BP1, BP4, BP5)](https://docs.aws.amazon.com/whitepapers/latest/aws-best-practices-ddos-resiliency/obfuscating-aws-resources-bp1-bp4-bp5.html) · AWS · วางบริการ edge อย่าง CloudFront หรือโหลดบาลานเซอร์ไว้หน้าเซิร์ฟเวอร์ต้นทาง เพื่อลดการเปิดเผยโดยตรง - [RFC 7999: BLACKHOLE Community](https://www.rfc-editor.org/rfc/rfc7999) · IETF · BLACKHOLE community ที่ใช้ BGP แจ้ง ISP ข้างเคียงให้ทิ้งทราฟฟิกที่ไปยัง IP หนึ่ง ๆ #### isp-cgnat · IP ที่ ISP ให้ใช้ร่วมกัน (CGNAT) · Carrier-grade NAT เครือข่ายมือถือและ ISP บางรายให้ผู้ใช้บริการหลายรายใช้ IP เดียวร่วมกัน และลบ mapping ของการเชื่อมต่อที่ idle ภายในเวลาสั้น ๆ - ทำไม → ผลคือ → บนหน้าจอ: อุปกรณ์ของ ISP ดูแลตารางเซสชันของผู้ใช้บริการจำนวนมหาศาล → ตารางเซสชันมีขีดจำกัด และ idle timeout สั้น → อยู่เฉย ๆ แล้วหลุด, false positive ที่ทำให้คนที่ใช้ IP เดียวกันถูกบล็อกไปพร้อมกัน - อาการ: หลุด, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: บางพื้นที่/บาง ISP / เกิดเมื่อไร: หลังอยู่เฉย ๆ สักพัก - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), อินฟราเครือข่าย (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ส่ง heartbeat ทุกช่วงไม่เกินครึ่งหนึ่งของ idle timeout ที่สั้นที่สุด (เครือข่ายมือถือบางแห่งอยู่ราว 30 วินาที) โดยให้ไคลเอนต์เป็นฝ่ายส่ง เพราะ mapping ของ CGNAT ฝั่ง ISP จะต่ออายุแน่นอนก็ต่อเมื่อมีแพ็กเก็ตจากข้างในส่งออกไปเท่านั้น, เชื่อมต่อใหม่อัตโนมัติเมื่อหลุด เซิร์ฟเวอร์: ตอบ heartbeat และถ้าไม่ได้รับตามเวลาที่กำหนดให้เก็บกวาดการเชื่อมต่อก่อน, ถ้า mapping เปลี่ยนจน IP หรือพอร์ตเปลี่ยน ให้ใช้ session token รับต่อเป็นผู้เล่นคนเดิม, IP เดียวอาจมีหลายคนใช้ร่วมกัน จึงต้องระวังนโยบายบล็อกตาม IP (ตัดสินร่วมกับบัญชีและอุปกรณ์) - งานฝั่งทีมอินฟรา: ปรับเกณฑ์จำนวนการเชื่อมต่อต่อ IP และจำนวนการเชื่อมต่อใหม่ต่อวินาทีของไฟร์วอลล์และอุปกรณ์ป้องกัน DDoS ให้เหมาะกับ IP ที่ ISP ให้ใช้ร่วมกัน (เพิ่มเกณฑ์หรือยกเว้นช่วง IP ของค่ายมือถือ) - ตัวเลขที่ควรรู้: idle timeout ของ UDP ในเครือข่ายมือถือบางแห่งอยู่ราว 30 วินาที - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนครั้งที่หลุด (heartbeat timeout), ระยะเวลา idle ก่อนหลุด (แยกตาม ISP)) - จุดที่ต้องดู: ดูจำนวนบัญชีที่เชื่อมต่อพร้อมกันจาก IP เดียวและ ISP (ASN) จาก log การเชื่อมต่อ แล้วรวบรวมระยะเวลา idle ของการเชื่อมต่อที่หลุดหลังอยู่เฉย ๆ แยกตาม ISP ฝั่งผู้เล่นให้ดู IP อินเทอร์เน็ต (WAN) ในหน้าตั้งค่าเราเตอร์ - สัญญาณว่าใช่: ในช่วง IP ของค่ายมือถือ มีหลายบัญชีเชื่อมต่อจาก IP เดียว และระยะเวลา idle ก่อนหลุดกระจุกอยู่ที่ราว 30–60 วินาที IP WAN ของเราเตอร์อยู่ใน 100.64.0.0/10 (ที่อยู่ร่วมสำหรับ NAT ของ ISP) หรือไม่ตรงกับ IP ที่เซิร์ฟเวอร์เห็น - สัญญาณว่าไม่ใช่: ถ้าไม่เกี่ยวกับ ISP แต่กระจุกที่ผู้ที่ใช้เราเตอร์ในบ้าน น่าจะเป็น “NAT mapping หมดอายุ” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [A Multi-perspective Analysis of Carrier-Grade NAT Deployment (IMC 2016)](https://www.icir.org/vern/papers/cgn-imc16.pdf) · ACM · ระยะเวลาคง mapping ของ UDP ใน NAT ที่วัดได้อยู่ที่ 10–200 วินาที, 74% ไม่เกิน 1 นาที, ค่ามัธยฐานของ CGN คือเครือข่ายมือถือ 65 วินาที และเครือข่ายแบบมีสาย 35 วินาที - [RFC 6888: Common Requirements for Carrier-Grade NATs (CGNs)](https://www.rfc-editor.org/rfc/rfc6888) · IETF · CGN ต้องรองรับการจำกัดจำนวนพอร์ตภายนอกต่อผู้ใช้บริการ และการจำกัดอัตราการสร้าง mapping ใหม่ - [RFC 6269: Issues with IP Address Sharing](https://www.rfc-editor.org/rfc/rfc6269) · IETF · เมื่อหลายคนใช้ IP ร่วมกัน การบล็อกตาม IP (penalty box) จะบล็อกผู้ใช้บริการคนอื่นที่ใช้ IP เดียวกันไปด้วย - [RFC 6598: IANA-Reserved IPv4 Prefix for Shared Address Space](https://www.rfc-editor.org/rfc/rfc6598) · IETF · 100.64.0.0/10 คือช่วง IP ร่วมที่ใช้ระหว่างอุปกรณ์ NAT ของ ISP (CGN) กับเราเตอร์ของผู้ใช้บริการ #### isp-vpn · เชื่อมต่อผ่าน VPN/โปรแกรมลดปิง · VPN / game accelerator detour เมื่อเปิด VPN หรือโปรแกรมลดปิง แพ็กเก็ตจะวิ่งผ่านเซิร์ฟเวอร์ตัวกลาง (relay) ของบริษัทนั้น ถ้าเซิร์ฟเวอร์ตัวกลางอยู่ไกลหรือแออัด ก็กลับยิ่งช้าลง - ทำไม → ผลคือ → บนหน้าจอ: VPN หรือโปรแกรมลดปิงส่งแพ็กเก็ตเกมทั้งหมดอ้อมไปที่เซิร์ฟเวอร์ตัวกลาง → ระยะทางและความแออัดถึงเซิร์ฟเวอร์ตัวกลางเพิ่มเข้ามา และเฮดเดอร์ของ tunnel ทำให้ MTU (ขนาดแพ็กเก็ตที่ส่งได้ในครั้งเดียว) ลดลงด้วย → ปิงสูงขึ้นและแพ็กเก็ตหาย, ถูกบล็อกไปพร้อมกับคนที่ใช้ IP ตัวกลางเดียวกันจนเข้าเกมไม่ได้ - อาการ: อินพุตดีเลย์, วาร์ป, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: ความหน่วง, แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตลอดเวลา, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: อินฟราเครือข่าย (ทีมอินฟรา), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: รักษาขนาดแพ็กเก็ต UDP ไว้ไม่เกิน 1,200 ไบต์ (เพื่อไม่ให้เกิด fragmentation แม้ MTU จะลดลงเพราะเฮดเดอร์ของ tunnel), การบล็อกตาม IP ให้คำนึงถึง IP ตัวกลางที่ VPN และโปรแกรมลดปิงใช้ร่วมกัน และตัดสินร่วมกับบัญชีและอุปกรณ์ - งานฝั่งทีมอินฟรา: ถ้ามีผู้เล่นต่างประเทศมาก ให้ตั้งจุดเชื่อมต่อใกล้ผู้เล่นเอง, ตรวจเส้นทางของ ISP ที่มีรายงานว่า “เปิดโปรแกรมลดปิงแล้วดีขึ้น” เข้ามามาก - งานฝั่งภายนอก: แนะนำผู้เล่นให้ลองปิด VPN หรือโปรแกรมลดปิงแล้วเปรียบเทียบ - ตัวเลขที่ควรรู้: ถ้าเซิร์ฟเวอร์ตัวกลางอยู่ใกล้ จะเพิ่มแค่ไม่กี่ ms แต่ถ้าอ้อมผ่านประเทศอื่น จะเพิ่มหลายสิบถึงเกิน 100 ms - บนกราฟ: สูงเฉพาะบางกลุ่ม (RTT (แยกตามผู้เล่น), ผู้ให้บริการของ IP ที่เชื่อมต่อ) - จุดที่ต้องดู: ดูว่า ASN ของ IP ที่เชื่อมต่อเป็นผู้ให้บริการ VPN, โปรแกรมลดปิง หรือโฮสติ้งหรือไม่ แล้วให้ผู้เล่นปิด VPN หรือโปรแกรมลดปิงแล้วเทียบปิงและ traceroute - สัญญาณว่าใช่: RTT และแพ็กเก็ตหายเพิ่มขึ้น หรือเชื่อมต่อไม่ได้เฉพาะตอนเปิด VPN หรือโปรแกรมลดปิง และใน traceroute เห็นช่วงที่ผ่านเซิร์ฟเวอร์ตัวกลาง - สัญญาณว่าไม่ใช่: ถ้าเปิดหรือปิดก็เหมือนกัน น่าจะเป็นช่วงเน็ตหรือเครือข่ายของ ISP ถ้าเปิดแล้วดีขึ้น น่าจะเป็นปัญหาที่เส้นทางเดิมของ ISP (“เส้นทางวิ่งอ้อม”, “จุด peering แออัดช่วงพีค”) - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: ในทางกลับกัน เมื่อเส้นทางของ ISP แย่ โปรแกรมลดปิงอาจอ้อมไปเส้นทางที่ดีกว่าจนปิงลดลง รายงานว่า “เปิดโปรแกรมลดปิงแล้วดีขึ้น” จึงเป็นเบาะแสของปัญหาเส้นทางของ ISP เช่น เส้นทางวิ่งอ้อมหรือความแออัดช่วงหัวค่ำ - แหล่งอ้างอิง: - [RFC 4459: MTU and Fragmentation Issues with In-the-Network Tunneling](https://www.rfc-editor.org/rfc/rfc4459) · IETF · fragmentation และปัญหา path MTU ที่เกิดจากขนาดที่ส่งได้ลดลงเพราะเฮดเดอร์ encapsulation ของ tunnel กลางทาง - [RFC 8899: Packetization Layer Path MTU Discovery for Datagram Transports](https://www.rfc-editor.org/rfc/rfc8899) · IETF · แนะนำ 1,200 ไบต์เป็นขนาดปลอดภัยพื้นฐาน (BASE_PLPMTU) สำหรับการส่งแบบ datagram เช่น UDP - [Azure network round-trip latency statistics](https://learn.microsoft.com/en-us/azure/networking/azure-network-latency) · Microsoft Azure · ความหน่วงไปกลับที่เพิ่มขึ้นเมื่อผ่านจุดเชื่อมต่อในประเทศอื่น: โซล–โตเกียว 30 ms, โซล–ฮ่องกง 39 ms, โซล–สิงคโปร์ 68 ms ### L5 อุปกรณ์เครือข่ายในดาต้าเซ็นเตอร์ (11 สาเหตุ) #### dc-firewall · ตารางเซสชันของไฟร์วอลล์เต็ม · Firewall session table exhaustion ไฟร์วอลล์บันทึกทุกการเชื่อมต่อที่ปล่อยผ่านไว้ในตารางเซสชันเพื่อติดตาม เมื่อตารางเต็มก็รับการเชื่อมต่อใหม่ไม่ได้ - ทำไม → ผลคือ → บนหน้าจอ: จำนวนเซสชันชนขีดจำกัดเพราะการเชื่อมต่อทะลักหรือถูกโจมตี → ไม่มีช่องว่างให้บันทึกการเชื่อมต่อใหม่ จึงถูกปฏิเสธ → คนที่พยายามเข้าใหม่เข้าเกมไม่ได้หรือโหลดไม่จบ และการเชื่อมต่อเดิมบางส่วนก็หลุด - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ใช้ระบบคิวล็อกอินคุมการเชื่อมต่อที่ทะลักเข้ามาพร้อมกัน, ใช้การเชื่อมต่อซ้ำเพื่อไม่ให้เปิดการเชื่อมต่อสั้น ๆ ซ้ำไปมา, เก็บกวาดการเชื่อมต่อที่ heartbeat ขาดไปก่อน (เพื่อไม่ให้การเชื่อมต่อที่ตายแล้วกินที่ในตารางเซสชันนาน) ไคลเอนต์: ส่ง heartbeat ทุกช่วงไม่เกินครึ่งหนึ่งของ idle timeout ที่สั้นที่สุด, เชื่อมต่อใหม่อัตโนมัติเมื่อหลุด โดยค่อย ๆ เพิ่มช่วงห่างของการลองใหม่และกระจายแบบสุ่ม (เพื่อไม่ให้แห่กลับมาพร้อมกันอีก) - งานฝั่งทีมอินฟรา: เพิ่มขนาดตารางเซสชัน, เก็บกวาดการเชื่อมต่อที่จบเร็วให้ไว (ลดไทม์เอาต์ของเซสชันที่ปิดแล้ว), ถ้าจะลด idle timeout ของเซสชัน ให้แจ้งค่านั้นแก่ทีมพัฒนาเกมเพื่อปรับช่วง heartbeat ให้ตรงกัน, บล็อกการโจมตี, ตั้ง alert อัตราการใช้งานของจำนวนเซสชัน - บนกราฟ: ชนเพดานแล้วแบนราบ (จำนวนเซสชันของไฟร์วอลล์, จำนวนการเชื่อมต่อใหม่ที่ล้มเหลว) - จุดที่ต้องดู: ดูกราฟจำนวนเซสชันพร้อมกันของไฟร์วอลล์คู่กับขีดจำกัดเซสชัน และหาใน log ของอุปกรณ์ว่ามีการทิ้งเพราะสร้างเซสชันไม่ได้หรือไม่ ถ้าเป็นไฟร์วอลล์ Linux ให้เทียบ nf_conntrack_count กับ nf_conntrack_max และดู “nf_conntrack: table full, dropping packet” ใน dmesg ถ้าเป็นอินสแตนซ์ AWS ให้ดู conntrack_allowance_exceeded ใน ethtool -S - สัญญาณว่าใช่: ตั้งแต่จำนวนเซสชันแบนราบที่ขีดจำกัด การเชื่อมต่อใหม่ที่ล้มเหลวก็เพิ่มขึ้น พร้อมกับ log ที่สร้างเซสชันไม่สำเร็จหรือตัวนับการทิ้งที่เพิ่มขึ้น - สัญญาณว่าไม่ใช่: ถ้าจำนวนเซสชันยังห่างจากขีดจำกัดมากแต่เข้าเกมไม่ได้ น่าจะเป็น “คิวรอเชื่อมต่อ (backlog) ล้น” หรือฝั่งเซิร์ฟเวอร์ล็อกอิน ถ้าหลุดเฉพาะการเชื่อมต่อที่ idle น่าจะเป็น “connection tracking ของ security group บนคลาวด์หมดอายุ” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Netfilter Conntrack Sysfs variables](https://docs.kernel.org/networking/nf_conntrack-sysctl.html) · Linux kernel · จำนวนรายการสูงสุดในตาราง connection tracking (nf_conntrack_max), เวลาเก็บการเชื่อมต่อที่กำลังปิด (TIME_WAIT และ FIN_WAIT ค่าเริ่มต้น 120 วินาที), TCP ที่เชื่อมต่อแล้วค่าเริ่มต้น 5 วัน, จำนวนรายการปัจจุบัน (nf_conntrack_count) - [Amazon EC2 security group connection tracking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-connection-tracking.html) · AWS · ถ้าเกินจำนวนการเชื่อมต่อที่ติดตามได้ต่ออินสแตนซ์ แพ็กเก็ตของการเชื่อมต่อใหม่จะถูกทิ้ง, การเชื่อมต่อที่ idle อาจทำให้ตารางติดตามเต็ม - [Infrastructure layer attacks](https://docs.aws.amazon.com/whitepapers/latest/aws-best-practices-ddos-resiliency/infrastructure-layer-attacks.html) · AWS · การโจมตีอย่าง SYN flood จับทรัพยากรของเซิร์ฟเวอร์ ไฟร์วอลล์ และโหลดบาลานเซอร์ไว้ - [net/netfilter/nf_conntrack_core.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/nf_conntrack_core.c?h=v6.12) · Linux kernel · เมื่อตาราง connection tracking เต็ม จะบันทึก “nf_conntrack: table full, dropping packet” แล้วทิ้งแพ็กเก็ตของการเชื่อมต่อใหม่ - [Monitor network performance for ENA settings on your EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html) · AWS · conntrack_allowance_exceeded: จำนวนแพ็กเก็ตที่ถูกทิ้งเพราะเกินขีดจำกัด connection tracking ของอินสแตนซ์ ดูได้ด้วย ethtool -S #### dc-ddos · การอ้อมผ่านระบบป้องกัน DDoS และ false positive · DDoS scrubbing latency, false positives เมื่อเบี่ยงทราฟฟิกไป scrubbing center เพื่อกันการโจมตี เส้นทางจะยาวขึ้น และบางครั้งระบบเข้าใจผิดว่าผู้เล่นปกติเป็นการโจมตีแล้วบล็อก - ทำไม → ผลคือ → บนหน้าจอ: หลังตรวจพบการโจมตี (หรือตลอดเวลา) ทราฟฟิกขาเข้าถูกเบี่ยงไป scrubbing center → เส้นทางยาวขึ้น และแพ็กเก็ตปกติบางส่วนถูกตัดสินว่าเป็นการโจมตี → ปิงสูงขึ้นทั้งหมด, เฉพาะบางพื้นที่หรือบาง ISP ที่เข้าเกมไม่ได้ - อาการ: อินพุตดีเลย์, เข้าเกมไม่ได้/โหลดไม่จบ, วาร์ป / ปัจจัย: ความหน่วง, แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: สรุปรูปแบบทราฟฟิกของเกม (พอร์ต, ขนาดแพ็กเก็ต, จำนวนแพ็กเก็ตต่อวินาที) แชร์ให้ทีมอินฟรา, รักษาขนาดแพ็กเก็ต UDP ไว้ไม่เกิน 1,200 ไบต์ - งานฝั่งทีมอินฟรา: ตั้งกฎป้องกันให้ตรงกับรูปแบบทราฟฟิกของเกม, มีจุด scrubbing แยกตามภูมิภาค, ลดขนาดแพ็กเก็ต TCP ในช่วง tunnel (ปรับ MSS), ตรวจ false positive จากอัตราการเชื่อมต่อล้มเหลวแยกตามพื้นที่และ ISP - ตัวเลขที่ควรรู้: ถ้าจุด scrubbing อยู่ในประเทศเดียวกัน จะเพิ่มไม่กี่ ms ถ้าผ่านจุดในประเทศอื่น จะเพิ่ม 30–100 ms ขึ้นไป ปกติเฉพาะขาเข้าที่อ้อม ส่วนการตอบกลับของเซิร์ฟเวอร์ออกไปตรง ๆ ถ้ารับทราฟฟิกที่กรองแล้วกลับมาทาง tunnel ขนาดที่ส่งได้ในครั้งเดียว (MTU) ก็ลดลง และอาจกลายเป็นปัญหาที่เฉพาะแพ็กเก็ตใหญ่หายไป - บนกราฟ: ขึ้นเป็นขั้นบันไดจากจุดหนึ่ง (RTT (ปิง), อัตราการเชื่อมต่อล้มเหลวแยกตามพื้นที่/ISP) - จุดที่ต้องดู: วางประวัติการเริ่มและหยุดเบี่ยงทราฟฟิก (scrubbing) และ log การบล็อกของอุปกรณ์หรือบริการป้องกัน ไว้บนแกนเวลาเดียวกับกราฟ RTT และอัตราการเชื่อมต่อล้มเหลวแยกตามพื้นที่และ ISP แล้วใช้ mtr หรือ traceroute จากพื้นที่ที่มีปัญหาดูว่ามีจุด scrubbing แทรกอยู่ในเส้นทางหรือไม่ - สัญญาณว่าใช่: เมื่อเปิดการเบี่ยงทราฟฟิก RTT ขึ้นไปหนึ่งขั้นและอยู่ระดับนั้น แล้วกลับมาเมื่อปิด หรือ log การบล็อกมี IP ของผู้เล่นปกติ และอัตราการเชื่อมต่อล้มเหลวสูงขึ้นเฉพาะพื้นที่หรือ ISP นั้น - สัญญาณว่าไม่ใช่: ถ้า RTT สูงขึ้นในเวลาที่ไม่มีประวัติการเบี่ยงหรือบล็อก น่าจะเป็น “เส้นทางวิ่งอ้อม” หรือ “เส้นทาง BGP เปลี่ยนและ converge ใหม่” ถ้าหายเฉพาะแพ็กเก็ตใหญ่ น่าจะเป็น “MTU ไม่ตรงกัน (เฉพาะแพ็กเก็ตใหญ่ที่หาย)” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Maximum transmission unit and maximum segment size](https://developers.cloudflare.com/magic-transit/reference/mtu-mss/) · Cloudflare · ทราฟฟิกขาเข้าถูกกรองแล้วส่งต่อผ่าน GRE tunnel (MTU 1,476), การตอบกลับขาออกส่งออกอินเทอร์เน็ตตรง (DSR), แนะนำให้จำกัด TCP MSS ไว้ไม่เกิน 1,436 ถ้าไม่ปรับ แพ็กเก็ตใหญ่จะถูกทิ้งหรือถูก fragment - [Azure network round-trip latency statistics](https://learn.microsoft.com/en-us/azure/networking/azure-network-latency) · Microsoft Azure · ระดับความหน่วงไปกลับตามตำแหน่งของจุด scrubbing: โซล–พื้นที่ปูซาน 8 ms, โซล–โตเกียว 30 ms, โซล–สิงคโปร์ 68 ms #### dc-lb-idle · idle timeout ของโหลดบาลานเซอร์ · Load balancer idle timeout โหลดบาลานเซอร์ลบการเชื่อมต่อที่ idle ทิ้งหลังผ่านไประยะหนึ่ง ฝั่งเกมยังคิดว่าการเชื่อมต่อยังอยู่ จนกระทั่งหลุด - ทำไม → ผลคือ → บนหน้าจอ: ผู้เล่นไม่ส่งแพ็กเก็ตใด ๆ อยู่พักหนึ่ง (เปิดหน้าต่างสนทนา, ไม่อยู่หน้าจอ) → โหลดบาลานเซอร์เก็บกวาดการเชื่อมต่อที่ idle (ค่าเริ่มต้นที่พบบ่อย 60–350 วินาที) → หลุดทันทีที่กลับมาขยับอีกครั้ง - อาการ: หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: หลังอยู่เฉย ๆ สักพัก - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ส่ง heartbeat ทุกช่วงไม่เกินครึ่งหนึ่งของ idle timeout ที่สั้นที่สุด (ถ้าอยู่หลัง ALB ที่ 60 วินาที ก็ไม่เกิน 30 วินาที), เชื่อมต่อใหม่อัตโนมัติเมื่อหลุด เซิร์ฟเวอร์: ตอบ heartbeat และถ้าไม่ได้รับตามเวลาที่กำหนดให้เก็บกวาดการเชื่อมต่อก่อน, ใช้ session token รับต่อ - งานฝั่งทีมอินฟรา: ตรวจค่า idle timeout ของโหลดบาลานเซอร์ในเส้นทางแล้วแชร์ให้ทีมพัฒนาเกม และเพิ่มค่าถ้าจำเป็น - ตัวเลขที่ควรรู้: ค่าเริ่มต้นของ AWS ALB คือ 60 วินาที, NLB คือ TCP 350 วินาทีและ UDP 120 วินาที, Azure Load Balancer คือ TCP 4 นาที ค่า TCP ของ ALB และ NLB ปรับได้ แต่ UDP 120 วินาทีของ NLB ปรับไม่ได้ เมื่อครบเวลา ALB จะปิดการเชื่อมต่อฝั่งเซิร์ฟเวอร์ด้วย ส่วน NLB ลบทิ้งเงียบ ๆ การเชื่อมต่อจึงมักยังเหลืออยู่ฝั่งเซิร์ฟเวอร์โดยที่เซิร์ฟเวอร์ไม่รู้ - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนครั้งที่หลุด, ระยะเวลา idle ก่อนหลุด) - จุดที่ต้องดู: ตรวจค่า idle timeout ที่ตั้งไว้ของโหลดบาลานเซอร์ในเส้นทาง แล้วรวบรวมเวลาตั้งแต่แพ็กเก็ตสุดท้ายจนหลุดของแต่ละการเชื่อมต่อที่หลุด ถ้าเป็น AWS NLB ให้ดู TCP_ELB_Reset_Count (จำนวน RST ที่โหลดบาลานเซอร์ส่ง) ใน CloudWatch ด้วย - สัญญาณว่าใช่: ระยะเวลา idle ของการเชื่อมต่อที่หลุดกระจุกอยู่หลังค่าที่ตั้งไว้พอดี (ALB 60 วินาที, NLB TCP 350 วินาที ฯลฯ) และทำซ้ำได้เมื่ออยู่เฉย ๆ นานกว่านั้นแล้วขยับ ถ้าเป็น NLB TCP_ELB_Reset_Count จะเพิ่มในเวลานั้น - สัญญาณว่าไม่ใช่: ถ้าหลุดโดยไม่เกี่ยวกับระยะเวลา idle ก็ไม่ใช่สาเหตุนี้ ถ้าเซิร์ฟเวอร์ที่ผู้เล่นเชื่อมต่อตรงโดยไม่มีโหลดบาลานเซอร์หลุดกระจุกแถว 350 วินาที น่าจะเป็น “connection tracking ของ security group บนคลาวด์หมดอายุ” ถ้าเป็นฝั่งเราเตอร์ที่บ้านผู้เล่น น่าจะเป็น “NAT mapping หมดอายุ” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Edit attributes for your Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/edit-load-balancer-attributes.html) · AWS · idle timeout ของ ALB ค่าเริ่มต้น 60 วินาที (1–4,000 วินาที) ถ้าการเชื่อมต่อฝั่งไคลเอนต์หรือฝั่ง target เงียบตลอดช่วงนี้ โหลดบาลานเซอร์จะปิดการเชื่อมต่อ - [Network Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/network-load-balancers.html) · AWS · idle timeout ของ TCP ใน NLB ค่าเริ่มต้น 350 วินาที (60–6,000 วินาที) เมื่อเลยไปจะหยุดติดตามเท่านั้น ถ้ามีข้อมูลมาหลังจากนั้นจะตอบด้วย RST, flow ของ UDP 120 วินาทีปรับไม่ได้ - [Configure load balancer TCP reset and idle timeout](https://learn.microsoft.com/en-us/azure/load-balancer/load-balancer-tcp-idle-timeout) · Microsoft Azure · idle timeout ของ Azure Load Balancer ค่าเริ่มต้น 4 นาที (4–100 นาที) ถ้าเกินจะไม่รับประกันว่าเซสชันยังอยู่, TCP reset เป็นตัวเลือกที่เปิดเพิ่มได้ - [CloudWatch metrics for your Network Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-cloudwatch-metrics.html) · AWS · TCP_ELB_Reset_Count: จำนวนแพ็กเก็ต RST ที่โหลดบาลานเซอร์สร้างและส่งออกไป #### dc-cloud-conntrack · connection tracking ของ security group บนคลาวด์หมดอายุ · Cloud security group connection tracking timeout ไฟร์วอลล์ที่ผูกกับเซิร์ฟเวอร์บนคลาวด์ (security group) ก็ติดตามการเชื่อมต่อเช่นกัน และรายการที่ติดตามของการเชื่อมต่อที่ idle จะหมดอายุหลังเวลาที่กำหนด แม้เป็นเซิร์ฟเวอร์ที่ผู้เล่นเชื่อมต่อตรงโดยไม่มีโหลดบาลานเซอร์ ผู้เล่นที่อยู่เฉย ๆ ก็อาจหลุดได้ - ทำไม → ผลคือ → บนหน้าจอ: security group ถูกตั้งค่าในแบบที่ติดตามการเชื่อมต่อของเกม (อนุญาตเฉพาะบาง IP, จำกัดกฎขาออก, ผ่าน NLB ฯลฯ) → รายการที่ติดตามของการเชื่อมต่อที่ idle อยู่พักหนึ่งหมดอายุ แล้ว security group ทิ้งแพ็กเก็ตที่มาหลังจากนั้นเงียบ ๆ → กลับมาขยับหลังจากไม่อยู่หน้าจอ แล้วไม่มีการตอบสนองจนหลุด โปรแกรมเซิร์ฟเวอร์ไม่รู้ตัวอยู่นาน - อาการ: หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: หลังอยู่เฉย ๆ สักพัก - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ส่ง heartbeat ทุกช่วงไม่เกินครึ่งหนึ่งของ idle timeout ที่สั้นที่สุด (ถ้า TCP 350 วินาที ก็ไม่เกิน 175 วินาที ถ้า UDP stream 180 วินาที ก็ไม่เกิน 90 วินาที), เชื่อมต่อใหม่อัตโนมัติเมื่อหลุด เซิร์ฟเวอร์: ตอบ heartbeat และถ้าไม่ได้รับตามเวลาที่กำหนดให้เก็บกวาดการเชื่อมต่อก่อน, ใช้ session token รับต่อ - งานฝั่งทีมอินฟรา: ตรวจเวลา connection tracking ของอินสแตนซ์ (TcpEstablishedTimeout) และเพิ่มถ้าจำเป็น (UDP สูงสุด 180 วินาที จึงเพิ่มไม่ได้), ทบทวนการตั้ง security group แบบที่ไม่เกิดการติดตาม (พอร์ตเกมอนุญาตทุก IP, กฎขาออกอนุญาตทั้งหมด ส่วนการเชื่อมต่อที่ผ่าน NLB ยังถูกติดตามอยู่ดี), ทดสอบ idle เมื่อย้ายไปอินสแตนซ์รุ่นใหม่ - ตัวเลขที่ควรรู้: สำหรับ AWS อินสแตนซ์ประเภท Nitro v6 จะลบรายการที่ติดตามของการเชื่อมต่อ TCP ที่ idle หลัง 350 วินาทีเป็นค่าเริ่มต้น (ประเภทอื่น 5 วัน) ส่วน UDP ค่าเริ่มต้นคือ 180 วินาทีสำหรับ flow ที่มีคำขอและการตอบกลับไปมาหลายครั้ง (stream) และ 30 วินาทีสำหรับ flow ที่ไปทางเดียวหรือมีคำขอและการตอบกลับแค่ครั้งเดียว - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนครั้งที่หลุด, ระยะเวลา idle ก่อนหลุด) - จุดที่ต้องดู: ตรวจการตั้งเวลา connection tracking ของอินสแตนซ์และกฎของ security group (ว่าเป็นแบบที่เกิดการติดตามหรือไม่) แล้วรวบรวมระยะเวลา idle ของการเชื่อมต่อที่หลุด ทันทีหลังหลุด ใช้ ss -tnoi บนเซิร์ฟเวอร์ดูว่าการเชื่อมต่อนั้นยังเป็น ESTABLISHED ตัวจับเวลาส่งซ้ำ (timer:(on,…)) ทำงานอยู่ และ backoff เพิ่มขึ้นหรือไม่ - สัญญาณว่าใช่: ระยะเวลา idle ของการเชื่อมต่อที่หลุดกระจุกอยู่หลัง TCP 350 วินาที, UDP stream 180 วินาที หรือ UDP ทางเดียว 30 วินาทีพอดี และซ็อกเก็ตฝั่งเซิร์ฟเวอร์ยังเป็น ESTABLISHED โดยไม่รู้ว่าการเชื่อมต่อขาดไปแล้ว (ถ้าเซิร์ฟเวอร์มีข้อมูลต้องส่ง ก็จะส่งซ้ำวนไปเรื่อย ๆ) - สัญญาณว่าไม่ใช่: ถ้า security group ตั้งแบบที่ไม่ติดตาม (พอร์ตเกมอนุญาตทุก IP, กฎขาออกอนุญาตทั้งหมด, ไม่ผ่าน NLB) ก็ไม่ใช่สาเหตุนี้ ถ้าผ่าน NLB ให้เทียบค่ากับ “idle timeout ของโหลดบาลานเซอร์” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Amazon EC2 security group connection tracking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-connection-tracking.html) · AWS · ค่าเริ่มต้นของการติดตาม TCP ที่ idle คือ 350 วินาที (Nitro v6, ประเภทอื่น 432,000 วินาที=5 วัน), UDP ทางเดียว 30 วินาที และ stream 180 วินาที (สูงสุด 180), ถ้ากฎอนุญาตทุก IP จะไม่ติดตาม, การเชื่อมต่อที่ผ่าน NLB ถูกติดตามเสมอ - [Update the TCP idle timeout for your Network Load Balancer listener](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/update-idle-timeout.html) · AWS · ถ้า idle timeout ของ NLB ยาวกว่าเวลา connection tracking ของอินสแตนซ์ปลายทาง ฝั่งอินสแตนซ์จะทิ้งสถานะการเชื่อมต่อเงียบ ๆ ไปก่อน - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · timer:(on,…) ของ -o คือตัวจับเวลาส่งซ้ำ ส่วน backoff ของ -i คือจำนวนครั้งที่เพิ่มเวลารอส่งซ้ำเป็นสองเท่า #### dc-nat-gateway · ขีดจำกัดการเชื่อมต่อ/พอร์ตของ NAT gateway บนคลาวด์ · Cloud NAT gateway connection / port limits การเชื่อมต่อที่เซิร์ฟเวอร์ใน private subnet ออกไปภายนอก (ระบบยืนยันตัวตนของแพลตฟอร์ม, ระบบชำระเงิน, API ภายนอก) จะผ่าน NAT gateway ซึ่งแปลง IP และพอร์ตก่อนส่งออก ถ้าการเชื่อมต่อพร้อมกันไปยังปลายทางเดียวกันเกินขีดจำกัดพอร์ตของ gateway การเชื่อมต่อใหม่จะล้มเหลว - ทำไม → ผลคือ → บนหน้าจอ: เซิร์ฟเวอร์เปิดการเชื่อมต่อสั้น ๆ จำนวนมากไปยัง IP ภายนอกเดียวกัน เช่น ระบบยืนยันตัวตนของแพลตฟอร์มหรือระบบชำระเงิน หรือเปิดการเชื่อมต่อทิ้งไว้นาน → NAT gateway จัดสรรพอร์ตต้นทางสำหรับปลายทางนั้นเพิ่มไม่ได้ การเชื่อมต่อใหม่จึงล้มเหลว → ในเกมปกติดี แต่เฉพาะฟีเจอร์ที่เรียกภายนอก เช่น ล็อกอิน ชำระเงิน หรือแจกของรางวัล ที่ล้มเหลวหรือช้า (เข้าเกมไม่ได้/โหลดไม่จบ, กดไม่ติด/โรลแบ็ค) - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, กดไม่ติด/โรลแบ็ค / ปัจจัย: แพ็กเก็ตหาย, ความหน่วง - ใครเจอ: เฉพาะบางฟีเจอร์, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ช่วงพีคหัวค่ำ, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ใช้การเชื่อมต่อกับ API ภายนอกซ้ำ (HTTP keep-alive, connection pool) และไม่เปิดการเชื่อมต่อใหม่ทุกคำขอ, การเชื่อมต่อ idle ที่เก็บไว้ใน pool ให้ส่ง keepalive ถี่กว่า idle timeout ของ NAT (AWS 350 วินาที) หรือปิดก่อน, เมื่อล้มเหลวให้ค่อย ๆ เพิ่มช่วงห่างของการลองใหม่และกระจายแบบสุ่ม, บันทึกอัตราล้มเหลวและความหน่วงแยกตามการเรียกภายนอกแต่ละแบบ - งานฝั่งทีมอินฟรา: เพิ่ม IP ให้ NAT gateway (AWS public NAT gateway ผูก Elastic IP ได้แค่ 2 ตัวเป็นค่าเริ่มต้น ถ้าต้องการมากกว่านั้นต้องขอเพิ่มโควตา), แยก gateway ตาม availability zone และ subnet, ตั้ง alert เมตริกการจัดสรรพอร์ตล้มเหลว (AWS ErrorPortAllocation, สถานะ Failed ของ Azure SNAT Connection Count, OUT_OF_RESOURCES ของ Google Cloud dropped_sent_packets_count), สำหรับ Google Cloud NAT ให้เพิ่มจำนวนพอร์ตขั้นต่ำต่อ VM หรือใช้ dynamic port allocation - ตัวเลขที่ควรรู้: AWS NAT gateway เปิดการเชื่อมต่อพร้อมกันไปยังปลายทางเดียวกัน (IP, พอร์ต, โปรโตคอล) ได้สูงสุด 55,000 การเชื่อมต่อต่อ IP หนึ่งตัว และเพิ่มได้ด้วยการผูก IP สูงสุด 8 ตัว การเชื่อมต่อที่เงียบไป 350 วินาทีจะถูกลบ และแพ็กเก็ตที่ส่งมาทางการเชื่อมต่อนั้นหลังจากนั้นจะได้ RST กลับไป Azure NAT Gateway มี SNAT port 64,512 พอร์ตต่อ public IP หนึ่งตัว (IP สูงสุด 16 ตัว) ส่วน Google Cloud NAT แบ่ง 64,512 พอร์ตต่อ NAT IP หนึ่งตัวให้แต่ละ VM และค่าเริ่มต้นของพอร์ตขั้นต่ำต่อ VM คือ 64 พอร์ต (static allocation) ถ้าใช้ค่าเริ่มต้น VM หนึ่งเครื่องจึงมักเปิดการเชื่อมต่อพร้อมกันไปยังปลายทางเดียวกันได้แค่ 64 การเชื่อมต่อ - บนกราฟ: ชนเพดานแล้วแบนราบ (จำนวนการเชื่อมต่อพร้อมกันของ NAT gateway, จำนวนการจัดสรรพอร์ตล้มเหลว) - จุดที่ต้องดู: สำหรับ AWS ดูเมตริก NAT gateway ใน CloudWatch ได้แก่ ErrorPortAllocation, ActiveConnectionCount และ PacketsDropCount (Azure ดู SNAT Connection Count ที่กรองสถานะ Failed และ Dropped Packets, Google Cloud ดู dropped_sent_packets_count ที่ reason เป็น OUT_OF_RESOURCES) เทียบกับเวลาที่เซิร์ฟเวอร์เกมเรียกภายนอกล้มเหลว - สัญญาณว่าใช่: ในเวลาที่เรียกภายนอกล้มเหลว ErrorPortAllocation (Azure คือ SNAT Connection Count สถานะ Failed, Google Cloud คือการทิ้งแบบ OUT_OF_RESOURCES) มีค่ามากกว่า 0 และความล้มเหลวกระจุกที่การเรียกไปยังปลายทางหนึ่งหรือสองแห่งที่มีการเชื่อมต่อมาก เช่น เซิร์ฟเวอร์ยืนยันตัวตนหรือชำระเงิน - สัญญาณว่าไม่ใช่: ถ้าการจัดสรรพอร์ตล้มเหลวเป็น 0 แต่ connect ของเซิร์ฟเวอร์เกมล้มเหลวด้วย EADDRNOTAVAIL และ TIME_WAIT ใกล้เต็มช่วง ephemeral port น่าจะเป็น “ephemeral port หมดในการเชื่อมต่อระหว่างเซิร์ฟเวอร์” ถ้าเชื่อมต่อได้แต่ตอบช้า น่าจะเป็น “การพึ่งพาเซอร์วิสภายนอก” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ต่างจาก “ephemeral port หมดในการเชื่อมต่อระหว่างเซิร์ฟเวอร์” ที่ ephemeral port ของเซิร์ฟเวอร์เครื่องเดียวหมด ขีดจำกัดนี้อยู่ที่ NAT gateway และเซิร์ฟเวอร์ทุกเครื่องหลัง gateway ใช้ร่วมกัน (Google Cloud NAT แบ่งให้แต่ละ VM) ถ้า TIME_WAIT และช่วง ephemeral port ฝั่งเซิร์ฟเวอร์ยังเหลือ แต่เฉพาะการเรียกภายนอกที่ล้มเหลว ก็น่าจะเป็นสาเหตุนี้ พอร์ตของการเชื่อมต่อที่ปิดแล้วก็ไม่ถูกนำกลับมาใช้กับปลายทางเดียวกันทันที (Azure มี cooldown, Google Cloud ใช้ไม่ได้ระหว่าง TIME_WAIT) ยิ่งเปิดการเชื่อมต่อสั้น ๆ ซ้ำ ๆ ก็ยิ่งชนขีดจำกัดเร็ว - แหล่งอ้างอิง: - [NAT gateway basics](https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-basics.html) · AWS · การเชื่อมต่อพร้อมกันไปยังปลายทางเดียวกัน (IP, พอร์ต, โปรโตคอลปลายทาง) 55,000 การเชื่อมต่อต่อ IPv4 หนึ่งตัว เพิ่มได้ด้วยการผูก IP สูงสุด 8 ตัว (Elastic IP ของ public NAT gateway ค่าเริ่มต้น 2 ตัว เพิ่มได้ด้วยการขอเพิ่มโควตา), แบนด์วิดท์ขยายเองจาก 5 Gbps ถึง 100 Gbps และปริมาณงานขยายเองจาก 1 ล้านถึง 10 ล้านแพ็กเก็ตต่อวินาที ถ้าเกินขีดนั้นแพ็กเก็ตจะถูกทิ้ง - [NAT gateway metrics and dimensions](https://docs.aws.amazon.com/vpc/latest/userguide/metrics-dimensions-nat-gateway.html) · AWS · ErrorPortAllocation: จำนวนครั้งที่จัดสรรพอร์ตต้นทางไม่ได้ (ถ้ามากกว่า 0 แปลว่าการเชื่อมต่อพร้อมกันมากเกินไป), ActiveConnectionCount, IdleTimeoutCount (การเชื่อมต่อที่ถูกเก็บกวาดเพราะ idle 350 วินาที), PacketsDropCount - [Troubleshoot NAT gateways](https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-troubleshooting.html) · AWS · ถ้า idle 350 วินาที การเชื่อมต่อจะหมดอายุ และถ้าส่งต่อจะได้ RST กลับ, แนะนำ keepalive ที่สั้นกว่า 350 วินาที, ถ้าชนขีดจำกัดการเชื่อมต่อ ให้แยก gateway ตาม availability zone, เพิ่ม IP หรือลดจำนวนการเชื่อมต่อ - [Source Network Address Translation (SNAT) with Azure NAT Gateway](https://learn.microsoft.com/en-us/azure/nat-gateway/nat-gateway-snat) · Microsoft Azure · SNAT port 64,512 พอร์ตต่อ public IP หนึ่งตัว (IP สูงสุด 16 ตัว), การเชื่อมต่อแต่ละรายการไปยังปลายทางเดียวกันต้องใช้พอร์ตต่างกัน, พอร์ตที่ปิดแล้วมี cooldown ก่อนนำกลับมาใช้กับปลายทางเดียวกัน - [Metrics and alerts for Azure NAT Gateway](https://learn.microsoft.com/en-us/azure/nat-gateway/nat-metrics) · Microsoft Azure · ถ้า SNAT Connection Count ที่กรองสถานะ Failed มากกว่า 0 อาจเป็นไปได้ว่า SNAT port หมด, Dropped Packets - [IP addresses and ports](https://cloud.google.com/nat/docs/ports-and-addresses) · Google Cloud · TCP และ UDP อย่างละ 64,512 พอร์ตต่อ NAT IP หนึ่งตัว, ค่าเริ่มต้นของพอร์ตขั้นต่ำต่อ VM คือ 64 (static allocation) และ 32 (dynamic allocation), จำนวนพอร์ตที่จองให้ VM จำกัดจำนวนการเชื่อมต่อพร้อมกันไปยังปลายทางเดียวกัน, การเชื่อมต่อที่ปิดแล้วใช้ไม่ได้ระหว่าง TIME_WAIT - [Logs and metrics](https://cloud.google.com/nat/docs/monitoring) · Google Cloud · reason OUT_OF_RESOURCES ของ dropped_sent_packets_count: แพ็กเก็ตที่ถูกทิ้งเพราะ NAT IP หรือพอร์ตไม่พอ #### dc-lb-imbalance · โหลดบาลานเซอร์กระจายไม่เท่ากัน/health check ตัดสินผิด · LB imbalance, bad health checks การเชื่อมต่อไปกองที่เซิร์ฟเวอร์เครื่องเดียว หรือยังส่งคนไปเซิร์ฟเวอร์ที่ตายแล้วอยู่เรื่อย ๆ - ทำไม → ผลคือ → บนหน้าจอ: กฎการกระจายไม่เหมาะ หรือ health check มองไม่เห็นสถานะจริง → เซิร์ฟเวอร์เครื่องเดียวโหลดเกิน หรือพยายามเชื่อมต่อไปเซิร์ฟเวอร์ที่ตายแล้ว → เฉพาะบางแชนแนลหรือบางคนที่เป็นสโลว์โมชั่น, เข้าเกมไม่ได้หรือโหลดไม่จบ - อาการ: สโลว์โมชั่น, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: การหยุดชะงัก, แพ็กเก็ตหาย - ใครเจอ: บางจุด/บางแชนแนล / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ทำ health check ที่ตอบคำขอตรวจของโหลดบาลานเซอร์ตามสถานะเกมจริง (ทิกยังเดิน, การเชื่อมต่อ DB), ส่งค่าโหลดของเซิร์ฟเวอร์ไปด้วย - งานฝั่งทีมอินฟรา: เปลี่ยน health check เป็นแบบที่ตรวจการตอบสนองจริงของเกม, กระจายโหลดตามโหลดของเซิร์ฟเวอร์, มอนิเตอร์ความต่างของจำนวนการเชื่อมต่อในแต่ละเซิร์ฟเวอร์ - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนการเชื่อมต่อ/อัตราการใช้ CPU แยกตามเซิร์ฟเวอร์) - จุดที่ต้องดู: ซ้อนจำนวนการเชื่อมต่อ (ss -s) และอัตราการใช้ CPU ของแต่ละเซิร์ฟเวอร์หลังโหลดบาลานเซอร์ไว้ในกราฟเดียว แล้วเทียบสถานะ health ของ target ในโหลดบาลานเซอร์ (AWS ดู HealthyHostCount และ UnHealthyHostCount ใน CloudWatch) กับสถานะจริงของเซิร์ฟเวอร์เกม - สัญญาณว่าใช่: มีเซิร์ฟเวอร์หนึ่งหรือสองเครื่องที่จำนวนการเชื่อมต่อและ CPU สูงกว่าเครื่องอื่นมาก หรือเซิร์ฟเวอร์ที่ทิกหยุดไปแล้วยังมีสถานะ health เป็น “healthy” และยังรับการเชื่อมต่อใหม่อยู่เรื่อย ๆ - สัญญาณว่าไม่ใช่: ถ้าจำนวนการเชื่อมต่อแต่ละเซิร์ฟเวอร์เท่า ๆ กันแต่ช้าเฉพาะแชนแนลเดียว น่าจะเป็นโหลดภายในแชนแนลนั้น (“พื้นที่ที่รันบนเธรดเดียวโหลดเกิน (hotspot)”) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - กรณีจริง: aws-2025 - แหล่งอ้างอิง: - [Load Balancing in the Datacenter](https://sre.google/sre-book/load-balancing-datacenter/) · Google · round robin แบบธรรมดาทำให้การใช้ CPU ระหว่างงานต่างกันได้ถึง 2 เท่า, การกระจายแบบถ่วงน้ำหนักที่ backend ส่งค่าโหลดมากับการตอบกลับและ health check, สถานะ lame duck ที่บอกว่าจะไม่รับคำขอเพิ่ม - [Health checks for Network Load Balancer target groups](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/target-group-health-checks.html) · AWS · ค่าเริ่มต้นของ health check คือทุก 30 วินาที และถ้าล้มเหลว 2 ครั้งจะถูกถอดออก, บริการ UDP ตรวจด้วย health check แบบ TCP หรือ HTTP จึงแนะนำให้ตั้งค่าให้สะท้อนสถานะจริงของบริการ - [CloudWatch metrics for your Network Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-cloudwatch-metrics.html) · AWS · HealthyHostCount และ UnHealthyHostCount: จำนวน target ที่ถูกตัดสินว่าปกติและผิดปกติ #### dc-microburst · microburst ที่สวิตช์ · Switch microburst drops ถ้าเซิร์ฟเวอร์หลายเครื่องส่งแพ็กเก็ตถึงผู้เล่นหลายพันคนพร้อมกันในชั่วขณะเดียว บัฟเฟอร์ขนาดเล็กของพอร์ตสวิตช์ที่ทราฟฟิกนั้นไหลมารวมกันจะล้นภายในไม่ถึง 1 ms - ทำไม → ผลคือ → บนหน้าจอ: เวิลด์บอสเกิดหรือมีสกิลขนาดใหญ่ หรือทิกของเซิร์ฟเวอร์หลายเครื่องตรงกันพอดีจนส่งออกพร้อมกัน → บัฟเฟอร์ (หลายร้อย KB ถึงหลาย MB ต่อพอร์ต) ตรงจุดที่หลายพอร์ตไหลเข้าพอร์ตเดียว หรือจุดที่ข้อมูลไหลจากพอร์ตเร็วไปพอร์ตช้า เต็มในชั่วขณะ → แพ็กเก็ตบางส่วนถูกทิ้ง หลายคนวาร์ปหรือสกิลไม่ออกพร้อมกัน - อาการ: วาร์ป, กดไม่ติด/โรลแบ็ค / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: บางจุด/บางแชนแนล / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเครือข่าย (ทีมอินฟรา), อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: กระจายการส่งให้สม่ำเสมอภายในทิก (pacing), เหลื่อมเวลาเริ่มทิกของแต่ละเซิร์ฟเวอร์เล็กน้อย - งานฝั่งทีมอินฟรา: เครือข่าย: ใช้สวิตช์บัฟเฟอร์ใหญ่, กระจายทราฟฟิก (วางเซิร์ฟเวอร์แยกไปหลายสวิตช์และหลายพอร์ต), เฝ้าดูตัวนับการทิ้งแยกตามพอร์ตสวิตช์ เครื่องเซิร์ฟเวอร์/OS: จำกัดความเร็วส่งรวมของเซิร์ฟเวอร์ (shaper ของ Linux tc) - ตัวเลขที่ควรรู้: พอร์ต 10 Gbps ส่งข้อมูลได้ประมาณ 1.25 MB ใน 1 ms ถ้าทราฟฟิกจากสองพอร์ตไหลเข้าพอร์ตเดียวพร้อมกัน ข้อมูลจะกองเพิ่ม 1.25 MB ทุก 1 ms แม้อัตราการใช้งานเฉลี่ย 1 วินาทีจะอยู่ที่ 10% ในระดับ 1 ms ก็อาจล้นได้ - บนกราฟ: สูงตามจำนวนคนและโหลด (จำนวนการทิ้งขาออกของพอร์ตสวิตช์) - จุดที่ต้องดู: เก็บตัวนับการทิ้งขาออก (ifOutDiscards หรือ output drops แล้วแต่อุปกรณ์) ของพอร์ตสวิตช์ที่เซิร์ฟเวอร์ต่ออยู่และของพอร์ตที่ทราฟฟิกนั้นไหลมารวม ด้วยช่วงเวลาสั้นที่สุดเท่าที่ทำได้ แล้วเทียบกับเวลาที่บอสเกิดหรือมีการต่อสู้ขนาดใหญ่ ดูแค่กราฟอัตราการใช้งานเฉลี่ย 1 วินาทีหรือ 1 นาทีจะมองไม่เห็น - สัญญาณว่าใช่: อัตราการใช้งานเฉลี่ยต่ำ แต่ทุกครั้งที่คนไปรวมกันที่จุดเดียว การทิ้งขาออกจะเพิ่ม และตอนนั้นผู้เล่นหลายคนแจ้งพร้อมกันว่าวาร์ปหรือสกิลไม่ออก - สัญญาณว่าไม่ใช่: ถ้าการทิ้งเพิ่มต่อเนื่องในช่วงที่อัตราการใช้งานเฉลี่ยสูง น่าจะเป็น “ลิงก์ของดาต้าเซ็นเตอร์เต็ม” ถ้า input error (CRC) เพิ่ม น่าจะเป็น “สายเสีย/พอร์ตมี error” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [High-Resolution Measurement of Data Center Microbursts (IMC 2017)](https://conferences.sigcomm.org/imc/2017/papers/imc17-final60.pdf) · ACM · burst ในดาต้าเซ็นเตอร์มากกว่า 70% จบภายในหลายสิบ µs, แม้พอร์ตที่อัตราการใช้งานเฉลี่ยราว 9% ก็ยังทิ้งแพ็กเก็ตเพราะ burst - [Data Center TCP (DCTCP) (SIGCOMM 2010)](https://conferences.sigcomm.org/sigcomm/2010/papers/sigcomm/p63.pdf) · ACM · สวิตช์ทั่วไปมีบัฟเฟอร์ตื้น (48 พอร์ตใช้ร่วมกัน 4 MB, พอร์ตเดียวใช้ได้ถึงราว 700 KB), ถ้าหลาย flow ไหลเข้าพอร์ตเดียวในชั่วขณะสั้น ๆ จะเกิดแพ็กเก็ตหาย - [RFC 2863: The Interfaces Group MIB](https://www.rfc-editor.org/rfc/rfc2863) · IETF · ifOutDiscards: จำนวนแพ็กเก็ตที่ส่งออกไม่ได้และถูกทิ้งแม้ไม่มี error ด้วยเหตุผล เช่น ต้องเคลียร์พื้นที่บัฟเฟอร์ #### dc-uplink · ลิงก์ของดาต้าเซ็นเตอร์เต็ม · Uplink saturation ถ้าการแจกจ่ายแพตช์ การส่ง log หรือการสำรองข้อมูล ใช้ลิงก์เดียวกับเกม ลิงก์จะเต็ม - ทำไม → ผลคือ → บนหน้าจอ: การส่งข้อมูลขนาดใหญ่กินลิงก์เดียวกัน → คิวในลิงก์และแพ็กเก็ตหายเพิ่มขึ้น → ปิงสูงขึ้นทั้งเซิร์ฟเวอร์และวาร์ป - อาการ: อินพุตดีเลย์, วาร์ป / ปัจจัย: ความหน่วง, แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: เป็นรอบสม่ำเสมอ, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: เครือข่าย: ให้ความสำคัญกับทราฟฟิกเกมก่อน (QoS), แยกลิงก์สำหรับส่งข้อมูลขนาดใหญ่, ตั้ง alert อัตราการใช้งานลิงก์ เครื่องเซิร์ฟเวอร์/OS: จำกัดความเร็วการสำรองข้อมูล การส่ง log และการ deploy แล้วรันในช่วงที่คนน้อย - บนกราฟ: ชนเพดานแล้วแบนราบ (อัตราการใช้งานลิงก์, RTT (ปิง)) - จุดที่ต้องดู: วางอัตราการใช้งาน (คำนวณจาก SNMP ifHCInOctets และ ifHCOutOctets) และการทิ้งขาออก (ifOutDiscards) ของอินเทอร์เฟซลิงก์ดาต้าเซ็นเตอร์ (uplink) ไว้บนแกนเวลาเดียวกับตารางการสำรองข้อมูล การ deploy และการส่ง log - สัญญาณว่าใช่: ในเวลาที่อัตราการใช้งานลิงก์ชนขีดจำกัดแบนด์วิดท์จนแบนราบ RTT ของทั้งเซิร์ฟเวอร์และการทิ้งสูงขึ้น และเวลานั้นตรงกับงานส่งข้อมูลขนาดใหญ่ - สัญญาณว่าไม่ใช่: ถ้าอัตราการใช้งานระดับนาทียังห่างจากขีดจำกัดมากแต่มีการทิ้ง น่าจะเป็น “microburst ที่สวิตช์” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 4594: Configuration Guidelines for DiffServ Service Classes](https://www.rfc-editor.org/rfc/rfc4594) · IETF · แยกทราฟฟิกแบบโต้ตอบเรียลไทม์อย่างเกม กับการส่งข้อมูลขนาดใหญ่อย่างการสำรองข้อมูล ไว้คนละ service class - [RFC 7567: IETF Recommendations Regarding Active Queue Management](https://www.rfc-editor.org/rfc/rfc7567) · IETF · ถ้าข้อมูลที่เข้ามาในอุปกรณ์มากกว่าความเร็วที่ส่งออกได้ คิวจะสะสม และคิวที่มากเกินไปเป็นสาเหตุหลักของความหน่วง - [RFC 2863: The Interfaces Group MIB](https://www.rfc-editor.org/rfc/rfc2863) · IETF · ifHCInOctets และ ifHCOutOctets: จำนวนไบต์ที่อินเทอร์เฟซรับและส่ง (64 บิต), ifOutDiscards: จำนวนแพ็กเก็ตที่ส่งออกไม่ได้และถูกทิ้ง #### dc-failover · การ failover ของอุปกรณ์เครือข่าย · Network device failover ระหว่างไม่กี่วินาทีที่เราเตอร์หรือไฟร์วอลล์ตัวหนึ่งเสียแล้วสลับไปใช้อุปกรณ์สำรอง (failover) ทุกคนจะค้างพร้อมกัน - ทำไม → ผลคือ → บนหน้าจอ: อุปกรณ์เสียหรืออยู่ระหว่างซ่อมบำรุง จึงสลับไปใช้อุปกรณ์สำรอง → การสลับใช้เวลาหลายวินาที ถ้าข้อมูลเซสชันไม่ได้ซิงก์ไว้ การเชื่อมต่อจะถูกรีเซ็ต → ผู้เล่นทุกคนในเซิร์ฟเวอร์ค้างพร้อมกัน, หลุดจำนวนมาก - อาการ: ค้าง, หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ตั้งไทม์เอาต์ที่ทนการขาดช่วงสั้น ๆ (หลายวินาที) ได้, ถ้าขาดแล้วเชื่อมต่อใหม่ ให้รับเซสชันต่อด้วย session token ไคลเอนต์: เชื่อมต่อใหม่อัตโนมัติเมื่อหลุด (กระจายช่วงห่างของการลองใหม่แบบสุ่มเพื่อไม่ให้แห่มาพร้อมกัน) - งานฝั่งทีมอินฟรา: ทำระบบสำรอง (redundancy) ที่แชร์สถานะการเชื่อมต่อ, ใช้ BFD ตรวจจับความเสียหายภายใน 1 วินาที, ทดสอบการสลับเป็นประจำ - ตัวเลขที่ควรรู้: ถ้าอุปกรณ์ตรวจพบความเสียหายได้ทันที จะใช้เวลาราว 1–3 วินาที ถ้าไม่มีการตรวจจับความเสียหายแบบเร็ว (BFD) และปล่อยให้ใช้ตัวจับเวลาค่าเริ่มต้นของ BGP อย่างเดียว เส้นทางอาจขาดไป 90–180 วินาทีกว่าอุปกรณ์ข้างเคียงจะตรวจพบ - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนการเชื่อมต่อ, ปริมาณรับส่งรวมของเซิร์ฟเวอร์) - จุดที่ต้องดู: ดู event log ของเราเตอร์และไฟร์วอลล์ (VRRP เปลี่ยนบทบาท, เซสชัน BFD หรือ BGP ล่ม, ประวัติ failover) และจำนวนการเชื่อมต่อกับปริมาณรับส่งรวมของเซิร์ฟเวอร์ในเวลาเดียวกัน - สัญญาณว่าใช่: ในเวลาที่ log ของอุปกรณ์บันทึกการสลับ ทราฟฟิกของเซิร์ฟเวอร์ทุกเครื่องหลังอุปกรณ์นั้นเป็น 0 ไปหลายวินาที หรือจำนวนการเชื่อมต่อลดลงพร้อมกัน - สัญญาณว่าไม่ใช่: ถ้าการเชื่อมต่อลดลงเฉพาะเซิร์ฟเวอร์เครื่องเดียว น่าจะเป็น “เซิร์ฟเวอร์แครช” หรือ “ปัญหาไดรเวอร์/เฟิร์มแวร์ของ NIC” ถ้า log ของอุปกรณ์สะอาด และเซิร์ฟเวอร์ที่หยุดเป็น VM บนคลาวด์เครื่องเดียว น่าจะเป็น “การซ่อมบำรุงโฮสต์คลาวด์/live migration” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 5880: Bidirectional Forwarding Detection (BFD)](https://www.rfc-editor.org/rfc/rfc5880) · IETF · วิธี Hello ของโปรโตคอล routing ใช้เวลาตรวจพบความเสียหายเกิน 1 วินาที จึงมี BFD ที่สร้างขึ้นเพื่อตรวจพบได้เร็วกว่านั้น - [RFC 7938: Use of BGP for Routing in Large-Scale Data Centers](https://www.rfc-editor.org/rfc/rfc7938) · IETF · ถ้าพึ่งแค่ BGP keepalive การ converge จะช้า ถ้ารับสัญญาณลิงก์ล่มทันทีแล้วตัดเซสชัน จะตรวจพบได้ในระดับ ms และ converge ใหม่ - [RFC 5798: Virtual Router Redundancy Protocol (VRRP) Version 3 for IPv4 and IPv6](https://www.rfc-editor.org/rfc/rfc5798) · IETF · VRRP ส่ง advertisement ทุก 1 วินาทีเป็นค่าเริ่มต้น ถ้า advertisement ขาดไปนานกว่าราว 3 เท่าของช่วงนั้น อุปกรณ์สำรองจะรับบทบาทแทน (ถ้าใช้ค่าเริ่มต้นจะเป็นราว 3 วินาทีเศษ) #### dc-bad-cable · สายเสีย/พอร์ตมี error · Bad cable / optics (CRC errors) ถ้าโมดูลออปติกหรือสายเสีย แพ็กเก็ตที่ผ่านเส้นทางนั้นจะเสียหายในสัดส่วนคงที่ - ทำไม → ผลคือ → บนหน้าจอ: บิตผิดพลาดเพราะโมดูลออปติกหรือสายเสีย → อุปกรณ์ทิ้งแพ็กเก็ตที่เสียหายเงียบ ๆ → เฉพาะเซิร์ฟเวอร์และผู้เล่นบางส่วนที่ใช้เส้นทางนั้น แพ็กเก็ตหายต่อเนื่องจนวาร์ปหรือดีดกลับ - อาการ: วาร์ป, ดีดกลับ / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: บางจุด/บางแชนแนล / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: มอนิเตอร์และตั้ง alert ตัวนับ error ของพอร์ต (CRC), เปลี่ยนชิ้นส่วนอย่างโมดูลออปติกหรือสาย, ระหว่างรอเปลี่ยนให้ถอดลิงก์ที่มีปัญหาออกแล้วอ้อมไปทางอื่น - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวน CRC error แยกตามพอร์ต, อัตราแพ็กเก็ตหายแยกตามเซิร์ฟเวอร์/เส้นทาง) - จุดที่ต้องดู: ดูตัวนับ CRC ทั้งสองฝั่งของลิงก์ สวิตช์ดู FCS error (dot3StatsFCSErrors) และ input error (ifInErrors) ของพอร์ต เซิร์ฟเวอร์ดู crc ใน RX errors ของ ip -s -s link (สถิติเคอร์เนล rx_crc_errors) - สัญญาณว่าใช่: CRC error ของพอร์ตหนึ่งเพิ่มขึ้นต่อเนื่องไม่ว่าปริมาณทราฟฟิกหรือช่วงเวลาจะเป็นอย่างไร และมีแพ็กเก็ตหายเฉพาะเซิร์ฟเวอร์และผู้เล่นที่ผ่านพอร์ตนั้น - สัญญาณว่าไม่ใช่: ถ้าไม่มี CRC error แต่การทิ้งขาออกเพิ่มอย่างเดียว น่าจะเป็นความแออัด (“microburst ที่สวิตช์”, “ลิงก์ของดาต้าเซ็นเตอร์เต็ม”) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Understanding and Mitigating Packet Corruption in Data Center Networks (SIGCOMM 2017)](https://www.microsoft.com/en-us/research/wp-content/uploads/2017/06/CorrOpt_SIGCOMM2017.pdf) · ACM · วิเคราะห์ลิงก์ 350,000 เส้นในดาต้าเซ็นเตอร์: สาเหตุของความเสียหายคือโมดูลออปติกเสีย ไฟเบอร์เสียหาย และขั้วต่อสกปรก อัตราความเสียหายคงที่ไม่ว่าปริมาณการใช้งานจะเป็นเท่าไร และถอดลิงก์ที่มีปัญหาออกมาซ่อมโดยยังรักษาจำนวนเส้นทางไว้ - [RFC 3635: Definitions of Managed Objects for the Ethernet-like Interface Types](https://www.rfc-editor.org/rfc/rfc3635) · IETF · ตัวนับการตรวจเฟรม (FCS) ล้มเหลว (dot3StatsFCSErrors) และ error นี้รวมอยู่ใน input error (ifInErrors) - [Interface statistics](https://docs.kernel.org/networking/statistics.html) · Linux kernel · rx_crc_errors: จำนวนแพ็กเก็ตที่รับมาพร้อม CRC error, ดูแยกตามชนิด error ได้ด้วย ip -s -s link #### dc-mtu · MTU ไม่ตรงกัน (เฉพาะแพ็กเก็ตใหญ่ที่หาย) · MTU black hole ถ้า MTU (ขนาดที่ส่งได้ในครั้งเดียว) ของช่วงกลางทางลดลง แต่ข้อความแจ้งว่าขนาดเกินถูกบล็อก แพ็กเก็ตใหญ่จะหายไปเรื่อย ๆ - ทำไม → ผลคือ → บนหน้าจอ: MTU ลดลงในช่วง tunnel หรือ VPN → ข้อความแจ้งว่าขนาดเกิน (ICMP) ถูกไฟร์วอลล์บล็อก ฝั่งส่งจึงไม่รู้ → ค้างเฉพาะตอนเปิดหน้าจอที่ข้อมูลเยอะ เช่น กระเป๋าหรือรายชื่อตัวละคร แล้วหลุด - อาการ: ค้าง, หลุด, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: บางพื้นที่/บาง ISP, เราคนเดียว / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ถ้าจะลดจากฝั่งเซิร์ฟเวอร์โดยตรง ให้ตั้งขนาด segment สูงสุดของซ็อกเก็ต (TCP_MAXSEG) เพราะแค่แบ่งข้อความในโค้ดเกมให้เล็กลงยังกันไม่ได้, รักษาขนาดแพ็กเก็ต UDP ไว้ไม่เกิน 1,200 ไบต์ - งานฝั่งทีมอินฟรา: เครือข่าย: ลดขนาดแพ็กเก็ต TCP ในช่วง tunnel (ปรับ MSS), อนุญาตข้อความแจ้งว่าขนาดเกิน (ICMP) ในไฟร์วอลล์และ network ACL บนคลาวด์ เครื่องเซิร์ฟเวอร์/OS: อนุญาตข้อความแจ้งว่าขนาดเกิน (ICMP) ในไฟร์วอลล์ของเซิร์ฟเวอร์และ security group บนคลาวด์ด้วย, เปิดการค้นหา MTU ของเคอร์เนลเซิร์ฟเวอร์ (tcp_mtu_probing=1) ไว้เป็นด่านสุดท้าย ซึ่งจะทำงานหลังหยุดไปหลายวินาทีแล้ว - ตัวเลขที่ควรรู้: ปกติ 1,500 ไบต์ เมื่อผ่าน tunnel จะลดเหลือราว 1,400 - บนกราฟ: สูงเฉพาะบางกลุ่ม (หลุดแยกตามพื้นที่/ISP, การตอบกลับขนาดใหญ่ล้มเหลว) - จุดที่ต้องดู: จาก PC ของผู้เล่นที่มีปัญหา ส่ง ping ที่เปิดแฟล็กห้ามแบ่ง (DF) ไปที่เซิร์ฟเวอร์โดยเปลี่ยนขนาดไปเรื่อย ๆ Windows ใช้ ping /f /l 1472 SERVER_IP, Linux ใช้ ping -M do -s 1472 SERVER_IP (1,472 คือ MTU 1,500 ลบเฮดเดอร์ IP 20 ไบต์และเฮดเดอร์ ICMP 8 ไบต์) ลดขนาดลงเรื่อย ๆ เพื่อหาขนาดสูงสุดที่ผ่าน และตรวจว่า security group และไฟร์วอลล์ฝั่งเซิร์ฟเวอร์อนุญาตข้อความ ICMP แจ้งว่าขนาดเกิน (Fragmentation Needed) หรือไม่ - สัญญาณว่าใช่: ping ขนาดเล็กผ่าน แต่ ping DF ขนาด 1,472 ไบต์ล้มเหลว (ไม่มีการตอบกลับ หรือได้ error ว่าต้อง fragment) และขนาดสูงสุดที่ผ่านเล็กราว 1,400 ผู้เล่นในพื้นที่เดียวกันค้างเฉพาะตอนเปิดหน้าจอที่ข้อมูลเยอะ - สัญญาณว่าไม่ใช่: ถ้า ping DF ขนาด 1,472 ไบต์ก็ผ่านดี แสดงว่าไม่ใช่ปัญหา path MTU ถ้า ping ขนาดเล็กก็ไม่ผ่าน แสดงว่า ICMP ถูกบล็อกทั้งหมด จึงใช้วิธีนี้ตัดสินไม่ได้ - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [RFC 2923: TCP Problems with Path MTU Discovery](https://www.rfc-editor.org/rfc/rfc2923) · IETF · ถ้าไฟร์วอลล์บล็อก ICMP (Fragmentation Needed) การค้นหา path MTU จะล้มเหลว และแพ็กเก็ตใหญ่จะหายไปเรื่อย ๆ (black hole), ping และการสื่อสารขนาดเล็กยังใช้ได้ จึงวินิจฉัยยาก - [Maximum transmission unit and maximum segment size](https://developers.cloudflare.com/magic-transit/reference/mtu-mss/) · Cloudflare · path MTU ของอินเทอร์เน็ต 1,500 เมื่อผ่าน GRE tunnel เหลือ 1,476 แนะนำให้จำกัด TCP MSS ไว้ไม่เกิน 1,436 - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_mtu_probing=1 ปกติปิดอยู่ และจะเปิดการค้นหา path MTU ของ TCP เมื่อตรวจพบ ICMP black hole - [ping(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ping.8.html) · iputils · -M do เปิดแฟล็ก DF และปฏิเสธแพ็กเก็ตที่ใหญ่กว่า path MTU, -s กำหนดขนาดข้อมูล (ค่าเริ่มต้น 56 ไบต์ บวกเฮดเดอร์ ICMP 8 ไบต์) - [ping](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ping) · Microsoft · /f เปิดแฟล็ก DF ใช้หาปัญหา path MTU และ /l กำหนดขนาดข้อมูล ### L6 การ์ดเครือข่ายของเซิร์ฟเวอร์ (9 สาเหตุ) #### nic-irq · อินเทอร์รัปต์ของ NIC กระจุกที่คอร์เดียว · Single-queue NIC / no RSS ถ้า NIC ส่งอินเทอร์รัปต์แจ้งว่าแพ็กเก็ตมาถึงไปที่คอร์ CPU เพียงคอร์เดียว คอร์นั้นจะกลายเป็นคอขวด - ทำไม → ผลคือ → บนหน้าจอ: มี receive queue เดียว หรือปิด RSS ที่กระจายไปหลายคอร์ → คอร์หนึ่งขึ้นถึง 100% จนดึงแพ็กเก็ตออกมาไม่ทัน → ตอนคนแห่มารวมกัน ทั้งเซิร์ฟเวอร์มีแพ็กเก็ตหายและความหน่วง (วาร์ป, อินพุตดีเลย์) - อาการ: วาร์ป, ดีดกลับ, อินพุตดีเลย์ / ปัจจัย: แพ็กเก็ตหาย, ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: ตั้งค่า RSS (NIC กระจาย) และ RPS (เคอร์เนลกระจาย), กระจายอินเทอร์รัปต์ไปหลายคอร์, ตั้งให้ UDP แบ่งคิวโดยดูถึงพอร์ตด้วย (rx-flow-hash udp4 sdfn ของ ethtool -N), แยกคอร์ที่จัดการอินเทอร์รัปต์ออกจากคอร์ของเธรดทิกเกม, เฝ้าดู %soft แยกตามคอร์ - ตัวเลขที่ควรรู้: ปริมาณที่คอร์หนึ่งประมวลผลผ่านเคอร์เนลได้อยู่ที่ราวหลายแสนแพ็กเก็ตต่อวินาที ขึ้นกับขนาดแพ็กเก็ตและการตั้งค่า ถ้าดูอัตราการใช้งานแยกตามคอร์แล้วพบว่าสัดส่วนงานรับแพ็กเก็ต (%soft ของ mpstat) กระจุกอยู่ที่คอร์เดียว ก็คือกรณีนี้ - บนกราฟ: ชนเพดานแล้วแบนราบ (%soft แยกตามคอร์, จำนวนแพ็กเก็ตรับต่อวินาที) - จุดที่ต้องดู: ใช้ mpstat -P ALL 1 ดู %soft (สัดส่วนเวลาจัดการ software interrupt) แยกตามคอร์ แล้วตรวจด้วย /proc/interrupts ว่าอินเทอร์รัปต์ของแต่ละคิวของ NIC ไปที่คอร์ไหน ใช้ ethtool -l ดูจำนวนคิว และ ethtool -S ดูจำนวนแพ็กเก็ตแยกตามคิว (ชื่อต่างกันตามไดรเวอร์) - สัญญาณว่าใช่: มีคอร์เดียวที่ %soft ติดอยู่ใกล้ 100% ส่วนคอร์อื่นว่าง และอินเทอร์รัปต์กับแพ็กเก็ตกระจุกอยู่ที่คิวเดียว ตั้งแต่นั้นจำนวนแพ็กเก็ตรับต่อวินาทีก็ไม่ขึ้นอีก - สัญญาณว่าไม่ใช่: ถ้า %soft กระจายเท่า ๆ กันในหลายคอร์ ก็ไม่ใช่สาเหตุนี้ ถ้า CPU ว่างแต่มีแพ็กเก็ตหาย น่าจะเป็น “เกินขีดจำกัด PPS ของคลาวด์” หรือ “ring buffer ไม่พอ” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: แม้มีหลายคิว ถ้าทราฟฟิกส่วนใหญ่มาจาก IP ไม่กี่ตัว เช่น เกตเวย์หรือ proxy ก็จะไปกองที่คิวเดียว สำหรับ UDP การตั้งค่าเริ่มต้นของ NIC บางรุ่นแบ่งคิวโดยดูแค่ IP ต้องเปลี่ยนให้ดูถึงพอร์ตด้วยจึงจะกระจายได้เท่า ๆ กัน - แหล่งอ้างอิง: - [Scaling in the Linux Networking Stack](https://docs.kernel.org/networking/scaling.html) · Linux kernel · RSS (NIC กระจายไปหลาย receive queue) และ RPS (เคอร์เนลกระจาย), การตั้งให้แต่ละคิวมีอินเทอร์รัปต์ของตัวเองแล้วแบ่งไปหลายคอร์, ถ้าการจัดการอินเทอร์รัปต์ขาเข้าเป็นคอขวด แนะนำ RSS - [How to receive a million packets per second](https://blog.cloudflare.com/how-to-receive-a-million-packets/) · Cloudflare · ผลการวัดที่ receive queue เดียวไปที่คอร์เดียว ทำให้คอร์นั้นตันที่ราว 350,000–430,000 แพ็กเก็ตต่อวินาที, กรณีที่ NIC hash UDP ด้วย IP อย่างเดียวจนไปกองที่คิวเดียว - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · ออปชัน ethtool -N rx-flow-hash udp4 ที่ใส่พอร์ต (f และ n) ลงใน hash ของ UDP ด้วย - [mpstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/mpstat.1.html) · sysstat · %soft: สัดส่วนเวลาที่ CPU ใช้จัดการ software interrupt, -P ALL ดูแยกตามคอร์ #### nic-ring · ring buffer ไม่พอ · RX ring buffer overflow ถ้า ring buffer ที่ NIC ใช้พักแพ็กเก็ตไว้ชั่วคราวมีขนาดเล็ก เมื่อแพ็กเก็ตทะลักเข้ามาในชั่วขณะ บัฟเฟอร์จะล้นและแพ็กเก็ตถูกทิ้ง - ทำไม → ผลคือ → บนหน้าจอ: ring buffer เล็กตามค่าเริ่มต้น (256–2,048 สล็อต แล้วแต่ไดรเวอร์) → ตอนเกิด burst บัฟเฟอร์ล้นก่อนที่ CPU จะดึงออกไป → แพ็กเก็ตหายเฉพาะตอน burst (วาร์ป, สกิลไม่ออก) ไม่มีร่องรอยใน log ของเซิร์ฟเวอร์เกม - อาการ: วาร์ป, กดไม่ติด/โรลแบ็ค / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: ขยาย ring buffer (ethtool -G), เฝ้าดูตัวนับการทิ้ง (drop) เช่น rx_missed_errors ใน ethtool -S (ชื่อต่างกันตามไดรเวอร์) - ตัวเลขที่ควรรู้: ถ้าแพ็กเก็ตทะลักเข้ามา 1 ล้านแพ็กเก็ตต่อวินาที 1,024 สล็อตจะเต็มในเวลาประมาณ 1 ms ระหว่างนั้นถ้า CPU มาช้าแค่ครั้งเดียวก็ล้น NIC ส่วนใหญ่เพิ่มได้ถึงหลายพันสล็อต - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (ตัวนับการทิ้งขาเข้าของ NIC) - จุดที่ต้องดู: เก็บตัวนับการทิ้งขาเข้าใน ethtool -S (rx_missed_errors, rx_fifo_errors ฯลฯ ชื่อต่างกันตามไดรเวอร์) และ missed ใน ip -s -s link ด้วยช่วงเวลาสั้น ๆ และใช้ ethtool -g ดูขนาด ring ปัจจุบันกับค่าสูงสุด - สัญญาณว่าใช่: ตัวนับการทิ้งเพิ่มขึ้นตอนเกิด burst และขนาด ring ปัจจุบันเล็กกว่าค่าสูงสุดมาก เมื่อขยาย ring การทิ้งก็ลดลง - สัญญาณว่าไม่ใช่: ถ้าตัวนับการทิ้งไม่ขยับแต่มีแพ็กเก็ตหาย น่าจะเป็นขั้นถัดไปในเคอร์เนล (“บัฟเฟอร์ซ็อกเก็ตของเคอร์เนลไม่พอ”) หรือช่วงเครือข่าย ถ้า %soft ของคอร์เดียวอยู่ที่ 100% น่าจะเป็น “อินเทอร์รัปต์ของ NIC กระจุกที่คอร์เดียว” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Linux Base Driver for Intel(R) Ethernet Network Connection (e1000)](https://docs.kernel.org/networking/device_drivers/ethernet/intel/e1000.html) · Linux kernel · receive descriptor ของ e1000 (สล็อตของ ring) ค่าเริ่มต้น 256 เพิ่มได้ถึง 4,096 - [drivers/net/ethernet/intel/ice/ice.h (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/net/ethernet/intel/ice/ice.h?h=v6.12) · Linux kernel · receive descriptor ของไดรเวอร์ ice ค่าเริ่มต้น 2,048 สูงสุด 8,160 - [Interface statistics](https://docs.kernel.org/networking/statistics.html) · Linux kernel · แพ็กเก็ตที่อุปกรณ์ทิ้งเพราะบัฟเฟอร์ไม่พอนับเป็น rx_missed_errors, สถิติเฉพาะไดรเวอร์ดูได้ด้วย ethtool -S - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · -g ดูขนาด ring (ค่าปัจจุบันและค่าสูงสุด), -G เปลี่ยนขนาด, -S ดูสถิติเฉพาะไดรเวอร์ #### nic-coalesce · interrupt coalescing มากเกินไป · Interrupt coalescing การรวบแพ็กเก็ตไว้แล้วแจ้งทีเดียวช่วยลดภาระ CPU แต่ก็ทำให้ช้าลงตามเวลาที่ใช้รวบ - ทำไม → ผลคือ → บนหน้าจอ: NIC รวบแพ็กเก็ตตามเวลาหรือจำนวนที่กำหนดแล้วจึงแจ้ง → แพ็กเก็ตต้องรอระหว่างรวบ → ความหน่วงเพิ่มขึ้นเล็กน้อย ปกติน้อยมาก แต่ถ้าตั้งมากเกินไปจะถึงระดับ ms - อาการ: อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: ใช้ adaptive coalescing, ปรับค่าให้เหมาะกับเซิร์ฟเวอร์เกม (ethtool -C) - ตัวเลขที่ควรรู้: ปกติหลายสิบถึงหลายร้อย µs ซึ่งสำหรับเกมมักน้อยจนไม่ต้องสนใจ แต่ถ้าตั้งค่ามากเกินไปจะขึ้นไปถึงระดับ ms - บนกราฟ: สูงตลอดตั้งแต่แรก (เวลาไปกลับภายในดาต้าเซ็นเตอร์เดียวกัน) - จุดที่ต้องดู: ใช้ ethtool -c ดูการตั้ง coalescing ปัจจุบัน (adaptive-rx, rx-usecs, rx-frames) แล้วเทียบเวลาไปกลับของ ping กับเซิร์ฟเวอร์อื่นในดาต้าเซ็นเตอร์เดียวกันก่อนและหลังเปลี่ยนค่า - สัญญาณว่าใช่: rx-usecs ตั้งไว้สูงตั้งแต่หลายร้อย µs ขึ้นไป และเมื่อลดค่าลง เวลาไปกลับภายในดาต้าเซ็นเตอร์เดียวกันก็ลดลงตามนั้น - สัญญาณว่าไม่ใช่: ถ้าลดค่าแล้วเวลาไปกลับยังเท่าเดิม ก็ไม่ใช่สาเหตุนี้ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Linux Driver for Intel(R) Ethernet Network Connection (e1000e)](https://docs.kernel.org/networking/device_drivers/ethernet/intel/e1000e.html) · Linux kernel · ค่าเริ่มต้นคือ adaptive interrupt moderation ที่ 4,000–20,000 ครั้งต่อวินาที (ห่างกัน 50–250 µs), ลดอินเทอร์รัปต์แล้วประหยัด CPU แต่ความหน่วงเพิ่มขึ้น - [Linux Base Driver for Intel(R) Ethernet Network Connection (e1000)](https://docs.kernel.org/networking/device_drivers/ethernet/intel/e1000.html) · Linux kernel · RxIntDelay หน่วงอินเทอร์รัปต์ขาเข้าได้ทีละ 1.024 µs สูงสุด 65,535 หน่วย (ราว 67 ms) ยิ่งตั้งค่าสูง ความหน่วงขาเข้ายิ่งเพิ่ม - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · การตั้งค่า adaptive-rx, rx-usecs และ rx-frames ของ ethtool -C #### nic-cloud-pps · เกินขีดจำกัด PPS ของคลาวด์ · Cloud PPS / bandwidth allowance เซิร์ฟเวอร์บนคลาวด์แต่ละประเภทมีขีดจำกัดจำนวนแพ็กเก็ตต่อวินาทีและแบนด์วิดท์ ถ้าเกินจะถูกทิ้งเงียบ ๆ - ทำไม → ผลคือ → บนหน้าจอ: ผู้เล่นออนไลน์พร้อมกันเพิ่มขึ้นจนจำนวนแพ็กเก็ตต่อวินาทีเกินขีดจำกัดของอินสแตนซ์ → เครือข่ายของคลาวด์ทิ้งส่วนที่เกิน → วาร์ปหรือสกิลไม่ออกจากแพ็กเก็ตหายที่หาสาเหตุไม่เจอ ขณะที่ CPU ของเซิร์ฟเวอร์ยังว่าง - อาการ: วาร์ป, กดไม่ติด/โรลแบ็ค / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: รวมแพ็กเก็ต (ส่งข้อความของหนึ่งทิกในแพ็กเก็ตเดียว), ไม่ส่งแพ็กเก็ตเล็กมาก ๆ ถี่เกินไป - งานฝั่งทีมอินฟรา: ตรวจและตั้ง alert ตัวนับที่เกินขีดจำกัด (AWS คือ pps_allowance_exceeded, conntrack_allowance_exceeded ฯลฯ), ใช้อินสแตนซ์ที่ใหญ่ขึ้น, เลี่ยงขีดจำกัด connection tracking ด้วยการตั้ง security group แบบที่ไม่เกิดการติดตาม - งานฝั่งภายนอก: สอบถามผู้ให้บริการคลาวด์ถึงขีดจำกัดจำนวนแพ็กเก็ตต่อวินาทีและ connection tracking ของอินสแตนซ์แต่ละประเภท - ตัวเลขที่ควรรู้: ขีดจำกัดต่างกันตามขนาดอินสแตนซ์ และขีดจำกัดจำนวนแพ็กเก็ตต่อวินาทีมักไม่เปิดเผย “สูงสุด 10 Gbps” ของอินสแตนซ์ขนาดเล็กเป็นความเร็ว burst ที่ใช้ได้เฉพาะช่วงที่ยังมีเครดิตเหลือ (ปกติ 5–60 นาที) ความเร็วพื้นฐานตามปกติต่ำกว่านั้นมาก - บนกราฟ: ชนเพดานแล้วแบนราบ (จำนวนแพ็กเก็ตต่อวินาที, ตัวนับ allowance ที่เกิน) - จุดที่ต้องดู: เก็บตัวนับของ ENA ใน ethtool -S ได้แก่ pps_allowance_exceeded, bw_in_allowance_exceeded, bw_out_allowance_exceeded และ conntrack_allowance_exceeded ด้วยช่วงเวลาสั้น ๆ แล้วดูคู่กับจำนวนแพ็กเก็ตต่อวินาที จะใช้ CloudWatch agent ส่งตัวนับเหล่านี้ขึ้น CloudWatch แล้วตั้ง alert ก็ได้ - สัญญาณว่าใช่: ในเวลาที่แพ็กเก็ตหาย ตัวนับ allowance ที่เกินเพิ่มขึ้น และจำนวนแพ็กเก็ตต่อวินาทีไม่ขึ้นเกินค่าหนึ่ง ขณะที่ CPU ของเซิร์ฟเวอร์ยังว่าง - สัญญาณว่าไม่ใช่: ถ้าตัวนับที่เกินไม่ขยับ ก็ไม่ใช่สาเหตุนี้ ถ้า %soft ของคอร์เดียวอยู่ที่ 100% น่าจะเป็น “อินเทอร์รัปต์ของ NIC กระจุกที่คอร์เดียว” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: conntrack_allowance_exceeded คือกรณีที่ตาราง connection tracking เต็มจนการเชื่อมต่อใหม่ถูกทิ้ง ถ้าตารางยังมีที่ว่างแต่การเชื่อมต่อที่ idle หลุดเพราะการติดตามหมดอายุ ให้ดูสาเหตุ “connection tracking ของ security group บนคลาวด์หมดอายุ” - แหล่งอ้างอิง: - [Monitor network performance for ENA settings on your EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html) · AWS · อินสแตนซ์แต่ละตัวมีขีดจำกัดแบนด์วิดท์, PPS และ connection tracking ถ้าเกินจะเข้าคิวไว้แล้วทิ้ง, ตัวนับ pps_allowance_exceeded และ conntrack_allowance_exceeded - [Amazon EC2 instance network bandwidth](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-network-bandwidth.html) · AWS · “สูงสุด N Gbps” ของอินสแตนซ์ที่มีไม่เกิน 16 vCPU คือ burst ที่ใช้เครดิต network I/O (ปกติ 5–60 นาที) เมื่อเครดิตหมดจะกลับไปที่แบนด์วิดท์พื้นฐาน #### nic-saturate · แบนด์วิดท์ NIC เต็ม · NIC bandwidth saturation ถ้าใช้การ์ด 1 Gbps หรือ 10 Gbps จนถึงขีดจำกัด transmit queue จะยาวขึ้น และสุดท้ายแพ็กเก็ตจะถูกทิ้ง - ทำไม → ผลคือ → บนหน้าจอ: broadcast เพิ่มขึ้นจนปริมาณส่งชนขีดจำกัดของการ์ด → transmit queue ยาวขึ้น และเมื่อล้นก็ทิ้ง → ความหน่วงและแพ็กเก็ตหายทั้งเซิร์ฟเวอร์ (อินพุตดีเลย์, วาร์ป) - อาการ: อินพุตดีเลย์, วาร์ป / ปัจจัย: ความหน่วง, แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ลดปริมาณการส่ง (AOI, บีบอัด, ส่งเฉพาะส่วนที่เปลี่ยน) - งานฝั่งทีมอินฟรา: เพิ่มการ์ด (NIC ที่เร็วขึ้น, บนคลาวด์ใช้อินสแตนซ์ที่ใหญ่ขึ้น), ตั้ง alert อัตราการใช้งาน NIC - บนกราฟ: ชนเพดานแล้วแบนราบ (ปริมาณส่งของ NIC, การทิ้งขาออก) - จุดที่ต้องดู: เทียบ txkB/s และ %ifutil (อัตราการใช้งานเทียบกับความเร็วอินเทอร์เฟซ) ของ sar -n DEV 1 กับความเร็ว NIC และแบนด์วิดท์ของอินสแตนซ์ แล้วดู TX dropped ของ ip -s link ประกอบ - สัญญาณว่าใช่: ปริมาณส่งแบนราบใกล้แบนด์วิดท์ของ NIC หรืออินสแตนซ์ และตั้งแต่นั้นการทิ้งขาออกและความหน่วงของทั้งเซิร์ฟเวอร์ก็เพิ่มขึ้น - สัญญาณว่าไม่ใช่: ถ้าแบนด์วิดท์ยังเหลือ ก็ไม่ใช่สาเหตุนี้ ถ้ามีแพ็กเก็ตเล็กจำนวนมากและมีแพ็กเก็ตหาย น่าจะเป็น “เกินขีดจำกัด PPS ของคลาวด์” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Interface statistics](https://docs.kernel.org/networking/statistics.html) · Linux kernel · tx_dropped: จำนวนแพ็กเก็ตที่ถูกทิ้งระหว่างส่งเพราะทรัพยากรไม่พอ - [Amazon EC2 instance network bandwidth](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-network-bandwidth.html) · AWS · แบนด์วิดท์ที่อินสแตนซ์ใช้ได้กำหนดตามจำนวน vCPU (ขนาดอินสแตนซ์) - [sar(1) — Linux manual page](https://man7.org/linux/man-pages/man1/sar.1.html) · sysstat · rxkB/s, txkB/s และ %ifutil (อัตราการใช้งานเทียบกับความเร็วอินเทอร์เฟซ) ของ -n DEV #### nic-noisy · โอเวอร์เฮดจาก virtualization/noisy neighbor · Noisy neighbors in virtualization ถ้า VM อื่นที่อยู่บนเครื่องจริงเครื่องเดียวกันใช้เครือข่ายหรือ CPU มาก การประมวลผลของเซิร์ฟเวอร์เราจะถูกเลื่อนออกไปอย่างไม่สม่ำเสมอ - ทำไม → ผลคือ → บนหน้าจอ: VM อื่นบนเครื่องจริงเครื่องเดียวกันใช้ทรัพยากรมาก → การประมวลผลแพ็กเก็ตของ VM เราล่าช้าอย่างไม่สม่ำเสมอ → เกิดจิตเตอร์ (ช่วงเวลาที่แพ็กเก็ตมาถึงไม่สม่ำเสมอ) เป็นครั้งคราวโดยไม่มีสาเหตุชัดเจน จนภาพกระตุก - อาการ: กระตุก / ปัจจัย: จิตเตอร์ - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: ภายนอก (ภายนอก) - งานฝั่งทีมอินฟรา: ใช้ dedicated host, ใช้อินสแตนซ์ที่รับประกันประสิทธิภาพ, อินสแตนซ์ที่จิตเตอร์ไม่หายให้หยุดแล้วเริ่มใหม่เพื่อย้ายไปโฮสต์อื่น - งานฝั่งภายนอก: แจ้งผู้ให้บริการคลาวด์เรื่องโฮสต์ที่มีปัญหา - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (จิตเตอร์ของเวลาไปกลับภายในดาต้าเซ็นเตอร์เดียวกัน, %steal) - จุดที่ต้องดู: ส่ง ping ต่อเนื่องไปยังเซิร์ฟเวอร์อื่นในดาต้าเซ็นเตอร์เดียวกันเพื่อบันทึกจิตเตอร์ของเวลาไปกลับ แล้วเทียบกับอินสแตนซ์อื่นที่ตั้งค่าแบบเดียวกัน พร้อมดู %steal ของ mpstat - สัญญาณว่าใช่: เฉพาะอินสแตนซ์นี้ที่จิตเตอร์ของเวลาไปกลับหรือ %steal พุ่งแบบไม่สม่ำเสมอ ส่วนอินสแตนซ์อื่นที่ตั้งค่าเหมือนกันนิ่ง เมื่อหยุดแล้วเริ่มใหม่จนย้ายไปโฮสต์อื่น อาการก็หายไป - สัญญาณว่าไม่ใช่: ถ้าอินสแตนซ์ที่ตั้งค่าเหมือนกันพุ่งเหมือนกันหมด ก็ไม่ใช่ปัญหาที่โฮสต์ ให้ดูโหลดฝั่งเซิร์ฟเวอร์เกมหรือช่วงเครือข่าย - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [How EC2 instance stop and start works](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/how-ec2-instance-stop-start-works.html) · AWS · เมื่อหยุดแล้วเริ่มอินสแตนซ์ใหม่ ส่วนใหญ่จะถูกย้ายไปโฮสต์ใหม่ (ยกเว้น dedicated host) - [mpstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/mpstat.1.html) · sysstat · %steal: สัดส่วนเวลาที่ virtual CPU นี้จำต้องรอ ขณะที่ไฮเปอร์ไวเซอร์รัน virtual CPU ตัวอื่น #### nic-host-maintenance · การซ่อมบำรุงโฮสต์คลาวด์/live migration · Cloud host maintenance / live migration เมื่อผู้ให้บริการคลาวด์ซ่อมบำรุงเครื่องจริง (โฮสต์) จะย้าย VM ไปโฮสต์อื่น (live migration) หรือหยุด VM ชั่วคราว ระหว่างนั้นทั้งเซิร์ฟเวอร์จะหยุด และถ้าหยุดนาน การเชื่อมต่อจะขาด - ทำไม → ผลคือ → บนหน้าจอ: ผู้ให้บริการย้าย VM ไปโฮสต์อื่นหรือพักการทำงานชั่วคราว เพราะซ่อมบำรุงโฮสต์หรือคาดว่าฮาร์ดแวร์จะเสีย → ระหว่างย้าย CPU หน่วยความจำ และเครือข่ายช้าลง และช่วงท้าย VM จะหยุดสนิทชั่วครู่ (ไม่ถึง 1 วินาทีจนถึงราว 30 วินาที ขึ้นกับผู้ให้บริการและวิธี) → ทุกคนในเซิร์ฟเวอร์ค้างพร้อมกันแล้วกรอเร็วหรือวาร์ป ถ้าหยุดนานกว่าไทม์เอาต์ จะหลุดจำนวนมาก - อาการ: ค้าง, กรอเร็ว, วาร์ป, หลุด / ปัจจัย: การหยุดชะงัก, แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: ตั้งไทม์เอาต์ที่ทนการหยุดหลายวินาทีได้, ตั้งเพดานจำนวนทิกที่เร่งคำนวณให้ทันหลังหยุด, คำนวณเวลาที่ผ่านไปด้วย monotonic clock, มีขั้นตอนบันทึกความคืบหน้าและย้ายผู้เล่นไปเซิร์ฟเวอร์อื่นเมื่อได้รับแจ้งการซ่อมบำรุง - งานฝั่งทีมอินฟรา: สมัครรับและตั้ง alert การแจ้งซ่อมบำรุง (Google Cloud maintenance-event, AWS scheduled event และ AWS Health, Azure Scheduled Events), เมื่อได้รับแจ้งให้เปลี่ยนเซิร์ฟเวอร์ล่วงหน้าในช่วงที่ผู้เล่นน้อย, ปรับเวลาซ่อมบำรุงถ้าผู้ให้บริการอนุญาต (Azure Maintenance Configuration, AWS scheduled event บางประเภท), เทียบประวัติการซ่อมบำรุงกับบันทึกเหตุขัดข้อง - งานฝั่งภายนอก: สอบถามผู้ให้บริการคลาวด์ถึงกำหนดการซ่อมบำรุงและขอบเขตผลกระทบ, แจ้งผู้ให้บริการถ้าอินสแตนซ์เดิมหยุดซ้ำ ๆ - ตัวเลขที่ควรรู้: Google Compute Engine ระบุว่าการหยุดระหว่าง live migration ปกติสั้นกว่า 1 วินาทีมาก และระหว่างหยุด นาฬิการะบบอาจกระโดดไปข้างหน้าได้สูงสุด 5 วินาที ค่า maintenance-event ใน metadata จะเปลี่ยน 60 วินาทีก่อนย้าย (กรณีที่เคยอ่านค่านี้ไว้อย่างน้อยหนึ่งครั้งก่อนหน้านั้น) Azure ระบุว่าการซ่อมบำรุงที่ไม่ต้องรีบูตแทบทุกครั้งหยุดไม่ถึง 10 วินาที นาน ๆ ครั้ง (ขนาดทั่วไปไม่เกินหนึ่งครั้งใน 18 เดือน) หยุดราว 30 วินาที และ live migration ปกติไม่เกิน 5 วินาที Azure Scheduled Events แจ้งการหยุดแบบนี้ (Freeze) ล่วงหน้าอย่างน้อย 15 นาที แต่ถ้าฮาร์ดแวร์ของโฮสต์เสียกะทันหัน จะเริ่มกู้คืนทันทีโดยไม่แจ้ง - บนกราฟ: ขาดช่วงแล้วมารวดเดียว (จำนวนแพ็กเก็ตรับส่งของเซิร์ฟเวอร์, ช่วงห่างระหว่างทิก) - จุดที่ต้องดู: เทียบเวลาที่หยุดกับบันทึกของผู้ให้บริการ Google Cloud ดู compute.instances.migrateOnHostMaintenance ใน audit log, AWS ดู scheduled event ใน describe-instance-status และ AWS Health, Azure ดูเวลาที่มี Microsoft.Compute/virtualMachines/liveMigration/action ใน Activity Log และเวลาที่เมตริกความพร้อมใช้งานของ VM (VmAvailabilityMetric) ลดลงเป็น 0 ภายในเซิร์ฟเวอร์ให้ดูว่าเมตริกและ log ว่างไปในช่วงที่หยุดหรือไม่ และนาฬิกากระโดดทันทีหลังจากนั้นหรือไม่ (log การซิงก์เวลา) - สัญญาณว่าใช่: เวลาที่ทั้งเซิร์ฟเวอร์หยุดตรงกับเวลาซ่อมบำรุงหรือ migration ที่ผู้ให้บริการบันทึกไว้ และในไม่กี่วินาทีนั้นเมตริกและ log ในเซิร์ฟเวอร์ว่างหมด - สัญญาณว่าไม่ใช่: ถ้าไม่มีในบันทึกของผู้ให้บริการ และมีการหยุดสั้น ๆ ซ้ำบ่อย น่าจะเป็น “CPU steal (VM)” ถ้า log ของเคอร์เนลมีประวัติรีเซ็ต NIC น่าจะเป็น “ปัญหาไดรเวอร์/เฟิร์มแวร์ของ NIC” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: AWS แจ้งผ่าน scheduled event โดย system-reboot หมายถึงรีบูตพร้อมย้ายไปโฮสต์ใหม่ และ system-maintenance หมายถึงอาจได้รับผลกระทบชั่วครู่จากการซ่อมบำรุงเครือข่ายหรือไฟฟ้า แม้จะหยุดแค่ไม่กี่วินาที ไคลเอนต์ที่ไม่ได้รับ ACK ของแพ็กเก็ตที่ส่งไปยังเซิร์ฟเวอร์ระหว่างนั้นจะเพิ่มเวลารอส่งซ้ำเป็นสองเท่าไปเรื่อย ๆ แม้เซิร์ฟเวอร์จะกลับมาทำงานแล้ว การเชื่อมต่อ TCP จึงอาจยังหยุดอยู่ต่ออีกพักหนึ่ง (“TCP RTO และ exponential backoff”) เมื่อตื่นจากการหยุด นาฬิกาอาจกระโดดจนกลายเป็น “นาฬิการะบบกระโดด (NTP step)” และ health check ของโหลดบาลานเซอร์อาจล้มเหลวจนถอดเซิร์ฟเวอร์นั้นออกชั่วคราว อินสแตนซ์ที่ย้ายไม่ได้ (เช่น bare metal instance ของ Google Cloud) จะถูกหยุดหรือเริ่มใหม่เมื่อมีการซ่อมบำรุง - แหล่งอ้างอิง: - [Live migration process during maintenance events](https://cloud.google.com/compute/docs/instances/live-migration-process) · Google Cloud · การหยุดระหว่าง live migration ปกติสั้นกว่า 1 วินาทีมาก, ระหว่างหยุดนาฬิการะบบกระโดดไปข้างหน้าได้สูงสุด 5 วินาที, ระหว่างย้าย ประสิทธิภาพดิสก์ CPU หน่วยความจำ และเครือข่ายลดลงชั่วครู่, VM ที่ไม่ใช้ live migration จะถูกปิดเมื่อซ่อมบำรุง (bare metal instance ไม่รองรับ) - [Query metadata server for maintenance event notices](https://cloud.google.com/compute/docs/metadata/getting-live-migration-notice) · Google Cloud · ค่า metadata ของ maintenance-event เปลี่ยน 60 วินาทีก่อน live migration (กรณีตั้งค่าเป็น live migration และเคยอ่านค่านี้อย่างน้อยหนึ่งครั้งหลังการซ่อมบำรุงครั้งก่อน) - [Monitor and plan for a host maintenance event](https://cloud.google.com/compute/docs/instances/monitor-plan-host-maintenance-event) · Google Cloud · เมื่อซ่อมบำรุงจะมี system event compute.instances.migrateOnHostMaintenance ใน audit log - [Scheduled events for Amazon EC2 instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-instances-status-check_sched.html) · AWS · ประเภทของ scheduled event (system-reboot คือรีบูตพร้อมย้ายไปโฮสต์ใหม่, system-maintenance คือได้รับผลกระทบชั่วครู่จากการซ่อมบำรุงเครือข่ายหรือไฟฟ้า), แจ้งทางอีเมลและ AWS Health, ดูได้ด้วย describe-instance-status, บางประเภทปรับเวลาได้ - [Maintenance and updates](https://learn.microsoft.com/en-us/azure/virtual-machines/maintenance-and-updates) · Microsoft Azure · การซ่อมบำรุงที่ไม่ต้องรีบูตแทบทุกครั้งหยุดไม่ถึง 10 วินาที, นาน ๆ ครั้ง (ขนาดทั่วไปไม่เกินหนึ่งครั้งใน 18 เดือน) ราว 30 วินาที, live migration ปกติไม่เกิน 5 วินาที, หลังหยุดนาฬิกาซิงก์อัตโนมัติ, การเชื่อมต่อ TCP ที่เปิดไว้นานอาจขาด หรืออีกฝั่งส่งซ้ำข้อมูลที่ส่งไปยัง VM ที่หยุดอยู่แบบ exponential backoff ทำให้กู้คืนช้าลงอีก, health check ของโหลดบาลานเซอร์ตัดสินว่าผิดปกติภายในราว 10 วินาที, ตรวจได้จาก Microsoft.Compute/virtualMachines/liveMigration/action ใน Activity Log และ VmAvailabilityMetric ที่เป็น 0 ระหว่างหยุด, เลือกเวลาที่จะใช้การซ่อมบำรุงได้ด้วย Maintenance Configuration - [Scheduled Events for Linux VMs in Azure](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/scheduled-events) · Microsoft Azure · Freeze (หยุดไม่กี่วินาที CPU และเครือข่ายอาจหยุด) แจ้งล่วงหน้าอย่างน้อย 15 นาที, กรณีฮาร์ดแวร์ของโฮสต์เสีย จะเริ่มกู้คืนทันทีโดยไม่มีช่วงแจ้งล่วงหน้า #### nic-reset · ปัญหาไดรเวอร์/เฟิร์มแวร์ของ NIC · NIC hang / reset ระหว่างที่การ์ดหยุดแล้วเริ่มใหม่เพราะบั๊กของไดรเวอร์หรือฟีเจอร์ทำงานผิดพลาด การรับส่งทั้งหมดจะขาด - ทำไม → ผลคือ → บนหน้าจอ: บั๊กของไดรเวอร์, ฟีเจอร์ offload ทำงานผิดพลาด → NIC หยุดแล้วเริ่มใหม่ (หลายวินาที) → ทุกคนในเซิร์ฟเวอร์นั้นค้างพร้อมกันแล้ววาร์ปหรือหลุด - อาการ: ค้าง, หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ยิ่งเปิดไว้นานยิ่งเป็น - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: ตรวจและตั้ง alert ข้อความ “transmit queue … timed out” และ “Link is Down” ใน log ของเคอร์เนล, อัปเดตไดรเวอร์และเฟิร์มแวร์, ปิดฟีเจอร์ที่มีปัญหา (เช่น offload) - บนกราฟ: ขาดช่วงแล้วมารวดเดียว (จำนวนแพ็กเก็ตรับส่งของเซิร์ฟเวอร์) - จุดที่ต้องดู: ใช้ dmesg หาใน log ของเคอร์เนลว่ามี “NETDEV WATCHDOG … transmit queue N timed out”, การรีเซ็ตของไดรเวอร์ หรือ “Link is Down” และ “Link is Up” หรือไม่ แล้วดูจำนวนแพ็กเก็ตรับส่งของเซิร์ฟเวอร์ในเวลานั้น - สัญญาณว่าใช่: ในเวลาที่หยุด log ของเคอร์เนลมี transmit queue timeout หรือบันทึกลิงก์ down และ up และในไม่กี่วินาทีนั้นจำนวนแพ็กเก็ตรับส่งเป็น 0 - สัญญาณว่าไม่ใช่: ถ้า log ของเคอร์เนลสะอาดและพอร์ตฝั่งสวิตช์ก็ปกติ น่าจะเป็นโปรเซสเซิร์ฟเวอร์เกมหยุด (“GC ของเซิร์ฟเวอร์หยุดทั้งระบบ”, “เดดล็อก”) หรือ “การ failover ของอุปกรณ์เครือข่าย” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [net/sched/sch_generic.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/sched/sch_generic.c?h=v6.12) · Linux kernel · เมื่อ transmit queue หยุด watchdog ของเคอร์เนลจะบันทึก “NETDEV WATCHDOG … transmit queue N timed out” และเรียกฟังก์ชันรีเซ็ตของไดรเวอร์ - [drivers/net/ethernet/intel/ixgbe/ixgbe_main.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c?h=v6.12) · Linux kernel · ไดรเวอร์ ixgbe รีเซ็ตอะแดปเตอร์เมื่อการส่งหยุด และบันทึก “NIC Link is Down” เมื่อลิงก์ขาด - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · ออปชัน ethtool -K สำหรับปิดฟีเจอร์ offload ทีละตัว #### nic-offload · ดีเลย์จากการรอรวมแพ็กเก็ตของ GRO/LRO · GRO/LRO batching เป็นฟีเจอร์ที่รวมหลายแพ็กเก็ตเป็นก้อนเดียวเพื่อลดภาระ CPU บางการตั้งค่าทำให้แพ็กเก็ตเกมขนาดเล็กต้องรอแพ็กเก็ตถัดไปที่จะรวมด้วยสักครู่ - ทำไม → ผลคือ → บนหน้าจอ: NIC และเคอร์เนลรวมแพ็กเก็ตที่มาถึงแล้วประมวลผลพร้อมกัน → ถ้าเปิดการรวมในฮาร์ดแวร์ (LRO) หรือตั้งเวลารอรวมไว้ จะรอแพ็กเก็ตถัดไปสักครู่ → ความหน่วงเพิ่มขึ้นเล็กน้อย (ส่วนใหญ่ไม่เกินหลายสิบ µs) - อาการ: อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: ปรับให้เหมาะกับทราฟฟิกเกม (ปิด LRO, ตรวจการตั้งเวลารอรวม), ผลกระทบส่วนใหญ่น้อย จึงตรวจหลังสาเหตุอื่น - บนกราฟ: สูงตลอดตั้งแต่แรก (เวลาไปกลับภายในดาต้าเซ็นเตอร์เดียวกัน) - จุดที่ต้องดู: ใช้ ethtool -k ดูสถานะ lro และ gro และดูค่า gro_flush_timeout ในการตั้งค่า sysfs ของอุปกรณ์ แล้วเทียบเวลาไปกลับของแพ็กเก็ตขนาดเล็กภายในดาต้าเซ็นเตอร์เดียวกันก่อนและหลังเปลี่ยน - สัญญาณว่าใช่: LRO เปิดอยู่ หรือ gro_flush_timeout มากกว่า 0 และเมื่อปิดหรือตั้งเป็น 0 เวลาไปกลับของแพ็กเก็ตขนาดเล็กลดลง - สัญญาณว่าไม่ใช่: ถ้าเปลี่ยนแล้วต่างกันแค่ไม่กี่ µs ก็ไม่ใช่สาเหตุนี้ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [NAPI](https://docs.kernel.org/networking/napi.html) · Linux kernel · ถ้าตั้ง gro_flush_timeout ไว้สูง จะได้การประมวลผลแบบรวมก้อน แต่เกิดความหน่วงเมื่อโหลดต่ำ - [Linux Base Driver for the Intel(R) Ethernet 10 Gigabit PCI Express Adapters (ixgbe)](https://docs.kernel.org/networking/device_drivers/ethernet/intel/ixgbe.html) · Linux kernel · GRO คือฟีเจอร์ที่รวมทราฟฟิกขาเข้าเป็นก้อนใหญ่เพื่อประหยัด CPU และพัฒนาต่อมาจาก LRO - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · การตั้งค่า gro และ lro on|off ของ ethtool -K ### L7 OS ของเซิร์ฟเวอร์ (เคอร์เนล) (14 สาเหตุ) #### so-backlog · คิวรอเชื่อมต่อ (backlog) ล้น · Listen backlog / SYN queue overflow ทันทีหลังปิดปรับปรุง ถ้าผู้เล่นหลายหมื่นคนเชื่อมต่อพร้อมกัน คิวรอเชื่อมต่อ (backlog) ของเคอร์เนลจะล้น และความพยายามเชื่อมต่อจะถูกทิ้ง - ทำไม → ผลคือ → บนหน้าจอ: พอปิดปรับปรุงเสร็จ การเชื่อมต่อก็ทะลักเข้ามาเร็วกว่าที่เซิร์ฟเวอร์เกมรับด้วย accept ทัน → คิวรอเชื่อมต่อของเคอร์เนล (backlog คือค่าที่น้อยกว่าระหว่างค่าที่โค้ดเซิร์ฟเวอร์ส่งให้ listen กับเพดานของเคอร์เนล) เต็ม → ความพยายามเชื่อมต่อถูกทิ้งและลองใหม่วนไปเรื่อย ๆ จนเข้าเกมไม่ได้/โหลดไม่จบ - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: เพิ่มค่า listen ที่ส่งในโค้ด, อย่าให้เธรดที่รับการเชื่อมต่อหยุดเพราะงานอื่น, ทำระบบคิวล็อกอิน ไคลเอนต์: เพิ่มช่วงห่างการลองใหม่ (สุ่มให้กระจาย) - งานฝั่งทีมอินฟรา: เพิ่ม somaxconn ของเคอร์เนล (ต้องเพิ่มคู่กับค่า listen ในโค้ดเซิร์ฟเวอร์จึงจะได้ผล), เปิด SYN cookie ไว้, มอนิเตอร์จำนวนครั้งที่ล้น (TcpExtListenOverflows ของ nstat) - ตัวเลขที่ควรรู้: เพดานของเคอร์เนล Linux (somaxconn) มีค่าเริ่มต้น 4,096 ตั้งแต่ 5.4 (ก่อนหน้านั้น 128) แต่ถ้าโค้ดเซิร์ฟเวอร์ส่งค่าที่น้อยกว่าให้ listen ค่านั้นจะกลายเป็นขีดจำกัด เมื่อคิวเต็ม Linux จะทิ้งคำขอเชื่อมต่อเงียบ ๆ โดยไม่ส่งข้อผิดพลาดกลับ OS ฝั่งไคลเอนต์จะส่งซ้ำอีกไม่กี่ครั้งโดยเริ่มหลัง 1 วินาที ผู้เล่นจึงเห็นแค่หน้าโหลดนาน ๆ โดยไม่ขึ้น “เชื่อมต่อไม่สำเร็จ” ส่วนเซิร์ฟเวอร์ Windows จะส่งการตอบกลับว่าปฏิเสธ ไคลเอนต์จึงเห็น “เชื่อมต่อไม่สำเร็จ” ทันที - บนกราฟ: พุ่งทันทีหลังเปิดเซิร์ฟ/ปิดปรับปรุง (จำนวนครั้งที่คิวรอเชื่อมต่อล้น (ListenOverflows), จำนวนความพยายามเชื่อมต่อ) - จุดที่ต้องดู: ค่าที่เพิ่มขึ้นของ TcpExtListenOverflows และ TcpExtListenDrops จาก nstat -az และใช้ ss -ltn เทียบ Recv-Q (จำนวนการเชื่อมต่อที่รอ accept) กับ Send-Q (ขีดจำกัด backlog) ของซ็อกเก็ตที่ listen อยู่ - สัญญาณว่าใช่: ช่วงที่การเชื่อมต่อแห่เข้ามา ListenOverflows เพิ่มขึ้น และ Recv-Q ของซ็อกเก็ตที่ listen อยู่ชนค่า Send-Q - สัญญาณว่าไม่ใช่: ListenOverflows ไม่ขยับ: ไม่ใช่สาเหตุนี้ ถ้าเชื่อมต่อได้แต่โหลดไม่จบ ให้ดู “คนแห่ล็อกอินและคิวรี N+1” ถ้าติดที่จำนวนคนค่าหนึ่งพอดี ให้ดู “ขีดจำกัด file descriptor” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [listen(2) — Linux manual page](https://man7.org/linux/man-pages/man2/listen.2.html) · Linux man-pages · ถ้า backlog ของ listen เกิน somaxconn จะถูกตัดเงียบ ๆ, somaxconn ค่าเริ่มต้น 4,096 (ตั้งแต่ 5.4 ก่อนหน้านั้น 128), เมื่อคิวเต็มอาจเพิกเฉยต่อคำขอแล้วปล่อยให้ไคลเอนต์ลองใหม่เอง - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_syn_retries: ส่งคำขอเชื่อมต่อ (SYN) ซ้ำหลายครั้ง โดยรอ 1 วินาทีก่อนส่งซ้ำครั้งแรก, tcp_abort_on_overflow ปิดเป็นค่าเริ่มต้น (คิวล้นก็ไม่ส่งการปฏิเสธกลับ), tcp_syncookies เปิดเป็นค่าเริ่มต้น - [listen function (winsock2.h)](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen) · Microsoft · บน Windows เมื่อคิวเต็ม ไคลเอนต์จะได้รับข้อผิดพลาด WSAECONNREFUSED - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtListenOverflows: จำนวนครั้งที่ทิ้งคำขอเชื่อมต่อ (SYN) เพราะคิว accept เต็ม ซึ่ง TcpExtListenDrops จะเพิ่มขึ้นไปพร้อมกัน - [net/ipv4/tcp_diag.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_diag.c?h=v6.12) · Linux kernel · ค่า Recv-Q และ Send-Q ของ ss: ซ็อกเก็ตที่ listen อยู่คือจำนวนการเชื่อมต่อที่รอ accept และขีดจำกัด backlog, ซ็อกเก็ตที่เชื่อมต่อแล้วคือไบต์ที่แอปยังไม่ได้อ่าน และไบต์ที่ส่งไปแล้วแต่ยังไม่ได้รับ ACK #### so-fd · ขีดจำกัด file descriptor · File descriptor limit (ulimit) ทุกการเชื่อมต่อต้องใช้ file descriptor (fd คือหมายเลขที่ OS กำหนดให้ไฟล์หรือซ็อกเก็ตที่เปิดอยู่) แต่จำนวน fd ที่หนึ่งโปรเซสเปิดได้มีจำกัด - ทำไม → ผลคือ → บนหน้าจอ: จำนวนผู้เล่นออนไลน์พร้อมกันแตะขีดจำกัด file descriptor ของโปรเซส → เซิร์ฟเวอร์รับการเชื่อมต่อใหม่ไม่ได้ (Too many open files) การเปิดไฟล์ log และการเชื่อมต่อ DB ก็ล้มเหลวไปด้วย → พอถึงจำนวนคนค่าหนึ่งพอดีก็ไม่มีใครเข้าได้อีก: เข้าเกมไม่ได้/โหลดไม่จบ - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ปิดซ็อกเก็ตให้แน่นอนเมื่อจบการเชื่อมต่อ (กัน fd รั่ว), ถ้า accept ล้มเหลวด้วย EMFILE (fd ไม่พอ) ให้หยุดรับการเชื่อมต่อชั่วครู่ หรือใช้ fd สำรองที่กันไว้รับแล้วปิดทันที (จะได้ไม่เสีย CPU ไปกับการจัดการแจ้งเตือนการเชื่อมต่อเดิมซ้ำ ๆ) - งานฝั่งทีมอินฟรา: ตรวจ ulimit และการตั้งค่าเซอร์วิส (LimitNOFILE ของ systemd), ตั้ง alert เมื่อใกล้ขีดจำกัด - ตัวเลขที่ควรรู้: บน Linux ถ้าไม่ได้ตั้งค่าเซอร์วิสแยกไว้ ขีดจำกัดยังเป็น 1,024 อยู่บ่อย ๆ เซิร์ฟเวอร์เกมมักเพิ่มเป็นหลายหมื่นถึงหลายแสน ส่วน Windows ไม่มีขีดจำกัดเริ่มต้นที่ต่ำแบบนี้ - บนกราฟ: ชนเพดานแล้วแบนราบ (จำนวน fd ที่โปรเซสเปิดอยู่, จำนวนผู้เล่นออนไลน์พร้อมกัน) - จุดที่ต้องดู: fd-nr (จำนวน file descriptor ที่เปิดอยู่) ของโปรเซสเซิร์ฟเวอร์เกมจาก pidstat -v, ขีดจำกัดจำนวนไฟล์ที่เปิดได้จาก /proc/PID/limits และ accept ที่ล้มเหลว (EMFILE, Too many open files) ใน log ของเซิร์ฟเวอร์ - สัญญาณว่าใช่: จำนวน fd แบนราบที่ค่าขีดจำกัด และตั้งแต่ตอนนั้น accept ล้มเหลวด้วย EMFILE - สัญญาณว่าไม่ใช่: จำนวน fd ยังห่างจากขีดจำกัดมาก: ไม่ใช่สาเหตุนี้ ถ้าคำขอเชื่อมต่อถูกทิ้งในเคอร์เนล ให้ดู “คิวรอเชื่อมต่อ (backlog) ล้น” ถ้าเกี่ยวกับ connection tracking ให้ดู “ตาราง conntrack ของเซิร์ฟเวอร์เต็ม” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: การเชื่อมต่อที่รับไม่ได้จะยังรออยู่ในคิวรอเชื่อมต่อ (backlog) ของเคอร์เนล ขึ้นอยู่กับโค้ดเซิร์ฟเวอร์ บางครั้งจะได้รับแจ้ง “มีการเชื่อมต่อใหม่” ซ้ำไปเรื่อย ๆ จนเปลือง CPU - แหล่งอ้างอิง: - [systemd-system.conf(5) — Linux manual page](https://man7.org/linux/man-pages/man5/systemd-system.conf.5.html) · systemd · ค่าเริ่มต้นของ DefaultLimitNOFILE สำหรับเซอร์วิสคือ 1024:524288 (soft limit 1,024) - [accept(2) — Linux manual page](https://man7.org/linux/man-pages/man2/accept.2.html) · Linux man-pages · เมื่อโปรเซสแตะขีดจำกัด fd แล้ว accept จะล้มเหลวด้วย EMFILE - [Maximum Number of Sockets Supported](https://learn.microsoft.com/en-us/windows/win32/winsock/maximum-number-of-sockets-supported-2) · Microsoft · Winsock ของ Windows จำกัดจำนวนซ็อกเก็ตด้วยหน่วยความจำที่ใช้ได้เท่านั้น - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · fd-nr ของ -v: จำนวน file descriptor ที่โปรเซสเปิดอยู่ - [proc_pid_limits(5) — Linux manual page](https://man7.org/linux/man-pages/man5/proc_pid_limits.5.html) · Linux man-pages · /proc/PID/limits แสดงค่า soft และ hard ของขีดจำกัดทรัพยากรแต่ละรายการของโปรเซส #### so-sockbuf · บัฟเฟอร์ซ็อกเก็ตของเคอร์เนลไม่พอ · Small socket buffers ถ้าบัฟเฟอร์ส่งและรับเล็ก เมื่อทราฟฟิกมาเป็น burst แพ็กเก็ตที่รับทาง UDP จะถูกทิ้ง ส่วนการส่งทาง TCP จะติดเพราะบัฟเฟอร์ไม่มีที่ว่าง - ทำไม → ผลคือ → บนหน้าจอ: SO_SNDBUF และ SO_RCVBUF ใช้ค่าเริ่มต้นหรือเล็กเกินไป → ระหว่าง burst หรือช่วงที่เธรดรับหยุดชั่วครู่ receive buffer ของ UDP ล้นจนแพ็กเก็ตถูกทิ้ง ส่วน TCP ต้องรอเพราะ send buffer ไม่มีที่ว่าง → วาร์ป (UDP แพ็กเก็ตหาย) หรือกรอเร็ว (TCP รอ) - อาการ: วาร์ป, กรอเร็ว / ปัจจัย: แพ็กเก็ตหาย, การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ตั้งขนาดบัฟเฟอร์ (SO_SNDBUF, SO_RCVBUF) ในโค้ดให้เหมาะกับทราฟฟิก, ระวังว่าถ้ากำหนดขนาดบัฟเฟอร์ของ TCP เอง Linux จะปิดการปรับขนาดอัตโนมัติ, อย่าตั้งใหญ่เกินไปเพราะข้อมูลเก่าจะกองในบัฟเฟอร์จนดีเลย์เพิ่ม, อย่าให้เธรดรับหยุด - งานฝั่งทีมอินฟรา: ปรับเพดานของเคอร์เนล (rmem_max, wmem_max: ขนาดบัฟเฟอร์ที่ตั้งในโค้ดก็เกินค่านี้ไม่ได้) และค่าเริ่มต้น (rmem_default), มอนิเตอร์ตัวนับบัฟเฟอร์ล้น (RcvbufErrors) - ตัวเลขที่ควรรู้: ค่าเริ่มต้นของ receive buffer ของ UDP บน Linux อยู่ที่ประมาณ 208 KB แพ็กเก็ตเล็ก ๆ หนึ่งแพ็กเก็ตก็กินหน่วยความจำเคอร์เนลมากกว่าขนาดจริงมาก แค่หลายสิบถึงหลายร้อยแพ็กเก็ตก็เต็ม ถ้าเซิร์ฟเวอร์รับ 100,000 แพ็กเก็ตต่อวินาที เธรดรับหยุดแค่ไม่กี่ ms บัฟเฟอร์ก็ล้น - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (receive buffer ของ UDP ล้น (UdpRcvbufErrors)) - จุดที่ต้องดู: ค่าที่เพิ่มขึ้นของ UdpRcvbufErrors จาก nstat -az และ skmem ใน ss -uamn (rb คือขนาด receive buffer, d คือจำนวนแพ็กเก็ตที่ใส่ซ็อกเก็ตไม่ได้จนถูกทิ้ง) ส่วน TCP ดู skmem ของ ss -tm ว่าหน่วยความจำที่รอส่ง (w) ชนขนาด send buffer (tb) หรือไม่ - สัญญาณว่าใช่: ช่วง burst หรือตอนที่เธรดรับหยุด UdpRcvbufErrors (หรือ d ของซ็อกเก็ต) เพิ่มขึ้น และ rb อยู่ใกล้ค่าเริ่มต้น (ประมาณ 208 KB) ส่วน TCP: w ติดอยู่ที่ tb และ send ถูกบล็อก - สัญญาณว่าไม่ใช่: ตัวนับไม่ขยับแต่มีแพ็กเก็ตหาย: น่าจะเป็นที่ขั้น NIC (“ring buffer ไม่พอ”) หรือช่วงเครือข่าย - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [socket(7) — Linux manual page](https://man7.org/linux/man-pages/man7/socket.7.html) · Linux man-pages · ค่าเริ่มต้นของ SO_RCVBUF และ SO_SNDBUF คือ rmem_default และ wmem_default, เพดานคือ rmem_max และ wmem_max, เคอร์เนลจะเพิ่มค่าที่ตั้งเป็นสองเท่า - [include/net/sock.h (Linux v6.18)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/sock.h?h=v6.18) · Linux kernel · กำหนดบัฟเฟอร์ซ็อกเก็ตเริ่มต้นเท่ากับแพ็กเก็ตขนาด 256 ไบต์จำนวน 256 แพ็กเก็ตรวม overhead ของ sk_buff (SKB_TRUESIZE(256)×256), เฟรมเล็ก ๆ ก็คิดเป็น sk_buff+MTU (ประมาณ 208 KB คือค่าที่คำนวณบน x86-64) - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_rmem, tcp_wmem: ถ้ากำหนด SO_RCVBUF หรือ SO_SNDBUF เอง การปรับขนาดอัตโนมัติของซ็อกเก็ตนั้นจะปิด - [net/ipv4/udp.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/udp.c?h=v6.12) · Linux kernel · ถ้าคิวรับของ UDP เกินขนาดบัฟเฟอร์ซ็อกเก็ต จะทิ้งทันทีและเพิ่มค่า RcvbufErrors - [net/ipv4/proc.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/proc.c?h=v6.12) · Linux kernel · ชื่อตัวนับที่ nstat แสดง: RcvbufErrors และ SndbufErrors ในกลุ่ม Udp - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · skmem ของ -m: rb ขนาด receive buffer, tb ขนาด send buffer, w หน่วยความจำที่รอส่ง, d จำนวนแพ็กเก็ตที่ถูกทิ้งก่อนเข้าซ็อกเก็ต #### so-context · เธรดมากเกินไปและ context switch · Thread oversubscription, context switching ถ้ารันเธรดมากกว่าจำนวนคอร์มาก ๆ OS จะเสีย CPU ไปกับการสลับให้เธรดผลัดกันทำงานอย่างเดียว - ทำไม → ผลคือ → บนหน้าจอ: มีเธรดหลายร้อยถึงหลายพันตัว เช่น สร้างเธรดหนึ่งตัวต่อหนึ่งการเชื่อมต่อ → ต้นทุน context switch (การสลับเธรดที่กำลังรัน) และ cache miss เพิ่มขึ้น → CPU ยุ่งแต่ throughput ต่ำ ทิกไม่สม่ำเสมอ จึงกระตุกและเป็นสโลว์โมชั่น - อาการ: กระตุก, สโลว์โมชั่น / ปัจจัย: การหยุดชะงัก, จิตเตอร์ - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ใช้จำนวนเธรดให้เหมาะกับจำนวนคอร์, ใช้ async I/O (epoll, IOCP) - งานฝั่งทีมอินฟรา: มอนิเตอร์จำนวน context switch และจำนวนเธรดที่รอรัน (cs และ r ของ vmstat) - ตัวเลขที่ควรรู้: context switch หนึ่งครั้งใช้เวลาไม่กี่ µs และถ้ารวมต้นทุน cache miss ที่ตามมาด้วยจะมากกว่านั้น - บนกราฟ: สูงตามจำนวนคนและโหลด (จำนวน context switch ต่อวินาที, จำนวนเธรดที่รอรัน) - จุดที่ต้องดู: cs (context switch ต่อวินาที) และ r (จำนวนที่กำลังรันหรือรอ CPU) จาก vmstat 1 เทียบกับจำนวนคอร์ และ context switch แบบ voluntary (cswch/s) กับ involuntary (nvcswch/s) แยกตามเธรดของเซิร์ฟเวอร์เกมจาก pidstat -w -t - สัญญาณว่าใช่: เมื่อผู้เล่นออนไลน์เพิ่มขึ้น r พุ่งสูงกว่าจำนวนคอร์มาก cs ก็พุ่งตาม และมีเธรดหลายร้อยตัวที่มี involuntary context switch สูง - สัญญาณว่าไม่ใช่: r อยู่ไม่เกินจำนวนคอร์: ไม่ใช่สาเหตุนี้ ถ้ามีแต่ voluntary switch สูง แปลว่าเธรดกำลังรอล็อกหรือ I/O (“การแย่งล็อก”, “โครงสร้างแบบ blocking I/O”) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Quantifying The Cost of Context Switch (ExpCS 2007)](https://www.usenix.org/legacy/events/expcs07/papers/2-li.pdf) · ACM · ต้นทุนทางตรงของ context switch ประมาณ 3.8 µs, ต้นทุนทางอ้อมรวมผลต่อแคชตั้งแต่ไม่กี่ µs ถึงมากกว่า 1,000 µs (ตามสภาพแวดล้อมที่วัด) - [vmstat(8) — Linux manual page](https://man7.org/linux/man-pages/man8/vmstat.8.html) · procps-ng · ฟิลด์ cs (จำนวน context switch ต่อวินาที) และ r (จำนวนโปรเซสที่กำลังรันหรือรอรัน) - [I/O Completion Ports](https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports) · Microsoft · ใช้ thread pool ที่สร้างไว้ล่วงหน้าร่วมกับ IOCP จัดการ async I/O จำนวนมาก และกำหนดจำนวนเธรดที่รันพร้อมกันให้เท่ากับจำนวนที่ CPU รันพร้อมกันได้ - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · cswch/s ของ -w คือ voluntary context switch ที่เธรดหยุดเองเพื่อรอทรัพยากร, nvcswch/s คือ involuntary context switch ที่ถูกสลับออกเพราะใช้ time slice หมด, -t แสดงแยกตามเธรด #### so-steal · CPU steal (VM) · CPU steal time ระหว่างที่เครื่องจริง (ไฮเปอร์ไวเซอร์) ยก CPU time ของ VM ไปให้ VM ตัวอื่นชั่วครู่ (CPU steal) เซิร์ฟเวอร์เกมจะหยุดชะงัก - ทำไม → ผลคือ → บนหน้าจอ: VM ตัวอื่นบนโฮสต์เดียวกันใช้ CPU มาก → VM ของเราไม่ได้รับ CPU ครั้งละไม่กี่ ms ถึงหลายสิบ ms → เวลาต่อทิกพุ่งโดยหาสาเหตุไม่เจอ: กระตุก, ค้าง - อาการ: กระตุก, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: ภายนอก (ภายนอก) - งานฝั่งทีมอินฟรา: มอนิเตอร์ค่า steal (st ของ top และ vmstat), ใช้คอร์หรือโฮสต์เฉพาะ (dedicated), เลี่ยงอินสแตนซ์แบบ burstable ที่ช้าลงเมื่อ CPU credit หมด, อินสแตนซ์ที่ steal สูงต่อเนื่องให้ stop แล้ว start ใหม่เพื่อย้ายไปโฮสต์อื่น - งานฝั่งภายนอก: แจ้งผู้ให้บริการคลาวด์ว่าโฮสต์มีค่า steal สูงต่อเนื่อง - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (%steal, เวลาต่อทิกของเซิร์ฟเวอร์) - จุดที่ต้องดู: %steal จาก mpstat -P ALL 1 วางบนแกนเวลาเดียวกับเวลาต่อทิกของเซิร์ฟเวอร์ - สัญญาณว่าใช่: ตอนที่ทิกพุ่ง %steal พุ่งด้วย และลดลงหลัง stop แล้ว start ใหม่เพื่อย้ายไปโฮสต์อื่น - สัญญาณว่าไม่ใช่: %steal ใกล้ 0 แต่ทิกยังพุ่ง: น่าจะเป็นสาเหตุภายในเซิร์ฟเวอร์เกม (“GC ของเซิร์ฟเวอร์หยุดทั้งระบบ”, “การแย่งล็อก”) ถ้าเป็นคอนเทนเนอร์ ให้ดู “CPU throttling ของคอนเทนเนอร์ (CFS quota)” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [proc_stat(5) — Linux manual page](https://man7.org/linux/man-pages/man5/proc_stat.5.html) · Linux man-pages · steal: เวลาที่ถูกแย่งไปเพราะระบบปฏิบัติการอื่นกำลังรันอยู่ในสภาพแวดล้อมเสมือน - [Standard mode for burstable performance instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances-standard-mode.html) · AWS · อินสแตนซ์แบบ burstable ใช้เครดิตเพื่อทำงานเกินประสิทธิภาพพื้นฐาน และเมื่อเครดิตหมด อัตราการใช้ CPU จะลดลงเหลือระดับพื้นฐาน - [How EC2 instance stop and start works](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/how-ec2-instance-stop-start-works.html) · AWS · ถ้า stop แล้ว start อินสแตนซ์ใหม่ ส่วนใหญ่จะถูกย้ายไปโฮสต์ใหม่ - [mpstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/mpstat.1.html) · sysstat · %steal: สัดส่วนเวลาที่ virtual CPU นี้จำต้องรอ ขณะที่ไฮเปอร์ไวเซอร์รัน virtual CPU ตัวอื่น #### so-cpu-quota · CPU throttling ของคอนเทนเนอร์ (CFS quota) · Container CPU throttling (CFS quota) ถ้าตั้งขีดจำกัด CPU ให้คอนเทนเนอร์ เมื่อใช้โควตาหมดภายในรอบที่กำหนด (ปกติ 100 ms) คอนเทนเนอร์จะถูกบังคับหยุดตลอดเวลาที่เหลือของรอบนั้น (throttling) - ทำไม → ผลคือ → บนหน้าจอ: ตั้งขีดจำกัด CPU (limit) ให้คอนเทนเนอร์เซิร์ฟเวอร์เกมไว้ เช่น ใน Kubernetes → ตอนที่งานคำนวณทิกมากระจุกกัน โควตาหมดจนต้องหยุดหลายสิบ ms รอรอบถัดไป → CPU เฉลี่ยต่ำ แต่ทิกพุ่งเป็นรอบ: กระตุก, สโลว์โมชั่น - อาการ: กระตุก, สโลว์โมชั่น / ปัจจัย: การหยุดชะงัก, จิตเตอร์ - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ตั้งจำนวน worker thread ให้เท่ากับขีดจำกัด CPU (อย่าให้ runtime สร้างเธรดเท่าจำนวนคอร์ทั้งหมดของโฮสต์) - งานฝั่งทีมอินฟรา: ตั้งขีดจำกัด CPU ให้เผื่อมาก ๆ หรือเอาออกแล้วจัดคอร์เฉพาะให้, มอนิเตอร์จำนวนครั้งที่ถูก throttle (nr_throttled) - ตัวเลขที่ควรรู้: บนเซิร์ฟเวอร์ที่จำกัดไว้ 2 คอร์ ถ้าเธรด 8 ตัวทำงานพร้อมกัน จะใช้โควตาของรอบ 100 ms หมดภายใน 25 ms แล้วหยุดไป 75 ms - บนกราฟ: สูงตามจำนวนคนและโหลด (จำนวนครั้งที่ถูก throttle (nr_throttled), เวลาต่อทิกของเซิร์ฟเวอร์) - จุดที่ต้องดู: ค่าที่เพิ่มขึ้นของ nr_throttled และ throttled_usec (cgroup v1 คือ nr_throttled และ throttled_time) ใน cpu.stat ของ cgroup คอนเทนเนอร์ ดูคู่กับเวลาต่อทิกของเซิร์ฟเวอร์ - สัญญาณว่าใช่: อัตราการใช้ CPU เฉลี่ยต่ำกว่าขีดจำกัด แต่ nr_throttled และ throttled_usec เพิ่มขึ้นเรื่อย ๆ และตรงกับช่วงที่ทิกพุ่ง - สัญญาณว่าไม่ใช่: nr_throttled ไม่เพิ่ม: ไม่ใช่สาเหตุนี้ ถ้าตัว VM เองถูกแย่ง CPU ให้ดู “CPU steal (VM)” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [CFS Bandwidth Control](https://docs.kernel.org/scheduler/sched-bwc.html) · Linux kernel · ถ้าใช้โควตาของรอบหมด เธรดจะหยุดจนถึงรอบถัดไป (throttling), รอบเริ่มต้น 100 ms, สถิติ nr_throttled - [Control Group v2](https://docs.kernel.org/admin-guide/cgroup-v2.html) · Linux kernel · cpu.max ใช้รูปแบบ “$MAX $PERIOD” (โควตา, รอบ) และค่าเริ่มต้นคือ “max 100000” (รอบ 100 ms) - [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) · Kubernetes · CPU limit ของคอนเทนเนอร์เป็นขีดจำกัดแบบ hard ที่เคอร์เนลบังคับด้วย CPU throttling #### so-cstate · ความหน่วงพุ่งจากการจัดการพลังงานของเซิร์ฟเวอร์ (C-state/การปรับความถี่) · CPU power management latency (C-states, frequency scaling) คอร์ CPU ที่ว่างจะเข้าโหมดประหยัดพลังงานระดับลึก (C-state) และลดความถี่ลงเพื่อประหยัดไฟ เมื่อแพ็กเก็ตหรือตัวจับเวลามาถึง ต้องใช้เวลาตื่นและเพิ่มความถี่ การประมวลผลแพ็กเก็ตเล็ก ๆ จึงมีดีเลย์เพิ่มขึ้น - ทำไม → ผลคือ → บนหน้าจอ: นโยบายปรับความถี่ของ OS (governor) หรือการตั้งค่าพลังงานใน BIOS ยอมให้ใช้ C-state ระดับลึกและความถี่ต่ำ → คอร์ที่ว่างอยู่ตื่นช้าสูงสุดหลายร้อย µs ทุกครั้งที่ออกจากโหมดประหยัดพลังงานระดับลึก และถ้าความถี่ถูกตรึงไว้ต่ำ การคำนวณทิกเองก็ช้าลง → ปกติรู้สึกได้ยาก แต่ถ้าเรียกกันระหว่างเซิร์ฟเวอร์บ่อย ดีเลย์จะสะสมเป็นอินพุตดีเลย์ที่กลับหนักขึ้นตอนเซิร์ฟเวอร์ว่าง ถ้าความถี่ถูกตรึงไว้ต่ำ ตอนคนแห่มารวมกันทิกจะตามไม่ทันจนเป็นสโลว์โมชั่น - อาการ: อินพุตดีเลย์, สโลว์โมชั่น / ปัจจัย: ความหน่วง, จิตเตอร์, การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: ตั้งค่าพลังงานใน BIOS ไปทางประสิทธิภาพ, ตั้ง governor ของ OS เป็น performance (scaling_governor ของ cpufreq), จำกัด C-state ระดับลึกบนเซิร์ฟเวอร์ที่ไวต่อความหน่วง (โปรไฟล์ tuned latency-performance, /dev/cpu_dma_latency ของ PM QoS, พารามิเตอร์เคอร์เนล intel_idle.max_cstate), หลังเปลี่ยนให้เทียบเวลาไปกลับภายในดาต้าเซ็นเตอร์เดียวกัน จิตเตอร์ของเวลาต่อทิก และการใช้พลังงานไปพร้อมกัน - ตัวเลขที่ควรรู้: ตารางของไดรเวอร์ intel_idle ใน Linux 6.12 ระบุว่า CPU เซิร์ฟเวอร์ของ Intel ใช้เวลาตื่นจาก C1 (ระดับตื้น) 1–2 µs และจาก C6 (ระดับลึก) 133 µs (Skylake-SP) ถึง 290 µs (Sapphire Rapids) ครั้งเดียวถือว่าน้อย แต่ถ้าคำขอหนึ่งต้องผ่านเซิร์ฟเวอร์หลายเครื่อง ดีเลย์ก็สะสมตามจำนวนนั้น เคอร์เนลจะเลือกสถานะที่ลึกขึ้นเมื่อคาดว่าจะว่างนานขึ้น อาการนี้จึงพบบ่อยกว่าบนเซิร์ฟเวอร์ที่ว่างซึ่งแพ็กเก็ตมาห่าง ๆ governor แบบ powersave ของ cpufreq ทั่วไปจะตรึงความถี่ไว้ที่ค่าต่ำสุดในช่วงที่อนุญาต (ส่วนอัลกอริทึมชื่อเดียวกันของ intel_pstate จะปรับตามโหลด) - บนกราฟ: สูงตลอดตั้งแต่แรก (เวลาไปกลับภายในดาต้าเซ็นเตอร์เดียวกัน, ความถี่ของคอร์) - จุดที่ต้องดู: สัดส่วนเวลาที่แต่ละคอร์อยู่ในแต่ละ C-state และความถี่จริงจาก cpupower monitor, name, latency (µs ที่ใช้ตื่น) และ usage ของแต่ละ state ใต้ /sys/devices/system/cpu/cpu0/cpuidle/, scaling_governor ของ cpufreq และโปรไฟล์ปัจจุบันจาก tuned-adm active - สัญญาณว่าใช่: ตอนว่าง คอร์อยู่ใน C-state ลึกสุดเป็นเวลานาน หรือความถี่ถูกตรึงไว้ใกล้ค่าต่ำสุด และเมื่อเปลี่ยนเป็น performance governor กับ C-state ระดับตื้น เวลาไปกลับและจิตเตอร์ของคำขอเล็ก ๆ ลดลง - สัญญาณว่าไม่ใช่: เปลี่ยนแล้วต่างกันไม่เกินหลักสิบ µs: ข้ามสาเหตุนี้ได้ ถ้าพุ่งในระดับ ms ให้ดู “CPU steal (VM)” หรือชั้นอื่น - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: เซิร์ฟเวอร์ bare-metal ใน IDC ต้องดูการตั้งค่าพลังงานใน BIOS (เฟิร์มแวร์) ควบคู่กับการตั้งค่าของ OS ส่วนบนคลาวด์ มีเพียงอินสแตนซ์บางประเภทที่ OS เปลี่ยน C-state และความถี่ได้ และ AWS ตั้งค่าเริ่มต้นไว้ทางประสิทธิภาพสูงสุด ส่วนใหญ่จึงปล่อยไว้ตามเดิมได้ โปรไฟล์ tuned latency-performance ของตระกูล Red Hat ตั้ง governor เป็น performance และใช้ PM QoS ให้ใช้เฉพาะ C-state ระดับตื้น การปิดโหมดประหยัดพลังงานทำให้ใช้ไฟมากขึ้น จึงควรใช้เฉพาะกับเซิร์ฟเวอร์ที่ไวต่อความหน่วง - แหล่งอ้างอิง: - [CPU Idle Time Management](https://docs.kernel.org/admin-guide/pm/cpuidle.html) · Linux kernel · โหมดประหยัดพลังงานแต่ละระดับมีเวลาตื่น (exit latency) และเวลาอยู่ขั้นต่ำ (target residency) และจะเลือกสถานะลึกตามเวลาว่างที่คาดไว้, latency, usage และ time ของแต่ละ state ใน sysfs, จำกัดสถานะลึกด้วย PM QoS (/dev/cpu_dma_latency) และ intel_idle.max_cstate - [drivers/idle/intel_idle.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/idle/intel_idle.c?h=v6.12) · Linux kernel · เวลาตื่นของแต่ละ C-state บน CPU เซิร์ฟเวอร์ของ Intel: Skylake-SP (C1 2 µs, C1E 10 µs, C6 133 µs), Ice Lake (C6 170 µs), Sapphire Rapids (C1 1 µs, C6 290 µs) - [CPU Performance Scaling](https://docs.kernel.org/admin-guide/pm/cpufreq.html) · Linux kernel · ตรวจและเปลี่ยน governor ด้วย scaling_governor, performance จะขอความถี่สูงสุดในช่วงที่อนุญาต, powersave ขอความถี่ต่ำสุด - [intel_pstate CPU Performance Scaling Driver](https://docs.kernel.org/admin-guide/pm/intel_pstate.html) · Linux kernel · อัลกอริทึม powersave ของ intel_pstate ต่างจาก powersave governor ทั่วไป คือปรับตามโหลด (คล้าย schedutil และ ondemand) - [Chapter 2. Getting started with TuneD](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/monitoring_and_managing_system_status_and_performance/getting-started-with-tuned_monitoring-and-managing-system-status-and-performance) · Red Hat · โปรไฟล์ latency-performance ปิดฟีเจอร์ประหยัดพลังงาน ตั้ง governor เป็น performance และใช้ PM QoS ให้ใช้เฉพาะ C-state ระดับตื้น, ตรวจโปรไฟล์ปัจจุบันด้วย tuned-adm active - [tools/power/cpupower/man/cpupower-monitor.1 (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/power/cpupower/man/cpupower-monitor.1?h=v6.12) · Linux kernel · cpupower monitor: สถิติความถี่และโหมดประหยัดพลังงานแยกตามคอร์ - [Processor state control for Amazon EC2 Linux instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/processor_state_control.html) · AWS · มีเพียงอินสแตนซ์บางประเภทที่ OS ควบคุม C-state และ P-state ได้ และปรับเพื่อลดความหน่วงได้, ค่าเริ่มต้นเป็นประสิทธิภาพสูงสุดซึ่งเหมาะกับงานส่วนใหญ่, Graviton ใช้ความถี่คงที่ OS จึงไม่ได้ควบคุม #### so-oom · OOM killer · Out-of-memory killer เมื่อหน่วยความจำหมด Linux จะเลือกโปรเซสที่ใช้หน่วยความจำมากที่สุดแล้วบังคับปิด ซึ่งส่วนใหญ่คือเซิร์ฟเวอร์เกม - ทำไม → ผลคือ → บนหน้าจอ: หน่วยความจำหมดเพราะรั่วหรือใช้พุ่ง หรือคอนเทนเนอร์แตะขีดจำกัดหน่วยความจำ → เคอร์เนลบังคับปิดโปรเซสเซิร์ฟเวอร์เกม → ทุกคนในเซิร์ฟเวอร์นั้นหลุดพร้อมกัน ความคืบหน้าล่าสุดอาจโดนโรลแบ็ค - อาการ: หลุด, กดไม่ติด/โรลแบ็ค / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: แก้หน่วยความจำรั่ว, กำหนดเพดานการใช้หน่วยความจำและมีขั้นตอนบันทึกข้อมูลแล้วปิดอย่างปกติเมื่อใกล้เพดาน - งานฝั่งทีมอินฟรา: ตั้ง alert หน่วยความจำ, ตั้งขีดจำกัดหน่วยความจำของคอนเทนเนอร์ให้เหมาะกับการใช้งานจริง, ปรับลำดับโปรเซสที่จะถูกปิดก่อน (oom_score_adj) - ตัวเลขที่ควรรู้: log ของเคอร์เนล (dmesg) จะมี “Out of memory: Killed process” และใน Kubernetes จะเห็นเป็น OOMKilled ส่วน Windows ไม่มี OOM killer และเซิร์ฟเวอร์มักล่มด้วยข้อผิดพลาดเมื่อจัดสรรหน่วยความจำไม่สำเร็จ - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนการเชื่อมต่อ, ปริมาณการใช้หน่วยความจำ) - จุดที่ต้องดู: บันทึก “Out of memory: Killed process” ใน dmesg, OOMKilled ในสถานะพ็อด (Kubernetes) หรือ oom_kill ใน memory.events ที่เพิ่มขึ้น (cgroup v2) เทียบกับเวลาที่ผู้เล่นหลุด - สัญญาณว่าใช่: ตอนที่การเชื่อมต่อหลุดพร้อมกัน มีบันทึกว่าโปรเซสเซิร์ฟเวอร์เกมถูกปิด และก่อนหน้านั้นการใช้หน่วยความจำไต่ขึ้นไปถึงขีดจำกัด - สัญญาณว่าไม่ใช่: ไม่มีบันทึก OOM แต่โปรเซสตาย: ให้ดู crash log และ core dump ตาม “เซิร์ฟเวอร์แครช” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [mm/oom_kill.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/oom_kill.c?h=v6.12) · Linux kernel · คำนวณให้โปรเซสที่ใช้หน่วยความจำมากที่สุดได้คะแนนสูงสุด (รวม oom_score_adj), เมื่อปิดจะบันทึก “Out of memory: Killed process …” - [Assign Memory Resources to Containers and Pods](https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/) · Kubernetes · ถ้าคอนเทนเนอร์ยังใช้หน่วยความจำเกิน limit ต่อไป จะถูกปิดและสถานะแสดงเป็น OOMKilled - [Pushing the Limits of Windows: Virtual Memory](https://learn.microsoft.com/en-us/archive/blogs/markrussinovich/pushing-the-limits-of-windows-virtual-memory) · Microsoft · บน Windows เมื่อถึง commit limit การจัดสรรที่ต้อง commit หน่วยความจำจะล้มเหลว และอาจนำไปสู่ข้อผิดพลาดของแอปหรือระบบขัดข้อง - [Control Group v2](https://docs.kernel.org/admin-guide/cgroup-v2.html) · Linux kernel · oom_kill ของ memory.events: จำนวนโปรเซสใน cgroup นี้ที่ถูก OOM killer ปิด #### so-reclaim · หยุดชะงักจาก memory reclaim และ compaction · Memory compaction / reclaim stalls (THP) โปรเซสจะหยุดชะงักระหว่างที่ OS ทำ compaction หน่วยความจำเพื่อสร้างเพจขนาดใหญ่ (huge page) หรือเรียกคืนหน่วยความจำให้ว่าง (reclaim) - ทำไม → ผลคือ → บนหน้าจอ: หน่วยความจำว่างเหลือน้อย หรือฟีเจอร์ huge page (THP) สั่ง compaction หน่วยความจำ → เธรดที่ขอหน่วยความจำต้องรอจนการ reclaim หรือ compaction เสร็จ → เซิร์ฟเวอร์ค้างเป็นพัก ๆ แบบไม่สม่ำเสมอ (ไม่กี่ ms ถึงหลายร้อย ms) - อาการ: ค้าง, กระตุก / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ลดการจัดสรรหน่วยความจำก้อนใหญ่ระหว่างรัน (จองไว้ล่วงหน้าตอนเริ่มแล้วใช้ซ้ำ) - งานฝั่งทีมอินฟรา: ตั้งให้ใช้ huge page (THP) เฉพาะส่วนที่ร้องขอ (madvise), เพิ่มเกณฑ์หน่วยความจำว่างขั้นต่ำ (vm.min_free_kbytes เป็นต้น) - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (เวลาต่อทิกของเซิร์ฟเวอร์, PSI ของหน่วยความจำ) - จุดที่ต้องดู: some และ full ใน /proc/pressure/memory (สัดส่วนเวลาที่หยุดรอหน่วยความจำ) และค่าที่เพิ่มขึ้นของ compact_stall ใน /proc/vmstat ดูคู่กับเวลาต่อทิกของเซิร์ฟเวอร์ และตรวจค่า /sys/kernel/mm/transparent_hugepage/defrag - สัญญาณว่าใช่: ตอนที่ทิกพุ่ง PSI ของหน่วยความจำสูงขึ้นและ compact_stall เพิ่มขึ้น defrag ตั้งเป็น always - สัญญาณว่าไม่ใช่: PSI และ compact_stall ไม่ขยับ: ไม่ใช่สาเหตุนี้ ถ้าการใช้ swap เพิ่มขึ้น ให้ดู “สวอป” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Transparent Hugepage Support](https://docs.kernel.org/admin-guide/mm/transhuge.html) · Linux kernel · ถ้า defrag=always เมื่อจัดสรร THP ไม่สำเร็จจะ reclaim และ compaction หน่วยความจำทันทีตรงนั้นและหยุดรอ, madvise จะทำแบบนั้นเฉพาะส่วนที่ร้องขอ - [Documentation for /proc/sys/vm/](https://docs.kernel.org/admin-guide/sysctl/vm.html) · Linux kernel · min_free_kbytes: เกณฑ์หน่วยความจำว่างขั้นต่ำ (watermark) ที่เคอร์เนลกันไว้ - [PSI - Pressure Stall Information](https://docs.kernel.org/accounting/psi.html) · Linux kernel · some (สัดส่วนเวลาที่งานบางส่วนหยุดรอหน่วยความจำ) และ full (สัดส่วนเวลาที่งานทั้งหมดหยุด) ใน /proc/pressure/memory #### so-timejump · นาฬิการะบบกระโดด (NTP step) · Wall-clock jump (NTP step) ถ้านาฬิกาของเซิร์ฟเวอร์ถูกปรับไปข้างหน้าหรือถอยหลังหลายวินาทีในครั้งเดียว ตัวจับเวลาที่อิงนาฬิการะบบจะทำงานพร้อมกันรวดเดียวหรือหยุดไป - ทำไม → ผลคือ → บนหน้าจอ: การซิงก์เวลาปรับนาฬิกาครั้งใหญ่ในครั้งเดียว → ตัวจับเวลาทำงานรวดเดียวหรือหยุด และตัดสินไทม์เอาต์ผิด → บัฟและคูลดาวน์ผิดปกติ, หลุดพร้อมกันหลายคน, กรอเร็ว - อาการ: กรอเร็ว, หลุด, กดไม่ติด/โรลแบ็ค / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: คำนวณเวลาที่ผ่านไป ไทม์เอาต์ และคูลดาวน์ด้วย monotonic clock ที่ไม่กระโดดและไม่ถอยหลัง, ใช้ wall clock เฉพาะการแสดงผลและการบันทึก log - งานฝั่งทีมอินฟรา: ปรับนาฬิกาแบบค่อยเป็นค่อยไป (ใช้ makestep ของ chrony เฉพาะตอนเพิ่งเริ่มทำงาน), มอนิเตอร์สถานะการซิงก์เวลา (ความต่างของนาฬิกา) - ตัวเลขที่ควรรู้: ntpd จะปรับทีเดียวเมื่อต่างเกิน 0.128 วินาที ถ้าน้อยกว่านั้นจะค่อย ๆ ปรับด้วยอัตราที่ต้องใช้เวลา 30 นาทีเศษในการลบความต่าง 1 วินาที ส่วน chrony ที่นิยมใช้ในปัจจุบัน เมื่อใช้การตั้งค่าที่แนะนำ (makestep) จะปรับทีเดียวเพียงไม่กี่ครั้งหลังเริ่มทำงาน จากนั้นจะค่อย ๆ ปรับ นาฬิกายังกระโดดได้เมื่อ VM หยุดชั่วครู่แล้วกลับมาทำงาน - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (จำนวนครั้งที่ตัวจับเวลาทำงานและจำนวนการหลุด, บันทึกการปรับนาฬิกา) - จุดที่ต้องดู: บันทึกการปรับนาฬิกาครั้งใหญ่ใน log ของเซอร์วิสซิงก์เวลา เทียบกับเวลาที่เกิดปัญหา chrony จะเขียนลง syslog เมื่อปรับมากกว่าค่า logchange (ค่าเริ่มต้น 1 วินาที) - สัญญาณว่าใช่: มีบันทึกการปรับนาฬิกาตรงกับเวลาที่บัฟและคูลดาวน์ผิดปกติ หลุดพร้อมกัน หรือกรอเร็ว และขนาดที่ปรับใกล้เคียงกับขนาดความผิดปกติ - สัญญาณว่าไม่ใช่: ไม่มีบันทึกการปรับนาฬิกา: ไม่ใช่สาเหตุนี้ ถ้าเป็น VM ให้ดูกรณีที่หยุดแล้วกลับมาทำงานด้วย (“การซ่อมบำรุงโฮสต์คลาวด์/live migration”) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [ntpd - Network Time Protocol (NTP) daemon](https://www.ntp.org/documentation/4.2.8-series/ntpd/) · Network Time Foundation · ถ้าต่างเกินเกณฑ์ step 128 ms จะปรับทีเดียว ถ้าน้อยกว่าจะค่อย ๆ ปรับ ด้วยอัตรา 0.5 ms ต่อวินาที การปรับ 1 วินาทีจึงใช้ 2,000 วินาที (ประมาณ 33 นาที) - [chrony – Frequently Asked Questions](https://chrony-project.org/faq.html) · chrony · แนะนำให้อนุญาต step เพียงไม่กี่ครั้งหลังเริ่มทำงาน เช่น makestep 1 3, VM ที่หยุดแล้วกลับมาทำงานอาจตื่นมาพร้อมเวลาที่คลาดเคลื่อน - [clock_gettime(2) — Linux manual page](https://man7.org/linux/man-pages/man2/clock_gettime.2.html) · Linux man-pages · CLOCK_MONOTONIC ไม่ได้รับผลจากการกระโดดแบบไม่ต่อเนื่องของนาฬิการะบบและไม่ถอยหลัง - [chrony.conf(5)](https://chrony-project.org/doc/4.6/chrony.conf.html) · chrony · logchange: ถ้าปรับนาฬิกามากกว่าค่านี้ (ค่าเริ่มต้น 1 วินาที) จะบันทึกลง syslog #### so-cron · งานตามกำหนดเวลา (scheduled job) · Cron jobs (log rotation, backup, scans) งานบีบอัด log, การสำรองข้อมูล และการสแกนความปลอดภัยที่รันเวลาเดิมทุกวันจะกิน CPU และดิสก์ - ทำไม → ผลคือ → บนหน้าจอ: งานของ OS เริ่มรันตามเวลาที่กำหนด → แย่ง CPU และดิสก์กับเซิร์ฟเวอร์เกม → กระตุกและสโลว์โมชั่นในเวลาเดิม เช่น 04:00 ทุกวัน - อาการ: กระตุก, สโลว์โมชั่น / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: เป็นรอบสม่ำเสมอ - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: กระจายเวลาของงาน, ลดลำดับความสำคัญ (nice, ionice), แยกออกจากเซิร์ฟเวอร์เกม (รันบนเซิร์ฟเวอร์อื่น) - บนกราฟ: พุ่งเป็นรอบ (อัตราการใช้ CPU, คิวดิสก์, เวลาต่อทิกของเซิร์ฟเวอร์) - จุดที่ต้องดู: เวลารันของงานตามกำหนดเวลาจาก crontab และ systemctl list-timers และโปรเซสที่ใช้ CPU และดิสก์ตอนที่ทิกพุ่งจาก pidstat -u -d - สัญญาณว่าใช่: ทิกพุ่งเวลาเดิมทุกวัน (หรือทุกชั่วโมง) และตอนนั้นโปรเซสของงานตามกำหนดเวลากิน CPU และดิสก์ - สัญญาณว่าไม่ใช่: เวลาที่พุ่งไม่ตรงกับเวลาเดิมของแต่ละวัน: ไม่ใช่สาเหตุนี้ ถ้าพุ่งทุกไม่กี่วินาทีหรือไม่กี่นาที ให้ดู “GC ของเซิร์ฟเวอร์หยุดทั้งระบบ”, “ตัวจับเวลาทำงานพร้อมกันจำนวนมาก” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [ionice(1) — Linux manual page](https://man7.org/linux/man-pages/man1/ionice.1.html) · util-linux · งานในคลาส idle จะได้ disk I/O ก็ต่อเมื่อไม่มีโปรแกรมอื่นใช้ดิสก์ - [systemd.timer(5) — Linux manual page](https://man7.org/linux/man-pages/man5/systemd.timer.5.html) · systemd · RandomizedDelaySec ใช้หน่วงเวลางานตามกำหนดเวลาแบบสุ่ม เพื่อลดโหลดที่กระจุกตัว - [systemctl(1) — Linux manual page](https://man7.org/linux/man-pages/man1/systemctl.1.html) · systemd · list-timers: แสดง timer unit เรียงตามเวลารันครั้งถัดไป - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -u แสดง CPU, -d แสดง disk I/O แยกตามโปรเซส #### so-os-update · ประสิทธิภาพเปลี่ยนไปหลังอัปเดต OS/เคอร์เนล/ไดรเวอร์/เฟิร์มแวร์ · Performance regression after OS / kernel / driver / firmware update โค้ดเกมเหมือนเดิม แต่เซิร์ฟเวอร์ช้าลงตั้งแต่อัปเดต OS, เคอร์เนล, ไดรเวอร์ หรือเฟิร์มแวร์ การอัปเดตอาจเปลี่ยนค่าเริ่มต้น, scheduler, การป้องกันช่องโหว่ CPU (mitigations) และพฤติกรรมของไดรเวอร์ - ทำไม → ผลคือ → บนหน้าจอ: แพตช์ความปลอดภัยตามรอบหรืออิมเมจเซิร์ฟเวอร์ใหม่ทำให้เคอร์เนล ไดรเวอร์ หรือเฟิร์มแวร์เปลี่ยน → ค่าเริ่มต้นหรือ scheduler เปลี่ยน หรือเปิด mitigation ตัวใหม่ งานเดิมจึงใช้ CPU time มากขึ้น และลำดับที่เธรดได้รับ CPU เปลี่ยนไป → เซิร์ฟเวอร์ที่เคยลื่นช้าลงนิดหน่อยตลอดเวลาตั้งแต่วันที่อัปเดต: อินพุตดีเลย์ และพอคนแห่มารวมกันก็กระตุกและเป็นสโลว์โมชั่น - อาการ: อินพุตดีเลย์, กระตุก, สโลว์โมชั่น / ปัจจัย: ความหน่วง, การหยุดชะงัก, จิตเตอร์ - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: ลงอัปเดตกับเซิร์ฟเวอร์บางเครื่องก่อน แล้วเทียบเวลาต่อทิก ความหน่วง และอัตราการใช้ CPU กับเวอร์ชันก่อนหน้าก่อนขยายวง, deploy คนละวันกับแพตช์เกม, บันทึกเวอร์ชันเคอร์เนล ไดรเวอร์ เฟิร์มแวร์ และค่า sysctl สำคัญก่อนและหลังอัปเดต, ถ้ามีปัญหาให้บูตด้วยเคอร์เนลเดิมเพื่อยืนยัน, ชั่งความเสี่ยงด้านความปลอดภัยก่อนตัดสินใจปิด mitigation (mitigations=off) - ตัวเลขที่ควรรู้: เมื่อเวอร์ชันเคอร์เนลเปลี่ยน พฤติกรรมเริ่มต้นก็เปลี่ยนด้วย เช่น Linux เริ่มย้าย scheduler จาก CFS ไปเป็น EEVDF ตั้งแต่ 6.6 และค่าเริ่มต้นของเพดานคิวรอเชื่อมต่อ (somaxconn) ก็เปลี่ยนจาก 128 เป็น 4,096 ตั้งแต่ 5.4 การป้องกันช่องโหว่ CPU เพิ่มงาน เช่น ล้างบัฟเฟอร์ภายใน CPU ตอนกลับจากเคอร์เนลไปยังโปรแกรม (ทุกครั้งที่จบ system call) และตอน context switch หรือสลับเข้าออก VM เซิร์ฟเวอร์เครือข่ายที่เรียก system call ทุกแพ็กเก็ตจึงได้รับผลมากกว่า ช่องโหว่บางตัวต้องปิด SMT (ฟีเจอร์ที่ให้หนึ่งคอร์ทำงานเหมือนสองเธรด) จึงจะป้องกันได้สมบูรณ์ และการปิด SMT อาจทำให้ประสิทธิภาพลดลงมากแล้วแต่ประเภทงาน พารามิเตอร์เคอร์เนล mitigations=off ปิดการป้องกันทั้งหมดนี้เพื่อเอาประสิทธิภาพคืน แต่ระบบจะเสี่ยงต่อช่องโหว่เหล่านั้น - บนกราฟ: ขึ้นเป็นขั้นบันไดจากจุดหนึ่ง (เวลาต่อทิกของเซิร์ฟเวอร์, อัตราการใช้ CPU, ความหน่วงที่โหลดเท่ากัน) - จุดที่ต้องดู: ประวัติการอัปเดตจาก package manager และเวลารีบูต, เวอร์ชันเคอร์เนลจาก uname -r, ข้อมูลไดรเวอร์ NIC จาก ethtool -i เทียบกับเวลาที่ความหน่วงเริ่มสูง และเทียบเซิร์ฟเวอร์ที่อัปเดตกับที่ยังไม่อัปเดตภายใต้โหลดเท่ากันด้วย mpstat และ pidstat รวมถึงสถานะ mitigation ใน /sys/devices/system/cpu/vulnerabilities/ - สัญญาณว่าใช่: ความหน่วงและอัตราการใช้ CPU ขึ้นหนึ่งขั้นตั้งแต่รีบูตหลังอัปเดตแล้วอยู่ระดับนั้น ที่โหลดเท่ากัน เฉพาะเซิร์ฟเวอร์ที่อัปเดตเท่านั้นที่สูง บูตด้วยเคอร์เนลหรือไดรเวอร์เดิมแล้วกลับเป็นปกติ - สัญญาณว่าไม่ใช่: เซิร์ฟเวอร์ที่อัปเดตกับที่ยังไม่อัปเดตช้าเท่ากันที่โหลดเท่ากัน: ไม่ใช่สาเหตุนี้ ถ้าวันเดียวกัน deploy แพตช์เกมด้วย และจำนวนหรือขนาดแพ็กเก็ตต่อผู้เล่นเปลี่ยน ให้ดู “แพตช์ทำให้รูปแบบทราฟฟิกเปลี่ยน” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ตรวจสถานะ mitigation ได้จากไฟล์ใต้ /sys/devices/system/cpu/vulnerabilities/ ค่าเริ่มต้น (mitigations=auto) ป้องกันโดยยังเปิด SMT ไว้ แต่ถ้าตั้ง auto,nosmt จะปิด SMT บน CPU ที่มีช่องโหว่ หลังอัปเกรดเคอร์เนล จำนวนคอร์ตรรกะ (logical core) จึงอาจลดลงครึ่งหนึ่ง ถ้าอัปเดต OS วันเดียวกับแพตช์เกมจะแยกสาเหตุได้ยาก จึงควร deploy แยกกัน - แหล่งอ้างอิง: - [The kernel’s command-line parameters](https://docs.kernel.org/admin-guide/kernel-parameters.html) · Linux kernel · mitigations=: off ปิดการป้องกันช่องโหว่ CPU ทั้งหมดเพื่อเพิ่มประสิทธิภาพแต่จะเสี่ยงต่อช่องโหว่, ค่าเริ่มต้น auto ป้องกันโดยเปิด SMT ไว้, auto,nosmt ปิด SMT เมื่อจำเป็น - [MDS - Microarchitectural Data Sampling](https://docs.kernel.org/admin-guide/hw-vuln/mds.html) · Linux kernel · mitigation จะล้างบัฟเฟอร์ CPU ตอนกลับจากเคอร์เนลไปยัง user space และตอนเข้า VM, ตรวจสถานะช่องโหว่และ mitigation ได้จากไฟล์ใต้ /sys/devices/system/cpu/vulnerabilities/, CPU จำนวนมากต้องปิด SMT จึงจะป้องกันได้สมบูรณ์ และการปิด SMT อาจกระทบประสิทธิภาพมากแล้วแต่งาน - [Spectre Side Channels](https://docs.kernel.org/admin-guide/hw-vuln/spectre.html) · Linux kernel · เพื่อป้องกันช่องโหว่ จะล้างบัฟเฟอร์ branch prediction ตอน context switch และตอนสลับ VM, mitigation แบบเข้มเพิ่ม overhead ให้ทุกโปรแกรม - [EEVDF Scheduler](https://docs.kernel.org/scheduler/sched-eevdf.html) · Linux kernel · Linux เริ่มย้ายจาก CFS ไปใช้ scheduler EEVDF ตั้งแต่ 6.6 - [listen(2) — Linux manual page](https://man7.org/linux/man-pages/man2/listen.2.html) · Linux man-pages · ค่าเริ่มต้นของ somaxconn เปลี่ยนจาก 128 เป็น 4,096 ตั้งแต่ Linux 5.4 - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · ดูข้อมูลไดรเวอร์ของอุปกรณ์เครือข่ายด้วย ethtool -i #### so-conntrack · ตาราง conntrack ของเซิร์ฟเวอร์เต็ม · conntrack table full เมื่อตาราง connection tracking (conntrack) ที่ไฟร์วอลล์ของ Linux ใช้บันทึกทุกการเชื่อมต่อแตะขีดจำกัด แพ็กเก็ตใหม่จะถูกทิ้ง - ทำไม → ผลคือ → บนหน้าจอ: การเชื่อมต่อทะลักและการเชื่อมต่อสั้น ๆ ซ้ำไปมาทำให้รายการในตารางเพิ่มขึ้น → ตารางเต็ม การเชื่อมต่อใหม่และแพ็กเก็ตบางส่วนถูกทิ้ง → เข้าเกมไม่ได้ และวาร์ปเพราะแพ็กเก็ตหายโดยไม่รู้สาเหตุ - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, วาร์ป / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ลดการเชื่อมต่อสั้น ๆ (ใช้การเชื่อมต่อซ้ำสำหรับการเรียกระหว่างเซิร์ฟเวอร์) ไคลเอนต์: เมื่อเชื่อมต่อไม่สำเร็จหรือหลุด ให้เพิ่มช่วงห่างการลองใหม่ขึ้นเรื่อย ๆ และสุ่มให้กระจาย - งานฝั่งทีมอินฟรา: เพิ่มขนาดตาราง (nf_conntrack_max), ไม่ติดตามพอร์ตเกม (NOTRACK ในตาราง raw), ตั้ง alert ปริมาณการใช้งาน - ตัวเลขที่ควรรู้: ขีดจำกัดเริ่มต้นอยู่ที่ประมาณ 60,000–260,000 รายการตามขนาดหน่วยความจำของเซิร์ฟเวอร์ ถ้าล้น log ของเคอร์เนลจะมี “nf_conntrack: table full, dropping packet” - บนกราฟ: ชนเพดานแล้วแบนราบ (จำนวนรายการ conntrack (nf_conntrack_count)) - จุดที่ต้องดู: net.netfilter.nf_conntrack_count (จำนวนรายการปัจจุบัน) จาก sysctl วางบนกราฟเดียวกับ nf_conntrack_max และหา “nf_conntrack: table full, dropping packet” ใน dmesg - สัญญาณว่าใช่: nf_conntrack_count แบนราบที่ค่า max และตั้งแต่ตอนนั้น log ของเคอร์เนลขึ้น table full - สัญญาณว่าไม่ใช่: จำนวนรายการยังห่างจาก max มาก: ไม่ใช่สาเหตุนี้ ขีดจำกัด connection tracking ของตัวอินสแตนซ์ AWS เองให้ดู conntrack_allowance_exceeded ใน “เกินขีดจำกัด PPS ของคลาวด์” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Netfilter Conntrack Sysfs variables](https://docs.kernel.org/networking/nf_conntrack-sysctl.html) · Linux kernel · ค่าเริ่มต้นของ nf_conntrack_max เท่ากับจำนวน hash bucket (nf_conntrack_buckets) ซึ่งกำหนดจากขนาดหน่วยความจำ - [net/netfilter/nf_conntrack_core.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/nf_conntrack_core.c?h=v6.12) · Linux kernel · ขนาดเริ่มต้นคือ 65,536 ถ้าหน่วยความจำเกิน 1 GB และ 262,144 ถ้าเกิน 4 GB (64 บิต), เมื่อเต็มจะบันทึก “nf_conntrack: table full, dropping packet” แล้วทิ้ง - [iptables-extensions(8) — Linux manual page](https://man7.org/linux/man-pages/man8/iptables-extensions.8.html) · netfilter · ใช้ CT --notrack ในตาราง raw เพื่อไม่ติดตามการเชื่อมต่อ #### so-ports · ephemeral port หมดในการเชื่อมต่อระหว่างเซิร์ฟเวอร์ · Ephemeral port exhaustion (TIME_WAIT) ถ้าเซิร์ฟเวอร์เกมเปิดและปิดการเชื่อมต่อสั้น ๆ ไปยัง DB หรือเซิร์ฟเวอร์อื่นบ่อย ๆ การเชื่อมต่อที่ปิดแล้วจะยังถือครองพอร์ตอยู่สักพัก จนเปิดการเชื่อมต่อใหม่ไม่ได้ - ทำไม → ผลคือ → บนหน้าจอ: เปิดและปิดการเชื่อมต่อใหม่ทุกคำขอ → ฝั่งที่ปิดก่อนจะถือครองพอร์ตไว้ประมาณ 60 วินาทีบน Linux (TIME_WAIT) จนพอร์ตที่ใช้ได้หมด → คำขอภายในล้มเหลว: บันทึกข้อมูลไม่สำเร็จ, ฟีเจอร์ทำงานผิดพลาด - อาการ: กดไม่ติด/โรลแบ็ค, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์, เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ใช้การเชื่อมต่อซ้ำ (connection pool), เลิกเปิดและปิดการเชื่อมต่อใหม่ทุกคำขอ - งานฝั่งทีมอินฟรา: ขยายช่วงพอร์ต (ip_local_port_range), พิจารณาใช้ TIME_WAIT ซ้ำสำหรับการเชื่อมต่อขาออก (tcp_tw_reuse ของ Linux), มอนิเตอร์จำนวน TIME_WAIT - ตัวเลขที่ควรรู้: ช่วงพอร์ตเริ่มต้นของ Linux (32768–60999) มีประมาณ 28,000 พอร์ต ถ้าเปิดการเชื่อมต่อใหม่ไปยังปลายทางเดียวกันเกิน 470 ครั้งใน 1 วินาที พอร์ตจะหมด Windows มีพอร์ตเริ่มต้นประมาณ 16,000 พอร์ต (49152–65535) และ TIME_WAIT ยาวกว่า จึงหมดเร็วกว่า - บนกราฟ: ชนเพดานแล้วแบนราบ (จำนวนซ็อกเก็ต TIME_WAIT, จำนวนการเชื่อมต่อภายในที่ล้มเหลว) - จุดที่ต้องดู: นับซ็อกเก็ต TIME_WAIT แยกตามปลายทางด้วย ss -tan state time-wait และหา connect ที่ล้มเหลว (EADDRNOTAVAIL) ใน log ของเซิร์ฟเวอร์เกม - สัญญาณว่าใช่: TIME_WAIT ไปยังปลายทางเดียวกัน (เช่น DB) แบนราบใกล้ขนาดช่วง ephemeral port (ค่าเริ่มต้นประมาณ 28,000) และ connect ล้มเหลวด้วย EADDRNOTAVAIL - สัญญาณว่าไม่ใช่: TIME_WAIT น้อย แต่เฉพาะการเชื่อมต่อออกไปภายนอกที่ล้มเหลว: ให้ดู “ขีดจำกัดการเชื่อมต่อ/พอร์ตของ NAT gateway บนคลาวด์” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: TIME_WAIT 60 วินาทีของ Linux เป็นค่าที่ฝังตายตัวในเคอร์เนล การลด tcp_fin_timeout ซึ่งชื่อคล้ายกันไม่ได้ทำให้ TIME_WAIT สั้นลง - แหล่งอ้างอิง: - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · ip_local_port_range ค่าเริ่มต้น 32768–60999, tcp_tw_reuse, tcp_fin_timeout คือระยะเวลาที่คงสถานะ FIN_WAIT_2 - [include/net/tcp.h (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp.h?h=v6.12) · Linux kernel · TCP_TIMEWAIT_LEN (60*HZ): TIME_WAIT ประมาณ 60 วินาทีเป็นค่าคงที่ของเคอร์เนล - [TCP/IP port exhaustion troubleshooting](https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/tcp-ip-port-exhaustion-troubleshooting) · Microsoft · ช่วง dynamic port เริ่มต้นของ Windows คือ 49152–65535 และการเชื่อมต่อที่ปิดแล้วจะถือพอร์ตไว้ในสถานะ TIME_WAIT 4 นาทีโดยค่าเริ่มต้น - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · ใช้ตัวกรองสถานะ state time-wait เพื่อดูเฉพาะซ็อกเก็ต TIME_WAIT - [connect(2) — Linux manual page](https://man7.org/linux/man-pages/man2/connect.2.html) · Linux man-pages · EADDRNOTAVAIL: เปิดการเชื่อมต่อไม่ได้เพราะพอร์ตทั้งหมดในช่วง ephemeral port ถูกใช้อยู่ ### L8 ซ็อกเก็ตและโปรโตคอล (14 สาเหตุ) #### sk-hol · TCP HOL blocking · Head-of-line blocking เพื่อรักษาลำดับข้อมูล TCP จะไม่ส่งแพ็กเก็ตที่มาถึงทีหลังให้เกม จนกว่าจะได้รับแพ็กเก็ตที่หายไปหนึ่งตัวนั้นอีกครั้ง - ทำไม → ผลคือ → บนหน้าจอ: แพ็กเก็ตหายไปหนึ่งตัว → แพ็กเก็ตที่ตามมามาถึงแล้ว แต่ต้องรออยู่ใน receive buffer → ค้างไปก่อนแล้วปล่อยออกมารวดเดียวเป็นอาการกรอเร็ว - อาการ: ค้าง, กรอเร็ว / ปัจจัย: แพ็กเก็ตหาย, การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ส่งตำแหน่งแบบเรียลไทม์ทาง UDP, ใช้การส่งแบบ reliable เฉพาะข้อมูลที่จำเป็นจริง ๆ, แบ่งเป็นหลาย stream ไคลเอนต์: เปลี่ยนการจัดการเครือข่ายให้ตรงกับเซิร์ฟเวอร์ (UDP, แยกช่องทาง) - ตัวเลขที่ควรรู้: ถ้าแพ็กเก็ตหายหนึ่งตัว จะหยุดอย่างน้อยเท่ากับเวลาไปกลับ + α ถ้าแพ็กเก็ตที่ส่งซ้ำหายอีก จะหยุดหลายร้อย ms ถึงหลายวินาที - บนกราฟ: ขาดช่วงแล้วมารวดเดียว (ปริมาณข้อมูลที่รับต่อการเชื่อมต่อ, จำนวนการส่งซ้ำ) - จุดที่ต้องดู: แพ็กเก็ตที่ส่งซ้ำและช่วงว่างก่อนและหลังแพ็กเก็ตนั้นในการเชื่อมต่อของผู้เล่นคนนั้น จาก packet capture ฝั่งเซิร์ฟเวอร์ (tcpdump, Wireshark) ส่วนทั้งเซิร์ฟเวอร์ดูค่าที่เพิ่มขึ้นของ TcpRetransSegs จาก nstat -az - สัญญาณว่าใช่: ช่วงที่หยุดเริ่มด้วยการส่งซ้ำของแพ็กเก็ตเดียว และทันทีที่แพ็กเก็ตที่ส่งซ้ำมาถึง ข้อมูลที่กองรอก็ถูกประมวลผลรวดเดียว (ปริมาณที่รับเป็น 0 แล้วพุ่ง) - สัญญาณว่าไม่ใช่: เกมที่สื่อสารทาง UDP: ไม่เกี่ยว ถ้าไม่มีการส่งซ้ำแต่ยังหยุด ให้ดูฝั่งทิกของเซิร์ฟเวอร์ (“ทิกเกินงบเวลา”) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · TCP เป็นบริการ byte stream ที่เชื่อถือได้และรักษาลำดับ - [RFC 5681: TCP Congestion Control](https://www.rfc-editor.org/rfc/rfc5681) · IETF · ตรวจพบแพ็กเก็ตหายจาก duplicate ACK 3 ตัวแล้วทำ fast retransmit ถ้าไม่ใช่ก็รอตัวจับเวลาส่งซ้ำ - [RFC 6298: Computing TCP's Retransmission Timer](https://www.rfc-editor.org/rfc/rfc6298) · IETF · backoff ที่เพิ่มเวลารอเป็นสองเท่าทุกครั้งที่ตัวจับเวลาส่งซ้ำหมดเวลา - [net/ipv4/proc.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/proc.c?h=v6.12) · Linux kernel · ชื่อตัวนับที่ nstat แสดง: RetransSegs (จำนวน segment ที่ส่งซ้ำ) ในกลุ่ม Tcp #### sk-rto · TCP RTO และ exponential backoff · RTO and exponential backoff ทุกครั้งที่การส่งซ้ำล้มเหลวอีก เวลารอจะเพิ่มเป็นสองเท่า การเชื่อมต่อที่ขาดไปแป๊บเดียวจึงกลายเป็นการหยุดยาว - ทำไม → ผลคือ → บนหน้าจอ: การเชื่อมต่อขาดไปแป๊บหนึ่ง การส่งซ้ำจึงล้มเหลวติดต่อกัน → เวลารอก่อนลองครั้งถัดไปเพิ่มเป็นสองเท่าทุกครั้ง เช่น 0.3 → 0.6 → 1.2 → 2.4 วินาที (ที่ปิง 100 ms) → เน็ตขาดแค่ 1 วินาที แต่เกมค้างเกิน 2 วินาที ถ้าขาดนานกว่านั้นสุดท้ายก็หลุด - อาการ: ค้าง, หลุด / ปัจจัย: แพ็กเก็ตหาย, การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ตอบ heartbeat และถ้าไม่ได้รับนานเกินกำหนดให้ปิดการเชื่อมต่อเองก่อน (ร่นจุดที่เลิกรอด้วย TCP_USER_TIMEOUT), ใช้ session token เพื่อต่อเซสชันเดิม, ใช้ reliable UDP ไคลเอนต์: ส่ง heartbeat ถี่ ๆ และเมื่อการตอบกลับขาดหาย ให้เชื่อมต่อใหม่ทันทีโดยไม่ต้องรอการส่งซ้ำของ TCP - ตัวเลขที่ควรรู้: RTO (เวลารอก่อนส่งซ้ำ) ของ Linux มีค่าต่ำสุดเป็น “ปิง + 200 ms” และตอนเริ่มเชื่อมต่อจะเริ่มที่ 1 วินาที ถ้าใช้ค่าเริ่มต้น (tcp_retries2=15) ต่อให้ส่งซ้ำล้มเหลวต่อเนื่อง ก็จะเลิกเชื่อมต่อหลังผ่านไปประมาณ 15 นาที - บนกราฟ: ขาดช่วงแล้วมารวดเดียว (RTO และ backoff ต่อการเชื่อมต่อ, จำนวนครั้งที่ RTO หมดเวลา) - จุดที่ต้องดู: rto (เวลารอส่งซ้ำ ms) และ backoff (จำนวนครั้งที่หมดเวลาติดกัน) ของการเชื่อมต่อที่หยุดจาก ss -ti ส่วนทั้งเซิร์ฟเวอร์ดูค่าที่เพิ่มขึ้นของ TcpExtTCPTimeouts (จำนวนครั้งที่ตัวจับเวลาส่งซ้ำหมดเวลา) จาก nstat -az - สัญญาณว่าใช่: backoff ของการเชื่อมต่อที่หยุดอยู่ตั้งแต่ 1 ขึ้นไป rto โตจนเป็นระดับวินาที และตอนนั้น TCPTimeouts เพิ่มขึ้น - สัญญาณว่าไม่ใช่: การส่งซ้ำจบด้วย fast retransmit และไม่มี RTO หมดเวลา: การหยุดจะสั้น กรณีนั้นให้ดู “TCP HOL blocking” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 6298: Computing TCP's Retransmission Timer](https://www.rfc-editor.org/rfc/rfc6298) · IETF · RTO แรก 1 วินาที, เพิ่ม RTO เป็นสองเท่าทุกครั้งที่ตัวจับเวลาหมดเวลา (exponential backoff) - [net/ipv4/tcp_input.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_input.c?h=v6.12) · Linux kernel · RTO ของ Linux = smoothed RTT + ค่าความแปรปรวนของ RTT และค่าความแปรปรวนมีขั้นต่ำเป็น tcp_rto_min (200 ms) RTO จึงไม่ต่ำกว่า RTT + 200 ms - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_rto_min_us ค่าเริ่มต้น 200 ms, RTO แรกของคำขอเชื่อมต่อ 1 วินาที, ถ้า tcp_retries2=15 จะใช้อย่างน้อย 924.6 วินาที (ประมาณ 15 นาที) ก่อนเลิก - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · rto (ตัวจับเวลาส่งซ้ำ, ms) และ backoff (จำนวนครั้งของ exponential backoff) ใน -i - [net/ipv4/proc.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/proc.c?h=v6.12) · Linux kernel · ชื่อตัวนับที่ nstat แสดง: TCPTimeouts ในกลุ่ม TcpExt - [net/ipv4/tcp_timer.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_timer.c?h=v6.12) · Linux kernel · ทุกครั้งที่ตัวจับเวลาส่งซ้ำหมดเวลา จะเพิ่ม TCPTimeouts เพิ่ม backoff ทีละหนึ่ง และเพิ่ม RTO เป็นสองเท่า (จนถึงค่าสูงสุด) #### sk-nagle · อัลกอริทึม Nagle + delayed ACK · Nagle + delayed ACK (TCP_NODELAY off) อัลกอริทึม Nagle ที่รวบรวมแพ็กเก็ตเล็กก่อนส่ง กับ delayed ACK ที่ส่ง ACK ช้า ทำงานประกบกัน ทุกครั้งที่เขียนข้อความแบ่งเป็นหลายส่วนจึงดีเลย์ 40–200 ms - ทำไม → ผลคือ → บนหน้าจอ: เขียนข้อความเล็ก ๆ แบ่งเป็นหลายส่วนโดยไม่ได้เปิด TCP_NODELAY → ฝั่งส่งรอ ACK ส่วนฝั่งรับส่ง ACK ช้า → ปิงต่ำ แต่ทุกการกระทำตอบสนองช้าสม่ำเสมอ: อินพุตดีเลย์ - อาการ: อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์, เราคนเดียว / เกิดเมื่อไร: ตลอดเวลา, ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: เปิด TCP_NODELAY, รวบรวมข้อความในหนึ่งทิกแล้วเขียนครั้งเดียว, อย่าพึ่งวิธีปิด delayed ACK ฝั่งรับ (TCP_QUICKACK ของ Linux มีผลแค่ชั่วครู่ ส่วน Windows ต้องแก้ registry ทีละเครื่อง) เพราะเกมควบคุมได้ไม่แน่นอน ไคลเอนต์: เปิด TCP_NODELAY, รวบรวมข้อความในหนึ่งเฟรมแล้วเขียนครั้งเดียว - ตัวเลขที่ควรรู้: delayed ACK ของ Linux ปกติอยู่ที่ 40 ms (สูงสุด 200 ms แล้วแต่สถานการณ์) Windows รุ่นเก่าใช้ 200 ms ส่วนรุ่นปัจจุบันใช้ 40 ms (เทมเพลตเริ่มต้นของ Windows Server 2019 คือ 40 ms) OS ฝั่งรับเป็นผู้กำหนด delayed ACK ถ้าเซิร์ฟเวอร์เปิด Nagle ไว้แล้วส่งข้อความแบ่งเป็นหลายส่วน ก็อาจดีเลย์ครั้งละ 40–200 ms แล้วแต่ PC ที่รับ - บนกราฟ: สูงตลอดตั้งแต่แรก (เวลาตอบสนองของแอ็กชัน (RTT ในเกม)) - จุดที่ต้องดู: ช่วงห่างระหว่างคำขอกับการตอบกลับใน packet capture ฝั่งเซิร์ฟเวอร์ (tcpdump, Wireshark) และตรวจว่าโค้ดเซิร์ฟเวอร์และไคลเอนต์เปิด TCP_NODELAY หรือไม่ - สัญญาณว่าใช่: ปิงต่ำ แต่มีช่วงว่างราว 40 ms (Windows รุ่นเก่า 200 ms) ระหว่างแพ็กเก็ตเล็กซ้ำ ๆ และช่วงว่างนั้นจบทันทีที่ ACK จากอีกฝั่งมาถึง เปิด TCP_NODELAY แล้วหายไป - สัญญาณว่าไม่ใช่: ช่วงห่างการตอบกลับใกล้เคียงปิง: ไม่ใช่สาเหตุนี้ ถ้าเซิร์ฟเวอร์เกมสร้างการตอบกลับช้า น่าจะเป็นฝั่งการประมวลผลของเซิร์ฟเวอร์ (“ข้อความสะสมในคิว”) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · Nagle จะเก็บข้อมูลเล็ก ๆ ไว้ก่อนเมื่อมีข้อมูลที่ยังไม่ได้รับ ACK, ต้องปิดได้เป็นรายการเชื่อมต่อ, delayed ACK น้อยกว่า 0.5 วินาที, ปัญหาเมื่อทั้งสองทำงานประกบกัน - [include/net/tcp.h (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp.h?h=v6.12) · Linux kernel · delayed ACK ของ Linux ต่ำสุด TCP_DELACK_MIN (HZ/25 = 40 ms), สูงสุด TCP_DELACK_MAX (HZ/5 = 200 ms) - [TCP improvements in the Windows network stack (IETF 98 TCPM)](https://datatracker.ietf.org/meeting/98/materials/slides-98-tcpm-tcp-improvements-in-windows-01) · Microsoft · เปลี่ยนไทม์เอาต์ delayed ACK เริ่มต้นของ Windows เป็น 40 ms (ประกาศปี 2017) - [TCP Templates for Windows Server 2019 – How to tune your Windows Server Transports (Advanced users only 😉)](https://techcommunity.microsoft.com/blog/networkingblog/tcp-templates-for-windows-server-2019-8211-how-to-tune-your-windows-server-trans/339795) · Microsoft · เทมเพลต Server 2019: DelayedAckTimeout 40 ms, MaxSynRetransmissions 2, InitialRto 3000 ms - [Design issues - Sending small data segments over TCP with Winsock](https://learn.microsoft.com/en-us/previous-versions/troubleshoot/windows/win32/data-segment-tcp-winsock) · Microsoft · TCP ของ Windows รุ่นเก่าตั้งตัวจับเวลา delayed ACK 200 ms เมื่อได้รับข้อมูล และ Nagle เปิดเป็นค่าเริ่มต้น แพ็กเก็ตเล็กจึงต้องรอ ACK, แก้ด้วย TCP_NODELAY #### sk-block-send · การส่งแบบ blocking เพราะไคลเอนต์ช้า · Blocking send on a full socket ถ้า send buffer ของผู้เล่นคนหนึ่งที่เน็ตช้าเต็ม แล้วเซิร์ฟเวอร์ส่งแบบ blocking (การส่งที่ฟังก์ชันจะไม่คืนค่าจนกว่าบัฟเฟอร์จะมีที่ว่าง) เธรดของเซิร์ฟเวอร์จะต้องรอผู้เล่นคนนั้นคนเดียว - ทำไม → ผลคือ → บนหน้าจอ: send buffer ของไคลเอนต์ที่ช้าเต็ม → เป็นการส่งแบบ blocking เธรดของเซิร์ฟเวอร์จึงรอจนบัฟเฟอร์มีที่ว่าง → ทุกคนที่เธรดนั้นดูแลค้างหรือเป็นสโลว์โมชั่น - อาการ: ค้าง, สโลว์โมชั่น / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ใช้การส่งแบบ non-blocking, จำกัดความยาวคิวส่งของแต่ละไคลเอนต์, ทิ้งอัปเดตที่เก่าแล้ว - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (เวลาต่อทิกของเซิร์ฟเวอร์, Send-Q ต่อการเชื่อมต่อ) - จุดที่ต้องดู: หาการเชื่อมต่อที่ Send-Q (ไบต์ที่ยังไม่ได้รับ ACK หรือยังไม่ได้ส่ง) เต็มเท่าขนาด send buffer ด้วย ss -tn และดู thread dump (stack) ของเซิร์ฟเวอร์เกมตอนที่ทิกพุ่งว่ามีเธรดที่หยุดอยู่ใน send หรือไม่ - สัญญาณว่าใช่: ขณะที่มีการเชื่อมต่อช้าที่ Send-Q เต็ม เธรดที่ดูแลการเชื่อมต่อนั้นหยุดอยู่ใน send และเฉพาะคนที่อยู่ในเธรดเดียวกันเท่านั้นที่หยุดไปด้วย - สัญญาณว่าไม่ใช่: เธรดที่หยุดรออยู่นอก send (ล็อก, การเรียก DB): ให้ดู “การแย่งล็อก”, “synchronous call บนเธรดเกม” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [send(2) — Linux manual page](https://man7.org/linux/man-pages/man2/send.2.html) · Linux man-pages · ถ้า send buffer ไม่มีที่ว่าง send() จะถูกบล็อก ส่วนโหมด non-blocking จะคืนค่า EAGAIN ทันที - [send function (winsock2.h)](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send) · Microsoft · Winsock ก็เช่นกัน ถ้าพื้นที่บัฟเฟอร์ไม่พอ send จะถูกบล็อก เว้นแต่อยู่ในโหมด non-blocking - [net/ipv4/tcp_diag.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_diag.c?h=v6.12) · Linux kernel · ค่า Recv-Q และ Send-Q ของ ss: ซ็อกเก็ตที่ listen อยู่คือจำนวนการเชื่อมต่อที่รอ accept และขีดจำกัด backlog, ซ็อกเก็ตที่เชื่อมต่อแล้วคือไบต์ที่แอปยังไม่ได้อ่าน และไบต์ที่ส่งไปแล้วแต่ยังไม่ได้รับ ACK #### sk-slow-client · นโยบายจัดการไคลเอนต์ที่ช้า (slow consumer) · Slow-consumer policy เมื่อข้อมูลที่ต้องส่งให้ไคลเอนต์ใดกองสะสมเรื่อย ๆ เซิร์ฟเวอร์จะทิ้งอัปเดตเก่าหรือตัดการเชื่อมต่อ - ทำไม → ผลคือ → บนหน้าจอ: เน็ตของไคลเอนต์รับไม่ทันปริมาณที่เซิร์ฟเวอร์ส่ง → เซิร์ฟเวอร์ทิ้งอัปเดตเก่า หรือตัดการเชื่อมต่อเมื่อเกินขีดจำกัด → เฉพาะคนนั้นวาร์ปหรือหลุด - อาการ: วาร์ป, หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ลดปริมาณที่ส่ง (ความถี่อัปเดตตามระยะห่าง), ลดคุณภาพแต่ยังส่งต่อไป, ลดปริมาณข้อมูลที่กองไว้ในเคอร์เนล (TCP_NOTSENT_LOWAT ของ Linux) - ตัวเลขที่ควรรู้: ถ้า send buffer มีขนาด 256 KB บนเน็ต 30 KB/s จะมีข้อมูลกองรอเกิน 8 วินาที และบางครั้ง Linux ยังขยายบัฟเฟอร์นี้เองได้ถึงหลาย MB - บนกราฟ: สูงเฉพาะบางกลุ่ม (Send-Q ต่อการเชื่อมต่อ, จำนวนอัปเดตที่ทิ้งต่อไคลเอนต์) - จุดที่ต้องดู: ความยาวคิวส่ง จำนวนอัปเดตที่ทิ้ง และสาเหตุการหลุดของแต่ละไคลเอนต์ที่เซิร์ฟเวอร์เกมบันทึกไว้ และ Send-Q กับ cwnd ของการเชื่อมต่อนั้นคู่กันจาก ss -tni บนเซิร์ฟเวอร์ - สัญญาณว่าใช่: เฉพาะการเชื่อมต่อของคนที่วาร์ปหรือหลุดเท่านั้นที่ Send-Q เต็มตลอด และ log ของเกมบันทึกว่าทิ้งอัปเดตของคนนั้นหรือตัดการเชื่อมต่อเพราะคิวส่งเกิน - สัญญาณว่าไม่ใช่: Send-Q ว่างแต่ยังวาร์ป: ไม่ใช่ปัญหาฝั่งการส่งของเซิร์ฟเวอร์ ให้ดูแพ็กเก็ตหายบนเน็ตของคนนั้น (“แพ็กเก็ตหายช่วงไร้สาย”) หรือ interpolation บนหน้าจอ - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_wmem: ค่าสูงสุดของ send buffer ที่ปรับอัตโนมัติ ค่าเริ่มต้น 64 KB–4 MB (ตามหน่วยความจำ), tcp_notsent_lowat และ TCP_NOTSENT_LOWAT จำกัดปริมาณข้อมูลที่ยังไม่ได้ส่ง - [net/ipv4/tcp_diag.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_diag.c?h=v6.12) · Linux kernel · ค่า Recv-Q และ Send-Q ของ ss: ซ็อกเก็ตที่ listen อยู่คือจำนวนการเชื่อมต่อที่รอ accept และขีดจำกัด backlog, ซ็อกเก็ตที่เชื่อมต่อแล้วคือไบต์ที่แอปยังไม่ได้อ่าน และไบต์ที่ส่งไปแล้วแต่ยังไม่ได้รับ ACK #### sk-keepalive · keepalive ค่าเริ่มต้น 2 ชั่วโมง · TCP keepalive defaults ถ้าอีกฝั่งหายไปโดยไม่ส่งสัญญาณปิด TCP จะรู้ตัวหลังผ่านไปนานมาก keepalive (ฟีเจอร์ของ TCP ที่ตรวจว่าการเชื่อมต่อที่ idle ยังอยู่หรือไม่) ปิดไว้เป็นค่าเริ่มต้น และต่อให้เปิด ก็ต้อง idle ครบ 2 ชั่วโมงก่อนจึงเริ่มตรวจ - ทำไม → ผลคือ → บนหน้าจอ: ไคลเอนต์หายไปโดยไม่ส่งสัญญาณปิด เพราะเครื่องดับหรือเน็ตขาด → เซิร์ฟเวอร์ถือว่าการเชื่อมต่อยังอยู่ (keepalive ค่าเริ่มต้น 7,200 วินาที ถ้ามีข้อมูลที่กำลังส่งอยู่ จะใช้ประมาณ 15 นาทีกว่าจะเลิกส่งซ้ำ) → เหลือตัวผีของตัวละครอยู่ในเกม และพอเชื่อมต่อใหม่ก็เจอข้อผิดพลาด “ล็อกอินอยู่แล้ว” - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, มองไม่เห็น/ตัวผี / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: หลังอยู่เฉย ๆ สักพัก, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม), อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ตอบ heartbeat และถ้าไม่ได้รับนานเกินกำหนดให้ปิดการเชื่อมต่อเองก่อน (ปรับ TCP_KEEPIDLE และ TCP_USER_TIMEOUT), เมื่อเชื่อมต่อใหม่ให้ใช้ session token แทนที่เซสชันเดิมแล้วเล่นต่อ ไคลเอนต์: ส่ง heartbeat ระดับเกมทุกไม่กี่วินาทีถึงหลายสิบวินาที (ไม่เกินครึ่งหนึ่งของ idle timeout ที่สั้นที่สุด), เชื่อมต่อใหม่อัตโนมัติเมื่อหลุด - งานฝั่งทีมอินฟรา: ลดค่าเริ่มต้นของเคอร์เนล (tcp_keepalive_time เป็นต้น) สำหรับซ็อกเก็ตที่โค้ดไม่ได้กำหนดค่าเอง (มีผลเฉพาะซ็อกเก็ตที่เปิด SO_KEEPALIVE) - ตัวเลขที่ควรรู้: ค่าเริ่มต้นของ Linux จะเริ่มตรวจเมื่อ idle ครบ 7,200 วินาที แล้วส่ง probe 9 ครั้งห่างกันครั้งละ 75 วินาที ถ้าไม่มีการตอบกลับเลยจะตัดการเชื่อมต่อ รวมแล้วประมาณ 2 ชั่วโมง 11 นาที ค่าเริ่มต้นของ Windows ก็ต้อง idle 2 ชั่วโมงก่อนจึงเริ่มตรวจเช่นกัน - บนกราฟ: สูงเฉพาะบางกลุ่ม (เวลาที่ผ่านไปนับจากรับข้อมูลครั้งล่าสุดของแต่ละการเชื่อมต่อ) - จุดที่ต้องดู: lastrcv (ms ที่ผ่านไปนับจากรับข้อมูลครั้งล่าสุด) และตัวจับเวลา keepalive (timer:(keepalive,…)) ของแต่ละการเชื่อมต่อจาก ss -tnoi เทียบกับบันทึกการปฏิเสธ “ล็อกอินอยู่แล้ว” ของเซิร์ฟเวอร์เกม - สัญญาณว่าใช่: ยังมีการเชื่อมต่อ ESTABLISHED ที่ lastrcv นานหลายนาทีถึงหลายชั่วโมง และการเชื่อมต่อใหม่ของบัญชีนั้นถูกปฏิเสธด้วย “ล็อกอินอยู่แล้ว” - สัญญาณว่าไม่ใช่: ไม่มีการเชื่อมต่อที่เงียบไปนาน แต่ยังขึ้น “ล็อกอินอยู่แล้ว”: น่าจะเป็นโค้ดเก็บกวาดเซสชันของเซิร์ฟเวอร์เกม - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · หลัง idle 7,200 วินาที ส่ง probe 9 ครั้งห่างกัน 75 วินาที (เพิ่มอีกประมาณ 11 นาที), มีผลเฉพาะซ็อกเก็ตที่เปิด SO_KEEPALIVE, TCP_KEEPIDLE และ TCP_USER_TIMEOUT - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · keepalive ต้องปิดเป็นค่าเริ่มต้น และช่วง idle เริ่มต้นต้องไม่ต่ำกว่า 2 ชั่วโมง - [SO_KEEPALIVE socket option](https://learn.microsoft.com/en-us/windows/win32/winsock/so-keepalive) · Microsoft · ไทม์เอาต์เริ่มต้นของ TCP keepalive บน Windows คือ 2 ชั่วโมง - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · lastrcv (ms ที่ผ่านไปนับจากรับข้อมูลครั้งล่าสุด) ใน -i, timer:(keepalive,…) ใน -o #### sk-fragment · IP fragmentation ของแพ็กเก็ต UDP · IP fragmentation of large UDP แพ็กเก็ต UDP ที่ใหญ่กว่า MTU (ขนาดสูงสุดที่ส่งได้ในครั้งเดียว) จะถูกแบ่งเป็นชิ้น (fragmentation) ที่ชั้น IP และถ้า fragment หายไปแค่ชิ้นเดียว ทั้งแพ็กเก็ตจะถูกทิ้ง - ทำไม → ผลคือ → บนหน้าจอ: สแนปช็อตในที่ที่คนเยอะมีขนาดเกิน 1,500 ไบต์ → ถูกแบ่งเป็นหลาย fragment ตอนส่ง ถ้าหายแม้แต่ชิ้นเดียวก็ทิ้งทั้งหมด → ยิ่งแพ็กเก็ตใหญ่ อัตราแพ็กเก็ตหายยิ่งสูงขึ้นหลายเท่า วาร์ปเฉพาะในที่ที่คนเยอะ - อาการ: วาร์ป / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: บางจุด/บางแชนแนล, บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แบ่งแพ็กเก็ตเองให้ไม่เกิน 1,200 ไบต์, ส่งเฉพาะส่วนที่เปลี่ยน - ตัวเลขที่ควรรู้: บนเน็ตที่แพ็กเก็ตหาย 2% แพ็กเก็ตที่ถูกแบ่งเป็น 4 fragment จะหายไปประมาณ 8% ไฟร์วอลล์และ ISP บางรายทิ้งแพ็กเก็ตที่ถูกแบ่งชิ้นไปเลย ผู้เล่นกลุ่มนั้นจึงไม่ได้รับแพ็กเก็ตใหญ่เลยสักตัว - บนกราฟ: สูงตามจำนวนคนและโหลด (จำนวน IP fragment (IpFragCreates), ขนาดสแนปช็อต) - จุดที่ต้องดู: ค่าที่เพิ่มขึ้นของ IpFragCreates (จำนวน fragment ที่สร้างตอนส่ง) จาก nstat -az บนเซิร์ฟเวอร์ และ IpReasmFails (จำนวนครั้งที่ประกอบกลับไม่สำเร็จ) ฝั่งรับ ตรวจการกระจายขนาดแพ็กเก็ต UDP จาก log ของเซิร์ฟเวอร์เกมหรือ packet capture - สัญญาณว่าใช่: ในที่ที่คนแห่มารวมกัน IpFragCreates เพิ่มขึ้น มีแพ็กเก็ต UDP ที่ใหญ่เกิน 1,500 ไบต์ และตอนนั้นมีผู้เล่นแจ้งอาการวาร์ปมากขึ้น - สัญญาณว่าไม่ใช่: IpFragCreates ไม่เพิ่ม: ฝั่งที่เซิร์ฟเวอร์ส่งไม่ได้เกิด fragmentation - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 8085: UDP Usage Guidelines](https://www.rfc-editor.org/rfc/rfc8085) · IETF · ถ้า fragment หายหนึ่งชิ้น จะประกอบกลับไม่ได้และเสียทั้งแพ็กเก็ต, แอป UDP ควรเลี่ยง IP fragmentation - [RFC 8900: IP Fragmentation Considered Fragile](https://www.rfc-editor.org/rfc/rfc8900) · IETF · กรณีที่ไฟร์วอลล์และเครือข่ายบางแห่งทิ้ง IP fragment - [net/ipv4/proc.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/proc.c?h=v6.12) · Linux kernel · ชื่อตัวนับที่ nstat แสดง: FragCreates (จำนวน fragment ที่สร้าง) และ ReasmFails (จำนวนครั้งที่ประกอบกลับไม่สำเร็จ) ในกลุ่ม Ip #### sk-reliable-udp · การตั้งค่าการส่งซ้ำของ reliable UDP · Reliable-UDP tuning (KCP, ENet…) ถ้ากฎการส่งซ้ำที่สร้างเองบน UDP ระมัดระวังเกินไป การกู้คืนจะช้า ถ้าดุดันเกินไปก็จะยิ่งทำให้เน็ตอุดตัน - ทำไม → ผลคือ → บนหน้าจอ: ช่วงห่าง จำนวนครั้ง และขนาด window ของการส่งซ้ำไม่เหมาะกับเน็ต → กู้คืนช้า หรือส่งซ้ำซ้อนจนความแออัดแย่ลง → สกิลไม่ออก, กรอเร็ว, แลคหนักขึ้นตอนเครือข่ายแออัด - อาการ: กดไม่ติด/โรลแบ็ค, กรอเร็ว / ปัจจัย: แพ็กเก็ตหาย, ความหน่วง - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ส่งซ้ำตามเวลาไปกลับที่วัดได้, แยกช่องทางตามความสำคัญ ไคลเอนต์: ใช้การตั้งค่าการส่งซ้ำและช่องทางเดียวกับเซิร์ฟเวอร์ - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (อัตราการส่งซ้ำของ reliable UDP, RTT ในเกม) - จุดที่ต้องดู: สถิติต่อการเชื่อมต่อของไลบรารีที่ใช้ (จำนวนการส่งซ้ำ, เวลาไปกลับที่ประมาณ, เวลารอก่อนส่งซ้ำ) บันทึกทั้งฝั่งเซิร์ฟเวอร์และไคลเอนต์ แล้วเทียบกับอัตราแพ็กเก็ตหายจริงบนเน็ตของผู้เล่นคนเดียวกัน (ค่าที่วัดด้วย mtr) - สัญญาณว่าใช่: อัตราการส่งซ้ำสูงกว่าอัตราแพ็กเก็ตหายจริงหลายเท่า: ตั้งค่าดุดันเกินไป เวลารอก่อนส่งซ้ำนานเป็นหลายเท่าของเวลาไปกลับที่วัดได้: ตั้งค่าระมัดระวังเกินไป - สัญญาณว่าไม่ใช่: อัตราการส่งซ้ำใกล้เคียงอัตราแพ็กเก็ตหาย และเวลารอสอดคล้องกับเวลาไปกลับ: ไม่ใช่ปัญหาการตั้งค่า ให้ดูแพ็กเก็ตหายบนเน็ตโดยตรง - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [RFC 8085: UDP Usage Guidelines](https://www.rfc-editor.org/rfc/rfc8085) · IETF · การส่งซ้ำอาจเพิ่มความแออัดจึงต้องอยู่ภายใต้ congestion control, ประมาณเวลาไปกลับจากค่าเฉลี่ยของการวัดหลายครั้ง (EWMA), ค่าเริ่มต้น 1 วินาที, ลดอัตราการส่งเมื่อตัวจับเวลาหมดเวลา #### sk-slowstart · slow start หลัง idle · Slow start after idle เมื่อการเชื่อมต่อ idle ไปสักพัก TCP จะลด congestion window (ปริมาณที่ส่งได้ในครั้งเดียว) กลับลงมา พอต้องส่งข้อมูลก้อนใหญ่ขึ้นมากะทันหันจึงต้องแบ่งส่งหลายรอบ - ทำไม → ผลคือ → บนหน้าจอ: ส่งข้อมูลก้อนใหญ่ผ่านการเชื่อมต่อที่ idle อยู่ เช่น ตอนเข้าเมือง → congestion window ลดลงแล้ว จึงต้องแบ่งส่งเป็นหลายรอบเวลาไปกลับ → หลังเข้าพื้นที่ ตัวละครและ NPC รอบตัวโผล่ช้าไปเท่ากับเวลาไปกลับไม่กี่รอบ (ยิ่งเซิร์ฟเวอร์อยู่ไกลยิ่งเห็นชัด) - อาการ: อินพุตดีเลย์, มองไม่เห็น/ตัวผี / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ, หลังอยู่เฉย ๆ สักพัก - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ลดข้อมูลตอนเข้าพื้นที่ (ส่งสิ่งที่จำเป็นก่อน) - งานฝั่งทีมอินฟรา: ปิด tcp_slow_start_after_idle (Linux, ตั้งค่าทั้งเซิร์ฟเวอร์) - ตัวเลขที่ควรรู้: ถ้า idle นานกว่า RTO congestion window จะเริ่มลดลง และถ้า idle นานมากจะลดลงเหลือประมาณ 14 KB (10 แพ็กเก็ต) ข้อมูล 100 KB จึงส่งครั้งเดียวไม่ได้ ต้องแบ่งส่งใน 3 รอบเวลาไปกลับ - บนกราฟ: สูงเฉพาะบางกลุ่ม (เวลาส่งข้อมูลหลังเข้าพื้นที่ (ผู้เล่นที่ RTT ยาว)) - จุดที่ต้องดู: ค่า sysctl net.ipv4.tcp_slow_start_after_idle และ cwnd (congestion window) ใน ss -ti ของการเชื่อมต่อนั้นว่าเล็กลงหรือไม่ ตอนที่ผู้เล่นเข้าพื้นที่หลัง idle - สัญญาณว่าใช่: ค่าตั้งเป็น 1 (ค่าเริ่มต้น) และตอนเข้าพื้นที่หลัง idle cwnd ลดลงเหลือราว 10 จนการส่งถูกแบ่งเป็นหลายรอบเวลาไปกลับ ผู้เล่นที่ RTT ยาวจะเห็นของโผล่ช้ากว่า และเปลี่ยนเป็น 0 แล้วหายไป - สัญญาณว่าไม่ใช่: cwnd ยังใหญ่อยู่แต่ของยังโผล่ช้า: น่าจะเป็นการจัดการตอนเข้าพื้นที่ฝั่งเซิร์ฟเวอร์ (“spawn ทะลักเมื่อเข้าพื้นที่คนหนาแน่น”) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_slow_start_after_idle เปิดเป็นค่าเริ่มต้น, ถ้า idle นานเท่า RTO จะลด congestion window (วิธีของ RFC 2861) - [RFC 5681: TCP Congestion Control](https://www.rfc-editor.org/rfc/rfc5681) · IETF · ถ้าไม่ได้ส่งข้อมูลนานกว่า RTO จะลด congestion window ให้ไม่เกิน restart window min(IW, cwnd) แล้วเริ่ม slow start ใหม่ - [RFC 6928: Increasing TCP's Initial Window](https://www.rfc-editor.org/rfc/rfc6928) · IETF · initial window 10 segment, สูงสุด 14,600 ไบต์ - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · cwnd (congestion window) และ ssthresh (เกณฑ์ของ slow start) ใน -i #### sk-congestion · อัตราการส่งดิ่งลงเพราะ congestion control · Congestion control backoff TCP มองว่าแพ็กเก็ตหายคือสัญญาณของความแออัด จึงลดอัตราการส่งลง 30–50% และตอบสนองแบบเดียวกันแม้แพ็กเก็ตหายเพราะ Wi-Fi - ทำไม → ผลคือ → บนหน้าจอ: มีแพ็กเก็ตหายเล็กน้อยบน Wi-Fi หรือเน็ตตอนที่มีข้อมูลต้องส่งมาก → TCP ลดอัตราการส่งลงมากแล้วค่อย ๆ ฟื้นตัว (CUBIC ซึ่งเป็นค่าเริ่มต้นของ Linux และ Windows ลด 30%) → ในที่ที่คนเยอะ อัปเดตตามไม่ทัน: กรอเร็ว, อินพุตดีเลย์ - อาการ: กรอเร็ว, อินพุตดีเลย์ / ปัจจัย: ความหน่วง, การหยุดชะงัก - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ลดปริมาณที่ส่ง (AOI, ส่งเฉพาะส่วนที่เปลี่ยน), แบ่งส่งไม่ให้ออกไปรวดเดียว - งานฝั่งทีมอินฟรา: เปลี่ยนไปใช้ congestion control แบบอื่น เช่น BBR (tcp_congestion_control) - บนกราฟ: ค่อย ๆ ขึ้นแล้วดิ่งลง (congestion window (cwnd) และอัตราการส่งต่อการเชื่อมต่อ) - จุดที่ต้องดู: ss -ti ของการเชื่อมต่อของผู้เล่นที่อัปเดตตามไม่ทัน เก็บหลาย ๆ ครั้งเพื่อดูการเปลี่ยนแปลงของ cwnd และ ssthresh และชื่อ congestion control (cubic, bbr) พร้อมดูว่า Send-Q กองสะสมหรือไม่ - สัญญาณว่าใช่: หลังแพ็กเก็ตหาย cwnd ลดฮวบแล้วค่อย ๆ ขึ้นวนซ้ำ และระหว่างที่ลดอยู่ Send-Q กองสะสม ตรงกับเวลาที่มีผู้เล่นแจ้งอาการกรอเร็วและอินพุตดีเลย์ - สัญญาณว่าไม่ใช่: cwnd เหลือเฟือแต่อัปเดตยังตามไม่ทัน: น่าจะเป็น window ฝั่งรับ (“zero window (การหยุดที่ดูเหมือนการส่งซ้ำ)”) หรือฝั่งการส่งของเซิร์ฟเวอร์ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 9438: CUBIC for Fast and Long-Distance Networks](https://www.rfc-editor.org/rfc/rfc9438) · IETF · เมื่อแพ็กเก็ตหาย CUBIC ลด window เหลือ 0.7 เท่า (ลด 30%) ส่วน Reno เหลือ 0.5 เท่า, CUBIC เป็นค่าเริ่มต้นของ Linux, Windows และ Apple - [TCP BBR congestion control comes to GCP – your Internet just got faster](https://cloud.google.com/blog/products/networking/tcp-bbr-congestion-control-comes-to-gcp-your-internet-just-got-faster) · Google Cloud · congestion control ที่อิงการหายของแพ็กเก็ตจะลดอัตราการส่งลงมาก แม้แพ็กเก็ตจะหายด้วยเหตุอื่นนอกจากความแออัด, BBR ตัดสินจากอัตราการส่งถึงปลายทาง (delivery rate) และ RTT - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_congestion_control ใช้เลือกอัลกอริทึม congestion control ของการเชื่อมต่อใหม่ - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · cwnd, ssthresh และชื่ออัลกอริทึม congestion control ใน -i - [net/ipv4/tcp_diag.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_diag.c?h=v6.12) · Linux kernel · ค่า Recv-Q และ Send-Q ของ ss: ซ็อกเก็ตที่ listen อยู่คือจำนวนการเชื่อมต่อที่รอ accept และขีดจำกัด backlog, ซ็อกเก็ตที่เชื่อมต่อแล้วคือไบต์ที่แอปยังไม่ได้อ่าน และไบต์ที่ส่งไปแล้วแต่ยังไม่ได้รับ ACK #### sk-linger · ข้อมูลสุดท้ายหายเพราะปิดการเชื่อมต่อแบบบังคับ (RST) · SO_LINGER, abrupt RST ถ้าเซิร์ฟเวอร์ตัดการเชื่อมต่อแบบกะทันหัน ข้อความแจ้งสุดท้ายหรือสัญญาณว่าบันทึกข้อมูลเสร็จที่ส่งออกไปจะหายไป - ทำไม → ผลคือ → บนหน้าจอ: เซิร์ฟเวอร์ปิดการเชื่อมต่อแบบบังคับ (RST) เกิดเมื่อตั้ง SO_LINGER เป็น 0 วินาที หรือปิดทั้งที่ยังอ่านข้อมูลที่รับมาไม่หมด → เหตุผลที่ถูกเตะออก (kick) และข้อมูลสุดท้ายที่ยังส่งไม่ถึงถูกทิ้ง → ขึ้นข้อความ “การเชื่อมต่อถูกตัดเนื่องจากข้อผิดพลาดที่ไม่ทราบสาเหตุ” โดยไม่รู้เหตุผล - อาการ: หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ส่งเหตุผลแล้วปิดเฉพาะทิศทางส่งก่อน (shutdown) อ่านข้อมูลที่รับเข้ามาจนอีกฝั่งปิด แล้วจึงปิดซ็อกเก็ต, เลี่ยง SO_LINGER 0 วินาที - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (จำนวนการเชื่อมต่อที่จบด้วย RST) - จุดที่ต้องดู: ค่าที่เพิ่มขึ้นของ TcpExtTCPAbortOnData (ปิดด้วย RST ขณะที่ยังมีข้อมูลรอส่ง, SO_LINGER 0 วินาที) และ TcpExtTCPAbortOnClose (ปิดขณะที่ยังมีข้อมูลที่ยังไม่ได้อ่าน) จาก nstat -az และดูใน packet capture ฝั่งเซิร์ฟเวอร์ตอนที่หลุดว่าส่ง RST ออกไปแทน FIN หรือไม่ - สัญญาณว่าใช่: ตรงกับเวลาที่ผู้เล่นแจ้งว่าขึ้น “การเชื่อมต่อถูกตัดเนื่องจากข้อผิดพลาดที่ไม่ทราบสาเหตุ” เซิร์ฟเวอร์ส่ง RST และ AbortOnData กับ AbortOnClose เพิ่มขึ้น - สัญญาณว่าไม่ใช่: เซิร์ฟเวอร์ปิดตามปกติด้วย FIN แต่ผู้เล่นไม่เห็นเหตุผล: น่าจะเป็นการจัดการตอนปิดของไคลเอนต์ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [closesocket function (winsock.h)](https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-closesocket) · Microsoft · ถ้าเปิด SO_LINGER และตั้งเวลาเป็น 0 การปิดจะกลายเป็นการปิดแบบบังคับที่รีเซ็ตการเชื่อมต่อทันที และข้อมูลที่ยังส่งไม่ออกจะหายไป - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · ถ้าปิดขณะที่ยังมีข้อมูลที่รับมาแต่ยังไม่ได้อ่าน จะส่ง RST เพื่อแจ้งว่าข้อมูลสูญหาย - [Graceful Shutdown, Linger Options, and Socket Closure](https://learn.microsoft.com/en-us/windows/win32/winsock/graceful-shutdown-linger-options-and-socket-closure-2) · Microsoft · ลำดับคือใช้ shutdown ปิดเฉพาะฝั่งส่งก่อน แล้วปิดซ็อกเก็ตหลังได้รับการแจ้งปิดจากอีกฝั่ง - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtTCPAbortOnData: ปิดด้วย RST ขณะที่ยังมีข้อมูลรอส่ง (เช่น SO_LINGER 0 วินาที), TcpExtTCPAbortOnClose: ปิดขณะที่ยังมีข้อมูลที่ยังไม่ได้อ่านจึงส่ง RST #### sk-blocking-io · โครงสร้างแบบ blocking I/O · Blocking I/O model โครงสร้างที่เธรดทำอย่างอื่นไม่ได้ระหว่างรอซ็อกเก็ตตัวเดียว จะทำให้ทั้งระบบช้าลงเมื่อคนเพิ่มขึ้น - ทำไม → ผลคือ → บนหน้าจอ: ใช้วิธีที่เธรดรอการอ่านและเขียนของแต่ละการเชื่อมต่อ → ดีเลย์ของการเชื่อมต่อหนึ่งลามไปยังการเชื่อมต่ออื่นในเธรดเดียวกัน → ยิ่งผู้เล่นออนไลน์เพิ่มขึ้น ทุกคนยิ่งเจอสโลว์โมชั่นและอินพุตดีเลย์ - อาการ: สโลว์โมชั่น, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เปลี่ยนไปใช้ async I/O ที่ใช้ epoll, IOCP หรือ io_uring - บนกราฟ: สูงตามจำนวนคนและโหลด (เวลาตอบสนอง, จำนวนเธรด) - จุดที่ต้องดู: จำนวนเธรดของเซิร์ฟเวอร์เกมและ voluntary context switch ของแต่ละเธรด (cswch/s คือจำนวนครั้งที่หยุดรอทรัพยากร) จาก pidstat -w -t เทียบกับเวลาตอบสนองเมื่อผู้เล่นออนไลน์เพิ่มขึ้น - สัญญาณว่าใช่: ยิ่งผู้เล่นออนไลน์เพิ่มขึ้น เวลาตอบสนองยิ่งพุ่งชัน และเธรดส่วนใหญ่ที่เพิ่มขึ้นตามจำนวนการเชื่อมต่อมีแต่ voluntary switch สูง แทบไม่ใช้ CPU (รอซ็อกเก็ต) - สัญญาณว่าไม่ใช่: เธรดใช้ CPU ต่อเนื่องโดยไม่ได้รอ: น่าจะเป็นการคำนวณเกินกำลัง (“ทิกเกินงบเวลา”) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [epoll(7) — Linux manual page](https://man7.org/linux/man-pages/man7/epoll.7.html) · Linux man-pages · การแจ้งเหตุการณ์ I/O ที่ขยายให้เฝ้าดู fd จำนวนมากพร้อมกันได้ - [I/O Completion Ports](https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports) · Microsoft · แนวทางของ Windows ที่จัดการ async I/O จำนวนมากด้วย thread pool ที่สร้างไว้ล่วงหน้า - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · cswch/s ของ -w: voluntary context switch ที่เธรดหยุดเองเพื่อรอทรัพยากร, -t แสดงแยกตามเธรด #### sk-reuseport · การกระจายของ SO_REUSEPORT ไม่สมดุล · SO_REUSEPORT imbalance, stuck worker เมื่อหลายโปรเซสแบ่งกันรับพอร์ตเดียวกัน เคอร์เนลจะกำหนดโปรเซสที่รับแต่ละการเชื่อมต่อด้วย hash ของที่อยู่ และไม่เปลี่ยนอีก ถ้าโปรเซสตัวใดหยุด เฉพาะคนที่ถูกจัดให้โปรเซสนั้นจะต้องรอ - ทำไม → ผลคือ → บนหน้าจอ: เกตเวย์หรือเซิร์ฟเวอร์ล็อกอินรันหลายโปรเซสด้วย SO_REUSEPORT → แม้โปรเซสหนึ่งหยุดเพราะ GC หรือโหลดเกิน การเชื่อมต่อใหม่และแพ็กเก็ต UDP ที่ถูกจัดให้โปรเซสนั้นก็ไม่ย้ายไปโปรเซสอื่น → บางคนเท่านั้นที่เข้าเกมไม่ได้หรือค้าง ตอนรีสตาร์ตที่จำนวนโปรเซสเปลี่ยน เซสชัน UDP บางส่วนจะหลุด - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, ค้าง, หลุด / ปัจจัย: การหยุดชะงัก, แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: อย่าให้เธรดรับหยุดเด็ดขาด, ทำขั้นตอนส่งต่อเซสชันตอนรีสตาร์ต - งานฝั่งทีมอินฟรา: มอนิเตอร์คิวรอเชื่อมต่อของแต่ละโปรเซส (Recv-Q ของ ss), ถ้า deploy แล้วจำนวนโปรเซสเปลี่ยน ให้ทำตามขั้นตอนส่งต่อเซสชัน - บนกราฟ: สูงเฉพาะบางกลุ่ม (คิวรอเชื่อมต่อ (Recv-Q) ของแต่ละซ็อกเก็ตที่ listen) - จุดที่ต้องดู: Recv-Q (จำนวนการเชื่อมต่อที่รอ accept) และโปรเซสเจ้าของของแต่ละซ็อกเก็ตที่ listen บนพอร์ตเดียวกันจาก ss -ltnp และเทียบ throughput ของแต่ละโปรเซส - สัญญาณว่าใช่: ในซ็อกเก็ตหลายตัวบนพอร์ตเดียวกัน มีตัวเดียวที่ Recv-Q สะสมเรื่อย ๆ และโปรเซสนั้นหยุดอยู่หรือ throughput ใกล้ 0 - สัญญาณว่าไม่ใช่: Recv-Q ของทุกซ็อกเก็ตสะสมเท่า ๆ กัน: น่าจะเป็นโหลดเกินทั้งระบบ (“คิวรอเชื่อมต่อ (backlog) ล้น”) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [socket(7) — Linux manual page](https://man7.org/linux/man-pages/man7/socket.7.html) · Linux man-pages · SO_REUSEPORT ให้ซ็อกเก็ตหลายตัว bind ที่อยู่เดียวกันและแบ่งกันรับการเชื่อมต่อ TCP และแพ็กเก็ต UDP - [net/core/sock_reuseport.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/core/sock_reuseport.c?h=v6.12) · Linux kernel · ถ้าไม่มีโปรแกรม BPF จะนำค่า hash ของแพ็กเก็ตมาแบ่งตามจำนวนซ็อกเก็ตในกลุ่มเพื่อเลือกซ็อกเก็ตที่รับ - [Why does one NGINX worker take all the load?](https://blog.cloudflare.com/the-sad-state-of-linux-socket-balancing/) · Cloudflare · SO_REUSEPORT แบ่งคิวให้ worker แต่ละตัวด้วย hash แบบง่าย ถ้า worker ตัวหนึ่งติดขัด การเชื่อมต่อทั้งหมดที่กองอยู่ในคิวนั้นจะหยุดหมด - [net/ipv4/tcp_diag.c (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_diag.c?h=v6.12) · Linux kernel · ค่า Recv-Q และ Send-Q ของ ss: ซ็อกเก็ตที่ listen อยู่คือจำนวนการเชื่อมต่อที่รอ accept และขีดจำกัด backlog, ซ็อกเก็ตที่เชื่อมต่อแล้วคือไบต์ที่แอปยังไม่ได้อ่าน และไบต์ที่ส่งไปแล้วแต่ยังไม่ได้รับ ACK #### sk-udp-connreset · ข้อผิดพลาด WSAECONNRESET บนซ็อกเก็ต UDP ของ Windows · WSAECONNRESET on a Windows UDP socket ถ้าเซิร์ฟเวอร์ Windows ส่ง UDP ไปหาไคลเอนต์ที่ออกไปแล้ว จะได้ข้อความแจ้ง “ไม่พบพอร์ต” (ICMP) กลับมา ข้อความนั้นทำให้การเรียกรับครั้งถัดไปจบด้วยข้อผิดพลาด และถ้าโค้ดเซิร์ฟเวอร์ถือว่าข้อผิดพลาดนี้คือซ็อกเก็ตเสีย ทุกคนที่ใช้ซ็อกเก็ตนั้นจะได้รับผลกระทบ - ทำไม → ผลคือ → บนหน้าจอ: ยังส่ง UDP ไปยังที่อยู่ของไคลเอนต์ที่เพิ่งออกไปเรื่อย ๆ จนได้ข้อความแจ้ง “ไม่พบพอร์ต” (ICMP) กลับมา → Windows ทำให้การเรียกรับครั้งถัดไปจบด้วยข้อผิดพลาด WSAECONNRESET (10054) แล้วโค้ดเซิร์ฟเวอร์หยุดรับหรือปิดซ็อกเก็ต → ทุกคนที่ใช้ซ็อกเก็ตนั้นค้างหรือหลุดพร้อมกัน - อาการ: หลุด, ค้าง / ปัจจัย: แพ็กเก็ตหาย, การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ปิด SIO_UDP_CONNRESET ด้วย WSAIoctl เพื่อไม่ให้รับข้อความแจ้งนี้, ถ้าเกิดข้อผิดพลาดตอนรับให้บันทึก log ไว้แล้วรับต่อไป - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนการเชื่อมต่อ, log ข้อผิดพลาดตอนรับ) - จุดที่ต้องดู: รหัสข้อผิดพลาดตอนรับ UDP (WSAECONNRESET, 10054) และบันทึกว่าลูปรับหยุดหรือซ็อกเก็ตถูกปิดใน log ของเซิร์ฟเวอร์เกม และดูใน packet capture ฝั่งเซิร์ฟเวอร์ว่ามี ICMP Port Unreachable มาถึงก่อนหน้านั้นหรือไม่ - สัญญาณว่าใช่: มีบันทึกข้อผิดพลาดตอนรับ WSAECONNRESET ก่อนการเชื่อมต่อหลุดพร้อมกัน และก่อนหน้านั้นมี ICMP Port Unreachable มาจากที่อยู่ของไคลเอนต์ที่เพิ่งออกไป - สัญญาณว่าไม่ใช่: เซิร์ฟเวอร์ Linux หรือโค้ดที่ปิด SIO_UDP_CONNRESET แล้ว: ไม่เกี่ยว - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Winsock IOCTLs](https://learn.microsoft.com/en-us/windows/win32/winsock/winsock-ioctls) · Microsoft · SIO_UDP_CONNRESET ใช้เปิดหรือปิดการรายงานข้อความแจ้ง “ไม่พบพอร์ต” (PORT_UNREACHABLE) ของ UDP - [recvfrom function (winsock.h)](https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom) · Microsoft · WSAECONNRESET บนซ็อกเก็ต UDP หมายความว่าการส่งครั้งก่อนได้รับ ICMP Port Unreachable กลับมา - [Windows Sockets Error Codes](https://learn.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2) · Microsoft · หมายเลขข้อผิดพลาดของ WSAECONNRESET คือ 10054 ### L9 โปรเซสเกมฝั่งเซิร์ฟเวอร์ (18 สาเหตุ) #### sp-tick-overrun · ทิกเกินงบเวลา · Tick overrun ถ้างานในหนึ่งทิกเกินงบเวลา รอบทิกของเซิร์ฟเวอร์จะยืดออก และทั้งพื้นที่จะช้าลงหรือกระตุก - ทำไม → ผลคือ → บนหน้าจอ: งานในหนึ่งทิก (เช่น 50 ms) เกินงบเวลา → สถานะเกมที่ควรคำนวณ 20 ครั้งใน 1 วินาที ถูกคำนวณแค่ 8 ครั้ง → ทั้งพื้นที่เป็นสโลว์โมชั่น (แล้วแต่การออกแบบเซิร์ฟเวอร์ อาจเป็นอาการกระตุก), สกิลตอบสนองช้า - อาการ: สโลว์โมชั่น, อินพุตดีเลย์, กระตุก / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ลดการคำนวณที่หนัก, แบ่งงานในทิกไปหลายเธรด, กระจายผู้เล่น (แชนแนล), เก็บเวลาประมวลผลต่อทิกเป็นเมตริก - งานฝั่งทีมอินฟรา: เพิ่มเวลาต่อทิกและอัตราการใช้ CPU แยกตามคอร์ในการมอนิเตอร์และ alert, พิจารณา CPU หรืออินสแตนซ์ที่ประสิทธิภาพคอร์เดียว (คล็อก) สูง - ตัวเลขที่ควรรู้: งบเวลาของเซิร์ฟเวอร์ 20 ทิกคือ 50 ms, 30 ทิกคือ 33 ms และ 60 ทิกคือ 16.7 ms เพื่อรับมือตอนคนแห่เข้ามากะทันหัน ตามปกติควรใช้แค่ราวครึ่งหนึ่งของงบและเผื่อที่เหลือไว้ จะปลอดภัยกว่า - บนกราฟ: สูงตามจำนวนคนและโหลด (เวลาต่อทิกของเซิร์ฟเวอร์, จำนวนผู้เล่นแยกตามโซน/แชนแนล, CPU ของเธรดเกม) - จุดที่ต้องดู: เวลาประมวลผลต่อทิก (p99) และจำนวนครั้งที่ทิกเกินงบที่เซิร์ฟเวอร์บันทึก วางบนกราฟเดียวกับจำนวนผู้เล่นแยกตามโซน/แชนแนล ถ้าไม่มีเมตริกทิก ให้ดู CPU ของเธรดเกมตัวเดียวด้วย pidstat -t 1 - สัญญาณว่าใช่: ช่วงที่คนรวมตัวกัน เวลาต่อทิกเกินงบ (50 ms ที่ 20 ทิก) และระหว่างนั้น CPU ของเธรดเกมอยู่ใกล้ 100% - สัญญาณว่าไม่ใช่: ทิกเกินงบแต่ CPU ของเธรดเกมต่ำ: น่าจะเป็นสาเหตุฝั่งการรอ (GC pause, ล็อก, synchronous call) ถ้า run queue latency จาก bcc runqlat สูง แปลว่าเธรดไม่ได้รับ CPU: น่าจะเป็น CPU ไม่พอหรือเธรดมากเกินไป - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: ทิกที่ช้าจะออกมาในลักษณะไหนขึ้นอยู่กับการออกแบบเซิร์ฟเวอร์ เซิร์ฟเวอร์ที่เดินสถานะเกมไปทีละช่วงเวลาคงที่ทุกทิก (เช่น 50 ms) จะทำให้เวลาในเกมเองช้าลงจนเป็นสโลว์โมชั่น ส่วนเซิร์ฟเวอร์ที่ขยับทุกอย่างตามเวลาที่ผ่านไปจริงในครั้งเดียว จะรักษาความเร็วของเกมไว้ได้ แต่แพ็กเก็ตจะมาห่างและแต่ละครั้งขยับไกล จึงเห็นเป็นอาการกระตุกหรือวาร์ป ไม่ว่าแบบไหน การตอบสนองต่ออินพุตก็ช้าลง ถ้าเธรดเกมตัวเดียวดูแลทั้งเซิร์ฟเวอร์ ทั้งเซิร์ฟเวอร์จะช้า ถ้าแยกเธรดตามพื้นที่ พื้นที่นั้นจะช้า บางเกม เช่น EVE Online จงใจทำให้เวลาในเกมช้าลงได้สูงสุด 10 เท่าในศึกใหญ่ (Time Dilation) เพื่อให้การคำนวณตามทัน - กรณีจริง: eve-hedgp-2014 - แหล่งอ้างอิง: - [VALORANT's 128-Tick Servers](https://www.riotgames.com/en/news/valorants-128-tick-servers) · Riot Games · เซิร์ฟเวอร์ 128 ทิกต้องจบหนึ่งเฟรมภายใน 7.8125 ms, วัดเวลาเฟรมของเซิร์ฟเวอร์แยกตามระบบย่อย และแบ่งงบเวลาให้แต่ละระบบย่อยดูแล - [HED-GP Technical Retrospective: What a HED-ache](https://www.eveonline.com/news/view/what-a-hed-ache) · CCP Games · EVE Online ใช้ Time Dilation ทำให้เวลาในเกมช้าลงเมื่อโหลดเกิน โดยต่ำสุดที่ 10% (ช้าลง 10 เท่า), ปกติ CPU ของโหนดอยู่ต่ำกว่า 80% - [Handling variation in time](https://docs.unity3d.com/Manual/time-handling-variations.html) · Unity · เมื่อการจำลองแบบช่วงเวลาคงที่ (fixed step) ตามไม่ทัน จะรันหลายขั้นติดกันรวดเดียวเพื่อไล่ให้ทัน และทิ้งเวลาส่วนที่เกินขีดจำกัด เวลาในเกมจึงเดินช้ากว่าเวลาจริง - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -t แสดงสถิติแยกตามเธรดของโปรเซส (อัตราการใช้ CPU เป็นต้น) ไปด้วย - [Demonstrations of runqlat, the Linux eBPF/bcc version](https://raw.githubusercontent.com/iovisor/bcc/master/tools/runqlat_example.txt) · IO Visor · แสดง run queue latency ของ scheduler (เวลาที่งานรอจนได้รับ CPU) เป็นฮิสโทแกรม #### sp-aoi · การคำนวณระยะมองเห็น (AOI) พุ่ง (N²) · Area-of-interest explosion ถ้าเปรียบเทียบทุกคนกับทุกคนเพื่อหาว่าใครมองเห็นใคร เมื่อจำนวนคนเพิ่มเป็น 10 เท่า การคำนวณจะเพิ่มเป็น 100 เท่า - ทำไม → ผลคือ → บนหน้าจอ: เทียบระยะห่างระหว่างตัวละครทุกตัว หรือแม้แบ่งเป็นตาราง (grid) แล้วก็ยังมีคนหลายร้อยคนกระจุกรอบเซลล์เดียว → 100 คนเทียบประมาณ 1 หมื่นครั้ง 1,000 คนเทียบประมาณ 1 ล้านครั้ง → ในที่ที่คนกระจุกตัว เช่น เวิลด์บอสหรือศึกชิงปราสาท ทิกพุ่ง: สโลว์โมชั่น, กระตุก - อาการ: สโลว์โมชั่น, กระตุก / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แบ่งเป็นตารางหรือเขตแล้วเทียบเฉพาะที่อยู่ใกล้, อัปเดตเป้าหมายที่อยู่ไกลให้ห่างขึ้น, จำกัดจำนวนคนที่ผู้เล่นหนึ่งคนมองเห็น - ตัวเลขที่ควรรู้: ถ้าคิดการเทียบระยะและการอัปเดตรายการว่าเห็นหรือไม่เห็นรวมกันเป็น 0.1 µs (หนึ่งในสิบล้านวินาที) ต่อหนึ่งคู่ 1,000 คน (ประมาณ 1 ล้านคู่) จะใช้ 100 ms ต่อทิก เป็นสองเท่าของงบเวลาที่ 20 ทิก (50 ms) - บนกราฟ: สูงตามจำนวนคนและโหลด (เวลาต่อทิกของเซิร์ฟเวอร์, จำนวนคนที่รวมตัวในจุดเดียว) - จุดที่ต้องดู: จำนวนผู้เล่นและเวลาต่อทิกแยกตามโซน/แชนแนลบนกราฟเดียวกัน และเวลาที่ใช้คำนวณระยะมองเห็นในทิกซึ่งวัดแยกไว้ ถ้าไม่ได้วัดแยก ให้ดูสัดส่วน CPU แยกตามฟังก์ชันของโปรเซสเกมด้วย perf top -p - สัญญาณว่าใช่: เมื่อจำนวนคนในจุดเดียวเพิ่มเป็น 2 เท่า เวลาต่อทิกเพิ่มเกือบ 4 เท่า และฟังก์ชันคำนวณระยะมองเห็นและระยะห่างกินเวลา CPU ส่วนใหญ่ - สัญญาณว่าไม่ใช่: เวลาต่อทิกเพิ่มเป็นสัดส่วนตรงกับจำนวนคน หรือฟังก์ชันส่งข้อมูลและ serialization กินสัดส่วนมาก: น่าจะเป็นปริมาณ broadcast พุ่ง หรือต้นทุนของ serialization และการบีบอัด - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Comparing Interest Management Algorithms for Massively Multiplayer Games](https://www.sable.mcgill.ca/~clump/papers/boulanger-06-comparing.pdf) · ACM · เปเปอร์ NetGames 2006 (ฉบับที่ผู้เขียนเผยแพร่เอง) วิธีวัดระยะทุกคู่รับไม่ไหวเมื่อจำนวนคนเพิ่มขึ้น และถ้าแบ่งเป็นตารางสี่เหลี่ยมจัตุรัส จะตรวจเฉพาะ 9 เซลล์โดยรอบ - [Replication Graph in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/replication-graph-in-unreal-engine) · Epic Games · วิธีพื้นฐานที่ไล่ตรวจทุกการเชื่อมต่อสำหรับ actor แต่ละตัว จะเป็นคอขวด CPU ของเซิร์ฟเวอร์เมื่อผู้เล่นและ actor มีจำนวนมาก, MMORPG และเกมลักษณะเดียวกันแบ่งโลกเป็นตารางและใช้รายการของแต่ละเซลล์ซ้ำ - [perf-top(1) — Linux manual page](https://man7.org/linux/man-pages/man1/perf-top.1.html) · perf · แสดงสัดส่วนการใช้ CPU ของโปรเซส (-p) หรือเธรด (-t) ที่กำลังรันแยกตามฟังก์ชัน (symbol) แบบเรียลไทม์ #### sp-broadcast · ปริมาณ broadcast พุ่ง · Broadcast fan-out (N×N) ถ้าส่งการเคลื่อนไหวของผู้เล่นหนึ่งคนไปให้ทุกคนที่มองเห็น จะเกิดอัปเดตที่ต้องส่งเท่ากับจำนวนคนที่รวมตัวยกกำลังสอง - ทำไม → ผลคือ → บนหน้าจอ: ส่งการเปลี่ยนแปลงของผู้เล่นหนึ่งคนไปให้ทุกคนที่มองเห็น → ถ้า 1,000 คนมองเห็นกันทั้งหมด จะมีอัปเดต 1 ล้านรายการทุกทิก → คิวส่งและแบนด์วิดท์เต็ม เกิดดีเลย์และแพ็กเก็ตหาย (อินพุตดีเลย์, กรอเร็ว, วาร์ป) - อาการ: อินพุตดีเลย์, วาร์ป, กรอเร็ว / ปัจจัย: ความหน่วง, แพ็กเก็ตหาย - ใครเจอ: บางจุด/บางแชนแนล, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ลดความถี่อัปเดตตามระยะห่างและความสำคัญ (ศัตรูใกล้ตัวทุกทิก คนที่อยู่ไกลไม่กี่ครั้งต่อวินาที), จำกัดปริมาณที่ส่งให้แต่ละคนแล้วเติมจากสิ่งที่สำคัญก่อน, รวมหลายอัปเดตไว้ในแพ็กเก็ตเดียว, จำกัดจำนวนคนที่แสดง - งานฝั่งทีมอินฟรา: ตั้ง alert โดยเทียบแบนด์วิดท์ขาออกและจำนวนแพ็กเก็ตต่อวินาทีของแต่ละเซิร์ฟเวอร์กับขีดจำกัดเครือข่ายของ NIC และอินสแตนซ์, ตรวจว่ายังเหลือที่ว่างพอก่อนอีเวนต์ใหญ่ - ตัวเลขที่ควรรู้: 1,000 คน × 1,000 คน × 20 ทิก = 20 ล้านรายการต่อวินาที ถ้ารายการละ 40 ไบต์ ทั้งเซิร์ฟเวอร์จะใช้ประมาณ 6.4 Gbps และผู้รับแต่ละคนประมาณ 6.4 Mbps ถ้าจำกัดจำนวนคนที่มองเห็นไว้ 150 คน จะเหลือทั้งหมดประมาณ 1 Gbps และคนละประมาณ 1 Mbps - บนกราฟ: สูงตามจำนวนคนและโหลด (แพ็กเก็ตและไบต์ขาออกของเซิร์ฟเวอร์, จำนวนคนที่รวมตัวในจุดเดียว) - จุดที่ต้องดู: txpck/s และ txkB/s (แพ็กเก็ตและ KB ที่ NIC ของเซิร์ฟเวอร์ส่งต่อวินาที) จาก sar -n DEV 1 ดูคู่กับกราฟจำนวนผู้เล่น สำหรับอินสแตนซ์บนคลาวด์ ดูตัวนับที่เกินขีดจำกัดใน ethtool -S (AWS ENA คือ bw_out_allowance_exceeded และ pps_allowance_exceeded) - สัญญาณว่าใช่: เมื่อคนรวมตัวมากขึ้น แพ็กเก็ตและไบต์ขาออกเพิ่มเร็วกว่าจำนวนคน (เกือบเท่ากำลังสอง) และตั้งแต่ตอนที่ชนขีดจำกัด ตัวนับที่เกินขีดจำกัดหรือการทิ้งแพ็กเก็ตขาออกจะเพิ่มขึ้น - สัญญาณว่าไม่ใช่: ปริมาณขาออกไม่เปลี่ยนแต่เวลาต่อทิกเพิ่มอย่างเดียว: น่าจะเป็นการคำนวณระยะมองเห็นหรือลอจิกของเกม - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - กรณีจริง: eve-hedgp-2014 - แหล่งอ้างอิง: - [HED-GP Technical Retrospective: What a HED-ache](https://www.eveonline.com/news/view/what-a-hed-ache) · CCP Games · การส่งแบบ O(n²) ที่การกระทำของ n คนต้องให้ n คนเห็น เป็นปัจจัยจำกัดที่เลี่ยงไม่ได้ในศึกกองยานขนาดใหญ่ - [Actor Priority in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-priority-in-unreal-engine) · Epic Games · เมื่อแบนด์วิดท์ของการเชื่อมต่อเต็ม จะให้ลำดับความสำคัญกับ actor แต่ละตัว (ระยะห่าง, แนวสายตา, เวลาที่ผ่านไปนับจากส่งครั้งล่าสุด) แล้วแบ่งแบนด์วิดท์ให้ตัวที่สำคัญก่อน - [Detailed Actor Replication Flow in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/detailed-actor-replication-flow-in-unreal-engine) · Epic Games · NetUpdateFrequency กำหนดความถี่อัปเดตของ actor แต่ละตัว และส่งตามลำดับความสำคัญ เมื่อการเชื่อมต่อเต็มแล้ว ที่เหลือจะเลื่อนไปทิกถัดไป - [sar(1) — Linux manual page](https://man7.org/linux/man-pages/man1/sar.1.html) · sysstat · rxpck/s และ txpck/s (แพ็กเก็ตที่รับและส่งต่อวินาที), rxkB/s และ txkB/s (KB ที่รับและส่งต่อวินาที) ใน sar -n DEV - [Monitor network performance for ENA settings on your EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html) · AWS · bw_out_allowance_exceeded (เกินขีดจำกัดแบนด์วิดท์ขาออก) และ pps_allowance_exceeded (เกินขีดจำกัด PPS): จำนวนแพ็กเก็ตที่ถูกพักไว้ในคิวหรือถูกทิ้ง #### sp-hotzone · พื้นที่ที่รันบนเธรดเดียวโหลดเกิน (hotspot) · Single-threaded hot zone ในโครงสร้างที่แต่ละพื้นที่มีเธรดดูแลตัวเดียว ถ้าคนแห่ไปที่จุดเดียว คอร์ตัวนั้นตัวเดียวจะขึ้นไป 100% - ทำไม → ผลคือ → บนหน้าจอ: เธรดหนึ่งตัวดูแลหนึ่งพื้นที่ (แชนแนล) → ถ้าคนแห่ไปที่จุดเดียว คอร์นั้นเต็มอยู่ตัวเดียว ส่วนคอร์อื่นยังว่าง → แลคเฉพาะพื้นที่นั้น พื้นที่อื่นปกติ - อาการ: สโลว์โมชั่น, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: กระจายผู้เล่นไปหลายแชนแนล, ทำงานภายในพื้นที่แบบขนาน, จำกัดจำนวนผู้เล่น - งานฝั่งทีมอินฟรา: เพิ่มอัตราการใช้ CPU แยกตามคอร์ในการมอนิเตอร์และ alert (คอร์เดียวที่เต็มจะจมหายในค่าเฉลี่ยของทั้งเซิร์ฟเวอร์) - ตัวเลขที่ควรรู้: บนเซิร์ฟเวอร์ 16 คอร์ ต่อให้คอร์หนึ่งอยู่ที่ 100% อัตราการใช้ CPU รวมของเซิร์ฟเวอร์ก็ดูเหมือนแค่ประมาณ 6% ต้องดูอัตราการใช้แยกตามคอร์จึงจะเจอ - บนกราฟ: สูงตามจำนวนคนและโหลด (อัตราการใช้ CPU แยกตามคอร์, CPU แยกตามเธรด) - จุดที่ต้องดู: อัตราการใช้แยกตามคอร์จาก mpstat -P ALL 1 และ CPU แยกตามเธรดของโปรเซสเกมจาก pidstat -t 1 เทียบกับจำนวนผู้เล่นในโซนที่เธรดที่ยุ่งที่สุดดูแล - สัญญาณว่าใช่: CPU รวมของเซิร์ฟเวอร์ต่ำ แต่มีเธรดเดียว (คอร์เดียว) อยู่ใกล้ 100% และตอนนั้นคนกระจุกอยู่ในโซนที่เธรดนั้นดูแล - สัญญาณว่าไม่ใช่: หลายคอร์สูงพอ ๆ กัน: ทั้งเซิร์ฟเวอร์โหลดเกิน ถ้า %soft (การประมวลผลแพ็กเก็ตขาเข้า) สูงแค่คอร์เดียว: น่าจะเป็นอินเทอร์รัปต์ของ NIC กระจุกที่คอร์เดียว - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: พื้นที่อื่นจะยังปกติก็ต่อเมื่อแต่ละพื้นที่รันทิกแยกกัน ถ้าเป็นโครงสร้างที่เธรดของหลายพื้นที่รอกันทุกทิกแล้วขยับไปทิกถัดไปพร้อมกัน พื้นที่ที่ยุ่งที่สุดเพียงพื้นที่เดียวจะทำให้ทิกของทั้งเซิร์ฟเวอร์ช้าลง - กรณีจริง: eve-hedgp-2014 - แหล่งอ้างอิง: - [Time Dilation – How’s That Going?](https://www.eveonline.com/news/view/time-dilation-hows-that-going) · CCP Games · Time Dilation ของ EVE Online ทำงานเป็นหน่วยโหนด ระบบดาวที่อยู่ไกลแต่รันอยู่บนโหนดเดียวกันจึงช้าไปด้วย, ศึกใหญ่ใช้โหนดเสริมกำลังที่รันระบบดาวเพียง 4 ระบบ - [mpstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/mpstat.1.html) · sysstat · แสดงอัตราการใช้ของแต่ละโปรเซสเซอร์แยกจากค่าเฉลี่ยรวม (-P ALL), %soft คือสัดส่วนเวลาที่ใช้จัดการ software interrupt - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -t แสดงสถิติแยกตามเธรดของโปรเซส (อัตราการใช้ CPU เป็นต้น) ไปด้วย #### sp-lock · การแย่งล็อก · Lock contention ถ้าหลายเธรดรอล็อกตัวเดียวเพื่อเขียนข้อมูลเดียวกัน ต่อให้เพิ่มเธรด ก็ทำงานได้ทีละตัวเท่านั้น - ทำไม → ผลคือ → บนหน้าจอ: หลายเธรดใช้ข้อมูลที่ใช้ร่วมกันพร้อมกัน เช่น ตลาดประมูลหรือคลังกิลด์ → เธรดอื่นต้องรอจนเธรดที่ถือล็อกทำงานเสร็จ → ช้าเฉพาะบางฟีเจอร์ ถ้าหนัก ทิกทั้งหมดก็ช้าไปด้วย - อาการ: อินพุตดีเลย์, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: เฉพาะบางฟีเจอร์, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แบ่งล็อกให้ละเอียดขึ้น, ลดงานที่ทำในล็อก, ใช้โครงสร้างแบบส่งข้อความ (กำหนดเธรดเจ้าของให้ข้อมูลแต่ละชุด แล้วให้เธรดอื่นส่งคำขอเป็นข้อความเท่านั้น) - ตัวเลขที่ควรรู้: ถ้างานในล็อกคิดเป็น 20% ของงานทั้งหมด ต่อให้เพิ่มเธรดเท่าไร throughput ก็ได้สูงสุด 5 เท่าของตอนมีเธรดเดียว ถ้า 40% จะหยุดที่ 2.5 เท่า - บนกราฟ: สูงตามจำนวนคนและโหลด (เวลาประมวลผลคำขอ, CPU และ context switch แยกตามเธรด) - จุดที่ต้องดู: voluntary context switch แยกตามเธรด (cswch/s คือจำนวนครั้งที่หยุดรอทรัพยากร) จาก pidstat -w -t 1, จุดที่เธรดออกจาก CPU ไปรอ (เวลารอแยกตาม call stack) จาก bcc offcputime -p สำหรับ .NET ดูจำนวนการแย่งล็อกใน dotnet-counters (.NET 9 ขึ้นไปคือ dotnet.monitor.lock_contentions, 8 ลงไปคือ Monitor Lock Contention Count) - สัญญาณว่าใช่: โหลดเพิ่มแต่อัตราการใช้ CPU ยังต่ำ ขณะที่เวลาประมวลผลเพิ่มขึ้น เวลารอส่วนใหญ่กระจุกอยู่ใน call stack ที่พยายามถือล็อก และจำนวนการแย่งล็อกสูงขึ้นตาม - สัญญาณว่าไม่ใช่: CPU เต็ม: เป็นปัญหาปริมาณการคำนวณ (ทิกเกินงบเวลา, พื้นที่ที่รันบนเธรดเดียวโหลดเกิน) ถ้ารอการเรียก DB หรือไฟล์ น่าจะเป็น synchronous call บนเธรดเกม - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: เกิดในโครงสร้างที่หลายเธรดแก้ข้อมูลเกมร่วมกัน โครงสร้างที่ให้เธรดเดียวดูแลแต่ละพื้นที่หรือฟีเจอร์และสื่อสารกันด้วยข้อความเท่านั้นแทบไม่มีล็อก แต่ต้องระวังปัญหางานกระจุกที่เธรดเดียว (พื้นที่ที่รันบนเธรดเดียวโหลดเกิน) ถ้าเธรดเกมรอล็อกที่งานบันทึกข้อมูลซึ่งช้าถืออยู่ ทั้งทิกนั้นจะหยุดชะงัก - กรณีจริง: roblox-2021 - แหล่งอ้างอิง: - [Amdahl's Law in the Multicore Era](https://research.cs.wisc.edu/multifacet/papers/ieeecomputer08_amdahl_multicore.pdf) · IEEE · เปเปอร์ IEEE Computer 2008 (ฉบับที่ผู้เขียนเผยแพร่เอง) ถ้าสัดส่วนที่แบ่งทำแบบขนานไม่ได้คือ 1−f ต่อให้เพิ่มคอร์เท่าไร ความเร็วที่เพิ่มขึ้นก็ไม่เกิน 1/(1−f) (กฎของ Amdahl) - [Request scheduling](https://learn.microsoft.com/en-us/dotnet/orleans/grains/request-scheduling) · Microsoft · grain (actor) ของ Orleans ใช้โมเดลรันแบบเธรดเดียวที่ประมวลผลคำขอทีละรายการจนจบ จึงไม่มีการแก้สถานะพร้อมกัน, ถ้า grain รอการตอบกลับของกันและกันอาจเกิดเดดล็อก - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · cswch/s ของ -w คือจำนวน voluntary context switch ที่หยุดเพราะรอทรัพยากร, -t แสดงแยกตามเธรด - [Demonstrations of offcputime, the Linux eBPF/bcc version](https://raw.githubusercontent.com/iovisor/bcc/master/tools/offcputime_example.txt) · IO Visor · รวมเวลาที่เธรดหยุดอยู่นอก CPU (off-CPU) แยกตาม call stack, -p ระบุโปรเซส - [Well-known EventCounters in .NET](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/available-counters) · Microsoft · Monitor Lock Contention Count (monitor-lock-contention-count): จำนวนครั้งที่เกิดการแย่งกันตอนพยายามถือ monitor lock - [.NET runtime metrics](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/built-in-metrics-runtime) · .NET · dotnet.monitor.lock_contentions ตั้งแต่ .NET 9: จำนวนครั้งที่เกิดการแย่งกันตอนพยายามถือ monitor lock นับตั้งแต่โปรเซสเริ่ม #### sp-deadlock · เดดล็อก · Deadlock ถ้าสองเธรดต่างรอล็อกที่อีกฝ่ายถืออยู่ ทั้งคู่จะหยุดไปตลอดกาล - ทำไม → ผลคือ → บนหน้าจอ: เธรด A ถือล็อก 1 แล้วรอล็อก 2 ส่วน B ถือล็อก 2 แล้วรอล็อก 1 → ทั้งคู่หยุดไปตลอดกาล และเธรดที่เกี่ยวข้องก็หยุดตามกันเป็นทอด ๆ → ทั้งเซิร์ฟเวอร์ค้าง และเมื่อ watchdog รีสตาร์ต ทุกคนก็หลุด - อาการ: ค้าง, หลุด / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์, เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: กำหนดกฎลำดับการถือล็อก, ใช้ล็อกที่มีไทม์เอาต์, มี watchdog และเก็บ thread dump ตอนที่โปรเซสหยุด - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนการเชื่อมต่อ, ปริมาณที่เซิร์ฟเวอร์ส่งออก) - จุดที่ต้องดู: call stack ของทุกเธรดที่เก็บไว้ระหว่างที่หยุด JVM ใช้ jstack (หาเดดล็อกและแสดงให้อัตโนมัติ) .NET ใช้ dotnet-stack เซิร์ฟเวอร์ native ใช้ thread apply all bt ของ gdb หรือใช้ gcore เก็บ core file ไว้ก่อน แล้วรีสตาร์ตและวิเคราะห์ภายหลัง - สัญญาณว่าใช่: มีเธรดตั้งแต่สองตัวขึ้นไปหยุดอยู่ใน stack ที่รอล็อกที่อีกฝ่ายถือ และระหว่างนั้นอัตราการใช้ CPU ของโปรเซสใกล้ 0 - สัญญาณว่าไม่ใช่: ระหว่างที่หยุดมีเธรดหนึ่งวิ่งอยู่ที่ CPU 100%: ลูปไม่รู้จบ ถ้าเธรดรอการตอบกลับจาก DB หรือภายนอก: น่าจะเป็น synchronous call หรือ thread pool หมด - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Runtime locking correctness validator](https://docs.kernel.org/locking/lockdep-design.html) · Linux kernel · ถ้าถือล็อกสองตัวในลำดับสลับกัน จะเกิดการรอวนเป็นเดดล็อก (lock inversion deadlock), เคอร์เนล Linux ตรวจลำดับการถือล็อกและเตือนล่วงหน้า - [Liveness, Readiness, and Startup Probes](https://kubernetes.io/docs/concepts/workloads/pods/probes/) · Kubernetes · liveness probe จับสถานะเดดล็อกที่แอปยังรันอยู่แต่ทำงานต่อไม่ได้ แล้วรีสตาร์ตคอนเทนเนอร์ - [Diagnostic Tools (Java SE 21 Troubleshooting Guide)](https://docs.oracle.com/en/java/javase/21/troubleshoot/diagnostic-tools.html) · Oracle · jstack พิมพ์ stack ของทุกเธรดใน JVM ที่กำลังรัน และหาเดดล็อกแล้วแสดงด้วย (Found one Java-level deadlock) - [dotnet-stack diagnostic tool - .NET CLI](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-stack) · Microsoft · เก็บและพิมพ์ managed stack ของทุกเธรดในโปรเซส .NET - [Threads (Debugging with GDB)](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Threads.html) · GNU Project · thread apply all รันคำสั่งเดียวกัน (bt: พิมพ์ call stack) กับทุกเธรด - [gcore(1) — Linux manual page](https://man7.org/linux/man-pages/man1/gcore.1.html) · gdb · สร้าง core file ของโปรแกรมที่กำลังรัน และโปรแกรมยังรันต่อไปตามเดิมหลังสร้างเสร็จ #### sp-sync-call · synchronous call บนเธรดเกม · Synchronous DB / file I/O on the game loop ถ้ารอการตอบกลับจาก DB หรือการเขียนไฟล์กลางทิก การดำเนินเกมทั้งหมดบนเซิร์ฟเวอร์จะหยุดไปเท่ากับเวลานั้น - ทำไม → ผลคือ → บนหน้าจอ: ทิกต้องรอการอ่านหรือบันทึก DB, การเขียน log หรือการเรียก API ภายนอก → ถ้า DB ใช้ 100 ms ทิกก็หยุด 100 ms → ทุกครั้งที่ DB หรือดิสก์ช้าลง ทั้งฟิลด์จะหยุดแวบ - อาการ: ค้าง, กระตุก / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ส่งงานที่ช้าทั้งหมด (อ่านหรือบันทึก DB, เขียน log, เรียก API ภายนอก) ไปทำแบบ asynchronous แล้วนำผลมาใช้ในทิกถัดไป (ถ้าแค่ตั้งไทม์เอาต์ ระหว่างรอก็ยังหยุดอยู่ดี) - ตัวเลขที่ควรรู้: ต่อให้เวลาไปกลับถึง DB ในดาต้าเซ็นเตอร์เดียวกันแค่ 0.5 ms ถ้าเรียก 100 ครั้งในหนึ่งทิกก็เป็น 50 ms ลำพังส่วนนี้ก็กินงบเวลาที่ 20 ทิกจนหมด - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (เวลาต่อทิกของเซิร์ฟเวอร์, ความหน่วงของคิวรี DB) - จุดที่ต้องดู: กราฟเวลาต่อทิกวางบนแกนเวลาเดียวกับความหน่วงของคิวรี DB (slow query log เป็นต้น) และความหน่วงของดิสก์ ถ้าไม่มีเมตริกทิก ให้ใช้ bcc offcputime -p ดูว่าเธรดเกมรออยู่ที่ไหน - สัญญาณว่าใช่: เวลาที่ทิกพุ่งตรงกับเวลาที่ความหน่วงของ DB หรือไฟล์พุ่ง และเวลารอของเธรดเกมกระจุกอยู่ใน call stack ที่รับการตอบกลับจาก DB หรือเขียนไฟล์ - สัญญาณว่าไม่ใช่: ความหน่วงของ DB และดิสก์ปกติ แต่ทิกพุ่ง: น่าจะเป็น GC pause หรือการแย่งล็อก - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Designs, Lessons and Advice from Building Large Distributed Systems](https://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf) · Google · keynote ของ LADIS 2009 (Jeff Dean) เวลาไปกลับภายในดาต้าเซ็นเตอร์เดียวกันประมาณ 0.5 ms (500,000 ns) - [ASP.NET Core Best Practices](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/best-practices) · Microsoft · เรียกการเข้าถึงข้อมูล I/O และงานที่ใช้เวลานานแบบ asynchronous, การเรียกแบบ synchronous ที่บล็อกทำให้ thread pool หมดและตอบสนองช้า - [Demonstrations of offcputime, the Linux eBPF/bcc version](https://raw.githubusercontent.com/iovisor/bcc/master/tools/offcputime_example.txt) · IO Visor · รวมเวลาที่เธรดหยุดอยู่นอก CPU (off-CPU) แยกตาม call stack, -p ระบุโปรเซส #### sp-queue · ข้อความสะสมในคิว · Mailbox / job queue backlog ถ้าคำขอเข้ามาเร็วกว่าความเร็วในการประมวลผลจนกองในคิว คำขอที่อยู่ท้ายคิวจะถูกประมวลผลอีกหลายวินาทีต่อมาหรือถูกทิ้ง - ทำไม → ผลคือ → บนหน้าจอ: คำขอมาถึงเร็วกว่าความเร็วในการประมวลผล → คิวยาวขึ้น และถ้าเกินขีดจำกัดก็ทิ้ง → สกิลและเทรดตอบสนองช้า หรือกดไม่ติด - อาการ: อินพุตดีเลย์, กดไม่ติด/โรลแบ็ค / ปัจจัย: ความหน่วง, แพ็กเก็ตหาย - ใครเจอ: บางจุด/บางแชนแนล, เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: มอนิเตอร์ความยาวคิว, ใช้นโยบายทิ้งคำขอเก่าก่อน, ประมวลผลแบบขนาน - บนกราฟ: ชนเพดานแล้วแบนราบ (ความยาวคิวและอายุของข้อความที่เก่าที่สุด, จำนวนที่ประมวลผลต่อวินาที) - จุดที่ต้องดู: ความยาวของแต่ละคิว อายุของข้อความที่เก่าที่สุด และจำนวนที่เข้ามา ประมวลผล และทิ้งต่อวินาทีที่เซิร์ฟเวอร์บันทึก ถ้าไม่มีเมตริกจากโค้ด ให้ดู Recv-Q ของซ็อกเก็ตเกมด้วย ss (หรือ netstat) ซึ่งคือปริมาณที่เคอร์เนลรับไว้แล้วแต่โปรเซสยังไม่ได้อ่าน - สัญญาณว่าใช่: ระหว่างที่จำนวนที่เข้ามามากกว่าจำนวนที่ประมวลผล จำนวนที่ประมวลผลติดอยู่ที่ค่าหนึ่งไม่ขึ้นอีก และความยาวคิว อายุข้อความ และจำนวนที่ทิ้งเพิ่มขึ้นเรื่อย ๆ - สัญญาณว่าไม่ใช่: คิวสั้นและข้อความยังใหม่ แต่ตอบสนองช้า: น่าจะเป็นความหน่วงของเน็ตหรือตัวทิกเองที่ช้า - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - กรณีจริง: eve-hedgp-2014 - แหล่งอ้างอิง: - [Avoiding insurmountable queue backlogs](https://d1.awsstatic.com/builderslibrary/pdfs/avoiding-insurmountable-queue-backlogs.pdf) · AWS · Amazon Builders' Library มอนิเตอร์ backlog ด้วยอายุของข้อความที่รออยู่, ระบบเรียลไทม์ประมวลผลข้อมูลใหม่ก่อน (ใกล้เคียง LIFO), บางครั้งทิ้งข้อความเก่า - [Site Reliability Engineering, Chapter 22: Addressing Cascading Failures](https://sre.google/sre-book/addressing-cascading-failures/) · Google · ถ้าคำขอเข้ามาเร็วกว่าความเร็วในการประมวลผล คิวจะเต็มและความหน่วงเพิ่ม, ใช้ LIFO หรือ CoDel แทน FIFO เพื่อลดคำขอเก่าที่ไม่มีประโยชน์แล้วออก - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · เครื่องมือแสดงสถิติซ็อกเก็ต (ข้อมูลคล้าย netstat), -p แสดงโปรเซสที่ใช้ซ็อกเก็ต - [netstat(8) — Linux manual page](https://man7.org/linux/man-pages/man8/netstat.8.html) · net-tools · Recv-Q: จำนวนไบต์ในซ็อกเก็ตที่เชื่อมต่อแล้วที่โปรแกรมของผู้ใช้ยังไม่ได้ดึงไป #### sp-timer-burst · ตัวจับเวลาทำงานพร้อมกันจำนวนมาก · Synchronized timers ถ้าการรีสปอนของมอนสเตอร์ทั้งหมด การหมดอายุของบัฟทั้งหมด และของรางวัลตอนตรงชั่วโมงมารวมในทิกเดียวกัน ทิกนั้นจะหนักขึ้นหลายสิบเท่า - ทำไม → ผลคือ → บนหน้าจอ: ตัวจับเวลาของการรีสปอน การหมดอายุ ของรางวัล และการบันทึกอัตโนมัติถูกตั้งไว้เวลาเดียวกัน → ทิกนั้นทิกเดียวมีงานมากกว่าปกติหลายสิบเท่า → หยุดแวบหนึ่งครั้งทุกเวลาที่กำหนด - อาการ: ค้าง, กระตุก / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: เป็นรอบสม่ำเสมอ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: สุ่มกระจายเวลาของตัวจับเวลาเล็กน้อย, แบ่งงานไปประมวลผลในหลายทิก - บนกราฟ: พุ่งเป็นรอบ (เวลาต่อทิกของเซิร์ฟเวอร์) - จุดที่ต้องดู: รวบรวมเวลาที่ทิกพุ่งแล้วดูช่วงห่าง (ตรงชั่วโมง, ทุก 5 นาที เป็นต้น) เทียบกับรายการตัวจับเวลาของการรีสปอน การหมดอายุบัฟ ของรางวัล และการบันทึกอัตโนมัติที่ทำงานในเวลาเดียวกัน - สัญญาณว่าใช่: ทิกพุ่งเวลาเดิมหรือช่วงห่างเดิมทุกครั้ง และเวลานั้นมีงานจากตัวจับเวลาของเกมที่ทำงานพร้อมกันรวดเดียว - สัญญาณว่าไม่ใช่: เป็นรอบ แต่ตรงกับเวลาหยุดใน GC log หรือเวลา cron และการสำรองข้อมูลของเซิร์ฟเวอร์: น่าจะเป็น “GC ของเซิร์ฟเวอร์หยุดทั้งระบบ” หรือ “งานตามกำหนดเวลา (scheduled job)” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Timeouts, retries, and backoff with jitter](https://d1.awsstatic.com/builderslibrary/pdfs/timeouts-retries-and-backoff-with-jitter.pdf) · AWS · Amazon Builders' Library เพิ่ม jitter ให้ตัวจับเวลา งานตามรอบ และงานที่หน่วงเวลาไว้ทุกตัว เพื่อกระจายโหลดที่จะมารวมกันในเวลาเดียวกัน, กรณีที่คำขอรอบ 1 นาทีจากเซิร์ฟเวอร์หลายเครื่องมากระจุกในไม่กี่วินาทีแรกของทุกนาที #### sp-pathfinding · การคำนวณ pathfinding พุ่ง · Pathfinding storms ถ้ามอนสเตอร์หลายร้อยตัวไล่ตามผู้เล่นและคำนวณเส้นทางพร้อมกัน จะกิน CPU มาก - ทำไม → ผลคือ → บนหน้าจอ: มอนสเตอร์จำนวนมากไล่ตามพร้อมกันจากการลากมอนหรือการ spawn ครั้งใหญ่ → มอนสเตอร์แต่ละตัวคำนวณ pathfinding ของตัวเอง → สโลว์โมชั่นเฉพาะในจุดฟาร์มนั้น - อาการ: สโลว์โมชั่น / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แคชเส้นทาง, จำกัดจำนวนครั้งที่คำนวณ, แบ่งไปคำนวณในหลายทิก - บนกราฟ: สูงตามจำนวนคนและโหลด (เวลาต่อทิกของเซิร์ฟเวอร์, จำนวนมอนสเตอร์แยกตามโซน) - จุดที่ต้องดู: จำนวนมอนสเตอร์ที่กำลังไล่ตามผู้เล่นและเวลาต่อทิกแยกตามโซน ถ้าไม่ได้นับแยกไว้ ให้ดูสัดส่วน CPU แยกตามฟังก์ชันของโปรเซสเกมด้วย perf top -p - สัญญาณว่าใช่: ตอนลากมอนหรือ spawn ครั้งใหญ่ เวลาต่อทิกเพิ่มขึ้น และฟังก์ชัน pathfinding (ค้นหาเส้นทาง) กินสัดส่วนเวลา CPU มาก - สัญญาณว่าไม่ใช่: มอนสเตอร์น้อยแต่ผู้เล่นเยอะแล้วทิกเพิ่ม: น่าจะเป็นการคำนวณระยะมองเห็นหรือ broadcast - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [AI.NavMesh.pathfindingIterationsPerFrame](https://docs.unity3d.com/ScriptReference/AI.NavMesh-pathfindingIterationsPerFrame.html) · Unity · ประมวลผล pathfinding ทีละจำนวนโหนดที่กำหนดต่อเฟรม เพื่อแบ่งงานไปหลายเฟรม เกมจึงยังลื่นแม้เส้นทางยาวหรือมีคำขอพร้อมกันจำนวนมาก - [perf-top(1) — Linux manual page](https://man7.org/linux/man-pages/man1/perf-top.1.html) · perf · แสดงสัดส่วนการใช้ CPU ของโปรเซสที่กำลังรัน (-p) แยกตามฟังก์ชัน (symbol) แบบเรียลไทม์ #### sp-serialize · ต้นทุนของ serialization และการบีบอัด · Serialization / compression cost การแปลงข้อมูลที่จะส่งเป็นไบต์และบีบอัดก็ใช้ CPU เช่นกัน และเมื่อคนเยอะ ต้นทุนนี้จะพุ่งสูง - ทำไม → ผลคือ → บนหน้าจอ: แปลง struct เป็นไบต์และบีบอัดทุกครั้งที่อัปเดต → ต้นทุนเพิ่มตามจำนวนคนยกกำลังสอง → การส่งช้าลงจนเกิดอินพุตดีเลย์ - อาการ: อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: บางจุด/บางแชนแนล / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ใช้แพ็กเก็ตที่สร้างครั้งเดียวซ้ำกับหลายคน, ใช้ฟอร์แมตที่เบา - บนกราฟ: สูงตามจำนวนคนและโหลด (อัตราการใช้ CPU ของเซิร์ฟเวอร์, CPU ของเธรดที่สร้างแพ็กเก็ต) - จุดที่ต้องดู: สัดส่วนเวลา CPU ของโปรเซสเกมที่ใช้ในฟังก์ชัน serialization การบีบอัด และการเข้ารหัส (รวมฟังก์ชันของไลบรารีอย่าง zlib, LZ4 และ OpenSSL) จาก perf top -p เทียบระหว่างตอนคนน้อยกับตอนคนแห่ - สัญญาณว่าใช่: ยิ่งคนแห่มาก สัดส่วนของฟังก์ชัน serialization การบีบอัด และการเข้ารหัสยิ่งสูง และ CPU ของเธรดที่สร้างแพ็กเก็ตเต็มก่อน - สัญญาณว่าไม่ใช่: ฟังก์ชันเหล่านี้กินสัดส่วนน้อย: น่าจะเป็นการคำนวณระยะมองเห็นหรือลอจิกของเกม - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ถ้าการเชื่อมต่อเข้ารหัสแพ็กเก็ต (TLS, DTLS เป็นต้น) การเข้ารหัสและถอดรหัสก็ใช้ CPU ด้วย การเข้ารหัสทำแยกต่อการเชื่อมต่อ ต่อให้ใช้แพ็กเก็ตที่สร้างครั้งเดียวซ้ำกับหลายคน ต้นทุนการเข้ารหัสก็ยังเพิ่มตามจำนวนผู้รับ การเข้ารหัสแบบสมมาตรอย่าง AES-GCM เร็วพอที่คอร์เดียวจะประมวลผลได้หลาย GB ต่อวินาที ปกติจึงกินสัดส่วนน้อย แต่ความเร็วจะต่างกันมากตามขนาดของหน่วยที่เข้ารหัสในครั้งเดียว (record) ถ้ามีแพ็กเก็ตเล็กจำนวนมากแบบเกม ต้นทุนต่อไบต์จะสูงขึ้น ใน handshake ที่ทำครั้งเดียวต่อการเชื่อมต่อ เซิร์ฟเวอร์ต้องเซ็นด้วยคีย์ของใบรับรองและคำนวณการแลกเปลี่ยนคีย์ (ECDHE) คอร์เดียวเซ็นได้ประมาณ 1,100 ครั้ง (RSA 2048) ถึง 18,000 ครั้ง (ECDSA P-256) ต่อวินาที และแลกเปลี่ยนคีย์ได้ประมาณ 9,000 ครั้ง จึงเป็นภาระเมื่อคนแห่ล็อกอิน - แหล่งอ้างอิง: - [Introduction to Iris in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/introduction-to-iris-in-unreal-engine) · Epic Games · เก็บสถานะที่ต้อง replicate เป็นสำเนาที่ quantize แล้วชุดเดียวเพื่อลดงานที่หนัก และให้หลายการเชื่อมต่อใช้งานนั้นร่วมกัน - [VALORANT's 128-Tick Servers](https://www.riotgames.com/en/news/valorants-128-tick-servers) · Riot Games · วิธีเทียบตัวแปรที่ replicate ของไคลเอนต์แต่ละรายทุกเฟรมแล้วรวบรวมค่าที่เปลี่ยน ต้องอ่านหน่วยความจำกระจัดกระจาย ซึ่งช้าและกิน CPU ของเซิร์ฟเวอร์มาก - [How "expensive" is crypto anyway?](https://blog.cloudflare.com/how-expensive-is-crypto-anyway/) · Cloudflare · ผลวัดของ BoringSSL: AES-128-GCM ประมาณ 3.7 GB ต่อวินาที (ต่างกันมากตามขนาด record), คอร์เดียวต่อวินาทีทำ RSA 2048 signature ได้ 1,120 ครั้ง, ECDSA P-256 signature 18,477 ครั้ง และ P-256 ECDHE 9,394 ครั้ง, บนเอดจ์เซิร์ฟเวอร์ของ Cloudflare ไลบรารี TLS ใช้ CPU ประมาณ 1.8% - [perf-top(1) — Linux manual page](https://man7.org/linux/man-pages/man1/perf-top.1.html) · perf · แสดงสัดส่วนการใช้ CPU ของโปรเซสที่กำลังรัน (-p) แยกตามฟังก์ชัน (symbol) แบบเรียลไทม์ #### sp-crash · เซิร์ฟเวอร์แครช · Server process crash ถ้าโปรเซสเซิร์ฟเวอร์ตายเพราะข้อผิดพลาดที่ไม่ได้จัดการ ทุกคนในเซิร์ฟเวอร์นั้นจะหลุดพร้อมกัน - ทำไม → ผลคือ → บนหน้าจอ: ข้อผิดพลาดร้ายแรง เช่น อ้างถึงสิ่งที่ไม่มีอยู่ (null reference), ข้อมูลผิดพลาด หรือหน่วยความจำไม่พอ → โปรเซสเซิร์ฟเวอร์ (หรือโซน) จบการทำงาน → ทุกคนหลุดพร้อมกัน ความคืบหน้าหลังการบันทึกครั้งล่าสุดอาจโดนโรลแบ็ค - อาการ: หลุด, กดไม่ติด/โรลแบ็ค / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: วิเคราะห์ crash dump แล้วแก้ที่ต้นเหตุ, บันทึกข้อมูลบ่อย ๆ - งานฝั่งทีมอินฟรา: รีสตาร์ตโปรเซสอัตโนมัติ, เตรียมระบบเก็บและรักษา crash dump, ตั้ง alert ทันทีที่เซิร์ฟเวอร์ล่ม - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนการเชื่อมต่อ, จำนวนครั้งที่โปรเซสรีสตาร์ต) - จุดที่ต้องดู: บันทึก core dump ใน coredumpctl list (เวลา, PID, สัญญาณที่ทำให้จบ) และบันทึกการจบผิดปกติและการรีสตาร์ตของ service manager (systemd) สำหรับเซิร์ฟเวอร์ Windows ดูไฟล์ dump ที่ WER เก็บไว้ - สัญญาณว่าใช่: ตอนที่จำนวนการเชื่อมต่อดิ่งลงเกือบ 0 ทันที โปรเซสเซิร์ฟเวอร์เกมจบแบบผิดปกติและทิ้ง core dump ไว้ - สัญญาณว่าไม่ใช่: โปรเซสยังทำงานอยู่แต่การเชื่อมต่อหลุด: น่าจะเป็นอุปกรณ์เครือข่ายหรือ idle timeout ถ้ามีบันทึกว่า watchdog รีสตาร์ตหลังหยุดไปนาน: น่าจะเป็นลูปไม่รู้จบหรือเดดล็อก - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Collecting User-Mode Dumps](https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps) · Microsoft · ตั้งค่า Windows Error Reporting (WER) ให้เก็บ full dump หรือ mini dump ไว้ในเครื่องเมื่อโปรแกรม user mode แครช - [systemd.service(5) — Linux manual page](https://man7.org/linux/man-pages/man5/systemd.service.5.html) · systemd · Restart=on-failure รีสตาร์ตเซอร์วิสอัตโนมัติเมื่อจบผิดปกติ ถูกปิดด้วยสัญญาณ (รวม core dump) หรือ watchdog หมดเวลา แนะนำสำหรับเซอร์วิสที่รันนาน - [coredumpctl(1) — Linux manual page](https://man7.org/linux/man-pages/man1/coredumpctl.1.html) · systemd · ใช้ list ดู core dump ที่ systemd-coredump เก็บไว้ แสดงเวลาแครช, PID และสัญญาณที่ทำให้แครช #### sp-threadpool · thread pool หมด · Thread pool starvation ถ้า worker thread ที่ประมวลผลงานติดอยู่กับงานช้าทั้งหมด คำขอใหม่จะต้องรอไปเรื่อย ๆ อย่างไม่มีกำหนด - ทำไม → ผลคือ → บนหน้าจอ: worker thread ติดรอการตอบกลับจาก API ภายนอกหรือ DB → ไม่มีเธรดว่างรับคำขอใหม่ → บางฟีเจอร์ เช่น ล็อกอินหรือร้านค้า โหลดไม่จบ - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, อินพุตดีเลย์, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: เฉพาะบางฟีเจอร์, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ตั้งไทม์เอาต์ให้การเรียกที่ช้า, แยก thread pool ตามฟีเจอร์, เปลี่ยนเป็นแบบ asynchronous - บนกราฟ: ชนเพดานแล้วแบนราบ (จำนวนเธรดและความยาวคิวของ thread pool, เวลาประมวลผลคำขอ) - จุดที่ต้องดู: สำหรับ .NET ดูจำนวนเธรดและความยาวคิวของ thread pool ใน dotnet-counters monitor (.NET 9 ขึ้นไปคือ dotnet.thread_pool.thread.count และ dotnet.thread_pool.queue.length, 8 ลงไปคือ ThreadPool Thread Count และ ThreadPool Queue Length) และดูว่า worker thread รออยู่ที่ไหนด้วย dotnet-stack สำหรับ JVM และเซิร์ฟเวอร์ native ดูแบบเดียวกันจาก thread dump - สัญญาณว่าใช่: อัตราการใช้ CPU ต่ำกว่า 100% มาก แต่จำนวนเธรดค่อย ๆ เพิ่มขึ้นเรื่อย ๆ หรือติดเพดาน คิวสะสม และ worker ส่วนใหญ่รอการตอบกลับจากการเรียกภายนอกตัวเดียวกัน (DB, HTTP) - สัญญาณว่าไม่ใช่: คิวว่างแต่ยังช้า: ปลายทางที่เรียกช้าเอง น่าจะเป็นความล้มเหลวแบบลูกโซ่หรือการพึ่งพาเซอร์วิสภายนอก - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ถ้าการรับแพ็กเก็ตกับลอจิกเกมใช้ worker thread pool เดียวกัน ทันทีที่งานช้าไม่กี่งานถือครอง worker ทั้งหมด การประมวลผลแพ็กเก็ตของทั้งเซิร์ฟเวอร์จะหยุด - กรณีจริง: riot-euw-2021 - แหล่งอ้างอิง: - [Debug ThreadPool Starvation](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/debug-threadpool-starvation) · Microsoft · เมื่อ pool ไม่มีเธรดเหลือและงานใหม่ต้องรอ การตอบสนองจะช้าลง ต้นเหตุคือโค้ดแบบ blocking ที่ถือครองเธรด ใน dotnet-counters ถ้า CPU ต่ำกว่า 100% มากแต่ dotnet.thread_pool.thread.count ค่อย ๆ เพิ่มเรื่อย ๆ คือสัญญาณว่าหมด (dotnet.thread_pool.queue.length มักสูงด้วย), ดูจุดที่เธรดรอด้วย dotnet-stack - [Avoiding insurmountable queue backlogs](https://d1.awsstatic.com/builderslibrary/pdfs/avoiding-insurmountable-queue-backlogs.pdf) · AWS · จำนวนงานพร้อมกัน = อัตราการเข้ามา × ความหน่วง (กฎของ Little) ที่ 100 คำขอต่อวินาที ถ้าความหน่วงเพิ่มจาก 100 ms เป็น 10 วินาที เธรด 10 ตัวจะกลายเป็น 1,000 ตัวจน pool หมด - [Bulkhead Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/bulkhead) · Microsoft Azure · ถ้าแยก connection pool และ thread pool ไว้ต่อปลายทางที่เรียก ความขัดข้องของปลายทางหนึ่งจะบล็อกแค่ pool ของมันเอง - [.NET runtime metrics](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/built-in-metrics-runtime) · .NET · dotnet.thread_pool.thread.count (จำนวนเธรดของ thread pool) และ dotnet.thread_pool.queue.length (จำนวนงานที่รออยู่) มีตั้งแต่ .NET 9 - [Well-known EventCounters in .NET](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/available-counters) · Microsoft · ThreadPool Thread Count (threadpool-thread-count) และ ThreadPool Queue Length (threadpool-queue-length) ของ .NET 8 ลงไป #### sp-infinite-loop · ลูปไม่รู้จบ/ลอจิกทำงานไม่หยุด · Infinite loop / runaway logic ถ้าบั๊กทำให้ทิกหนึ่งไม่จบ เซิร์ฟเวอร์จะหยุด และ watchdog จะบังคับรีสตาร์ต - ทำไม → ผลคือ → บนหน้าจอ: ลูปไม่จบเพราะเงื่อนไขผิด หรือ recursion ไม่หยุด → ทิกไม่จบ เซิร์ฟเวอร์จึงหยุด → ค้างแล้วทุกคนก็หลุด - อาการ: ค้าง, หลุด / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: จำกัดจำนวนรอบของลูป, มี watchdog, เขียนเทสต์ที่จำลองอินพุตที่ก่อปัญหา - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนการเชื่อมต่อ, CPU แยกตามเธรด) - จุดที่ต้องดู: CPU แยกตามเธรดจาก pidstat -t 1 ระหว่างที่หยุด และดูว่าเธรดที่วิ่งอยู่ที่ 100% วนอยู่ในฟังก์ชันไหนด้วย perf top -t (thread ID) หรือ gdb ถ้ารีสตาร์ตไปแล้ว ให้ดูบันทึก watchdog หมดเวลา (WatchdogSec ของ systemd, liveness probe ของ Kubernetes ล้มเหลว) - สัญญาณว่าใช่: ระหว่างที่เซิร์ฟเวอร์หยุด เธรดเกมตัวหนึ่งติดอยู่ที่ CPU 100% และ stack วนอยู่ในฟังก์ชันหรือลูปเดิม - สัญญาณว่าไม่ใช่: CPU ใกล้ 0 ระหว่างที่หยุด: น่าจะเป็นเดดล็อกหรือการรอการตอบกลับจากภายนอก - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [systemd.service(5) — Linux manual page](https://man7.org/linux/man-pages/man5/systemd.service.5.html) · systemd · WatchdogSec=: ถ้าเซอร์วิสไม่ส่งสัญญาณว่ายังทำงานอยู่ (WATCHDOG=1) ภายในเวลาที่กำหนด จะถือว่าล้มเหลวและหยุดการทำงาน แล้วรีสตาร์ตอัตโนมัติตามค่า Restart= - [Liveness, Readiness, and Startup Probes](https://kubernetes.io/docs/concepts/workloads/pods/probes/) · Kubernetes · liveness probe จับสถานะที่แอปยังรันอยู่แต่ทำงานต่อไม่ได้แล้วรีสตาร์ต, ค่าเริ่มต้นตรวจทุก 10 วินาที และรีสตาร์ตเมื่อล้มเหลวติดกัน 3 ครั้ง - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -t แสดงสถิติแยกตามเธรดของโปรเซส (อัตราการใช้ CPU เป็นต้น) ไปด้วย - [perf-top(1) — Linux manual page](https://man7.org/linux/man-pages/man1/perf-top.1.html) · perf · แสดงสัดส่วนการใช้ CPU ของเธรด (-t) หรือโปรเซส (-p) ที่กำลังรันแยกตามฟังก์ชัน (symbol) แบบเรียลไทม์ #### sp-hot-entity · การต่อสู้ที่รุมเป้าหมายเดียว (เวิลด์บอส) · Hot entity / combat event fan-out ถ้าคนหลายร้อยคนตีบอสตัวเดียวพร้อมกัน การคำนวณของบอสตัวนั้นจะกระจุกที่จุดเดียว และข้อมูลการโจมตีจะถูกส่งให้ทุกคนที่มองเห็น - ทำไม → ผลคือ → บนหน้าจอ: คนหลายร้อยคนใช้สกิล บัฟ และดีบัฟใส่บอสตัวเดียวไม่หยุด → การคำนวณ HP รายการ aggro และดีบัฟของบอสกระจุกที่จุดเดียว และทุกครั้งที่โจมตี จะส่งแพ็กเก็ตตัวเลขดาเมจและเอฟเฟกต์ให้ทุกคนที่มองเห็น → สกิลเข้าช้า ตัวเลขดาเมจขึ้นมารวดเดียว และเป็นสโลว์โมชั่นเฉพาะรอบบอส - อาการ: อินพุตดีเลย์, กรอเร็ว, สโลว์โมชั่น / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: บางจุด/บางแชนแนล / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: รวบหรือข้ามตัวเลขดาเมจและเอฟเฟกต์ของคนอื่น, จำกัดจำนวนดีบัฟบนเป้าหมายเดียว, แบ่งการประมวลผลการโจมตีไปหลายทิก - ตัวเลขที่ควรรู้: ถ้า 800 คนตีคนละ 2 ครั้งต่อวินาที จะเป็นการโจมตี 1,600 ครั้งต่อวินาที ถ้าแจ้งทุกครั้งให้ 800 คนที่มองเห็น จะเป็น 1.28 ล้านข้อความต่อวินาที - บนกราฟ: สูงตามจำนวนคนและโหลด (เวลาต่อทิกของเซิร์ฟเวอร์, จำนวนข้อความที่ส่ง) - จุดที่ต้องดู: เวลาต่อทิกและจำนวนแพ็กเก็ตที่ส่งช่วงสู้บอส ดูคู่กับจำนวนคนรอบบอส และถ้าทำได้ ดูจำนวนอีเวนต์ต่อวินาที (โจมตี, บัฟ, ดีบัฟ) แยกตามเป้าหมาย - สัญญาณว่าใช่: เมื่อคนรอบบอสเพิ่มขึ้น เวลาต่อทิกและปริมาณที่ส่งพุ่งชัน และบอสตัวเดียวมีจำนวนอีเวนต์ต่อวินาทีมากกว่าเป้าหมายอื่นหลายสิบเท่า - สัญญาณว่าไม่ใช่: แค่คนมารวมกันในจุดเดียวก็ช้าเท่ากันโดยไม่เกี่ยวกับบอส: น่าจะเป็นการคำนวณระยะมองเห็นหรือปริมาณ broadcast พุ่ง - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [HED-GP Technical Retrospective: What a HED-ache](https://www.eveonline.com/news/view/what-a-hed-ache) · CCP Games · แม้การโจมตีครั้งเดียวก็ต้องแจ้งไคลเอนต์ทุกตัวที่มองเห็น จึงเกิดภาระ O(n²) ที่ n คนแจ้ง n คน และการโจมตีด้วยโดรนที่มีข้อความมากทำให้ภาระนี้โตเร็วขึ้นอีก #### sp-spawn-burst · spawn ทะลักเมื่อเข้าพื้นที่คนหนาแน่น · Spawn burst when entering a crowd ถ้าเทเลพอร์ตเข้าเมืองที่คนแน่น เซิร์ฟเวอร์ต้องส่งรูปลักษณ์ อุปกรณ์ และสถานะของคนหลายร้อยคนที่เพิ่งมองเห็นไปพร้อมกันรวดเดียว - ทำไม → ผลคือ → บนหน้าจอ: โผล่ในที่ที่คนเยอะทันทีจากการเทเลพอร์ต ล็อกอิน หรือย้ายแชนแนล → สร้างข้อมูลทั้งหมดของคนหลายร้อยคนแล้วส่งในครั้งเดียว และ PC ของเราก็โหลดทั้งหมดในครั้งเดียว → ค้างไปชั่วครู่หลังมาถึง, ตัวละครทยอยโผล่ช้าทีละตัว, อินพุตดีเลย์ - อาการ: ค้าง, อินพุตดีเลย์, กรอเร็ว / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: เราคนเดียว, บางจุด/บางแชนแนล / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ทยอยส่งตามลำดับระยะใกล้ไปหลายทิก, แคชข้อมูลรูปลักษณ์ ไคลเอนต์: โหลดล่วงหน้าระหว่างหน้าโหลด, ทยอยสร้างตัวละครที่ได้รับไปหลายเฟรม - ตัวเลขที่ควรรู้: ถ้าข้อมูลรูปลักษณ์ อุปกรณ์ และบัฟของคนหนึ่งคนมีขนาด 300 ไบต์ คน 500 คนจะเป็นประมาณ 150 KB เท่ากับหลายสิบเท่าของปริมาณที่ส่งต่อทิกตามปกติ (ไม่กี่ KB) มารวมในชั่วพริบตาเดียว - บนกราฟ: พุ่งทันทีหลังเปิดเซิร์ฟ/ปิดปรับปรุง (ไบต์ที่ส่งต่อการเชื่อมต่อ, เฟรมไทม์ของไคลเอนต์) - จุดที่ต้องดู: ไบต์และจำนวนแพ็กเก็ตที่ส่งผ่านการเชื่อมต่อนั้นในไม่กี่วินาทีหลังมาถึงที่ที่คนเยอะ (log เซิร์ฟเวอร์) และเฟรมไทม์ของไคลเอนต์ (net graph, log ไคลเอนต์) - สัญญาณว่าใช่: หลังมาถึง ปริมาณที่ส่งของการเชื่อมต่อนั้นพุ่งขึ้นเป็นหลายสิบเท่าของทิกปกติแล้วค่อยลดลง และเฟรมไทม์ของไคลเอนต์พุ่งในจังหวะเดียวกัน - สัญญาณว่าไม่ใช่: ย้ายไปที่ที่คนน้อยก็หยุดเหมือนกัน: น่าจะเป็นการย้ายโซน (ส่งต่อระหว่างเซิร์ฟเวอร์) หรือการโหลดของไคลเอนต์ - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Detailed Actor Replication Flow in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/detailed-actor-replication-flow-in-unreal-engine) · Epic Games · ตอนเปิด actor channel ครั้งแรก จะส่งข้อมูลเริ่มต้นอย่างตำแหน่งและการหมุนไปด้วย และเมื่อการเชื่อมต่อเต็ม actor ที่เหลือจะเลื่อนไปทิกถัดไป - [Actor Priority in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-priority-in-unreal-engine) · Epic Games · ให้ลำดับความสำคัญตามระยะห่างและทิศทางการมอง แล้วส่ง actor ที่อยู่ใกล้และมองเห็นก่อน #### sp-entity-buildup · เอนทิตีสะสม (ไอเทม/ซัมมอนที่ไม่ถูกเก็บกวาด) · Entity / timer buildup over uptime ถ้าไอเทมที่ตกบนพื้นซึ่งควรหายไปแล้ว ซัมมอน และตัวจับเวลาที่จบแล้วไม่ถูกเก็บกวาดจนกองสะสม ยิ่งเปิดเซิร์ฟเวอร์ไว้นาน งานในแต่ละทิกก็ยิ่งเพิ่ม - ทำไม → ผลคือ → บนหน้าจอ: ไอเทมบนพื้น ซัมมอน ตัวจับเวลาที่หมดอายุ และข้อมูลปาร์ตี้ที่ว่างไม่ถูกลบตามเวลา → รายการที่ต้องไล่ทุกทิกยาวขึ้นทุกวัน → ทันทีหลังปิดปรับปรุงยังปกติ แต่ผ่านไปไม่กี่วัน เฉพาะเซิร์ฟเวอร์หรือพื้นที่นั้นก็ค่อย ๆ อืดขึ้นเรื่อย ๆ - อาการ: สโลว์โมชั่น, กระตุก, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางจุด/บางแชนแนล / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เก็บจำนวนเอนทิตีแยกตามพื้นที่เป็นเมตริกเพื่อดูแนวโน้ม, กำหนดอายุและจำนวนสูงสุดให้เอนทิตีแต่ละแบบ, เก็บกวาดเป็นรอบ - ตัวเลขที่ควรรู้: ถ้าเซิร์ฟเวอร์ไล่ทุกเอนทิตีหนึ่งรอบทุกทิก เมื่อจำนวนเอนทิตีเพิ่มเป็นสองเท่า เวลาต่อทิกในส่วนนั้นก็เพิ่มเป็นสองเท่า - บนกราฟ: ค่อย ๆ ขึ้นแล้วดิ่งลง (จำนวนเอนทิตีแยกตามโซน, เวลาต่อทิกของเซิร์ฟเวอร์) - จุดที่ต้องดู: จำนวนเอนทิตี (ไอเทมบนพื้น, ซัมมอน, ตัวจับเวลา) และเวลาต่อทิกแยกตามโซนและเซิร์ฟเวอร์ ในช่วงที่ยาวกว่ารอบปิดปรับปรุง (หลายสัปดาห์) - สัญญาณว่าใช่: จำนวนเอนทิตีและเวลาต่อทิกเริ่มต่ำหลังปิดปรับปรุง ไต่ขึ้นทุกวัน แล้วดิ่งลงเมื่อปิดปรับปรุงหรือรีสตาร์ตวนซ้ำ ระหว่างนั้นหน่วยความจำยังเหลือเฟือ - สัญญาณว่าไม่ใช่: ทิกไม่เปลี่ยนแต่หน่วยความจำเพิ่มขึ้นเรื่อย ๆ: น่าจะเป็นหน่วยความจำรั่ว - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: คล้ายหน่วยความจำรั่วตรงที่ยิ่งเปิดนานยิ่งแย่ แต่ต่างกันตรงที่หน่วยความจำยังเหลือเฟือ มีแค่เวลาต่อทิกที่เพิ่มขึ้น ถ้ากราฟจำนวนเอนทิตีเป็นรูปฟันเลื่อยตามรอบปิดปรับปรุง ก็คือกรณีนี้ - แหล่งอ้างอิง: - [Actor Ticking in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-ticking-in-unreal-engine) · Epic Games · actor และ component จะทิกหนึ่งครั้งทุกเฟรมถ้าไม่ได้กำหนดช่วงห่างแยก และปิดการทิกได้ถ้าไม่จำเป็น - [AActor::SetLifeSpan](https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Engine/AActor/SetLifeSpan) · Epic Games · ถ้ากำหนดอายุ (lifespan) ให้ actor จะถูกทำลายอัตโนมัติเมื่อหมดอายุ #### sp-patch-traffic · แพตช์ทำให้รูปแบบทราฟฟิกเปลี่ยน · Patch changes traffic pattern ถ้าคอนเทนต์ เอฟเฟกต์ หรือรายการที่ต้องซิงก์ใหม่ทำให้ขนาดและความถี่ของแพ็กเก็ตเพิ่ม เซิร์ฟเวอร์ที่เคยลื่นจะเริ่มชนขีดจำกัด MTU แบนด์วิดท์ และจำนวนแพ็กเก็ตหลังลงแพตช์ - ทำไม → ผลคือ → บนหน้าจอ: แพตช์เพิ่มเอฟเฟกต์สกิล รายการที่ต้องซิงก์ หรือข้อมูลไอเทมใหม่ แพ็กเก็ตจึงใหญ่ขึ้นหรือถี่ขึ้น → แพ็กเก็ตใหญ่เกิน MTU จนถูกแบ่งชิ้น และปริมาณที่เพิ่มขึ้นไปชนแบนด์วิดท์ ขีดจำกัด PPS ของคลาวด์ และ send buffer → ตั้งแต่ลงแพตช์ ที่ที่คนเยอะมีอาการวาร์ป สกิลไม่ออก และอินพุตดีเลย์ ฝั่งอินฟราไม่ได้เปลี่ยนอะไร แต่แพ็กเก็ตหายเพิ่มขึ้น - อาการ: วาร์ป, กดไม่ติด/โรลแบ็ค, อินพุตดีเลย์ / ปัจจัย: แพ็กเก็ตหาย, ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางจุด/บางแชนแนล, บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ช่วงพีคหัวค่ำ, ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), อินฟราเครือข่าย (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: แบ่งแพ็กเก็ตเองให้ไม่เกิน 1,200 ไบต์, รายการซิงก์ใหม่ให้ส่งเฉพาะส่วนที่เปลี่ยนและลดความถี่ตามระยะห่างและความสำคัญ, ก่อน deploy ให้เทียบแพ็กเก็ตและไบต์ต่อวินาทีต่อผู้เล่นและขนาดแพ็กเก็ตใหญ่สุดกับบิลด์ก่อนหน้าบนเซิร์ฟเวอร์ทดสอบ, ใส่เวอร์ชันบิลด์ไว้ในเมตริกทราฟฟิก - งานฝั่งทีมอินฟรา: เครื่องเซิร์ฟเวอร์/OS: ทำเครื่องหมายเวลา deploy บนกราฟ และเทียบแพ็กเก็ตและไบต์ต่อวินาทีต่อผู้เล่นกับขนาดแพ็กเก็ตเฉลี่ยก่อนและหลัง deploy, ตั้ง alert ตัวนับเกินขีดจำกัดของอินสแตนซ์, ถ้าจำเป็นให้ใช้อินสแตนซ์ที่ใหญ่ขึ้น เครือข่าย: ตรวจขีดความสามารถของไฟร์วอลล์ โหลดบาลานเซอร์ และอุปกรณ์ป้องกัน DDoS และดูว่าบล็อก fragment หรือไม่ - ตัวเลขที่ควรรู้: แพ็กเก็ต UDP ที่ไม่เกิน 1,200 ไบต์ถือว่าปลอดภัย path MTU ของอินเทอร์เน็ตปกติคือ 1,500 ไบต์ และจะเล็กลงเมื่อผ่าน tunnel (ถ้าเป็น GRE tunnel คือ 1,476 ไบต์) แพ็กเก็ตที่เกิน path MTU จะถูกแบ่งชิ้นหรือถูกทิ้ง และแพ็กเก็ตที่ถูกแบ่งชิ้นจะเสียทั้งหมดแม้ fragment หายไปแค่ชิ้นเดียว ถ้าแพ็กเก็ตต่อวินาทีต่อผู้เล่นเพิ่ม 20% ทั้งเซิร์ฟเวอร์ก็เพิ่ม 20% อินสแตนซ์ที่ใช้อยู่ใกล้ขีดจำกัดจะล้นทันที - บนกราฟ: ขึ้นเป็นขั้นบันไดจากจุดหนึ่ง (แพ็กเก็ตและไบต์ต่อวินาทีต่อผู้เล่น, ขนาดแพ็กเก็ตเฉลี่ย) - จุดที่ต้องดู: แพ็กเก็ตและไบต์ต่อวินาทีของ NIC เซิร์ฟเวอร์ (rxpck/s, txpck/s, rxkB/s และ txkB/s จาก sar -n DEV, บน EC2 คือ NetworkPacketsOut และ NetworkOut) หารด้วยจำนวนผู้เล่นออนไลน์พร้อมกัน แล้วเทียบก่อนและหลังเวลา deploy ขนาดแพ็กเก็ตเฉลี่ยคือไบต์ ÷ แพ็กเก็ต ส่วนการกระจายขนาด ให้ใช้สถิติ Packet Lengths ของ Wireshark กับ packet capture - สัญญาณว่าใช่: ตั้งแต่หลัง deploy แพ็กเก็ตและไบต์ต่อผู้เล่นหรือขนาดแพ็กเก็ตเฉลี่ยขึ้นหนึ่งขั้นแล้วอยู่ระดับนั้น และตั้งแต่เวลาเดียวกัน จำนวน fragment ที่เซิร์ฟเวอร์สร้าง (fragcrt/s ของ sar -n IP) หรือตัวนับเกินขีดจำกัดของอินสแตนซ์ (pps_allowance_exceeded และ bw_out_allowance_exceeded ของ AWS ENA) เพิ่มขึ้น - สัญญาณว่าไม่ใช่: รูปแบบทราฟฟิกก่อนและหลัง deploy เหมือนกัน แต่มีแค่ความหน่วงและแพ็กเก็ตหายที่เพิ่มขึ้น: ให้ดูการเปลี่ยนแปลงฝั่งอินฟราในเวลาเดียวกัน (คอนฟิก, เส้นทาง, อุปกรณ์, การอัปเดต OS หรือเคอร์เนล) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: เมื่อมีผู้เล่นแจ้งว่า “ก่อนลงแพตช์ยังเล่นได้ปกติ” นี่คือสาเหตุฝั่งเกมที่ต้องตรวจก่อนพร้อมกับการเปลี่ยนแปลงฝั่งอินฟรา ต่อให้แพตช์โน้ตไม่ได้ระบุการเปลี่ยนแปลงด้านเครือข่าย เอฟเฟกต์หรือรายการซิงก์ใหม่เพียงอย่างเดียวก็ถูกคูณด้วยคนหลายร้อยคนในที่ที่คนเยอะ จุดที่ทราฟฟิกที่เพิ่มขึ้นไปชนขีดจำกัดจริงอยู่ในสาเหตุ “IP fragmentation ของแพ็กเก็ต UDP”, “เกินขีดจำกัด PPS ของคลาวด์”, “แบนด์วิดท์ NIC เต็ม”, “บัฟเฟอร์ซ็อกเก็ตของเคอร์เนลไม่พอ” และ “อุปกรณ์กลางทางเกินขีดความสามารถ (ไฟร์วอลล์/IPS/ระบบป้องกัน DDoS)” ส่วนสาเหตุนี้คือกรณีที่จุดเริ่มต้นที่ทำให้ชนขีดจำกัดเหล่านั้นคือแพตช์เกม จึงควรลดทราฟฟิกที่เพิ่มขึ้นจากแพตช์ก่อนจะขยายขีดจำกัด ถ้าอัปเดต OS หรือเคอร์เนลในเวลาเดียวกันด้วย ให้แยกจาก “ประสิทธิภาพเปลี่ยนไปหลังอัปเดต OS/เคอร์เนล/ไดรเวอร์/เฟิร์มแวร์” โดยดูว่าทราฟฟิกต่อผู้เล่นเปลี่ยนหรือไม่ - แหล่งอ้างอิง: - [RFC 8085: UDP Usage Guidelines](https://www.rfc-editor.org/rfc/rfc8085) · IETF · แอป UDP ไม่ควรส่ง datagram ที่ใหญ่กว่า path MTU (SHOULD NOT), ถ้า fragment หายหนึ่งชิ้นจะเสียแพ็กเก็ตที่ถูกแบ่งชิ้นทั้งหมด และ NAT หรือไฟร์วอลล์บางตัวทิ้ง fragment ทั้งหมด - [RFC 8899: Packetization Layer Path MTU Discovery for Datagram Transports](https://www.rfc-editor.org/rfc/rfc8899) · IETF · แนะนำ 1,200 ไบต์เป็นขนาดปลอดภัยพื้นฐาน (BASE_PLPMTU) สำหรับการส่งแบบ datagram เช่น UDP - [Maximum transmission unit and maximum segment size](https://developers.cloudflare.com/magic-transit/reference/mtu-mss/) · Cloudflare · path MTU ของอินเทอร์เน็ต 1,500, ผ่าน GRE tunnel 1,476 - [Monitor network performance for ENA settings on your EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html) · AWS · pps_allowance_exceeded และ bw_out_allowance_exceeded: จำนวนแพ็กเก็ตที่ถูกพักในคิวหรือถูกทิ้งเพราะเกินขีดจำกัด PPS หรือแบนด์วิดท์ขาออกของอินสแตนซ์ - [sar(1) — Linux manual page](https://man7.org/linux/man-pages/man1/sar.1.html) · sysstat · rxpck/s และ txpck/s (แพ็กเก็ตต่อวินาที) และ rxkB/s และ txkB/s (KB ต่อวินาที) ใน sar -n DEV, fragcrt/s (IP fragment ที่สร้างต่อวินาที, ipFragCreates) ใน sar -n IP - [CloudWatch metrics that are available for your instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html) · AWS · NetworkPacketsOut (จำนวนแพ็กเก็ตที่อินสแตนซ์ส่งออกทาง network interface ทั้งหมด) และ NetworkOut (จำนวนไบต์ที่ส่ง) - [8.7. Packet Lengths](https://www.wireshark.org/docs/wsug_html_chunked/ChStatPacketLengths.html) · Wireshark · แบ่งแพ็กเก็ตที่ดักจับไว้ตามช่วงความยาว แล้วแสดงจำนวน ค่าเฉลี่ย ค่าต่ำสุด และค่าสูงสุดของแต่ละช่วง ### L10 หน่วยความจำ (9 สาเหตุ) #### mem-gc · GC ของเซิร์ฟเวอร์หยุดทั้งระบบ · Stop-the-world GC pause ระหว่างที่เซิร์ฟเวอร์ Java หรือ C# หยุดทุกเธรดเพื่อเก็บคืน garbage (stop-the-world) ทั้งเซิร์ฟเวอร์จะหยุดชะงักไปด้วย - ทำไม → ผลคือ → บนหน้าจอ: heap เต็ม GC จึงเริ่มทำงาน → หยุดเธรดเกมทั้งหมดแล้วเก็บคืน (ยิ่งมีข้อมูลที่ยังใช้อยู่มาก ยิ่งนาน) → ทุกคนบนเซิร์ฟเวอร์ค้างพร้อมกันแล้วตามด้วยอาการกรอเร็ว - อาการ: ค้าง, กรอเร็ว / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: เป็นรอบสม่ำเสมอ, ยิ่งเปิดไว้นานยิ่งเป็น - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ระบุ GC ที่หยุดสั้น (ZGC, Shenandoah หรือ G1 ที่ลดเวลาเป้าหมายลง) ในออปชันตอนรันโดยตรง, ลดการจัดสรรหน่วยความจำ, ปรับขนาด heap - งานฝั่งทีมอินฟรา: ใช้อินสแตนซ์ที่มีหน่วยความจำพอจะให้ heap ได้อย่างเหลือเฟือ, ให้คอนเทนเนอร์มี CPU อย่างน้อย 2 ตัวและหน่วยความจำประมาณ 1.8 GB ขึ้นไป (ถ้าน้อยกว่านั้น JDK 26 ลงไปจะเลือก Serial GC เป็นค่าเริ่มต้น), มอนิเตอร์เวลา GC pause - ตัวเลขที่ควรรู้: Minor GC ที่เก็บคืนเฉพาะออบเจ็กต์ใหม่ (Young generation) ใช้เวลาหลักหน่วยถึงหลักสิบ ms ส่วน Full GC ที่เก็บคืนทั้ง heap ซึ่งมีข้อมูลที่ยังใช้อยู่ (live data) หลาย GB อาจนานเกิน 1 วินาที ZGC หยุดไม่ถึง 1 ms แทบไม่ขึ้นกับขนาด heap และ Shenandoah ก็หยุดสั้นเพราะเวลาหยุดไม่แปรตามขนาด heap - บนกราฟ: พุ่งเป็นรอบ (เวลาต่อทิกของเซิร์ฟเวอร์, เวลา GC pause) - จุดที่ต้องดู: เปิด GC log แล้ววางช่วงที่หยุดและระยะเวลาที่หยุดซ้อนบนกราฟเวลาต่อทิกของเซิร์ฟเวอร์ Java ดูบรรทัด Pause จากออปชันตอนเริ่ม -Xlog:gc* (JDK 8 ลงไปใช้ -XX:+PrintGCDetails), .NET ดูเมตริก GC pause ของ dotnet-counters (ตั้งแต่ .NET 9 คือ dotnet.gc.pause.time, 8 ลงไปคือ % Time in GC since last GC), Go ดูบรรทัดที่ GODEBUG=gctrace=1 เขียนทุกครั้งที่ GC ทำงาน - สัญญาณว่าใช่: ช่วงที่ทิกพุ่งตรงกับช่วง GC pause และระยะเวลาที่หยุดใกล้เคียงกับระยะเวลาที่ทิกพุ่ง ทุกโซนและแชนแนลบนเซิร์ฟเวอร์พุ่งพร้อมกัน - สัญญาณว่าไม่ใช่: GC log ไม่มีการหยุดนานแต่ทิกยังพุ่ง: น่าจะเป็นสาเหตุอื่น เช่น ล็อก, synchronous call หรือการเขียนดิสก์ ถ้าพุ่งแค่โซนเดียว น่าจะเป็น GC ของสคริปต์เอนจิน (mem-script-gc) หรือโหลดของโซนนั้น - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: เป้าหมายเวลาหยุดต่อครั้งของ G1 ใน Java มีค่าเริ่มต้น 200 ms ซึ่งเท่ากับ 4 ทิกบนเซิร์ฟเวอร์ 20 ทิก ถ้าให้คอนเทนเนอร์มี CPU น้อยกว่า 2 ตัวหรือหน่วยความจำน้อยกว่าประมาณ 1.8 GB Java รุ่น JDK 26 ลงไปจะเลือก Serial GC ที่เก็บคืนด้วยเธรดเดียวเป็น GC เริ่มต้น ทำให้หยุดนานขึ้นมาก เซิร์ฟเวอร์ C# (.NET) มักเปิด server GC และ background GC ไว้ แต่การเก็บคืน generation 0 และ 1 (Gen0/1) ซึ่งเป็นที่อยู่ของออบเจ็กต์ใหม่ และ Full GC ที่มีการบีบอัด (compaction) ยังหยุดทุกเธรดอยู่ดี ส่วน Go ปกติหยุดไม่ถึง 1 ms แต่ถ้าจัดสรรหน่วยความจำมาก ฝั่งที่ขอหน่วยความจำต้องช่วยแบ่งงาน GC ทิกจึงช้าลง ไม่ว่าแบบไหน ถ้าจัดสรรเร็วกว่าเก็บคืน สุดท้ายเธรดเกมก็ต้องหยุด G1 จะเปลี่ยนไปทำ Full GC ส่วน ZGC จะหยุดเธรดที่ขอหน่วยความจำไว้จนกว่าการเก็บคืนจะเสร็จ - กรณีจริง: riot-euw-2021 - แหล่งอ้างอิง: - [Garbage-First (G1) Garbage Collector](https://docs.oracle.com/en/java/javase/25/gctuning/garbage-first-g1-garbage-collector1.html) · Oracle · ค่าเริ่มต้นของเป้าหมายเวลาหยุดของ G1 คือ 200 ms (MaxGCPauseMillis), ถ้าหน่วยความจำหมดระหว่างเก็บคืนจะเปลี่ยนไปทำ Full GC ที่หยุดทั้ง heap แล้วบีบอัด - [JEP 523: Make G1 the Default Garbage Collector in All Environments](https://openjdk.org/jeps/523) · OpenJDK · ที่ผ่านมาถ้ามี CPU 1 ตัวหรือหน่วยความจำน้อยกว่า 1792 MB จะเลือก Serial GC เป็นค่าเริ่มต้น และตั้งแต่ JDK 27 G1 เป็นค่าเริ่มต้นในทุกสภาพแวดล้อม - [JEP 439: Generational ZGC](https://openjdk.org/jeps/439) · OpenJDK · ZGC หยุดไม่เกิน 1 ms และไม่ขึ้นกับขนาด heap ส่วน G1 หยุดตั้งแต่หลาย ms ถึงหลายวินาที ถ้าจัดสรรเร็วกว่าเก็บคืนเสี่ยงเกิด allocation stall - [JEP 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)](https://openjdk.org/jeps/189) · OpenJDK · เวลาหยุดของ Shenandoah ใกล้เคียงกันไม่ว่า heap จะ 200 MB หรือ 200 GB - [Background garbage collection](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/background-gc) · .NET · background GC ใช้กับการเก็บคืน generation 2 เท่านั้น ส่วนการเก็บคืน generation 0 และ 1 (foreground GC) หยุด managed thread ทั้งหมด - [A Guide to the Go Garbage Collector](https://go.dev/doc/gc-guide) · Go · GC ของ Go ส่วนใหญ่ทำงานพร้อมกับโปรแกรม (concurrent) และมีแค่ช่วง stop-the-world สั้น ๆ, ถ้าจัดสรรมาก goroutine ต้องรับงาน GC ไปช่วยทำ (assist) จนเกิดดีเลย์ - [JEP 271: Unified GC Logging](https://openjdk.org/jeps/271) · OpenJDK · ตั้งแต่ JDK 9 GC log ถูกสร้างใหม่บน unified logging (-Xlog), -Xlog:gc เขียนหนึ่งบรรทัดต่อ GC หนึ่งครั้งเหมือน -XX:+PrintGC แบบเดิม - [The java Command](https://docs.oracle.com/en/java/javase/25/docs/specs/man/java.html) · Oracle · ตารางเทียบออปชัน GC log แบบเดิมกับ -Xlog: -XX:+PrintGCDetails คือ -Xlog:gc* - [dotnet-counters diagnostic tool](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters) · .NET · ตั้งแต่ .NET 9 แสดงเป็น meter ของ System.Runtime (dotnet.gc.pause.time ฯลฯ) ส่วน .NET 8 ลงไปแสดงเป็น EventCounter แบบเดิม (% Time in GC since last GC ฯลฯ) - [runtime package](https://pkg.go.dev/runtime) · Go · GODEBUG=gctrace=1: หนึ่งบรรทัดต่อ GC หนึ่งครั้ง, wall clock time ของแต่ละเฟส, ขนาด heap ตอนเริ่มและจบ GC และ heap เป้าหมาย #### mem-script-gc · GC pause ของสคริปต์เอนจิน · Scripting VM GC (Lua, etc.) แม้เซิร์ฟเวอร์จะเขียนด้วย C++ แต่ถ้ารันเควสต์, AI และสกิลด้วยสคริปต์อย่าง Lua ระหว่างที่ GC ของสคริปต์เอนจินทำงาน โซนนั้นจะหยุดชะงัก - ทำไม → ผลคือ → บนหน้าจอ: สคริปต์เอนจินของแต่ละโซนรันเควสต์, AI และอีเวนต์ พร้อมสร้างออบเจ็กต์ชั่วคราวจำนวนมาก → ถ้า GC ของสคริปต์เอนจินเก็บคืนทีละมาก ๆ ทิกของโซนนั้นจะหยุดชะงัก → หยุดแวบเป็นรอบเฉพาะในบางโซนหรือระหว่างบางอีเวนต์ - อาการ: กระตุก, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, เป็นรอบสม่ำเสมอ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ตั้งค่า GC แบบ incremental หรือ generational, ให้ GC ทำงานทีละน้อยทุกทิก, ลดออบเจ็กต์ชั่วคราวในสคริปต์ - ตัวเลขที่ควรรู้: เมื่อ heap ของสคริปต์โตถึงหลายร้อย MB การเก็บคืนรวดเดียว (เมื่อปิด incremental collection หรือเป็น full collection ในโหมด generational) อาจใช้เวลาหลายสิบถึงหลายร้อย ms - บนกราฟ: พุ่งเป็นรอบ (เวลาต่อทิกแยกตามโซน, หน่วยความจำของสคริปต์เอนจิน) - จุดที่ต้องดู: บันทึกเวลาต่อทิกของแต่ละโซนและปริมาณหน่วยความจำของสคริปต์เอนจินในโซนนั้น (Lua ใช้ collectgarbage("count")) ทุกทิก แล้ววางซ้อนบนกราฟเดียวกัน - สัญญาณว่าใช่: จังหวะที่หน่วยความจำของสคริปต์ดิ่งลง (เก็บคืนรวดเดียว) ตรงกับจังหวะที่ทิกของโซนนั้นพุ่ง และโซนอื่นปกติดี - สัญญาณว่าไม่ใช่: ทิกพุ่งโดยหน่วยความจำของสคริปต์ไม่เปลี่ยน: น่าจะเป็นโหลดหรือล็อกของโซนนั้น ถ้าทุกโซนบนเซิร์ฟเวอร์พุ่งพร้อมกัน น่าจะเป็น GC ของเซิร์ฟเวอร์ (mem-gc) หรือสวอป (mem-swap) - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Lua 5.4 Reference Manual](https://www.lua.org/manual/5.4/manual.html) · Lua.org · โหมด incremental แบ่งการเก็บคืนเป็นขั้นเล็ก ๆ แทรกระหว่างการรันโปรแกรม (ถ้าตั้งขั้นใหญ่จะกลายเป็น stop-the-world), major collection ในโหมด generational เป็น stop-the-world ที่ไล่ทุกออบเจ็กต์, collectgarbage("count") คือปริมาณหน่วยความจำทั้งหมดที่ Lua ใช้ (KB) #### mem-alloc · การจัดสรรหน่วยความจำพุ่ง · Allocation storms ถ้าสร้างออบเจ็กต์ชั่วคราวจำนวนมากระหว่างอีเวนต์ GC จะทำงานบ่อยกว่าปกติมาก - ทำไม → ผลคือ → บนหน้าจอ: ออบเจ็กต์ชั่วคราวพุ่งจากไอเทมดรอป, log การต่อสู้ และของรางวัลอีเวนต์ → GC ทำงานบ่อยขึ้นหลายเท่า และออบเจ็กต์ที่ยังไม่ทันถูกทิ้งย้ายไปอยู่ใน Old generation ทำให้ Full GC มาเร็วขึ้นด้วย → หยุดแวบเป็นรอบเฉพาะช่วงอีเวนต์ - อาการ: กระตุก, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางจุด/บางแชนแนล / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ใช้ object pool, ใช้บัฟเฟอร์ซ้ำ, ทำ allocation profiling - บนกราฟ: สูงตามจำนวนคนและโหลด (จำนวนครั้งที่ GC ทำงาน, อัตราการจัดสรรหน่วยความจำ) - จุดที่ต้องดู: นับจำนวนครั้งที่ GC ทำงานต่อนาทีจาก GC log (Java -Xlog:gc, Go GODEBUG=gctrace=1) ส่วน .NET ดูปริมาณการจัดสรรและจำนวนครั้งของ GC ใน dotnet-counters (ตั้งแต่ .NET 9 คือ dotnet.gc.heap.total_allocated และ dotnet.gc.collections, 8 ลงไปคือ Allocation Rate และ Gen 0 GC Count) วางซ้อนกับจำนวนผู้เล่นออนไลน์พร้อมกันและเวลาของอีเวนต์ - สัญญาณว่าใช่: พออีเวนต์เริ่ม อัตราการจัดสรรและจำนวนครั้งของ GC เพิ่มชันกว่าจำนวนผู้เล่นที่เพิ่มขึ้น และหยุดสั้น ๆ ถี่ขึ้น พออีเวนต์จบก็กลับเป็นปกติ - สัญญาณว่าไม่ใช่: จำนวนครั้งของ GC เท่าเดิมแต่หยุดแต่ละครั้งนานขึ้น: ข้อมูลที่ยังใช้อยู่เพิ่มขึ้น (mem-gc-thrash, mem-leak) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Garbage Collector Implementation](https://docs.oracle.com/en/java/javase/25/gctuning/garbage-collector-implementation.html) · Oracle · เมื่อ Young generation เต็มจะเกิด minor GC, ออบเจ็กต์ที่รอดบางส่วนย้ายไป Old generation และเมื่อ Old เต็มจะเก็บคืนทั้ง heap (นานกว่า minor มาก), -Xlog:gc เขียนหนึ่งบรรทัดต่อ GC หนึ่งครั้ง - [A Guide to the Go Garbage Collector](https://go.dev/doc/gc-guide) · Go · ยิ่งอัตราการจัดสรรสูง รอบ GC ยิ่งถี่, ใช้ GODEBUG=gctrace=1 แสดงผลการติดตาม GC - [dotnet-counters diagnostic tool](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters) · .NET · ตั้งแต่ .NET 9 แสดงเป็น dotnet.gc.heap.total_allocated และ dotnet.gc.collections ส่วน .NET 8 ลงไปแสดงเป็น Allocation Rate และ Gen 0 GC Count #### mem-leak · หน่วยความจำรั่ว · Memory leak หน่วยความจำที่ไม่ถูกคืนค่อย ๆ สะสม จนหลายวันต่อมานำไปสู่ GC ทำงานไม่หยุด, สวอป หรือโปรเซสถูกบังคับปิด - ทำไม → ผลคือ → บนหน้าจอ: ข้อมูลตัวละครที่ออกจากเกมไปแล้วและ event handler ไม่ถูกคืนหน่วยความจำ → หน่วยความจำว่างลดลงเรื่อย ๆ ตลอดหลายวัน → หลังปิดปรับปรุงใหม่ ๆ ยังปกติ ยิ่งผ่านไปหลายวันยิ่งแลค สุดท้ายเซิร์ฟเวอร์ล่ม - อาการ: สโลว์โมชั่น, ค้าง, หลุด / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: วิเคราะห์ heap dump, ทดสอบโหลดต่อเนื่องเป็นเวลานาน - งานฝั่งทีมอินฟรา: มอนิเตอร์แนวโน้มการใช้หน่วยความจำของแต่ละโปรเซสและตั้ง alert - บนกราฟ: ค่อย ๆ สูงขึ้น (หน่วยความจำของโปรเซส (RSS), heap หลัง GC) - จุดที่ต้องดู: ดูหน่วยความจำของโปรเซสเซิร์ฟเวอร์เกม (RSS จาก pidstat -r) เป็นช่วงหลายวัน และสำหรับเซิร์ฟเวอร์ที่ใช้ GC ให้ดู heap ที่เหลือหลัง GC Java ดูค่าหลัง GC จากปริมาณการใช้ก่อนและหลัง GC ในบรรทัดของ -Xlog:gc, .NET ดูขนาด heap หลัง GC ใน dotnet-counters (ตั้งแต่ .NET 9 คือ dotnet.gc.last_collection.heap.size, 8 ลงไปคือ GC Heap Size) - สัญญาณว่าใช่: heap ที่เหลือหลัง GC (เส้นฐาน) สูงขึ้นทุกวันหลังรีสตาร์ต และไม่ลดลงแม้ช่วงเช้ามืดที่ผู้เล่นน้อย - สัญญาณว่าไม่ใช่: เส้นฐานของ heap แบนราบแต่ RSS สูงขึ้นอย่างเดียว: น่าจะเป็น fragmentation (mem-fragment) หรือหน่วยความจำ native ถ้าขึ้นลงตามจำนวนผู้เล่นคือการใช้งานปกติ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ถ้ารีสตาร์ตทุกสัปดาห์ตอนปิดปรับปรุงตามรอบ อาการรั่วจะถูกกลบจนไม่มีใครเจอไปนาน และมักโผล่ขึ้นมากะทันหันเมื่อเลื่อนการปิดปรับปรุงไปหนึ่งครั้ง หรือเมื่อมีอีเวนต์ที่ทำให้ผู้เล่นเพิ่มขึ้น - แหล่งอ้างอิง: - [Troubleshoot Memory Leaks](https://docs.oracle.com/en/java/javase/25/troubleshoot/troubleshooting-memory-leaks.html) · Oracle · ถ้าโปรแกรมช้าลงเรื่อย ๆ ให้สงสัยหน่วยความจำรั่ว สุดท้ายหน่วยความจำจะหมดและโปรแกรมปิดตัวผิดปกติ, ข้อมูลหลักในการวิเคราะห์การรั่วคือ heap dump - [Debug a memory leak in .NET](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/debug-memory-leak) · .NET · แม้มี GC แต่ถ้ายังอ้างอิงออบเจ็กต์ที่ไม่ต้องใช้แล้วไว้ก็คือหน่วยความจำรั่ว ทำให้ประสิทธิภาพตกและเกิด OutOfMemoryException, ตรวจแนวโน้มหน่วยความจำและวิเคราะห์ dump - [Garbage Collector Implementation](https://docs.oracle.com/en/java/javase/25/gctuning/garbage-collector-implementation.html) · Oracle · บรรทัดของ -Xlog:gc อยู่ในรูปแบบ “ปริมาณที่ใช้ก่อน GC->ปริมาณที่ใช้หลัง GC (ขนาด heap)” - [dotnet-counters diagnostic tool](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters) · .NET · ตั้งแต่ .NET 9 แสดงเป็น dotnet.gc.last_collection.heap.size ส่วน .NET 8 ลงไปแสดงเป็น GC Heap Size - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -r: RSS ของแต่ละโปรเซส (หน่วยความจำที่อยู่ใน RAM จริง) และ page fault #### mem-gc-thrash · GC thrashing (heap เหลือที่ว่างไม่พอ) · GC thrashing (heap nearly full) เมื่อข้อมูลที่ยังใช้อยู่ (live data) เข้าใกล้ขีดจำกัดของ heap แม้ GC ทำงานก็แทบไม่มีอะไรให้เก็บคืน GC จึงวนทำงานซ้ำไม่หยุด - ทำไม → ผลคือ → บนหน้าจอ: ผู้เล่นเพิ่มขึ้นช่วงอีเวนต์หรือหน่วยความจำรั่ว ทำให้ข้อมูลที่ยังใช้อยู่เต็มจนใกล้ขีดจำกัดของ heap → GC เก็บคืนได้นิดเดียวจึงต้องทำ Full GC อีกทันที และ GC ใช้ CPU ไปเกือบทั้งหมด → ทั้งเซิร์ฟเวอร์เป็นสโลว์โมชั่นสลับกับค้างซ้ำ ๆ อยู่หลายนาที แล้วปิดตัวเพราะหน่วยความจำไม่พอ - อาการ: สโลว์โมชั่น, ค้าง, หลุด / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ช่วงพีคหัวค่ำ, ตอนคนแห่มารวมกัน, ยิ่งเปิดไว้นานยิ่งเป็น - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ตั้ง heap ให้ใหญ่กว่าข้อมูลที่ยังใช้อยู่ตอนพีคอย่างเหลือเฟือ (ปกติ 2 เท่าขึ้นไป), ลดข้อมูลที่ถูกอ้างอิงไว้นานและลดการรั่ว - งานฝั่งทีมอินฟรา: ตั้ง alert ตามสัดส่วนเวลาที่ใช้ไปกับ GC, รีสตาร์ตให้เร็วแทนการฝืนรันต่อ, ใช้อินสแตนซ์ที่มีหน่วยความจำเหลือพอให้ขยาย heap ได้ - ตัวเลขที่ควรรู้: ถ้า GC ใช้เวลาเกิน 10% ของเวลารันทั้งหมด มักถือเป็นสัญญาณอันตราย GC บางตัวของ Java จะแจ้งข้อผิดพลาดหน่วยความจำไม่พอ ถ้าใช้เวลา 98% ไปกับ GC แต่แทบเก็บคืนอะไรไม่ได้ - บนกราฟ: ชนเพดานแล้วแบนราบ (heap หลัง GC, สัดส่วนเวลาที่ใช้ไปกับ GC) - จุดที่ต้องดู: heap ที่เหลือหลัง GC ใกล้ heap สูงสุดแค่ไหน และสัดส่วนเวลาที่ใช้ไปกับ GC Java ดู “หลัง GC (ขนาด heap)” ในบรรทัดของ -Xlog:gc และความถี่ของบรรทัด Pause Full, .NET ดู dotnet-counters (.NET 8 ลงไปคือ % Time in GC since last GC, ตั้งแต่ .NET 9 คือค่าที่เพิ่มขึ้นของ dotnet.gc.pause.time), Go ดูช่วงห่างระหว่างบรรทัดของ GODEBUG=gctrace=1 - สัญญาณว่าใช่: แม้หลัง GC heap ก็ยังเหลือใกล้ค่าสูงสุด Full GC ทำงานติดกันหลายครั้ง และสัดส่วนเวลาที่ใช้ไปกับ GC สูงกว่าปกติมาก (ปกติเกิน 10%) ระหว่างนั้นทิกของทั้งเซิร์ฟเวอร์ช้าลงไปด้วย - สัญญาณว่าไม่ใช่: heap หลัง GC ยังมีที่ว่างแต่หยุดนานอย่างเดียว: น่าจะเป็นเรื่องประเภทหรือการตั้งค่า GC (mem-gc) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [The Parallel Collector](https://docs.oracle.com/en/java/javase/25/gctuning/parallel-collector1.html) · Oracle · parallel GC จะแจ้ง OutOfMemoryError ถ้าใช้เวลาเกิน 98% ของเวลาทั้งหมดไปกับ GC แล้วเก็บคืน heap ได้น้อยกว่า 2% - [Garbage-First Garbage Collector Tuning](https://docs.oracle.com/en/java/javase/25/gctuning/garbage-first-garbage-collector-tuning.html) · Oracle · ค่าเริ่มต้นของ G1 (GCTimeRatio=12) จะกำหนดขนาด heap ให้เวลา GC อยู่ที่ประมาณไม่เกิน 8% ของเวลาทั้งหมด, Full GC ที่เกิดจาก heap ถูกใช้สูงเกินไปหาได้จาก Pause Full (G1 Compaction Pause) ใน log - [A Guide to the Go Garbage Collector](https://go.dev/doc/gc-guide) · Go · ค่าเริ่มต้น GOGC=100 ทำให้ heap เป้าหมายประมาณ 2 เท่าของ heap ที่ยังใช้อยู่, ถ้าชนขีดจำกัดหน่วยความจำ GC จะทำงานไม่หยุดจนเกิด thrashing, ใช้ GODEBUG=gctrace=1 แสดงผลการติดตาม GC - [Garbage Collector Implementation](https://docs.oracle.com/en/java/javase/25/gctuning/garbage-collector-implementation.html) · Oracle · บรรทัดของ -Xlog:gc แสดงประเภท GC (Pause Young, Pause Full), “ปริมาณที่ใช้ก่อน GC->ปริมาณที่ใช้หลัง GC (ขนาด heap)” และเวลาที่หยุด - [dotnet-counters diagnostic tool](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters) · .NET · ตั้งแต่ .NET 9 แสดงเป็น dotnet.gc.pause.time ส่วน .NET 8 ลงไปแสดงเป็น % Time in GC since last GC #### mem-swap · สวอป · Swapping ถ้าหน่วยความจำไม่พอจน OS ย้ายบางส่วนไปไว้บนดิสก์ ทุกครั้งที่ใช้หน่วยความจำส่วนนั้นต้องรอดิสก์ที่ช้ากว่าเกิน 1,000 เท่า - ทำไม → ผลคือ → บนหน้าจอ: หน่วยความจำที่ใช้เกิน RAM จริง → OS ย้ายบางส่วนไปไว้บนดิสก์และอ่านกลับเมื่อต้องใช้ → เวลาต่อทิกพุ่งเป็นหลายร้อย ms ผู้เล่นทุกคนบนเซิร์ฟเวอร์เจอสโลว์โมชั่นและค้าง - อาการ: สโลว์โมชั่น, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: กำหนดเพดานการใช้หน่วยความจำของโปรเซส (เช่น ขนาด heap) และตรวจหาการรั่ว - งานฝั่งทีมอินฟรา: ตั้งไม่ให้เซิร์ฟเวอร์เกมใช้สวอป, รับมือด้วย alert หน่วยความจำ, ถ้าปิดสวอป โปรเซสจะถูกบังคับปิด (OOM) ทันทีที่หน่วยความจำไม่พอ จึงต้องมี RAM เหลือมากกว่าการใช้งานตอนพีคอย่างเหลือเฟือ - ตัวเลขที่ควรรู้: อ่าน RAM ประมาณ 100 ns, อ่านกลับจาก SSD ประมาณ 100 µs (1,000 เท่า), ดิสก์คลาวด์ที่อยู่อีกฝั่งของเครือข่ายประมาณ 1 ms (1 หมื่นเท่า), HDD 10 ms (1 แสนเท่า) - บนกราฟ: ค่อย ๆ สูงขึ้น (ปริมาณการใช้สวอป, swap in/out) - จุดที่ต้องดู: คอลัมน์ si และ so ของ vmstat 1 (ปริมาณที่อ่านเข้าจากสวอปและเขียนออกไปสวอปต่อวินาที), some และ full ใน /proc/pressure/memory (สัดส่วนเวลาที่หยุดรอหน่วยความจำ) และ majflt/s จาก pidstat -r ของโปรเซสเซิร์ฟเวอร์เกม (page fault ที่ต้องอ่านจากดิสก์) วางซ้อนกับเวลาต่อทิก - สัญญาณว่าใช่: ช่วงที่แลค si มากกว่า 0 และ majflt/s ของเซิร์ฟเวอร์เกมกับค่า full ของ memory สูงขึ้นพร้อมกัน - สัญญาณว่าไม่ใช่: si และ so เป็น 0 และแรงกดดันของ memory (PSI) ก็ใกล้ 0: สวอปไม่ใช่สาเหตุ ถ้าไม่มีสวอปแต่ majflt/s และ PSI สูงขึ้น แปลว่าหน่วยความจำหมดจนต้องอ่าน code page ซ้ำ ต้องหาหน่วยความจำเพิ่มก่อน - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: เซิร์ฟเวอร์ที่ใช้ GC ต้องอ่าน heap ทั่วทุกส่วนตอนเก็บคืน แม้ heap ถูกสวอปออกไปแค่บางส่วน GC หนึ่งครั้งก็อาจยืดเป็นหลายวินาทีถึงหลายสิบวินาที ถ้าปิดสวอป โปรเซสจะข้ามช่วงที่ช้าลงเพราะสวอปไปถูกบังคับปิด (OOM) ทันที จึงต้องกันหน่วยความจำเหลือไว้ก่อน แม้ไม่มีสวอป ถ้าหน่วยความจำใกล้หมด OS จะเอาแม้แต่ code page ของไฟล์โปรแกรมออกจากหน่วยความจำแล้วอ่านกลับซ้ำ ๆ ทั้งเซิร์ฟเวอร์จึงอาจช้าลงอย่างหนักไปพักหนึ่งก่อนถูกบังคับปิด - แหล่งอ้างอิง: - [Designs, Lessons and Advice from Building Large Distributed Systems (LADIS 2009 keynote)](https://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf) · Google · “Numbers Everyone Should Know”: อ้างอิงหน่วยความจำหลัก 100 ns, seek ดิสก์ 10 ms (ข้อมูลปี 2009) - [Documentation for /proc/sys/vm/](https://docs.kernel.org/admin-guide/sysctl/vm.html) · Linux kernel · swappiness: ต้นทุนเทียบกันระหว่างการสวอปกับการเก็บคืน file page, สวอปเป็น random I/O จึงแพง - [Concepts overview](https://docs.kernel.org/admin-guide/mm/concepts.html) · Linux kernel · เก็บคืน page cache ที่มีต้นฉบับอยู่บนดิสก์และ page ที่สวอปได้ ถ้ายังไม่พอ OOM killer จะฆ่าโปรเซส - [Solidigm™ D7-P5520 and D7-P5620 Product Brief](https://www.solidigm.com/products/data-center/product-briefs/d7-p5520-p5620-product-brief.html) · Solidigm · ความหน่วงที่ 99.99% (four-nines latency) ของ NVMe SSD สำหรับเซิร์ฟเวอร์คือ 130 µs: เป็นหลักฐานว่าการอ่าน SSD หนึ่งครั้งใช้ราว 100 µs - [Amazon EBS General Purpose SSD volumes](https://docs.aws.amazon.com/ebs/latest/userguide/general-purpose.html) · AWS · ความหน่วงของดิสก์คลาวด์พื้นฐาน (gp3) อยู่ในระดับหลักหน่วย ms - [vmstat(8) — Linux manual page](https://man7.org/linux/man-pages/man8/vmstat.8.html) · procps-ng · si: หน่วยความจำที่อ่านเข้าจากสวอปต่อวินาที, so: หน่วยความจำที่เขียนออกไปสวอปต่อวินาที - [PSI - Pressure Stall Information](https://docs.kernel.org/accounting/psi.html) · Linux kernel · some (สัดส่วนเวลาที่งานบางส่วนหยุด) และ full (สัดส่วนเวลาที่งานทั้งหมดหยุดพร้อมกัน) ใน /proc/pressure/memory - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -r: majflt/s (จำนวน fault ที่ต้องอ่าน page จากดิสก์) #### mem-cache-miss · แคชมิส · CPU cache misses ถ้าข้อมูลกระจัดกระจายอยู่ทั่วหน่วยความจำ CPU ต้องไปรอถึง RAM ที่ช้าทุกครั้ง - ทำไม → ผลคือ → บนหน้าจอ: ออบเจ็กต์กระจัดกระจายเชื่อมกันด้วยพอยน์เตอร์ และถูกเข้าถึงแบบไม่เรียงลำดับ → ไม่มีข้อมูลในแคชของ CPU จึงต้องอ่านจาก RAM ทุกครั้ง (ช้ากว่าราว 100 เท่า) → งานเท่าเดิมแต่ต้นทุนต่อทิกเพิ่มเป็นหลายเท่า ถ้าหนักจะเป็นสโลว์โมชั่น - อาการ: สโลว์โมชั่น / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: จัดข้อมูลที่ใช้คู่กันบ่อยให้อยู่ติดกัน (data-oriented design) - บนกราฟ: สูงตลอดตั้งแต่แรก (เวลาต่อทิก, อัตราการใช้ CPU) - จุดที่ต้องดู: ใช้ perf stat -d -p PID กับโปรเซสเซิร์ฟเวอร์เกมเพื่อวัดจำนวนคำสั่งต่อรอบสัญญาณนาฬิกา (insn per cycle) และ cache miss ของ L1 และ LLC แล้วดูคู่กับเวลาต่อทิกและอัตราการใช้ CPU - สัญญาณว่าใช่: CPU ยุ่งตลอดแต่ insn per cycle ต่ำและ LLC miss สูง ถ้าบิลด์ที่เปลี่ยนการจัดวางข้อมูลทำให้เวลาต่อทิกลดลงมากที่จำนวนผู้เล่นเท่ากัน ถือว่ายืนยันได้ - สัญญาณว่าไม่ใช่: อัตราการใช้ CPU ต่ำแต่ทิกช้า: น่าจะเป็นสาเหตุที่รออยู่นอก CPU เช่น ล็อกหรือรอ I/O - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Designs, Lessons and Advice from Building Large Distributed Systems (LADIS 2009 keynote)](https://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf) · Google · แคช L1 ใช้ 0.5 ns, แคช L2 ใช้ 7 ns, หน่วยความจำหลัก 100 ns (ข้อมูลปี 2009): ถ้าต้องไปถึง RAM จะช้ากว่าแคชราวสิบถึงร้อยเท่า - [perf-stat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/perf-stat.1.html) · perf · -p นับ hardware event ของโปรเซสที่รันอยู่และแสดง insn per cycle, -d เพิ่ม event ของแคชข้อมูล L1 และ LLC #### mem-fragment · หน่วยความจำแตกกระจาย (fragmentation) · Heap fragmentation ถ้าจัดสรรและคืนหน่วยความจำซ้ำไปมาจนพื้นที่ว่างแตกเป็นชิ้นเล็ก ๆ โปรเซสจะถือครองหน่วยความจำมากกว่าที่ใช้จริงมาก - ทำไม → ผลคือ → บนหน้าจอ: หลายเธรดจัดสรรและคืนหน่วยความจำขนาดไม่เท่ากันเป็นเวลานาน → พื้นที่ว่างกระจายเป็นชิ้นเล็ก ๆ คืนให้ OS ไม่ได้ ปริมาณการใช้จึงเพิ่มขึ้นเรื่อย ๆ เหมือนหน่วยความจำรั่ว → ยิ่งเปิดไว้นานยิ่งช้าลงเพราะสวอปและหน่วยความจำไม่พอ แล้วถูกบังคับปิด - อาการ: สโลว์โมชั่น, หลุด / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ใช้ memory pool แยกตามขนาด, ใช้ allocator ที่ทนต่อ fragmentation (jemalloc, mimalloc ฯลฯ) - บนกราฟ: ค่อย ๆ สูงขึ้น (หน่วยความจำของโปรเซส (RSS)) - จุดที่ต้องดู: รันเซิร์ฟเวอร์จากบิลด์เดียวกันสองตัว ตัวหนึ่งลดจำนวน arena ของ glibc ด้วยตัวแปรสภาพแวดล้อม MALLOC_ARENA_MAX หรือเปลี่ยนไปใช้ allocator อื่นอย่าง jemalloc แล้วเทียบ RSS จาก pidstat -r เป็นเวลาหลายวัน - สัญญาณว่าใช่: จำนวนผู้เล่นและจำนวนเอนทิตีใกล้เคียงกัน แต่เฉพาะเซิร์ฟเวอร์ที่เปลี่ยนแล้ว RSS หยุดเพิ่มหรือลดลงมาก - สัญญาณว่าไม่ใช่: เปลี่ยน allocator แล้วยังขึ้นเหมือนเดิม: หน่วยความจำที่ไม่ได้คืน (mem-leak) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: รูปแบบเหมือนหน่วยความจำรั่ว การวิเคราะห์ heap จึงหาจุดที่รั่วไม่เจอ อาการนี้หนักเป็นพิเศษกับ allocator พื้นฐานของ Linux (glibc) บนเซิร์ฟเวอร์ที่มีเธรดเยอะ บางครั้งแค่เปลี่ยน allocator ปริมาณการใช้ก็ลดลงมาก - แหล่งอ้างอิง: - [mallopt(3) — Linux manual page](https://man7.org/linux/man-pages/man3/mallopt.3.html) · Linux man-pages · glibc malloc สร้าง arena ได้ถึงจำนวนที่เป็นพหุคูณของจำนวน CPU เพื่อลดการแย่งกันระหว่างเธรด และยิ่งมี arena มาก หน่วยความจำยิ่งถูกใช้มาก (จำกัดด้วย M_ARENA_MAX หรือตั้งผ่านตัวแปรสภาพแวดล้อม MALLOC_ARENA_MAX ก็ได้) - [jemalloc memory allocator](https://jemalloc.net/) · jemalloc · malloc อเนกประสงค์ที่เน้นหลีกเลี่ยง fragmentation และรองรับ concurrency ที่สเกลได้ดี - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -r: RSS ของแต่ละโปรเซส (หน่วยความจำที่อยู่ใน RAM จริง) #### mem-numa · หน่วยความจำ NUMA ฝั่งไกล (remote) · Remote NUMA access บนเซิร์ฟเวอร์ที่มี CPU สองตัว ถ้าใช้หน่วยความจำที่ต่ออยู่กับ CPU อีกฝั่ง การเข้าถึงจะช้าลง - ทำไม → ผลคือ → บนหน้าจอ: เธรดกับหน่วยความจำถูกวางไว้คนละซ็อกเก็ต CPU → การเข้าถึงหน่วยความจำช้าลง (1.5–2 เท่า แล้วแต่เครื่อง) → สเปกเดียวกันแต่ประสิทธิภาพต่างกันในแต่ละโปรเซส - อาการ: สโลว์โมชั่น / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: ผูกโปรเซสและหน่วยความจำไว้กับซ็อกเก็ตเดียว (numactl), ถ้ามีสองซ็อกเก็ตให้แยกโปรเซสเซิร์ฟเวอร์เกมไปวางซ็อกเก็ตละชุด - บนกราฟ: สูงเฉพาะบางกลุ่ม (เวลาต่อทิกแยกตามโปรเซส, หน่วยความจำแยกตามโหนด) - จุดที่ต้องดู: ใช้ numastat -p PID ดูว่าหน่วยความจำของโปรเซสเซิร์ฟเวอร์เกมอยู่บน NUMA โหนดไหน และ numa_miss กับ other_node ของ numastat เพิ่มขึ้นหรือไม่ แล้วเทียบกับโหนดของ CPU ที่โปรเซสนั้นรันอยู่ - สัญญาณว่าใช่: เฉพาะโปรเซสที่ช้า หน่วยความจำส่วนใหญ่อยู่คนละโหนดกับ CPU ที่ตัวเองรัน และเมื่อใช้ numactl ผูก CPU กับหน่วยความจำไว้โหนดเดียวแล้วรันใหม่ ความต่างก็หายไป - สัญญาณว่าไม่ใช่: การวางโหนดเหมือนกับโปรเซสที่เร็วแต่ยังช้า: น่าจะเป็นสาเหตุอื่น เช่น noisy neighbor, CPU throttling หรือโหลดของโปรเซสนั้น - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [What is NUMA?](https://docs.kernel.org/mm/numa.html) · Linux kernel · หน่วยความจำใน cell เดียวกันเร็วกว่าและมีแบนด์วิดท์สูงกว่า ส่วนหน่วยความจำใน cell อื่น (remote) เข้าถึงช้ากว่า - [numactl(8) — Linux manual page](https://man7.org/linux/man-pages/man8/numactl.8.html) · numactl · ใช้ --cpunodebind และ --membind ผูก CPU และหน่วยความจำของโปรเซสไว้กับ NUMA โหนดที่กำหนด - [numastat(8) — Linux manual page](https://man7.org/linux/man-pages/man8/numastat.8.html) · numactl · ตัวนับ numa_miss (จัดสรรบนโหนดที่ไม่ใช่โหนดที่ต้องการ) และ other_node (โปรเซสที่รันบนโหนดอื่นมาจัดสรรบนโหนดนี้), -p แสดงหน่วยความจำแยกตามโหนดของโปรเซส ### L11 ดิสก์ (9 สาเหตุ) #### dk-sync-log · การเขียน log แบบ synchronous · Synchronous logging ถ้าเธรดเกมต้องรอให้ดิสก์เขียนเสร็จทุกครั้งที่เขียน log หนึ่งบรรทัด พอดิสก์ยุ่ง เกมก็หยุดเดินไปด้วย - ทำไม → ผลคือ → บนหน้าจอ: เขียน log การต่อสู้และการเทรดลงไฟล์โดยตรงจากเธรดเกม → ถ้าสั่งให้เขียนลงดิสก์แบบแน่นอน (fsync) หรือบัฟเฟอร์การเขียนของ OS (page cache) เต็มถึงขีดจำกัด ตอนที่ดิสก์ยุ่ง การเขียนหนึ่งครั้งจะใช้เวลาหลายสิบ ms → หยุดแวบในการต่อสู้ที่มี log เยอะ - อาการ: กระตุก, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ใช้ async logging (บัฟเฟอร์ในหน่วยความจำ + เธรดแยก), ลดปริมาณ log, ไม่เรียก fsync ในเธรดเกม - งานฝั่งทีมอินฟรา: รัน log rotation และการบีบอัด log ด้วยลำดับความสำคัญ I/O ต่ำ, แยก log ไว้คนละดิสก์กับข้อมูล, มอนิเตอร์ดีเลย์การเขียนดิสก์ - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (เวลาต่อทิกของเซิร์ฟเวอร์, ดีเลย์การเขียนดิสก์) - จุดที่ต้องดู: w_await และ aqu-sz จาก iostat -x 1 วางซ้อนกับเวลาต่อทิก และใช้ perf trace -p PID --duration 10 หาการเรียก write และ fsync ในเซิร์ฟเวอร์เกมที่ใช้เวลาเกิน 10 ms พร้อมเธรดที่เรียก - สัญญาณว่าใช่: ช่วงที่ทิกพุ่ง การเรียก write และ fsync ของเธรดเกมใช้เวลาหลายสิบ ms และดีเลย์การเขียนดิสก์ก็พุ่งในจังหวะเดียวกัน มักตรงกับเวลาทำ log rotation หรือบีบอัด log - สัญญาณว่าไม่ใช่: เธรดเกมไม่มี system call ที่ใช้เวลานานแต่ทิกยังพุ่ง: น่าจะเป็นสาเหตุอื่น เช่น GC, ล็อก หรือทิกเกินงบ ถ้านานเฉพาะเธรดที่เขียน log อย่างเดียว จะไม่กระทบการเดินของเกม - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ปกติ OS จะรับการเขียนไว้ในหน่วยความจำ (page cache) ก่อนแล้วค่อยเขียนลงดิสก์ทีหลัง log หนึ่งบรรทัดจึงมักเสร็จทันที การหยุดเกิดขึ้นเมื่อสั่งให้เขียนแบบแน่นอนด้วย fsync, เมื่อการเขียนที่กองรอเกินขีดจำกัดจน OS บล็อกการเรียกเขียน และเมื่อทำ rotation หรือบีบอัดไฟล์ log ด้วยเหตุนี้ ปกติจึงไม่มีปัญหา แต่จะพุ่งเฉพาะตอนที่ดิสก์ยุ่ง - แหล่งอ้างอิง: - [fsync(2) — Linux manual page](https://man7.org/linux/man-pages/man2/fsync.2.html) · Linux man-pages · fsync ส่งข้อมูลที่เปลี่ยนแปลงลงไปถึงดิสก์ (รวมแคชของดิสก์) และบล็อกจนกว่าอุปกรณ์จะแจ้งว่าเสร็จ - [Documentation for /proc/sys/vm/](https://docs.kernel.org/admin-guide/sysctl/vm.html) · Linux kernel · เมื่อการเขียนที่กองรอ (dirty) ถึง dirty_ratio โปรเซสที่กำลังเขียนต้องรับหน้าที่เขียนลงดิสก์เอง - [ionice(1) — Linux manual page](https://man7.org/linux/man-pages/man1/ionice.1.html) · util-linux · งานที่รันด้วยลำดับความสำคัญ I/O แบบ idle จะได้ใช้ดิสก์เฉพาะตอนที่โปรแกรมอื่นไม่ได้ใช้ดิสก์ - [iostat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/iostat.1.html) · sysstat · -x: w_await (เวลาประมวลผลเฉลี่ยของคำขอเขียน รวมเวลาที่รอในคิว), aqu-sz (ความยาวคิวเฉลี่ย ชื่อเดิมคือ avgqu-sz) - [perf-trace(1) — Linux manual page](https://man7.org/linux/man-pages/man1/perf-trace.1.html) · perf · -p ติดตาม system call ของโปรเซสที่รันอยู่, --duration แสดงเฉพาะการเรียกที่ใช้เวลานานกว่าจำนวน ms ที่กำหนด #### dk-fsync · fsync ทะลัก · fsync storms ถ้าสั่งให้เขียนข้อมูลลงดิสก์ “แบบแน่นอน” แต่ละครั้งจะใช้เวลา 0.1 ms ถึงหลายสิบ ms แล้วแต่ดิสก์ และถ้ามาพร้อมกันมาก คิวจะยาวขึ้น - ทำไม → ผลคือ → บนหน้าจอ: คำขอเขียนแบบแน่นอนมากระจุกกันจากการเซฟตามรอบและผู้เล่นแห่ล็อกเอาต์ → คิวดิสก์ยาวขึ้น → แลคทุกครั้งที่ถึงเวลาเซฟ, ล็อกเอาต์และย้ายแชนแนลช้า - อาการ: กระตุก, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: เป็นรอบสม่ำเสมอ, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: รวมการเซฟให้ทำทีเดียว (หลายการเซฟต่อ fsync หนึ่งครั้ง), กระจายเวลาเซฟ - งานฝั่งทีมอินฟรา: เครื่องเซิร์ฟเวอร์/OS: ใช้ SSD สำหรับเซิร์ฟเวอร์ที่มีระบบป้องกันไฟดับ, มอนิเตอร์ความยาวคิวดิสก์และดีเลย์ของ fsync เครื่อง DB: ถ้าการเซฟไปลง DB ให้ดิสก์ log ของ DB ใช้ SSD แบบเดียวกันด้วย, มอนิเตอร์ดีเลย์ของ commit - ตัวเลขที่ควรรู้: เวลาต่อครั้งต่างกันไปตามเครื่อง แต่โดยประมาณคือ SSD สำหรับเซิร์ฟเวอร์ (มีระบบป้องกันไฟดับ) 0.1 ms, SSD ทั่วไป 1 ms ถึงหลาย ms, ดิสก์คลาวด์ 1–2 ms และ HDD 10 ms ขึ้นไป ถ้าเธรดเดียวรอทีละครั้ง HDD จะทำได้ไม่ถึง 100 ครั้งใน 1 วินาที - บนกราฟ: พุ่งเป็นรอบ (ความยาวคิวดิสก์, ดีเลย์ของ flush และการเขียน) - จุดที่ต้องดู: f/s และ f_await (จำนวน flush ที่ดิสก์ประมวลผลและเวลาที่ใช้), w/s, aqu-sz และ w_await จาก iostat -x 1 วางซ้อนกับเวลาเซฟตามรอบและเวลาล็อกเอาต์ sysstat รุ่นเก่าแสดง aqu-sz เป็น avgqu-sz ดิสก์คลาวด์ให้ดู VolumeQueueLength และ VolumeAvgWriteLatency ของ EBS - สัญญาณว่าใช่: ทุกครั้งที่ถึงเวลาเซฟหรือมีคนแห่ล็อกเอาต์ จำนวน flush และความยาวคิวพุ่งพร้อมกัน และ w_await กับ f_await สูงเป็นหลายเท่าของปกติ ระหว่างนั้นการเซฟและการย้ายแชนแนลช้าลง - สัญญาณว่าไม่ใช่: คิวพุ่งในช่วงที่ไม่เกี่ยวกับการเซฟหรือล็อกเอาต์: น่าจะเป็นการสำรองข้อมูลหรือการบีบอัด (dk-backup) หรือขีดจำกัด IOPS (dk-iops) ถ้าจำนวน flush เท่าเดิมแต่ช้าลง ให้ดู burst credit หมด (dk-burst) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [fsync(2) — Linux manual page](https://man7.org/linux/man-pages/man2/fsync.2.html) · Linux man-pages · fsync ล้างข้อมูลลงไปถึงแคชของดิสก์ และบล็อกจนกว่าอุปกรณ์จะแจ้งว่าเสร็จ - [Reliability (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/wal-reliability.html) · PostgreSQL · ดิสก์ SATA ทั่วไปและ SSD จำนวนมากมีแคชการเขียนที่ข้อมูลหายเมื่อไฟดับ การเขียนแบบแน่นอนจึงต้องใช้แคชที่มีแบตเตอรี่หรือระบบป้องกันไฟดับ - [Amazon EBS General Purpose SSD volumes](https://docs.aws.amazon.com/ebs/latest/userguide/general-purpose.html) · AWS · ความหน่วงของดิสก์คลาวด์พื้นฐาน (gp3) อยู่ในระดับหลักหน่วย ms, io2 Block Express ใช้เวลาเฉลี่ยไม่ถึง 500 µs ต่อ I/O ขนาด 16 KiB - [Designs, Lessons and Advice from Building Large Distributed Systems (LADIS 2009 keynote)](https://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf) · Google · seek ของ HDD หนึ่งครั้ง 10 ms - [iostat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/iostat.1.html) · sysstat · -x: f/s และ f_await (จำนวนคำขอ flush ที่ดิสก์ประมวลผลและเวลาเฉลี่ย), w/s, w_await, aqu-sz (ชื่อเดิมคือ avgqu-sz) - [Amazon CloudWatch metrics for Amazon EBS](https://docs.aws.amazon.com/ebs/latest/userguide/using_cloudwatch_ebs.html) · AWS · VolumeQueueLength (จำนวนคำขอที่รอให้เสร็จ), VolumeAvgWriteLatency (ดีเลย์การเขียนเฉลี่ยราย 1 นาที, อินสแตนซ์ Nitro) #### dk-burst · burst credit ของดิสก์คลาวด์หมด · Burst credit depletion ดิสก์คลาวด์บางประเภทและเซิร์ฟเวอร์สเปกเล็กมี burst credit ที่ให้ทำงานเร็วกว่าประสิทธิภาพพื้นฐานได้ชั่วคราว ถ้าช่วงที่ยุ่งยาวนานจนเครดิตหมด ความเร็วจะตกลงกะทันหัน - ทำไม → ผลคือ → บนหน้าจอ: ใช้งานเกินประสิทธิภาพพื้นฐานเป็นเวลานาน → burst credit หมด ประสิทธิภาพจึงดิ่งลงไปเหลือระดับพื้นฐาน → ทุกเย็นพอผ่านไปไม่กี่ชั่วโมงก็เริ่มแลค - อาการ: กระตุก, สโลว์โมชั่น, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ช่วงพีคหัวค่ำ, ยิ่งเปิดไว้นานยิ่งเป็น - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: เครื่องเซิร์ฟเวอร์/OS: ใช้ดิสก์แบบรับประกันประสิทธิภาพ (gp3 หรือแบบกำหนด IOPS), ตั้ง alert เครดิตคงเหลือ, ตรวจขีดจำกัด burst ของแบนด์วิดท์ดิสก์ของอินสแตนซ์และ CPU credit ไปด้วย เครื่อง DB: เปลี่ยนดิสก์ DB รวมถึง managed DB เป็นแบบรับประกันประสิทธิภาพด้วย, ตั้ง alert เครดิตคงเหลือ - ตัวเลขที่ควรรู้: ดิสก์ AWS gp2 ขนาด 100 GB ปกติได้ 300 IOPS และ burst ได้ 3,000 IOPS ถ้าเครดิตเต็มจะอยู่ได้ประมาณ 30 นาที ส่วน gp3 ไม่มีเครดิตและได้ 3,000 เสมอ Premium SSD ขนาดเล็กของ Azure ก็ burst ด้วยเครดิตได้สูงสุด 30 นาทีเช่นกัน - บนกราฟ: ชนเพดานแล้วแบนราบ (IOPS, burst credit คงเหลือ) - จุดที่ต้องดู: BurstBalance ของ EBS ใน CloudWatch (gp2, st1, sc1), EBSIOBalance% และ EBSByteBalance% ของอินสแตนซ์ (บางอินสแตนซ์ที่ burst ได้), CPUCreditBalance ของอินสแตนซ์แบบ burstable ส่วน Azure ดูเมตริกอัตราการใช้ burst credit เช่น Data Disk Used Burst IO Credits Percentage - สัญญาณว่าใช่: ตั้งแต่ช่วงที่เครดิตคงเหลือลดลงใกล้ 0 IOPS (VolumeReadOps, VolumeWriteOps) แบนราบที่ระดับประสิทธิภาพพื้นฐาน และ VolumeQueueLength กับแลคเพิ่มขึ้นพร้อมกัน เริ่มหลังจากพีคต่อเนื่องมาแล้วหลายชั่วโมง - สัญญาณว่าไม่ใช่: เครดิตคงเหลือทุกตัวยังมีพอแต่ IOPS แบนราบ: ขีดจำกัดคงที่ของ volume หรืออินสแตนซ์ (dk-iops) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: แม้ดิสก์จะปกติ VM ขนาดเล็กก็มีขีดจำกัด burst ที่แบนด์วิดท์ดิสก์ของอินสแตนซ์เอง (เช่น อย่างน้อย 30 นาทีต่อวัน) จึงเกิดรูปแบบเดียวกันได้ เซิร์ฟเวอร์ราคาถูกที่ใช้ CPU credit ก็ช้าลงจนเหลือประสิทธิภาพพื้นฐานเมื่อเครดิตหมด - แหล่งอ้างอิง: - [Amazon EBS General Purpose SSD volumes](https://docs.aws.amazon.com/ebs/latest/userguide/general-purpose.html) · AWS · ประสิทธิภาพพื้นฐานของ gp2 คือ 3 IOPS ต่อ GiB (ขั้นต่ำ 100), burst ได้ถึง 3,000 IOPS ด้วย I/O credit, เครดิต 5,400,000 หน่วยอยู่ได้อย่างน้อย 30 นาที gp3 ไม่มี burst และได้ 3,000 IOPS เสมอ - [Managed disk bursting](https://learn.microsoft.com/en-us/azure/virtual-machines/disk-bursting) · Microsoft Azure · Premium SSD ตั้งแต่ P20 ลงไป burst ด้วยระบบเครดิต, ถ้าเครดิตเต็มจะ burst ที่ความเร็วสูงสุดได้ 30 นาที - [Amazon EBS-optimized instance types](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html) · AWS · บางอินสแตนซ์คงประสิทธิภาพ EBS สูงสุดได้เพียง 30 นาทีครั้งเดียวใน 24 ชั่วโมง จากนั้นกลับไปที่ประสิทธิภาพพื้นฐาน - [Standard mode for burstable performance instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances-standard-mode.html) · AWS · อินสแตนซ์แบบ burstable ในโหมด standard จะลดอัตราการใช้ CPU ลงสู่ระดับพื้นฐานเมื่อ CPU credit หมด (ค่อย ๆ ลดเพื่อไม่ให้ดิ่งลงทันที) - [Amazon CloudWatch metrics for Amazon EBS](https://docs.aws.amazon.com/ebs/latest/userguide/using_cloudwatch_ebs.html) · AWS · BurstBalance: I/O credit คงเหลือของ gp2 และ throughput credit คงเหลือของ st1 และ sc1 (%), VolumeReadOps, VolumeWriteOps, VolumeQueueLength - [CloudWatch metrics that are available for your instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html) · AWS · EBSIOBalance% และ EBSByteBalance%: EBS credit คงเหลือของบางอินสแตนซ์ที่ burst ได้ 30 นาทีครั้งเดียวใน 24 ชั่วโมง, CPUCreditBalance: CPU credit คงเหลือของอินสแตนซ์แบบ burstable - [Disk metrics](https://learn.microsoft.com/en-us/azure/virtual-machines/disks-metrics) · Microsoft Azure · อัตราการใช้ burst credit ของดิสก์และ VM เช่น Data Disk Used Burst IO Credits Percentage (ทุก 5 นาที) #### dk-iops · ขีดจำกัด IOPS/คิวดิสก์อิ่มตัว · IOPS limit / queue saturation ถ้าคำขอเกินจำนวนที่ดิสก์ประมวลผลได้ใน 1 วินาที คิวจะยาวขึ้นและดีเลย์พุ่งสูง - ทำไม → ผลคือ → บนหน้าจอ: คำขออ่านและเขียนเข้าใกล้ขีดความสามารถของดิสก์ → คิวยาวขึ้น (มักพุ่งเมื่ออัตราการใช้งานเกิน 90%) → เซฟและโหลดช้า ถ้าเป็น synchronous call เกมจะค้าง - อาการ: อินพุตดีเลย์, ค้าง / ปัจจัย: ความหน่วง, การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: รวมคำขอเข้าด้วยกัน, แคชข้อมูลที่อ่านบ่อย, ทำเป็น async เพื่อไม่ให้เธรดเกมรอดิสก์ - งานฝั่งทีมอินฟรา: เครื่องเซิร์ฟเวอร์/OS: ใช้ดิสก์ที่เร็วขึ้น, ตรวจขีดจำกัดแบนด์วิดท์ดิสก์และ IOPS ของแต่ละประเภทอินสแตนซ์, ตั้ง alert อัตราการใช้งานดิสก์และคิว, คัดลอกไฟล์ใหญ่ในช่วงที่ว่าง เครื่อง DB: ตั้ง alert อัตราการใช้ IOPS และ throughput ของดิสก์ DB ด้วย, ตรวจขีดจำกัดดิสก์ตามสเปกของอินสแตนซ์ DB - ตัวเลขที่ควรรู้: HDD ประมาณ 150, SATA SSD หลายหมื่น และ NVMe หลายแสน IOPS ดิสก์คลาวด์พื้นฐาน (AWS gp3) ได้ 3,000 IOPS และ 125 MiB ต่อวินาที ขีดจำกัดปริมาณรับส่งต่อวินาทีแยกจาก IOPS ถ้าการคัดลอกไฟล์ใหญ่ใช้จนเต็ม แม้การเขียนเล็ก ๆ ก็ต้องรอคิวไปด้วย - บนกราฟ: ชนเพดานแล้วแบนราบ (IOPS, ความยาวคิวดิสก์) - จุดที่ต้องดู: r/s, w/s, rkB/s, wkB/s, aqu-sz, r_await และ w_await จาก iostat -x 1 บนคลาวด์ดู VolumeReadOps, VolumeWriteOps และ VolumeQueueLength ของ EBS กับตัวบอกว่าเกินขีดจำกัดหรือไม่ คือ VolumeIOPSExceededCheck และ VolumeThroughputExceededCheck ส่วนฝั่งอินสแตนซ์ดู InstanceEBSIOPSExceededCheck และ InstanceEBSThroughputExceededCheck - สัญญาณว่าใช่: จำนวนคำขอต่อวินาทีหรือปริมาณรับส่งแบนราบที่ค่าขีดจำกัด และ aqu-sz กับ await พุ่งขึ้นพร้อมกัน บนคลาวด์เมตริกตรวจการเกินขีดจำกัดเป็น 1 - สัญญาณว่าไม่ใช่: แม้ %util เป็น 100% ถ้า await ต่ำก็อาจยังมีกำลังเหลือ บน SSD และ RAID ที่ประมวลผลคำขอแบบขนาน %util ไม่ได้บอกขีดจำกัด ถ้ายังไม่ถึงขีดจำกัดแต่ await สูงอย่างเดียว น่าจะเป็นดีเลย์ของตัวดิสก์เอง (dk-hdd) หรือ fsync (dk-fsync) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: บนคลาวด์ นอกจากขีดจำกัดของดิสก์แล้ว สเปกเซิร์ฟเวอร์แต่ละแบบ (ประเภทอินสแตนซ์) ยังมีขีดจำกัดแบนด์วิดท์ดิสก์และ IOPS ของตัวเอง ต่อให้ใช้ดิสก์ราคาแพง ถ้าเซิร์ฟเวอร์เล็กก็จะติดที่ขีดจำกัดของอินสแตนซ์ - แหล่งอ้างอิง: - [Exos X18 Data Sheet](https://www.seagate.com/www-content/datasheets/pdfs/exos-x18-mango-DS2045-1N-2007US-en_US.pdf) · Seagate · HDD สำหรับเซิร์ฟเวอร์ 7,200 rpm อ่านแบบสุ่ม 4K ได้ 170 IOPS (QD16) - [D3-S4520 SSD](https://www.solidigm.com/products/data-center/d3/s4520.html) · Solidigm · SATA SSD สำหรับเซิร์ฟเวอร์ อ่าน/เขียนแบบสุ่ม 4 KB สูงสุด 92K/48K IOPS - [Solidigm™ D7-P5520 and D7-P5620 Product Brief](https://www.solidigm.com/products/data-center/product-briefs/d7-p5520-p5620-product-brief.html) · Solidigm · NVMe SSD สำหรับเซิร์ฟเวอร์ อ่าน/เขียนแบบสุ่ม 1,000K/200K IOPS - [Amazon EBS General Purpose SSD volumes](https://docs.aws.amazon.com/ebs/latest/userguide/general-purpose.html) · AWS · ประสิทธิภาพพื้นฐานของ gp3 คือ 3,000 IOPS และ 125 MiB/s เป็นขีดจำกัดสองตัวที่แยกกันและเพิ่มแยกกันได้ - [Amazon EBS-optimized instance types](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html) · AWS · แต่ละประเภทอินสแตนซ์มีขีดจำกัดพื้นฐานและสูงสุดของแบนด์วิดท์ EBS, throughput และ IOPS แยกกัน - [iostat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/iostat.1.html) · sysstat · -x: r/s, w/s, rkB/s, wkB/s, aqu-sz (ชื่อเดิมคือ avgqu-sz), r_await, w_await, %util บน RAID และ SSD รุ่นใหม่ที่ประมวลผลคำขอแบบขนาน %util ไม่ได้บอกขีดจำกัดของประสิทธิภาพ - [Amazon CloudWatch metrics for Amazon EBS](https://docs.aws.amazon.com/ebs/latest/userguide/using_cloudwatch_ebs.html) · AWS · VolumeIOPSExceededCheck และ VolumeThroughputExceededCheck: เป็น 1 ถ้ามีการพยายามใช้เกินขีดจำกัด IOPS หรือ throughput ของ volume (อินสแตนซ์ Nitro), VolumeQueueLength - [CloudWatch metrics that are available for your instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html) · AWS · InstanceEBSIOPSExceededCheck และ InstanceEBSThroughputExceededCheck: เป็น 1 ถ้ามีการพยายามใช้เกินขีดจำกัด EBS IOPS หรือ throughput ของอินสแตนซ์ #### dk-full · ดิสก์เต็ม · Disk full ถ้า log และ dump สะสมจนดิสก์เต็ม การเขียนจะล้มเหลว และถ้าไม่ได้เตรียมรับมือไว้ เซิร์ฟเวอร์จะล่ม - ทำไม → ผลคือ → บนหน้าจอ: log, dump และไฟล์ชั่วคราวสะสมจนถึง 100% → เขียนไม่สำเร็จ ถ้าไม่มีการจัดการข้อผิดพลาดจะแครช ถ้ามีก็เซฟไม่สำเร็จ → หลุด, ความคืบหน้าถูกโรลแบ็ค - อาการ: หลุด, กดไม่ติด/โรลแบ็ค / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: จัดการกรณีเขียนไม่สำเร็จให้ลองเซฟใหม่และแจ้ง alert แทนการแครช, ลด log และ dump ที่ไม่จำเป็น - งานฝั่งทีมอินฟรา: เครื่องเซิร์ฟเวอร์/OS: ทำ log rotation, ตั้ง alert พื้นที่ดิสก์, แยกดิสก์ log กับดิสก์ข้อมูล เครื่อง DB: เฝ้าดูไม่ให้ transaction log ของ DB (WAL, binlog) สะสมเพราะ replication หยุดหรือขาดการสำรอง log - บนกราฟ: ค่อย ๆ สูงขึ้น (อัตราการใช้พื้นที่ดิสก์) - จุดที่ต้องดู: อัตราการใช้พื้นที่จาก df -h และอัตราการใช้ inode จาก df -i และหาข้อผิดพลาด ENOSPC ใน log ของเซิร์ฟเวอร์และ DB ฝั่ง DB ดู slot ที่ active เป็น false ใน pg_replication_slots ของ PostgreSQL, จำนวนและขนาดไฟล์จาก SHOW BINARY LOGS ของ MySQL, log_reuse_wait_desc ใน sys.databases ของ SQL Server และ FreeStorageSpace ของ RDS - สัญญาณว่าใช่: อัตราการใช้พื้นที่ค่อย ๆ สูงขึ้นตลอดหลายวัน ช่วงที่แตะ 100% ตรงกับช่วงที่แครชหรือเซฟไม่สำเร็จ และมี ENOSPC ใน log - สัญญาณว่าไม่ใช่: พื้นที่ยังเหลือพอแต่เขียนไม่สำเร็จ: น่าจะเป็นสาเหตุอื่น เช่น สิทธิ์ (permission) หรือขีดจำกัดขนาดไฟล์ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: transaction log ของ DB (WAL, binlog ฯลฯ) จะไม่ถูกลบและสะสมไปเรื่อย ๆ ถ้า replica หยุดหรือการสำรอง log ขาดไป ถ้าดิสก์นี้เต็ม การเขียนทั้งหมดของ DB จะหยุด การเซฟและการเทรดจึงล้มเหลวพร้อมกันทั้งหมด - แหล่งอ้างอิง: - [write(2) — Linux manual page](https://man7.org/linux/man-pages/man2/write.2.html) · Linux man-pages · ถ้าอุปกรณ์ไม่มีพื้นที่เหลือ การเขียนจะล้มเหลวด้วยข้อผิดพลาด ENOSPC - [Monitoring Disk Usage (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/diskusage.html) · PostgreSQL · ถ้าดิสก์ของ WAL เต็ม เซิร์ฟเวอร์ DB อาจ panic และปิดตัว - [Log-Shipping Standby Servers (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/warm-standby.html) · PostgreSQL · replication slot จะไม่ลบ WAL จนกว่า replica จะรับไป จึงอาจใช้พื้นที่ pg_wal จนเต็ม (จำกัดด้วย max_slot_wal_keep_size) - [Troubleshoot a full transaction log (SQL Server Error 9002)](https://learn.microsoft.com/en-us/sql/relational-databases/logs/troubleshoot-a-full-transaction-log-sql-server-error-9002) · Microsoft SQL Server · ถ้า log เต็ม DB จะอ่านได้อย่างเดียวและแก้ไขไม่ได้, สาเหตุที่พบบ่อยที่ขวางการล้าง log คือขาดการสำรอง log, replication lag และทรานแซกชันที่ยาว, ดูว่าอะไรขวางอยู่ได้จาก log_reuse_wait_desc ใน sys.databases - [df(1) — Linux manual page](https://man7.org/linux/man-pages/man1/df.1.html) · coreutils · ปริมาณการใช้แยกตามไฟล์ซิสเต็ม, -i แสดงการใช้ inode แทนบล็อก - [pg_replication_slots (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/view-pg-replication-slots.html) · PostgreSQL · active: slot นี้กำลัง stream อยู่หรือไม่, wal_status: WAL ที่ slot กันไว้เกิน max_wal_size แล้วหรือยัง - [SHOW BINARY LOGS Statement](https://dev.mysql.com/doc/refman/8.4/en/show-binary-logs.html) · MySQL · รายการไฟล์ binary log ของเซิร์ฟเวอร์และขนาดไฟล์ (File_size) - [Amazon CloudWatch metrics for Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-metrics.html) · AWS · FreeStorageSpace: พื้นที่จัดเก็บที่เหลือของอินสแตนซ์ DB #### dk-backup · งานสำรองข้อมูล/บีบอัด/สแกน · Backup / compression / scans ถ้าการสำรองข้อมูลตอนเช้ามืด, การบีบอัด log หรือการสแกนความปลอดภัยยึดดิสก์ไว้ การอ่านและเขียนของเซิร์ฟเวอร์เกมจะล่าช้า - ทำไม → ผลคือ → บนหน้าจอ: งานสำรองข้อมูลหรือบีบอัดที่ตั้งเวลาไว้เริ่มทำงาน → ใช้แบนด์วิดท์และ IOPS ของดิสก์ไปเกือบหมด → แลคเวลาเดิมทุกวัน - อาการ: กระตุก, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: เป็นรอบสม่ำเสมอ - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: เครื่องเซิร์ฟเวอร์/OS: ลดลำดับความสำคัญ I/O ของงานสำรองข้อมูล บีบอัด และสแกน, กระจายเวลา เครื่อง DB: สำรองข้อมูลจาก replica - บนกราฟ: พุ่งเป็นรอบ (อัตราการใช้งานดิสก์, เวลารอของดิสก์) - จุดที่ต้องดู: ใช้ sar -d ดูค่า %util, await และ aqu-sz จากข้อมูลย้อนหลังหลายวัน (ไฟล์รายวันใน /var/log/sa โดย sadc ต้องเก็บข้อมูลดิสก์ด้วย -S DISK) วางซ้อนแยกตามวัน และในช่วงเวลานั้นใช้ pidstat -d 1 หาโปรเซสที่มี kB_rd/s และ kB_wr/s สูงสุด แล้วเทียบกับตารางเวลาของ cron และ systemd timer - สัญญาณว่าใช่: await และ %util พุ่งเวลาเดิมทุกวัน และช่วงนั้นโปรเซสสำรองข้อมูล บีบอัด หรือสแกน กินการอ่านเขียนดิสก์ไปเกือบทั้งหมด - สัญญาณว่าไม่ใช่: เวลาที่พุ่งไม่ตรงกันในแต่ละวัน: ไม่น่าใช่งานที่ตั้งเวลาไว้ ถ้า I/O ส่วนใหญ่ในช่วงนั้นมาจากเซิร์ฟเวอร์เกมเอง ให้ดูฝั่งการเซฟและ log (dk-fsync, dk-sync-log) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [ionice(1) — Linux manual page](https://man7.org/linux/man-pages/man1/ionice.1.html) · util-linux · งานที่รันด้วยคลาส idle จะได้ใช้ดิสก์เฉพาะตอนที่โปรแกรมอื่นไม่ได้ใช้ดิสก์ - [Using Replication for Backups](https://dev.mysql.com/doc/refman/8.4/en/replication-solutions-backups.html) · MySQL · หยุด replica แล้วสำรองข้อมูลก็ไม่กระทบการทำงานของ DB หลัก - [sar(1) — Linux manual page](https://man7.org/linux/man-pages/man1/sar.1.html) · sysstat · -d: await, aqu-sz และ %util แยกตามอุปกรณ์จากไฟล์รายวัน (ค่าเริ่มต้น /var/log/sa), ข้อมูลดิสก์ต้องเก็บด้วยออปชัน -S DISK ของ sadc - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -d: kB_rd/s และ kB_wr/s ของแต่ละโปรเซส (ปริมาณอ่านและเขียนดิสก์ต่อวินาที) #### dk-lazy-load · lazy loading บนเซิร์ฟเวอร์ · Lazy loading on the server ถ้าเซิร์ฟเวอร์อ่านข้อมูลดันเจี้ยนหรือแมพจากดิสก์ตอนที่ถูกขอเป็นครั้งแรก ทุกคนจะหยุดไปตลอดทิกนั้น - ทำไม → ผลคือ → บนหน้าจอ: มีคนเข้าดันเจี้ยนหรือพื้นที่นั้นเป็นคนแรก → เซิร์ฟเวอร์อ่านข้อมูลจากดิสก์ในเธรดเกม → ทุกคนบนเซิร์ฟเวอร์นั้นค้างไปครู่หนึ่ง - อาการ: ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: โหลดล่วงหน้าตอนเซิร์ฟเวอร์เริ่ม, โหลดแบบ async - งานฝั่งทีมอินฟรา: เซิร์ฟเวอร์ที่เพิ่งสร้างจากสแนปช็อตให้วอร์มดิสก์ก่อนเปิดให้บริการ (อ่านทุกบล็อกหนึ่งรอบ) หรือใช้ฟีเจอร์ fast snapshot restore - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (เวลาต่อทิกของเซิร์ฟเวอร์, การอ่านดิสก์) - จุดที่ต้องดู: เทียบช่วงที่หยุดกับรายการเข้าดันเจี้ยนหรือพื้นที่ครั้งแรกใน log ของเซิร์ฟเวอร์เกม และดูการอ่านดิสก์ของเซิร์ฟเวอร์เกมในจังหวะนั้น (kB_rd/s ของ pidstat -d) กับการเรียก read และ open ที่ใช้เวลานานจาก perf trace --duration ถ้าเป็นเซิร์ฟเวอร์คลาวด์ที่เพิ่งเปิด ให้เทียบ VolumeAvgReadLatency ของ EBS กับเซิร์ฟเวอร์ที่เปิดมานาน - สัญญาณว่าใช่: หยุดเฉพาะตอนเข้าครั้งแรก เข้าที่เดิมครั้งที่สองไม่หยุด ระหว่างที่หยุด เธรดเกมรอการอ่านไฟล์อยู่ - สัญญาณว่าไม่ใช่: พื้นที่ที่โหลดไว้แล้วก็หยุดเหมือนกัน: น่าจะเป็นสาเหตุอื่น เช่น ทิกเกินงบหรือ GC - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: บนคลาวด์ เซิร์ฟเวอร์ที่เพิ่งสร้างจากสแนปช็อต (สำเนาดิสก์) ต้องดึงทุกบล็อกที่อ่านครั้งแรกมาจากสตอเรจระยะไกล จึงช้ากว่าปกติมาก ถ้าการเข้าครั้งแรกนานผิดปกติเฉพาะบนเซิร์ฟเวอร์ที่เพิ่งเปิดขึ้นจาก autoscaling ให้สงสัยสาเหตุนี้ - แหล่งอ้างอิง: - [Initialize Amazon EBS volumes](https://docs.aws.amazon.com/ebs/latest/userguide/ebs-initialize.html) · AWS · volume ที่สร้างจากสแนปช็อตจะมีดีเลย์สูงขึ้นและประสิทธิภาพตกระหว่างดึงบล็อกจาก S3, ใช้ dd หรือ fio อ่านทุกบล็อกเพื่อ initialize ล่วงหน้า - [Amazon EBS fast snapshot restore](https://docs.aws.amazon.com/ebs/latest/userguide/ebs-fast-snapshot-restore.html) · AWS · fast snapshot restore ให้ volume ที่ initialize แล้วตั้งแต่ตอนสร้าง จึงไม่มีดีเลย์ตอนเข้าถึงครั้งแรก - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -d: kB_rd/s ของแต่ละโปรเซส (ปริมาณอ่านดิสก์ต่อวินาที) - [perf-trace(1) — Linux manual page](https://man7.org/linux/man-pages/man1/perf-trace.1.html) · perf · --duration แสดงเฉพาะ system call ที่ใช้เวลานานกว่าจำนวน ms ที่กำหนด - [Amazon CloudWatch metrics for Amazon EBS](https://docs.aws.amazon.com/ebs/latest/userguide/using_cloudwatch_ebs.html) · AWS · VolumeAvgReadLatency: ดีเลย์การอ่านเฉลี่ยราย 1 นาที (อินสแตนซ์ Nitro) #### dk-coredump · การเขียน core dump · Core dump writing ตอนเซิร์ฟเวอร์ล่ม ต้องเขียนหน่วยความจำหลาย GB ลงดิสก์ บางครั้งการรีสตาร์ตจึงล่าช้าไปหลายนาที - ทำไม → ผลคือ → บนหน้าจอ: เซิร์ฟเวอร์แครช จึงเขียนหน่วยความจำทั้งหมดลงไฟล์ → รีสตาร์ตไม่ได้ระหว่างเขียนหลาย GB → เซิร์ฟเวอร์ล่มจนผู้เล่นหลุด แล้วเข้าเกมไม่ได้อยู่นาน - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: พิจารณาใช้ dump ขนาดเล็กที่เก็บเฉพาะหน่วยความจำที่จำเป็น (minidump), แก้ต้นเหตุของการแครช - งานฝั่งทีมอินฟรา: จำกัดขนาด dump (ตั้งค่า core dump ของ OS), ใช้ดิสก์ที่เร็ว, แยกการรีสตาร์ตออกจากการทำ dump (บีบอัดและอัปโหลด dump แยกทำหลังรีสตาร์ต) - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนการเชื่อมต่อ, เวลารีสตาร์ตเซิร์ฟเวอร์) - จุดที่ต้องดู: วางเวลาที่แครช ขนาดไฟล์ core (coredumpctl list และ info หรือไฟล์ในตำแหน่งที่ core_pattern ชี้ไว้) เวลาที่เขียนไฟล์เสร็จ และเวลาที่เซอร์วิสกลับมาทำงานเรียงคู่กัน แล้วดู wkB/s ของ iostat -x ในช่วงนั้น - สัญญาณว่าใช่: หลังแครช ระหว่างเขียนไฟล์ core ขนาดหลาย GB การเขียนดิสก์อยู่ใกล้ขีดจำกัดตลอด และรีสตาร์ตเริ่มหลังเขียนเสร็จแล้วเท่านั้น - สัญญาณว่าไม่ใช่: core dump ปิดอยู่หรือจบเร็วเพราะไฟล์เล็ก แต่รีสตาร์ตยังช้า: น่าจะเป็นขั้นตอนเริ่มเซิร์ฟเวอร์ เช่น การโหลดแมพ หรือ cold cache ของ DB (db-cold-cache) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [core(5) — Linux manual page](https://man7.org/linux/man-pages/man5/core.5.html) · Linux man-pages · RLIMIT_CORE กำหนดเพดานขนาดไฟล์ core, coredump_filter เลือกส่วนของหน่วยความจำที่จะเก็บ, ส่ง core dump ผ่าน pipe ไปให้โปรแกรมจัดการแยก - [Minidump Files](https://learn.microsoft.com/en-us/windows/win32/debug/minidump-files) · Microsoft · minidump เก็บเฉพาะข้อมูลส่วนที่มีประโยชน์ของ crash dump จึงสร้างได้เร็วและเล็ก - [coredumpctl(1) — Linux manual page](https://man7.org/linux/man-pages/man1/coredumpctl.1.html) · systemd · list: รายการ core dump ที่เหลืออยู่ใน journal (TIME คือเวลาแครชที่เคอร์เนลรายงาน), info: รายละเอียดของแต่ละ dump และขนาดที่เขียนลงดิสก์ - [iostat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/iostat.1.html) · sysstat · -x: wkB/s (ปริมาณเขียนดิสก์ต่อวินาที) #### dk-hdd · ดีเลย์จาก seek ของ HDD · HDD seek latency HDD ต้องเลื่อนหัวอ่านไปบนจานแม่เหล็ก (seek) การอ่านเขียนข้อมูลที่กระจัดกระจายจึงใช้เวลาเกือบ 10 ms ต่อครั้ง - ทำไม → ผลคือ → บนหน้าจอ: ใช้ HDD บนเซิร์ฟเวอร์เก่าหรือสตอเรจราคาถูก → อ่านเขียนแบบกระจัดกระจายครั้งละประมาณ 10 ms → เซฟและโหลดช้าโดยรวม - อาการ: อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ออกแบบให้เน้นการเขียนแบบต่อเนื่อง (sequential write) - งานฝั่งทีมอินฟรา: เครื่องเซิร์ฟเวอร์/OS: เปลี่ยนเป็น SSD (เริ่มจากดิสก์เก็บข้อมูลที่อ่านเขียนแบบกระจัดกระจายมาก) เครื่อง DB: เปลี่ยนดิสก์ DB ที่อ่านเขียนแบบกระจัดกระจายมากที่สุดเป็น SSD ก่อน - บนกราฟ: สูงตลอดตั้งแต่แรก (ดีเลย์การอ่านและเขียนดิสก์ (r_await, w_await)) - จุดที่ต้องดู: ใช้ lsblk -d -o NAME,ROTA ตรวจว่าเป็นดิสก์หมุน (HDD) หรือไม่ และดู r/s, w/s, r_await และ w_await จาก iostat -x 1 ถ้าเป็น VM ให้ตรวจประเภทดิสก์จากสเปกของคลาวด์หรือสตอเรจ - สัญญาณว่าใช่: เป็นดิสก์หมุน และแม้คำขอต่อวินาทีอยู่แค่ระดับหลายสิบถึงร้อยกว่า r_await และ w_await ก็อยู่ที่หลาย ms ถึงหลายสิบ ms ตลอด - สัญญาณว่าไม่ใช่: เป็น SSD แต่ดีเลย์สูง: ให้ดูคิวดิสก์อิ่มตัว (dk-iops) หรือ burst credit หมด (dk-burst) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Designs, Lessons and Advice from Building Large Distributed Systems (LADIS 2009 keynote)](https://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf) · Google · seek ดิสก์หนึ่งครั้ง 10 ms, อ่านแบบต่อเนื่อง 1 MB จากดิสก์ 20 ms - [Exos X18 Data Sheet](https://www.seagate.com/www-content/datasheets/pdfs/exos-x18-mango-DS2045-1N-2007US-en_US.pdf) · Seagate · HDD 7,200 rpm มี rotational latency เฉลี่ย 4.16 ms, อ่านแบบสุ่ม 4K ได้ 170 IOPS - [lsblk(8) — Linux manual page](https://man7.org/linux/man-pages/man8/lsblk.8.html) · util-linux · -o เลือกคอลัมน์ที่จะแสดง, คอลัมน์ topology ของอุปกรณ์มี ROTA (เป็นดิสก์หมุนหรือไม่) - [ABI stable symbols](https://docs.kernel.org/admin-guide/abi-stable.html) · Linux kernel · /sys/block/(ดิสก์)/queue/rotational: บอกว่าอุปกรณ์เป็นแบบหมุนหรือไม่หมุน - [iostat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/iostat.1.html) · sysstat · -x: r/s, w/s, r_await, w_await (เวลาประมวลผลเฉลี่ยต่อคำขอ รวมเวลาที่รอในคิว) ### L12 ฐานข้อมูล (16 สาเหตุ) #### db-no-index · คิวรีที่ไม่มีอินเด็กซ์ · Missing index / full table scan ถ้าไม่มีอินเด็กซ์ การหาแถวที่ตรงเงื่อนไขต้องอ่านทั้งตาราง (full table scan) - ทำไม → ผลคือ → บนหน้าจอ: deploy ฟีเจอร์ใหม่ที่เพิ่มการค้นหาด้วยเงื่อนไขที่ไม่มีอินเด็กซ์ → สแกนทุกแถวหลายล้านแถว คิวรีเดียวใช้เวลาหลายร้อย ms ถึงหลายวินาที → กล่องจดหมายและประวัติการเทรดโหลดช้า, connection ถูกถือครองไว้จนคำขออื่นต้องรอไปด้วย - อาการ: อินพุตดีเลย์, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: ความหน่วง, การหยุดชะงัก - ใครเจอ: เฉพาะบางฟีเจอร์, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ตรวจ query plan ของคิวรีใหม่ก่อน deploy, เพิ่มอินเด็กซ์, ตรวจว่าคิวรีที่แก้ข้อมูล (UPDATE, DELETE) ใช้อินเด็กซ์ด้วย - งานฝั่งทีมอินฟรา: เฝ้าดู slow query log, หาคิวรีที่ทำ full table scan แล้วแชร์ให้ทีมพัฒนาเกม, เพิ่มอินเด็กซ์ระหว่างให้บริการด้วยวิธี online ที่ล็อกเพียงสั้น ๆ - ตัวเลขที่ควรรู้: ถ้ามีอินเด็กซ์ใช้เวลาหลาย ms ถ้าไม่มีจะช้าลงตามขนาดข้อมูล บนตารางใหญ่อาจช้ากว่าหลายร้อยถึงหลายหมื่นเท่า - บนกราฟ: ขึ้นเป็นขั้นบันไดจากจุดหนึ่ง (ดีเลย์ของคิวรี DB, จำนวนแถวที่อ่าน) - จุดที่ต้องดู: MySQL ดู Rows_examined และ Rows_sent ใน slow query log (ถ้าเปิด log_queries_not_using_indexes จะบันทึกคิวรีที่ไม่ใช้อินเด็กซ์ด้วย) และ SUM_NO_INDEX_USED กับ SUM_ROWS_EXAMINED ใน events_statements_summary_by_digest ของ performance_schema แล้วรัน EXPLAIN ส่วน PostgreSQL ดู seq_scan และ seq_tup_read ใน pg_stat_user_tables แล้วรัน EXPLAIN - สัญญาณว่าใช่: คิวรีที่เพิ่งโผล่หลัง deploy อ่านแถว (Rows_examined) มากกว่าแถวที่ส่งกลับ (Rows_sent) หลายพันเท่า และ EXPLAIN แสดงการสแกนทั้งตาราง (MySQL type ALL, PostgreSQL Seq Scan) seq_tup_read ของตารางใหญ่เพิ่มชันตั้งแต่เวลาที่ deploy - สัญญาณว่าไม่ใช่: ใช้อินเด็กซ์แล้วแต่ยังช้า: น่าจะเป็นการรอล็อก (db-hot-row, db-ddl-lock) หรือ query plan เปลี่ยน (db-plan-flip) การสแกนทั้งตารางบนตารางเล็กอาจเป็นเรื่องปกติ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ผลกระทบมีมากกว่าการอ่านที่ช้าลง คิวรีที่แก้ข้อมูล (UPDATE, DELETE) โดยไม่มีอินเด็กซ์ ใน DB บางตัวจะล็อกแถวที่สแกนผ่านทั้งหมดด้วย จนอาจขวางการเซฟของผู้เล่นที่ไม่เกี่ยวข้อง - แหล่งอ้างอิง: - [How MySQL Uses Indexes](https://dev.mysql.com/doc/refman/8.4/en/mysql-indexes.html) · MySQL · ถ้าไม่มีอินเด็กซ์ จะอ่านทั้งตารางตั้งแต่แถวแรก และยิ่งตารางใหญ่ต้นทุนยิ่งสูง - [Locks Set by Different SQL Statements in InnoDB](https://dev.mysql.com/doc/refman/8.4/en/innodb-locks-set.html) · MySQL · ถ้าไม่มีอินเด็กซ์ที่เหมาะจนต้องสแกนทั้งตาราง ทุกแถวจะถูกล็อก จนผู้ใช้อื่นเพิ่มข้อมูลไม่ได้ไปด้วย - [The Slow Query Log](https://dev.mysql.com/doc/refman/8.4/en/slow-query-log.html) · MySQL · บันทึกคิวรีที่เกิน long_query_time (ค่าเริ่มต้น 10 วินาที), เลือกบันทึกคิวรีที่ไม่ใช้อินเด็กซ์แยกได้ - [CREATE INDEX (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/sql-createindex.html) · PostgreSQL · ถ้าสร้างด้วย CONCURRENTLY จะสร้างอินเด็กซ์โดยไม่บล็อกการเขียน ส่วนการสร้างแบบปกติจะบล็อกการเขียนจนเสร็จ - [Statement Summary Tables](https://dev.mysql.com/doc/refman/8.4/en/performance-schema-statement-summary-tables.html) · MySQL · events_statements_summary_by_digest: SUM_NO_INDEX_USED (จำนวนครั้งที่รันโดยไม่ใช้อินเด็กซ์) และ SUM_ROWS_EXAMINED แยกตามรูปแบบคิวรี - [EXPLAIN Output Format](https://dev.mysql.com/doc/refman/8.4/en/explain-output.html) · MySQL · ถ้า type เป็น ALL คือสแกนทั้งตาราง ปกติแก้ด้วยการเพิ่มอินเด็กซ์ - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · seq_scan (จำนวนครั้งที่ทำ sequential scan) และ seq_tup_read (จำนวนแถวที่อ่านด้วย sequential scan) ใน pg_stat_user_tables - [Using EXPLAIN (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/using-explain.html) · PostgreSQL · Seq Scan: query plan ที่อ่านทุกแถวของตารางไล่ตามลำดับ #### db-hot-row · แย่งล็อกบน hot row · Hot row lock contention ถ้าทุกคนพยายามแก้แถวเดียวกัน (โกดังกิลด์, ไอเทมฮิตในตลาดประมูล, ตัวนับของทั้งเซิร์ฟเวอร์) จะได้ล็อกทีละคนเท่านั้น - ทำไม → ผลคือ → บนหน้าจอ: การแก้ไขมากระจุกที่แถวเดียวกันเพราะอีเวนต์หรือไอเทมฮิต → คำขอต้องรอจนกว่าจะได้ล็อก → เทรดไม่สำเร็จ, “โปรดลองใหม่ภายหลัง”, ไทม์เอาต์ - อาการ: กดไม่ติด/โรลแบ็ค, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: แบ่งแถว (sharded counter), ทำทรานแซกชันให้สั้น, รวมในหน่วยความจำแล้วเขียนลงทีเดียว - งานฝั่งทีมอินฟรา: มอนิเตอร์เวลาและจำนวนครั้งที่รอ row lock เพื่อหาแถวที่มีการแย่งกันมากแล้วแชร์ผล - ตัวเลขที่ควรรู้: ถ้าคำขอหนึ่งถือล็อกไว้ 10 ms แถวนั้นจะแก้ได้สูงสุด 100 ครั้งใน 1 วินาที ถ้ามีการเรียกไปกลับเซิร์ฟเวอร์อื่นแทรกอยู่ในทรานแซกชัน ก็จะลดลงไปอีกตามเวลานั้น - บนกราฟ: สูงตามจำนวนคนและโหลด (จำนวนและเวลารอ row lock) - จุดที่ต้องดู: MySQL ดูค่าที่เพิ่มขึ้นของ Innodb_row_lock_waits และ Innodb_row_lock_time กับ Innodb_row_lock_current_waits และใช้ sys.innodb_lock_waits หาว่าใครรอใคร PostgreSQL ดูเซสชันที่ wait_event_type เป็น Lock ใน pg_stat_activity และคำขอที่ granted เป็น false ใน pg_locks ถ้าเปิด log_lock_waits (ค่าเริ่มต้นปิด) ล็อกที่รอนานจะถูกบันทึกใน log - สัญญาณว่าใช่: การรอล็อกเพิ่มชันตามอีเวนต์และจำนวนผู้เล่น และคำขอที่รออยู่ส่วนใหญ่ชี้ไปที่แถวเดียวกัน (คีย์เดียวกัน) ในตารางเดียวกัน - สัญญาณว่าไม่ใช่: การรอกระจายเท่า ๆ กันในหลายตารางและหลายแถว: น่าจะเป็นดิสก์หรือ CPU อิ่มตัว ถ้าเซสชันเดียวถือล็อกไว้นานไม่ปล่อย ให้ดูทรานแซกชันที่เปิดทิ้งไว้นาน (db-long-tx) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [InnoDB Locking](https://dev.mysql.com/doc/refman/8.4/en/innodb-locking.html) · MySQL · เมื่อทรานแซกชันหนึ่งล็อกแถว (index record) ไว้ ทรานแซกชันอื่นจะแก้แถวนั้นไม่ได้และต้องรอ - [How to Minimize and Handle Deadlocks](https://dev.mysql.com/doc/refman/8.4/en/innodb-deadlocks-handling.html) · MySQL · คำแนะนำให้ทำทรานแซกชันให้เล็กและสั้น และ commit ทันทีหลังแก้ข้อมูลที่เกี่ยวข้องเพื่อลดการชนกัน - [Server Status Variables](https://dev.mysql.com/doc/refman/8.4/en/server-status-variables.html) · MySQL · ดูจำนวนครั้งและเวลาที่รอ row lock จาก Innodb_row_lock_waits และ Innodb_row_lock_time และจำนวนที่กำลังรออยู่จาก Innodb_row_lock_current_waits - [The innodb_lock_waits and x$innodb_lock_waits Views](https://dev.mysql.com/doc/refman/8.4/en/sys-innodb-lock-waits.html) · MySQL · คิวรีที่รออยู่ (waiting_query), เซสชันที่ขวางอยู่ (blocking_pid) และเวลาที่รอ (wait_age) - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · wait_event_type ของ pg_stat_activity: ถ้าเป็น Lock แปลว่ากำลังรอ heavyweight lock - [pg_locks (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/view-pg-locks.html) · PostgreSQL · ถ้า granted เป็น false แปลว่าโปรเซสนั้นกำลังรอให้ได้ล็อก - [Error Reporting and Logging (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/runtime-config-logging.html) · PostgreSQL · log_lock_waits: บันทึก log เมื่อรอล็อกนานกว่า deadlock_timeout, ค่าเริ่มต้นปิด #### db-deadlock · เดดล็อกใน DB · Database deadlock ถ้าสองทรานแซกชัน (งาน DB ที่ประมวลผลเป็นชุดเดียว) ต่างรอแถวที่อีกฝ่ายล็อกไว้ DB จะบังคับยกเลิกฝั่งหนึ่ง - ทำไม → ผลคือ → บนหน้าจอ: การเทรด A ล็อกตามลำดับไอเทม→เงินในเกม ส่วน B ล็อกตามลำดับเงินในเกม→ไอเทม → DB ตรวจพบเดดล็อกแล้วโรลแบ็คฝั่งหนึ่ง → เทรดและคราฟต์ล้มเหลวเป็นบางครั้ง, ไอเทมย้อนกลับ - อาการ: กดไม่ติด/โรลแบ็ค, อินพุตดีเลย์ / ปัจจัย: แพ็กเก็ตหาย, การหยุดชะงัก - ใครเจอ: เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ใช้ลำดับการล็อกเดียวกันทุกที่, ทำทรานแซกชันให้สั้น, ลองใหม่อัตโนมัติเมื่อล้มเหลว - งานฝั่งทีมอินฟรา: เปิดการตรวจจับเดดล็อกไว้และเก็บบันทึกเดดล็อกมาแชร์, เซิร์ฟเวอร์ MySQL ที่ปิดการตรวจจับให้ลดขีดจำกัดการรอล็อก (ค่าเริ่มต้น 50 วินาที) - ตัวเลขที่ควรรู้: MySQL (InnoDB) ตรวจพบได้แทบทันที PostgreSQL ใช้เวลา 1 วินาทีตามค่าเริ่มต้น และ SQL Server ใช้นานสุดราว 5 วินาที ระหว่างนั้นคำขอทั้งสองหยุดรออยู่ ถ้าเป็นเซิร์ฟเวอร์ MySQL ที่ปิดการตรวจจับไว้เพราะคำขอพร้อมกันมากเป็นพิเศษ จะต้องรอจนถึงขีดจำกัดการรอล็อก (ค่าเริ่มต้น 50 วินาที) - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (จำนวนเดดล็อก, จำนวนการเทรดที่ล้มเหลว) - จุดที่ต้องดู: MySQL ดู LATEST DETECTED DEADLOCK ใน SHOW ENGINE INNODB STATUS (เฉพาะรายการล่าสุด 1 รายการ), เดดล็อกทั้งหมดที่บันทึกใน error log เมื่อเปิด innodb_print_all_deadlocks และ lock_deadlocks ใน INFORMATION_SCHEMA.INNODB_METRICS ส่วน PostgreSQL ดู deadlocks ใน pg_stat_database และ SQL Server ดู xml_deadlock_report ของเซสชัน system_health ที่เปิดไว้เป็นค่าเริ่มต้น รหัสข้อผิดพลาดฝั่งเซิร์ฟเวอร์เกมคือ MySQL 1213, PostgreSQL 40P01, SQL Server 1205 - สัญญาณว่าใช่: ช่วงที่เทรดหรือคราฟต์ล้มเหลว จำนวนเดดล็อกเพิ่มขึ้น และสองทรานแซกชันที่บันทึกไว้ล็อกตารางชุดเดียวกันในลำดับตรงข้ามกัน - สัญญาณว่าไม่ใช่: จำนวนเดดล็อกเท่าเดิมแต่ยังล้มเหลว: น่าจะเป็นการรอล็อกเกินขีดจำกัด (MySQL ข้อผิดพลาด 1205) หรือ hot row (db-hot-row) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [InnoDB Startup Options and System Variables](https://dev.mysql.com/doc/refman/8.4/en/innodb-parameters.html) · MySQL · ถ้าเปิดการตรวจจับ (ค่าเริ่มต้น) InnoDB จะตรวจพบเดดล็อกทันทีแล้วโรลแบ็ค, innodb_lock_wait_timeout ค่าเริ่มต้น 50 วินาที - [Deadlock Detection](https://dev.mysql.com/doc/refman/8.4/en/innodb-deadlock-detection.html) · MySQL · ถ้าทำงานพร้อมกันสูงมาก การตรวจจับเองอาจช้าลง บางครั้งจึงปิดไว้แล้วอาศัยขีดจำกัดการรอล็อกแทน - [Lock Management (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/runtime-config-locks.html) · PostgreSQL · deadlock_timeout ค่าเริ่มต้น 1 วินาที: ต้องรอล็อกนานเท่านี้ก่อนจึงจะตรวจเดดล็อก - [Deadlocks guide](https://learn.microsoft.com/en-us/sql/relational-databases/sql-server-deadlocks-guide) · Microsoft SQL Server · ตรวจเดดล็อกทุก 5 วินาทีตามค่าเริ่มต้น ถ้าเกิดเดดล็อกบ่อยจะลดลงได้ถึง 100 ms, เซสชัน system_health ที่เปิดไว้เป็นค่าเริ่มต้นเก็บ xml_deadlock_report, ฝั่งที่ถูกเลือกให้ยกเลิกจะได้ข้อผิดพลาด 1205 - [How to Minimize and Handle Deadlocks](https://dev.mysql.com/doc/refman/8.4/en/innodb-deadlocks-handling.html) · MySQL · แก้หลายแถวหรือหลายตารางตามลำดับเดิมเสมอ, ล้มเหลวให้ลองใหม่, ใช้ innodb_print_all_deadlocks บันทึกเดดล็อกทั้งหมด - [InnoDB Standard Monitor and Lock Monitor Output](https://dev.mysql.com/doc/refman/8.4/en/innodb-standard-monitor.html) · MySQL · LATEST DETECTED DEADLOCK: สองทรานแซกชันของเดดล็อกล่าสุด, ล็อกที่ถือและที่รอ และฝั่งที่ถูกโรลแบ็ค - [InnoDB INFORMATION_SCHEMA Metrics Table](https://dev.mysql.com/doc/refman/8.4/en/innodb-information-schema-metrics-table.html) · MySQL · ตัวนับ lock_deadlocks ใน INNODB_METRICS (เปิดใช้เป็นค่าเริ่มต้น) - [Server Error Message Reference](https://dev.mysql.com/doc/mysql-errors/8.4/en/server-error-reference.html) · MySQL · 1213 ER_LOCK_DEADLOCK (เดดล็อก), 1205 ER_LOCK_WAIT_TIMEOUT (รอล็อกเกินขีดจำกัด) - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · deadlocks ใน pg_stat_database: จำนวนเดดล็อกที่ตรวจพบใน DB นี้ - [PostgreSQL Error Codes (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/errcodes-appendix.html) · PostgreSQL · 40P01 deadlock_detected #### db-pool · connection pool หมด · Connection pool exhaustion จำนวนการเชื่อมต่อที่เปิดไว้กับ DB มีจำกัด ถ้าคิวรีที่ช้าถือครองการเชื่อมต่อไว้ คำขอที่เหลือต้องรอ - ทำไม → ผลคือ → บนหน้าจอ: ทุกการเชื่อมต่อถูกใช้หมดเพราะคิวรีช้าหรือคำขอทะลัก → คำขอใหม่ต้องรอจนกว่าจะมีการเชื่อมต่อว่าง → ล็อกอินโหลดไม่จบ, เซฟช้า, ไทม์เอาต์ - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์, เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: กำจัดคิวรีที่ช้า, ปรับขนาด pool และไทม์เอาต์การรอ (อย่าขยาย pool ไปเรื่อย ๆ), แยก pool ตามฟีเจอร์ - งานฝั่งทีมอินฟรา: ตรวจจำนวนการเชื่อมต่อสูงสุดของ DB และ CPU กับ IOPS ที่เหลือ, ก่อนเพิ่มเครื่องหรือทำ autoscaling ให้ตรวจว่าจำนวนเซิร์ฟเวอร์ × ขนาด pool ไม่เกินจำนวนการเชื่อมต่อสูงสุด, เพิ่มเมตริกการรอ connection และการรอล็อกเข้าในการมอนิเตอร์ - ตัวเลขที่ควรรู้: จำนวนการเชื่อมต่อที่ต้องใช้ประมาณได้จาก “คำขอต่อวินาที × เวลาที่คำขอหนึ่งถือครองการเชื่อมต่อ” ถ้า 2,000 รายการใน 1 วินาที รายการละ 5 ms จะมีการเชื่อมต่อที่ยุ่งตลอดเฉลี่ย 10 ตัว เพื่อเผื่อช่วงที่คำขอแห่เข้ามา ปกติจะตั้งไว้สองสามเท่าของค่านั้น ถ้าคิวรีช้าลงเป็น 150 ms คำขอปริมาณเท่าเดิมจะต้องใช้ 300 ตัว - บนกราฟ: ชนเพดานแล้วแบนราบ (จำนวนการเชื่อมต่อ DB ที่ใช้อยู่, เวลารอ connection) - จุดที่ต้องดู: นับสถานะการเชื่อมต่อแยกตามเซิร์ฟเวอร์เกมจากฝั่ง DB MySQL ดู Host, Command (การเชื่อมต่อที่ว่างคือ Sleep) และ Time ใน SHOW PROCESSLIST กับ Threads_connected, Threads_running และจำนวนการเชื่อมต่อที่ถูกปฏิเสธ Connection_errors_max_connections ส่วน PostgreSQL นับ pg_stat_activity โดยจัดกลุ่มตาม client_addr และ state ถ้าไลบรารี connection pool ของเซิร์ฟเวอร์เกมส่งออกจำนวนที่รอและเวลารอ ให้ดูคู่กันด้วย - สัญญาณว่าใช่: การเชื่อมต่อของเซิร์ฟเวอร์เกมตัวหนึ่งกำลังรันคิวรีครบเท่าขนาด pool และการเชื่อมต่อว่างเป็น 0 ระหว่างนั้นล็อกอินและเซฟต้องรอ หรือจำนวนการเชื่อมต่อทั้งหมดของ DB แตะ max_connections จนการเชื่อมต่อใหม่ถูกปฏิเสธ - สัญญาณว่าไม่ใช่: มีการเชื่อมต่อว่างเหลือพอแต่ยังช้า: น่าจะเป็นดีเลย์ของตัวคิวรีเอง (db-no-index, db-hot-row) หรือทรัพยากร DB อิ่มตัว - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ถ้าขยาย pool ไปเรื่อย ๆ จะมีแต่ CPU ของ DB และการแย่งล็อกที่เพิ่มขึ้น จนทุกอย่างช้าลงพร้อมกัน และถ้าจำนวนเซิร์ฟเวอร์ × ขนาด pool เกินจำนวนการเชื่อมต่อสูงสุดของ DB เซิร์ฟเวอร์ที่เพิ่มเข้ามาหรือเพิ่งรีสตาร์ตจะเชื่อมต่อไม่ได้เลย มักเกิดตอน autoscaling หรือทันทีหลังปิดปรับปรุง - แหล่งอ้างอิง: - [Number Of Database Connections](https://wiki.postgresql.org/wiki/Number_Of_Database_Connections) · PostgreSQL · เมื่อทรัพยากร DB ถูกใช้เต็มแล้ว การเพิ่มการเชื่อมต่อกลับทำให้ throughput ลดลง, จำกัดการเชื่อมต่อที่ทำงานอยู่ให้พอดีกับทรัพยากรแล้วให้ที่เหลือรอในคิว ได้ผลดีกว่าทั้งด้านดีเลย์และ throughput - [Too many connections](https://dev.mysql.com/doc/refman/8.4/en/too-many-connections.html) · MySQL · เมื่อใช้ max_connections ครบแล้ว การเชื่อมต่อใหม่จะถูกปฏิเสธด้วยข้อผิดพลาด Too many connections - [Connections and Authentication (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/runtime-config-connection.html) · PostgreSQL · max_connections: เพดานจำนวนการเชื่อมต่อพร้อมกัน ค่าเริ่มต้นมักเป็น 100 - [SHOW PROCESSLIST Statement](https://dev.mysql.com/doc/refman/8.4/en/show-processlist.html) · MySQL · Host (ที่อยู่ไคลเอนต์), Command (เซสชันที่ว่างคือ Sleep), Time, State - [Server Status Variables](https://dev.mysql.com/doc/refman/8.4/en/server-status-variables.html) · MySQL · Threads_connected และ Threads_running, Connection_errors_max_connections (จำนวนการเชื่อมต่อที่ถูกปฏิเสธเพราะแตะ max_connections) - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · pg_stat_activity: client_addr และ state (active, idle, idle in transaction ฯลฯ) ของแต่ละการเชื่อมต่อ #### db-replica-lag · การทำสำเนาข้อมูลล่าช้า · Replication lag การเขียนไปที่ DB หลัก ส่วนการอ่านทำจาก replica ถ้า replica ตามไม่ทัน ข้อมูลที่เพิ่งเขียนจะยังมองไม่เห็น - ทำไม → ผลคือ → บนหน้าจอ: การเขียนกระจุกที่ DB หลักจน replica ตามหลังไปหลายวินาที → อ่านข้อมูลที่เพิ่งเซฟจาก replica แล้วยังไม่มี → ไอเทมที่เพิ่งซื้อไม่ขึ้น, ราคาในตลาดซื้อขายเป็นค่าเก่า, บั๊กแจกของซ้ำ - อาการ: กดไม่ติด/โรลแบ็ค / ปัจจัย: ความหน่วง - ใครเจอ: เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: อินฟรา DB (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: อ่านข้อมูลที่เพิ่งเขียนจาก DB หลัก, ตรวจว่าแจกไปแล้วหรือยังและแจกของใน DB หลักภายในทรานแซกชันเดียว (กันการแจกซ้ำด้วย unique key หรือ UPDATE แบบมีเงื่อนไข) - งานฝั่งทีมอินฟรา: ตั้ง alert replication lag, ให้ replica มีสเปกเท่า DB หลักหรือสูงกว่าและใช้ parallel replication, แบ่งการลบข้อมูลจำนวนมากเป็นชุดเล็ก ๆ, ดูแลคิวรีสรุปผลที่รันนานบน replica - บนกราฟ: สูงตามจำนวนคนและโหลด (replication lag (วินาที)) - จุดที่ต้องดู: MySQL ดู Seconds_Behind_Source ของ SHOW REPLICA STATUS บน replica (เวอร์ชันก่อน 8.0.22 ใช้ SHOW SLAVE STATUS) PostgreSQL ดู write_lag, flush_lag และ replay_lag ใน pg_stat_replication บนเซิร์ฟเวอร์หลัก ส่วน RDS ดู ReplicaLag - สัญญาณว่าใช่: ช่วงที่มีคนแจ้งว่า “มองไม่เห็น” ค่า lag อยู่ที่หลายวินาทีขึ้นไป และเมื่อ lag หายแล้วดูอีกครั้งก็ปกติ lag เพิ่มขึ้นช่วงที่การเขียนทะลัก ลบข้อมูลจำนวนมาก หรือมีคิวรีสรุปผลยาว ๆ บน replica - สัญญาณว่าไม่ใช่: lag ใกล้ 0 แต่ยังมองไม่เห็น: น่าจะเป็นแคชของเซิร์ฟเวอร์เกมหรือการซิงก์ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: แม้การเขียนไม่มาก replica ก็ตามหลังได้ การลบข้อมูลจำนวนมากครั้งเดียวที่ใช้เวลา 10 นาทีบน DB หลัก จะทำให้ replica ตามหลังไปนานเท่ากันระหว่างที่รันซ้ำบน replica และคิวรีสรุปผลที่รันนานบน replica ก็ทำให้ตามช้าลงด้วย - แหล่งอ้างอิง: - [SHOW REPLICA STATUS Statement](https://dev.mysql.com/doc/refman/8.4/en/show-replica-status.html) · MySQL · Seconds_Behind_Source: ส่วนต่างเทียบกับเวลาที่ DB หลักบันทึกอีเวนต์ซึ่ง replica กำลัง apply อยู่ (replication lag) - [Replica Server Options and Variables](https://dev.mysql.com/doc/refman/8.4/en/replication-options-replica.html) · MySQL · replica_parallel_workers ให้หลายเธรด apply ทรานแซกชันแบบขนาน (ค่าเริ่มต้น 4, ถ้าเป็น 0 จะใช้เธรดเดียวไล่ตามลำดับ) - [Log-Shipping Standby Servers (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/warm-standby.html) · PostgreSQL · streaming replication เป็นแบบ asynchronous ตามค่าเริ่มต้น จึงมีดีเลย์ระหว่าง commit กับการสะท้อนบน replica (ถ้า replica ตามทัน ปกติไม่ถึง 1 วินาที) - [MySQL 8.0 Reference Manual: SHOW REPLICA STATUS Statement](https://dev.mysql.com/doc/refman/8.0/en/show-replica-status.html) · MySQL · ตั้งแต่ 8.0.22 ใช้ SHOW REPLICA STATUS แทน SHOW SLAVE STATUS เวอร์ชันก่อนหน้านั้นใช้ SHOW SLAVE STATUS - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · write_lag, flush_lag และ replay_lag ใน pg_stat_replication: เวลาตั้งแต่เซิร์ฟเวอร์หลักเขียน WAL จนถึงตอนที่ replica แจ้งว่าเขียนแล้ว, flush ลงดิสก์แล้ว และ apply แล้ว - [Amazon CloudWatch metrics for Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-metrics.html) · AWS · ReplicaLag: เวลาที่ read replica ตามหลังต้นทาง (วินาที) #### db-checkpoint · checkpoint/log flush · Checkpoint / log flush stalls จังหวะที่ DB เขียนส่วนที่เปลี่ยนแปลงในหน่วยความจำลงดิสก์รวดเดียวตามรอบ คิวรีจะช้าลง - ทำไม → ผลคือ → บนหน้าจอ: ส่วนที่เปลี่ยนแปลงสะสมแล้วถูกเขียนลงดิสก์ตามรอบ → ช่วงนั้นดิสก์ยุ่ง คิวรีจึงช้า → เซฟและโหลดช้าลงเป็นรอบ - อาการ: อินพุตดีเลย์, กระตุก / ปัจจัย: ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์, เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: เป็นรอบสม่ำเสมอ - ผู้รับผิดชอบหลัก: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: แบ่ง checkpoint เป็นช่วงเล็ก ๆ ให้สม่ำเสมอ, ตั้ง transaction log (redo log, WAL) ให้ใหญ่พอ, ใช้ดิสก์ที่เร็ว - บนกราฟ: พุ่งเป็นรอบ (ดีเลย์ของคิวรี DB, ปริมาณการเขียนดิสก์) - จุดที่ต้องดู: PostgreSQL ดูเวลา checkpoint และจำนวนบัฟเฟอร์ที่เขียนจาก log ของ log_checkpoints (เวอร์ชันใหม่เปิดเป็นค่าเริ่มต้น), จำนวนครั้งของ checkpoint (ตั้งแต่ 17 คือ num_timed และ num_requested ใน pg_stat_checkpointer, 16 ลงไปคือ checkpoints_timed และ checkpoints_req ใน pg_stat_bgwriter) และคำเตือน checkpoint_warning ส่วน MySQL ดูส่วนต่างระหว่าง Log sequence number กับ Last checkpoint at ในส่วน LOG ของ SHOW ENGINE INNODB STATUS วางซ้อนกับปริมาณการเขียนและดีเลย์การเขียนดิสก์ของเซิร์ฟเวอร์ - สัญญาณว่าใช่: ช่วงที่ดีเลย์ของคิวรีพุ่งตรงกับเวลา checkpoint และตอนนั้นปริมาณการเขียนกับดีเลย์การเขียนดิสก์พุ่งขึ้น ใน PostgreSQL ถ้า checkpoint ตามคำขอ (num_requested) มากกว่า checkpoint ตามเวลา (num_timed) มาก ถือว่า WAL แตะ max_wal_size บ่อยจน checkpoint มาเร็วกว่ากำหนด - สัญญาณว่าไม่ใช่: พุ่งเป็นรอบที่ไม่เกี่ยวกับเวลา checkpoint: น่าจะเป็นการสำรองข้อมูลหรืองาน batch (dk-backup, db-batch) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ถ้าตั้ง transaction log ที่เก็บบันทึกการเปลี่ยนแปลง (redo log ของ MySQL, WAL ของ PostgreSQL) เล็กเกินไป ทุกครั้งที่ log เต็ม DB ต้องรีบทำ checkpoint รวดเดียว throughput การเขียนจึงตกฮวบเป็นพัก ๆ - แหล่งอ้างอิง: - [WAL Configuration (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/wal-configuration.html) · PostgreSQL · checkpoint เกิดทุก 5 นาทีหรือทุก WAL 1 GB (max_wal_size) ตามค่าเริ่มต้น และแพงเพราะต้องเขียน dirty page ทั้งหมด checkpoint_completion_target กระจายการเขียนเพื่อเลี่ยง I/O พุ่ง ถ้าช่วงห่างระหว่าง checkpoint สั้นกว่า checkpoint_warning จะเขียนคำเตือนใน log ให้เพิ่ม max_wal_size - [Configuring Buffer Pool Flushing](https://dev.mysql.com/doc/refman/8.4/en/innodb-buffer-pool-flushing.html) · MySQL · เมื่อ redo log เต็มจะเกิด sharp checkpoint ทำให้ throughput ตกชั่วครู่, adaptive flushing กระจายการเขียนให้สม่ำเสมอ - [Error Reporting and Logging (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/runtime-config-logging.html) · PostgreSQL · log_checkpoints: เขียนจำนวนบัฟเฟอร์ที่เขียนและเวลาที่ใช้ของทุก checkpoint ลง log, ค่าเริ่มต้นเปิด - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · num_timed (checkpoint ที่ทำเพราะถึงเวลา) และ num_requested (checkpoint ที่ถูกร้องขอ) ใน pg_stat_checkpointer - [PostgreSQL 17 Release Notes](https://www.postgresql.org/docs/release/17.0/) · PostgreSQL · เพิ่ม pg_stat_checkpointer ใหม่ และย้ายคอลัมน์เกี่ยวกับ checkpoint มาจาก pg_stat_bgwriter - [The Cumulative Statistics System (PostgreSQL 16 Documentation)](https://www.postgresql.org/docs/16/monitoring-stats.html) · PostgreSQL · ถึงเวอร์ชัน 16 ใช้ checkpoints_timed และ checkpoints_req ใน pg_stat_bgwriter - [InnoDB Standard Monitor and Lock Monitor Output](https://dev.mysql.com/doc/refman/8.4/en/innodb-standard-monitor.html) · MySQL · ส่วน LOG: log sequence number ปัจจุบันและตำแหน่ง checkpoint ล่าสุด #### db-cold-cache · cold cache (หลังรีสตาร์ตใหม่ ๆ) · Cold buffer pool after restart เมื่อรีสตาร์ต DB แคชในหน่วยความจำจะว่างเปล่า ช่วงหนึ่งข้อมูลทุกอย่างที่ดึงจึงต้องอ่านจากดิสก์ - ทำไม → ผลคือ → บนหน้าจอ: รีสตาร์ต DB ตอนปิดปรับปรุง → ข้อมูลที่ใช้บ่อยไม่อยู่ในหน่วยความจำ จึงต้องอ่านจากดิสก์ → หลังปิดปรับปรุงใหม่ ๆ ล็อกอินและโหลดช้าอยู่พักหนึ่ง - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: อินฟรา DB (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เปิดให้เข้าแบบค่อยเป็นค่อยไป (ใช้คิวล็อกอินเพิ่มจำนวนคนที่ล็อกอินทีละขั้น) - งานฝั่งทีมอินฟรา: วอร์มแคชหลังรีสตาร์ต (warming, ตรวจการตั้งค่าบันทึกและกู้คืน buffer pool), DB ที่กู้มาจากสแนปช็อตให้อ่านดิสก์ล่วงหน้าด้วย - บนกราฟ: พุ่งทันทีหลังเปิดเซิร์ฟ/ปิดปรับปรุง (การอ่านดิสก์, อัตรา hit ของ buffer cache) - จุดที่ต้องดู: MySQL ดูอัตราส่วนระหว่าง Innodb_buffer_pool_reads (จำนวนครั้งที่ไม่มีใน buffer pool จนต้องอ่านจากดิสก์) กับ Innodb_buffer_pool_read_requests และความคืบหน้าการวอร์ม Innodb_buffer_pool_load_status ส่วน PostgreSQL ดู blks_read และ blks_hit ใน pg_stat_database และดูจำนวนการอ่านดิสก์ของเซิร์ฟเวอร์ DB ประกอบ - สัญญาณว่าใช่: หลังรีสตาร์ตใหม่ ๆ การอ่านดิสก์พุ่งและอัตรา hit ต่ำ แล้วค่อย ๆ ฟื้นตามเวลา และช่วงนั้นล็อกอินและโหลดช้า - สัญญาณว่าไม่ใช่: อัตรา hit เท่าปกติแต่หลังปิดปรับปรุงยังช้า: น่าจะเป็นคนแห่ล็อกอินและ N+1 (db-login-storm) หรือ connection pool (db-pool) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: MySQL จะบันทึกรายการ page ใน buffer pool ตอนปิด แล้วอ่านกลับเข้ามาเบื้องหลังตอนเปิด แต่ต้องใช้เวลากว่าจะเต็ม ถ้ากู้ DB บนคลาวด์มาจากสแนปช็อต (สำเนาดิสก์) ตัวดิสก์เองก็ช้าในทุกบล็อกที่อ่านครั้งแรก อาการจึงนานขึ้นไปอีก - กรณีจริง: roblox-2021 - แหล่งอ้างอิง: - [Saving and Restoring the Buffer Pool State](https://dev.mysql.com/doc/refman/8.4/en/innodb-preload-buffer-pool.html) · MySQL · เพื่อลดเวลาวอร์มหลังรีสตาร์ต จะบันทึกรายการ page ที่ใช้ล่าสุด (ค่าเริ่มต้น 25%) ตอนปิดแล้วอ่านกลับตอนเปิด, ทั้งสองอย่างเปิดเป็นค่าเริ่มต้น - [pg_prewarm — preload relation data into buffer caches](https://www.postgresql.org/docs/current/pgprewarm.html) · PostgreSQL · บันทึกเนื้อหาของ shared buffer ไว้เป็นระยะแล้วโหลดกลับหลังรีสตาร์ต (autoprewarm) - [Initialize Amazon EBS volumes](https://docs.aws.amazon.com/ebs/latest/userguide/ebs-initialize.html) · AWS · volume ที่สร้างจากสแนปช็อตจะมีดีเลย์สูงขึ้นและประสิทธิภาพตกจนกว่าจะดึงครบทุกบล็อก - [Server Status Variables](https://dev.mysql.com/doc/refman/8.4/en/server-status-variables.html) · MySQL · Innodb_buffer_pool_reads (จำนวน logical read ที่ไม่มีใน buffer pool จนต้องอ่านจากดิสก์โดยตรง), Innodb_buffer_pool_read_requests, Innodb_buffer_pool_load_status (ความคืบหน้าการวอร์ม) - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · blks_read (จำนวนบล็อกที่อ่านจากดิสก์) และ blks_hit (จำนวนบล็อกที่เจอใน buffer cache) ใน pg_stat_database #### db-login-storm · คนแห่ล็อกอินและคิวรี N+1 · Login storm, N+1 queries ถ้าการโหลดตัวละครหนึ่งตัวต้องดึงข้อมูลแยกกันหลายสิบครั้ง ผู้เล่นหลายหมื่นคนที่ล็อกอินพร้อมกันจะกลายเป็นคิวรีหลายล้านคิวรี - ทำไม → ผลคือ → บนหน้าจอ: ตอนโหลดตัวละคร ดึงไอเทม สกิล และเควสต์แยกกันทีละอย่าง → หลังปิดปรับปรุงใหม่ ๆ คนล็อกอินพร้อมกันจนคิวรีพุ่ง → ล็อกอินโหลดไม่จบ, การเซฟของคนที่กำลังเล่นอยู่ก็ล่าช้าไปด้วย - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ดึงข้อมูลรวมทีเดียว, คิวล็อกอิน, แคช, ตรวจจำนวนคิวรีที่เกิดจาก lazy loading ของ ORM - งานฝั่งทีมอินฟรา: จัดอันดับคิวรีที่ถูกเรียกบ่อยแล้วแชร์, มอนิเตอร์จำนวนคิวรีและจำนวน connection ช่วงล็อกอินหลังปิดปรับปรุง - บนกราฟ: พุ่งทันทีหลังเปิดเซิร์ฟ/ปิดปรับปรุง (จำนวนคิวรีต่อวินาทีของ DB, จำนวนการล็อกอิน) - จุดที่ต้องดู: วางจำนวนการล็อกอินหลังปิดปรับปรุงซ้อนกับจำนวนคิวรีต่อวินาทีของ DB (MySQL คือค่าที่เพิ่มขึ้นของ Questions) แล้วคำนวณจำนวนคิวรีต่อการล็อกอินหนึ่งครั้ง คิวรีที่ถูกเรียกบ่อยที่สุดดึงได้จาก COUNT_STAR ใน events_statements_summary_by_digest ของ MySQL และ calls ใน pg_stat_statements ของ PostgreSQL - สัญญาณว่าใช่: การล็อกอินหนึ่งครั้งใช้คิวรีหลายสิบคิวรี และคิวรีอันดับต้น ๆ เป็นคิวรีสั้น ๆ หน้าตาเดียวกันที่ดึงข้อมูลด้วย ID ตัวละครตัวเดียว ถ้าจำนวนคิวรีต่อการล็อกอินเพิ่มขึ้นหลังแพตช์ แพตช์นั้นคือจุดเริ่มต้น - สัญญาณว่าไม่ใช่: จำนวนคิวรีต่อการล็อกอินน้อยแต่แต่ละคิวรีช้า: น่าจะเป็น cold cache (db-cold-cache) หรืออินเด็กซ์ (db-no-index) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: lazy loading ของ ORM (ไลบรารีที่สร้างคิวรี DB ให้) สร้างคิวรีแบบนี้โดยที่นักพัฒนาเองก็ไม่รู้ตัว บนเซิร์ฟเวอร์ dev มีตัวละครไม่กี่ตัวจึงไม่เห็นผล แล้วมาโผล่ครั้งแรกตอนคนล็อกอินพร้อมกันบนเซิร์ฟเวอร์ live - แหล่งอ้างอิง: - [Efficient Querying](https://learn.microsoft.com/en-us/ef/core/performance/efficient-querying) · .NET · lazy loading ของ ORM ทำให้เกิดปัญหา N+1 ที่ส่งคิวรีเพิ่มอีกหนึ่งครั้งต่อทุกรายการ ทำให้ประสิทธิภาพตกมาก, แนะนำให้โหลดรวมทีเดียว (eager loading) - [pg_stat_statements — track statistics of SQL planning and execution](https://www.postgresql.org/docs/current/pgstatstatements.html) · PostgreSQL · รวบรวมจำนวนครั้งที่รัน (calls) และเวลารันรวมของแต่ละคำสั่ง เพื่อจัดอันดับคิวรีที่ถูกเรียกบ่อย - [Performance Schema Statement Digests and Sampling](https://dev.mysql.com/doc/refman/8.4/en/performance-schema-statement-digests.html) · MySQL · events_statements_summary_by_digest จัดกลุ่มคิวรีหน้าตาเดียวกันแล้วสรุปจำนวนครั้งและเวลา - [Statement Summary Tables](https://dev.mysql.com/doc/refman/8.4/en/performance-schema-statement-summary-tables.html) · MySQL · COUNT_STAR (จำนวนครั้งที่รัน) และ SUM_TIMER_WAIT (เวลารวม) ในตารางสรุป - [Server Status Variables](https://dev.mysql.com/doc/refman/8.4/en/server-status-variables.html) · MySQL · Questions: จำนวนคำสั่งที่ไคลเอนต์ส่งมา #### db-batch · งาน batch ขนาดใหญ่ · Batch jobs during service ถ้ารันการสรุปอันดับ, การส่งจดหมายในเกมแบบรวดเดียว หรือการล้างข้อมูลเก่าระหว่างให้บริการ งานเหล่านี้จะยึดล็อกและดิสก์ไว้ - ทำไม → ผลคือ → บนหน้าจอ: รันงานขนาดใหญ่ในช่วงเวลาให้บริการ → ล็อกเป็นช่วงกว้าง, ถือครองดิสก์และ CPU → เทรดและเซฟล้มเหลวในบางช่วงเวลา, โหลดช้า - อาการ: อินพุตดีเลย์, กดไม่ติด/โรลแบ็ค / ปัจจัย: ความหน่วง, การหยุดชะงัก - ใครเจอ: เฉพาะบางฟีเจอร์, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: เป็นรอบสม่ำเสมอ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: แบ่งเป็นชุดเล็ก ๆ ทำทีละน้อย, สรุปผลบน replica - งานฝั่งทีมอินฟรา: จัด replica สำหรับงานสรุปผล, ตั้งเวลา batch ไว้ช่วงที่ว่าง, เฝ้าดู lock escalation และการรอ gap lock - บนกราฟ: พุ่งเป็นรอบ (ดีเลย์ของคิวรี DB, การรอล็อก) - จุดที่ต้องดู: หาคิวรียาวที่รันอยู่ในช่วงที่แลค MySQL ดู slow query log ส่วน PostgreSQL ดู query_start และ query ใน pg_stat_activity แล้วเทียบกับเมตริกการรอล็อกในช่วงเดียวกันและตารางเวลา batch (cron, event scheduler ของ DB) SQL Server บันทึก lock escalation ด้วย extended event lock_escalation - สัญญาณว่าใช่: คิวรี UPDATE, DELETE หรือสรุปผลขนาดใหญ่รันเวลาเดิมทุกครั้ง และระหว่างนั้นการรอล็อกกับอัตราการใช้งานดิสก์สูงขึ้นพร้อมกัน - สัญญาณว่าไม่ใช่: ช่วงนั้นไม่มีคิวรียาว: น่าจะเป็น checkpoint (db-checkpoint) หรือการสำรองข้อมูลของเซิร์ฟเวอร์ (dk-backup) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: SQL Server จะเปลี่ยนเป็นล็อกทั้งตารางเมื่อคำสั่งเดียวถือ row lock เกินประมาณ 5,000 ตัว (lock escalation) ทันทีนั้นทุกคำขอที่ใช้ตารางเดียวกันจะหยุด MySQL ก็เช่นกัน ด้วยการตั้งค่าเริ่มต้น ถ้าแก้ข้อมูลด้วยเงื่อนไขแบบช่วง จะล็อกไปถึงช่องว่างระหว่างแถว (gap lock) ทำให้เพิ่มแถวใหม่ไม่ได้ - แหล่งอ้างอิง: - [Transaction Locking and Row Versioning Guide](https://learn.microsoft.com/en-us/sql/relational-databases/sql-server-transaction-locking-and-row-versioning-guide) · Microsoft SQL Server · ถ้าคำสั่งเดียวถือล็อกบนตาราง (หรืออินเด็กซ์) เดียวตั้งแต่ 5,000 ตัวขึ้นไปจะเกิด lock escalation, บันทึกได้ด้วย extended event lock_escalation - [InnoDB Locking](https://dev.mysql.com/doc/refman/8.4/en/innodb-locking.html) · MySQL · ที่ระดับ isolation เริ่มต้นของ InnoDB คือ REPEATABLE READ การค้นและการสแกนใช้ next-key lock ทำให้ gap lock ขวางการเพิ่มแถวใหม่ในช่องว่างนั้น - [The Slow Query Log](https://dev.mysql.com/doc/refman/8.4/en/slow-query-log.html) · MySQL · บันทึกคิวรีที่เกิน long_query_time พร้อมเวลารัน (Query_time), เวลาล็อก (Lock_time) และจำนวนแถวที่อ่าน - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · pg_stat_activity: คิวรีที่กำลังรันอยู่ (query) และเวลาที่เริ่ม (query_start) ของแต่ละเซสชัน #### db-failover · failover ของ DB · Database failover ระหว่างที่ DB หลักล่มและสลับไปใช้ DB สำรอง จะเขียนข้อมูลไม่ได้ และข้อมูลช่วงท้ายที่ยัง replicate ไม่ทันอาจหายไป - ทำไม → ผลคือ → บนหน้าจอ: DB หลักขัดข้อง DB สำรองจึงถูกยกขึ้นเป็นตัวหลัก (promote) → ระหว่างสลับ เขียนไม่ได้หลายวินาทีถึงไม่กี่นาที ถ้าเป็น asynchronous replication ข้อมูลที่ยังไม่ได้ replicate อาจหาย → เซฟไม่สำเร็จทั้งหมดชั่วครู่, ไอเทมและค่าประสบการณ์ถูกโรลแบ็ค - อาการ: กดไม่ติด/โรลแบ็ค, ค้าง, หลุด, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: การหยุดชะงัก, แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟรา DB (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ทำให้การเซฟลองใหม่ได้, ตั้งค่าให้ทิ้งการเชื่อมต่อที่ขาดอย่างรวดเร็วแล้วเชื่อมต่อใหม่ไปยังที่อยู่ใหม่ (connection pool, DNS cache), ตรวจการเชื่อมต่อใหม่ตอนซ้อม failover - งานฝั่งทีมอินฟรา: ใช้ synchronous หรือ semi-synchronous replication (แลกกับดีเลย์การเขียน), ซ้อม failover, มอนิเตอร์เวลา failover และ replication lag - ตัวเลขที่ควรรู้: การ failover อัตโนมัติของ managed DB ปกติใช้เวลาหลายสิบวินาทีถึง 2 นาที ถ้าเป็น asynchronous replication อาจเสียการเซฟล่าสุดไปเท่ากับ replication lag (ไม่ถึง 1 วินาทีถึงหลายวินาที) - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนการเชื่อมต่อ DB, จำนวนข้อผิดพลาดในการเขียน) - จุดที่ต้องดู: วางบันทึก failover ฝั่ง DB (RDS คืออีเวนต์ RDS-EVENT-0013 เริ่ม failover และ RDS-EVENT-0049 failover เสร็จ ส่วน DB ที่ดูแลเองคือ log การ promote) กับจำนวนการเชื่อมต่อ DB และจำนวนข้อผิดพลาดในการเชื่อมต่อของเซิร์ฟเวอร์เกมไว้บนกราฟเดียวกัน ถ้าเป็น asynchronous replication ให้ดู replication lag ก่อนเกิดเหตุด้วย (RDS ReplicaLag, replay_lag ใน pg_stat_replication ของ PostgreSQL) - สัญญาณว่าใช่: เซฟไม่สำเร็จกระจุกอยู่ในช่วงหนึ่ง และช่วงนั้นอยู่ระหว่างเวลาเริ่มและเวลาเสร็จ failover ปริมาณที่ย้อนกลับใกล้เคียงกับ replication lag ก่อนเกิดเหตุ เซิร์ฟเวอร์เกมที่ยัง error ต่อหลัง failover เสร็จ คือยังใช้การเชื่อมต่อที่ต่อไว้กับที่อยู่เดิม - สัญญาณว่าไม่ใช่: การเชื่อมต่อขาดในช่วงที่ไม่มีบันทึก failover: น่าจะเป็นเครือข่ายหรือ DB โหลดเกิน - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - กรณีจริง: riot-euw-2021 - แหล่งอ้างอิง: - [Failing over a Multi-AZ DB instance for Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.Failover.html) · AWS · Multi-AZ failover ปกติใช้ 60–120 วินาที, หลัง failover ต้องเชื่อมต่อใหม่ และแนะนำให้ตั้ง TTL ของ DNS cache ใน JVM ไม่เกิน 60 วินาที - [High availability for Amazon Aurora](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Concepts.AuroraHighAvailability.html) · AWS · ระหว่างขัดข้อง การอ่านและเขียนล้มเหลว ปกติกลับมาภายใน 60 วินาที (ส่วนใหญ่ภายใน 30 วินาที) - [Semisynchronous Replication](https://dev.mysql.com/doc/refman/8.4/en/replication-semisync.html) · MySQL · asynchronous replication ถ้า DB หลักล่ม ทรานแซกชันที่ commit แล้วอาจไม่มีบน replica, semi-synchronous ลดปัญหานี้ด้วยการรอให้ replica หนึ่งตัวยืนยันว่าได้รับแล้ว แต่ดีเลย์จะเพิ่มขึ้น - [Log-Shipping Standby Servers (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/warm-standby.html) · PostgreSQL · log shipping เป็นแบบ asynchronous ถ้าเซิร์ฟเวอร์หลักล่ม ทรานแซกชันที่ยังส่งไม่ทันจะหาย, ดีเลย์ของ streaming replication ปกติไม่ถึง 1 วินาที - [Amazon RDS event categories and event messages](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Events.Messages.html) · AWS · RDS-EVENT-0013: เริ่ม Multi-AZ failover, RDS-EVENT-0049: Multi-AZ failover เสร็จ - [Amazon CloudWatch metrics for Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-metrics.html) · AWS · ReplicaLag: เวลาที่ read replica ตามหลังต้นทาง (วินาที) - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · replay_lag ใน pg_stat_replication: เวลาตั้งแต่เซิร์ฟเวอร์หลักเขียน WAL จนถึงตอนที่ replica แจ้งว่า apply แล้ว #### db-save-interval · ความคืบหน้าหายเพราะรอบเซฟยาว · Periodic save window ถ้าเซฟแค่ทุกไม่กี่นาทีเพื่อลดโหลด เมื่อเซิร์ฟเวอร์ล่มในระหว่างนั้น ความคืบหน้าจะหายไป - ทำไม → ผลคือ → บนหน้าจอ: เซฟสถานะตัวละครทุกไม่กี่นาที → เซิร์ฟเวอร์แครชหรือขัดข้องในระหว่างนั้น → เข้าเกมใหม่แล้วกลับไปเป็นสถานะเมื่อไม่กี่นาทีก่อน (โรลแบ็ค) - อาการ: กดไม่ติด/โรลแบ็ค / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางจุด/บางแชนแนล / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: เซฟทันทีเมื่อเกิดเหตุการณ์สำคัญ (เทรด, ได้ของหายาก), บันทึก log การเปลี่ยนแปลง - งานฝั่งทีมอินฟรา: ตรวจว่า DB มี IOPS และ CPU เหลือพอรองรับการเขียนที่เพิ่มขึ้นเมื่อลดรอบเซฟ - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนการเชื่อมต่อ, จำนวนการแจ้งโรลแบ็ค) - จุดที่ต้องดู: วางเวลาที่แครชหรือขัดข้องคู่กับเวลาเซฟล่าสุดของตัวละครที่แจ้งโรลแบ็ค (log การเซฟของเซิร์ฟเวอร์เกมหรือคอลัมน์เวลาแก้ไขใน DB) - สัญญาณว่าใช่: จุดที่ย้อนกลับตรงกับเวลาเซฟล่าสุดก่อนแครช และเวลาที่หายไปสั้นกว่ารอบเซฟ - สัญญาณว่าไม่ใช่: log ของเซิร์ฟเวอร์เกมบอกว่าเซฟเสร็จแล้วแต่ยังย้อนกลับ: น่าจะเป็นข้อมูลหายจาก failover ของ DB (db-failover) หรือค่าเก่าที่อ่านจาก replica (db-replica-lag) - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Asynchronous Commit (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/wal-async-commit.html) · PostgreSQL · การรวมการเขียนแล้วค่อย flush ทีหลังทำให้ throughput สูงขึ้น แต่ตอนขัดข้องทรานแซกชันล่าสุดอาจหายได้ (เป็นการแลกแบบเดียวกัน) - [Redis persistence](https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/) · Redis · ถ้าสร้าง RDB snapshot ทุกไม่กี่นาที ต้องยอมรับว่าข้อมูลช่วงไม่กี่นาทีสุดท้ายอาจหายเมื่อปิดตัวผิดปกติ #### db-cache-stampede · แคชสแตมปีด · Cache stampede / thundering herd ถ้าแคชของข้อมูลยอดนิยมหมดอายุพร้อมกัน คำขอหลายพันรายการจะแห่ไปที่ DB พร้อมกัน - ทำไม → ผลคือ → บนหน้าจอ: ข้อมูลยอดนิยมที่เก็บใน Redis หรือที่อื่นหมดอายุพร้อมกัน → คำขอที่พยายามสร้างข้อมูลเดียวกันใหม่แห่ไปที่ DB พร้อมกัน → DB โหลดเกิน หลายฟีเจอร์จึงช้าลงหรือค้างต่อ ๆ กันไป - อาการ: อินพุตดีเลย์, ค้าง, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: เป็นรอบสม่ำเสมอ, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: สุ่มกระจายเวลาหมดอายุ, ให้คำขอเดียวรีเฟรช ที่เหลือใช้ค่าเก่า - งานฝั่งทีมอินฟรา: จัด replica และ failover อัตโนมัติไม่ให้แคชว่างทั้งก้อนเมื่อ Redis รีสตาร์ตหรือขัดข้อง, ตรวจว่า DB มีกำลังเหลือพอรับได้แม้แคชว่าง - บนกราฟ: พุ่งเป็นรอบ (อัตรา hit ของแคช, จำนวนคิวรีต่อวินาทีของ DB) - จุดที่ต้องดู: keyspace_hits และ keyspace_misses (อัตรา hit), expired_keys และการรีสตาร์ต (uptime_in_seconds) จาก INFO ของ Redis วางซ้อนกับจำนวนคิวรีต่อวินาทีของ DB และนับว่าในจังหวะนั้นมีคิวรีเดียวกันรันพร้อมกันบน DB กี่ตัว (MySQL SHOW PROCESSLIST, PostgreSQL pg_stat_activity) - สัญญาณว่าใช่: ช่วงที่ cache miss พุ่งขึ้นทันที จำนวนคิวรี DB พุ่งตาม และคิวรีที่รันพร้อมกันส่วนใหญ่เป็นคิวรีเดียวกันที่อ่านข้อมูลเดียวกัน ตรงกับรอบหมดอายุของคีย์ยอดนิยมหรือเวลาที่ Redis รีสตาร์ต - สัญญาณว่าไม่ใช่: cache miss เท่าปกติแต่คิวรี DB เพิ่มอย่างเดียว: น่าจะเป็นคนแห่ล็อกอิน (db-login-storm) หรืองาน batch (db-batch) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ถ้า Redis รีสตาร์ตหรือขัดข้องจนแคชว่างทั้งก้อน ก็เกิดเรื่องเดียวกัน ยิ่งระบบที่วางใจแคชจนตั้ง DB ไว้เล็กยิ่งเสี่ยง - แหล่งอ้างอิง: - [Scaling Memcache at Facebook (NSDI '13)](https://www.usenix.org/conference/nsdi13/technical-sessions/presentation/nishtala) · USENIX · เมื่อคีย์ที่ใช้บ่อยถูก invalidate การอ่านจำนวนมากจะแห่ไปที่ DB (thundering herd), ป้องกันด้วย lease (ให้ไคลเอนต์เดียวรีเฟรช) และการคืนค่าเก่า, คลัสเตอร์ที่แคชว่างต้องวอร์มแยก - [Optimal Probabilistic Cache Stampede Prevention](https://www.vldb.org/pvldb/vol8/p886-vattani.pdf) · VLDB Endowment · เมื่อรายการยอดนิยมหมดอายุ หลายคำขอจะสร้างใหม่พร้อมกัน (cache stampede), ป้องกันด้วยการรีเฟรชล่วงหน้าแบบสุ่มตามความน่าจะเป็นก่อนหมดอายุ - [High availability with Redis Sentinel](https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/) · Redis · failover อัตโนมัติที่ promote replica เมื่อเซิร์ฟเวอร์หลักล่ม - [INFO](https://redis.io/docs/latest/commands/info/) · Redis · keyspace_hits และ keyspace_misses (จำนวนครั้งที่ค้นคีย์เจอและไม่เจอ), expired_keys (จำนวนคีย์ที่หมดอายุ), uptime_in_seconds (เวลาตั้งแต่เริ่มทำงาน) - [SHOW PROCESSLIST Statement](https://dev.mysql.com/doc/refman/8.4/en/show-processlist.html) · MySQL · คำสั่งที่กำลังรัน (Info) และเวลาที่อยู่ในสถานะปัจจุบัน (Time, วินาที) ของแต่ละเซสชัน - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · pg_stat_activity: คิวรีที่กำลังรันอยู่ (query) ของแต่ละเซสชัน #### db-long-tx · ทรานแซกชันที่เปิดทิ้งไว้นาน · Long-running transaction / MVCC purge lag ถ้าทรานแซกชันหนึ่งเปิดทิ้งไว้นาน จะถือล็อกไว้ตลอด และ DB ล้างข้อมูลเวอร์ชันเก่า (purge) ไม่ได้ ทั้งระบบจึงค่อย ๆ ช้าลง - ทำไม → ผลคือ → บนหน้าจอ: เปิดทรานแซกชันไว้แล้วรอคำตอบจากเซิร์ฟเวอร์อื่น หรือรันคิวรีสรุปผลยาว ๆ บน DB หลักระหว่างให้บริการ → ล็อกที่ถือไว้ไม่ถูกปล่อย และข้อมูลเวอร์ชันเก่าที่ต้องล้างสะสมเรื่อย ๆ → ฟีเจอร์ที่ใช้แถวนั้นไทม์เอาต์, การเซฟและการอ่านข้อมูลโดยรวมช้าลงตลอดหลายชั่วโมง - อาการ: อินพุตดีเลย์, กดไม่ติด/โรลแบ็ค / ปัจจัย: ความหน่วง, การหยุดชะงัก - ใครเจอ: เฉพาะบางฟีเจอร์, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ยิ่งเปิดไว้นานยิ่งเป็น, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ไม่รอ network call หรืออินพุตจากผู้ใช้ภายในทรานแซกชัน, รันคิวรีสรุปผลบน replica - งานฝั่งทีมอินฟรา: ตั้ง alert และบังคับปิดทรานแซกชันที่เปิดนาน, จัด replica สำหรับงานสรุปผล, เฝ้าดูการเพิ่มของ undo log และ dead tuple - บนกราฟ: ค่อย ๆ สูงขึ้น (ความยาว undo log (History list length), จำนวน dead tuple) - จุดที่ต้องดู: MySQL หาทรานแซกชันที่เก่าที่สุดจาก trx_started ใน INFORMATION_SCHEMA.INNODB_TRX และดู History list length (ปริมาณ undo log ที่ยังล้างไม่ได้) ในส่วน TRANSACTIONS ของ SHOW ENGINE INNODB STATUS ส่วน PostgreSQL ดู xact_start และเซสชันที่ state เป็น idle in transaction ใน pg_stat_activity และ n_dead_tup ใน pg_stat_user_tables - สัญญาณว่าใช่: มีทรานแซกชันที่เปิดมาไม่กี่นาทีถึงหลายชั่วโมง ระหว่างนั้น History list length หรือ n_dead_tup สูงขึ้นเรื่อย ๆ และลดลงเมื่อปิดทรานแซกชันนั้นแล้วการล้าง (purge, VACUUM) ได้ทำงาน - สัญญาณว่าไม่ใช่: ไม่มีทรานแซกชันเก่าแต่ช้าโดยรวม: น่าจะเป็น checkpoint (db-checkpoint) หรือดิสก์ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: DB เก็บเวอร์ชันเก่าไว้เพื่อให้ฝั่งที่อ่านเห็นข้อมูลก่อนถูกแก้ (MVCC) และลบบันทึกนี้ได้ก็ต่อเมื่อทรานแซกชันที่เก่าที่สุดจบแล้ว ถ้าทรานแซกชันเดียวเปิดอยู่หลายชั่วโมง MySQL จะมี undo log สะสม ส่วน PostgreSQL จะมีแถวที่ตายแล้ว (dead tuple) ซึ่ง VACUUM ล้างไม่ได้สะสมอยู่ ส่วน SQL Server บางครั้ง transaction log ไม่ลดขนาดลงจนดิสก์เต็ม - แหล่งอ้างอิง: - [InnoDB Multi-Versioning](https://dev.mysql.com/doc/refman/8.4/en/innodb-multi-versioning.html) · MySQL · ถ้ายังมีทรานแซกชันที่อาจต้องเห็นเวอร์ชันเก่า จะทิ้ง update undo log ไม่ได้ rollback segment จึงโตขึ้น, แนะนำให้ commit บ่อย ๆ แม้เป็นทรานแซกชันที่อ่านอย่างเดียว - [Routine Vacuuming (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/routine-vacuuming.html) · PostgreSQL · เวอร์ชันเก่าของแถวลบไม่ได้ตราบที่ทรานแซกชันอื่นยังมองเห็นได้, ทรานแซกชันที่เปิดนานต้องจบหรือปิดเซสชัน - [Client Connection Defaults (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/runtime-config-client.html) · PostgreSQL · idle_in_transaction_session_timeout: ตัดเซสชันที่เปิดทรานแซกชันไว้แล้วอยู่เฉย ๆ เพื่อไม่ให้ถือล็อกไว้นาน - [Troubleshoot a full transaction log (SQL Server Error 9002)](https://learn.microsoft.com/en-us/sql/relational-databases/logs/troubleshoot-a-full-transaction-log-sql-server-error-9002) · Microsoft SQL Server · active transaction ที่รันนานขวางการล้าง transaction log - [The INFORMATION_SCHEMA INNODB_TRX Table](https://dev.mysql.com/doc/refman/8.4/en/information-schema-innodb-trx-table.html) · MySQL · TRX_STARTED: เวลาที่ทรานแซกชันเริ่ม - [Purge Configuration](https://dev.mysql.com/doc/refman/8.4/en/innodb-purge-configuration.html) · MySQL · purge ล้างรายการ undo log ของทรานแซกชันที่ commit แล้ว (history list), ปริมาณที่กองรอแสดงเป็น History list length ในส่วน TRANSACTIONS ของ SHOW ENGINE INNODB STATUS - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · xact_start (เวลาที่ทรานแซกชันเริ่ม) และ state (idle in transaction) ใน pg_stat_activity, n_dead_tup (จำนวน dead tuple โดยประมาณ) ใน pg_stat_user_tables #### db-redis-block · คำสั่ง Redis ที่ช้า · Redis blocking commands (single-threaded) Redis ประมวลผลคำสั่งทีละคำสั่ง คำสั่งที่ช้าเพียงคำสั่งเดียวจึงขวางทุกคำขอที่ตามมา - ทำไม → ผลคือ → บนหน้าจอ: ใช้ KEYS ค้นทั้งหมดระหว่างให้บริการ, อ่านหรือลบอันดับหรือลิสต์ที่มีสมาชิกหลายล้านตัวทั้งก้อน → คำขออื่นทั้งหมดต้องรอจนคำสั่งนั้นเสร็จ (หลายสิบ ms ถึงหลายวินาที) → ฟีเจอร์ที่ใช้เซสชัน อันดับ และแคชหยุดแวบพร้อมกัน, ล็อกอินช้า - อาการ: ค้าง, อินพุตดีเลย์, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์, เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, เป็นรอบสม่ำเสมอ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ใช้ SCAN แทน KEYS, แบ่งคีย์ใหญ่, ลบด้วย UNLINK (ลบเบื้องหลัง), กระจายเวลาหมดอายุที่กระจุกอยู่ในวินาทีเดียวกัน - งานฝั่งทีมอินฟรา: เฝ้าดูบันทึกคำสั่งช้า (SLOWLOG), บล็อกคำสั่งอันตรายอย่าง KEYS บนเซิร์ฟเวอร์ที่ให้บริการจริง, ตรวจคีย์ใหญ่เป็นประจำ, ปิด THP และกันหน่วยความจำไว้พอสำหรับ fork, ทำการบันทึก RDB และ AOF บน replica - ตัวเลขที่ควรรู้: คำสั่งทั่วไปใช้ไม่ถึง 1 ms ถ้าจัดการสมาชิกหลายล้านตัวในครั้งเดียว อาจใช้ตั้งแต่หลายร้อย ms ไปจนถึงหลายวินาที - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (ดีเลย์การตอบของ Redis, จำนวนคำสั่งที่ช้า) - จุดที่ต้องดู: ดูคำสั่งที่เกิน slowlog-log-slower-than ด้วย SLOWLOG GET และเปิด latency monitor (ค่าเริ่มต้นปิด) ด้วย CONFIG SET latency-monitor-threshold แล้วดูดีเลย์แยกตามอีเวนต์ เช่น fork และ expire-cycle ด้วย LATENCY LATEST และ LATENCY DOCTOR ตรวจเวลา fork และคีย์ใหญ่ด้วย latest_fork_usec ใน INFO และ redis-cli --bigkeys - สัญญาณว่าใช่: ช่วงที่หยุด SLOWLOG มีคำสั่ง KEYS หรือคำสั่งที่จัดการคีย์ใหญ่ทั้งก้อน หรือ LATENCY บันทึกอีเวนต์ fork หรือ expire-cycle ในช่วงเดียวกันนานตั้งแต่หลายสิบ ms ขึ้นไป - สัญญาณว่าไม่ใช่: SLOWLOG และ LATENCY ว่างแต่ช้าเฉพาะฝั่งเซิร์ฟเวอร์เกม: น่าจะเป็นเครือข่ายหรือการรอภายในเซิร์ฟเวอร์เกม (SLOWLOG วัดแค่เวลารันคำสั่ง ไม่รวมเวลารับส่งกับไคลเอนต์) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ตอน fork โปรเซสเพื่อสร้างไฟล์บันทึก (RDB snapshot) หรือเขียน AOF ใหม่ ก็หยุดเช่นกัน บนเซิร์ฟเวอร์ปัจจุบันใช้ราว 10 ms ต่อหน่วยความจำ 1 GB ถ้า 30 GB ก็ประมาณ 300 ms ถ้าเปิด huge page (THP) ไว้ หลัง fork การเขียนแต่ละครั้งจะคัดลอก huge page ทั้งหน้า (copy-on-write) ทำให้ทั้งเวลาหยุดและการใช้หน่วยความจำเพิ่มขึ้นมาก จึงมักปิด THP และกันหน่วยความจำเหลือไว้มาก ๆ ตอนที่คีย์จำนวนมากหมดอายุในวินาทีเดียวกัน Redis ก็หยุดแวบเพราะต้องลบคีย์เหล่านั้น - แหล่งอ้างอิง: - [Diagnosing latency issues](https://redis.io/docs/latest/operate/oss_and_stack/management/optimization/latency/) · Redis · เธรดเดียวประมวลผลคำขอตามลำดับ คำสั่งที่ช้าจึงขวางทุกคำขอที่ตามมา, ใช้ SCAN แทน KEYS, fork วัดจริงบนเครื่อง physical และ VM รุ่นใหม่ได้ราว 9–13 ms ต่อ 1 GB, THP ทำให้ดีเลย์และหน่วยความจำพุ่งจากการคัดลอกหลัง fork, คีย์จำนวนมากหมดอายุในวินาทีเดียวกันทำให้หยุด - [KEYS](https://redis.io/docs/latest/commands/keys/) · Redis · ใช้บน production ด้วยความระมัดระวังอย่างยิ่ง อาจทำลายประสิทธิภาพบน DB ขนาดใหญ่ได้ (บนโน้ตบุ๊กระดับทั่วไป คีย์ 1,000,000 ตัวใช้ 40 ms) - [UNLINK](https://redis.io/docs/latest/commands/unlink/) · Redis · การลบแบบ asynchronous ที่ถอดคีย์ออกทันทีแล้วไปคืนหน่วยความจำในเธรดอื่น - [SLOWLOG](https://redis.io/docs/latest/commands/slowlog/) · Redis · log คำสั่งช้าที่บันทึกคำสั่งที่เกิน slowlog-log-slower-than, เวลารันไม่รวม I/O รับส่งกับไคลเอนต์ - [Redis latency monitoring](https://redis.io/docs/latest/operate/oss_and_stack/management/optimization/latency-monitor/) · Redis · latency-monitor-threshold ค่าเริ่มต้น 0 (ปิด), LATENCY LATEST และ LATENCY DOCTOR, บันทึกดีเลย์แยกตามอีเวนต์ เช่น fork และ expire-cycle - [INFO](https://redis.io/docs/latest/commands/info/) · Redis · latest_fork_usec: เวลาที่ fork ครั้งล่าสุดใช้ (ไมโครวินาที) - [Redis CLI](https://redis.io/docs/latest/develop/tools/cli/) · Redis · --bigkeys: ไล่ดู keyspace เพื่อหาคีย์ใหญ่ #### db-plan-flip · คิวรีช้าเพราะ query plan เปลี่ยน · Query plan regression (stats, parameter sniffing) โค้ดเหมือนเดิม แต่ถ้า DB เปลี่ยนวิธีประมวลผลคิวรีเดิม (query plan) คิวรีที่เมื่อวานใช้ 2 ms วันนี้อาจกลายเป็นหลายร้อย ms - ทำไม → ผลคือ → บนหน้าจอ: สถิติอัปเดตอัตโนมัติ, DB รีสตาร์ต หรือการกระจายของข้อมูลเปลี่ยน ทำให้ DB วาง query plan ใหม่ → DB เลือก plan ที่ไม่ใช้อินเด็กซ์ คิวรีเดิมจึงช้าลงหลายสิบถึงหลายร้อยเท่า และ connection ถูกถือครองไว้ → ไม่มี deploy อะไรเลย แต่การโหลดของบางฟีเจอร์ช้าลงทันที และคำขออื่นต้องรอไปด้วย - อาการ: อินพุตดีเลย์, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: ความหน่วง, การหยุดชะงัก - ใครเจอ: เฉพาะบางฟีเจอร์, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟรา DB (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: คิวรีที่จำนวนผลลัพธ์ต่างกันมากตามค่าที่ส่งเข้าไปให้แยกเขียนหรือพิจารณาใช้ plan hint, ออกแบบคิวรีให้ใช้อินเด็กซ์แน่นอน - งานฝั่งทีมอินฟรา: เฝ้าดูคิวรีที่ช้าและบันทึก query plan, ตรึง plan ที่ดีไว้ (Query Store ของ SQL Server ฯลฯ), จัดการเวลาอัปเดตสถิติ - บนกราฟ: ขึ้นเป็นขั้นบันไดจากจุดหนึ่ง (เวลารันเฉลี่ยแยกตามคิวรี) - จุดที่ต้องดู: เก็บเวลาเฉลี่ยของคิวรีรูปแบบเดียวกันเป็นระยะแล้วดูแนวโน้ม MySQL คือ AVG_TIMER_WAIT ใน events_statements_summary_by_digest, PostgreSQL คือ mean_exec_time ใน pg_stat_statements (12 ลงไปคือ mean_time) เทียบ query plan ก่อนและหลังช้าด้วย EXPLAIN หรือ auto_explain ของ PostgreSQL ส่วน SQL Server ใช้หน้า Regressed Queries ของ Query Store - สัญญาณว่าใช่: ในช่วงที่ไม่มี deploy เวลาเฉลี่ยของคิวรีหนึ่งขึ้นเป็นขั้นบันไดหลายสิบเท่า จุดนั้นตรงกับการอัปเดตสถิติหรือการรีสตาร์ต DB และ query plan เปลี่ยนไปแล้ว - สัญญาณว่าไม่ใช่: query plan เหมือนเดิมแต่ช้าลง: น่าจะเป็นข้อมูลที่เพิ่มขึ้น, การรอล็อก (db-hot-row) หรือดิสก์ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: SQL Server จะใช้ plan ที่วางตามค่าที่เข้ามาครั้งแรกซ้ำ (parameter sniffing) ถ้า plan ที่วางจากตัวละครใหม่ที่มีไอเทมไม่กี่ชิ้นถูกใช้กับตัวละครเก่าที่มีไอเทมหลายหมื่นชิ้น จะช้าลงมาก และกรณีกลับกันก็พบบ่อย บางครั้งรีสตาร์ตแล้ว plan ถูกล้างจนกลับมาปกติ ก่อนจะแย่ลงอีกครั้ง - แหล่งอ้างอิง: - [Query Processing Architecture Guide](https://learn.microsoft.com/en-us/sql/relational-databases/query-processing-architecture-guide) · Microsoft SQL Server · parameter sniffing: วาง query plan ตามค่าพารามิเตอร์ที่เข้ามาตอน compile หรือ recompile - [Parameter Sensitive Plan Optimization](https://learn.microsoft.com/en-us/sql/relational-databases/performance/parameter-sensitive-plan-optimization) · Microsoft SQL Server · ถ้าข้อมูลกระจายไม่สม่ำเสมอ plan ที่แคชไว้ตัวเดียวจะไม่เหมาะกับทุกค่าพารามิเตอร์ - [Monitor performance by using the Query Store](https://learn.microsoft.com/en-us/sql/relational-databases/performance/monitoring-performance-by-using-the-query-store) · Microsoft SQL Server · plan เปลี่ยนเพราะสถิติ สคีมา หรืออินเด็กซ์เปลี่ยน และ plan cache เก็บแค่ plan ล่าสุด, ใช้ plan forcing ของ Query Store ตรึง plan ที่ดี, ใช้หน้า Regressed Queries เทียบคิวรีที่ช้าลงกับ plan - [Statement Summary Tables](https://dev.mysql.com/doc/refman/8.4/en/performance-schema-statement-summary-tables.html) · MySQL · events_statements_summary_by_digest: COUNT_STAR และ AVG_TIMER_WAIT (เวลาเฉลี่ย) แยกตามรูปแบบคิวรี - [pg_stat_statements — track statistics of SQL planning and execution](https://www.postgresql.org/docs/current/pgstatstatements.html) · PostgreSQL · calls, total_exec_time และ mean_exec_time (เวลารันเฉลี่ย) ของแต่ละคำสั่ง - [pg_stat_statements (PostgreSQL 12 Documentation)](https://www.postgresql.org/docs/12/pgstatstatements.html) · PostgreSQL · ถึงเวอร์ชัน 12 ชื่อคอลัมน์คือ total_time และ mean_time - [auto_explain — log execution plans of slow queries](https://www.postgresql.org/docs/current/auto-explain.html) · PostgreSQL · บันทึก query plan ของคิวรีที่ใช้เวลานานกว่า auto_explain.log_min_duration ลง log #### db-ddl-lock · ล็อกจากการเปลี่ยนสคีมา (DDL) ระหว่างให้บริการ · Schema change lock (DDL / metadata lock) ถ้าเพิ่มคอลัมน์หรืออินเด็กซ์ให้ตารางระหว่างให้บริการ ล็อกที่ต้องใช้เพียงชั่วครู่ตัวเดียวอาจทำให้ทุกคำขอที่ใช้ตารางนั้นต้องรอ - ทำไม → ผลคือ → บนหน้าจอ: เพิ่มคอลัมน์หรืออินเด็กซ์ให้ตารางที่ใช้งานอยู่ผ่าน hotfix → การเปลี่ยนสคีมารอทรานแซกชันยาวที่เปิดอยู่ก่อน และทุกคำขอที่ตามมาต้องรอการเปลี่ยนสคีมานั้น → ฟีเจอร์ที่ใช้ตารางนั้น (กระเป๋า, จดหมาย ฯลฯ) ค้างทั้งหมดแล้วไทม์เอาต์ - อาการ: อินพุตดีเลย์, กดไม่ติด/โรลแบ็ค, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: การหยุดชะงัก - ใครเจอ: เฉพาะบางฟีเจอร์, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: อินฟรา DB (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: hotfix ที่มีการเปลี่ยนสคีมาให้นัดเวลากับอินฟรา DB, deploy โค้ดที่ทำงานได้แม้ยังไม่มีคอลัมน์ใหม่ก่อน - งานฝั่งทีมอินฟรา: ตั้งขีดจำกัดการรอล็อกให้สั้นแล้วลองใหม่เมื่อล้มเหลว, รันตอนที่ไม่มีทรานแซกชันยาว, ใช้เครื่องมือ online schema change, ตารางใหญ่ให้ทำช่วงปิดปรับปรุง - บนกราฟ: ขึ้นเป็นขั้นบันไดจากจุดหนึ่ง (จำนวนเซสชันที่รอล็อก, ดีเลย์ของคิวรีบนตารางนั้น) - จุดที่ต้องดู: MySQL นับเซสชันที่ State เป็น Waiting for table metadata lock ใน SHOW PROCESSLIST และหาเซสชันที่ขวางอยู่ (blocking_pid) ด้วย sys.schema_table_lock_waits ส่วน PostgreSQL ดูคำขอที่ granted เป็น false และ AccessExclusiveLock ใน pg_locks และหาเซสชันที่ขวางอยู่ด้วย pg_blocking_pids() - สัญญาณว่าใช่: ตั้งแต่เวลาที่เริ่มเปลี่ยนสคีมา ทุกคิวรีที่ใช้ตารางนั้นกองรอล็อก และหัวแถวคือทรานแซกชันที่ยังไม่จบหรือคำสั่งเปลี่ยนสคีมา - สัญญาณว่าไม่ใช่: การรอกระจุกเฉพาะบางแถว และแถวอื่นในตารางเดียวกันยังประมวลผลได้ดี: น่าจะเป็น hot row (db-hot-row) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ตอนเปลี่ยนสคีมา MySQL ถือ metadata lock ส่วน PostgreSQL ถือล็อกตารางที่แรงที่สุดชั่วครู่ แม้ตัวการเปลี่ยนจะเสร็จในพริบตา แต่ถ้ามีทรานแซกชันที่ยังไม่จบอยู่ข้างหน้าแม้แต่ตัวเดียว ทุกคำขอที่ตามมาจะต้องรอ - แหล่งอ้างอิง: - [Online DDL Performance and Concurrency](https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-performance.html) · MySQL · แม้เป็น online DDL ตอนจบก็ต้องใช้ exclusive metadata lock ชั่วครู่ ถ้ามีทรานแซกชันยาวจะต้องรอ และคำขอล็อกที่รออยู่จะขวางทุกทรานแซกชันที่ตามมา - [Server System Variables](https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html) · MySQL · lock_wait_timeout: ขีดจำกัดการรอ metadata lock, ค่าเริ่มต้น 31,536,000 วินาที (1 ปี) - [ALTER TABLE (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/sql-altertable.html) · PostgreSQL · ALTER TABLE ที่ไม่ได้ระบุอะไรเพิ่มจะถือล็อก ACCESS EXCLUSIVE ซึ่งแรงที่สุด - [Client Connection Defaults (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/runtime-config-client.html) · PostgreSQL · lock_timeout: ถ้ารอล็อกนานเกินเวลานี้จะยกเลิกคำสั่ง - [General Thread States](https://dev.mysql.com/doc/refman/8.4/en/general-thread-states.html) · MySQL · Waiting for table metadata lock: สถานะของเธรดที่กำลังรอ metadata lock - [The schema_table_lock_waits and x$schema_table_lock_waits Views](https://dev.mysql.com/doc/refman/8.4/en/sys-schema-table-lock-waits.html) · MySQL · เซสชันที่รอ metadata lock (waiting_query) และเซสชันที่ขวางอยู่ (blocking_pid) - [pg_locks (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/view-pg-locks.html) · PostgreSQL · ถ้า granted เป็น false แปลว่ากำลังรอล็อก, mode แสดงประเภทล็อก เช่น AccessExclusiveLock - [System Information Functions and Operators (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/functions-info.html) · PostgreSQL · pg_blocking_pids(): รายการเซสชันที่ขวางไม่ให้เซสชันที่ระบุได้ล็อก ### L13 สถาปัตยกรรมเซิร์ฟเวอร์และการดูแลระบบ (13 สาเหตุ) #### in-gateway · การผ่านเกตเวย์/พร็อกซี · Gateway / proxy hop ถ้าวางเซิร์ฟเวอร์คั่นกลางระหว่างไคลเอนต์กับเซิร์ฟเวอร์เกม ทุกครั้งที่ผ่านจะมีเวลาประมวลผลเพิ่มขึ้น และเซิร์ฟเวอร์ตัวนั้นจะกลายเป็น single point of failure (จุดเดียวที่ล่มแล้วกระทบทั้งระบบ) - ทำไม → ผลคือ → บนหน้าจอ: โครงสร้างแบบ ไคลเอนต์ ↔ เกตเวย์ ↔ เซิร์ฟเวอร์เกม → มีเวลาประมวลผลและเวลารอที่เซิร์ฟเวอร์คั่นกลางเพิ่มเข้ามา ถ้าโหลดเกินจะกระทบทุกคน → ปิงสูงขึ้นทั้งหมด ถ้าเกตเวย์ล่ม ผู้เล่นทุกคนที่ผ่านเกตเวย์นั้นหลุด - อาการ: อินพุตดีเลย์, หลุด / ปัจจัย: ความหน่วง, การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ออกแบบให้เพิ่มเกตเวย์เป็นหลายเครื่องได้, ออกแบบให้เมื่อเกตเวย์ล่มแล้วต่อเข้าเกตเวย์อื่นใหม่ ตัวละครยังอยู่ต่อจากเดิม (เชื่อมต่อเซสชันใหม่) ไคลเอนต์: เชื่อมต่อใหม่อัตโนมัติเมื่อเกตเวย์หลุด - งานฝั่งทีมอินฟรา: ขยายเกตเวย์แนวนอน (เพิ่มจำนวนเครื่อง), มอนิเตอร์ CPU, จำนวนการเชื่อมต่อ และดีเลย์การประมวลผลของเกตเวย์แต่ละตัว - ตัวเลขที่ควรรู้: เพราะอยู่ในดาต้าเซ็นเตอร์เดียวกัน ปกติการผ่านแต่ละครั้งใช้เวลาไม่ถึง 1 ms แต่ถ้าเกตเวย์โหลดเกินจะเพิ่มเป็นหลายสิบถึงหลายร้อย ms - บนกราฟ: สูงตามจำนวนคนและโหลด (ดีเลย์การประมวลผลของเกตเวย์, CPU/จำนวนการเชื่อมต่อของเกตเวย์) - จุดที่ต้องดู: CPU และจำนวนการเชื่อมต่อของเกตเวย์ กับ Recv-Q ของซ็อกเก็ตบนเกตเวย์ (ss, netstat), ส่วนต่างของดีเลย์ก่อนและหลังผ่านเกตเวย์ ถ้าเป็นการเรียก HTTP/gRPC ที่ผ่าน service mesh ให้เทียบเมตริกมาตรฐานของ Istio istio_request_duration_milliseconds แยกฝั่งผู้ส่ง (reporter=source) กับฝั่งผู้รับ (reporter=destination) - สัญญาณว่าใช่: เวลาประมวลผลของเซิร์ฟเวอร์เกมเท่าเดิม แต่ดีเลย์ช่วงเกตเวย์เพิ่มขึ้นอย่างเดียว และช่วงนั้น CPU ของเกตเวย์อิ่มตัวหรือ Recv-Q สะสม - สัญญาณว่าไม่ใช่: เส้นทางที่ไม่ผ่านเกตเวย์ (ต่อตรง, เกตเวย์อื่น) ก็ช้าเท่ากัน: น่าจะเป็นที่เครือข่ายหรือเซิร์ฟเวอร์เกม - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ถ้าใช้ service mesh (เช่น Istio) sidecar proxy (Envoy) ที่ติดอยู่ข้างเซิร์ฟเวอร์ทุกตัวจะเพิ่มเข้ามาอีกหนึ่งขั้น คำขอระหว่างเซอร์วิสต้องผ่าน sidecar ฝั่งผู้ส่งแล้วจึงผ่าน sidecar ฝั่งผู้รับ และยิ่งเพิ่มฟีเจอร์ให้พร็อกซี เช่น การเก็บ log และเมตริก เวลาประมวลผลและเวลารอก็ยิ่งเพิ่มขึ้น - กรณีจริง: riot-edge-2020 - แหล่งอ้างอิง: - [The Unique Architecture behind Amazon Games’ Seamless MMO New World](https://aws.amazon.com/blogs/gametech/the-unique-architecture-behind-amazon-games-seamless-mmo-new-world/) · AWS · New World ให้ไคลเอนต์เชื่อมต่อเข้าเซิร์ฟเวอร์ทางเข้า (REP) ที่มี public IP ตัวใดตัวหนึ่งจาก 4 ตัว แล้วจึงสื่อสารกับเซิร์ฟเวอร์ simulation (hub) ที่อยู่ด้านหลัง - [Designs, Lessons and Advice from Building Large Distributed Systems](https://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf) · Google · keynote ในงาน LADIS 2009 (Jeff Dean) เวลาไปกลับภายในดาต้าเซ็นเตอร์เดียวกันประมาณ 0.5 ms - [Site Reliability Engineering, Chapter 22: Addressing Cascading Failures](https://sre.google/sre-book/addressing-cascading-failures/) · Google · ถ้าคิวยาวขึ้นเพราะโหลดเกิน เวลารอจะเพิ่มเป็นหลายเท่าของเวลาประมวลผล (ประมวลผล 100 ms และคิวยาว 10 เท่าของจำนวนเธรด จะเป็น 1.1 วินาที) - [Performance and Scalability](https://istio.io/latest/docs/ops/deployment/performance-and-scalability/) · Istio · ในโหมด sidecar คำขอผ่าน sidecar proxy ฝั่งผู้ส่งแล้วจึงผ่านฝั่งผู้รับ, ยิ่งเพิ่มฟีเจอร์ เส้นทางประมวลผลในพร็อกซียิ่งยาว และการเก็บ telemetry ทำให้คำขอถัดไปต้องรอนานขึ้น - [What is Envoy](https://www.envoyproxy.io/docs/envoy/latest/intro/what_is_envoy) · Envoy · Envoy เป็นโปรเซสแยกที่รันข้างแอปพลิเคชันเซิร์ฟเวอร์ทุกตัว และแอปรับส่งข้อมูลผ่าน Envoy บน localhost - [Istio Standard Metrics](https://istio.io/latest/docs/reference/config/metrics/) · Istio · istio_request_duration_milliseconds (การกระจายของเวลาประมวลผลคำขอ HTTP/gRPC), ใช้ label reporter แยกพร็อกซีฝั่งผู้ส่ง (source) กับฝั่งผู้รับ (destination) - [netstat(8) — Linux manual page](https://man7.org/linux/man-pages/man8/netstat.8.html) · net-tools · Recv-Q: จำนวนไบต์ในซ็อกเก็ตที่เชื่อมต่อแล้วที่โปรแกรมของผู้ใช้ยังไม่ได้ดึงไป #### in-zone-transfer · ย้ายโซน (ส่งต่อตัวละครข้ามเซิร์ฟเวอร์) · Zone / server handoff เมื่อเข้าพื้นที่หรือดันเจี้ยนอื่น ขั้นตอนส่งข้อมูลตัวละครไปยังเซิร์ฟเวอร์อื่นทำให้เกิดดีเลย์และความล้มเหลว - ทำไม → ผลคือ → บนหน้าจอ: เข้าดันเจี้ยนหรือข้ามทวีป ทำให้เซิร์ฟเวอร์ที่ดูแลตัวละครเปลี่ยนไป → บันทึก → ส่งต่อ → โหลด ถ้าเซิร์ฟเวอร์ปลายทางแน่นหรือไม่มีอินสแตนซ์ดันเจี้ยนว่าง ต้องรอ → โหลดนาน, เข้าไม่สำเร็จ, หลุดระหว่างย้าย - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, ค้าง, หลุด, ดีดกลับ / ปัจจัย: ความหน่วง, การหยุดชะงัก - ใครเจอ: เราคนเดียว, บางจุด/บางแชนแนล / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ลดข้อมูลที่ต้องส่งต่อ, จองเซิร์ฟเวอร์ปลายทางไว้ล่วงหน้า, ถ้าล้มเหลวให้กลับไปที่เดิม - งานฝั่งทีมอินฟรา: มอนิเตอร์จำนวนอินสแตนซ์ว่างที่เหลือของเซิร์ฟเวอร์ดันเจี้ยนและโซน, เตรียมจำนวนเครื่องให้พอก่อนช่วงพีค - บนกราฟ: สูงตามจำนวนคนและโหลด (เวลาที่ใช้ย้ายโซน, จำนวนครั้งที่ล้มเหลว) - จุดที่ต้องดู: เวลาที่ใช้ในแต่ละขั้นของการส่งต่อ (บันทึก, ส่งต่อ, โหลด) และสาเหตุที่ล้มเหลวที่เซิร์ฟเวอร์บันทึกไว้, จำนวนผู้เล่นและจำนวนอินสแตนซ์ว่างของเซิร์ฟเวอร์ปลายทาง - สัญญาณว่าใช่: ช่วงที่มีการแจ้งว่าโหลดนานหรือเข้าไม่สำเร็จ เวลาส่งต่อเพิ่มขึ้นหรือความล้มเหลวกระจุกตัว และเซิร์ฟเวอร์ปลายทางแน่นหรืออินสแตนซ์ว่างหมด - สัญญาณว่าไม่ใช่: ส่งต่อเสร็จเร็วแต่เกมค้างหลังไปถึง: น่าจะเป็น spawn ทะลักเมื่อเข้าพื้นที่คนหนาแน่น หรือการโหลดฝั่งไคลเอนต์ - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: แม้เป็นโลกแบบ seamless ที่เดินต่อกันโดยไม่มีหน้าโหลด เมื่อข้ามขอบเขตของเซิร์ฟเวอร์ เซิร์ฟเวอร์ที่ดูแลก็เปลี่ยนเช่นกัน ใกล้ขอบเขตจึงอาจหยุดแวบหรือโดนดีดกลับไปด้านหลังได้ - แหล่งอ้างอิง: - [The Unique Architecture behind Amazon Games’ Seamless MMO New World](https://aws.amazon.com/blogs/gametech/the-unique-architecture-behind-amazon-games-seamless-mmo-new-world/) · AWS · แบ่งโลกที่ไม่มีหน้าโหลดเป็นตาราง (grid) ให้เซิร์ฟเวอร์ (hub) หลายตัวดูแล เมื่อผู้เล่นเคลื่อนที่จะส่งสถานะจาก hub หนึ่งไปอีก hub หนึ่ง, โหมดแบบเซสชันรับเซิร์ฟเวอร์ที่ว่างจาก pool ที่ใช้ร่วมกันมาใช้ #### in-cascade · ความล้มเหลวแบบลูกโซ่ · Cascading failure เมื่อเซอร์วิสหนึ่งช้าลง เซิร์ฟเวอร์ที่เรียกใช้เซอร์วิสนั้นจะติดอยู่กับการรอคำตอบ จนฟีเจอร์ที่ไม่เกี่ยวข้องก็หยุดทำงานไปด้วย - ทำไม → ผลคือ → บนหน้าจอ: เซอร์วิสหนึ่ง เช่น DB หรือระบบยืนยันตัวตน ช้าลง → เธรดและการเชื่อมต่อของเซิร์ฟเวอร์ที่เรียกใช้ติดอยู่กับการรอคำตอบ และการลองส่งคำขอที่ล้มเหลวใหม่ยิ่งเพิ่มโหลด → ฟีเจอร์ที่ดูไม่เกี่ยวข้องก็ช้าลงหรือค้างไปหมด - อาการ: ค้าง, อินพุตดีเลย์, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเครือข่าย (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ตั้งไทม์เอาต์ให้ทุกการเรียก, ใช้ circuit breaker, แยกส่วนตามฟีเจอร์ (bulkhead), ลองใหม่โดยเว้นช่วงห่างขึ้นเรื่อย ๆ และจำกัดจำนวนครั้ง, แยกการตอบ health check ออกจากงานที่หนัก - งานฝั่งทีมอินฟรา: ตั้งจำนวนครั้งที่ล้มเหลวและช่วงห่างของ health check บนโหลดบาลานเซอร์ให้มีระยะเผื่อ เพื่อไม่ให้ถอดเซิร์ฟเวอร์ที่ช้าแค่ชั่วครู่ออกทันที, จำกัดจำนวนเซิร์ฟเวอร์ที่ถูกถอดออกพร้อมกัน - บนกราฟ: ชนเพดานแล้วแบนราบ (เวลาตอบสนองและอัตราข้อผิดพลาดแยกตามเซอร์วิส, จำนวนเธรด/การเชื่อมต่อที่ใช้อยู่) - จุดที่ต้องดู: วางเวลาตอบสนอง, อัตราข้อผิดพลาด และจำนวนการลองใหม่ของแต่ละเซอร์วิสบนหน้าจอเดียวโดยให้แกนเวลาตรงกัน แล้วหาจุดที่ช้าลงก่อน ถ้าอยู่หลังโหลดบาลานเซอร์ ให้ดูเวลาตอบสนองของ target (AWS ALB คือ TargetResponseTime), จำนวน 5xx ของ target (HTTPCode_Target_5XX_Count) และจำนวน target ที่ถูกถอดเพราะผิดปกติ (UnHealthyHostCount) - สัญญาณว่าใช่: ดีเลย์ของเซอร์วิสหนึ่งขึ้นก่อน ตามด้วยจำนวนเธรด/การเชื่อมต่อของฝั่งที่เรียกใช้ชนเพดาน error ลามไปเซอร์วิสอื่น และจำนวนการลองใหม่กับจำนวน target ที่ถูกถอดเพิ่มขึ้นพร้อมกัน - สัญญาณว่าไม่ใช่: หลายเซอร์วิสช้าลงพร้อมกันในจังหวะเดียวกัน: ให้ดูเหตุขัดข้องของทรัพยากรที่ใช้ร่วมกัน (DB, เครือข่าย, โฮสต์) ก่อน - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: health check (การตรวจว่าเซิร์ฟเวอร์ยังทำงานอยู่) ก็ทำให้ลูกโซ่ลามหนักขึ้นได้ ถ้าเซิร์ฟเวอร์ที่งานล้นมือตอบการตรวจช้า โหลดบาลานเซอร์จะถอดเซิร์ฟเวอร์ที่ยังใช้งานได้ออก ทราฟฟิกนั้นจึงไหลไปรวมที่เซิร์ฟเวอร์ที่เหลือ และเซิร์ฟเวอร์ถัดไปก็ช้าตาม - กรณีจริง: riot-edge-2020, riot-euw-2021, roblox-2021, aws-2021, aws-2025 - แหล่งอ้างอิง: - [Site Reliability Engineering, Chapter 22: Addressing Cascading Failures](https://sre.google/sre-book/addressing-cascading-failures/) · Google · เซิร์ฟเวอร์ที่โหลดเกินไม่ผ่าน health check แล้วถูกถอดออก โหลดจึงไปรวมที่เซิร์ฟเวอร์ที่เหลือ และการลองใหม่ยิ่งเพิ่มโหลด, แนะนำให้จำกัดจำนวนครั้งที่ลองใหม่, ใช้ exponential backoff แบบสุ่ม และกำหนด deadline - [Circuit Breaker Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker) · Microsoft Azure · คำขอที่ถูกบล็อกจนถึงไทม์เอาต์ถือครองเธรดและการเชื่อมต่อ DB ทำให้ฟีเจอร์ที่ไม่เกี่ยวข้องล้มเหลวไปด้วย, ถ้าความล้มเหลวสะสมถึงเกณฑ์ภายในเวลาที่กำหนด ให้ปฏิเสธการเรียกทันที - [Timeouts, retries, and backoff with jitter](https://d1.awsstatic.com/builderslibrary/pdfs/timeouts-retries-and-backoff-with-jitter.pdf) · AWS · Amazon Builders' Library การเรียกต่อกัน 5 ชั้นที่แต่ละชั้นลองใหม่ 3 ครั้ง ทำให้โหลดของ DB เพิ่มเป็น 243 เท่า, ให้ลองใหม่ที่จุดเดียวและจำกัดด้วย token bucket - [CloudWatch metrics for your Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-cloudwatch-metrics.html) · AWS · TargetResponseTime (เวลาตั้งแต่คำขอออกจากโหลดบาลานเซอร์จนถึง target เริ่มตอบ), HTTPCode_Target_5XX_Count (จำนวน 5xx ที่ target ส่งกลับ), UnHealthyHostCount (จำนวน target ที่ผิดปกติ) #### in-subservice · เซิร์ฟเวอร์เสริมขัดข้อง · Auxiliary service outage ถ้าเซิร์ฟเวอร์ที่รันแยกจากเซิร์ฟเวอร์เกม เช่น แชต, ปาร์ตี้ หรือตลาดประมูล ขัดข้อง จะใช้งานไม่ได้เฉพาะฟีเจอร์นั้น - ทำไม → ผลคือ → บนหน้าจอ: เซิร์ฟเวอร์เฉพาะฟีเจอร์ช้าลงหรือล่ม → เฉพาะคำขอของฟีเจอร์นั้นที่ไม่ได้รับคำตอบ → แชตไม่ได้, เชิญปาร์ตี้แล้วไม่มีอะไรเกิดขึ้น, ตลาดซื้อขายโหลดไม่จบ (การต่อสู้ปกติ) - อาการ: กดไม่ติด/โรลแบ็ค, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: การหยุดชะงัก, แพ็กเก็ตหาย - ใครเจอ: เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ออกแบบให้เกมเล่นต่อได้แม้ฟีเจอร์นั้นล้มเหลว, แสดงสถานะแยกตามฟีเจอร์, ไม่รวมหลายฟีเจอร์ไว้ที่เซิร์ฟเวอร์กลางเครื่องเดียว - งานฝั่งทีมอินฟรา: health check และ alert แยกตามเซิร์ฟเวอร์เสริม, ทำระบบสำรอง (redundancy) และรีสตาร์ตอัตโนมัติ - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (อัตราความสำเร็จของคำขอแยกตามฟีเจอร์, จำนวนการเชื่อมต่อ/health check ของเซิร์ฟเวอร์เสริม) - จุดที่ต้องดู: health check, สถานะโปรเซส และจำนวนการเชื่อมต่อของเซิร์ฟเวอร์เสริมแต่ละตัว เช่น แชต, ปาร์ตี้ และตลาดประมูล, อัตราความสำเร็จและเวลาตอบสนองของคำขอแยกตามฟีเจอร์ ถ้าอยู่หลังโหลดบาลานเซอร์ ให้ดู UnHealthyHostCount ของ target group - สัญญาณว่าใช่: เฉพาะเซิร์ฟเวอร์ที่ดูแลฟีเจอร์ที่ถูกแจ้งมาไม่ผ่าน health check หรือจำนวนการเชื่อมต่อดิ่งลง ขณะที่ทิกของเซิร์ฟเวอร์เกมและการต่อสู้ปกติ - สัญญาณว่าไม่ใช่: หลายฟีเจอร์หยุดพร้อมกัน: น่าจะเป็นเซิร์ฟเวอร์กลางที่ส่งต่อฟีเจอร์เหล่านั้นร่วมกัน หรือความล้มเหลวแบบลูกโซ่ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ถ้าใช้โครงสร้างที่เซิร์ฟเวอร์กลางเครื่องเดียว (เซิร์ฟเวอร์ world/manager) เป็นตัวกลางส่งต่อทั้งปาร์ตี้, กิลด์, กระซิบ และการย้ายข้ามเซิร์ฟเวอร์ เพียงเซิร์ฟเวอร์นั้นช้าลงเครื่องเดียว หลายฟีเจอร์จะหยุดทำงานพร้อมกัน - แหล่งอ้างอิง: - [Bulkhead Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/bulkhead) · Microsoft Azure · ถ้าแยกส่วนประกอบไว้คนละ pool เมื่อส่วนหนึ่งล้มเหลว ส่วนที่เหลือยังทำงานต่อได้และเหตุขัดข้องไม่ลามออกไป - [REL05-BP01 Implement graceful degradation to transform applicable hard dependencies into soft dependencies](https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/rel_mitigate_interaction_failure_graceful_degradation.html) · AWS · AWS Well-Architected ออกแบบให้ฟีเจอร์หลักยังทำงานต่อได้ด้วยข้อมูลที่เก่าไปเล็กน้อยหรือข้อมูลทดแทน แม้ระบบที่พึ่งพาอยู่จะขัดข้อง - [CloudWatch metrics for your Network Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-cloudwatch-metrics.html) · AWS · UnHealthyHostCount: จำนวน target ที่ health check ตัดสินว่าผิดปกติ #### in-deploy · การ deploy และรีสตาร์ต · Deploy / rolling restart ถ้ารีสตาร์ตเซิร์ฟเวอร์เพื่ออัปเดตโดยไม่ย้ายการเชื่อมต่อ ผู้เล่นที่อยู่บนเซิร์ฟเวอร์นั้นจะหลุด และการบันทึกข้อมูลก่อนปิดกับการเชื่อมต่อใหม่จะทะลักเข้ามาพร้อมกัน - ทำไม → ผลคือ → บนหน้าจอ: deploy hotfix โดยรีสตาร์ตเซิร์ฟเวอร์ทีละเครื่องตามลำดับ → ปิดเซิร์ฟเวอร์โดยไม่ย้ายการเชื่อมต่อไปเซิร์ฟเวอร์อื่น การบันทึกของผู้เล่นทุกคนบนเซิร์ฟเวอร์นั้นจึงไปกองที่ DB → หลุดโดยไม่มีประกาศ, คนแห่เชื่อมต่อใหม่ - อาการ: หลุด, เข้าเกมไม่ได้/โหลดไม่จบ, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางจุด/บางแชนแนล / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ทำฟีเจอร์ drain (กันเฉพาะการเชื่อมต่อใหม่ แล้วรอจนคนเดิมออกไป), ย้ายตัวละครไปเซิร์ฟเวอร์อื่น, ทยอยบันทึกก่อนปิด, หลังรีสตาร์ตให้โหลดแคชและ JIT warm-up ให้เสร็จก่อนแจ้งว่าพร้อม, hot reload ให้อ่านไว้ล่วงหน้าในเธรดแยกแล้วสลับทีเดียวระหว่างทิก - งานฝั่งทีมอินฟรา: ให้เครื่องมือ deploy รอ drain ทีละเครื่องก่อนรีสตาร์ต, ให้เซิร์ฟเวอร์ที่รีสตาร์ตแล้วรับทราฟฟิกหลังยืนยันว่าพร้อม (warm-up เสร็จ), ประกาศเวลา deploy - ตัวเลขที่ควรรู้: ถ้าเซิร์ฟเวอร์หนึ่งเครื่องมี 5,000 คน ในไม่กี่วินาทีก่อนปิดจะมีการบันทึก 5,000 รายการพุ่งเข้า DB พร้อมกัน - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนการเชื่อมต่อแยกตามเซิร์ฟเวอร์, จำนวนการเขียน DB) - จุดที่ต้องดู: วางประวัติงานของเครื่องมือ deploy (เวลารีสตาร์ตของแต่ละเซิร์ฟเวอร์) เป็นเส้นแนวตั้ง (annotation) บนกราฟจำนวนการเชื่อมต่อ, จำนวนการหลุด, การเขียน DB และคำขอล็อกอิน - สัญญาณว่าใช่: จำนวนการเชื่อมต่อของแต่ละเซิร์ฟเวอร์ดิ่งลงทีละเครื่องตามเวลารีสตาร์ต การเขียน DB พุ่งก่อนหน้านั้น และคำขอล็อกอินพุ่งหลังจากนั้น - สัญญาณว่าไม่ใช่: เวลาที่หลุดไม่ตรงกับประวัติ deploy หรือรีสตาร์ต: น่าจะเป็นเซิร์ฟเวอร์แครชหรืออุปกรณ์เครือข่าย - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ช่วงไม่กี่นาทีหลังเปิดใหม่ก็ยังช้า เพราะแคชว่างจึงมีการอ่าน DB ทะลักเข้ามา และเซิร์ฟเวอร์ Java หรือ C# ยังไม่เสร็จขั้นตอน optimize โค้ดระหว่างรัน (JIT warm-up) งานเดิมจึงใช้เวลามากขึ้น การโหลดสคริปต์หรือตารางข้อมูลใหม่โดยไม่ปิดเซิร์ฟเวอร์ (hot reload) ก็ทำให้ทิกหยุดระหว่างอ่าน จึงเกิดการหยุดสั้น ๆ - แหล่งอ้างอิง: - [Site Reliability Engineering, Chapter 20: Load Balancing in the Datacenter](https://sre.google/sre-book/load-balancing-datacenter/) · Google · เซิร์ฟเวอร์ที่ได้รับ SIGTERM เข้าสถานะ lame duck ส่งคำขอใหม่ไปเซิร์ฟเวอร์อื่นและทำเฉพาะคำขอที่กำลังทำอยู่ให้เสร็จ, ช่วงไม่กี่นาทีหลังรีสตาร์ตยังไม่ผ่าน JIT optimization จึงใช้ทรัพยากรมากกว่า ให้ warm-up ก่อนแล้วค่อยรับทราฟฟิก - [Liveness, Readiness, and Startup Probes](https://kubernetes.io/docs/concepts/workloads/pods/probes/) · Kubernetes · ใช้ readiness check ไม่ส่งทราฟฟิกมาจนกว่าการสร้างการเชื่อมต่อ, การโหลดไฟล์ และการ warm-up แคชจะเสร็จ - [Edit target group attributes for your Network Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/edit-target-group-attributes.html) · AWS · เมื่อ deregister target จะไม่ส่งการเชื่อมต่อใหม่ไปให้ และ drain การเชื่อมต่อเดิม (ค่าเริ่มต้น 300 วินาที) #### in-autoscale · autoscaling เพิ่มเครื่องไม่ทัน · Autoscaling lag เมื่อคนแห่เข้ามา ระบบจะเพิ่มเซิร์ฟเวอร์อัตโนมัติ แต่ต้องใช้เวลาเตรียมหลายนาที และระหว่างนั้นเซิร์ฟเวอร์เดิมรับโหลดเกิน - ทำไม → ผลคือ → บนหน้าจอ: อีเวนต์เริ่มแล้วผู้เล่นแห่เชื่อมต่อเข้ามา → กว่าเซิร์ฟเวอร์ใหม่จะเปิดและพร้อมใช้ต้องใช้เวลาหลายนาที → ไม่กี่นาทีแรกหลังอีเวนต์เริ่ม เกิดสโลว์โมชั่นและเข้าเกมไม่ได้ - อาการ: สโลว์โมชั่น, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: กระจายผู้เล่นตามแชนแนล (คนที่อยู่ในแชนแนลที่แน่นอยู่แล้วย้ายไปเซิร์ฟเวอร์ใหม่ไม่ได้), ลดเวลาเริ่มทำงานและเวลาโหลดข้อมูลของเซิร์ฟเวอร์ใหม่ - งานฝั่งทีมอินฟรา: ขยายไว้ก่อนอีเวนต์, เตรียมเซิร์ฟเวอร์สำรองที่ warm-up แล้ว, เวลาลดเครื่องให้รอคนที่เหลือออกไปก่อนแล้วค่อยปิด - ตัวเลขที่ควรรู้: ใช้เวลา 1 นาทีถึงไม่กี่นาทีกว่าจะตรวจพบโหลด (เพราะดูค่าเฉลี่ยของเมตริกหลายนาที) และอีกหลายนาทีเพื่อเปิดเซิร์ฟเวอร์ใหม่ อ่านข้อมูลเกม และเติมแคช - บนกราฟ: พุ่งทันทีหลังเปิดเซิร์ฟ/ปิดปรับปรุง (จำนวนอินสแตนซ์, อัตราการใช้ CPU, จำนวนผู้เล่นที่รอเข้าเกม) - จุดที่ต้องดู: วางประวัติกิจกรรมของ autoscaling (เวลาที่ตัดสินใจขยาย, เวลาที่อินสแตนซ์ใหม่เริ่มให้บริการ) บนกราฟอัตราการใช้ CPU และจำนวนการเชื่อมต่อ บน AWS ใช้เมตริกของ Auto Scaling group (ต้องเปิดก่อนจึงจะเห็น) GroupDesiredCapacity (จำนวนเป้าหมาย), GroupPendingInstances (กำลังเตรียม), GroupInServiceInstances (กำลังให้บริการ) - สัญญาณว่าใช่: หลังการเชื่อมต่อพุ่ง มีหลายนาทีที่เพิ่มขึ้นเฉพาะจำนวนเป้าหมายและอินสแตนซ์ที่กำลังเตรียม ขณะที่ CPU ของเซิร์ฟเวอร์เดิมชนเพดาน แล้วอาการคลายลงตั้งแต่ตอนที่จำนวนอินสแตนซ์ที่ให้บริการเพิ่มขึ้น - สัญญาณว่าไม่ใช่: อินสแตนซ์ใหม่เข้ามาแล้วยังช้า: น่าจะเป็นสาเหตุที่ไม่เกี่ยวกับจำนวนเซิร์ฟเวอร์ (ทรัพยากรที่ใช้ร่วมกันอย่าง DB, ความล้มเหลวแบบลูกโซ่) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: autoscaling มักใช้กับส่วนที่แค่ให้เซิร์ฟเวอร์ใหม่รับคนใหม่ก็พอ เช่น ล็อกอิน, เกตเวย์ และดันเจี้ยน ตอนลดเครื่องก็เกิดปัญหาได้ ถ้าลดเซิร์ฟเวอร์ตอนเช้ามืดที่คนบางตาแล้วปิดเลยโดยไม่รอคนที่เหลือออก คนเหล่านั้นจะหลุด - กรณีจริง: aws-2021, aws-2025 - แหล่งอ้างอิง: - [Target tracking scaling policies for Amazon EC2 Auto Scaling](https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-target-tracking.html) · AWS · เมตริกพื้นฐานของ EC2 มีช่วงห่าง 5 นาที (เปิด detailed monitoring จะเป็น 1 นาที) ถ้าต้องการตอบสนองเร็ว แนะนำเมตริกที่มีช่วงห่างไม่เกิน 1 นาที - [Amazon CloudWatch metrics for Amazon EC2 Auto Scaling](https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-metrics.html) · AWS · group metrics ต้องเปิดก่อนจึงเผยแพร่ทุก 1 นาที, GroupDesiredCapacity (จำนวนที่ต้องการรักษาไว้), GroupPendingInstances (จำนวนอินสแตนซ์ที่ยังไม่เริ่มให้บริการ), GroupInServiceInstances (จำนวนอินสแตนซ์ที่กำลังให้บริการ) - [Scheduled scaling for Amazon EC2 Auto Scaling](https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scheduled-scaling.html) · AWS · เพิ่มและลด capacity ล่วงหน้าตามเวลาที่กำหนด ให้ตรงกับการเปลี่ยนแปลงของโหลดที่คาดการณ์ได้ - [Decrease latency for applications with long boot times using warm pools](https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-warm-pools.html) · AWS · แอปที่บูตนานใช้ pool ของอินสแตนซ์ที่ initialize ไว้ล่วงหน้า (warm pool) เพื่อลดเวลาที่ใช้ขยาย #### in-monitoring · log และระบบมอนิเตอร์โหลดเกิน · Logging / monitoring overhead เมื่อเกิดเหตุขัดข้อง log จะพุ่งขึ้นมาก และเซิร์ฟเวอร์ที่ส่ง log แบบ synchronous จะยิ่งช้าลงเพราะ log - ทำไม → ผลคือ → บนหน้าจอ: เกิด error แล้วปริมาณ log และเมตริกที่ส่งออกพุ่ง → log collector รับไม่ทัน เซิร์ฟเวอร์ที่ส่งแบบ synchronous ต้องรอ → ตอนเกิดเหตุขัดข้อง อาการกระตุกและค้างยิ่งหนักขึ้นเพราะ log - อาการ: กระตุก, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ส่งแบบ async, ทำ sampling, ทิ้งเมื่อบัฟเฟอร์ล้น, รวม log ของ error เดียวกันแล้วส่งครั้งเดียว - งานฝั่งทีมอินฟรา: เตรียมความจุของ log collector โดยคิดจากปริมาณที่พุ่งตอนเกิดเหตุขัดข้อง, ตั้ง alert เมื่อ log collector มีงานสะสม - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (ปริมาณ log ที่ส่ง, คิวของ log collector) - จุดที่ต้องดู: จำนวนบรรทัดและไบต์ของ log ต่อวินาทีบนเซิร์ฟเวอร์ กับคิวและจำนวนที่ถูกทิ้งของ agent เก็บ log ดูคู่กับเวลาต่อทิก ถ้ามีเธรดที่หยุดอยู่ ใช้ bcc offcputime -p ดูว่ารอที่การเขียนหรือส่ง log หรือไม่ - สัญญาณว่าใช่: ช่วงที่ทิกพุ่ง ปริมาณ log พุ่งเป็นหลายสิบเท่าของปกติ และเวลารอของเธรดเกมกระจุกอยู่ที่ call stack ของการเขียนหรือส่ง log - สัญญาณว่าไม่ใช่: ปริมาณ log เท่าปกติ หรือเธรดเกมไม่ได้รอที่ฝั่ง log: log ที่พุ่งเป็นแค่ผลของเหตุขัดข้อง ให้หาสาเหตุที่ทำให้เกิด error แรกแยกต่างหาก - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Logging in C#](https://learn.microsoft.com/en-us/dotnet/core/extensions/logging) · Microsoft · เมธอด log ของ .NET เป็นแบบ synchronous ถ้าที่เก็บช้า แนะนำให้เขียนลงที่เก็บที่เร็วก่อนแล้วค่อยย้ายทีหลัง - [Asynchronous loggers](https://logging.apache.org/log4j/2.x/manual/async.html) · Apache Software Foundation · async logging ดูดซับ log ที่พุ่งช่วงสั้น ๆ ด้วยคิว แต่ถ้าปลายทาง (output) ช้าต่อเนื่อง คิวจะเต็มแล้วความเร็วลดลงเหลือเท่าปลายทางที่ช้าที่สุด หรือทิ้ง log ตามนโยบาย (Discard) - [Demonstrations of offcputime, the Linux eBPF/bcc version](https://raw.githubusercontent.com/iovisor/bcc/master/tools/offcputime_example.txt) · IO Visor · รวมเวลาที่เธรดหยุดอยู่นอก CPU (off-CPU) แยกตาม call stack, -p ระบุโปรเซส #### in-clock-skew · นาฬิการะหว่างเซิร์ฟเวอร์ไม่ตรงกัน · Clock skew between servers ถ้านาฬิกาของแต่ละเซิร์ฟเวอร์ต่างกันเล็กน้อย การตัดสินคูลดาวน์, บัฟ และเวลาเริ่มอีเวนต์จะไม่ตรงกันระหว่างเซิร์ฟเวอร์ - ทำไม → ผลคือ → บนหน้าจอ: นาฬิกาของเซิร์ฟเวอร์ที่การซิงก์เวลาหยุดทำงาน ห่างจากเซิร์ฟเวอร์อื่นหลายร้อย ms ถึงหลายวินาที → ถ้าส่งเวลาสัมบูรณ์ เช่น เวลาที่บัฟหมด ข้ามเซิร์ฟเวอร์ การตัดสินจะคลาดเคลื่อน → ย้ายเซิร์ฟเวอร์แล้วบัฟหายไป หรือคูลดาวน์กลับมานับใหม่ - อาการ: กดไม่ติด/โรลแบ็ค / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ส่งเวลาที่เหลือระหว่างเซิร์ฟเวอร์ แทนเวลาสัมบูรณ์ - งานฝั่งทีมอินฟรา: มอนิเตอร์การซิงก์เวลา (NTP, chrony), ตั้ง alert เมื่อนาฬิการะหว่างเซิร์ฟเวอร์ต่างกัน - ตัวเลขที่ควรรู้: ถ้าการซิงก์เวลา (NTP, chrony) ปกติ เซิร์ฟเวอร์ในดาต้าเซ็นเตอร์เดียวกันมักต่างกันไม่เกินไม่กี่ ms แต่ถ้าการซิงก์หยุด หรือ VM หยุดไปนานแล้วกลับมาทำงาน จะห่างกันหลายร้อย ms ถึงหลายวินาที - บนกราฟ: ค่อย ๆ สูงขึ้น (offset ของนาฬิกาแยกตามเซิร์ฟเวอร์) - จุดที่ต้องดู: รวบรวม System time (ส่วนต่างระหว่างนาฬิการะบบกับนาฬิกา NTP), Last offset และ Ref time (เวลาที่นำค่าวัดล่าสุดจากแหล่งเวลามาใช้) จาก chronyc tracking ของแต่ละเซิร์ฟเวอร์มาเทียบกัน - สัญญาณว่าใช่: offset ของเซิร์ฟเวอร์ที่มีปัญหาห่างจากเซิร์ฟเวอร์อื่นตั้งแต่หลายร้อย ms ขึ้นไป หรือ Ref time หยุดอยู่ที่เวลานานมาแล้ว และการตัดสินคลาดเคลื่อนเกิดเฉพาะเมื่อย้ายเข้าออกเซิร์ฟเวอร์นั้น - สัญญาณว่าไม่ใช่: offset ของทุกเซิร์ฟเวอร์อยู่ในหลักหน่วย ms: น่าจะเป็นการคำนวณเวลาฝั่งเกม หรือซิงก์นาฬิกาคลาดเคลื่อนฝั่งไคลเอนต์ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: กรณีที่นาฬิกาของเซิร์ฟเวอร์เครื่องหนึ่งกระโดดไปข้างหน้าหรือถอยหลังทีเดียว อธิบายไว้ในการ์ด “นาฬิการะบบกระโดด (NTP step)” ของชั้น OS ของเซิร์ฟเวอร์ - แหล่งอ้างอิง: - [RFC 5905: Network Time Protocol Version 4: Protocol and Algorithms Specification](https://www.rfc-editor.org/rfc/rfc5905) · IETF · NTP client บน LAN ความเร็วสูงมักตรงกันภายในหลายร้อย µs - [chrony – Frequently Asked Questions](https://chrony-project.org/faq.html) · chrony · drift ของนาฬิกาคอมพิวเตอร์ทั่วไปต่ำกว่า 100 ppm แต่ VM อาจมากกว่านั้น, VM ที่ถูก pause แล้ว resume อาจเวลาคลาดจนต้องแก้แบบ step - [clock_gettime(2) — Linux manual page](https://man7.org/linux/man-pages/man2/clock_gettime.2.html) · Linux man-pages · CLOCK_REALTIME อาจกระโดดไม่ต่อเนื่องจากการแก้เวลาด้วยมือหรือการปรับของ NTP ส่วน CLOCK_MONOTONIC ไม่ได้รับผลจากการกระโดดแบบนั้น - [chronyc(1)](https://chrony-project.org/doc/4.6/chronyc.html) · chrony · System time (ส่วนต่างระหว่างนาฬิกา NTP กับนาฬิการะบบ), Last offset (offset ที่ประมาณไว้ตอนปรับครั้งล่าสุด), Ref time (เวลาที่นำค่าวัดล่าสุดจากแหล่งเวลามาใช้) ของ chronyc tracking #### in-bots · มาโครและบอทมากเกินไป · Bots and macros บอทส่งคำขอถี่กว่าคนมาก จึงกินกำลังประมวลผลของเซิร์ฟเวอร์ - ทำไม → ผลคือ → บนหน้าจอ: บอทจำนวนมากเชื่อมต่อเข้ามา ฟาร์มมอน, เดิน และเทรดซ้ำไม่หยุด → ปริมาณงานที่เซิร์ฟเวอร์ต้องประมวลผลและโหลดของ DB เพิ่มขึ้น → บางจุดฟาร์มหรือทั้งเซิร์ฟเวอร์ช้าลง (สโลว์โมชั่น, อินพุตดีเลย์) - อาการ: สโลว์โมชั่น, อินพุตดีเลย์ / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางจุด/บางแชนแนล / เกิดเมื่อไร: ตลอดเวลา, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเครือข่าย (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ตรวจจับบอท, จำกัดความถี่คำขอต่อบัญชีและตัวละคร - งานฝั่งทีมอินฟรา: จำกัดความถี่การเชื่อมต่อและคำขอต่อ IP (ร้านเกมและเครือข่ายมือถือมีหลายคนใช้ IP เดียวกัน จึงต้องเผื่อไว้), บล็อกช่วง IP ของบอทด้วยไฟร์วอลล์หรือ WAF - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนคำขอต่อวินาทีแยกตามบัญชี/IP) - จุดที่ต้องดู: ใช้ log ของเซิร์ฟเวอร์เกมดูการกระจายและรายชื่ออันดับต้นของจำนวนคำขอต่อวินาทีแยกตามบัญชีและตัวละคร ถ้าไม่มีเมตริกจากโค้ด ให้ดูจำนวนคำขอแยกตาม IP จากไฟร์วอลล์หรือ WAF - สัญญาณว่าใช่: บัญชีหรือ IP ส่วนน้อยส่งคำขอไม่หยุดด้วยความถี่ที่คนทำไม่ได้ และเมื่อจำกัดกลุ่มนี้ โหลดของเซิร์ฟเวอร์ลดลงชัดเจน - สัญญาณว่าไม่ใช่: คำขอกระจายเท่า ๆ กันทุกบัญชี: น่าจะเป็นจำนวนผู้เล่นปกติที่เพิ่มขึ้น (ทิกเกินงบเวลา, autoscaling เพิ่มเครื่องไม่ทัน) - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Using rate-based rule statements in AWS WAF](https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-rate-based.html) · AWS · นับจำนวนคำขอตามเกณฑ์ เช่น IP และจำกัดอัตรา (rate limit) ถ้าเกินในช่วงเวลา (time window) ที่กำหนด - [RFC 6269: Issues with IP Address Sharing](https://www.rfc-editor.org/rfc/rfc6269) · IETF · เมื่อผู้ใช้บริการหลายรายใช้ IP เดียวกัน การบล็อกหรือจำกัดตาม IP จะกันผู้ใช้คนอื่นไปด้วย #### in-external · การพึ่งพาเซอร์วิสภายนอก · External dependencies (auth, billing, platform) ถ้าเซอร์วิสภายนอก เช่น ล็อกอินผ่านแพลตฟอร์ม, การชำระเงิน หรือการยืนยันตัวตน ช้าหรือหยุดทำงาน ผู้เล่นจะติดอยู่ที่ขั้นตอนนั้น - ทำไม → ผลคือ → บนหน้าจอ: เซอร์วิสยืนยันตัวตนหรือชำระเงินภายนอกขัดข้องหรือช้า → รอคำตอบอยู่ที่ขั้นตอนนั้น → ล็อกอินไม่ได้, ชำระเงินไม่สำเร็จ คนที่อยู่ในเกมแล้วเล่นได้ปกติ - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, กดไม่ติด/โรลแบ็ค / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์, เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ตั้งไทม์เอาต์ให้การเรียกภายนอกพร้อมข้อความแจ้งที่เข้าใจง่าย, แคชผลการยืนยันตัวตน, ทำขั้นตอนลองชำระเงินใหม่และชดเชย - งานฝั่งภายนอก: แจ้งผู้ให้บริการยืนยันตัวตน, ชำระเงิน และแพลตฟอร์มให้ตรวจสอบเหตุขัดข้องและกู้ระบบ, แจ้งผู้เล่นว่าเป็นเหตุขัดข้องของเซอร์วิสภายนอก - บนกราฟ: ขึ้นเป็นขั้นบันไดจากจุดหนึ่ง (เวลาตอบสนอง/อัตราข้อผิดพลาดของการเรียกภายนอก, จำนวนล็อกอินที่สำเร็จ) - จุดที่ต้องดู: เวลาตอบสนอง, อัตราข้อผิดพลาด และจำนวนไทม์เอาต์ของการเรียกภายนอกแต่ละประเภท เช่น ล็อกอินผ่านแพลตฟอร์ม, การชำระเงิน และการยืนยันตัวตน กับหน้าสถานะ (status page) ของผู้ให้บริการ - สัญญาณว่าใช่: ตั้งแต่ช่วงที่ล็อกอินหรือชำระเงินล้มเหลวกระจุกตัว error และไทม์เอาต์ของการเรียกภายนอกบางตัวเท่านั้นที่ขึ้นเป็นขั้นแล้วทรงตัวอยู่ระดับนั้น และหน้าสถานะของผู้ให้บริการมีเหตุขัดข้องในเวลาเดียวกัน - สัญญาณว่าไม่ใช่: การเรียกภายนอกปกติแต่ล็อกอินไม่ได้: น่าจะเป็นที่ตัวเซิร์ฟเวอร์ล็อกอินเอง (thread pool หมด, DB) หรือคิวรอเชื่อมต่อ (backlog) ของระบบปฏิบัติการ - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - กรณีจริง: fastly-2021, aws-2021, aws-2025 - แหล่งอ้างอิง: - [Timeouts, retries, and backoff with jitter](https://d1.awsstatic.com/builderslibrary/pdfs/timeouts-retries-and-backoff-with-jitter.pdf) · AWS · Amazon Builders' Library ระหว่างรอคำตอบจะถือครองทรัพยากร เช่น เธรดและการเชื่อมต่อ จึงต้องตั้งไทม์เอาต์ และ API ที่มีผลข้างเคียงให้ลองใหม่เฉพาะเมื่อรับประกัน idempotency ได้ - [Circuit Breaker Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker) · Microsoft Azure · การเรียกที่มีแนวโน้มล้มเหลวสูงให้ปฏิเสธทันทีโดยไม่ต้องรอถึงไทม์เอาต์ เพื่อรักษาเวลาตอบสนอง - [REL05-BP01 Implement graceful degradation to transform applicable hard dependencies into soft dependencies](https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/rel_mitigate_interaction_failure_graceful_degradation.html) · AWS · AWS Well-Architected แม้เซอร์วิสที่พึ่งพาจะขัดข้อง ก็ยังรักษาฟีเจอร์หลักไว้ได้ด้วยข้อมูลที่เก่าไปเล็กน้อย (เป็นเหตุผลของการแคชผลการยืนยันตัวตน) #### in-region-match · matchmaking/การจัดรีเจียนผิดพลาด · Wrong region assignment (matchmaking / GeoDNS) ถ้าถูกจัดไปเซิร์ฟเวอร์ในรีเจียนที่ไกลแทนรีเจียนที่ใกล้ ผู้เล่นคนนั้นจะปิงสูงตลอดแม้เน็ตจะปกติ - ทำไม → ผลคือ → บนหน้าจอ: ข้อมูล GeoIP ผิด, VPN, จัดทั้งปาร์ตี้ตามปิงเฉลี่ยของสมาชิก, กฎที่ขยายไปถึงรีเจียนไกลเมื่อคนไม่พอ, การจัดตามตำแหน่งของ DNS resolver → มีรีเจียนที่ใกล้อยู่แล้ว แต่ไปเชื่อมต่อกับเซิร์ฟเวอร์ในรีเจียนข้ามทะเล → ในเกมที่มีเซิร์ฟเวอร์หลายรีเจียน เราคนเดียว (หรือแค่ปาร์ตี้เรา) ปิงสูงตลอด และมีอินพุตดีเลย์, โดนดีดกลับ, สกิลไม่ออก - อาการ: อินพุตดีเลย์, ดีดกลับ, กดไม่ติด/โรลแบ็ค / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว, บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตลอดเวลา, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม), อินฟราเครือข่าย (ทีมอินฟรา), ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: จัดรีเจียนตามปิงแต่ละรีเจียนที่ไคลเอนต์วัดได้แทน GeoIP, ใส่เพดานปิงในกฎที่ขยายไปรีเจียนไกล, ปาร์ตี้ให้ดูปิงของสมาชิกที่สูงที่สุดด้วยนอกจากค่าเฉลี่ย, เก็บ log รีเจียนที่จัดให้และปิงในตอนนั้น ไคลเอนต์: วัดปิงแต่ละรีเจียนด้วย UDP แล้วส่งไปพร้อมคำขอจับคู่, แสดงรีเจียนที่เชื่อมต่อและปิงบนหน้าจอ, มีตัวเลือกให้เลือกรีเจียนเอง - งานฝั่งทีมอินฟรา: ถ้าใช้ DNS เลือกรีเจียน ให้ตรวจว่า authoritative DNS รองรับ EDNS Client Subnet หรือไม่ (ถ้า resolver ที่ผู้เล่นใช้ไม่ส่งมา จะจัดตามตำแหน่งของ resolver), อัปเดตฐานข้อมูล GeoIP เป็นประจำ, ใส่ประเทศและ ASN จาก GeoIP ลงใน log การเชื่อมต่อของเซิร์ฟเวอร์แต่ละรีเจียน เพื่อหาประเทศและ ISP ที่ไปลงรีเจียนไกล - งานฝั่งภายนอก: แนะนำให้ผู้เล่นปิด VPN หรือโปรแกรมลดปิงแล้วลองเชื่อมต่อใหม่, แนะนำผู้เล่นที่ใช้ DNS ของบริษัทหรือของต่างประเทศให้ลองเปลี่ยนไปใช้ DNS ของ ISP, แจ้งผู้ให้บริการ GeoIP ให้แก้ตำแหน่งที่ผิด - ตัวเลขที่ควรรู้: ถ้าผู้เล่นในโซลถูกจัดไปรีเจียนสหรัฐฯ ฝั่งตะวันตกแทนโตเกียว ปิงจะเพิ่มจากประมาณ 30 ms เป็นประมาณ 130 ms GeoIP ถูกต้องระดับประเทศประมาณ 99.8% แต่ระดับเมือง แม้ในสหรัฐฯ สัดส่วนที่อยู่ในรัศมี 50 กิโลเมตรมีเพียงประมาณ 66% และถ้าใช้ VPN จะได้ตำแหน่งของเซิร์ฟเวอร์ VPN แทนตำแหน่งผู้เล่น - บนกราฟ: สูงเฉพาะบางกลุ่ม (RTT (ปิง) แยกตามผู้เล่น, การกระจายของรีเจียนที่ถูกจัดให้) - จุดที่ต้องดู: ใส่ประเทศและ ASN จาก GeoIP ให้ IP ของไคลเอนต์ใน log การเชื่อมต่อของเซิร์ฟเวอร์แต่ละรีเจียน (access log ของโหลดบาลานเซอร์, VPC flow log) แล้วนับว่าแต่ละประเทศและ ISP เชื่อมต่อไปรีเจียนไหน ถ้าเป็นผู้เล่นคนเดียว ให้เทียบรีเจียนที่ผู้เล่นคนนั้นเชื่อมต่อจริงกับปิงไปยังรีเจียนที่ใกล้ (ให้ผู้เล่นวัดเอง หรือ mtr จากเซิร์ฟเวอร์ในรีเจียนนั้นไปยัง IP ของผู้เล่น) - สัญญาณว่าใช่: ผู้เล่นหรือประเทศที่ RTT สูงเชื่อมต่ออยู่กับรีเจียนไกลแทนรีเจียนใกล้ และปิงที่วัดไปยังรีเจียนใกล้ต่ำ - สัญญาณว่าไม่ใช่: ถูกจัดไปรีเจียนใกล้ถูกต้องแล้วแต่ปิงยังสูง: น่าจะเป็นเส้นทางวิ่งอ้อม หรือเน็ต/Wi-Fi ของผู้เล่นคนนั้น - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: วิธีเลือกรีเจียนด้วย DNS (DNS ที่อิงตำแหน่งหรือความหน่วง) จะเดาตำแหน่งจากที่อยู่ของ DNS resolver ที่ผู้เล่นใช้แทนที่อยู่ของผู้เล่นเอง ถ้า resolver ไม่รองรับ EDNS Client Subnet ที่ส่งที่อยู่บางส่วนของผู้เล่นต่อไป ผู้เล่นที่ใช้ DNS ของบริษัทหรือ DNS ที่อยู่ไกลจะถูกจัดตามตำแหน่งของ resolver ระบบจับคู่เองก็อาจตัดสินปาร์ตี้จากค่าเฉลี่ยปิงของสมาชิก หรือขยายเกณฑ์ปิงเมื่อรอนานจนจัดไปรีเจียนไกล AWS GameLift Servers ก็ใช้ค่าเฉลี่ยเป็นเกณฑ์ปิงของปาร์ตี้โดยค่าเริ่มต้น และยกตัวอย่างการตั้งค่าที่ขยายเพดานปิงจาก 50 ms เป็น 100 ms และ 200 ms ผู้เล่นที่เปิด VPN อาจเจอทั้งดีเลย์ที่เพิ่มจากการผ่านเซิร์ฟเวอร์ตัวกลาง (“เชื่อมต่อผ่าน VPN/โปรแกรมลดปิง”) และการถูกจัดไปรีเจียนไกลซ้อนกัน จึงแยกได้จากการดูว่าเมื่อปิด VPN แล้วเชื่อมต่อใหม่ รีเจียนที่ถูกจัดให้เปลี่ยนหรือไม่ กรณีที่ไม่มีรีเจียนใกล้เลยจนต้องต่อไปรีเจียนไกล อธิบายไว้ใน “ความหน่วงในการแพร่สัญญาณ (ระยะทางจริง)” - แหล่งอ้างอิง: - [RFC 7871: Client Subnet in DNS Queries](https://www.rfc-editor.org/rfc/rfc7871) · IETF · DNS ที่ตอบต่างกันตามตำแหน่งจะเดาตำแหน่งจากที่อยู่ของ resolver ที่ส่งคำถามมา และถ้าใช้ resolver ส่วนกลางที่อยู่ไกลจากผู้เล่น จะได้คำตอบที่ไม่เหมาะสม ส่งที่อยู่บางส่วนของผู้เล่นต่อไปด้วย EDNS Client Subnet (ฟีเจอร์ทางเลือก) - [How Amazon Route 53 uses EDNS0 to estimate the location of a user](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy-edns0.html) · AWS · ถ้า resolver ไม่รองรับ edns-client-subnet จะเดาตำแหน่งผู้เล่นจากที่อยู่ของ resolver และตอบตามตำแหน่งของ resolver (เหมือนกันทั้ง routing แบบอิงตำแหน่งและแบบอิงความหน่วง) - [Geolocation accuracy](https://support.maxmind.com/knowledge-base/articles/maxmind-geolocation-accuracy) · MaxMind · ระดับประเทศประมาณ 99.8%, ระดับเมืองในสหรัฐฯ (ในรัศมี 50 กิโลเมตร) ประมาณ 66%, ถ้าใช้ VPN จะได้ตำแหน่งเซิร์ฟเวอร์ VPN แทนผู้ใช้ปลายทาง, IP ของเครือข่ายมือถือใช้กันในพื้นที่กว้างจึงระบุตำแหน่งละเอียดไม่ได้, ฐานข้อมูลต้องอัปเดตอย่างต่อเนื่อง, ขอแก้ไขข้อมูลได้ - [FlexMatch rule types](https://docs.aws.amazon.com/gameliftservers/latest/flexmatchguide/match-rules-reference-ruletype.html) · AWS · กฎความหน่วง (maxLatency) ดูความหน่วงของผู้เล่นแยกตามตำแหน่ง, ปาร์ตี้ใช้ค่าเฉลี่ยของสมาชิก (partyAggregation avg) เป็นค่าเริ่มต้น, คิวอาจวางไปรีเจียนที่ไม่ตรงกฎความหน่วงก็ได้ - [Create a player latency policy](https://docs.aws.amazon.com/gameliftservers/latest/developerguide/queues-design-latency.html) · AWS · วางไว้ในตำแหน่งที่ความหน่วงเฉลี่ยของผู้เล่นทุกคนต่ำที่สุด แต่ผู้เล่นที่ความหน่วงสูงแบบสุดโต่งก็ถูกวางด้วย, ตัวอย่างนโยบายที่ขยายเพดานปิงจาก 50 ms เป็น 100 ms และ 200 ms - [Amazon GameLift Servers UDP ping beacons](https://docs.aws.amazon.com/gameliftservers/latest/developerguide/reference-udp-ping-beacons.html) · AWS · ไคลเอนต์เกมวัดความหน่วงด้วย UDP endpoint ที่มีในแต่ละตำแหน่งโฮสต์ แล้วใช้ในการวางเซิร์ฟเวอร์และจับคู่, ใกล้เคียงทราฟฟิกเกมจริงมากกว่า ICMP ping - [Azure network round-trip latency statistics](https://learn.microsoft.com/en-us/azure/networking/azure-network-latency) · Microsoft Azure · ค่ามัธยฐานเวลาไปกลับที่วัดจริงจากโซล (Korea Central): โตเกียว (Japan East) 29 ms, สหรัฐฯ ฝั่งตะวันตก 124–136 ms - [Flow log records](https://docs.aws.amazon.com/vpc/latest/userguide/flow-log-records.html) · AWS · srcaddr ใน record ของ VPC flow log: ถ้าเป็นทราฟฟิกขาเข้า คือ IP ของฝั่งผู้ส่ง #### in-cert · ใบรับรอง TLS หมดอายุ/ตั้งค่าผิด · TLS certificate expiry / misconfiguration ถ้าใบรับรองของเซิร์ฟเวอร์ล็อกอิน, API หรือแพตช์หมดอายุ หรือขาดใบรับรองกลาง (intermediate certificate) ไคลเอนต์ที่เชื่อมต่อใหม่ตั้งแต่วินาทีนั้นจะเชื่อมต่อ TLS ไม่สำเร็จ - ทำไม → ผลคือ → บนหน้าจอ: ใบรับรองพ้นวันหมดอายุ, เซิร์ฟเวอร์ส่งใบรับรองมาโดยไม่มีใบรับรองกลาง หรือวันที่/เวลาในเครื่องผู้เล่นผิด → ไคลเอนต์ตรวจใบรับรองไม่ผ่านแล้วตัดการเชื่อมต่อ TLS → เข้าเกมไม่ได้/โหลดไม่จบที่ขั้นตอนล็อกอินหรือแพตช์, ใช้ไม่ได้เฉพาะฟีเจอร์ HTTPS อย่างร้านค้า คนที่เชื่อมต่ออยู่แล้วส่วนใหญ่เล่นได้ปกติ - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, กดไม่ติด/โรลแบ็ค / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์, เฉพาะบางฟีเจอร์, เราคนเดียว / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: บันทึกข้อผิดพลาดของใบรับรองด้วย error code ที่แยกจากการเชื่อมต่อล้มเหลวแบบอื่นแล้วแจ้งผู้เล่น, ถ้าวันที่ผิดให้แนะนำให้ตั้งวันที่/เวลาของเครื่องเป็นอัตโนมัติ, ถ้าใช้ certificate pinning ให้ใส่คีย์สำรองไว้ด้วยและนัดกำหนดการเปลี่ยนใบรับรองกับทีมอินฟรา - งานฝั่งทีมอินฟรา: เครือข่าย: ถ้า terminate TLS ที่โหลดบาลานเซอร์หรือ CDN ให้ตั้ง alert สถานะการต่ออายุอัตโนมัติของใบรับรองแบบ managed และจำนวนวันที่เหลือ (ACM คือ DaysToExpiry), คง DNS record สำหรับการยืนยันไว้ เครื่องเซิร์ฟเวอร์/OS: ถ้า terminate TLS ที่เซิร์ฟเวอร์ ให้ตั้งการต่ออายุอัตโนมัติและโหลดคอนฟิกใหม่หลังต่ออายุ, ตั้งค่าเป็น chain ที่รวมใบรับรองกลางด้วย, ตรวจอายุที่เหลือของทุกที่อยู่ล็อกอิน, API และแพตช์จากภายนอกเป็นระยะแล้วตั้ง alert - ตัวเลขที่ควรรู้: ใบรับรอง Let’s Encrypt มีอายุ 90 วัน จึงแนะนำให้ต่ออายุทุก 60 วัน ส่วน AWS Certificate Manager ตรวจใบรับรองที่ยืนยันด้วย DNS ตั้งแต่ 45 วันก่อนหมดอายุแล้วต่ออายุอัตโนมัติ ถ้าการต่ออายุอัตโนมัติล้มเหลวเงียบ ๆ การเชื่อมต่อใหม่จะถูกปิดกั้นพร้อมกันตรงเวลาหมดอายุพอดี - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนล็อกอินที่สำเร็จ, จำนวน error ของ TLS handshake) - จุดที่ต้องดู: ใช้ openssl s_client -connect HOST:443 -showcerts ดูรายการใบรับรองที่เซิร์ฟเวอร์ส่งจริง แล้วตรวจวันหมดอายุ (notAfter) ของแต่ละใบด้วย openssl x509 -noout -enddate ถ้า terminate TLS ที่โหลดบาลานเซอร์ ให้ดูจำนวน error ในการเจรจา TLS (AWS ALB/NLB คือ ClientTLSNegotiationErrorCount) และจำนวนล็อกอินที่สำเร็จ - สัญญาณว่าใช่: เลยวันหมดอายุแล้ว หรือรายการที่เซิร์ฟเวอร์ส่งมาไม่มีใบรับรองกลาง และเวลาที่ error เริ่มเพิ่มตรงกับเวลาหมดอายุหรือเวลาที่เปลี่ยนใบรับรอง - สัญญาณว่าไม่ใช่: รายการใบรับรองและวันหมดอายุปกติ แต่ล้มเหลวเฉพาะผู้เล่นบางคน: ให้ดูวันที่/เวลาในเครื่องของผู้เล่นคนนั้น หรือรายการ root certificate ของ OS รุ่นเก่า - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: คอนฟิกที่ขาดใบรับรองกลางอาจดูปกติเมื่อเปิดด้วยเบราว์เซอร์บน PC เพราะเบราว์เซอร์จำใบรับรองกลางที่เคยได้รับจากเว็บไซต์อื่นไว้แล้วเติมส่วนที่ขาดให้ แต่ไคลเอนต์ที่ไม่ได้จำไว้แบบนั้น เช่น แอป Android จะล้มเหลว อายุใบรับรองก็กำลังสั้นลง Let’s Encrypt มีแผนลดอายุเริ่มต้นเหลือ 64 วันในปี 2027 และ 45 วันในปี 2028 คอนฟิกที่ตั้งตายตัวให้ต่ออายุทุก 60 วัน จะเหลือเวลาเผื่อแค่สี่วันกับใบรับรอง 64 วัน และจะเลยวันหมดอายุไปกับใบรับรอง 45 วัน AWS Certificate Manager เองก็ไม่ต่ออายุอัตโนมัติให้ใบรับรองที่นำเข้า (import) และถ้าลบ DNS record สำหรับการยืนยัน การต่ออายุจะล้มเหลว ลักษณะที่ล็อกอินไม่ได้คล้ายกับ “DNS ขัดข้อง/ช้า” แต่ปัญหาใบรับรองจะล้มเหลวที่ TLS handshake หลังหาที่อยู่เซิร์ฟเวอร์เจอแล้ว และเวลาเริ่มเกิดตรงกับเวลาหมดอายุหรือเวลาที่เปลี่ยนใบรับรอง - แหล่งอ้างอิง: - [RFC 5280: Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile](https://www.rfc-editor.org/rfc/rfc5280) · IETF · อายุของใบรับรองคือตั้งแต่ notBefore ถึง notAfter, การตรวจ path จะตรวจว่าเวลาปัจจุบันอยู่ในช่วงอายุของใบรับรองทุกใบใน chain หรือไม่ (ถ้านาฬิกาฝั่งที่ตรวจผิดก็จะล้มเหลว) - [FAQ](https://letsencrypt.org/docs/faq/) · Let's Encrypt · อายุใบรับรองเริ่มต้น 90 วัน, แนะนำให้ต่ออายุทุก 60 วัน - [Decreasing Certificate Lifetimes to 45 Days](https://letsencrypt.org/2025/12/02/from-90-to-45) · Let's Encrypt · ลดอายุเริ่มต้นเหลือ 64 วันตั้งแต่เดือนกุมภาพันธ์ 2027 และ 45 วันตั้งแต่เดือนกุมภาพันธ์ 2028, การต่ออายุแบบช่วงห่างตายตัว 60 วันจะไม่พอ จึงแนะนำให้ต่ออายุเมื่อผ่านไปประมาณ 2 ใน 3 ของอายุใบรับรอง - [Renewal for domains validated by DNS](https://docs.aws.amazon.com/acm/latest/userguide/dns-renewal-validation.html) · AWS · 45 วันก่อนหมดอายุ จะตรวจว่าใบรับรองถูกใช้งานอยู่ในเซอร์วิสของ AWS และมี CNAME record สำหรับการยืนยันหรือไม่ แล้วต่ออายุอัตโนมัติ ถ้ายืนยันไม่ได้จะแจ้งเตือนเมื่อเหลือ 30, 15, 7, 3 และ 1 วันก่อนหมดอายุ - [Managed certificate renewal in AWS Certificate Manager](https://docs.aws.amazon.com/acm/latest/userguide/managed-renewal.html) · AWS · ใบรับรองที่นำเข้า (import) และใบรับรองที่หมดอายุไปแล้ว ไม่อยู่ในขอบเขตการต่ออายุอัตโนมัติ - [Supported CloudWatch metrics](https://docs.aws.amazon.com/acm/latest/userguide/cloudwatch-metrics.html) · AWS · DaysToExpiry: จำนวนวันที่เหลือก่อนใบรับรองหมดอายุ, เผยแพร่วันละสองครั้งจนกว่าจะหมดอายุ - [Security with network protocols](https://developer.android.com/privacy-and-security/security-ssl) · Android (Google) · ถ้าเซิร์ฟเวอร์ส่งมาโดยไม่มีใบรับรองกลาง แอป Android จะล้มเหลวด้วย SSLHandshakeException แต่เบราว์เซอร์บน PC อาจไม่ error เพราะเติมด้วยใบรับรองกลางที่เคยได้รับไว้, ตรวจ chain ที่เซิร์ฟเวอร์ส่งด้วย openssl s_client - [Network security configuration](https://developer.android.com/privacy-and-security/security-config) · Android (Google) · ถ้าใช้ certificate pinning ต้องใส่คีย์สำรองไว้ด้วยเผื่อการเปลี่ยนคีย์หรือเปลี่ยน CA ไม่อย่างนั้นการเชื่อมต่อจะถูกตัดจนกว่าจะอัปเดตแอป - [openssl-s_client](https://docs.openssl.org/3.0/man1/openssl-s_client/) · OpenSSL · -showcerts: แสดงรายการใบรับรองที่เซิร์ฟเวอร์ส่งมาตามลำดับที่ส่ง (ยังไม่ได้ตรวจ chain) - [openssl-x509](https://docs.openssl.org/3.0/man1/openssl-x509/) · OpenSSL · -enddate: แสดงวันหมดอายุของใบรับรอง (notAfter), -checkend: ตรวจว่าจะหมดอายุภายในจำนวนวินาทีที่กำหนดหรือไม่ - [CloudWatch metrics for your Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-cloudwatch-metrics.html) · AWS · ClientTLSNegotiationErrorCount: จำนวนการเชื่อมต่อที่สร้าง TLS session ไม่สำเร็จ เช่น กรณีที่ไคลเอนต์ตรวจใบรับรองของเซิร์ฟเวอร์ไม่ผ่านแล้วตัดการเชื่อมต่อ - [CloudWatch metrics for your Network Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-cloudwatch-metrics.html) · AWS · ClientTLSNegotiationErrorCount: จำนวน TLS handshake ที่เจรจาระหว่างไคลเอนต์กับ TLS listener ไม่สำเร็จ #### in-login-queue · คิวล็อกอินชนเพดานและ grace period ตอนเชื่อมต่อใหม่ไม่พอ · Login queue cap / no reconnect grace เมื่อคนแห่เข้ามาทันทีหลังเปิดตัวเกมหรือปิดปรับปรุง คิวล็อกอินจะชนเพดานจนปฏิเสธคนที่จะเข้าคิวใหม่ และผู้เล่นที่รออยู่ถ้าหลุดไปแป๊บเดียวก็เสียลำดับคิวแล้วต้องกลับไปต่อท้าย - ทำไม → ผลคือ → บนหน้าจอ: คนที่จะเข้าเกมมีมากกว่าที่เซิร์ฟเวอร์ล็อกอินรับได้ในครั้งเดียว จึงต้องมีคิว และถ้าคิวยาวเกินไปก็ปฏิเสธการเข้าคิวใหม่เพื่อปกป้องเซิร์ฟเวอร์ → ยิ่งคิวยาว เวลารอยิ่งนาน และระหว่างนั้นถ้า Wi-Fi หรือเครือข่ายมือถือหลุดแค่แป๊บเดียวก็เสียลำดับคิว → เข้าเกมไม่ได้/โหลดไม่จบ, เกมปิดตัวพร้อม error ระหว่างรอ, ต้องกลับไปรอท้ายคิวใหม่ - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, หลุด / ปัจจัย: การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์, เราคนเดียว / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม), อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ตั้งเพดานคิวให้ตรงกับปริมาณที่เซิร์ฟเวอร์ล็อกอินประมวลผลได้จริง, เก็บลำดับคิวของผู้เล่นที่หลุดระหว่างรอไว้ช่วงหนึ่ง (grace period ตอนเชื่อมต่อใหม่), แสดงลำดับคิวและเวลารอโดยประมาณ, เก็บความยาวคิว, จำนวนที่ถูกปฏิเสธ และจำนวนที่หลุดระหว่างรอเป็นเมตริก ไคลเอนต์: ถ้าหลุดระหว่างรอ ให้เชื่อมต่อกลับเข้าตำแหน่งเดิมอัตโนมัติโดยไม่ปิดเกม, กระจายช่วงห่างของการลองใหม่ด้วย exponential backoff และ jitter - งานฝั่งทีมอินฟรา: เครื่องเซิร์ฟเวอร์/OS: ทดสอบโหลดก่อนเปิดตัวเพื่อวัดขีดจำกัดการประมวลผลของเซิร์ฟเวอร์ล็อกอินและล็อบบี้, ตอนเปิดตัวเตรียมให้เพิ่มเครื่องสำรองได้ทันที, ดูเมตริกคิวบนกราฟเดียวกับจำนวนครั้งที่พยายามเชื่อมต่อ - ตัวเลขที่ควรรู้: ตอนเปิดตัวภาคเสริม FINAL FANTASY XIV ในปี 2021 ถ้าจำนวนคนรอในแต่ละ logical data center เกิน 17,000 คน จะปฏิเสธการเข้าคิวใหม่ (Error 2002) และถ้าหลุดระหว่างรอ lobby server จะรอให้หลายสิบวินาทีถึง 1 นาที ถ้าต่อกลับเข้ามาภายในเวลานั้น จะได้ต่อคิวจากตำแหน่งเดิมกลางคิว - บนกราฟ: ชนเพดานแล้วแบนราบ (ความยาวคิวล็อกอิน, จำนวนที่ถูกปฏิเสธเพราะชนเพดาน, จำนวนที่หลุดระหว่างรอ) - จุดที่ต้องดู: วางความยาวคิว, เวลารอเฉลี่ย, จำนวนที่ถูกปฏิเสธเพราะชนเพดาน และจำนวนที่หลุดระหว่างรอ ที่เซิร์ฟเวอร์ล็อกอินและล็อบบี้บันทึกไว้ บนกราฟเดียวกับจำนวนครั้งที่พยายามเชื่อมต่อ - สัญญาณว่าใช่: ทันทีหลังเปิดตัวหรือปิดปรับปรุง ระหว่างที่ความยาวคิวชนเพดานแล้วแบนราบ จำนวนที่ถูกปฏิเสธเพิ่มขึ้น และการหลุดระหว่างรอกระจุกอยู่ที่ผู้เล่น Wi-Fi และเครือข่ายมือถือ - สัญญาณว่าไม่ใช่: คิวสั้นแต่ล็อกอินช้า: น่าจะเป็น DB (db-login-storm) หรือคิวรอเชื่อมต่อของ OS (so-backlog) - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: กรณีที่คนแห่ล็อกอินจน DB ช้า อธิบายไว้ใน “คนแห่ล็อกอินและคิวรี N+1” และกรณีที่คิวรอเชื่อมต่อของ OS ล้น อธิบายไว้ใน “คิวรอเชื่อมต่อ (backlog) ล้น” การ์ดนี้เป็นปัญหาการออกแบบคิวล็อกอินที่เกมตั้งใจสร้างไว้ เพดานคิวเป็นกลไกป้องกันเซิร์ฟเวอร์ล็อกอินจึงเอาออกไม่ได้ เพราะต้องปฏิเสธคำขอส่วนเกินแต่เนิ่น ๆ จึงจะประมวลผลคำขอที่รับไหวต่อไปได้ สิ่งสำคัญคือลดความเสียหายที่การปฏิเสธและการหลุดสร้างให้ผู้เล่น และยิ่งคิวยาว error ยิ่งไปกระจุกที่ผู้เล่นที่เน็ตไม่นิ่ง เช่น Wi-Fi และเครือข่ายมือถือ - กรณีจริง: ffxiv-2021 - แหล่งอ้างอิง: - [Response to Congestion (as of Dec. 11)](https://na.finalfantasyxiv.com/lodestone/news/detail/6a94b30182b6d963994fdc0b789264ac9f24986f) · Square Enix · ถ้าจำนวนคนรอในแต่ละ logical data center เกิน 17,000 คน จะปฏิเสธการเข้าคิวใหม่เพื่อไม่ให้เซิร์ฟเวอร์ล็อกอินล่ม (Error 2002), ถ้าหลุดระหว่างรอ lobby server จะรอหลายสิบวินาทีถึง 1 นาที ถ้าต่อกลับมาภายในเวลานั้นได้ต่อจากตำแหน่งเดิมกลางคิว ถ้าเกินจะไปต่อท้าย - [Using load shedding to avoid overload](https://aws.amazon.com/builders-library/using-load-shedding-to-avoid-overload/) · Amazon Builders' Library · load shedding ที่ปฏิเสธคำขอส่วนเกินแต่เนิ่น ๆ เพื่อให้ประมวลผลคำขอที่รับไหวต่อไปได้ ### การออกแบบการซิงก์ (16 สาเหตุ) #### sy-request-response · แสดงผลหลังเซิร์ฟเวอร์ตอบเท่านั้น (แบบ request-response) · Request-response (no client-side feedback) กดปุ่มแล้วไม่มีทั้งแอนิเมชันและเสียงจนกว่าคำตอบจากเซิร์ฟเวอร์จะมาถึง ปิงจึงกลายเป็นความเร็วในการตอบสนองโดยตรง - ทำไม → ผลคือ → บนหน้าจอ: แสดงผลสกิล, การเดิน และการเก็บของ หลังจากเซิร์ฟเวอร์ยืนยันแล้ว → ตั้งแต่วินาทีที่กดจะไม่มีปฏิกิริยาใด ๆ เป็นเวลาเท่ากับเวลาไปกลับ + เวลารอทิก → ถ้าปิง 150 ms ทุกการกระทำจะตอบสนองช้าไปครั้งละ 0.2 วินาที - อาการ: อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตลอดเวลา, ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: เริ่มแอนิเมชัน, เสียง และเอฟเฟกต์ทันทีที่กด (การแสดงผลล่วงหน้า), แสดงเฉพาะผลลัพธ์ (ดาเมจ, ของรางวัล) หลังเซิร์ฟเวอร์ยืนยัน, การเดินและการโจมตีปกติให้ใช้ prediction แสดงผลทันที, เมื่อได้รับตำแหน่งที่เซิร์ฟเวอร์แก้มา ให้ใช้อินพุตที่ยังไม่ได้รับการยืนยันซ้ำจากตำแหน่งนั้น เซิร์ฟเวอร์: คำนวณการเคลื่อนที่เองจากอินพุตที่ได้รับ และส่งค่าแก้กลับไปเฉพาะเมื่อตำแหน่งต่างจากที่ไคลเอนต์คาดการณ์เกินเกณฑ์ - ตัวเลขที่ควรรู้: เวลาตอบสนอง ≈ ปิง + ครึ่งหนึ่งของช่วงห่างระหว่างทิก + หนึ่งเฟรม ที่ 20 ทิกและปิง 150 ms จะได้ประมาณ 190 ms - บนกราฟ: สูงตลอดตั้งแต่แรก (เวลาตั้งแต่อินพุตจนเริ่มแสดงผล, RTT (ปิง)) - จุดที่ต้องดู: บันทึกเวลาที่กดปุ่ม, เวลาที่แอนิเมชันหรือเสียงแรกเริ่ม และเวลาที่คำตอบจากเซิร์ฟเวอร์มาถึง ลงใน log ไคลเอนต์ของ dev build แล้วดูคู่กับ RTT ในเกม วัดโดยเปลี่ยนปิงไปเรื่อย ๆ ด้วยการใส่ดีเลย์ผ่าน network emulation ของเอนจิน (Unreal NetEmulation.PktLag) หรือ tc netem ของ Linux บนเซิร์ฟเวอร์ทดสอบ - สัญญาณว่าใช่: การแสดงผลเริ่มพร้อมกับที่คำตอบจากเซิร์ฟเวอร์มาถึงทุกครั้ง เวลาตั้งแต่อินพุตจนแสดงผลเท่ากับ RTT + เวลารอทิก และเพิ่มขึ้นตามดีเลย์ที่ใส่เข้าไปพอดี - สัญญาณว่าไม่ใช่: การแสดงผลเริ่มทันทีที่กดและมีแค่ผลลัพธ์อย่างตัวเลขดาเมจที่ช้า: เป็นการออกแบบปกติ ถ้าช้าเกินช่วงห่างระหว่างทิกแม้ในที่ที่ปิงต่ำ น่าจะเป็นการรอทิกซ้อนสองชั้นหรือปัญหาเฟรมฝั่งไคลเอนต์ - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: เกมที่ไม่ต้องการการตอบสนองเร็ว เช่น เกมเทิร์นเบส, เกมการ์ด และเกม idle วิธีนี้ง่ายและปลอดภัยที่สุด ปัญหาเกิดเมื่อเกมที่มีการควบคุมแบบเรียลไทม์ทำแม้แต่การเดินหรือการโจมตีปกติด้วยวิธีนี้ - แหล่งอ้างอิง: - [Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization (Yahn W. Bernier, GDC 2001)](https://web.cs.wpi.edu/~claypool/courses/4513-B03/papers/games/bernier.pdf) · Valve · ไคลเอนต์ที่รอแต่ผลจากเซิร์ฟเวอร์ ถ้าดีเลย์ 500 ms ทุกการกระทำจะเห็นผลหลังผ่านไป 500 ms แก้ด้วย client-side prediction และ reconciliation - [Using Gameplay Abilities in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/using-gameplay-abilities-in-unreal-engine) · Epic Games · Local Predicted ทำงานทันทีที่กดและให้เซิร์ฟเวอร์ตัดสินขั้นสุดท้าย ส่วน Server Initiated ไม่มี prediction ผู้ใช้สกิลจึงเห็นดีเลย์ - [Understanding Networked Movement in the Character Movement Component for Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/understanding-networked-movement-in-the-character-movement-component-for-unreal-engine) · Epic Games · ไคลเอนต์คาดการณ์และเก็บการเคลื่อนที่ไว้ และแก้ตำแหน่งเฉพาะเมื่อความคลาดเคลื่อนจากเซิร์ฟเวอร์เกินค่าที่ยอมรับได้ (MAXPOSITIONERRORSQUARED) แล้วใช้การเคลื่อนที่ที่เก็บไว้ซ้ำหลังแก้ - [Using Network Emulation in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/using-network-emulation-in-unreal-engine) · Epic Games · ทดสอบโดยใส่ดีเลย์ต่ำสุด/สูงสุดและอัตราแพ็กเก็ตหายให้เซิร์ฟเวอร์และไคลเอนต์, ในคอนโซลตั้งค่าแบบ NetEmulation.PktLag - [tc-netem(8) — Linux manual page](https://man7.org/linux/man-pages/man8/tc-netem.8.html) · iproute2 · เครื่องมือทดสอบที่ใส่ดีเลย์/จิตเตอร์ (delay TIME JITTER) และการหาย (loss random PERCENT) ให้แพ็กเก็ตขาออก เพื่อจำลองเครือข่ายจริง #### sy-chatty · โปรโตคอลที่ต้องไปกลับตามลำดับหลายรอบ (chatty) · Chatty protocol / sequential round trips ถ้าการกระทำหนึ่งครั้งต้องไปกลับเซิร์ฟเวอร์หลายรอบตามลำดับ ปิงจะคูณตามจำนวนรอบนั้น - ทำไม → ผลคือ → บนหน้าจอ: เปิดร้านค้า → ขอรายการ → ตรวจราคา → ซื้อ → อัปเดตกระเป๋า แยกเป็นคำขอทีละรายการ → ต้องได้คำตอบของคำขอก่อนหน้าก่อนจึงส่งคำขอถัดไป → ที่ปิง 150 ms ซื้อของครั้งเดียวใช้เวลาเกือบ 1 วินาที และโหลดนานผิดปกติ - อาการ: อินพุตดีเลย์, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: ความหน่วง - ใครเจอ: เฉพาะบางฟีเจอร์, เราคนเดียว / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: เปลี่ยนโปรโตคอลให้รวมหลายขั้นตอนไว้ในคำขอและคำตอบเดียว (เช่น ใส่กระเป๋าที่อัปเดตแล้วไว้ในคำตอบของการซื้อด้วย) ไคลเอนต์: โหลดข้อมูลที่ต้องใช้ไว้ล่วงหน้า, ทำ UI ที่ไม่ต้องรอผลลัพธ์ - ตัวเลขที่ควรรู้: เวลาที่ใช้ ≈ จำนวนรอบไปกลับ × (ปิง + เวลาประมวลผลบนเซิร์ฟเวอร์ + เวลารอทิก) ถ้า 5 รอบที่ปิง 150 ms จะใช้ประมาณ 0.85–1 วินาที - บนกราฟ: สูงตลอดตั้งแต่แรก (เวลาที่แต่ละฟีเจอร์ใช้จนเสร็จ, จำนวนรอบไปกลับต่อการกระทำหนึ่งครั้ง) - จุดที่ต้องดู: ใน packet capture ฝั่งเซิร์ฟเวอร์ (Wireshark) นับว่าระหว่างที่บัญชีทดสอบกดซื้อของในร้านหรือล็อกอินหนึ่งครั้ง คำขอและคำตอบสลับกันไปมากี่รอบ และห่างกันเท่าไร ถ้ามี log คำขอของเซิร์ฟเวอร์ ให้จัดกลุ่มด้วย session ID แล้วดูจำนวนคำขอและเวลาที่แต่ละคำขอมาถึงและได้คำตอบ - สัญญาณว่าใช่: การกระทำหนึ่งครั้งมีคำขอที่ต้องรอคำตอบก่อนหน้าแล้วจึงส่งต่อกันหลายรอบ เวลาที่ใช้จนเสร็จประมาณจำนวนรอบ × RTT และผู้เล่นในพื้นที่ที่ปิงสูงจะใช้ฟีเจอร์เดียวกันได้ช้าลงตามสัดส่วน - สัญญาณว่าไม่ใช่: ไปกลับแค่หนึ่งสองรอบ แต่คำตอบเดียวใช้เวลานาน: น่าจะเป็นสาเหตุที่การประมวลผลบนเซิร์ฟเวอร์หรือ DB ถ้าผู้เล่นทุกคนช้าเท่ากันโดยไม่เกี่ยวกับปิง ให้ดูโหลดของเซิร์ฟเวอร์ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Chatty I/O antipattern](https://learn.microsoft.com/en-us/azure/architecture/antipatterns/chatty-io/) · Microsoft Azure · ถ้ามีคำขอ I/O เล็ก ๆ จำนวนมาก ดีเลย์สะสมจะทำให้การตอบสนองแย่ลงมาก แนะนำให้รวมคำขอให้ใหญ่ขึ้นและน้อยครั้งลง #### sy-no-queue · ไม่มีการกดสกิลล่วงหน้า · No input/spell queue ถ้าต้องได้การยืนยันจากเซิร์ฟเวอร์ว่าสกิลก่อนหน้าจบแล้วจึงจะกดสกิลถัดไปได้ ทุกคอมโบจะมีเวลาไปกลับแทรกเข้ามา - ทำไม → ผลคือ → บนหน้าจอ: รับอินพุตสกิลถัดไปเฉพาะ “หลังสกิลก่อนหน้ายืนยันแล้ว” เท่านั้น → ระหว่างสกิลแต่ละครั้งมีช่วงว่างเท่ากับปิง → เกิดช่องว่างระหว่างคอมโบทุกครั้ง และยิ่งปิงสูง DPS ยิ่งลดลง - อาการ: อินพุตดีเลย์, กดไม่ติด/โรลแบ็ค / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว, เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: มีช่วงเวลาที่รับการกดล่วงหน้า คือรับอินพุตที่กดภายในช่วงเวลาหนึ่งก่อนคูลดาวน์จบ (เช่น 0.3–0.4 วินาที) แล้วส่งให้เซิร์ฟเวอร์ทันที เซิร์ฟเวอร์: รับอินพุตที่มาถึงเร็วไปเล็กน้อยไว้ แล้วรันทันทีที่คูลดาวน์จบ - ตัวเลขที่ควรรู้: ในคอมโบที่คูลดาวน์ 1 วินาที ถ้าปิง 150 ms จะว่างระหว่างสกิลอย่างน้อย 0.15 วินาทีทุกครั้ง จำนวนสกิลที่ใช้ได้ในเวลาเท่ากันจะลดลงมากกว่า 13% - บนกราฟ: สูงตลอดตั้งแต่แรก (ช่วงว่างระหว่างสกิล, RTT (ปิง)) - จุดที่ต้องดู: บันทึกเวลาที่คูลดาวน์สกิลจบ, เวลาที่คำขอสกิลถัดไปมาถึง และเวลาที่รัน ของแต่ละตัวละครลงใน log ของเซิร์ฟเวอร์ แล้วเทียบช่วงว่างระหว่างนั้นกับ RTT ของผู้เล่น - สัญญาณว่าใช่: ตั้งแต่คูลดาวน์จบจนสกิลถัดไปทำงาน ว่างประมาณเท่า RTT ทุกครั้ง ผู้เล่นที่ปิงสูงกว่ามีช่วงว่างยาวกว่าและใช้สกิลได้น้อยกว่าในเวลาเท่ากัน - สัญญาณว่าไม่ใช่: ช่วงว่างคงที่ไม่ขึ้นกับปิง: น่าจะเป็นการออกแบบ global cooldown หรือความยาวแอนิเมชัน ถ้าช่วงว่างพุ่งสูงแค่บางครั้ง ให้ดูจิตเตอร์, แพ็กเก็ตหาย หรือทิกเกินงบเวลา - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: ตัวอย่างเช่น World of Warcraft มีช่วงเวลาที่รับการกดล่วงหน้า และให้ผู้เล่นปรับได้ในการตั้งค่า ถ้าช่วงเวลานี้ยาวกว่าเวลาไปกลับ ปิงแทบไม่แทรกเข้ามาระหว่างคอมโบ - แหล่งอ้างอิง: - [Latency and Player Actions in Online Games (Communications of the ACM, 2006)](https://web.cs.wpi.edu/~claypool/papers/precision-deadline/final.pdf) · ACM · ใน EverQuest 2 ยิ่งดีเลย์มาก ดาเมจที่ตัวละครทำได้ยิ่งลดลงและการต่อสู้ยิ่งยาวขึ้น (จาก 0 เป็น 500 ms การต่อสู้ที่ใช้เวลาประมาณ 2 นาทีนานขึ้น 5 วินาที) #### sy-short-window · ช่วงเวลาตัดสินสั้นจนปิงกินหมด · Timing window too short for latency + reaction ถ้าเวลาที่ต้องตอบสนอง เช่น การหลบ, การแพร์รี และการกัน สั้นเกินไป ปิงจะกินเวลานั้นไปจนเกิดการโจมตีที่หลบไม่ได้ - ทำไม → ผลคือ → บนหน้าจอ: ช่วงเวลาตัดสินที่สั้น เช่น สัญญาณเตือนท่าโจมตีของบอส 0.5 วินาที หรือช่วงเวลาตัดสินการแพร์รี 0.2 วินาที → เห็นสัญญาณเตือนช้า (ดีเลย์ขาลง + interpolation) และอินพุตของเราก็ไปถึงช้า (ดีเลย์ขาขึ้น + เวลารอทิก) → หลบพ้นแน่ ๆ แต่ยังโดน, แพร์รีไม่ออก - อาการ: กดไม่ติด/โรลแบ็ค, อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว, เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม), อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: นัดเวลาสัญญาณเตือนการโจมตีตามเวลาเซิร์ฟเวอร์แล้วส่งล่วงหน้า, ขยายช่วงเวลาตัดสินออกไปเท่ากับปิง (lag compensation) ไคลเอนต์: เล่นสัญญาณเตือนที่ได้รับให้ตรงกับเวลาเซิร์ฟเวอร์ที่นัดไว้ - งานฝั่งทีมอินฟรา: วางเซิร์ฟเวอร์ใกล้พื้นที่ที่มีผู้เล่นมาก (เซิร์ฟเวอร์ประจำภูมิภาค) เพื่อลดปิงโดยตรง - ตัวเลขที่ควรรู้: ถ้าปิง 150 ms และ interpolation 100 ms กว่าสัญญาณเตือนจะขึ้นบนจอเราใช้ประมาณ 0.18 วินาที และกว่าอินพุตของเราจะถึงเซิร์ฟเวอร์ใช้ประมาณ 0.1 วินาที บวกเวลาตอบสนองของคน 0.25 วินาทีเข้าไป สัญญาณเตือน 0.5 วินาทีจึงแทบหลบไม่ทัน - บนกราฟ: สูงเฉพาะบางกลุ่ม (อัตราการหลบ/แพร์รีพลาด (แยกตามช่วงปิง)) - จุดที่ต้องดู: บันทึกเวลาเริ่มและจบของช่วงเวลาตัดสิน, เวลาที่อินพุตของผู้เล่นมาถึงเซิร์ฟเวอร์ และ RTT ของผู้เล่นคนนั้นลงใน log ของเซิร์ฟเวอร์ แล้วแบ่งดูอัตราพลาดตามช่วงปิง (เช่น ทีละ 50 ms) - สัญญาณว่าใช่: ยิ่งช่วงปิงสูง อัตราพลาดยิ่งสูงชัดเจน และอินพุตที่พลาดมาถึงหลังช่วงเวลาตัดสินจบไปไม่นาน (ไม่เกิน RTT รวมเวลา interpolation) - สัญญาณว่าไม่ใช่: อัตราพลาดใกล้เคียงกันทุกช่วงปิง: น่าจะเป็นความยากของแพตเทิร์น ถ้าอินพุตมาถึงภายในช่วงเวลาตัดสินแล้วยังนับเป็นพลาด ให้ดูโค้ดตัดสินผลหรือการตรวจสอบของเซิร์ฟเวอร์ - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Factors influencing the latency of simple reaction time (Frontiers in Human Neuroscience, 2015)](https://doi.org/10.3389/fnhum.2015.00131) · Frontiers · เวลาตอบสนองอย่างง่าย (simple reaction time) เฉลี่ยประมาณ 231 ms (213 ms เมื่อหักดีเลย์ของอุปกรณ์), งานวิจัยขนาดใหญ่ระยะหลังได้ 233–400 ms - [Latency and Player Actions in Online Games (Communications of the ACM, 2006)](https://web.cs.wpi.edu/~claypool/papers/precision-deadline/final.pdf) · ACM · การกระทำที่ต้องแม่นยำและมีเส้นตายสั้นยิ่งไวต่อดีเลย์ (ขีดจำกัดอยู่ที่ประมาณ 100 ms สำหรับมุมมองบุคคลที่ 1, ประมาณ 500 ms สำหรับบุคคลที่ 3 และประมาณ 1,000 ms สำหรับมุมมองแบบพระเจ้า (omnipresent)) - [NetworkTime and ticks (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/advanced-topics/networktime-ticks.html) · Unity · วิธีนัดเวลาอีเวนต์ตามเวลาเซิร์ฟเวอร์ (ServerTime) เพื่อให้ทุกไคลเอนต์เล่นในจังหวะเดียวกัน #### sy-no-lagcomp · การตัดสินผลที่ไม่มี lag compensation · Server-now hit validation ถ้าเซิร์ฟเวอร์ตัดสินการโดนโดยดูแค่ “ตำแหน่งบนเซิร์ฟเวอร์ ณ ตอนนี้” ผลการตัดสินจะไม่ตรงกับที่เราเห็นบนจอ - ทำไม → ผลคือ → บนหน้าจอ: คู่ต่อสู้บนจอเราอยู่ในตำแหน่งย้อนหลังไปประมาณ 0.2 วินาที (เมื่อปิง 150 ms และ interpolation 100 ms) → เซิร์ฟเวอร์ตัดสินจากตำแหน่งปัจจุบัน ตรงที่เราเล็งไว้จึงไม่มีเป้าแล้ว → ยิงโดนแน่ ๆ แต่พลาด ต้องยิงดักหน้าเป้าที่กำลังเคลื่อนที่ - อาการ: กดไม่ติด/โรลแบ็ค / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ย้อนเวลาไปยังจังหวะที่ผู้โจมตีเห็นแล้วจึงตัดสิน (lag compensation) หรือเปลี่ยนเป็นระบบล็อกเป้า ไคลเอนต์: ตอนโจมตีให้ส่งจังหวะที่ตัวเองเห็นอยู่ (เวลาเซิร์ฟเวอร์ที่กำลัง interpolate) ไปด้วย - บนกราฟ: สูงเฉพาะบางกลุ่ม (อัตราการโจมตีโดนเป้าที่เคลื่อนที่ (แยกตามช่วงปิง)) - จุดที่ต้องดู: บันทึกเวลาโจมตี, ตำแหน่งเป้าบนจอผู้โจมตี (ค่าที่ไคลเอนต์ส่งมา), ตำแหน่งเป้าบนเซิร์ฟเวอร์ที่ใช้ตัดสิน และ RTT ของผู้โจมตีลงใน log การตัดสินของเซิร์ฟเวอร์ ใน dev build ถ้าวาดตำแหน่งที่เซิร์ฟเวอร์ใช้ตัดสินซ้อนบนจอไคลเอนต์จะเห็นทันที - สัญญาณว่าใช่: ในการตัดสินที่พลาด ระยะห่างระหว่างสองตำแหน่งประมาณความเร็วเป้า × (RTT ของผู้โจมตี + เวลา interpolation) และยิ่งปิงสูง อัตราโดนลดลงเฉพาะกับเป้าที่เคลื่อนที่ - สัญญาณว่าไม่ใช่: เป้าที่ยืนนิ่งก็พลาด: น่าจะเป็นปัญหา hitbox หรือการตรวจการชน ถ้าย้อนเวลาแล้วยังคลาด ให้ดูว่าไคลเอนต์แจ้งเวลา interpolation ให้เซิร์ฟเวอร์ผิดหรือไม่ - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization (Yahn W. Bernier, GDC 2001)](https://web.cs.wpi.edu/~claypool/courses/4513-B03/papers/games/bernier.pdf) · Valve · ถ้าไม่มี lag compensation ต้องยิงดักหน้าเท่ากับดีเลย์, lag compensation คือการที่เซิร์ฟเวอร์ย้อนเวลากลับเท่ากับดีเลย์และเวลา interpolation แล้วจึงตัดสิน - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · เซิร์ฟเวอร์ย้อนกลับไปยังสถานะเกมที่ผู้เล่นเห็นตอนยิงแล้วตัดสินการโดน, ไคลเอนต์ส่งเวลาของ simulation ที่ตัวเองเห็นไปด้วย - [Source SDK 2013: player_lagcompensation.cpp](https://raw.githubusercontent.com/ValveSoftware/source-sdk-2013/master/src/game/server/player_lagcompensation.cpp) · Valve · เวลาที่เอนจิน Source ย้อนกลับ = ดีเลย์ของเครือข่าย + เวลา interpolation #### sy-lagcomp-overreach · lag compensation มากเกินไป · Excessive lag compensation ถ้าย้อนเวลาให้ผู้โจมตีไกลเกินไป ฝ่ายที่ถูกโจมตีจะโดนทั้งที่หลบไปแล้ว - ทำไม → ผลคือ → บนหน้าจอ: เซิร์ฟเวอร์ย้อนเวลาไกลเพื่อผู้โจมตีที่ปิงสูง แล้วจึงตัดสิน → บนจอของฝ่ายที่ถูกโจมตี เขาเข้าที่กำบังไปแล้ว → “หลบหลังกำแพงแล้วยังโดน”, คนปิงสูงได้เปรียบ - อาการ: กดไม่ติด/โรลแบ็ค / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว, บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ตั้งเพดานการย้อนเวลา (เช่น 200–250 ms), ผู้โจมตีที่ปิงสูงกว่านั้นให้ย้อนได้ถึงเพดานเท่านั้น ส่วนที่เหลือให้ผู้โจมตียิงดักหน้าเอง - บนกราฟ: สูงเฉพาะบางกลุ่ม (เวลาที่ย้อนกลับต่อการโดนแต่ละครั้ง (แยกตามปิงของผู้โจมตี)) - จุดที่ต้องดู: บันทึกเวลาที่ย้อนกลับ, RTT ของผู้โจมตี และเวลาเซิร์ฟเวอร์ที่ฝ่ายถูกยิงเข้าที่กำบัง ของการโดนแต่ละครั้งลงใน log การตัดสินของเซิร์ฟเวอร์ ใน dev build ลองวาด hitbox ที่ย้อนเวลาแล้วบนจอ (เอนจิน Source ใช้ sv_showlagcompensation) - สัญญาณว่าใช่: การโดนในเคสที่แจ้งว่า “หลบหลังกำแพงแล้วยังโดน” กระจุกอยู่ที่ผู้โจมตีที่ย้อนเวลานาน และเวลาที่ย้อนเพิ่มตามปิงของผู้โจมตีโดยไม่มีเพดาน - สัญญาณว่าไม่ใช่: โดนหลังกำแพงแม้ในการโดนที่ย้อนเวลาสั้น: น่าจะเป็นปัญหา hitbox หรือการตรวจการชน ถ้าฝ่ายที่ถูกยิงปิงสูง เป็นเพราะการเคลื่อนที่ของคนนั้นไปถึงเซิร์ฟเวอร์ช้า - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: การตัดสินแบบย้อนเวลาเป็นแบบ “ยึดฝั่งผู้ยิง” และมีข้อเสนอข้อยกเว้นแบบ “ยึดฝั่งผู้ถูกยิง” คือไม่ย้อนเวลาถ้าฝ่ายที่ถูกยิงเข้าที่ปลอดภัยบนจอของตัวเองแล้ว - แหล่งอ้างอิง: - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · ถ้าการย้อนเวลาไม่มีเพดาน คนที่ดีเลย์ 500 ms จะยิงโดนได้แม้อีกฝ่ายเข้าที่กำบังไปแล้ว 0.5 วินาที จึงต้องตั้งเพดาน - [A Survey and Taxonomy of Latency Compensation Techniques for Network Computer Games (ACM Computing Surveys, 2022)](https://web.cs.wpi.edu/~claypool/papers/lag-taxonomy/paper.pdf) · ACM · ปรากฏการณ์ “โดนยิงทั้งที่หลบเข้ามุมแล้ว (shot around the corner)”, เพดานการย้อนเวลาของเกม FPS เชิงพาณิชย์, ข้อเสนอเทคนิคที่ไม่ย้อนเวลาถ้าฝ่ายที่ถูกยิงอยู่ในที่ปลอดภัยแล้ว - [Source SDK 2013: player_lagcompensation.cpp](https://raw.githubusercontent.com/ValveSoftware/source-sdk-2013/master/src/game/server/player_lagcompensation.cpp) · Valve · เพดานการย้อนเวลาของเอนจิน Source sv_maxunlag ค่าเริ่มต้น 1 วินาที (สูงสุด 1 วินาที), sv_showlagcompensation แสดง hitbox ที่ย้อนเวลาแล้วบนจอ #### sy-client-auth · client authoritative · Client-authoritative results ถ้าแต่ละคนตัดสินผลของตัวเอง จอของเราจะลื่น แต่ผลจะไม่ตรงกับจอของคนอื่นและโกงได้ง่าย - ทำไม → ผลคือ → บนหน้าจอ: ไคลเอนต์เป็นผู้กำหนดตำแหน่งและการโดน เซิร์ฟเวอร์แค่ส่งต่อ → สองคนต่างอ้างว่าตัวเองยิงโดนก่อน เซิร์ฟเวอร์ตรวจสอบไม่ได้ → อีกฝ่ายวาร์ปหรือเดินทะลุกำแพง, “ยิงโดนแล้วแต่ไม่เข้า” - อาการ: วาร์ป, กดไม่ติด/โรลแบ็ค / ปัจจัย: ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ผลที่สำคัญ (เช่น การโดน) ให้ตรวจเอง, การเคลื่อนที่ให้ตรวจความเร็วและระยะทาง ไคลเอนต์: เมื่อได้รับผลที่เซิร์ฟเวอร์ปฏิเสธหรือแก้มา ให้ย้อนกลับไปใช้ค่านั้น - บนกราฟ: สูงตลอดตั้งแต่แรก (จำนวนรายงานความเร็วการเคลื่อนที่ที่เป็นไปไม่ได้/รายงานการโดนที่ขัดกัน) - จุดที่ต้องดู: ให้เซิร์ฟเวอร์บันทึกตำแหน่งและการโดนที่ไคลเอนต์รายงานไว้ตามจริง แล้วคำนวณความเร็วการเคลื่อนที่จากรายงานตำแหน่งที่ต่อกัน นับรายงานที่เกินความเร็วสูงสุด และรายงานที่สองคนต่างบอกว่าตัวเองยิงโดนก่อน - สัญญาณว่าใช่: เซิร์ฟเวอร์ส่งรายงานต่อให้ไคลเอนต์อื่นโดยไม่ตรวจสอบ และรายงานความเร็วที่เป็นไปไม่ได้หรือการโดนที่ขัดกันเกิดขึ้นสม่ำเสมอโดยไม่เกี่ยวกับแพตช์หรือพื้นที่ - สัญญาณว่าไม่ใช่: เซิร์ฟเวอร์คำนวณหรือตรวจผลเองอยู่แล้ว: ไม่ใช่สาเหตุนี้ อาการวาร์ปในกรณีนั้นให้ดูแพ็กเก็ตหายหรือ interpolation buffer - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization (Yahn W. Bernier, GDC 2001)](https://web.cs.wpi.edu/~claypool/courses/4513-B03/papers/games/bernier.pdf) · Valve · การให้ไคลเอนต์รายงานผลทำได้เฉพาะเมื่อเชื่อใจไคลเอนต์ได้ จึงใช้ authoritative server เพราะกังวลเรื่องการโกง - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · โมเดล server authoritative: เซิร์ฟเวอร์ไม่เชื่อสถานะเกมที่ไคลเอนต์เห็นเด็ดขาด - [Distributed authority topologies (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/terms-concepts/distributed-authority.html) · Unity · ถ้าแบ่งอำนาจให้ไคลเอนต์ จะโกงได้ง่ายขึ้น และไม่มี simulation เดียวที่ควบคุมเอนทิตีทั้งหมด #### sy-lockstep · lockstep ต้องรอผู้เล่นที่ช้าที่สุด · Lockstep waits for the slowest peer ในโครงสร้างที่ทุกคนคำนวณเทิร์นเดียวกันไปพร้อมกัน ถ้าอินพุตของคนหนึ่งช้า ทุกคนต้องรอ - ทำไม → ผลคือ → บนหน้าจอ: แต่ละเทิร์นต้องได้อินพุตของผู้เล่นครบทุกคนจึงจะคำนวณได้ → อินพุตของคนหนึ่งมาถึงช้าเพราะจิตเตอร์หรือแพ็กเก็ตหาย → ทุกคนหยุดแวบพร้อมกัน ถ้าหนักจะขึ้นหน้าต่าง “กำลังรอผู้เล่น” - อาการ: ค้าง, กระตุก, อินพุตดีเลย์ / ปัจจัย: จิตเตอร์, แพ็กเก็ตหาย, การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ปรับ input delay ตามปิงอัตโนมัติ, ตัดเฉพาะคนที่ช้าออกชั่วคราวให้คนที่เหลือเล่นต่อโดยไม่ต้องรอ ไคลเอนต์: ใช้ input delay ตามที่กำหนด, ถ้าเป็น P2P ที่ไม่มีเซิร์ฟเวอร์ตัวกลาง ให้ไคลเอนต์ที่เป็นโฮสต์รับหน้าที่ปรับ input delay และจัดการคนที่ช้าด้วย - ตัวเลขที่ควรรู้: ถ้าตั้ง input delay สั้นกว่า “เวลาที่อินพุตไปถึงอีกฝ่าย + จิตเตอร์” จะหยุดชะงักบ่อยขึ้น เวลาที่ไปถึงคือครึ่งหนึ่งของปิงถ้ารับส่งกันโดยตรง และประมาณครึ่งหนึ่งของผลรวมปิงของทั้งสองคนถ้าผ่านเซิร์ฟเวอร์ตัวกลาง - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (เวลารอแต่ละเทิร์น, ดีเลย์การมาถึงของอินพุตแยกตามผู้เล่น) - จุดที่ต้องดู: บันทึกเวลาที่อินพุตของผู้เล่นแต่ละคนมาถึงและเวลาที่เทิร์นหยุดรอ ในทุกเทิร์น แล้วดูว่าเทิร์นที่หยุดนั้นรออินพุตของใคร ถ้ามีเซิร์ฟเวอร์ตัวกลาง ดูจากช่วงห่างการมาถึงของแพ็กเก็ตอินพุตแยกตามผู้เล่นใน packet capture ฝั่งเซิร์ฟเวอร์ได้ด้วย - สัญญาณว่าใช่: ทุกเทิร์นที่หยุด อินพุตของคนคนเดียวกันมาถึงช้ากว่า input delay และช่วงนั้นจิตเตอร์หรือแพ็กเก็ตหายของคนนั้นพุ่ง - สัญญาณว่าไม่ใช่: อินพุตมาครบตรงเวลาแต่ยังหยุด: น่าจะเป็นเวลาคำนวณของ PC ที่ช้าที่สุดหรือปัญหาการประมวลผลบนเซิร์ฟเวอร์ ถ้าไม่หยุดแต่ผลบนสองจอต่างกัน คือผลการคำนวณไม่ตรงกัน (desync) ให้ดู “การคำนวณเส้นทางไม่ตรงกันในการซิงก์คำสั่ง” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Deterministic Lockstep](https://gafferongames.com/post/deterministic_lockstep/) · Gaffer On Games · ต้องได้อินพุตของเฟรม n ครบจึงจะคำนวณได้ ถ้าช้าก็ต้องรอ ถ้าบัฟเฟอร์ playout delay ที่ดูดซับจิตเตอร์เล็กไป จะหยุดแวบ - [1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond](https://www.gamedeveloper.com/programming/1500-archers-on-a-28-8-network-programming-in-age-of-empires-and-beyond) · Game Developer · นัดให้คำสั่งทำงานในอีกสองเทิร์นข้างหน้า และปรับความยาวเทิร์นตามคอมพิวเตอร์ที่ช้าที่สุดและปิง (Speed Control) - [A Survey and Taxonomy of Latency Compensation Techniques for Network Computer Games (ACM Computing Surveys, 2022)](https://web.cs.wpi.edu/~claypool/papers/lag-taxonomy/paper.pdf) · ACM · ตั้ง input delay (incoming delay) เท่ากับดีเลย์ “A→เซิร์ฟเวอร์ + เซิร์ฟเวอร์→B” เพื่อให้ทุกคนใช้อินพุตในจังหวะเดียวกัน #### sy-rollback · rollback netcode คาดการณ์พลาด · Rollback misprediction แสดงผลไปก่อนโดยคาดการณ์อินพุตของอีกฝ่าย ถ้าผิดจะย้อนกลับไปคำนวณใหม่ ยิ่งปิงสูง ช่วงที่ต้องย้อนยิ่งยาว - ทำไม → ผลคือ → บนหน้าจอ: อีกฝ่ายเปลี่ยนอินพุต (ไม่ตรงกับที่คาดการณ์) → อินพุตจริงมาถึงช้าไปครึ่งหนึ่งของปิง จึงต้องย้อนกลับไปคำนวณใหม่เท่ากับช่วงนั้น → ท่าทางของอีกฝ่ายกระโดดข้ามไปหลายเฟรม หรือเปลี่ยนไปกะทันหัน - อาการ: วาร์ป / ปัจจัย: ความหน่วง, จิตเตอร์ - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ผสม input delay 1–3 เฟรมเพื่อลดช่วงที่ต้องย้อน, ตั้งเพดานการย้อน - ตัวเลขที่ควรรู้: ปิง 100 ms (ทางเดียว 50 ms) ที่ 60 fps จะย้อนประมาณ 3 เฟรม ถ้าตั้ง input delay 2 เฟรม จะลดเหลือ 1 เฟรม - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (จำนวนเฟรมที่ย้อน, RTT (ปิง)) - จุดที่ต้องดู: ให้ไคลเอนต์บันทึกจำนวนเฟรมที่ย้อน, RTT ในตอนนั้น, ค่า input delay ที่ตั้งไว้ และเวลาที่ใช้ย้อนและคำนวณใหม่ ทุกครั้งที่ย้อน - สัญญาณว่าใช่: จังหวะที่แจ้งว่าท่าทางอีกฝ่ายกระโดด จำนวนเฟรมที่ย้อนสูง และช่วงย้อนเฉลี่ยประมาณ (ดีเลย์ทางเดียว − input delay) ÷ เวลาต่อเฟรม และยิ่งปิงสูงยิ่งมาก - สัญญาณว่าไม่ใช่: ช่วงย้อนสั้นแต่ยังกระตุก: น่าจะเป็นปัญหาประสิทธิภาพที่การคำนวณใหม่ใช้เวลาเกินหนึ่งเฟรม ถ้าย้อนแล้วผลบนสองจอยังต่างกันต่อไป คือผลการคำนวณไม่ตรงกัน (desync) - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [GGPO Rollback Networking SDK](https://www.ggpo.net/) · GGPO · คาดการณ์อินพุตของอีกฝ่ายแล้วเดินเกมไปก่อน ถ้าอินพุตจริงต่างจากนั้น คำนวณใหม่ตั้งแต่จุดที่คลาดจนถึงปัจจุบัน - [8 Frames in 16ms: Rollback Networking in 'Mortal Kombat' and 'Injustice 2'](https://www.gdcvault.com/play/1025471/8-Frames-in-16ms-Rollback) · GDC · ใช้ rollback ตัด local input delay ของ lockstep ออก และย้อนคำนวณใหม่ได้สูงสุด 8 เฟรมภายใน 16 ms #### sy-no-timestamp · เล่นทันทีที่มาถึงโดยไม่มี timestamp · Events played on arrival (no timestamps) ถ้าไม่ติดเวลาที่เกิดให้อีเวนต์จากเซิร์ฟเวอร์ แล้วเล่นทันทีที่ได้รับ จังหวะการแสดงผลจะไม่สม่ำเสมอตามจิตเตอร์ของเครือข่าย - ทำไม → ผลคือ → บนหน้าจอ: รันอีเวนต์ “เริ่มโจมตี” และ “เล่นเอฟเฟกต์” ทันทีที่มาถึง → แต่ละแพ็กเก็ตมาถึงในเวลาต่างกัน ช่วงห่างจึงไม่สม่ำเสมอ → ท่าโจมตีต่อเนื่องเร็วบ้างช้าบ้าง, จังหวะแพตเทิร์นบอสไม่เหมือนเดิมทุกครั้ง - อาการ: กระตุก, กรอเร็ว / ปัจจัย: จิตเตอร์ - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: เล่นตามเวลาที่ติดมากับอีเวนต์ (นัดเวลาอีเวนต์, interpolation buffer) เซิร์ฟเวอร์: ติดเวลาที่เกิด (เวลาเซิร์ฟเวอร์) ให้อีเวนต์ก่อนส่ง - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (ช่วงห่างการเล่นอีเวนต์, ช่วงห่างการมาถึงของแพ็กเก็ต) - จุดที่ต้องดู: จับคู่เวลาที่เกิดอีเวนต์ใน log ของเซิร์ฟเวอร์กับเวลาที่มาถึงและเวลาที่เล่นใน log ของไคลเอนต์ด้วยหมายเลขอีเวนต์ แล้วเทียบช่วงห่าง ใน dev build ลองใส่จิตเตอร์ (ค่าจิตเตอร์ของ tc netem, ดีเลย์ต่ำสุด/สูงสุดใน network emulation ของ Unreal) เพื่อจำลองอาการ - สัญญาณว่าใช่: ช่วงห่างที่เกิดบนเซิร์ฟเวอร์คงที่ แต่ช่วงห่างการเล่นไม่สม่ำเสมอตามช่วงห่างการมาถึงทุกประการ - สัญญาณว่าไม่ใช่: ช่วงห่างการมาถึงสม่ำเสมอแต่การเล่นไม่สม่ำเสมอ: น่าจะเป็นปัญหาเฟรมฝั่งไคลเอนต์ (เฟรมไทม์พุ่ง) ถ้าช่วงห่างที่เกิดบนเซิร์ฟเวอร์แกว่งตั้งแต่ต้น น่าจะเป็นทิกเกินงบเวลา - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization (Yahn W. Bernier, GDC 2001)](https://web.cs.wpi.edu/~claypool/courses/4513-B03/papers/games/bernier.pdf) · Valve · ติดเวลาเซิร์ฟเวอร์ให้ทุกอัปเดต แล้ววาดตำแหน่ง ณ เวลาเป้าหมายซึ่งคือเวลาปัจจุบันลบเวลา interpolation (100 ms) - [Snapshot Interpolation](https://gafferongames.com/post/snapshot_interpolation/) · Gaffer On Games · ถ้าวาดสแนปช็อตที่ได้รับทันที จะกระตุกเพราะจิตเตอร์ ถ้าเก็บไว้ใน interpolation buffer สักครู่แล้วค่อยวาด จะลื่น - [NetworkTime and ticks (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/advanced-topics/networktime-ticks.html) · Unity · ตัวอย่างที่ใส่เวลาที่ส่งไว้ใน RPC ให้ฝั่งรับเล่นเอฟเฟกต์ตามเวลาเซิร์ฟเวอร์ - [tc-netem(8) — Linux manual page](https://man7.org/linux/man-pages/man8/tc-netem.8.html) · iproute2 · เครื่องมือทดสอบที่ใส่ดีเลย์/จิตเตอร์ (delay TIME JITTER) และการหาย (loss random PERCENT) ให้แพ็กเก็ตขาออก เพื่อจำลองเครือข่ายจริง - [Using Network Emulation in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/using-network-emulation-in-unreal-engine) · Epic Games · ทดสอบโดยใส่ดีเลย์ต่ำสุด/สูงสุดและอัตราแพ็กเก็ตหายให้เซิร์ฟเวอร์และไคลเอนต์, ในคอนโซลตั้งค่าแบบ NetEmulation.PktLag #### sy-double-tick · รอทิกซ้อนสองชั้น · Double tick quantization ถ้าเก็บคำขอรอจนถึงทิกถัดไปแล้วจึงประมวลผล และส่งผลลัพธ์ในทิกถัดจากนั้นอีก ช่วงห่างระหว่างทิกจะถูกบวกเพิ่มสองครั้ง - ทำไม → ผลคือ → บนหน้าจอ: คำขอที่ได้รับจะประมวลผลในทิกถัดไป → ผลการประมวลผลก็เก็บรวมไว้ส่งในทิกส่งถัดไป → ปิงของเน็ตต่ำ แต่การตอบสนองช้าคงที่ประมาณ 1.5 เท่าของช่วงห่างระหว่างทิก ถ้าเซิร์ฟเวอร์ 10 ทิก จะเฉลี่ย 0.15 วินาที แย่สุด 0.2 วินาที - อาการ: อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ส่งคำตอบทันทีในทิกที่ประมวลผล, เพิ่มทิกเรต, คำตอบที่สำคัญให้ส่งทันที - ตัวเลขที่ควรรู้: เซิร์ฟเวอร์ 10 ทิกมีหนึ่งทิกเท่ากับ 100 ms การรอทิกอย่างเดียวจึงเพิ่มเฉลี่ย 150 ms แย่สุด 200 ms ถ้ารอครั้งเดียว เฉลี่ยจะเป็น 50 ms - บนกราฟ: สูงตลอดตั้งแต่แรก (เวลาตั้งแต่คำขอมาถึงจนส่งคำตอบ) - จุดที่ต้องดู: ใน packet capture ฝั่งเซิร์ฟเวอร์ วัดช่วงห่างระหว่างเวลาที่แพ็กเก็ตคำขอมาถึงกับเวลาที่แพ็กเก็ตคำตอบออกไป ขณะที่บัญชีทดสอบทำแอ็กชันเดิมซ้ำหลายครั้ง (เช่น ใช้ไอเทม) ถ้ามี log ของเซิร์ฟเวอร์ ให้ดูเวลาที่คำขอมาถึง, หมายเลขทิกที่ประมวลผล และเวลาที่ส่งคำตอบ - สัญญาณว่าใช่: เวลาที่ใช้ภายในเซิร์ฟเวอร์เฉลี่ยประมาณ 1.5 เท่า สูงสุดประมาณ 2 เท่าของช่วงห่างระหว่างทิก และคงที่โดยไม่ขึ้นกับ RTT - สัญญาณว่าไม่ใช่: เวลาภายในเซิร์ฟเวอร์อยู่ราวครึ่งหนึ่งของช่วงห่างระหว่างทิก: รอทิกแค่ครั้งเดียว ถ้านานกว่าช่วงห่างระหว่างทิกแบบไม่สม่ำเสมอ ให้ดูทิกเกินงบเวลา - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · อินพุตที่มาถึงต้องรอจนถึงขอบทิกนานสุดหนึ่งทิก และใช้อีกหนึ่งเฟรมในการนำไปใช้และส่ง ยิ่งทิกเรตสูงยิ่งลดลง - [VALORANT's 128-Tick Servers](https://www.riotgames.com/en/news/valorants-128-tick-servers) · Riot Games · ดีเลย์ส่วนหนึ่งมาจากเครือข่าย อีกส่วนหนึ่งมาจากทิกเรตของเซิร์ฟเวอร์ - [NetworkTime and ticks (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/advanced-topics/networktime-ticks.html) · Unity · การเปลี่ยนแปลงของ NetworkVariable จะรอส่งรวมกันทุก network tick #### sy-strict-check · เซิร์ฟเวอร์ตรวจสอบเข้มงวดเกินไป · Over-strict server validation ถ้าเซิร์ฟเวอร์ตรวจความเร็วการเคลื่อนที่, คูลดาวน์ และระยะเข้มงวดเกินไป จะปฏิเสธแม้อินพุตปกติที่มาถึงรวมกันเพราะจิตเตอร์ - ทำไม → ผลคือ → บนหน้าจอ: เกณฑ์ที่เข้มงวด เช่น “ระยะที่เคลื่อนที่ได้ในหนึ่งทิก” หรือ “ยอมให้คูลดาวน์คลาดได้ 0 ms” → เมื่อคำสั่งสองคำสั่งมาถึงรวมกันในทิกเดียวเพราะจิตเตอร์ จะถูกตัดสินว่าผิดกฎ → โดนดีดกลับ, คูลดาวน์ครบแล้วแต่สกิลถูกปฏิเสธ - อาการ: ดีดกลับ, กดไม่ติด/โรลแบ็ค / ปัจจัย: จิตเตอร์ - ใครเจอ: เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ตรวจแบบโควตาสะสม (token bucket), เผื่อระยะไว้เท่ากับปิงและจิตเตอร์ - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (จำนวนครั้งที่เซิร์ฟเวอร์ปฏิเสธจากการตรวจสอบ/แก้ตำแหน่ง) - จุดที่ต้องดู: บันทึกสาเหตุ, จำนวนคำสั่งของผู้เล่นคนนั้นที่มาถึงในทิกนั้น และช่วงห่างการมาถึงจากคำสั่งก่อนหน้า ทุกครั้งที่ปฏิเสธหรือแก้ตำแหน่ง ลงใน log ของเซิร์ฟเวอร์ - สัญญาณว่าใช่: การปฏิเสธและการแก้ตำแหน่งกระจุกในจังหวะที่มีคำสั่งตั้งแต่ 2 คำสั่งมาถึงรวมกันในทิกเดียว แต่ปริมาณการเคลื่อนที่และจำนวนครั้งที่ใช้เมื่อรวมเป็นช่วงหลายวินาทียังอยู่ในกฎ - สัญญาณว่าไม่ใช่: รวมเป็นช่วงหลายวินาทีแล้วยังเกินกฎ: อาจเป็นการเคลื่อนที่เร็วเกินจริงหรือการโกง ถ้าการปฏิเสธกระจุกที่บาง ISP และช่วงหัวค่ำ น่าจะเป็น “false positive ของการตรวจสอบที่กระจุกกับผู้ใช้บาง ISP” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Source SDK 2013: player.cpp](https://raw.githubusercontent.com/ValveSoftware/source-sdk-2013/master/src/game/server/player.cpp) · Valve · ยอมรับคำสั่งที่มาถึงรวมกันด้วยงบการประมวลผลคำสั่งที่สะสมทุกทิก (สูงสุด sv_maxusrcmdprocessticks 24 ทิก) และมีคอมเมนต์ของนักพัฒนาว่าถ้ากันเข้มงวดกว่านี้ ผู้เล่นปกติก็เจออาการกระตุก - [RFC 2697: A Single Rate Three Color Marker](https://www.rfc-editor.org/rfc/rfc2697) · IETF · token bucket: ตัดสินด้วยอัตราเฉลี่ย (CIR) และขนาด burst ที่ยอมให้ในครั้งเดียว (CBS) #### sy-host · โครงสร้างแบบโฮสต์ (หัวห้อง) · Listen server / host advantage ถ้า PC ของผู้เล่นคนหนึ่งทำหน้าที่เป็นเซิร์ฟเวอร์ เน็ตและสเปก PC ของคนนั้นจะกำหนดฟีลการเล่นของทุกคน - ทำไม → ผลคือ → บนหน้าจอ: PC ของหัวห้องทำหน้าที่เป็นเซิร์ฟเวอร์ (P2P, listen server) → ถ้าเน็ตหรือ PC ของหัวห้องช้า จะส่งผลไปถึงทุกคน ส่วนหัวห้องปิง 0 → หัวห้องได้เปรียบคนเดียว ถ้าหัวห้องออก ทุกคนค้างหรือหลุด - อาการ: กระตุก, ค้าง, หลุด / ปัจจัย: ความหน่วง, การหยุดชะงัก - ใครเจอ: บางจุด/บางแชนแนล / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม), อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: เปลี่ยนไปใช้ dedicated server ที่รับหน้าที่ตัดสินผล ระหว่างนั้นให้เลือกคนที่เน็ตและ PC ดีเป็นหัวห้องตอนจับคู่ ไคลเอนต์: รองรับการย้ายโฮสต์ (host migration), ตอนจับคู่ให้วัดปิงกับผู้เล่นคนอื่น ความเร็วอัปโหลด และสเปก PC แล้วส่งไป - งานฝั่งทีมอินฟรา: เตรียมเครื่องเซิร์ฟเวอร์/อินสแตนซ์สำหรับ dedicated server, วางไว้ใกล้พื้นที่ที่มีผู้เล่นมาก - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนการแจ้งแลค/การหลุดแยกตามหัวห้อง) - จุดที่ต้องดู: บันทึกความเร็วอัปโหลดของหัวห้อง (โฮสต์), RTT ระหว่างผู้เล่นแต่ละคนกับหัวห้อง, เฟรมไทม์ของ PC หัวห้อง และเวลาที่หัวห้องออก ลงใน log ของแมตช์ แล้วจัดกลุ่มการแจ้งแลคและการหลุดตามหัวห้อง ผู้เล่นเองก็ตรวจได้โดยเล่นใหม่กับคนกลุ่มเดิมแต่เปลี่ยนแค่หัวห้อง - สัญญาณว่าใช่: แลคและการหลุดกระจุกอยู่ในห้องของหัวห้องบางคน เมื่อความเร็วอัปโหลดของหัวห้องคนนั้นต่ำหรือเฟรมไทม์ยาว ผู้เล่นทุกคนแย่ลงพร้อมกัน และถ้าไม่มีการย้ายโฮสต์ ทุกคนหลุดทันทีที่หัวห้องออก - สัญญาณว่าไม่ใช่: แย่เฉพาะผู้เล่นในพื้นที่เดียวกันโดยไม่เกี่ยวกับหัวห้อง: น่าจะเป็นปัญหาเน็ตหรือเส้นทาง ถ้าเป็นโครงสร้าง dedicated server ไม่ใช่สาเหตุนี้ - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Networking Overview for Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/networking-overview-for-unreal-engine) · Epic Games · โฮสต์ของ listen server ได้เปรียบกว่าไคลเอนต์อื่น และรับทั้งงานเซิร์ฟเวอร์และการเรนเดอร์จึงมีโหลดสูง - [Distributed authority topologies (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/terms-concepts/distributed-authority.html) · Unity · เมื่อเจ้าของเซสชันออก จะเลือกเจ้าของใหม่จากไคลเอนต์ที่เหลืออัตโนมัติ - [A Survey and Taxonomy of Latency Compensation Techniques for Network Computer Games (ACM Computing Surveys, 2022)](https://web.cs.wpi.edu/~claypool/papers/lag-taxonomy/paper.pdf) · ACM · เกม P2P ก็มองเป็นโครงสร้าง client-server ที่โฮสต์ทำหน้าที่เซิร์ฟเวอร์ควบคู่ไปด้วยได้ #### sy-optimistic-reject · แสดงผลล่วงหน้าแล้วเซิร์ฟเวอร์ปฏิเสธ · Client-side feedback rejected by server ถ้าการโจมตีหรือสกิลที่จอเราแสดงไปก่อนแล้วถูกเซิร์ฟเวอร์ปฏิเสธทีหลัง ผลที่เห็นกับตาจะกลายเป็นเหมือนไม่เคยเกิดขึ้น - ทำไม → ผลคือ → บนหน้าจอ: เล่นเอฟเฟกต์การโจมตีและท่าสกิลก่อนเซิร์ฟเวอร์ยืนยัน (การแสดงผลล่วงหน้า) → เซิร์ฟเวอร์ตรวจระยะ, ตำแหน่งเป้า, คูลดาวน์ และทรัพยากรอีกครั้งแล้วปฏิเสธ → เลือดกระเด็นแต่ไม่มีดาเมจ, ท่าสกิลออกแต่ไม่มีผล, มีแต่คูลดาวน์ที่เดิน - อาการ: กดไม่ติด/โรลแบ็ค, ดีดกลับ / ปัจจัย: ความหน่วง - ใครเจอ: เราคนเดียว, เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: แสดงผลจากเซิร์ฟเวอร์เฉพาะส่วนที่ต้องยืนยัน เช่น ตัวเลขดาเมจ, การตาย และของรางวัล, ตรวจสาเหตุการปฏิเสธที่พบบ่อยไว้ก่อน, ถ้าถูกปฏิเสธให้คืนคูลดาวน์และทรัพยากรแล้วแสดงเหตุผล เซิร์ฟเวอร์: เผื่อระยะในการตรวจระยะและตำแหน่งเป้าไว้เท่ากับปิง, ใส่สาเหตุในคำตอบปฏิเสธ, เก็บอัตราการปฏิเสธแยกตามสกิลเป็นเมตริก - ตัวเลขที่ควรรู้: คำตอบปฏิเสธมาถึงหลังกดช้าไปเท่ากับปิง + เวลารอทิก ถ้าปิง 150 ms จะ “นึกว่าโดนแล้ว” อยู่ประมาณ 0.2 วินาที - บนกราฟ: สูงเฉพาะบางกลุ่ม (อัตราที่เซิร์ฟเวอร์ปฏิเสธแยกตามสกิล (แยกตามช่วงปิง)) - จุดที่ต้องดู: เก็บอัตราการปฏิเสธและสาเหตุ (ระยะ, ตำแหน่งเป้า, คูลดาวน์, ทรัพยากร) แยกตามสกิลที่เซิร์ฟเวอร์ แล้วแบ่งตามช่วง RTT ของผู้เล่น ฝั่งไคลเอนต์ให้บันทึกจำนวนครั้งที่การกระทำที่แสดงผลล่วงหน้าถูกปฏิเสธ - สัญญาณว่าใช่: การปฏิเสธกระจุกที่บางสกิลและสาเหตุเรื่องระยะหรือตำแหน่งเป้า และยิ่งปิงสูง อัตราการปฏิเสธยิ่งสูง - สัญญาณว่าไม่ใช่: สาเหตุการปฏิเสธเป็นคูลดาวน์หรือทรัพยากรและไม่เกี่ยวกับปิง: ให้ดูว่าค่าข้อมูล (คูลดาวน์, ค่าใช้จ่าย) ของไคลเอนต์กับเซิร์ฟเวอร์ต่างกันหรือไม่ ถ้าไม่มีการปฏิเสธแต่การแสดงผลเริ่มหลังเซิร์ฟเวอร์ตอบเท่านั้น น่าจะเป็น “แสดงผลหลังเซิร์ฟเวอร์ตอบเท่านั้น (แบบ request-response)” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: การแสดงผลล่วงหน้าเป็นวิธีที่ดีที่สุดในการกลบปิง แต่ยิ่งข้อมูลที่ไคลเอนต์กับเซิร์ฟเวอร์ใช้ตัดสิน (ตำแหน่งอีกฝ่าย, ทรัพยากรที่เหลือ) ต่างกันมาก ก็ยิ่งถูกปฏิเสธบ่อย ถ้าเก็บอัตราการปฏิเสธแยกตามสกิลเป็นเมตริกไว้ จะหาจุดที่การตัดสินคลาดกันได้ง่าย - แหล่งอ้างอิง: - [Using Gameplay Abilities in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/using-gameplay-abilities-in-unreal-engine) · Epic Games · ability แบบ Local Predicted ทำงานทันทีบนไคลเอนต์ แต่เซิร์ฟเวอร์เป็นผู้ตัดสินขั้นสุดท้ายและกลับผลได้ - [Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization (Yahn W. Bernier, GDC 2001)](https://web.cs.wpi.edu/~claypool/courses/4513-B03/papers/games/bernier.pdf) · Valve · คาดการณ์การยิงอาวุธบนไคลเอนต์แล้วเล่นเอฟเฟกต์ไปก่อน จากนั้นแก้ความคลาดเคลื่อนของการคาดการณ์ด้วยผลจากเซิร์ฟเวอร์ #### sy-path-mismatch · การคำนวณเส้นทางไม่ตรงกันในการซิงก์คำสั่ง · Command sync with divergent pathing ถ้ารับส่งแค่ “ให้ไปที่นี่” แล้วแต่ละฝั่งคำนวณเส้นทางเอง เมื่อการคำนวณต่างกันแม้เพียงเล็กน้อย ตัวละครหรือมอนสเตอร์จะเดินไปคนละเส้นทางแล้วถูกดึงกลับมาที่ตำแหน่งที่ถูกต้อง - ทำไม → ผลคือ → บนหน้าจอ: การเดินแบบคลิกและการไล่ตามของมอนสเตอร์ ส่งแค่จุดหมาย และไคลเอนต์คำนวณเส้นทางแยกเอง → ข้อมูลภูมิประเทศต่างกัน, การชนกับตัวละครอื่น และลำดับการคำนวณที่ต่างกัน ทำให้เดินคนละเส้นทางกับเซิร์ฟเวอร์ → มอนสเตอร์เดินทะลุกำแพงแล้ววืดไปอยู่อีกที่, ตัวละครที่คลิกเดินเลี้ยวเหมือนไถลไป - อาการ: วาร์ป, ดีดกลับ / ปัจจัย: ความหน่วง - ใครเจอ: บางจุด/บางแชนแนล, เราคนเดียว / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ส่งจุดกลางทาง (waypoint) ของเส้นทางไปด้วย, ซิงก์ตำแหน่งเป็นระยะ ไคลเอนต์: ค่อย ๆ ปรับส่วนที่คลาดให้เข้าที่อย่างนุ่มนวล, ใช้ข้อมูลภูมิประเทศชุดเดียวกับเซิร์ฟเวอร์ - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (จำนวนครั้งและระยะการแก้ตำแหน่งแยกตามเอนทิตี) - จุดที่ต้องดู: บันทึกความต่างระหว่างตำแหน่งที่เซิร์ฟเวอร์ส่งมากับตำแหน่งที่ไคลเอนต์คำนวณ ของแต่ละเอนทิตี แล้วปักพิกัดที่เกิดการแก้ตำแหน่งลงบนแผนที่ ถ้าสรุปผลเส้นทางหรือตำแหน่งของทั้งสองฝั่งเป็น checksum แล้วเทียบเป็นระยะ จะหาจังหวะที่เริ่มคลาดได้ - สัญญาณว่าใช่: การแก้ตำแหน่งกระจุกอยู่ที่ภูมิประเทศบางแบบ (ขอบต่างระดับ, ทางแคบ, ทางลาด) หรือที่ที่คนแน่น และเกิดซ้ำที่จุดเดิมแม้กับผู้เล่นที่เมตริกเครือข่ายปกติ - สัญญาณว่าไม่ใช่: แก้ตำแหน่งเฉพาะจังหวะที่แพ็กเก็ตหายหรือจิตเตอร์พุ่งโดยไม่เกี่ยวกับสถานที่: น่าจะเป็นปัญหาเน็ต ถ้ามอนสเตอร์ตัวเดียวกระโดดบนจอของหลายคนพร้อมกัน ให้ดูว่าสิทธิ์ควบคุมมอนสเตอร์อยู่ที่ไคลเอนต์ที่ช้าหรือไม่ - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: วิธีนี้เป็นเหตุผลหนึ่งที่เกมคลิกเดินและเกม tab target ไม่ค่อยไวต่อปิง แต่ไม่มีอะไรรับประกันว่าผลทั้งสองฝั่งจะตรงกัน จึงจำเป็นต้องมีกลไกปรับตำแหน่งให้ตรงกันเป็นครั้งคราว การคำนวณแบบ floating point อาจให้ผลต่างกันเล็กน้อยตามชนิด CPU, คอมไพเลอร์ และการตั้งค่า optimization ของคอมไพเลอร์ (รวมถึงความต่างระหว่าง debug build กับ release build) ในโครงสร้างที่รับส่งแค่อินพุตและถือว่าผลการคำนวณทั้งสองฝั่งเหมือนกันทุกประการ อย่าง lockstep และ rollback ความต่างเล็ก ๆ นี้อาจสะสมจนสถานะเกมบนสองจอแยกออกจากกัน (desync) - แหล่งอ้างอิง: - [Deterministic Lockstep](https://gafferongames.com/post/deterministic_lockstep/) · Gaffer On Games · แม้จะ deterministic บนเครื่องเดียวกัน ถ้าคอมไพเลอร์, OS หรือ CPU ต่างกัน ผล floating point ก็อาจต่างกัน - [State Synchronization](https://gafferongames.com/post/state_synchronization/) · Gaffer On Games · ถ้าส่งสถานะไปพร้อมอินพุต ก็ปรับทั้งสองฝั่งให้ตรงกันได้โดยไม่ต้อง deterministic สมบูรณ์ - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · เมื่อแพ็กเก็ตหาย หรือตัวละครสองตัวพยายามไปที่จุดเดียวกัน simulation ของเซิร์ฟเวอร์กับไคลเอนต์จะคลาดกันจนต้องแก้ - [1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond](https://www.gamedeveloper.com/programming/1500-archers-on-a-28-8-network-programming-in-age-of-empires-and-beyond) · Game Developer · ความต่างเล็กมากขยายใหญ่ขึ้นตามเวลา จนเส้นทางของคนงาน (worker) ค่อย ๆ คลาด, เทียบโลก, เอนทิตี และ pathfinding ด้วย checksum เพื่อหาการคลาด (out-of-sync) - [Floating Point Determinism](https://gafferongames.com/post/floating_point_determinism/) · Gaffer On Games · โค้ด floating point เดียวกันอาจให้ผลต่างกันตามคอมไพเลอร์, สถาปัตยกรรม CPU และ debug/release build, มีกรณีที่ CPU ของ AMD และ Intel ให้ค่าฟังก์ชัน transcendental ต่างกันเล็กน้อย - [/fp (Specify floating-point behavior)](https://learn.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior) · Microsoft · /fp:fast อาจสลับหรือรวมลำดับการคำนวณ floating point จนผลต่างจากการตั้งค่า /fp แบบอื่น และการคำนวณที่รวมด้วย FMA ก็อาจต่างจากการคูณแล้วบวกแยกกัน #### sy-low-send-rate · อัตราส่งสแนปช็อตต่ำ · Low snapshot / update rate ถ้าเซิร์ฟเวอร์ส่งอัปเดตตำแหน่ง (สแนปช็อต) แค่ไม่กี่ครั้งใน 1 วินาที ต้องตั้ง interpolation buffer ให้ยาวตามไปด้วย จึงเห็นตัวละครอื่นเป็นภาพในอดีตที่ไกลขึ้น - ทำไม → ผลคือ → บนหน้าจอ: ส่งอัปเดตตำแหน่งแค่ 5–10 ครั้งใน 1 วินาทีเพื่อประหยัดปริมาณการส่ง → ถ้าจะวาดให้ลื่น ต้องตั้งบัฟเฟอร์เป็น 2 เท่าของช่วงห่างแพ็กเก็ต (200–400 ms) ถ้าตั้งสั้น พลาดแพ็กเก็ตเดียวตัวละครก็หยุดนิ่ง → เห็นอีกฝ่ายเปลี่ยนทิศช้าและไม่ตรงกับการตัดสินผล ถ้าบัฟเฟอร์สั้นจะกระตุก และวาร์ปเมื่อแพ็กเก็ตหาย - อาการ: กระตุก, วาร์ป, กดไม่ติด/โรลแบ็ค / ปัจจัย: ความหน่วง, แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: เป้าหมายที่อยู่ใกล้หรือกำลังต่อสู้ให้ส่งบ่อย ที่อยู่ไกลให้ส่งห่าง, ส่งเฉพาะส่วนที่เปลี่ยน (delta compression) เพื่อลดขนาดต่อครั้งแล้วเพิ่มความถี่ ไคลเอนต์: ปรับความยาว interpolation buffer ตามช่วงห่างแพ็กเก็ตอัตโนมัติ - ตัวเลขที่ควรรู้: ถ้า 10 ครั้งใน 1 วินาที ช่วงห่างแพ็กเก็ตคือ 100 ms บัฟเฟอร์ 200 ms บวกดีเลย์ทางเดียว 75 ms ของปิง 150 ms จะเห็นอีกฝ่ายย้อนหลังไปประมาณ 0.3 วินาที - บนกราฟ: สูงตลอดตั้งแต่แรก (ช่วงห่างการมาถึงของแพ็กเก็ตแยกตามไคลเอนต์, ความยาว interpolation buffer) - จุดที่ต้องดู: ใน packet capture ฝั่งเซิร์ฟเวอร์ กรองเฉพาะ flow ที่ไปหาผู้เล่นคนหนึ่ง แล้วดูจำนวนแพ็กเก็ตต่อวินาทีและช่วงห่างด้วย I/O Graphs ของ Wireshark ถ้ามี log ฝั่งเกม ให้ดูช่วงห่างการอัปเดตแยกตามเอนทิตีควบคู่กับระยะเผื่อของ interpolation buffer บนไคลเอนต์ (เวลาที่เหลือจนสแนปช็อตถัดไปมาถึง) - สัญญาณว่าใช่: อัปเดตตำแหน่งห่างตลอดที่ 5–10 ครั้งต่อวินาที (ช่วงห่าง 100–200 ms) และตั้ง interpolation buffer ไว้เกิน 200 ms หรือระยะเผื่อของบัฟเฟอร์เหลือ 0 บ่อย - สัญญาณว่าไม่ใช่: ส่งอัปเดตถี่แล้วแต่ช่วงห่างการมาถึงแกว่ง: น่าจะเป็นจิตเตอร์หรือแพ็กเก็ตหาย ถ้าตอนคนแน่นได้รับอัปเดตห่างเฉพาะเอนทิตีที่อยู่ไกล น่าจะเป็น “งบการส่ง/ลำดับความสำคัญต่อการเชื่อมต่อ” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization (Yahn W. Bernier, GDC 2001)](https://web.cs.wpi.edu/~claypool/courses/4513-B03/papers/games/bernier.pdf) · Valve · อัปเดต 10 ครั้งต่อวินาทีกับ interpolation 200 ms ทนการหายได้หนึ่งครั้ง ค่าเริ่มต้นของ Half-Life คือ 20 ครั้งต่อวินาที และ interpolation 100 ms - [Snapshot Interpolation](https://gafferongames.com/post/snapshot_interpolation/) · Gaffer On Games · ถ้า 10 แพ็กเก็ตต่อวินาที ต้องใช้ดีเลย์ 350 ms จึงจะทนการหายติดกันสองแพ็กเก็ตได้ ถ้า 30 แพ็กเก็ตต่อวินาที จะลดเหลือ 150 ms - [State Synchronization](https://gafferongames.com/post/state_synchronization/) · Gaffer On Games · สะสมค่าลำดับความสำคัญเพื่อส่งเอนทิตีที่สำคัญบ่อยขึ้น และวนส่งที่เหลือภายในขีดจำกัดแบนด์วิดท์ - [8.8. The “I/O Graphs” Window](https://www.wireshark.org/docs/wsug_html_chunked/ChStatIOGraphs.html) · Wireshark · วาดกราฟจำนวนแพ็กเก็ตและไบต์ที่ตรงกับ display filter แยกตามช่วงเวลา ### ปัญหาที่เกิดกับบางคนเท่านั้น (24 สาเหตุ) #### pt-slow-burst · คนเน็ตช้าขยับแบบกรอเร็วบนจอคนอื่น · Laggy player seen by others (bursty inputs) อินพุตของคนที่เน็ตไม่ดีจะไปถึงเซิร์ฟเวอร์แบบไม่สม่ำเสมอและมาเป็นกลุ่ม ถ้าเซิร์ฟเวอร์ใช้คำสั่งตามที่ได้รับในแต่ละทิก คนอื่นจะเห็นตัวละครนั้นหยุดแวบแล้วเดินหลายก้าวในทีเดียว - ทำไม → ผลคือ → บนหน้าจอ: คำสั่งเคลื่อนที่ของคนที่ช้า บางทิกมาถึง 0 คำสั่ง บางทิกมา 2–3 คำสั่ง → เซิร์ฟเวอร์ใช้คำสั่งทั้งหมดในทิกที่ได้รับ ตำแหน่งของตัวละครนั้นจึงเปลี่ยนเป็นขั้นบันได → บนจอคนอื่น ตัวละครนั้นตัวเดียวหยุดแวบแล้วขยับรวดเดียวแบบกรอเร็ว ที่เหลือปกติ - อาการ: กรอเร็ว, วาร์ป / ปัจจัย: จิตเตอร์, แพ็กเก็ตหาย - ใครเจอ: เห็นตัวละครบางตัวผิดปกติ, บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตลอดเวลา, ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: ใช้บัฟเฟอร์อินพุตของผู้เล่นแต่ละคนกระจายคำสั่งไปใช้อย่างสม่ำเสมอ, ดู sequence number ของอินพุตแล้วใช้ตามช่วงห่างเดิม, การเพิ่มแค่ interpolation buffer บนจอคนอื่นไม่พอ (เพราะประวัติตำแหน่งบนเซิร์ฟเวอร์เองเป็นขั้นบันไดอยู่แล้ว) - งานฝั่งภายนอก: แนะนำผู้เล่นที่ช้าให้ใช้สาย LAN และตรวจ Wi-Fi/เราเตอร์ - ตัวเลขที่ควรรู้: ถ้าจิตเตอร์ 80 ms บนเซิร์ฟเวอร์ 20 ทิก (50 ms) จำนวนคำสั่งต่อทิกจะแกว่งระหว่าง 0–3 คำสั่ง - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนคำสั่งที่ใช้ต่อทิกแยกตามผู้เล่น, จิตเตอร์แยกตามผู้เล่น) - จุดที่ต้องดู: ใน packet capture ฝั่งเซิร์ฟเวอร์ กรองเฉพาะแพ็กเก็ตที่ผู้เล่นที่ถูกแจ้งส่งมา แล้วนับว่ามาถึงกี่แพ็กเก็ตในแต่ละช่วงทิก (เช่น 50 ms) และเทียบกับผู้เล่นคนอื่น ถ้ามี log ของเซิร์ฟเวอร์ ให้ดูจำนวนคำสั่งเคลื่อนที่ที่ใช้ในแต่ละทิกและ sequence number ของอินพุตแยกตามผู้เล่น - สัญญาณว่าใช่: เฉพาะแพ็กเก็ตของผู้เล่นที่ถูกแจ้งที่มาถึงเป็นกลุ่มสลับระหว่าง 0 กับ 2–3 แพ็กเก็ตต่อทิก และจิตเตอร์/แพ็กเก็ตหายของผู้เล่นคนนั้นสูง ขณะที่แพ็กเก็ตของผู้เล่นคนอื่นมาสม่ำเสมอ ถ้าผู้เล่นคนนั้นเปลี่ยนไปใช้สาย LAN อาการจะลดลง - สัญญาณว่าไม่ใช่: หลายตัวละครขยับรวดเดียวพร้อมกัน: น่าจะเป็นทิกของเซิร์ฟเวอร์ล่าช้าหรือเน็ตของฝั่งคนดู ถ้าการมาถึงและการใช้คำสั่งสม่ำเสมอแต่ตัวละครนั้นยังดูกระโดด น่าจะเป็นปัญหา interpolation หรือการแสดงผลของฝั่งคนดู - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ในโครงสร้าง server authoritative นี่คือพฤติกรรมปกติ แลคของคนที่ช้าคนเดียวจะแสดงให้คนอื่นเห็นแค่เป็น “คนนั้นขยับแปลก ๆ” และไม่กระทบการควบคุมของคนอื่นหรือการเคลื่อนที่ของมอนสเตอร์ แต่สิ่งที่ต้องโต้ตอบกับคนนั้นโดยตรง (เทรด, กิมมิกปาร์ตี้, การตัดสินผล PvP) จะช้าตามไปด้วย - แหล่งอ้างอิง: - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · เซิร์ฟเวอร์ใส่อินพุตที่มาถึงลงในคิวการเคลื่อนที่ของผู้เล่นแต่ละคนตามลำดับทิก และถ้าคิวว่างจะเติมด้วยการคาดการณ์ การแก้ตำแหน่งจะเห็นเฉพาะผู้เล่นคนนั้น คนอื่นเห็นลื่นไหล - [State Synchronization](https://gafferongames.com/post/state_synchronization/) · Gaffer On Games · แม้แพ็กเก็ตที่ส่ง 60 ครั้งต่อวินาที ก็มาถึงเป็นกลุ่ม เช่น เฟรมหนึ่ง 2 แพ็กเก็ต เฟรมถัดไป 0 แพ็กเก็ต - [Source SDK 2013: player.cpp](https://raw.githubusercontent.com/ValveSoftware/source-sdk-2013/master/src/game/server/player.cpp) · Valve · กระจายคำสั่งที่มาถึงเป็นกลุ่มไปประมวลผลตามทิกของเซิร์ฟเวอร์ (meter out) #### pt-event-server · อาการกรอเร็วจากเซิร์ฟเวอร์ที่ประมวลผลทันทีที่ได้รับ · Event-driven processing of bursty inputs บนเซิร์ฟเวอร์ที่ประมวลผลและแจ้งผลทันทีที่แพ็กเก็ตมาถึง การกระทำของคนที่ช้าซึ่งมาถึงเป็นกลุ่มจะถูกรันทันทีต่อเนื่องกัน - ทำไม → ผลคือ → บนหน้าจอ: คำขอสกิลและการเคลื่อนที่ของคนที่ช้ามาถึงเป็นกลุ่ม → เซิร์ฟเวอร์รันตามลำดับทันทีที่ได้รับ และแจ้งทุกคนทันที → คนอื่นเห็นคนนั้นใช้สกิลหลายสกิลในพริบตาเดียว หรือขยับเหมือนกดกรอ - อาการ: กรอเร็ว / ปัจจัย: จิตเตอร์ - ใครเจอ: เห็นตัวละครบางตัวผิดปกติ / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง, ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: รันตามช่วงห่างของเวลาอินพุตที่ติดมากับการกระทำ (ยอมรับเวลานั้นเฉพาะเมื่ออยู่ในช่วงที่อนุญาต) หรือรับการกระทำที่มาเป็นกลุ่มไว้แล้วรันทีละอย่างโดยเว้นช่วงห่างขั้นต่ำ (global cooldown), อย่าตรวจคูลดาวน์จากเวลาที่มาถึงอย่างเดียว (อินพุตปกติจะกดไม่ติด) ไคลเอนต์: ติดเวลาอินพุตให้การกระทำก่อนส่ง - บนกราฟ: สูงเฉพาะบางกลุ่ม (ช่วงห่างการรันการกระทำแยกตามผู้เล่น) - จุดที่ต้องดู: บันทึกเวลาที่การกระทำของผู้เล่นแต่ละคนมาถึง, เวลาที่รัน และเวลาอินพุตที่ไคลเอนต์ติดมา (ถ้ามี) ลงใน log ของเซิร์ฟเวอร์ แล้วเทียบช่วงห่างการรันกับช่วงห่างอินพุต ดูช่วงห่างการมาถึงของแพ็กเก็ตจากผู้เล่นคนนั้นใน packet capture ฝั่งเซิร์ฟเวอร์ประกอบด้วย - สัญญาณว่าใช่: ช่วงห่างอินพุตปกติ แต่ช่วงห่างการมาถึงและการรันบนเซิร์ฟเวอร์อัดกันเหลือไม่กี่ ms และจังหวะที่อัดกันตรงกับเวลาที่คนอื่นแจ้งว่าเห็นอาการกรอเร็ว - สัญญาณว่าไม่ใช่: ช่วงห่างของเวลาอินพุตอัดกันตั้งแต่ต้น: น่าจะเป็นที่ไคลเอนต์หรือมาโคร ถ้าช่วงห่างการรันบนเซิร์ฟเวอร์สม่ำเสมอแต่ดูอัดกันเฉพาะบนจอคนอื่น น่าจะเป็นเน็ตของคนดู - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Understanding Networked Movement in the Character Movement Component for Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/understanding-networked-movement-in-the-character-movement-component-for-unreal-engine) · Epic Games · เซิร์ฟเวอร์คำนวณการเคลื่อนที่ทุกครั้งที่ได้รับ ServerMove และกำหนดช่วงเวลาจากผลต่างของ timestamp กับการเคลื่อนที่ก่อนหน้า ถ้าต่างจากเวลาเซิร์ฟเวอร์มากเกินไปจะทิ้งการเคลื่อนที่นั้น - [Deterministic Lockstep](https://gafferongames.com/post/deterministic_lockstep/) · Gaffer On Games · ถ้าใช้อินพุตทันทีที่มาถึง แม้ส่งที่ 60 Hz ช่วงห่างก็ไม่สม่ำเสมอ ผลจึงไม่สม่ำเสมอตาม #### pt-input-buffer · ขนาดบัฟเฟอร์อินพุตของผู้เล่นแต่ละคน · Per-player server input buffer (jitter buffer) ถ้าเซิร์ฟเวอร์เก็บอินพุตของแต่ละคนไว้เล็กน้อยแล้วดึงมาใช้ทิกละหนึ่งอัน คนอื่นจะเห็นลื่นไหล แต่จังหวะที่การกระทำของเจ้าตัวถูกยืนยันบนเซิร์ฟเวอร์จะช้าลงตามไปด้วย - ทำไม → ผลคือ → บนหน้าจอ: เซิร์ฟเวอร์เก็บอินพุตของคนที่ช้าไว้ในบัฟเฟอร์ แล้วใช้ทิกละหนึ่งอัน → ถ้าบัฟเฟอร์เล็กจะว่างบ่อย ตัวละครนั้นยืนนิ่งอยู่กับที่หรือเซิร์ฟเวอร์เดาจากอินพุตล่าสุดแล้วขยับให้ ถ้าบัฟเฟอร์ใหญ่ อินพุตของเจ้าตัวจะถูกยืนยันช้า → ถ้าเล็ก คนอื่นเห็นหยุดแวบ ถ้าใหญ่ ผลสกิลของเจ้าตัวออกช้า (อินพุตดีเลย์) - อาการ: กระตุก, อินพุตดีเลย์ / ปัจจัย: จิตเตอร์ - ใครเจอ: เห็นตัวละครบางตัวผิดปกติ, เราคนเดียว / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ปรับขนาดบัฟเฟอร์ของแต่ละคนอัตโนมัติตามสภาพเน็ต, ถ้าอินพุตสะสมให้ดึงครั้งละสองอันเพื่อไล่ให้ทัน, สั่งไคลเอนต์ของคนที่บัฟเฟอร์ว่างบ่อยให้ส่งอินพุตเร็วขึ้น ไคลเอนต์: ส่งอินพุตให้เร็วขึ้นอีกเล็กน้อยตามคำสั่งของเซิร์ฟเวอร์ (ปรับเวลาฝั่งไคลเอนต์) - ตัวเลขที่ควรรู้: แต่ละเกมต่างกัน แต่มักเท่ากับ 1–3 ทิก VALORANT บนเซิร์ฟเวอร์ 128 ทิกรักษาบัฟเฟอร์ฝั่งเซิร์ฟเวอร์ให้สั้นกว่านั้น คือเฉลี่ยครึ่งเฟรม (ประมาณ 4 ms) ส่วนแบบ adaptive ที่เพิ่มบัฟเฟอร์เฉพาะคนที่จิตเตอร์สูงพบได้ทั่วไป - บนกราฟ: สูงเฉพาะบางกลุ่ม (ความยาว/จำนวนครั้งที่ว่างของบัฟเฟอร์อินพุตแยกตามผู้เล่น) - จุดที่ต้องดู: ให้เซิร์ฟเวอร์บันทึกจำนวนอินพุตที่เหลือในบัฟเฟอร์ทุกทิก, จำนวนครั้งที่บัฟเฟอร์ว่างจนต้องเดาจากอินพุตล่าสุดมาเติม และเวลาตั้งแต่อินพุตมาถึงจนถูกใช้ แยกตามผู้เล่น - สัญญาณว่าใช่: คนที่บัฟเฟอร์เล็กมีจำนวนครั้งที่ว่างมาก และจังหวะนั้นหยุดสั้น ๆ บนจอคนอื่น ส่วนคนที่บัฟเฟอร์ใหญ่ เวลาตั้งแต่อินพุตจนถูกใช้เพิ่มขึ้นเท่ากับความยาวบัฟเฟอร์ - สัญญาณว่าไม่ใช่: บัฟเฟอร์แทบไม่ว่างแต่บนจอคนอื่นยังเห็นอาการกระตุก: น่าจะเป็นปัญหา interpolation ของฝั่งคนดู ถ้าบัฟเฟอร์สั้นแต่อินพุตดีเลย์ยังสูง น่าจะเป็นที่ RTT เองหรือการรอทิกซ้อนสองชั้น - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · เซิร์ฟเวอร์ปรับฐานเวลาของไคลเอนต์ ให้คิวอินพุตมีขนาดแค่พอดูดซับการมาถึงที่ไม่สม่ำเสมอโดยดีเลย์ต่ำที่สุด เป้าหมายบัฟเฟอร์บนเซิร์ฟเวอร์คือเฉลี่ยครึ่งเฟรม - [NetworkTimeSystem class (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/api/Unity.Netcode.NetworkTimeSystem.html) · Unity · LocalBufferSec: เวลาที่เซิร์ฟเวอร์บัฟเฟอร์ข้อความของไคลเอนต์ไว้ ปรับเวลาของไคลเอนต์ให้เร็วขึ้นเพื่อให้ข้อความไปถึงเซิร์ฟเวอร์เร็วขึ้น - [NetworkTime and ticks (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/advanced-topics/networktime-ticks.html) · Unity · ผู้เล่นที่เน็ตไม่ดีเพิ่มค่าบัฟเฟอร์ได้ #### pt-isp-validation · false positive ของการตรวจสอบที่กระจุกกับผู้ใช้บาง ISP · Anti-cheat / movement validation false positives on bad ISPs คนที่ใช้เน็ตที่จิตเตอร์สูง อินพุตจะไปถึงเป็นกลุ่ม จึงติดการตรวจความเร็วและคูลดาวน์ของเซิร์ฟเวอร์บ่อย - ทำไม → ผลคือ → บนหน้าจอ: จิตเตอร์ของเน็ตจากบาง ISP หรือบางพื้นที่สูงขึ้นในช่วงหัวค่ำ → เซิร์ฟเวอร์ตัดสินว่าอินพุตปกติที่มาถึงเป็นกลุ่มคือการเคลื่อนที่เร็วเกินหรือผิดกฎคูลดาวน์ → เฉพาะผู้ใช้ ISP นั้นที่โดนดีดกลับ สกิลถูกปฏิเสธ ถ้าหนักจะถูกเซิร์ฟเวอร์เตะออกจนหลุด - อาการ: ดีดกลับ, กดไม่ติด/โรลแบ็ค, หลุด / ปัจจัย: จิตเตอร์ - ใครเจอ: บางพื้นที่/บาง ISP, เราคนเดียว / เกิดเมื่อไร: ช่วงพีคหัวค่ำ, ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเครือข่าย (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ตรวจด้วยโควตาสะสมในช่วงหลายวินาที, ผ่อนเกณฑ์โดยดูสภาพเน็ต (ปิง, จิตเตอร์) ประกอบ, มีขั้นเตือนก่อนบังคับออก, ใช้บัฟเฟอร์อินพุตของผู้เล่นแต่ละคนกระจายอินพุตที่มาเป็นกลุ่มให้เท่ากันทุกทิก เพื่อลด false positive ตั้งแต่ต้นทาง - งานฝั่งทีมอินฟรา: ตรวจการกระจายของอัตราแพ็กเก็ตหายและจิตเตอร์แยกตาม ISP และช่วงเวลาแล้วแชร์ให้ทีมพัฒนาเกม, ตรวจเส้นทางช่วงของ ISP นั้น (mtr ทั้งสองทิศทาง), ถ้าจำเป็นให้เปลี่ยนเส้นทางหรือ escalate ไปยัง ISP - บนกราฟ: สูงเฉพาะบางช่วงเวลา (จำนวนการปฏิเสธจากการตรวจสอบ/การบังคับออกแยกตาม ISP (ASN), จิตเตอร์แยกตาม ISP) - จุดที่ต้องดู: ใส่ ISP (ASN) ของ IP ที่เชื่อมต่อและเวลาให้ log การปฏิเสธจากการตรวจสอบ, การแก้ตำแหน่ง และการบังคับออกของเซิร์ฟเวอร์ แล้วนับแยกตาม ISP และช่วงเวลา ทีมอินฟรารัน mtr ทั้งสองทิศทางไปยังฝั่ง ISP นั้นในช่วงเวลาเดียวกันเพื่อดูจิตเตอร์และแพ็กเก็ตหาย - สัญญาณว่าใช่: การปฏิเสธและการบังคับออกกระจุกที่บาง ISP และเพิ่มขึ้นช่วงหัวค่ำ จิตเตอร์ของ ISP นั้นสูงในเวลาเดียวกัน และปริมาณการเคลื่อนที่เมื่อรวมเป็นช่วงหลายวินาทียังอยู่ในกฎ - สัญญาณว่าไม่ใช่: เกิดซ้ำเฉพาะบางบัญชีโดยไม่เกี่ยวกับ ISP: อาจเป็นการโกงจริง ถ้าเพิ่มขึ้นพร้อมกันทุก ISP น่าจะเป็นสาเหตุฝั่งเซิร์ฟเวอร์ที่ทิกล่าช้าจนคำสั่งถูกใช้รวมกัน (ทิกเกินงบเวลา) - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Source SDK 2013: player.cpp](https://raw.githubusercontent.com/ValveSoftware/source-sdk-2013/master/src/game/server/player.cpp) · Valve · กันการเคลื่อนที่เร็วเกินด้วยงบคำสั่งที่สะสมทุกทิก และมีคอมเมนต์ของนักพัฒนาว่าการจำกัดที่เข้มงวดกว่านี้ทำให้ผู้เล่นปกติก็กระตุก - [RFC 2697: A Single Rate Three Color Marker](https://www.rfc-editor.org/rfc/rfc2697) · IETF · token bucket: ตัดสินด้วยอัตราเฉลี่ยและขนาด burst ที่ยอมให้ - [Understanding Networked Movement in the Character Movement Component for Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/understanding-networked-movement-in-the-character-movement-component-for-unreal-engine) · Epic Games · ถ้า timestamp ของไคลเอนต์กับเซิร์ฟเวอร์ต่างกันมาก จะทิ้งการเคลื่อนที่หรือจัดการด้วยขั้นตอนแก้ส่วนต่างเวลา, คำนวณด้วยเวลาเซิร์ฟเวอร์เพื่อกัน speed hack #### pt-raid-member · เพื่อนในปาร์ตี้ที่ช้าหนึ่งคนกับกิมมิกบอส · One laggy member in a synchronized mechanic ในกิมมิกเรดที่ทุกคนต้องตอบสนองพร้อมกันในจังหวะที่กำหนด การตอบสนองช้าของคนที่ช้าเพียงคนเดียวจะทำให้ทั้งปาร์ตี้ล้มเหลว - ทำไม → ผลคือ → บนหน้าจอ: กิมมิกร่วม เช่น “ทุกคนกระจายพร้อมกัน” หรือ “ให้คนหนึ่งกดปุ่ม” → คนที่ช้าเห็นสัญญาณเตือนช้า และอินพุตก็ไปถึงช้า → ตายยกปาร์ตี้เพราะคนคนเดียว เพื่อนในปาร์ตี้คนอื่นรู้สึกว่า “เพราะคนที่แลค” - อาการ: กดไม่ติด/โรลแบ็ค, อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: บางจุด/บางแชนแนล, เห็นตัวละครบางตัวผิดปกติ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ตอนทำแอ็กชันบางอย่าง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ให้ช่วงเวลาตัดสินกิมมิกเผื่อไว้เท่ากับปิง, ส่งสัญญาณเตือนล่วงหน้าตามเวลาเซิร์ฟเวอร์, ออกแบบไม่ให้คนเดียวพลาดแล้วตายยกปาร์ตี้ ไคลเอนต์: เล่นสัญญาณเตือนที่ได้รับให้ตรงกับเวลาเซิร์ฟเวอร์ - บนกราฟ: สูงเฉพาะบางกลุ่ม (RTT ของผู้เล่นที่ทำให้กิมมิกล้มเหลว) - จุดที่ต้องดู: บันทึกผู้เล่นที่ทำให้ล้มเหลว, เวลาที่อินพุตของคนนั้นมาถึง, ช่วงเวลาตัดสิน และ RTT/แพ็กเก็ตหายของคนนั้น ลงใน log กิมมิกของเซิร์ฟเวอร์ - สัญญาณว่าใช่: อินพุตที่ทำให้ตายยกปาร์ตี้ส่วนใหญ่เป็นของคนคนเดียวกัน RTT ของคนนั้นสูงกว่าค่าเฉลี่ยของปาร์ตี้ชัดเจน และอินพุตมาถึงหลังช่วงเวลาตัดสินจบไปไม่นาน - สัญญาณว่าไม่ใช่: ความล้มเหลวกระจายเท่า ๆ กันในปาร์ตี้: ช่วงเวลาตัดสินเองสั้นเกินไป (ช่วงเวลาตัดสินสั้นจนปิงกินหมด) ถ้าอินพุตของคนที่ช้ามาถึงภายในช่วงเวลาตัดสินแล้วยังล้มเหลว น่าจะเป็นโค้ดตัดสินผลของเซิร์ฟเวอร์ - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [NetworkTime and ticks (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/advanced-topics/networktime-ticks.html) · Unity · วิธีนัดเวลาอีเวนต์ตามเวลาเซิร์ฟเวอร์เพื่อให้ทุกคนเล่นในจังหวะเดียวกัน - [Factors influencing the latency of simple reaction time (Frontiers in Human Neuroscience, 2015)](https://doi.org/10.3389/fnhum.2015.00131) · Frontiers · เวลาตอบสนองอย่างง่ายเฉลี่ยประมาณ 231 ms #### pt-mob-control · สิทธิ์ควบคุมมอนสเตอร์อยู่ที่ไคลเอนต์ที่ช้า · Monster movement delegated to a player client บางเกมให้ไคลเอนต์ของผู้เล่นคนหนึ่งที่อยู่ใกล้คำนวณการเคลื่อนที่ของมอนสเตอร์เพื่อลดโหลดเซิร์ฟเวอร์ ถ้าเน็ตของคนนั้นไม่ดี มอนสเตอร์ตัวนั้นจะขยับแปลก ๆ บนจอของทุกคน - ทำไม → ผลคือ → บนหน้าจอ: เซิร์ฟเวอร์ให้ไคลเอนต์ของผู้เล่นที่อยู่ใกล้ที่สุด (หรือมาถึงก่อน) คำนวณการเคลื่อนที่ของมอนสเตอร์ → รายงานผลของคนที่รับหน้าที่ไปถึงเซิร์ฟเวอร์ช้าหรือมาเป็นกลุ่ม → เฉพาะมอนสเตอร์ตัวนั้นที่หยุดแวบแล้ววาร์ปบนจอของทุกคนรอบ ๆ ส่วนบนจอของคนที่รับหน้าที่เองปกติ - อาการ: วาร์ป, กระตุก, กรอเร็ว / ปัจจัย: จิตเตอร์, แพ็กเก็ตหาย - ใครเจอ: เห็นตัวละครบางตัวผิดปกติ, บางจุด/บางแชนแนล / เกิดเมื่อไร: ตลอดเวลา, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ย้ายสิทธิ์ควบคุมไปให้คนที่เน็ตดี (ดูจากปิงและแพ็กเก็ตหาย), ถ้ารายงานขาดหายให้เซิร์ฟเวอร์ดึงสิทธิ์คืนทันที, มอนสเตอร์สำคัญอย่างบอสให้เซิร์ฟเวอร์คำนวณเอง - บนกราฟ: สูงเฉพาะบางกลุ่ม (ช่วงห่างการรายงานตำแหน่งแยกตามมอนสเตอร์ (แยกตามไคลเอนต์ที่ถือสิทธิ์ควบคุม)) - จุดที่ต้องดู: ให้เซิร์ฟเวอร์บันทึกไคลเอนต์ที่ถือสิทธิ์ควบคุมของมอนสเตอร์แต่ละตัว พร้อมช่วงห่างการรายงาน, RTT และแพ็กเก็ตหายของไคลเอนต์นั้น ดูช่วงห่างการมาถึงของแพ็กเก็ตที่ไคลเอนต์นั้นส่งได้จาก packet capture ฝั่งเซิร์ฟเวอร์ด้วย - สัญญาณว่าใช่: สิทธิ์ควบคุมของมอนสเตอร์ที่ขยับแปลกอยู่ที่คนคนเดียวกันทั้งหมด ช่วงห่างการรายงานของคนนั้นไม่สม่ำเสมอหรือขาดหาย และเมื่อย้ายสิทธิ์ไปให้คนอื่นก็ปกติทันที - สัญญาณว่าไม่ใช่: มอนสเตอร์ที่เซิร์ฟเวอร์คำนวณเองก็กระโดดเหมือนกัน: น่าจะเป็นทิกของเซิร์ฟเวอร์ล่าช้าหรือเน็ตของฝั่งคนดู ถ้าย้ายสิทธิ์แล้วยังกระโดดต่อ น่าจะเป็น “การคำนวณเส้นทางไม่ตรงกันในการซิงก์คำสั่ง” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: คนที่รับหน้าที่รู้สึกว่าปกติ การแจ้งปัญหาจึงเข้ามาแค่ว่า “มอนสเตอร์แปลก ๆ” ถ้าทุกคนยกเว้นคนเดียวเห็นมอนสเตอร์ตัวเดียวกันขยับแปลก ให้ตรวจก่อนว่าใครถือสิทธิ์ควบคุมมอนสเตอร์ตัวนั้น - แหล่งอ้างอิง: - [Authority (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/terms-concepts/authority.html) · Unity · ในโมเดล distributed authority แต่ละอินสแตนซ์เกม (ไคลเอนต์) รับสิทธิ์ของ network object บางส่วนและคำนวณ object นั้น - [Distributed authority topologies (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/terms-concepts/distributed-authority.html) · Unity · ถ้าแบ่งสิทธิ์ให้ไคลเอนต์ จะไม่มี simulation เดียว และโกงได้ง่าย #### pt-heavy-char · ข้อมูลของตัวละครบางตัวใหญ่เกินไป · One character with oversized data (inventory, mail, buffs) ตัวละครที่มีไอเทมหรือจดหมายสะสมหลายพันชิ้น หรือมีรายชื่อเพื่อน รายชื่อบล็อก และบัฟมากผิดปกติ ต้องโหลดตอนเชื่อมต่อ บันทึก และแจ้งคนรอบข้างมากกว่าคนอื่นหลายเท่า จึงช้าเฉพาะตัวละครนั้นโดยไม่เกี่ยวกับเน็ต - ทำไม → ผลคือ → บนหน้าจอ: ตัวละครที่เล่นมานานหรือของรางวัลอีเวนต์สะสมในกระเป๋าและกล่องจดหมายหลายพันชิ้น → ทุกครั้งที่เชื่อมต่อ ย้ายแมพ หรือบันทึก ต้องอ่านเขียน DB มากตามไปด้วย และข้อมูลอุปกรณ์สวมใส่และบัฟที่ต้องส่งให้คนรอบข้างก็ใหญ่ → เฉพาะตัวละครนั้นที่โหลดเข้าโซนนาน และหยุดแวบตอนเปิดกระเป๋าหรือกล่องจดหมาย ถ้าเซิร์ฟเวอร์รอการบันทึกบนเธรดเกม คนรอบข้างก็ค้างไปครู่หนึ่งด้วย - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, อินพุตดีเลย์, ค้าง / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว, เฉพาะบางฟีเจอร์ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ตอนทำแอ็กชันบางอย่าง, ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟรา DB (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: จำกัดความจุกระเป๋าและกล่องจดหมาย และลบจดหมายเก่าอัตโนมัติ, โหลดแยกเฉพาะส่วนที่ต้องใช้, บันทึกเฉพาะส่วนที่เปลี่ยนนอกเธรดเกม - งานฝั่งทีมอินฟรา: หาการอ่านที่ช้าซึ่งเกิดซ้ำกับตัวละครเดิมจาก slow query log แล้วส่งให้ทีมพัฒนาเกม, ให้รายชื่ออันดับต้นของตัวละครที่มีจำนวนแถวไอเทมและจดหมายมาก - ตัวเลขที่ควรรู้: ถ้าไอเทมหนึ่งชิ้นคือหนึ่งแถวใน DB ตัวละครที่มีไอเทม 5,000 ชิ้นจะอ่าน 5,000 แถวทุกครั้งที่เชื่อมต่อ ซึ่งมากกว่าตัวละครทั่วไปหลายสิบเท่า - บนกราฟ: สูงเฉพาะบางกลุ่ม (เวลาเชื่อมต่อ/บันทึกแยกตามตัวละคร, จำนวนแถวที่อ่านจาก DB แยกตามตัวละคร) - จุดที่ต้องดู: หาการอ่านและการบันทึกที่ช้าซึ่งเกิดซ้ำกับ ID ตัวละครเดิมใน slow query log ของ DB (MySQL slow query log, PostgreSQL log_min_duration_statement) และดึงรายชื่ออันดับต้นของจำนวนแถวต่อตัวละครในตารางไอเทมและจดหมาย - สัญญาณว่าใช่: คิวรีที่ช้ากระจุกที่ ID ตัวละครไม่กี่ตัว จำนวนแถวไอเทมและจดหมายของตัวละครเหล่านั้นสูงเป็นหลายสิบเท่าของค่าเฉลี่ย และเชื่อมต่อจาก PC หรือเน็ตอื่นก็ช้าเท่าเดิม - สัญญาณว่าไม่ใช่: ตัวละครอื่นในบัญชีเดียวกันหรือผู้เล่นคนอื่นก็ช้าด้วย: น่าจะเป็นที่เครื่อง DB หรือล็อก ถ้าตัวละครนั้นปกติบน PC อื่น น่าจะเป็นสภาพแวดล้อมของผู้เล่น - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ถ้าใช้ตัวละครเดียวกันเชื่อมต่อจาก PC หรือเน็ตอื่นแล้วยังช้าเท่าเดิม แต่ตัวละครอื่นในบัญชีเดียวกันปกติ ให้สงสัยข้อมูลตัวละคร นี่คือเหตุผลที่การแจ้งปัญหาต้องมีชื่อตัวละครเสมอ - แหล่งอ้างอิง: - [Extraneous Fetching antipattern](https://learn.microsoft.com/en-us/azure/architecture/antipatterns/extraneous-fetching/) · Microsoft Azure · ถ้าดึงข้อมูลมากเกินจำเป็น ภาระ I/O จะเพิ่มขึ้นและตอบสนองช้าลง - [PostgreSQL Documentation: Error Reporting and Logging](https://www.postgresql.org/docs/current/runtime-config-logging.html) · PostgreSQL · log_min_duration_statement: บันทึก SQL ที่ใช้เวลาเกินกำหนดเพื่อติดตามคิวรีที่ช้า - [The Slow Query Log](https://dev.mysql.com/doc/refman/8.4/en/slow-query-log.html) · MySQL · slow query log ที่บันทึกคิวรีซึ่งใช้เวลาเกิน long_query_time #### pt-phase · แชนแนล/อินสแตนซ์/phasing ต่างกัน · Different channel / instance / phase ถ้าตัวละครสองตัวอยู่คนละแชนแนลหรือคนละอินสแตนซ์ หรืออยู่คนละ “phase” ที่เห็น NPC ต่างกันตามความคืบหน้าของเควสต์ จะเห็นโลกคนละแบบ - ทำไม → ผลคือ → บนหน้าจอ: ตัวละครตัวที่สองถูกจัดไปแชนแนลอื่น หรืออยู่ขั้นเควสต์ต่างกัน → เซิร์ฟเวอร์ไม่ส่ง NPC นั้นให้ตัวละครนั้น (ปกติ) → NPC หายไปฝั่งเดียว ดูเหมือนบั๊กแต่เป็นไปตามการออกแบบ - อาการ: มองไม่เห็น/ตัวผี / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ, เราคนเดียว / เกิดเมื่อไร: ตลอดเวลา, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ส่งข้อมูลแชนแนลและ phase ให้ไคลเอนต์, เพิ่ม “ตรวจแชนแนลและขั้นเควสต์ของตัวละครทั้งสอง” ในเช็กลิสต์ของ QA ไคลเอนต์: แสดงแชนแนลและ phase บนหน้าจอ - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนเอนทิตีรอบตัวแยกตามไคลเอนต์, แชนแนล/phase) - จุดที่ต้องดู: เทียบหมายเลขแชนแนลและขั้นความคืบหน้าของเควสต์ที่เกี่ยวข้องของตัวละครทั้งสองบนหน้าจอเกม แล้วปรับให้อยู่แชนแนลและขั้นเดียวกันแล้วดูอีกครั้ง ถ้ามี log การส่งเอนทิตีของเซิร์ฟเวอร์ ให้ดูเหตุผลที่ไม่ส่ง NPC นั้นให้ตัวละครนั้น (แชนแนล, phase) - สัญญาณว่าใช่: แชนแนลหรือขั้นเควสต์ของตัวละครทั้งสองต่างกัน และเมื่อปรับให้ตรงกันก็เห็น NPC - สัญญาณว่าไม่ใช่: แชนแนลและขั้นเควสต์ตรงกันแต่ยังหายไปฝั่งเดียว: น่าจะเป็น “ทิ้งข้อความแจ้งการปรากฏตัวที่มาถึงระหว่างโหลด”, “ข้อมูลการปรากฏตัวที่ทะลักมาหลังเข้าโซนหาย” หรือ “ลำดับการลงทะเบียนระยะมองเห็นพันกัน” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: ตรวจด้วยว่าความคืบหน้าเควสต์บันทึกระดับบัญชีหรือระดับตัวละคร ถ้าเป็นตัวละครสองตัวในบัญชีเดียวกัน ความคืบหน้าของตัวหนึ่งอาจเปลี่ยน phase ของอีกตัวได้ - แหล่งอ้างอิง: - [Actor Relevancy in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-relevancy-in-unreal-engine) · Epic Games · เซิร์ฟเวอร์ replicate เฉพาะ actor ที่เกี่ยวข้อง (relevant) ให้แต่ละการเชื่อมต่อ และไม่ส่ง actor ที่ไม่เกี่ยวข้อง - [Object visibility (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/basics/object-visibility.html) · Unity · ใช้ CheckObjectVisibility กำหนดเอนทิตีที่มองเห็นแยกตามไคลเอนต์ และไม่ส่งเอนทิตีที่ซ่อนไว้ให้ไคลเอนต์นั้น #### pt-loading-drop · ทิ้งข้อความแจ้งการปรากฏตัวที่มาถึงระหว่างโหลด · Spawn messages dropped before the client is ready ทันทีที่เข้าโซน เซิร์ฟเวอร์ส่งข้อความแจ้งการปรากฏตัวของ NPC รอบตัวมา แต่ไคลเอนต์ยังโหลดแมพอยู่จึงทิ้งข้อความนั้น - ทำไม → ผลคือ → บนหน้าจอ: เซิร์ฟเวอร์ส่งข้อความแจ้งการปรากฏตัวของเอนทิตีรอบตัวทันทีหลังประมวลผลการเข้าโซน → ไคลเอนต์กำลังโหลดและยังไม่มี message handler จึงทิ้งข้อความ → เซิร์ฟเวอร์ถือว่าส่งไปแล้วจึงไม่ส่งซ้ำ มองไม่เห็น NPC จนกว่าจะออกจากระยะมองเห็นแล้วกลับมา - อาการ: มองไม่เห็น/ตัวผี / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ, เราคนเดียว / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ส่ง “พร้อม” เมื่อโหลดเสร็จ หรือเก็บแพ็กเก็ตที่ได้ระหว่างโหลดไว้ประมวลผลทีหลัง เซิร์ฟเวอร์: ส่งข้อมูลรอบตัวหลังได้รับ “พร้อม” - ตัวเลขที่ควรรู้: ถ้าสองไคลเอนต์ในเครื่องเดียวกันโหลดพร้อมกัน หรือฝั่งที่โหลดเป็นหน้าต่างที่อยู่เบื้องหลัง จะต้องแบ่ง CPU และดิสก์กัน และถูกจำกัดการประมวลผลด้วย ฝั่งนั้นจึงอาจโหลดนานขึ้นหลายเท่า บั๊กเดียวกันนี้ยังโผล่ขึ้นมาได้เมื่อเซิร์ฟเวอร์ประมวลผลการเข้าโซนเร็วขึ้น - บนกราฟ: สูงเฉพาะบางกลุ่ม (เวลาโหลดแยกตามไคลเอนต์, จำนวนข้อความที่ทิ้งระหว่างโหลด) - จุดที่ต้องดู: เทียบจำนวนและชนิดของข้อความที่ไคลเอนต์ได้รับแล้วทิ้งระหว่างโหลด และเวลาที่โหลดเสร็จ กับเวลาที่เซิร์ฟเวอร์ส่งข้อความแจ้งการปรากฏตัว จำลองอาการได้ง่ายโดยให้สองไคลเอนต์ในเครื่องเดียวกันโหลดพร้อมกัน หรือให้ฝั่งที่โหลดเป็นหน้าต่างที่อยู่เบื้องหลัง - สัญญาณว่าใช่: เซิร์ฟเวอร์ส่งข้อความแจ้งการปรากฏตัวของ NPC ที่มองไม่เห็นแล้ว และมาถึงก่อนโหลดเสร็จ ช่วงนั้นจำนวนข้อความที่ถูกทิ้งเพิ่มขึ้น เกิดเฉพาะกับไคลเอนต์ฝั่งที่โหลดนาน - สัญญาณว่าไม่ใช่: ข้อความแจ้งการปรากฏตัวมาถึงหลังโหลดเสร็จแล้วแต่ยังมองไม่เห็น: น่าจะเป็น “สแนปช็อตตั้งต้น (baseline) หาย” หรือ “สับสนจากการนำ entity ID กลับมาใช้ซ้ำ” ถ้าเซิร์ฟเวอร์ไม่ได้ส่งข้อความของ NPC นั้นเลย น่าจะเป็น “ลำดับการลงทะเบียนระยะมองเห็นพันกัน” หรือ “แชนแนล/อินสแตนซ์/phasing ต่างกัน” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [NetworkConfig class (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/api/Unity.Netcode.NetworkConfig.html) · Unity · SpawnTimeout: เก็บข้อความของเอนทิตีที่ยังไม่ถูกสร้างไว้ก่อน ถ้าไม่ถูกสร้างภายในเวลาที่กำหนดจะทิ้ง - [Actor Relevancy in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-relevancy-in-unreal-engine) · Epic Games · actor ที่ไม่เกี่ยวข้องแล้วจะถูกลบออกจากไคลเอนต์ และเมื่อกลับมาเกี่ยวข้องอีกจะถูก replicate ใหม่ #### pt-aoi-race · ลำดับการลงทะเบียนระยะมองเห็นพันกัน · Interest-management race on enter/leave ถ้าจังหวะที่ตัวละครถูกลงทะเบียนในตาราง (grid) ระยะมองเห็นตรงกับจังหวะที่ NPC ย้ายช่องตาราง ข้อความแจ้งการปรากฏตัวของ NPC นั้นอาจตกหล่น - ทำไม → ผลคือ → บนหน้าจอ: การประมวลผลการเข้าโซน, การย้ายแชนแนล หรือการเทเลพอร์ต เกิดในจังหวะเดียวกับการเคลื่อนที่ของ NPC → NPC นั้นตกไปจากการคำนวณ “เอนทิตีที่เพิ่งมองเห็น” → มองไม่เห็นแค่ NPC บางตัว หรือ NPC ที่ออกไปแล้วยังยืนอยู่ - อาการ: มองไม่เห็น/ตัวผี / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ, เราคนเดียว / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ประมวลผลการอัปเดตระยะมองเห็นในเธรดเดียวและลำดับเดียว, ซิงก์ “รายการที่มองเห็น” ใหม่ทั้งหมดเป็นระยะ - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (จำนวนรายการที่ต่างกันระหว่างรายการที่มองเห็นบนเซิร์ฟเวอร์กับรายการเอนทิตีบนไคลเอนต์) - จุดที่ต้องดู: ให้เซิร์ฟเวอร์บันทึกการลงทะเบียนในตารางระยะมองเห็น, การย้ายเซลล์ของเอนทิตี และการส่งข้อความแจ้งการปรากฏตัว/ออกไป พร้อมหมายเลขทิก แล้วเทียบ “รายการที่มองเห็น” ของเซิร์ฟเวอร์กับรายการที่ไคลเอนต์มีเป็นระยะ - สัญญาณว่าใช่: NPC ที่หายไปย้ายเซลล์ในทิกเดียวกับที่ตัวละครนั้นเข้าโซนหรือเทเลพอร์ต และไม่มีบันทึกการส่งข้อความแจ้งการปรากฏตัวของ NPC นั้น - สัญญาณว่าไม่ใช่: ส่งข้อความแจ้งการปรากฏตัวแล้วแต่ไคลเอนต์ไม่ได้รับหรือทิ้งไป: น่าจะเป็นที่การส่ง (“ข้อมูลการปรากฏตัวที่ทะลักมาหลังเข้าโซนหาย”, “ทิ้งข้อความแจ้งการปรากฏตัวที่มาถึงระหว่างโหลด”) ถ้าหายเฉพาะ NPC ตัวเดิมเสมอ น่าจะเป็น phasing หรือตัวเลือกการแสดงผลต่างกัน - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Replication Graph in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/replication-graph-in-unreal-engine) · Epic Games · MMORPG และเกมอื่น ๆ แบ่งโลกเป็นตาราง มีรายการ actor ในแต่ละเซลล์ และส่งโดยอิงเซลล์ที่ไคลเอนต์อยู่ - [Actor Relevancy in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-relevancy-in-unreal-engine) · Epic Games · ความเกี่ยวข้อง (relevancy) ตัดสินแยกตามการเชื่อมต่อ และ actor ที่ไม่เกี่ยวข้องแล้วจะถูกลบออกจากไคลเอนต์ #### pt-baseline · สแนปช็อตตั้งต้น (baseline) หาย · Lost baseline for delta compression ในแบบที่เซิร์ฟเวอร์ส่ง “เฉพาะส่วนที่ต่างจากครั้งก่อน” ถ้าข้อมูลเต็ม (baseline) ที่ส่งครั้งแรกหายไป ก็จะใช้ส่วนเปลี่ยนแปลงที่ตามมาไม่ได้ - ทำไม → ผลคือ → บนหน้าจอ: แพ็กเก็ตข้อมูลเต็ม (baseline) ของเอนทิตีหาย หรือถูกทิ้งก่อนประมวลผล → ไคลเอนต์ไม่มีเป้าหมายให้ใช้ส่วนเปลี่ยนแปลงที่ตามมา จึงไม่สนใจ → มองไม่เห็นเอนทิตีนั้น หรือผ่านไปนานแล้วจู่ ๆ ก็โผล่ขึ้นมา - อาการ: มองไม่เห็น/ตัวผี, วาร์ป / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ, เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ต้องส่ง baseline ซ้ำจนกว่าจะได้รับ ACK (สัญญาณยืนยันการรับ), สร้างส่วนเปลี่ยนแปลงเทียบกับ baseline ที่ไคลเอนต์ยืนยันว่าได้รับแล้วเท่านั้น ไคลเอนต์: ส่ง ACK ของ baseline หลังนำไปใช้จริงแล้ว, ถ้าได้รับส่วนเปลี่ยนแปลงของเอนทิตีที่ไม่รู้จัก ให้ขอจากเซิร์ฟเวอร์ใหม่ - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (จำนวนครั้งที่ได้รับส่วนเปลี่ยนแปลงของเอนทิตีที่ไม่รู้จัก) - จุดที่ต้องดู: เทียบจำนวนครั้งและ entity ID ที่ไคลเอนต์ทิ้งส่วนเปลี่ยนแปลงเพราะไม่มี baseline กับเวลาที่เซิร์ฟเวอร์ส่ง baseline ของเอนทิตีนั้นและเวลาที่ได้รับ ACK จำลองอาการในสภาพแวดล้อม dev โดยใส่แพ็กเก็ตหาย (loss ของ tc netem, อัตราแพ็กเก็ตหายใน network emulation ของ Unreal) - สัญญาณว่าใช่: สำหรับเอนทิตีที่มองไม่เห็น เซิร์ฟเวอร์ส่ง baseline แล้วแต่ไม่ได้รับ ACK ก็ยังส่งแต่ส่วนเปลี่ยนแปลงต่อไป และไคลเอนต์ทิ้งส่วนเปลี่ยนแปลงนั้น - สัญญาณว่าไม่ใช่: baseline ได้รับ ACK และนำไปใช้บนไคลเอนต์แล้วแต่ยังมองไม่เห็น: น่าจะเป็น “ข้อความแจ้งการออกไปหาย (ตัวผี)” หรือ “สับสนจากการนำ entity ID กลับมาใช้ซ้ำ” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Snapshot Compression](https://gafferongames.com/post/snapshot_compression/) · Gaffer On Games · ส่วนเปลี่ยนแปลงต้องสร้างเทียบกับ baseline ที่อีกฝ่ายยืนยัน (ack) ว่าได้รับแล้วเท่านั้น และสถานะเริ่มต้นส่งแยกต่างหาก - [Quake III Arena source: code/server/sv_snapshot.c](https://raw.githubusercontent.com/id-Software/Quake-III-Arena/master/code/server/sv_snapshot.c) · id Software · ทำ delta compression โดยใช้สแนปช็อตที่ไคลเอนต์ยืนยันแล้วเป็น baseline ถ้า baseline เก่าเกินไปจะส่งสแนปช็อตเต็ม - [tc-netem(8) — Linux manual page](https://man7.org/linux/man-pages/man8/tc-netem.8.html) · iproute2 · เครื่องมือทดสอบที่ใส่ดีเลย์/จิตเตอร์ (delay TIME JITTER) และการหาย (loss random PERCENT) ให้แพ็กเก็ตขาออก เพื่อจำลองเครือข่ายจริง - [Using Network Emulation in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/using-network-emulation-in-unreal-engine) · Epic Games · ทดสอบโดยใส่ดีเลย์ต่ำสุด/สูงสุดและอัตราแพ็กเก็ตหายให้เซิร์ฟเวอร์และไคลเอนต์, ในคอนโซลตั้งค่าแบบ NetEmulation.PktLag #### pt-ghost · ข้อความแจ้งการออกไปหาย (ตัวผี) · Missed despawn (ghost entity) ในทางกลับกัน ถ้าพลาดข้อความแจ้งว่า “หายไปแล้ว” NPC หรือผู้เล่นที่ตายหรือออกไปแล้วจะยังอยู่บนจอเราคนเดียว - ทำไม → ผลคือ → บนหน้าจอ: ข้อความแจ้งการตาย, การออกไป หรือการออกจากระยะมองเห็นหาย หรือลำดับสลับกัน → ไคลเอนต์ถือว่าเอนทิตีนั้นยังอยู่ → มอนสเตอร์ที่ตีแล้วไม่มีปฏิกิริยา, ผู้เล่นที่ออกไปแล้วยังยืนอยู่ - อาการ: มองไม่เห็น/ตัวผี / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ, เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ส่ง “รายการที่มองเห็นตอนนี้” เป็นระยะ ไคลเอนต์: ลบเอนทิตีที่ไม่อยู่ในรายการ, ซ่อนเอนทิตีที่ควรเคลื่อนที่แต่ไม่ได้อัปเดตนาน - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (จำนวนเอนทิตีที่เหลืออยู่แค่บนไคลเอนต์) - จุดที่ต้องดู: เทียบ “รายการที่มองเห็นตอนนี้” ที่เซิร์ฟเวอร์ส่งกับรายการเอนทิตีที่ไคลเอนต์มี แล้วนับเอนทิตีที่มีแค่บนไคลเอนต์ และจับคู่ log การส่งและการรับข้อความแจ้งการออกไปด้วย entity ID - สัญญาณว่าใช่: เซิร์ฟเวอร์ส่งข้อความแจ้งการออกไปของตัวผีแล้วแต่ไคลเอนต์ไม่มีบันทึกการรับ หรือข้อความแจ้งการออกไปมาถึงก่อนข้อความแจ้งการปรากฏตัวจนลำดับกลับกัน - สัญญาณว่าไม่ใช่: รายการที่มองเห็นของเซิร์ฟเวอร์ก็ยังมีเอนทิตีนั้นอยู่: น่าจะเป็นฝั่งเซิร์ฟเวอร์เก็บกวาดเอนทิตีตกหล่น ถ้าเพิ่งมีเอนทิตีใหม่ใช้ ID เดียวกันโผล่มา น่าจะเป็น “สับสนจากการนำ entity ID กลับมาใช้ซ้ำ” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Actor Relevancy in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-relevancy-in-unreal-engine) · Epic Games · dynamic actor ที่ไม่เกี่ยวข้องแล้วจะถูกลบออกจากไคลเอนต์ - [Object visibility (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/basics/object-visibility.html) · Unity · เมื่อซ่อนเอนทิตี ไคลเอนต์นั้นจะ despawn/ลบเอนทิตีนั้น #### pt-spawn-burst · ข้อมูลการปรากฏตัวที่ทะลักมาหลังเข้าโซนหาย · Initial spawn burst lost (unreliable channel, receive buffer, fragmentation) วินาทีที่เข้าโซน เซิร์ฟเวอร์จะส่งข้อมูลการปรากฏตัวของเอนทิตีรอบตัวหลายสิบถึงหลายร้อยตัวมาพร้อมกัน ถ้าส่งผ่านช่องทางแบบ unreliable หรือ receive buffer ล้นในช่วงที่ยังโหลดอยู่จนอ่านซ็อกเก็ตไม่ได้ บางส่วนจะหายไปและไม่ถูกส่งมาอีก - ทำไม → ผลคือ → บนหน้าจอ: ทันทีหลังเข้าโซน ข้อมูลการปรากฏตัวมาถึงพร้อมกันในช่วงสั้น ๆ → ไคลเอนต์ที่กำลังโหลดอ่านซ็อกเก็ตช้าจน receive buffer ของ OS ล้น หรือแพ็กเก็ต UDP ขนาดใหญ่ถูก fragment และหาย fragment เดียวก็หายทั้งแพ็กเก็ต ถ้าเป็นช่องทางแบบ unreliable ก็ไม่ส่งซ้ำให้ด้วย → NPC หายไปบางตัวเฉพาะในไคลเอนต์ฝั่งที่โหลดช้า ถ้าออกจากระยะมองเห็นแล้วกลับมาจะเห็น - อาการ: มองไม่เห็น/ตัวผี / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ, เราคนเดียว / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ข้อความแจ้งการปรากฏตัว/ออกไปต้องส่งผ่านช่องทาง reliable ที่รับประกันการส่งซ้ำ, ข้อมูลเริ่มต้นให้แบ่งส่ง ไคลเอนต์: รับข้อมูลในเธรดที่แยกจากการโหลด, เพิ่มขนาด receive buffer - ตัวเลขที่ควรรู้: ค่าเริ่มต้นของ receive buffer สำหรับ UDP บน PC ต่างกันตาม OS แต่มักอยู่ที่หลายสิบถึงหลายร้อย KB ถ้าข้อมูลตอนเข้าเมืองที่คนเยอะใหญ่กว่านี้ แค่อ่านซ็อกเก็ตไม่ได้ชั่วครู่เพราะกำลังโหลดก็ล้นแล้ว - บนกราฟ: พุ่งทันทีหลังเปิดเซิร์ฟ/ปิดปรับปรุง (ปริมาณข้อมูลที่รับทันทีหลังเข้าโซน, จำนวนข้อความแจ้งการปรากฏตัวที่ตกหล่น) - จุดที่ต้องดู: เทียบจำนวนข้อความแจ้งการปรากฏตัวที่เซิร์ฟเวอร์ส่งทันทีหลังเข้าโซนกับจำนวนที่ไคลเอนต์ได้รับ และดูว่าส่งผ่านช่องทางไหน (reliable หรือ unreliable) ใน packet capture ฝั่งเซิร์ฟเวอร์ ให้ดูปริมาณที่ส่งไปหาผู้เล่นคนนั้นทันทีหลังเข้าโซน และแพ็กเก็ตที่ถูก fragment (filter ของ Wireshark ip.flags.mf == 1 || ip.frag_offset > 0) - สัญญาณว่าใช่: จำนวนที่ได้รับน้อยกว่าที่ส่ง ส่วนที่หายกระจุกในช่วงที่ข้อมูลทะลักมาหลังเข้าโซน และส่งผ่านช่องทางแบบ unreliable หรือแพ็กเก็ตใหญ่ถูก fragment เกิดบ่อยกว่าในไคลเอนต์ฝั่งที่โหลดช้า - สัญญาณว่าไม่ใช่: จำนวนที่ส่งกับที่ได้รับเท่ากันแต่ยังมองไม่เห็น: น่าจะเป็นการทิ้งหลังได้รับ (“ทิ้งข้อความแจ้งการปรากฏตัวที่มาถึงระหว่างโหลด”) หรือปัญหาการคำนวณระยะมองเห็น ถ้าหายเป็นระยะโดยไม่เกี่ยวกับช่วงหลังเข้าโซน น่าจะเป็นแพ็กเก็ตหายจากเน็ต - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [RFC 8085: UDP Usage Guidelines](https://www.rfc-editor.org/rfc/rfc8085) · IETF · แพ็กเก็ตที่ถูก fragment จะหายทั้งแพ็กเก็ตแม้หาย fragment เดียว - [UDP vs. TCP](https://gafferongames.com/post/udp_vs_tcp/) · Gaffer On Games · UDP ไม่รับประกันการส่งถึงและลำดับ แพ็กเก็ตที่หายต้องตรวจจับเองแล้วส่งซ้ำ - [Socket.ReceiveBufferSize Property](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receivebuffersize) · Microsoft · ขนาดเริ่มต้นของ receive buffer ของซ็อกเก็ตต่างกันตาม OS - [Display Filter Reference: Internet Protocol Version 4](https://www.wireshark.org/docs/dfref/i/ip.html) · Wireshark · กรองแพ็กเก็ต IP ที่ถูก fragment ด้วย ip.flags.mf (More fragments) และ ip.frag_offset (Fragment Offset) #### pt-id-reuse · สับสนจากการนำ entity ID กลับมาใช้ซ้ำ · Entity ID reused without a generation counter ถ้าเซิร์ฟเวอร์ใช้ entity ID เดิมซ้ำเมื่อ NPC ที่ตายเกิดใหม่ ไคลเอนต์ที่พลาดข้อความแจ้งการออกไปในช่วงนั้นจะเข้าใจผิดว่า NPC ตัวใหม่คือตัวเก่า - ทำไม → ผลคือ → บนหน้าจอ: NPC ตายแล้วเกิดใหม่ด้วย entity ID เดิม → ไคลเอนต์ที่พลาดข้อความแจ้งการออกไปถือว่าเป็น “เอนทิตีที่รู้จักอยู่แล้ว” จึงไม่สนใจข้อความแจ้งการปรากฏตัว หรือคงสถานะตายไว้เหมือนเดิม → NPC หายไปหรือเห็นนอนตายอยู่บนจอฝั่งเดียว บางครั้งเห็นเป็นหน้าตาของ NPC ตัวอื่น - อาการ: มองไม่เห็น/ตัวผี / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ, เราคนเดียว / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ใส่หมายเลข generation ให้ entity ID เพื่อแยกการนำกลับมาใช้ซ้ำ ไคลเอนต์: ถ้าได้รับข้อความแจ้งการปรากฏตัวของ ID ที่รู้จักอยู่แล้ว ให้ลบเอนทิตีเดิมแล้วสร้างใหม่ - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (จำนวนข้อความแจ้งการปรากฏตัวของ ID ที่รู้จักอยู่แล้ว) - จุดที่ต้องดู: ให้เซิร์ฟเวอร์บันทึกเวลาสร้างและลบของแต่ละ entity ID (พร้อมหมายเลข generation ถ้ามี) แล้วนับจำนวนครั้งที่ไคลเอนต์ได้รับข้อความแจ้งการปรากฏตัวด้วย ID ที่รู้จักอยู่แล้ว และการลบแล้วสร้างใหม่ที่ถูกนับเป็น “ไม่มีการเปลี่ยนแปลง” ตอนอัปเดตระยะมองเห็น - สัญญาณว่าใช่: ID ของ NPC ที่มองไม่เห็นหรือเห็นนอนตายอยู่ ตรงกับ NPC ที่เพิ่งตายไป และในช่วงนั้นไคลเอนต์นั้นไม่ได้รับข้อความแจ้งการออกไป หรือเซิร์ฟเวอร์ไม่ได้ส่งทั้งข้อความแจ้งการออกไปและการปรากฏตัว - สัญญาณว่าไม่ใช่: ID มีหมายเลข generation และใช้ในการเทียบด้วย: ไม่ใช่สาเหตุนี้ ถ้ามองไม่เห็นทั้งที่ ID ไม่ได้ถูกนำกลับมาใช้ซ้ำ น่าจะเป็นข้อความแจ้งการปรากฏตัวหาย - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - รายละเอียดเพิ่มเติม: เกิดที่ฝั่งเซิร์ฟเวอร์ได้เช่นกัน ถ้าเทียบรายการเอนทิตีในระยะมองเห็นด้วย ID อย่างเดียว NPC ที่ตายแล้วเกิดใหม่ด้วย ID เดิมระหว่างการอัปเดตระยะมองเห็นสองรอบจะถูกมองว่า “ไม่มีการเปลี่ยนแปลง” และไม่ส่งทั้งข้อความแจ้งการออกไปและการปรากฏตัว ถ้าจังหวะอัปเดตระยะมองเห็นของแต่ละคนไม่ตรงกัน จะเจอเฉพาะไคลเอนต์ที่ตรงกับจังหวะนั้น - แหล่งอ้างอิง: - [Entity struct (Entities 1.3)](https://docs.unity3d.com/Packages/com.unity.entities@1.3/api/Unity.Entities.Entity.html) · Unity · Entity ประกอบด้วย Index และหมายเลข generation (Version) เพื่อแยกว่า Index ที่ถูกนำกลับมาใช้ยังใช้ได้อยู่หรือไม่ - [NetworkConfig class (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/api/Unity.Netcode.NetworkConfig.html) · Unity · RecycleNetworkIds/NetworkIdRecycleDelay: เว้น network ID ไว้ช่วงหนึ่งก่อนนำกลับมาใช้ซ้ำ #### pt-port-collision · พอร์ต UDP แบบตายตัวชนกัน · Two clients bound to the same local UDP port ถ้าไคลเอนต์ถูกออกแบบให้ใช้พอร์ตภายในเครื่องที่กำหนดตายตัว ไคลเอนต์ตัวที่สองบน PC เดียวกันจะใช้พอร์ตไม่ได้ หรือต้องแบ่งรับแพ็กเก็ตกับตัวแรก - ทำไม → ผลคือ → บนหน้าจอ: สองไคลเอนต์พยายามเปิดพอร์ต UDP ภายในเครื่องพอร์ตเดียวกัน (ฝืนใช้ร่วมกันด้วยออปชัน reuse) → OS ส่งแพ็กเก็ตขาเข้าให้ซ็อกเก็ตฝั่งเดียว หรือไม่รับประกันว่าฝั่งไหนจะได้รับ เราเตอร์และเซิร์ฟเวอร์ก็มองสองไคลเอนต์เป็นที่อยู่เดียวกัน → ฝั่งหนึ่งไม่ได้รับแพ็กเก็ตของโลกในเกม จึงมองไม่เห็น NPC และผู้เล่นคนอื่น หรือหลุด - อาการ: มองไม่เห็น/ตัวผี, หลุด, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ให้ OS เลือกพอร์ตภายในเครื่องอัตโนมัติ (bind พอร์ต 0) เซิร์ฟเวอร์: แยกการเชื่อมต่อด้วย session token ที่ออกให้แต่ละการเชื่อมต่อ - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนแพ็กเก็ตที่ได้รับแยกตามไคลเอนต์) - จุดที่ต้องดู: บน PC ของผู้เล่น เปิดสองไคลเอนต์ไว้พร้อมกันแล้วใช้ netstat -ano -p udp ใน Command Prompt ดูพอร์ต UDP ภายในเครื่องที่แต่ละโปรเซสเกม (PID) เปิด ฝั่งเซิร์ฟเวอร์ให้ตรวจว่าสองเซสชันเข้ามาด้วย public IP และพอร์ตเดียวกันหรือไม่ - สัญญาณว่าใช่: สองโปรเซสเกมผูกอยู่กับพอร์ตภายในเครื่องเดียวกัน หรือบนเซิร์ฟเวอร์เห็นสองเซสชันเป็น IP และพอร์ตเดียวกัน ถ้าเปิดฝั่งเดียวจะปกติ - สัญญาณว่าไม่ใช่: สองไคลเอนต์ใช้พอร์ตภายในเครื่องต่างกันแต่ฝั่งหนึ่งยังผิดปกติ: น่าจะเป็น “บั๊กแยกเซสชันด้วย IP/ID เครื่อง” หรือ “การจำกัดการเปิดหลายไคลเอนต์” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE](https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse) · Microsoft · ถ้า bind พอร์ตเดียวกันเป็นตัวที่สองด้วย SO_REUSEADDR จะแย่งพอร์ตไป และไม่รู้ว่าซ็อกเก็ตไหนจะได้รับแพ็กเก็ต - [bind function (winsock.h)](https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind) · Microsoft · ถ้า bind พอร์ต 0 จะได้พอร์ตที่ไม่ซ้ำจากช่วง dynamic port (49152–65535) - [netstat](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/netstat) · Microsoft · -a แสดงพอร์ต TCP/UDP, -n แสดงที่อยู่เป็นตัวเลข, -o แสดง process ID (PID), -p udp แสดงเฉพาะ UDP #### pt-session-key · บั๊กแยกเซสชันด้วย IP/ID เครื่อง · Session keyed by IP or machine ID ถ้าเซิร์ฟเวอร์หรือเซิร์ฟเวอร์ตัวกลางแยกการเชื่อมต่อด้วย IP หรือ ID เครื่อง สองไคลเอนต์ใน PC เดียวกัน (public IP เดียวกัน) จะถูกมองเป็นคนเดียว - ทำไม → ผลคือ → บนหน้าจอ: สร้างตารางเซสชันโดยใช้ IP หรือ IP+ID เครื่องเป็นคีย์ → ข้อมูลของไคลเอนต์ตัวที่สองเขียนทับหรือปนกับเซสชันแรก → ฝั่งหนึ่งมองไม่เห็น NPC อีกฝั่งหลุดหรือได้รับข้อมูลของคนอื่น - อาการ: มองไม่เห็น/ตัวผี, หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ, คนในบ้านเดียวกัน, บางพื้นที่/บาง ISP / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: ทั้งเซิร์ฟเวอร์และเซิร์ฟเวอร์ตัวกลางต้องแยกการเชื่อมต่อด้วย session token ที่ไม่ซ้ำกัน, ต้องแก้ให้ได้เพราะหลายคนในบ้านเดียวกัน (หลัง NAT ของเราเตอร์) และผู้ใช้เครือข่ายมือถือที่ ISP แบ่ง IP เดียวให้ผู้ใช้หลายราย (CGNAT) ก็เจอปัญหาเดียวกัน ไคลเอนต์: ใช้ session token ที่ได้รับแยกกันสำหรับแต่ละไคลเอนต์ที่เปิด - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนเซสชันพร้อมกันจาก public IP เดียวกัน, จำนวนครั้งที่เซสชันถูกเขียนทับ) - จุดที่ต้องดู: บันทึกคีย์ที่ใช้ค้นหาเซสชัน, session token และ IP/พอร์ตของไคลเอนต์ ลงใน log ของเซิร์ฟเวอร์และเซิร์ฟเวอร์ตัวกลาง แล้วดูว่าเซสชันเดิมเปลี่ยนไปหรือไม่ในจังหวะที่มีการเชื่อมต่อที่สองจาก IP เดียวกัน จำลองอาการได้โดยเปิดสองไคลเอนต์ใน PC เดียวกันตามลำดับ - สัญญาณว่าใช่: ทันทีที่ไคลเอนต์ตัวที่สองเชื่อมต่อ ที่อยู่หรือข้อมูลตัวละครของเซสชันแรกเปลี่ยน และผู้เล่นคนอื่นที่อยู่หลังเราเตอร์เดียวกันหรือเครือข่ายมือถือ (CGNAT) ก็หลุดแบบเดียวกัน - สัญญาณว่าไม่ใช่: สองเซสชันจาก IP เดียวกันแยกกันด้วย token ต่างกัน: ไม่ใช่สาเหตุนี้ ถ้าสองโปรเซสใช้พอร์ตภายในเครื่องเดียวกัน น่าจะเป็น “พอร์ต UDP แบบตายตัวชนกัน” - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [RFC 6269: Issues with IP Address Sharing](https://www.rfc-editor.org/rfc/rfc6269) · IETF · เมื่อผู้ใช้บริการหลายรายใช้ IPv4 address เดียวกันผ่าน NAT หรือ CGN จะแยกผู้ใช้ด้วย IP อย่างเดียวไม่ได้ #### pt-multiclient · การจำกัดการเปิดหลายไคลเอนต์ · Multi-client restriction policy ถ้าโมดูลความปลอดภัยหรือนโยบายของเซิร์ฟเวอร์จำกัดการเปิดหลายไคลเอนต์ใน PC เดียว ไคลเอนต์ตัวที่สองจะเปิดหรือเชื่อมต่อไม่ได้ หรือตัวที่เปิดก่อนจะหลุด บางเกมบล็อกแค่บางฟีเจอร์ของไคลเอนต์ที่เปิดเพิ่ม - ทำไม → ผลคือ → บนหน้าจอ: โมดูลความปลอดภัยตรวจพบการเปิดซ้ำ หรือเซิร์ฟเวอร์จำกัดการเชื่อมต่อเพิ่มจากเครื่องเดียวกัน → ปฏิเสธการเปิดหรือการเชื่อมต่อครั้งที่สอง หรือตัดฝั่งหนึ่งออก บางกรณีบล็อกเฉพาะบางฟีเจอร์ของไคลเอนต์ที่เปิดเพิ่ม → เข้าเกมไม่ได้หรือฝั่งหนึ่งหลุด ในเกมที่บล็อกแค่ฟีเจอร์ ฝั่งหนึ่งจะมองไม่เห็น NPC หรือร้านค้า - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ, มองไม่เห็น/ตัวผี, หลุด / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ถ้าจะจำกัด ให้แสดงข้อความแจ้งที่ชัดเจน, ตั้งข้อยกเว้นสำหรับ QA ในโมดูลความปลอดภัย เซิร์ฟเวอร์: ตั้งข้อยกเว้นสำหรับ QA ในการจำกัดการเชื่อมต่อจากเครื่องเดียวกันด้วย - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนการปฏิเสธ/การหลุดแยกตามสาเหตุ (เชื่อมต่อซ้ำ)) - จุดที่ต้องดู: ดูข้อความที่ขึ้นตอนเปิดไคลเอนต์ตัวที่สอง และข้อความตอนหลุดของฝั่งที่เปิดก่อน ตรวจว่า log การปฏิเสธการเชื่อมต่อหรือการบังคับออกของเซิร์ฟเวอร์มีรหัสสาเหตุ เช่น เชื่อมต่อซ้ำหรือเครื่องเดียวกัน หรือไม่ - สัญญาณว่าใช่: ทันทีที่เปิดหรือเชื่อมต่อครั้งที่สอง มีข้อความปฏิเสธขึ้น หรือฝั่งที่เปิดก่อนหลุดด้วยสาเหตุเชื่อมต่อซ้ำ และถ้าเปิดไคลเอนต์ตัวเดียวก็ไม่มีปัญหา - สัญญาณว่าไม่ใช่: เชื่อมต่อได้ทั้งสองฝั่งโดยไม่มีสาเหตุการปฏิเสธหรือการหลุด แต่ฝั่งเดียวมองไม่เห็น NPC: น่าจะเป็น “พอร์ต UDP แบบตายตัวชนกัน”, “บั๊กแยกเซสชันด้วย IP/ID เครื่อง” หรือสาเหตุฝั่งการโหลดและการแสดงผล - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [CreateMutexW function (synchapi.h)](https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexw) · Microsoft · ถ้ามี named mutex อยู่แล้วจะคืน ERROR_ALREADY_EXISTS ใช้ตรวจการเปิดซ้ำและจำกัดให้เปิดได้ครั้งเดียว #### pt-background · จำกัดการประมวลผลของหน้าต่างเบื้องหลัง · Background window throttling สำหรับไคลเอนต์ที่เป็นหน้าต่างเบื้องหลัง ตัวเกม เอนจิน และ OS จะลดเฟรมและการประมวลผลลง แพ็กเก็ตที่ได้รับจึงประมวลผลไม่ทันจนกองรอหรือล้น - ทำไม → ผลคือ → บนหน้าจอ: การจำกัดเฟรมของหน้าต่างเบื้องหลังจากออปชันเกมหรือไดรเวอร์การ์ดจอ (เช่น ไดรเวอร์ NVIDIA ตั้งได้ตั้งแต่ 20–200 เฟรมต่อวินาที), โหมดประหยัดพลังงาน, การตั้งค่าหยุดทำงานเบื้องหลังของเอนจิน OS เองก็ให้ CPU/GPU กับหน้าต่างที่อยู่ด้านหน้า (foreground) ก่อน → จำนวนแพ็กเก็ตที่ประมวลผลต่อเฟรมลดลงจนคิวสะสม และถ้า receive buffer ล้นจะถูกทิ้ง → เมื่อดึงหน้าต่างขึ้นมาด้านหน้า ทุกอย่างโผล่มารวดเดียว หรือ NPC บางตัวไม่โผล่เลย - อาการ: มองไม่เห็น/ตัวผี, กรอเร็ว, หลุด / ปัจจัย: การหยุดชะงัก, แพ็กเก็ตหาย - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ / เกิดเมื่อไร: หลังอยู่เฉย ๆ สักพัก, ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: รับข้อมูลเครือข่ายต่อเนื่องในเธรดที่แยกจากลูปของเกม, รับประกันการประมวลผลขั้นต่ำแม้อยู่เบื้องหลัง, เปิดการตั้งค่าให้เอนจินทำงานเบื้องหลัง (Unity คือ runInBackground) - งานฝั่งภายนอก: แนะนำให้ผู้เล่นปิดการจำกัดเฟรมของหน้าต่างเบื้องหลังในไดรเวอร์การ์ดจอ และปิดโหมดประหยัดพลังงานของ PC - ตัวเลขที่ควรรู้: ใน Unity ถ้าปิดการตั้งค่า runInBackground ลูปของเกมจะหยุดทันทีที่หน้าต่างเสียโฟกัส ถ้ารับแพ็กเก็ตเฉพาะในลูปนั้น ระหว่างนั้นแพ็กเก็ตจะไม่ถูกประมวลผลเลย - บนกราฟ: ขาดช่วงแล้วมารวดเดียว (ช่วงห่างระหว่างเฟรมของไคลเอนต์, จำนวนแพ็กเก็ตที่ประมวลผลต่อเฟรม) - จุดที่ต้องดู: บน PC เดียวกัน ให้หน้าต่างหนึ่งอยู่ด้านหน้า อีกหน้าต่างอยู่ด้านหลัง แล้วสลับบทบาทเปรียบเทียบ วัดช่วงห่างระหว่างเฟรมของทั้งสองโปรเซสด้วย PresentMon ถ้ามี log ฝั่งเกม ให้ดูสถานะโฟกัสของหน้าต่างและจำนวนแพ็กเก็ตที่ประมวลผลในแต่ละเฟรม - สัญญาณว่าใช่: เฉพาะตอนเป็นหน้าต่างเบื้องหลังที่ช่วงห่างระหว่างเฟรมเพิ่มขึ้นมาก (ถ้าเป็นการจำกัดของไดรเวอร์ จะแบนราบที่ช่วงห่างที่ตรงกับจำนวนเฟรมที่ตั้งไว้) หรือการประมวลผลหยุด และเมื่อสลับหน้าต่าง ปัญหาก็ย้ายไปอีกไคลเอนต์ - สัญญาณว่าไม่ใช่: หน้าต่างที่อยู่ด้านหน้าก็เป็นเหมือนกัน: ไม่ได้เกิดจากการจำกัดของหน้าต่างเบื้องหลัง ถ้าผิดปกติแต่ไคลเอนต์ตัวเดิมเสมอโดยไม่เกี่ยวกับตำแหน่งหน้าต่าง น่าจะเป็นตัวเลือกการแสดงผลหรือเวอร์ชันต่างกัน - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [Application.runInBackground](https://docs.unity3d.com/ScriptReference/Application-runInBackground.html) · Unity · ค่าเริ่มต้นของ runInBackground คือ false และเมื่อเป็นเช่นนั้นแอปจะหยุดชั่วคราวเมื่ออยู่เบื้องหลัง - [Manage 3D Settings (reference) — NVIDIA Control Panel Help](https://www.nvidia.com/content/Control-Panel-Help/vLatest/en-us/mergedProjects/nv3d/Manage_3D_Settings_(reference).htm) · NVIDIA · Background Application Max Frame Rate: จำกัดเฟรมสูงสุดของเกมที่อยู่เบื้องหลังไว้ที่ 20–200 เฟรมต่อวินาที - [Priority Boosts](https://learn.microsoft.com/en-us/windows/win32/procthread/priority-boosts) · Microsoft · Windows เพิ่ม priority ของโปรเซสที่เป็นหน้าต่าง foreground ให้ไม่ต่ำกว่าโปรเซสเบื้องหลัง - [PresentMon README](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README.md) · Intel · เครื่องมือเก็บเฟรมไทม์ของ CPU, GPU และจอแสดงผลของแอปกราฟิกบน Windows แยกตามแอป #### pt-asset-lock · การเข้าถึงไฟล์แคช/แอสเซ็ตพร้อมกันจนชนกัน · Shared cache / asset file lock conflicts ถ้าสองไคลเอนต์เขียนลงโฟลเดอร์แคชเดียวกันพร้อมกันหรือล็อกไฟล์ไว้ ฝั่งหนึ่งจะโหลดโมเดลหรือเท็กซ์เจอร์ของ NPC ไม่ได้ - ทำไม → ผลคือ → บนหน้าจอ: สองไคลเอนต์เขียนไฟล์แคชหรือไฟล์แพตช์ในโฟลเดอร์ติดตั้งเดียวกันพร้อมกัน → ล็อกไฟล์ไม่สำเร็จ หรืออ่านไฟล์ที่เขียนไม่เสร็จ จึงโหลดล้มเหลว → NPC ที่มีป้ายชื่อแต่ไม่มีโมเดลตัวละคร หรือโปร่งใส - อาการ: มองไม่เห็น/ตัวผี / ปัจจัย: การหยุดชะงัก - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง, ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แยกโฟลเดอร์แคชตามไคลเอนต์, ลองใหม่เมื่อล็อกไฟล์ไม่สำเร็จ, ถ้าโหลดล้มเหลวให้แสดงโมเดลพื้นฐานไว้ก่อน - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนการโหลดแอสเซ็ตล้มเหลวแยกตามไคลเอนต์) - จุดที่ต้องดู: บน PC ของผู้เล่น ใช้ Process Monitor กรองเฉพาะ path ของโฟลเดอร์ติดตั้งและแคชของเกม แล้วดูผลการเปิดและเขียนไฟล์ของสองโปรเซสเกม ถ้ามี log ของไคลเอนต์ ให้หาการโหลดแอสเซ็ตล้มเหลวและรหัสข้อผิดพลาดการเปิดไฟล์ (ERROR_SHARING_VIOLATION) - สัญญาณว่าใช่: การเปิดไฟล์ของโมเดลที่มองไม่เห็นจบลงด้วย sharing violation หรือล็อกไม่สำเร็จ และในเวลาเดียวกันไคลเอนต์อีกตัวกำลังเขียนไฟล์นั้น ถ้าเปิดตัวเดียวหรือแยกโฟลเดอร์ติดตั้ง/แคช อาการจะหายไป - สัญญาณว่าไม่ใช่: เปิดไคลเอนต์ตัวเดียวแล้วโมเดลเดิมยังไม่ขึ้น: น่าจะเป็นไฟล์เสียหรือ “เวอร์ชันไคลเอนต์/ข้อมูลไม่ตรงกัน” ถ้าเปิดไฟล์ได้ปกติแต่ไม่ถูกวาด น่าจะเป็น “หน่วยความจำ/VRAM ไม่พอจนสตรีมล้มเหลว” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [Creating and Opening Files](https://learn.microsoft.com/en-us/windows/win32/fileio/creating-and-opening-files) · Microsoft · ไฟล์ที่เปิดโดยไม่มี share mode โปรเซสอื่นจะเปิดไม่ได้ และเกิด ERROR_SHARING_VIOLATION - [Process Monitor](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon) · Microsoft · บันทึกกิจกรรมของ file system, registry และโปรเซสแบบเรียลไทม์ และกรองได้ด้วยทุกฟิลด์ รวมถึง path #### pt-vram · หน่วยความจำ/VRAM ไม่พอจนสตรีมล้มเหลว · Memory / VRAM exhaustion ถ้าสองไคลเอนต์แบ่งหน่วยความจำกราฟิกกันใช้ จะไม่มีที่ให้โหลดโมเดลหรือเท็กซ์เจอร์ที่ต้องใช้ใหม่ บางส่วนจึงไม่ถูกวาด - ทำไม → ผลคือ → บนหน้าจอ: สองไคลเอนต์แบ่ง VRAM และ RAM กันใช้ บางครั้ง OS ลดโควตาหน่วยความจำกราฟิกของหน้าต่างเบื้องหลังก่อน → เอนจินโหลดโมเดลหรือเท็กซ์เจอร์ใหม่ไม่ได้ หรือเอาออกแล้วโหลดใหม่ซ้ำไปมา → NPC โผล่ช้า ภาพเบลอ หรือมองไม่เห็น, กระตุก - อาการ: มองไม่เห็น/ตัวผี, กระตุก / ปัจจัย: การหยุดชะงัก - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ / เกิดเมื่อไร: ระหว่างเดินทาง/ย้ายแมพ, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: ปรับคุณภาพอัตโนมัติให้อยู่ในงบหน่วยความจำ, แสดงโมเดลทดแทนเมื่อโหลดล้มเหลว - งานฝั่งภายนอก: แนะนำผู้เล่นที่เปิดสองไคลเอนต์พร้อมกันให้ลดคุณภาพกราฟิกหรือใช้โหมดสเปกต่ำ, แจ้งสเปก VRAM และ RAM ที่แนะนำ - บนกราฟ: ชนเพดานแล้วแบนราบ (ปริมาณการใช้ dedicated GPU memory แยกตามโปรเซส) - จุดที่ต้องดู: ใน Task Manager บน PC ของผู้เล่น เพิ่มคอลัมน์ Dedicated GPU memory ในแท็บ “Details” แล้วเทียบผลรวมการใช้ของสองไคลเอนต์กับความจุ VRAM ของการ์ดจอ ฝั่งเกมให้บันทึกงบ (Budget) และปริมาณที่ใช้อยู่ (CurrentUsage) ที่ QueryVideoMemoryInfo ของ DXGI รายงาน - สัญญาณว่าใช่: ผลรวมการใช้ของสองไคลเอนต์แบนราบใกล้ความจุ VRAM และการโหลดโมเดลหรือเท็กซ์เจอร์ล้มเหลวกระจุกในช่วงที่ปริมาณที่ใช้อยู่เกินงบ ถ้าลดคุณภาพหรือเปิดตัวเดียว อาการจะหายไป - สัญญาณว่าไม่ใช่: VRAM ยังเหลือแต่ยังมองไม่เห็น: น่าจะเป็น “การเข้าถึงไฟล์แคช/แอสเซ็ตพร้อมกันจนชนกัน” หรือ “ตัวเลือกการแสดงผลต่างกัน” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [Residency (Direct3D 12)](https://learn.microsoft.com/en-us/windows/win32/direct3d12/residency) · Microsoft · งบ video memory อาจลดลงมากเมื่อสลับไปแอปอื่น และถ้าเกินงบจะหยุดชะงักหรือสร้าง resource ไม่สำเร็จ ถ้าไม่ได้อยู่ foreground ส่วนที่จองไว้ก็ไม่รับประกัน - [GPUs in the task manager](https://devblogs.microsoft.com/directx/gpus-in-the-task-manager/) · Microsoft · ถ้าเพิ่มคอลัมน์ในแท็บ Details ของ Task Manager จะเห็นการใช้ dedicated และ shared GPU memory แยกตามโปรเซส dedicated GPU memory คือ VRAM ของการ์ดจอ - [DXGI_QUERY_VIDEO_MEMORY_INFO structure (dxgi1_4.h)](https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_4/ns-dxgi1_4-dxgi_query_video_memory_info) · Microsoft · Budget (งบ video memory ที่ OS กำหนด) และ CurrentUsage (ปริมาณที่แอปใช้อยู่) ถ้าใช้เกินงบอาจเกิดอาการกระตุก #### pt-display-option · ตัวเลือกการแสดงผลต่างกัน · Different display settings ถ้าตัวเลือกอย่างการจำกัดจำนวนตัวละครที่แสดง, การซ่อนป้ายชื่อหรือโมเดล NPC และโหมดสเปกต่ำ ตั้งไว้ต่างกันในสองไคลเอนต์ สิ่งที่มองเห็นก็จะต่างกัน - ทำไม → ผลคือ → บนหน้าจอ: ไคลเอนต์ฝั่งเดียวที่ตั้ง “จำกัดจำนวนตัวละครรอบตัวที่แสดง” หรือโหมดสเปกต่ำ → ไม่วาด NPC ที่อยู่ไกลหรือมีลำดับความสำคัญต่ำ (ปกติ) → NPC หายไปฝั่งเดียว - อาการ: มองไม่เห็น/ตัวผี / ปัจจัย: การหยุดชะงัก - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ, เราคนเดียว / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ตลอดเวลา - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แสดงให้รู้ว่าเป็นเอนทิตีที่ซ่อนด้วยตัวเลือก, แยกไฟล์ตั้งค่าตามไคลเอนต์เพื่อไม่ให้ปนกัน - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนเอนทิตีที่วาดบนจอแยกตามไคลเอนต์) - จุดที่ต้องดู: เทียบการตั้งค่าจำกัดจำนวนตัวละครที่แสดง, การซ่อนป้ายชื่อ/โมเดล และโหมดสเปกต่ำของสองไคลเอนต์แบบเคียงกัน แล้วลองตั้งฝั่งหนึ่งให้เหมือนอีกฝั่ง ตรวจด้วยว่าสองไคลเอนต์ใช้ไฟล์ตั้งค่าไฟล์เดียวกันจนเขียนทับกันหรือไม่ - สัญญาณว่าใช่: เมื่อตั้งค่าให้ตรงกัน สองจอก็เห็นเหมือนกัน และ NPC ที่มองไม่เห็นคือเอนทิตีที่อยู่ไกลเกินจำนวนที่จำกัดการแสดง หรือมีลำดับความสำคัญต่ำ - สัญญาณว่าไม่ใช่: ตั้งค่าเหมือนกันแล้วยังหายไปฝั่งเดียว: น่าจะเป็น “แชนแนล/อินสแตนซ์/phasing ต่างกัน” หรือข้อความแจ้งการปรากฏตัวหาย - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [Changing the Quantity of Characters Displayed On-screen (FINAL FANTASY XIV UI Guide)](https://na.finalfantasyxiv.com/uiguide/faq/faq-other/setting_ch_quantity.html) · Square Enix · ใช้การตั้งค่าจำกัดการแสดง (Character and Object Quantity) ปรับจำนวนตัวละครและออบเจ็กต์ที่วาดบนจอ #### pt-version · เวอร์ชันไคลเอนต์/ข้อมูลไม่ตรงกัน · Client version / data table mismatch ถ้าไคลเอนต์ตัวที่สองเป็นตัวติดตั้งอื่นหรือแพตช์ยังไม่ครบ จะไม่รู้จัก NPC ID ใหม่ที่เซิร์ฟเวอร์ส่งมา และข้ามไปเงียบ ๆ - ทำไม → ผลคือ → บนหน้าจอ: ตัวติดตั้งในโฟลเดอร์อื่น หรือไคลเอนต์ที่เปิดระหว่างลงแพตช์ → ถ้าได้รับ NPC ID หรือ model ID ที่ไม่รู้จัก ก็ข้ามไป → เฉพาะ NPC ที่เพิ่มเข้ามาใหม่ที่มองไม่เห็นในฝั่งเดียว - อาการ: มองไม่เห็น/ตัวผี / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ส่งเวอร์ชันข้อมูลตอนเชื่อมต่อ, ถ้าได้รับ ID ที่ไม่รู้จักให้บันทึก log และแสดงสิ่งทดแทน เซิร์ฟเวอร์: ตรวจเวอร์ชันข้อมูลตอนเชื่อมต่อ ถ้าไม่ตรงให้ปฏิเสธการเชื่อมต่อและแนะนำให้ลงแพตช์ - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวนครั้งที่ได้รับ ID ที่ไม่รู้จักแยกตามเวอร์ชันไคลเอนต์) - จุดที่ต้องดู: เทียบ path ของไฟล์โปรแกรมและเวอร์ชันไคลเอนต์/ข้อมูลที่แสดงบนจอหรือใน log ของสองไคลเอนต์ ฝั่งเกมให้บันทึกเวอร์ชันข้อมูลที่ส่งตอนเชื่อมต่อ และจำนวนครั้งที่ได้รับ NPC ID หรือ model ID ที่ไม่รู้จักแล้วข้ามไป - สัญญาณว่าใช่: เวอร์ชันหรือโฟลเดอร์ติดตั้งของสองไคลเอนต์ต่างกัน NPC ที่มองไม่เห็นเป็นตัวที่เพิ่มมาในแพตช์ล่าสุด และตัวติดตั้งที่ลงแพตช์ครบแล้วมองเห็น - สัญญาณว่าไม่ใช่: เวอร์ชันและโฟลเดอร์ติดตั้งเหมือนกันแต่หายไปฝั่งเดียว: น่าจะเป็น “แชนแนล/อินสแตนซ์/phasing ต่างกัน” หรือสาเหตุฝั่งการโหลดและการส่ง - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [NetworkConfig class (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/api/Unity.Netcode.NetworkConfig.html) · Unity · ถ้า ProtocolVersion ต่างกันจะไม่สื่อสารกัน และ ForceSamePrefabs ตรวจความต่างของรายการ prefab ตอนเชื่อมต่อ #### pt-priority · งบการส่ง/ลำดับความสำคัญต่อการเชื่อมต่อ · Per-connection bandwidth budget and priority ถ้าเซิร์ฟเวอร์จำกัดปริมาณที่ส่งต่อการเชื่อมต่อและส่งสิ่งที่อยู่ใกล้ก่อน ฝั่งที่ถูกตั้งเพดานไว้ต่ำจะได้รับ NPC ที่อยู่ไกลช้าหรือไม่ได้รับเลย - ทำไม → ผลคือ → บนหน้าจอ: ในที่ที่คนเยอะ เซิร์ฟเวอร์ส่งตามลำดับความสำคัญภายในเพดานปริมาณการส่งของแต่ละการเชื่อมต่อ → การเชื่อมต่อที่ค่าประเมินแบนด์วิดท์ต่ำ (เช่น ฝั่งที่เป็นหน้าต่างเบื้องหลังจึงส่งสัญญาณยืนยันการรับช้า) จะเลื่อนเอนทิตีลำดับท้าย ๆ ออกไปเรื่อย ๆ → NPC ที่อยู่ไกลโผล่ช้าหรือมองไม่เห็นในฝั่งเดียว - อาการ: มองไม่เห็น/ตัวผี, อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ, บางจุด/บางแชนแนล / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: เพิ่มลำดับความสำคัญของเอนทิตีที่ถูกเลื่อนตามเวลาที่ผ่านไป (กัน starvation), รับประกันรอบการอัปเดตขั้นต่ำ ไคลเอนต์: ส่งสัญญาณยืนยันการรับให้ทันเวลาแม้อยู่เบื้องหลัง เพื่อไม่ให้ค่าประเมินแบนด์วิดท์ลดลง - บนกราฟ: สูงตามจำนวนคนและโหลด (จำนวนเอนทิตีที่ถูกเลื่อนแยกตามการเชื่อมต่อ, ปริมาณการส่งแยกตามการเชื่อมต่อ) - จุดที่ต้องดู: ให้เซิร์ฟเวอร์บันทึกไบต์ที่ส่งในแต่ละทิก, เพดานการส่ง (แบนด์วิดท์ที่ประเมิน), จำนวนเอนทิตีที่ส่งไม่ได้และถูกเลื่อน และเวลาที่ผ่านไปนับจากส่งครั้งล่าสุดของแต่ละเอนทิตี แยกตามการเชื่อมต่อ ใน Unreal ใช้ Networking Insights ดูขนาดแพ็กเก็ตของแต่ละการเชื่อมต่อและเอนทิตีที่ replicate อยู่ในนั้นได้ - สัญญาณว่าใช่: NPC ที่มองไม่เห็นเป็นเอนทิตีที่ถูกเลื่อนมานานในการเชื่อมต่อนั้น เพดานของการเชื่อมต่อนั้นต่ำกว่าการเชื่อมต่ออื่น และยิ่งคนแน่น เอนทิตีที่ถูกเลื่อนยิ่งเพิ่ม - สัญญาณว่าไม่ใช่: ไม่มีเอนทิตีที่ถูกเลื่อน และส่ง NPC นั้นตรงเวลาแล้ว: น่าจะเป็นขั้นหลังการส่ง (receive buffer, การโหลด, ตัวเลือกการแสดงผล) ถ้าทุกการเชื่อมต่อชนเพดานหมด น่าจะเป็นปัญหาปริมาณการส่งของทั้งเซิร์ฟเวอร์หรือการออกแบบระยะมองเห็น - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [Actor Priority in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-priority-in-unreal-engine) · Epic Games · เมื่อแบนด์วิดท์อิ่มตัว จะเลือก actor ที่จะ replicate ตามลำดับความสำคัญ (ระยะทาง, แนวสายตา, เวลาที่ผ่านไปนับจาก replicate ครั้งล่าสุด) ไม่ใช่ทุก actor จะถูก replicate ทุกครั้ง - [State Synchronization](https://gafferongames.com/post/state_synchronization/) · Gaffer On Games · สะสมลำดับความสำคัญ: เอนทิตีที่ใส่ในแพ็กเก็ตรอบนี้ไม่ได้จะได้เข้าแพ็กเก็ตถัดไปก่อน และเพดานแบนด์วิดท์ปรับแบบเรียลไทม์ - [Networking Insights in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/networking-insights-in-unreal-engine) · Epic Games · แสดงขนาดแพ็กเก็ตที่รับส่งในแต่ละการเชื่อมต่อ และเอนทิตี/property ที่ replicate อยู่ในนั้น #### pt-clock-hold · เอนทิตีถูกพักไว้เพราะประมาณเวลาคลาดเคลื่อน · Clock estimate error holds or discards entities ถ้าเวลาเซิร์ฟเวอร์ที่ไคลเอนต์ประมาณไว้ผิด ข้อมูลเอนทิตีที่เพิ่งมาถึงจะถูกพักไว้เพราะ “ยังเป็นอนาคต” หรือถูกทิ้งเพราะ “เก่าเกินไป” - ทำไม → ผลคือ → บนหน้าจอ: การประมาณเวลาเซิร์ฟเวอร์ของไคลเอนต์ฝั่งหนึ่งคลาดไปมาก (วัดระหว่างโหลด, เพิ่งตื่นจากโหมดประหยัดพลังงาน) → เวลาอ้างอิงของ interpolation ไม่ตรงกับเวลาของข้อมูลเอนทิตี → เอนทิตีโผล่ช้าหรือดูหยุดนิ่งอยู่กับที่ - อาการ: มองไม่เห็น/ตัวผี, กระตุก / ปัจจัย: ความหน่วง - ใครเจอ: ไคลเอนต์เดียวในเครื่องที่เปิดหลายจอ / เกิดเมื่อไร: หลังอยู่เฉย ๆ สักพัก, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ซิงก์เวลาใหม่เป็นระยะ และถ้าต่างกันมากให้รีเซ็ตทันที, ไม่ใช้ค่าที่วัดระหว่างโหลดหรือทันทีหลังตื่นจากโหมดประหยัดพลังงาน - บนกราฟ: สูงเฉพาะบางกลุ่ม (ความคลาดเคลื่อนของการประมาณเวลาเซิร์ฟเวอร์แยกตามไคลเอนต์) - จุดที่ต้องดู: ให้ไคลเอนต์บันทึกเวลาเซิร์ฟเวอร์ที่ประมาณ, RTT, เวลาที่ซิงก์เวลาใหม่ และจำนวนครั้งที่พักหรือทิ้งข้อมูลเอนทิตี ลองจำลองอาการทันทีหลังโหลดหรือหลังตื่นจากโหมดประหยัดพลังงาน - สัญญาณว่าใช่: เฉพาะไคลเอนต์ที่มีปัญหาที่ความคลาดเคลื่อนเกินเกณฑ์รีเซ็ต (Unity คือ hardResetThresholdSec ค่าเริ่มต้น 0.2 วินาที) มีบันทึกว่าพักข้อมูลเอนทิตีไว้เพราะเป็นอนาคตหรือทิ้งเพราะเป็นอดีต และเมื่อซิงก์เวลาใหม่ก็ปกติทันที - สัญญาณว่าไม่ใช่: ความคลาดเคลื่อนต่ำแต่โผล่ช้า: น่าจะเป็น “งบการส่ง/ลำดับความสำคัญต่อการเชื่อมต่อ” หรือสาเหตุฝั่งการโหลด - วิธีตรวจ: ต้องมี log หรือเมตริกจากเซิร์ฟเวอร์/ไคลเอนต์เกม - แหล่งอ้างอิง: - [NetworkTimeSystem class (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/api/Unity.Netcode.NetworkTimeSystem.html) · Unity · ถ้าเวลาต่างกันเกิน hardResetThresholdSec (ค่าเริ่มต้น 0.2 วินาที) จะบังคับให้ตรงทันที ปกติจะใช้ adjustmentRatio ปรับให้เร็วขึ้นหรือช้าลงทีละน้อย - [NetworkTime and ticks (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/advanced-topics/networktime-ticks.html) · Unity · LocalTime นำหน้าเซิร์ฟเวอร์ ส่วน ServerTime ตามหลัง ข้อความที่มาถึงช้าอาจมีเวลารอติดลบได้ ### ต้นเหตุของการส่งซ้ำใน TCP (20 สาเหตุ) #### rt-wireless · แพ็กเก็ตหายช่วงไร้สาย · Wi-Fi / cellular link loss Wi-Fi และเครือข่ายมือถือจะลองส่งซ้ำในช่วงไร้สายอยู่หลายครั้ง ถ้ายังไม่สำเร็จก็จะทิ้งแพ็กเก็ตนั้น แล้ว TCP จะส่งแพ็กเก็ตที่ถูกทิ้งใหม่หลังจากนั้นพักใหญ่ - ทำไม → ผลคือ → บนหน้าจอ: สัญญาณอ่อนหรือถูกรบกวนหนัก การส่งในช่วงไร้สายจึงล้มเหลวติดต่อกัน → อุปกรณ์ไร้สายลองใหม่จนเกินขีดจำกัด (ปกติไม่กี่ครั้งถึงสิบกว่าครั้ง) แล้วทิ้งแพ็กเก็ต → ค้างนานเท่ากับเวลาที่ TCP รอส่งซ้ำ แพ็กเก็ตที่ตามมารออยู่ใน receive buffer แล้วทะลักออกมาเป็นอาการกรอเร็ว - อาการ: ค้าง, กรอเร็ว, วาร์ป / ปัจจัย: แพ็กเก็ตหาย, จิตเตอร์ - ใครเจอ: เราคนเดียว, คนในบ้านเดียวกัน / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ระหว่างเดินทาง/ย้ายแมพ - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: เปิด TCP_NODELAY (ถ้าเปิด Nagle ไว้ จะไม่มีแพ็กเก็ตที่ตามมาให้ RACK ใช้ตัดสินว่าหาย), ระหว่างที่การส่งติดรอการส่งซ้ำ ให้ส่งเฉพาะอัปเดตสถานะล่าสุดโดยไม่สะสมอันเก่าไว้ (จำกัดปริมาณที่กองรอในเคอร์เนลด้วย TCP_NOTSENT_LOWAT) ไคลเอนต์: เปิด TCP_NODELAY (แพ็กเก็ตที่หายในทิศทางที่ส่งอินพุตของเรา OS ฝั่งไคลเอนต์เป็นผู้กู้คืน), แสดงสถานะเครือข่ายบนจอเมื่อแพ็กเก็ตหายติด ๆ กันหรือปิงพุ่ง - งานฝั่งทีมอินฟรา: เร่งการกู้คืนแพ็กเก็ตหายด้วย RACK-TLP (เซิร์ฟเวอร์ป้องกันการหายในช่วงไร้สายไม่ได้ ทำได้แค่กู้คืนให้เร็วขึ้น), ตรวจว่าค่าเริ่มต้นของ Linux รุ่นใหม่ net.ipv4.tcp_recovery=1 (RACK) และ net.ipv4.tcp_early_retrans=3 (TLP) ไม่ถูกเปลี่ยน - งานฝั่งภายนอก: แนะนำให้ผู้เล่นใช้สาย LAN, ใช้ 5 GHz หรือ 6 GHz, ย้ายตำแหน่งเราเตอร์หรือเปลี่ยนช่องสัญญาณ - ตัวเลขที่ควรรู้: ถ้าแพ็กเก็ตหายในช่วงไร้สาย 1% แพ็กเก็ตเกม 100 ตัวจะหายไป 1 ตัว ถ้ารับ 10 ตัวใน 1 วินาที ก็จะหยุดแวบราวทุก 10 วินาที ถ้าไม่มี RACK-TLP แต่ละครั้งจะหยุดนานเท่ากับ RTO (ปิง + 200 ms ขึ้นไป) - บนกราฟ: สูงเฉพาะบางกลุ่ม (อัตราการส่งซ้ำต่อการเชื่อมต่อ, RTT (ปิง) ต่อการเชื่อมต่อ) - จุดที่ต้องดู: ping หลายร้อยครั้งจาก PC ของผู้เล่นไปยังเราเตอร์ (gateway) และไปยังเซิร์ฟเวอร์เกม เทียบแพ็กเก็ตหายและช่วงแกว่งของความหน่วง แล้ววัดใหม่หลังเปลี่ยนเป็นสาย LAN หรือเน็ตมือถือ ฝั่งเซิร์ฟเวอร์ดู retrans และ rtt (ค่าเฉลี่ย/ส่วนเบี่ยงเบน) ของการเชื่อมต่อผู้เล่นคนนั้นด้วย ss -ti - สัญญาณว่าใช่: ping แค่ถึงเราเตอร์ก็เห็นแพ็กเก็ตหายหรือความหน่วงขึ้นลงไม่นิ่งแล้ว และปัญหาหายไปเมื่อเปลี่ยนเป็นสาย LAN ฝั่งเซิร์ฟเวอร์เห็น retrans และส่วนเบี่ยงเบนของ RTT สูงเฉพาะการเชื่อมต่อของผู้เล่นคนนั้น - สัญญาณว่าไม่ใช่: ถึงเราเตอร์ยังปกติ แต่แพ็กเก็ตเริ่มหายหลังจากนั้น: น่าจะเป็นฝั่ง ISP หรือเส้นทาง (“คิวคอขวดล้น”, “เส้นทางเปลี่ยน/เส้นทาง ECMP ที่เสีย”) ถ้าผู้เล่น ISP เดียวกันหลายคนแย่ลงพร้อมกัน ให้เริ่มดูจากช่วงของ ISP - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - รายละเอียดเพิ่มเติม: การลองใหม่ของอุปกรณ์ไร้สายทำให้เกิดจิตเตอร์ (ครั้งละหลาย ms) และจะกลายเป็นแพ็กเก็ตหายก็ต่อเมื่อเกินขีดจำกัดการลองใหม่เท่านั้น ยิ่งคุณภาพสัญญาณไร้สายแย่ลง อาการจึงหนักขึ้นตามลำดับ “จิตเตอร์ → ค้างเป็นครั้งคราว → ค้างบ่อย” ตอนที่ย้ายจากเราเตอร์ (AP) ตัวหนึ่งไปอีกตัว (โรมมิ่ง) บางครั้งแพ็กเก็ตหายต่อเนื่องนานหลายสิบ ms ถึงหลายวินาที ส่วนเครือข่ายมือถือส่งซ้ำมากในช่วงระหว่างเครื่องกับเสาสัญญาณ จึงมักแสดงออกมาเป็นความหน่วงพุ่งหลายร้อย ms มากกว่าแพ็กเก็ตหาย - กรณีจริง: ffxiv-2021 - แหล่งอ้างอิง: - [net/wireless/core.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/wireless/core.c?h=v6.12) · Linux kernel · ขีดจำกัดการลองใหม่เริ่มต้นของ wireless stack ใน Linux: เฟรมสั้น 7 ครั้ง, เฟรมยาว 4 ครั้ง (dot11ShortRetryLimit, dot11LongRetryLimit) - [RFC 3481: TCP over Second (2.5G) and Third (3G) Generation Wireless Networks](https://www.rfc-editor.org/rfc/rfc3481) · IETF · เครือข่ายมือถือมีการส่งซ้ำที่ชั้น link จึงมีแพ็กเก็ตหายระดับ IP น้อย แต่การกู้คืนนั้นแสดงออกมาเป็นจิตเตอร์และความหน่วงพุ่ง - [Wi-Fi roaming support in Apple devices](https://support.apple.com/guide/deployment/wi-fi-roaming-support-dep98f116c0f/web) · Apple · ตอนย้าย AP จะส่งข้อมูลไม่ได้จนกว่าการยืนยันตัวตนกับ AP ใหม่จะเสร็จ และในสภาพแวดล้อม 802.1X อาจใช้เวลาหลายวินาที - [RFC 8985: The RACK-TLP Loss Detection Algorithm for TCP](https://www.rfc-editor.org/rfc/rfc8985) · IETF · นิยามของ RACK (ตัดสินการหายตามเวลา) และ TLP (ส่งแพ็กเก็ตท้ายสุดซ้ำ) - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_recovery ค่าเริ่มต้น 0x1 (RACK), tcp_early_retrans ค่าเริ่มต้น 3 (เปิด TLP), จำกัดปริมาณข้อมูลที่ยังไม่ได้ส่งด้วย TCP_NOTSENT_LOWAT และ tcp_notsent_lowat - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · TCP_NODELAY ปิดอัลกอริทึม Nagle ข้อมูลเล็ก ๆ จึงถูกส่งทันที - [include/net/tcp.h](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp.h?h=v6.12) · Linux kernel · ค่าต่ำสุดของ RTO คือ TCP_RTO_MIN = 200 ms - [misc/ss.c](https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/misc/ss.c?h=v6.12.0) · iproute2 · ss -ti แสดง retrans:จำนวนที่กำลังส่งซ้ำอยู่ตอนนี้/จำนวนส่งซ้ำสะสม และ rtt:RTT/ส่วนเบี่ยงเบนของ RTT (rttvar) #### rt-queue-drop · คิวคอขวดล้น (แพ็กเก็ตหายจากความแออัด) · Tail drop at a congested bottleneck จุดที่แคบที่สุดบนเส้นทาง เช่น เราเตอร์, ลิงก์เชื่อมระหว่าง ISP หรือลิงก์ของดาต้าเซ็นเตอร์ เมื่อคิวตรงนั้นเต็ม แพ็กเก็ตที่มาใหม่จะถูกทิ้ง - ทำไม → ผลคือ → บนหน้าจอ: วิดีโอ, การดาวน์โหลด และทราฟฟิกของผู้ใช้คนอื่นทำให้ช่วงคอขวดเต็ม → ระหว่างที่คิวเต็ม แพ็กเก็ตที่มาใหม่ถูกทิ้งติดต่อกัน (tail drop) แพ็กเก็ตที่ไม่ถูกทิ้งก็ต้องรออยู่ท้ายคิวที่เต็มแน่น → แพ็กเก็ตหายหลายตัวพร้อมกัน จึงค้างนานแล้วตามด้วยอาการกรอเร็ว เป็นบ่อยช่วงหัวค่ำ - อาการ: ค้าง, กรอเร็ว, ดีดกลับ / ปัจจัย: แพ็กเก็ตหาย, ความหน่วง - ใครเจอ: คนในบ้านเดียวกัน, บางพื้นที่/บาง ISP, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ช่วงพีคหัวค่ำ, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: ภายนอก (ภายนอก), พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แสดงสถานะเครือข่ายบนจอเมื่อแพ็กเก็ตหายติด ๆ กันหรือปิงพุ่ง (แจ้งว่าอาจมีการส่งข้อมูลปริมาณมากบนเน็ตเดียวกัน) - งานฝั่งทีมอินฟรา: เผื่อแบนด์วิดท์ของลิงก์ดาต้าเซ็นเตอร์ให้พอ, ตรวจตัวนับการทิ้งจากคิว (output drops) ของลิงก์และพอร์ตสวิตช์ของเรา, ถ้าช่วงของ ISP ตันให้อ้อมไปใช้ลิงก์หรือ peering อื่น - งานฝั่งภายนอก: แนะนำให้ผู้เล่นเปิด SQM (fq_codel, CAKE) และ ECN บนเราเตอร์ (ให้ลดความเร็วก่อนที่คิวจะล้น), ขอให้ ISP ขยายช่วงที่เป็นคอขวด - ตัวเลขที่ควรรู้: ช่วงที่คิวล้น แพ็กเก็ตจำนวนมากที่เข้ามาในช่วงหลายสิบ ms นั้นจะหายไปพร้อมกัน แพ็กเก็ตมักหายติดกันและตัวที่ส่งซ้ำก็มักหายอีก จึงมักลากยาวไปจนถึง RTO - บนกราฟ: สูงเฉพาะบางช่วงเวลา (อัตราการส่งซ้ำ, RTT (ปิง)) - จุดที่ต้องดู: อัตราการส่งซ้ำของเซิร์ฟเวอร์ (ค่าที่เพิ่มขึ้นของ TcpRetransSegs ÷ TcpOutSegs จากการรัน nstat ทุก 1 นาที) และ RTT ต่อการเชื่อมต่อ แยกตามพื้นที่, ISP และช่วงเวลา ดูคู่กับการทิ้งขาออก (ifOutDiscards) ของลิงก์และพอร์ตสวิตช์ของเรา รัน mtr ไปยังพื้นที่ที่มีปัญหาทั้งช่วงพีคและช่วงที่คนน้อยแล้วเทียบกัน - สัญญาณว่าใช่: อัตราการส่งซ้ำขึ้นเฉพาะช่วงพีคหัวค่ำ และ RTT ขึ้นก่อนแพ็กเก็ตจะหาย (คิวกำลังเต็ม) ใน mtr แพ็กเก็ตหายและความหน่วงเพิ่มพร้อมกันตั้งแต่ hop หนึ่งไปจนสุดทาง เฉพาะช่วงพีค - สัญญาณว่าไม่ใช่: RTT ไม่ขึ้นก่อนแพ็กเก็ตหาย: น่าจะเป็น “policer ทิ้งส่วนที่เกิน” ถ้าหายในระดับใกล้เคียงกันตลอดไม่ว่าช่วงเวลาไหน น่าจะเป็น “ข้อผิดพลาดทางกายภาพ” หรือ “เส้นทางเปลี่ยน/เส้นทาง ECMP ที่เสีย” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - กรณีจริง: riot-direct-2015 - แหล่งอ้างอิง: - [RFC 7567: IETF Recommendations Regarding Active Queue Management](https://www.rfc-editor.org/rfc/rfc7567) · IETF · คำอธิบายว่า tail drop ทำให้คิวเต็มอยู่นาน ความหน่วงจึงเพิ่มและแพ็กเก็ตหายเป็นกลุ่ม พร้อมคำแนะนำให้ใช้ AQM - [RFC 8290: The Flow Queue CoDel Packet Scheduler and Active Queue Management Algorithm](https://www.rfc-editor.org/rfc/rfc8290) · IETF · fq_codel: ใช้คิวแยกตาม flow และ AQM รักษาคิวให้สั้นเพื่อลด bufferbloat - [RFC 3168: The Addition of Explicit Congestion Notification (ECN) to IP](https://www.rfc-editor.org/rfc/rfc3168) · IETF · ECN: แจ้งความแออัดด้วยการทำเครื่องหมายใน IP header แทนการทิ้งแพ็กเก็ต - [Smart Queue Management](https://www.bufferbloat.net/projects/cerowrt/wiki/Smart_Queue_Management/) · Bufferbloat.net · SQM: วิธีที่ใช้ scheduling แยกตาม flow, การจัดการความยาวคิว (AQM) และ shaping ร่วมกัน - [Cake](https://www.bufferbloat.net/projects/codel/wiki/Cake/) · Bufferbloat.net · CAKE: SQM สำหรับเราเตอร์ที่รวม shaper กับการจัดการคิวตระกูล fq_codel ไว้ด้วยกัน - [net/ipv4/proc.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/proc.c?h=v6.12) · Linux kernel · TcpRetransSegs และ TcpOutSegs ที่แสดงใน nstat (RetransSegs และ OutSegs ในหมวด Tcp) - [nstat(8) — Linux manual page](https://man7.org/linux/man-pages/man8/nstat.8.html) · iproute2 · โดยค่าเริ่มต้น nstat แสดงค่าที่เพิ่มขึ้นนับจากการรันครั้งก่อน - [RFC 2863: The Interfaces Group MIB](https://www.rfc-editor.org/rfc/rfc2863) · IETF · ifOutDiscards: จำนวนแพ็กเก็ตที่ถูกทิ้งโดยไม่ได้ส่งออกทั้งที่ไม่มี error ด้วยเหตุผล เช่น เพื่อคืนพื้นที่บัฟเฟอร์ - [An Internet-Wide Analysis of Traffic Policing](https://research.google/pubs/an-internet-wide-analysis-of-traffic-policing/) · Google · การแยกแยะว่าคิวล้นจะทำให้เวลารอและ RTT ขึ้นก่อนแพ็กเก็ตหาย ส่วน policing ทิ้งส่วนที่เกินโดยที่ RTT ไม่เพิ่ม (SIGCOMM 2016) #### rt-burst · บัฟเฟอร์ตื้นล้นเพราะ burst จากฝั่งส่ง · Sender bursts overflow shallow buffers ถ้าเซิร์ฟเวอร์ส่งอัปเดตของผู้เล่นหลายพันคนออกไปรวดเดียวในทุกทิก บัฟเฟอร์เล็ก ๆ ของสวิตช์หรือขีดจำกัดชั่วขณะของคลาวด์จะล้นภายในไม่ถึง 1 ms และแพ็กเก็ตบางส่วนถูกทิ้ง - ทำไม → ผลคือ → บนหน้าจอ: ส่งแพ็กเก็ตของทุกคนออกไปพร้อมกันตอนเริ่มทิก → บัฟเฟอร์ของพอร์ตสวิตช์ที่ทราฟฟิกจากหลายเซิร์ฟเวอร์มารวมกัน (หลายร้อย KB ถึงหลาย MB ต่อพอร์ต) หรือขีดจำกัดของอินสแตนซ์คลาวด์ล้นชั่วขณะ (อัตราการใช้งานเฉลี่ยต่ำ) → หลายคนวาร์ปหรือหยุดแวบพร้อมกัน, ดูจากเมตริกค่าเฉลี่ยจะไม่เห็นสาเหตุ - อาการ: วาร์ป, ค้าง, กรอเร็ว / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: บางจุด/บางแชนแนล, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), อินฟราเครือข่าย (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: กระจายการส่งของหนึ่งทิกให้ทั่วช่วงเวลาของทิก (การที่การเชื่อมต่อหลายพันรายการกองรวมกันตอนเริ่มทิก pacing ต่อการเชื่อมต่อช่วยได้ไม่มาก), เหลื่อมเวลาเริ่มทิกของแต่ละเซิร์ฟเวอร์, จำกัดความเร็วสูงสุดของการเชื่อมต่อที่ส่งข้อมูลใหญ่ด้วย SO_MAX_PACING_RATE - งานฝั่งทีมอินฟรา: เครื่องเซิร์ฟเวอร์/OS: จำกัดความเร็วส่งรวมของทั้งเซิร์ฟเวอร์ (shaper ของ OS เซิร์ฟเวอร์, tc ของ Linux), เกลี่ยการส่งรวดเดียวของการเชื่อมต่อเดียวให้สม่ำเสมอด้วย pacing (คิว fq ของ Linux, BBR) เครือข่าย: ใช้สวิตช์ที่บัฟเฟอร์ใหญ่, ตรวจตัวนับการทิ้งขาออกของพอร์ตสวิตช์ด้วยช่วงเวลาสั้น ๆ (ดูจากอัตราการใช้งานเฉลี่ยจะไม่เห็น) - ตัวเลขที่ควรรู้: พอร์ต 10 Gbps ส่งข้อมูลได้ราว 1.25 MB ใน 1 ms ถ้าทิกของหลายเซิร์ฟเวอร์ตรงกันแล้วมารวมที่พอร์ตเดียว บัฟเฟอร์จะเต็มในพริบตา - บนกราฟ: สูงตามจำนวนคนและโหลด (จำนวนการทิ้งขาออกของพอร์ตสวิตช์, อัตราการส่งซ้ำ) - จุดที่ต้องดู: การทิ้งขาออก (ifOutDiscards) ของพอร์ตสวิตช์ที่เซิร์ฟเวอร์ต่ออยู่และพอร์ตชั้นบนถัดไป เก็บทุกไม่กี่วินาที ถ้าเป็นคลาวด์ดู bw_out_allowance_exceeded และ pps_allowance_exceeded จาก ethtool -S เก็บการส่งซ้ำในช่วงเวลาเดียวกันด้วย bcc tcpretrans แล้วเทียบกัน - สัญญาณว่าใช่: อัตราการใช้งานเฉลี่ยรายนาทีต่ำ แต่การทิ้งขาออกหรือ allowance exceeded เพิ่มขึ้น และเพิ่มตามจำนวนผู้เล่นออนไลน์พร้อมกันและจำนวนคนที่รวมตัวอยู่จุดเดียว การส่งซ้ำเกิดในหลายการเชื่อมต่อของเซิร์ฟเวอร์นั้นในจังหวะเดียวกัน โดยไม่กระจุกที่ช่วง IP ของผู้เล่นกลุ่มใด (ISP/พื้นที่) - สัญญาณว่าไม่ใช่: CRC และ input error ที่พอร์ตเดียวกันเพิ่มขึ้นด้วย: น่าจะเป็น “ข้อผิดพลาดทางกายภาพ” ถ้าตัวนับการทิ้งของ NIC หรือ softnet dropped ที่เซิร์ฟเวอร์ฝั่งรับเพิ่มขึ้น น่าจะเป็น “เครื่องเซิร์ฟเวอร์ฝั่งรับทิ้งแพ็กเก็ต” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: pacing ทำงานแยกกันในแต่ละการเชื่อมต่อ การกองรวมที่เกิดจากการเชื่อมต่อหลายพันรายการต่างส่งแพ็กเก็ตหนึ่งสองตัวตอนเริ่มทิก pacing ต่อการเชื่อมต่อช่วยได้ไม่มาก เซิร์ฟเวอร์เกมต้องกระจายจังหวะการส่งเอง ในทางกลับกัน เมื่อการเชื่อมต่อเดียวส่งข้อมูลใหญ่ NIC จะหั่นข้อมูลหลายสิบ KB เป็นขนาดแพ็กเก็ตแล้วส่งออกติดกัน (TSO) การกองรวมแบบนี้ pacing ช่วยกระจายได้ดี - แหล่งอ้างอิง: - [High-Resolution Measurement of Data Center Microbursts](https://research.facebook.com/publications/high-resolution-measurement-of-data-center-microbursts/) · Meta · burst ของสวิตช์ระดับแร็กในดาต้าเซ็นเตอร์ตั้งแต่ 70% ขึ้นไปจบภายในหลายสิบ µs และอัตราการใช้งานเฉลี่ยรายนาทีสัมพันธ์กับการดรอปน้อย (IMC 2017) - [tc-fq(8) — Linux manual page](https://man7.org/linux/man-pages/man8/tc-fq.8.html) · iproute2 · คิว fq ทำ pacing แยกตามซ็อกเก็ต (การเชื่อมต่อ) และกำหนดความเร็วสูงสุดต่อการเชื่อมต่อด้วย SO_MAX_PACING_RATE - [net/ipv4/tcp_bbr.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_bbr.c?h=v6.12) · Linux kernel · BBR กำหนด pacing_rate จากแบนด์วิดท์คอขวดที่ประเมินได้แล้วส่งตามนั้น - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · TCP กำหนดขนาดเฟรม TSO ตามความเร็วของ flow (สูงสุด 64 KB, tcp_min_tso_segs) - [Monitor network performance for ENA settings on your EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html) · AWS · bw_out_allowance_exceeded และ pps_allowance_exceeded: จำนวนแพ็กเก็ตที่ถูกเข้าคิวหรือถูกทิ้งเพราะเกินขีดจำกัดแบนด์วิดท์หรือจำนวนแพ็กเก็ตต่อวินาทีของอินสแตนซ์ - [RFC 2863: The Interfaces Group MIB](https://www.rfc-editor.org/rfc/rfc2863) · IETF · ifOutDiscards: จำนวนแพ็กเก็ตที่ถูกทิ้งโดยไม่ได้ส่งออกทั้งที่ไม่มี error ด้วยเหตุผล เช่น เพื่อคืนพื้นที่บัฟเฟอร์ - [Demonstrations of tcpretrans, the Linux eBPF/bcc version](https://raw.githubusercontent.com/iovisor/bcc/master/tools/tcpretrans_example.txt) · IO Visor · แสดงที่อยู่และพอร์ตของอีกฝั่งและสถานะการเชื่อมต่อ หนึ่งบรรทัดต่อการส่งซ้ำแต่ละครั้ง #### rt-policer · policer ทิ้งส่วนที่เกิน · Traffic policing แพ็กเกจเน็ตของ ISP, ขีดจำกัดของอินสแตนซ์คลาวด์ และอุปกรณ์ป้องกัน DDoS บางครั้งจะทิ้งแพ็กเก็ตที่เกินความเร็วที่กำหนดทันทีโดยไม่เข้าคิว - ทำไม → ผลคือ → บนหน้าจอ: ปริมาณที่ส่งในชั่วขณะเกินความเร็วหรือ burst ที่อนุญาต → แพ็กเก็ตส่วนที่เกินถูกทิ้งทันทีโดยไม่เข้าคิว (policing) → ทุกจังหวะที่ burst ใหญ่ แพ็กเก็ตหายหลายตัว จึงค้างแล้วตามด้วยอาการกรอเร็ว, ความเร็วเฉลี่ยดูต่ำกว่าขีดจำกัด - อาการ: ค้าง, กรอเร็ว, วาร์ป / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางพื้นที่/บาง ISP, เราคนเดียว / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: กระจายปริมาณที่ส่งรวดเดียวในแต่ละทิกให้ทั่วช่วงทิก เพื่อลดปริมาณการส่งชั่วขณะให้ต่ำกว่า burst ที่อนุญาต, ถ้าติดขีดจำกัดจำนวนแพ็กเก็ตต่อวินาที ให้รวมข้อความของหนึ่งทิกไว้ในแพ็กเก็ตเดียว - งานฝั่งทีมอินฟรา: เครือข่าย: ตรวจตัวนับส่วนเกินของ policer บนอุปกรณ์, ใช้ shaper แทน policer, เพิ่ม burst ที่อนุญาต เครื่องเซิร์ฟเวอร์/OS: ตรวจเมตริกการเกินขีดจำกัดของคลาวด์ (AWS ดู bw_out_allowance_exceeded และ pps_allowance_exceeded จาก ethtool -S), อัปเกรดอินสแตนซ์, ทำ pacing ที่เซิร์ฟเวอร์ (คิว fq ของ Linux) - ตัวเลขที่ควรรู้: shaper (เข้าคิวเพื่อหน่วงไว้) ทำให้ความหน่วงเพิ่ม ส่วน policer (ทิ้งทันที) ทำให้แพ็กเก็ตหายเพิ่ม การเชื่อมต่อเกมแบบ TCP อาจหยุดหลายร้อย ms จากการหายเพียงครั้งเดียว ถ้าแค่เกินขีดจำกัดชั่วครู่ policer มักส่งผลหนักกว่า - บนกราฟ: ชนเพดานแล้วแบนราบ (ปริมาณการส่งที่วัดด้วยช่วงเวลาสั้น, ตัวนับส่วนเกินของ policer/allowance) - จุดที่ต้องดู: ตัวนับ exceed และการทิ้งของอุปกรณ์ที่มี policer ถ้าเป็นคลาวด์ดู bw_out_allowance_exceeded และ pps_allowance_exceeded จาก ethtool -S การเชื่อมต่อที่แพ็กเก็ตหาย ดู RTT ก่อนหายจาก rtt ของ ss -ti หรือจาก packet capture - สัญญาณว่าใช่: ตัวนับส่วนเกินเพิ่มขึ้น และปริมาณการส่งที่วัดด้วยช่วงเวลาสั้นแบนราบเหมือนถูกตัดที่ค่าหนึ่ง RTT ไม่ขึ้นก่อนหาย และแพ็กเก็ตหลายตัวหายพร้อมกันเฉพาะจังหวะที่ burst ใหญ่ - สัญญาณว่าไม่ใช่: RTT ขึ้นก่อนแพ็กเก็ตหาย: น่าจะเป็นคิวล้น (“คิวคอขวดล้น”, “บัฟเฟอร์ตื้นล้นเพราะ burst จากฝั่งส่ง”) ถ้าตัวนับส่วนเกินไม่ขยับ น่าจะเป็นสาเหตุอื่น - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 2475: An Architecture for Differentiated Services](https://www.rfc-editor.org/rfc/rfc2475) · IETF · นิยามว่า shaping หน่วงแพ็กเก็ตให้เข้ากับ traffic profile ส่วน policing ทิ้งแพ็กเก็ตที่เกิน profile - [An Internet-Wide Analysis of Traffic Policing](https://research.google/pubs/an-internet-wide-analysis-of-traffic-policing/) · Google · การส่งที่โดน policing มีอัตราแพ็กเก็ตหายสูงกว่าเฉลี่ย 6 เท่า และใช้ pacing หรือ shaping ก็ได้ผลตามเป้าหมายเดียวกัน การแยกแยะว่า policing ทิ้งส่วนที่เกินโดยที่ RTT ไม่เพิ่ม ส่วนคิวล้นจะทำให้ RTT ขึ้นก่อนแพ็กเก็ตหาย (SIGCOMM 2016) - [Monitor network performance for ENA settings on your EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html) · AWS · bw_out_allowance_exceeded และ pps_allowance_exceeded ใน ethtool -S: จำนวนแพ็กเก็ตที่ถูกเข้าคิวหรือถูกทิ้งเพราะเกินขีดจำกัดของอินสแตนซ์ - [tc-fq(8) — Linux manual page](https://man7.org/linux/man-pages/man8/tc-fq.8.html) · iproute2 · pacing ต่อการเชื่อมต่อของคิว fq ใน Linux - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · rtt (เวลาไปกลับเฉลี่ย) ใน ss -i #### rt-physical · ข้อผิดพลาดทางกายภาพ (สายเสีย/โมดูลออปติก/คอนเน็กเตอร์) · Bit errors: bad cable, optics, dirty fiber สายที่ชำรุด, คอนเน็กเตอร์ไฟเบอร์ที่มีฝุ่น และโมดูลออปติกที่หมดอายุการใช้งาน ทำให้เกิด bit error และอุปกรณ์จะทิ้งแพ็กเก็ตที่เสียไปเงียบ ๆ - ทำไม → ผลคือ → บนหน้าจอ: บิตกลับค่าเพราะสาย, โมดูลออปติก หรือคอนเน็กเตอร์เสีย → อุปกรณ์ทิ้งแพ็กเก็ตที่ checksum (CRC) ไม่ตรง → เฉพาะคนที่ผ่านเส้นทางนั้นหยุดแวบสั้น ๆ แล้วกรอเร็วอยู่เป็นระยะ, ไม่ขึ้นกับช่วงเวลา - อาการ: ค้าง, กรอเร็ว, วาร์ป / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: บางจุด/บางแชนแนล, คนในบ้านเดียวกัน / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), ภายนอก (ภายนอก) - งานฝั่งทีมอินฟรา: ตรวจทั้งสองปลายของลิงก์ (CRC error สะสมที่ฝั่งรับของทิศทางที่เสีย) เครือข่าย: ตรวจตัวนับ CRC และ input error ของพอร์ตอุปกรณ์, ตรวจความแรงสัญญาณแสง (ข้อมูลโมดูลออปติกของสวิตช์), ทำความสะอาดคอนเน็กเตอร์ไฟเบอร์, เปลี่ยนสายหรือโมดูลออปติก เครื่องเซิร์ฟเวอร์/OS: ตรวจ rx_crc_errors ใน ethtool -S ของเซิร์ฟเวอร์ (ชื่อต่างกันเล็กน้อยตามไดรเวอร์), ตรวจความแรงสัญญาณแสง (ethtool -m), เปลี่ยนสายหรือ NIC ฝั่งเซิร์ฟเวอร์ - งานฝั่งภายนอก: ถ้าเป็นช่วงในบ้านผู้เล่น แนะนำให้เปลี่ยนสาย LAN หรือเราเตอร์, ถ้าเป็นช่วงลิงก์ของ ISP แจ้งให้ ISP ตรวจสาย - ตัวเลขที่ควรรู้: แพ็กเก็ตหายแค่ 0.1% ก็คือหนึ่งครั้งในแพ็กเก็ตเกม 1,000 ตัว ถ้ามีคนผ่านเส้นทางนั้นหลายสิบคน ทุกไม่กี่วินาทีจะมีใครสักคนหยุดแวบ bit error เกิดกับแพ็กเก็ตใหญ่ได้ง่ายกว่า - บนกราฟ: สูงเฉพาะบางกลุ่ม (จำนวน CRC error ต่อพอร์ต, อัตราการส่งซ้ำต่อเซิร์ฟเวอร์/ต่อพอร์ต) - จุดที่ต้องดู: ตัวนับ CRC ที่ปลายทั้งสองข้างของลิงก์ เซิร์ฟเวอร์ดู rx_crc_errors ใน ethtool -S หรือ crc ใน ip -s -s link สวิตช์ดู FCS error (dot3StatsFCSErrors) และ input error (ifInErrors) ของพอร์ต ถ้าเป็นลิงก์ไฟเบอร์ ดูความแรงแสงขาเข้าจาก ethtool -m และข้อมูลโมดูลออปติกของสวิตช์ - สัญญาณว่าใช่: CRC error ของพอร์ตหนึ่งเพิ่มขึ้นเรื่อย ๆ ไม่ขึ้นกับช่วงเวลา และอัตราการส่งซ้ำสูงเฉพาะเซิร์ฟเวอร์และการเชื่อมต่อที่ผ่านพอร์ตนั้น ความแรงแสงขาเข้าต่ำกว่าลิงก์ชนิดเดียวกันเส้นอื่น - สัญญาณว่าไม่ใช่: CRC ไม่ขยับแต่การทิ้งขาออกเพิ่มขึ้นอย่างเดียว: น่าจะเป็นคิวล้น (“บัฟเฟอร์ตื้นล้นเพราะ burst จากฝั่งส่ง”, “คิวคอขวดล้น”) ถ้า late collision ฝั่งหนึ่งกับ CRC อีกฝั่งเพิ่มขึ้นพร้อมกัน น่าจะเป็น “duplex ไม่ตรงกัน” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Interface statistics](https://docs.kernel.org/networking/statistics.html) · Linux kernel · rx_crc_errors คือจำนวนแพ็กเก็ตที่อินเทอร์เฟซฝั่งรับนับว่าเป็น CRC error ตรวจได้ด้วย ip -s -s link และ ethtool -S - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · -S ดูสถิติราย NIC/ไดรเวอร์, -m ดู EEPROM และข้อมูลวินิจฉัยสัญญาณแสงของโมดูลออปติก (SFP+, QSFP) - [RFC 3635: Definitions of Managed Objects for the Ethernet-like Interface Types](https://www.rfc-editor.org/rfc/rfc3635) · IETF · FCS error (dot3StatsFCSErrors) ของพอร์ตสวิตช์ ซึ่งถูกนับรวมใน input error (ifInErrors) #### rt-duplex · duplex ไม่ตรงกัน · Duplex mismatch ถ้าฝั่งหนึ่งใช้ auto-negotiation แต่อีกฝั่งล็อกความเร็วและ duplex ไว้ ฝั่งหนึ่งจะทำงานแบบ half duplex และทุกครั้งที่มีโหลดจะเกิด collision จนแพ็กเก็ตหาย - ทำไม → ผลคือ → บนหน้าจอ: ล็อกค่าความเร็วและ duplex ไว้ที่อุปกรณ์ฝั่งเดียว → ฝั่งหนึ่งทำงานแบบ full duplex อีกฝั่งเป็น half duplex จึงเกิด collision และ late collision → ปกติไม่มีอาการ แต่พอทราฟฟิกเพิ่ม ทุกคนที่ผ่านอุปกรณ์นั้นค้างแล้วตามด้วยอาการกรอเร็ว - อาการ: ค้าง, กรอเร็ว / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางจุด/บางแชนแนล / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: ตั้งทั้งสองฝั่งเป็น auto-negotiation หรือล็อกทั้งสองฝั่งเป็นค่าเดียวกัน เครือข่าย: ดูความเร็วและ duplex จากสถานะพอร์ตสวิตช์, ดูตัวนับของพอร์ตว่าฝั่ง half duplex มี late collision เพิ่ม และฝั่ง full duplex มี CRC error และเฟรมที่สั้นเกิน (runt) เพิ่มหรือไม่ เครื่องเซิร์ฟเวอร์/OS: ตรวจความเร็วและ duplex ด้วย ethtool - ตัวเลขที่ควรรู้: สายทองแดง 1 Gbps ต้องใช้ auto-negotiation เสมอ และที่ 10 Gbps ขึ้นไปไม่มี half duplex เลย ปัจจุบันจึงมักเกิดกับอุปกรณ์เก่าที่ 100 Mbps หรือต่ำกว่า, พอร์ต management และบางจุดที่ต่อเข้ากับวงจรของผู้ให้บริการ - บนกราฟ: สูงตามจำนวนคนและโหลด (จำนวน late collision และ CRC error ของพอร์ต, อัตราการส่งซ้ำ) - จุดที่ต้องดู: ความเร็วและ duplex จริงของทั้งสองฝั่งลิงก์ เซิร์ฟเวอร์รัน ethtool ตามด้วยชื่ออินเทอร์เฟซอย่างเดียว สวิตช์ดูสถานะพอร์ตหรือ dot3StatsDuplexStatus ใน SNMP ดู late collision (tx_window_errors ของเซิร์ฟเวอร์, dot3StatsLateCollisions ของสวิตช์) และ CRC error ไปพร้อมกัน - สัญญาณว่าใช่: ฝั่งหนึ่งแสดงเป็น half duplex อีกฝั่งเป็น full duplex ทุกครั้งที่ทราฟฟิกเพิ่ม ฝั่ง half duplex มี late collision และฝั่ง full duplex มี CRC error เพิ่มขึ้นพร้อมกัน - สัญญาณว่าไม่ใช่: ความเร็วและ duplex ทั้งสองฝั่งตรงกันแต่ CRC เพิ่มอย่างเดียว: น่าจะเป็น “ข้อผิดพลาดทางกายภาพ” ลิงก์ 10 Gbps ขึ้นไปไม่มี half duplex จึงตัดสาเหตุนี้ออก - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Linux Base Driver for Intel(R) Ethernet Network Connection](https://docs.kernel.org/networking/device_drivers/ethernet/intel/e1000.html) · Linux kernel · มาตรฐาน 1000BASE-T กำหนดให้ต้องใช้ auto-negotiation - [IEEE 802.3ae 10 Gigabit Ethernet: HSSG Objectives](https://www.ieee802.org/3/ae/objectives.pdf) · IEEE · 10 Gigabit Ethernet รองรับเฉพาะ full duplex - [IEEE P802.3ba Objectives](https://www.ieee802.org/3/ba/PAR/P802.3ba_Objectives_0709.pdf) · IEEE · 40 และ 100 Gigabit Ethernet ก็รองรับเฉพาะ full duplex - [Interface statistics](https://docs.kernel.org/networking/statistics.html) · Linux kernel · tx_window_errors คือจำนวนการส่งที่ล้มเหลวจาก late collision ส่วน rx_crc_errors คือจำนวนแพ็กเก็ตที่รับมาพร้อม CRC error - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · ตั้งความเร็ว, duplex และ auto-negotiation ด้วย speed, duplex และ autoneg ของ ethtool -s ถ้าให้แค่ชื่ออินเทอร์เฟซจะแสดงค่าที่ตั้งอยู่ตอนนี้ - [RFC 3635: Definitions of Managed Objects for the Ethernet-like Interface Types](https://www.rfc-editor.org/rfc/rfc3635) · IETF · dot3StatsDuplexStatus (แสดง duplex ปัจจุบันเป็น halfDuplex หรือ fullDuplex), dot3StatsLateCollisions (จำนวน late collision) #### rt-host-drop · เครื่องเซิร์ฟเวอร์ฝั่งรับทิ้งแพ็กเก็ต · Receiver host drops (ring, softirq, CPU) แพ็กเก็ตมาถึงเซิร์ฟเวอร์แล้ว แต่ถูกทิ้งเพราะ ring buffer ของ NIC (บัฟเฟอร์ที่พักแพ็กเก็ตที่มาถึงไว้ชั่วคราว) ล้น หรือคอร์ที่เคอร์เนลใช้ประมวลผลขาเข้าทำงานเต็ม - ทำไม → ผลคือ → บนหน้าจอ: ผู้เล่นทะลักเข้ามา, อินเทอร์รัปต์กระจุกที่คอร์เดียว, CPU steal ของ VM, virtual switch โหลดเกิน → ถูกทิ้งที่ ring buffer (เช่น rx_missed_errors ซึ่งชื่อต่างกันตามไดรเวอร์) หรือที่คิวรับของเคอร์เนล (softnet dropped) → ช่วงที่คนเยอะ อินพุตเข้าช้าและหยุดแวบพร้อมกันทั้งเซิร์ฟเวอร์ - อาการ: อินพุตดีเลย์, ค้าง, กรอเร็ว, วาร์ป / ปัจจัย: แพ็กเก็ตหาย, การหยุดชะงัก - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: เพิ่มตัวนับการทิ้ง เช่น rx_missed_errors ใน ethtool -S และ dropped ใน /proc/net/softnet_stat เข้าในการมอนิเตอร์, เพิ่มขนาด ring buffer (ethtool -G), กระจาย RSS และอินเทอร์รัปต์ไปหลายคอร์, แยกคอร์ของเธรดเกมกับคอร์ที่ประมวลผลขาเข้า, เผื่อ CPU ให้เหลือ, ถ้าเป็น VM ให้ตรวจ CPU steal และโหลดของ virtual switch - ตัวเลขที่ควรรู้: แพ็กเก็ตที่เซิร์ฟเวอร์รับแล้วทิ้งไป ไคลเอนต์จะเป็นฝ่ายส่งใหม่ จึงไม่ค่อยเห็นในเมตริกการส่งซ้ำของเซิร์ฟเวอร์ สัญญาณจะโผล่ก่อนในตัวนับการทิ้ง เช่น rx_missed_errors ใน ethtool -S (ชื่อต่างกันตามไดรเวอร์) และ dropped ใน /proc/net/softnet_stat - บนกราฟ: ชนเพดานแล้วแบนราบ (อัตราการใช้ softirq ต่อคอร์, ตัวนับการทิ้งของ NIC) - จุดที่ต้องดู: ตัวนับการทิ้งใน ethtool -S (เช่น rx_missed_errors, ส่วน mlx5 คือ rx_out_of_buffer และ rx_discards_phy), missed ใน ip -s -s link, คอลัมน์ที่ 2 (dropped) และคอลัมน์ที่ 3 (time_squeeze) ของ /proc/net/softnet_stat และ %soft (การประมวลผล soft interrupt) ต่อคอร์จาก mpstat -P ALL ถ้าเป็น VM ดู %steal ด้วย - สัญญาณว่าใช่: ช่วงที่คนเยอะ ตัวนับการทิ้งหรือ softnet dropped เพิ่มขึ้น และ %soft ของคอร์ที่ประมวลผลขาเข้าตันอยู่ใกล้ 100% อินพุตช้าลงพร้อมกันในทุกการเชื่อมต่อของเซิร์ฟเวอร์นั้น - สัญญาณว่าไม่ใช่: ตัวนับการทิ้งของเซิร์ฟเวอร์ไม่ขยับ และการส่งซ้ำกระจุกที่การเชื่อมต่อจากบางพื้นที่/บาง ISP: น่าจะเป็นแพ็กเก็ตหายระหว่างทาง ถ้าแพ็กเก็ตที่เซิร์ฟเวอร์ส่งไปหายระหว่างทาง TcpRetransSegs ใน nstat ของเซิร์ฟเวอร์จะเพิ่มขึ้น ส่วนตัวนับเหล่านี้ไม่ขยับ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Interface statistics](https://docs.kernel.org/networking/statistics.html) · Linux kernel · rx_missed_errors คือจำนวนแพ็กเก็ตที่โฮสต์รับไม่ได้เพราะไม่มีบัฟเฟอร์ (ใน /proc/net/dev นับรวมใน drop) ตรวจด้วย ip -s -s link - [drivers/net/ethernet/intel/igb/igb_ethtool.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/net/ethernet/intel/igb/igb_ethtool.c?h=v6.12) · Linux kernel · ไดรเวอร์เป็นผู้กำหนดชื่อตัวนับใน ethtool -S (เช่น rx_missed_errors และ rx_no_buffer_count ของ igb) - [Ethtool counters](https://docs.kernel.org/networking/device_drivers/ethernet/mellanox/mlx5/counters.html) · Linux kernel · rx_out_of_buffer (ไม่มีบัฟเฟอร์ในคิวรับ) และ rx_discards_phy (ทิ้งเพราะบัฟเฟอร์ของพอร์ตไม่พอ) ของไดรเวอร์ mlx5 - [net/core/net-procfs.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/core/net-procfs.c?h=v6.12) · Linux kernel · /proc/net/softnet_stat มีหนึ่งบรรทัดต่อ CPU เป็นเลขฐาน 16 คอลัมน์ที่ 2 คือ dropped และคอลัมน์ที่ 3 คือ time_squeeze - [Documentation for /proc/sys/net/](https://docs.kernel.org/admin-guide/sysctl/net.html) · Linux kernel · netdev_max_backlog: ขีดจำกัดของคิวรับที่ใช้พักแพ็กเก็ตเมื่อแพ็กเก็ตเข้ามาเร็วกว่าที่เคอร์เนลประมวลผลทัน - [Scaling in the Linux Networking Stack](https://docs.kernel.org/networking/scaling.html) · Linux kernel · RSS: NIC แบ่งแพ็กเก็ตไปหลายคิวรับเพื่อให้หลาย CPU ประมวลผล - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · เปลี่ยนขนาด ring buffer ด้วย -G (--set-ring) - [proc_stat(5) — Linux manual page](https://man7.org/linux/man-pages/man5/proc_stat.5.html) · Linux man-pages · steal ใน /proc/stat: เวลาที่ OS อื่นใช้ CPU ในสภาพแวดล้อม virtualization - [mpstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/mpstat.1.html) · sysstat · ดูอัตราการใช้งานต่อคอร์ด้วย mpstat -P ALL %soft คือเวลาประมวลผล soft interrupt ส่วน %steal คือเวลาที่ต้องรอเพราะ hypervisor ไปประมวลผล virtual CPU ตัวอื่น - [net/ipv4/proc.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/proc.c?h=v6.12) · Linux kernel · TcpRetransSegs ที่แสดงใน nstat (RetransSegs ในหมวด Tcp) #### rt-stateful-fw · ไฟร์วอลล์/connection tracking ทิ้งแพ็กเก็ต · Stateful firewall / conntrack drops ไฟร์วอลล์หรือ connection tracking ของ Linux (conntrack คือฟังก์ชันที่บันทึกการเชื่อมต่อที่ผ่านลงในตาราง) จะทิ้งแพ็กเก็ตเมื่อตารางเต็ม หรือเมื่อตัดสินว่าสถานะการเชื่อมต่อไม่ถูกต้อง - ทำไม → ผลคือ → บนหน้าจอ: ตาราง connection tracking เต็ม (table full) หรือเส้นทางขาไปกับขากลับต่างกันจนผ่านไฟร์วอลล์แค่ทิศทางเดียว (asymmetric routing) → ไฟร์วอลล์มองว่าเป็นแพ็กเก็ตของ “การเชื่อมต่อที่ไม่รู้จัก” หรือ “sequence number ที่อยู่นอกช่วง window” แล้วทิ้ง → ถ้าตารางเต็ม การเชื่อมต่อใหม่จะถูกบล็อก, ถ้าเส้นทางเหลื่อมกัน เฉพาะคนที่ใช้เส้นทางนั้นจะส่งซ้ำวนไปจนหลุด - อาการ: ค้าง, หลุด, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางพื้นที่/บาง ISP / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, หลังล็อกอิน/หลังปิดปรับปรุง, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: เผื่อกรณีตารางเต็มด้วยการคุมการเชื่อมต่อที่ทะลักเข้ามาผ่านระบบคิวล็อกอิน, ใช้การเชื่อมต่อซ้ำเพื่อไม่ให้เปิดปิดการเชื่อมต่อสั้น ๆ ซ้ำไปมา (รวมถึงการเรียกระหว่างเซิร์ฟเวอร์), ปิดการเชื่อมต่อที่ heartbeat ขาดไปก่อน ไคลเอนต์: เมื่อเชื่อมต่อไม่สำเร็จหรือหลุด ให้ยืดช่วงเวลาลองใหม่ขึ้นเรื่อย ๆ และสุ่มกระจาย (เพื่อไม่ให้กลับมาทะลักพร้อมกันตอนตารางเต็ม) - งานฝั่งทีมอินฟรา: เครือข่าย: เพิ่มขนาดตาราง connection tracking ของไฟร์วอลล์, เอาพอร์ตเกมออกจาก connection tracking, ปรับ routing ให้ขาไปและขากลับผ่านไฟร์วอลล์ตัวเดียวกัน, ตรวจการตั้งค่าการตรวจ TCP window ของไฟร์วอลล์ เครื่องเซิร์ฟเวอร์/OS: เพิ่มขนาดตารางของ Linux (nf_conntrack_max), เอาพอร์ตเกมออกจาก connection tracking (NOTRACK), ตรวจการตั้งค่าการตรวจ TCP window (nf_conntrack_tcp_be_liberal), ถ้าเป็น AWS ตรวจ conntrack_allowance_exceeded ด้วย - ตัวเลขที่ควรรู้: ขีดจำกัดเริ่มต้นของ conntrack ใน Linux (nf_conntrack_max) อยู่ที่หลายหมื่นถึงหลายแสนรายการ ขึ้นกับหน่วยความจำ เมื่อจำนวนปัจจุบัน (nf_conntrack_count) ชนขีดจำกัด จะมี log “nf_conntrack: table full, dropping packet” - บนกราฟ: ชนเพดานแล้วแบนราบ (จำนวนรายการ conntrack (nf_conntrack_count), จำนวนการเชื่อมต่อใหม่ที่ล้มเหลว) - จุดที่ต้องดู: เซิร์ฟเวอร์ Linux ดู nf_conntrack_count กับ nf_conntrack_max, “nf_conntrack: table full, dropping packet” ใน dmesg, drop และ invalid ใน /proc/net/stat/nf_conntrack (หนึ่งบรรทัดต่อคอร์, เลขฐาน 16) ไฟร์วอลล์ดูการใช้ตารางเซสชันและ log การดรอป AWS ดู conntrack_allowance_exceeded ใน ethtool -S - สัญญาณว่าใช่: จำนวนรายการแบนราบที่ขีดจำกัด และในเวลาเดียวกัน log table full กับ drop หรือ conntrack_allowance_exceeded เพิ่มขึ้น ถ้าเป็นเส้นทางไม่สมมาตร ขีดจำกัดยังเหลือ แต่ invalid และ log การดรอปของไฟร์วอลล์เพิ่มขึ้นในการเชื่อมต่อของเส้นทางหนึ่ง - สัญญาณว่าไม่ใช่: จำนวนรายการยังห่างจากขีดจำกัด และ invalid กับ log การดรอปก็ไม่ขยับ: เป็นสาเหตุอื่น ถ้าตารางยังเหลือที่แต่ CPU หรือจำนวนแพ็กเก็ตต่อวินาทีของไฟร์วอลล์เต็ม น่าจะเป็น “อุปกรณ์กลางทางเกินขีดความสามารถ” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [Netfilter Conntrack Sysfs variables](https://docs.kernel.org/networking/nf_conntrack-sysctl.html) · Linux kernel · ค่าเริ่มต้นของ nf_conntrack_max เท่ากับจำนวน hash bucket (หน่วยความจำ÷16384, 1,024–262,144), จำนวนปัจจุบันคือ nf_conntrack_count, nf_conntrack_tcp_be_liberal ทำให้นับเป็น INVALID เฉพาะ RST ที่อยู่นอก window - [net/netfilter/nf_conntrack_core.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/nf_conntrack_core.c?h=v6.12) · Linux kernel · เมื่อตารางเต็มจะเขียน log “nf_conntrack: table full, dropping packet” แล้วทิ้ง (สถิติ drop เพิ่ม) แพ็กเก็ตที่ไม่ตรงกับสถานะการเชื่อมต่อจะทำให้สถิติ invalid เพิ่ม - [iptables-extensions(8) — Linux manual page](https://man7.org/linux/man-pages/man8/iptables-extensions.8.html) · netfilter · ยกเว้นจาก connection tracking ด้วย CT --notrack ในตาราง raw - [Amazon EC2 security group connection tracking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-connection-tracking.html) · AWS · ถ้าเกินขีดจำกัดจำนวนการเชื่อมต่อที่ติดตามได้ของแต่ละอินสแตนซ์ แพ็กเก็ตจะถูกทิ้ง ตรวจได้จาก conntrack_allowance_exceeded พร้อมคำแนะนำให้หลีกเลี่ยงเส้นทางไม่สมมาตร - [net/netfilter/nf_conntrack_standalone.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/nf_conntrack_standalone.c?h=v6.12) · Linux kernel · /proc/net/stat/nf_conntrack มีหนึ่งบรรทัดต่อคอร์ เป็นเลขฐาน 16 และมีคอลัมน์ entries, invalid, insert_failed, drop, early_drop ฯลฯ #### rt-appliance-pps · อุปกรณ์กลางทางเกินขีดความสามารถ (ไฟร์วอลล์/IPS/ระบบป้องกัน DDoS) · Inline appliance PPS / CPU overload ไฟร์วอลล์, อุปกรณ์ป้องกันการบุกรุก (IPS) และอุปกรณ์ป้องกัน DDoS ตรวจแพ็กเก็ตที่ผ่านทีละตัว เมื่อเกินความสามารถในการตรวจ แพ็กเก็ตที่ประมวลผลไม่ทันจะถูกทิ้งตั้งแต่จังหวะนั้น - ทำไม → ผลคือ → บนหน้าจอ: ช่วงพีคหรืออีเวนต์ แพ็กเก็ตเกมเล็ก ๆ ทะลักเข้ามาเกินหลายแสนตัวต่อวินาที หรือกฎการตรวจหนัก → CPU หรือขีดจำกัดจำนวนแพ็กเก็ตต่อวินาทีของอุปกรณ์เต็ม อุปกรณ์จึงทิ้งแพ็กเก็ต ถ้าเป็น false positive แพ็กเก็ตปกติก็ถูกบล็อกด้วย → ผู้เล่นบนทุกเซิร์ฟเวอร์ที่อยู่หลังอุปกรณ์นั้นค้างหรือวาร์ปพร้อมกัน, หนักขึ้นเฉพาะตอนคนแห่มารวมกัน - อาการ: ค้าง, กรอเร็ว, วาร์ป, หลุด / ปัจจัย: แพ็กเก็ตหาย, ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางพื้นที่/บาง ISP / เกิดเมื่อไร: ช่วงพีคหัวค่ำ, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แชร์รูปแบบทราฟฟิกของเกม (พอร์ต, ขนาดแพ็กเก็ต, จำนวนแพ็กเก็ตต่อวินาที) ให้ทีมอินฟรา, รวมข้อความเล็ก ๆ ที่จะส่งในหนึ่งทิกแล้วส่งครั้งเดียวเพื่อลดจำนวนแพ็กเก็ต - งานฝั่งทีมอินฟรา: ดู CPU, จำนวนแพ็กเก็ตต่อวินาที และตัวนับการดรอปของอุปกรณ์คู่กับเมตริกของเกม, คำนวณความจุของอุปกรณ์โดยอิงแพ็กเก็ตเล็ก, เอาพอร์ตเกมออกจากการตรวจที่หนัก, ปรับกฎป้องกัน DDoS ให้เข้ากับรูปแบบทราฟฟิกของเกม - ตัวเลขที่ควรรู้: ตัวเลข “10 Gbps” ในสเปกอุปกรณ์มักวัดด้วยแพ็กเก็ตใหญ่ 1,500 ไบต์ แพ็กเก็ตเกมขนาดราว 100 ไบต์จะมีจำนวนแพ็กเก็ตมากกว่า 10 เท่าขึ้นไปในแบนด์วิดท์เท่ากัน ต่อให้ลิงก์ดูว่าง ขีดจำกัดจำนวนแพ็กเก็ตต่อวินาทีก็จะเต็มก่อน - บนกราฟ: ชนเพดานแล้วแบนราบ (จำนวนแพ็กเก็ตต่อวินาทีและอัตราการใช้ CPU ของอุปกรณ์, จำนวนการดรอปของอุปกรณ์) - จุดที่ต้องดู: CPU, จำนวนแพ็กเก็ตต่อวินาที และตัวนับการดรอปของอุปกรณ์ เทียบจำนวนแพ็กเก็ตของพอร์ตสวิตช์ด้านหน้าและด้านหลังอุปกรณ์ด้วยรอบการเก็บค่าเดียวกัน วางซ้อนบนหน้าจอเดียวกับจำนวนผู้เล่นออนไลน์พร้อมกันและอัตราการส่งซ้ำของเซิร์ฟเวอร์ - สัญญาณว่าใช่: ช่วงพีคหรืออีเวนต์ จำนวนแพ็กเก็ตต่อวินาทีหรือ CPU ของอุปกรณ์ตันอยู่ที่ค่าหนึ่ง แพ็กเก็ตที่ออกจากอุปกรณ์น้อยกว่าที่เข้าไป และในเวลาเดียวกันอัตราการส่งซ้ำของทุกเซิร์ฟเวอร์ที่อยู่หลังอุปกรณ์ก็ขึ้นตาม - สัญญาณว่าไม่ใช่: จำนวนแพ็กเก็ตด้านหน้าและด้านหลังอุปกรณ์เท่ากันและไม่มีการดรอปที่อุปกรณ์: เป็นสาเหตุอื่น ถ้าตัวนับการทิ้งของ NIC หรือ softnet dropped ของเซิร์ฟเวอร์เพิ่มขึ้น น่าจะเป็น “เครื่องเซิร์ฟเวอร์ฝั่งรับทิ้งแพ็กเก็ต” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 2544: Benchmarking Methodology for Network Interconnect Devices](https://www.rfc-editor.org/rfc/rfc2544) · IETF · ต้องทดสอบประสิทธิภาพของอุปกรณ์ด้วยเฟรมหลายขนาด รวมทั้งขนาดเล็กสุดและใหญ่สุด (ประสิทธิภาพการประมวลผลเปลี่ยนไปตามขนาดแพ็กเก็ต) #### rt-mtu · MTU black hole (เฉพาะแพ็กเก็ตใหญ่ที่หายซ้ำแล้วซ้ำอีก) · PMTU black hole เมื่อขนาดที่ช่วงกลางทางรับได้เล็กลง แต่ข้อความแจ้งว่า “ใหญ่เกินไป” (ICMP) ถูกบล็อก แพ็กเก็ตใหญ่จะหายไปทุกครั้งไม่ว่าจะส่งซ้ำกี่รอบ - ทำไม → ผลคือ → บนหน้าจอ: ขนาดสูงสุดลดลงในช่วงที่ผ่าน VPN หรืออุโมงค์ (tunnel) และข้อความแจ้งว่าเกินขนาดถูกไฟร์วอลล์บล็อก → ฝั่งส่งไม่รู้สาเหตุ จึงส่งแพ็กเก็ตใหญ่ตัวเดิมซ้ำไปเรื่อย ๆ และ RTO เพิ่มเป็นสองเท่าทุกครั้ง → ปกติไม่มีอาการ แต่ในจังหวะที่มีข้อมูลใหญ่วิ่ง เช่น เปิดกระเป๋า, ไปที่ที่คนเยอะ หรือโหลดตอนเข้าแมพ แพ็กเก็ตเล็กที่ตามมาก็หยุดไปหมด สุดท้ายหลุดหรือโหลดไม่จบ - อาการ: ค้าง, หลุด, เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: บางพื้นที่/บาง ISP, เราคนเดียว / เกิดเมื่อไร: ตอนทำแอ็กชันบางอย่าง, หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ถ้าจะลดขนาดจากฝั่งเซิร์ฟเวอร์เอง ให้ตั้งขนาด segment สูงสุดของซ็อกเก็ต (TCP_MAXSEG), แค่ซอยข้อความให้เล็กในโค้ดเกมไม่ช่วยป้องกัน (TCP จะรวมข้อมูลที่จะส่งเป็นก้อนขนาด MSS ใหม่) - งานฝั่งทีมอินฟรา: เครือข่าย: ปรับ MSS ที่อุปกรณ์ขอบเครือข่าย (clamping), อนุญาต ICMP แจ้งเกินขนาด (type 3 code 4, fragmentation needed) ที่ไฟร์วอลล์และ network ACL ของคลาวด์ เครื่องเซิร์ฟเวอร์/OS: ตั้งค่า path MTU, ตรวจว่าไฟร์วอลล์ของเซิร์ฟเวอร์และ security group ของคลาวด์ไม่บล็อก ICMP แจ้งเกินขนาด, ใช้ tcp_mtu_probing=1 ของ Linux เป็นตาข่ายรองรับชั้นสุดท้าย - ตัวเลขที่ควรรู้: ปกติ 1,500 ไบต์ ถ้าผ่านอุโมงค์จะเหลือราว 1,400 ถ้าแพ็กเก็ตเดิมถูกส่งซ้ำ 5–6 ครั้ง จะหยุดชะงักนานเกิน 10 วินาที - บนกราฟ: สูงเฉพาะบางกลุ่ม (RTO และ backoff ต่อการเชื่อมต่อ, จำนวนการหลุดแยกตามพื้นที่/ISP) - จุดที่ต้องดู: การส่งซ้ำของการเชื่อมต่อที่มีปัญหาจาก packet capture ฝั่งเซิร์ฟเวอร์หรือ bcc tcpretrans -s (แสดง sequence number) และ mss, pmtu, backoff ของการเชื่อมต่อนั้นจาก ss -ti จากเซิร์ฟเวอร์ส่ง ping เล็กกับ ping 1,500 ไบต์ที่เปิด DF (ping -M do -s 1472) ไปยังที่อยู่ของผู้เล่นคนนั้นแล้วเทียบกัน - สัญญาณว่าใช่: แพ็กเก็ตที่เต็มขนาด MSS ถูกส่งซ้ำด้วย sequence number เดิมไปเรื่อย ๆ โดยช่วงห่างเพิ่มเป็นสองเท่าทุกครั้ง ส่วนแพ็กเก็ตที่เล็กกว่าผ่านได้ ไม่มี ICMP แจ้งเกินขนาด (ตัวกรอง Wireshark icmp.type == 3 and icmp.code == 4) เข้ามา ping เล็กได้คำตอบ แต่ ping ใหญ่ที่เปิด DF หายเงียบ - สัญญาณว่าไม่ใช่: แพ็กเก็ตเล็กก็หายด้วย: เป็นการหายที่ไม่เกี่ยวกับขนาด (“คิวคอขวดล้น”, “เส้นทางเปลี่ยน/เส้นทาง ECMP ที่เสีย”) ถ้า ICMP แจ้งเกินขนาดมาถึงและ pmtu ใน ss -ti ลดลง แปลว่า path MTU discovery ทำงานปกติ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: tcp_mtu_probing=1 จะตัดสินว่าเป็น black hole แล้วลด MSS เหลือ 1,024 ไบต์ ก็ต่อเมื่อ RTO หมดเวลาต่อเนื่องไปหลายวินาที (เท่ากับ tcp_retries1=3) ระหว่างนั้นจะหยุดชะงัก จึงควรเก็บไว้เป็นตาข่ายรองรับชั้นสุดท้าย และทำ MSS clamping ที่ป้องกันไว้ล่วงหน้าเป็นอันดับแรก - แหล่งอ้างอิง: - [RFC 1191: Path MTU discovery](https://www.rfc-editor.org/rfc/rfc1191) · IETF · path MTU discovery: แพ็กเก็ตที่ใหญ่เกินจะได้รับแจ้งด้วย ICMP “fragmentation needed and DF set” (type 3 code 4) - [RFC 2923: TCP Problems with Path MTU Discovery](https://www.rfc-editor.org/rfc/rfc2923) · IETF · ปัญหา PMTU black hole ที่ ICMP ถูกบล็อกจนแพ็กเก็ตใหญ่หายไปเรื่อย ๆ - [RFC 4821: Packetization Layer Path MTU Discovery](https://www.rfc-editor.org/rfc/rfc4821) · IETF · วิธีที่ชั้น transport ค้นหาขนาดแพ็กเก็ตเองโดยไม่ใช้ ICMP (พื้นฐานของ tcp_mtu_probing ใน Linux) - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_mtu_probing: 0 ปิด, 1 เฉพาะเมื่อตรวจพบ black hole, 2 ตลอดเวลา (MSS เริ่มต้นคือ tcp_base_mss) tcp_retries1 ค่าเริ่มต้น 3 - [net/ipv4/tcp_timer.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_timer.c?h=v6.12) · Linux kernel · เมื่อการส่งซ้ำจาก RTO ต่อเนื่องครบ tcp_retries1 ครั้ง จะถือว่าตรวจพบ black hole แล้วเปิด MTU probing เพื่อลด MSS - [include/net/tcp.h](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp.h?h=v6.12) · Linux kernel · TCP_BASE_MSS = 1,024 ไบต์ - [RFC 6298: Computing TCP's Retransmission Timer](https://www.rfc-editor.org/rfc/rfc6298) · IETF · เพิ่ม RTO เป็นสองเท่าทุกครั้งที่ตัวจับเวลาส่งซ้ำหมดเวลา - [iptables-extensions(8) — Linux manual page](https://man7.org/linux/man-pages/man8/iptables-extensions.8.html) · netfilter · TCPMSS --clamp-mss-to-pmtu: เลี่ยงปัญหาแพ็กเก็ตใหญ่ไปไม่ถึงเพราะช่วงที่บล็อก ICMP ด้วยการปรับ MSS ใน SYN - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · TCP_MAXSEG: ขนาด segment สูงสุดของแพ็กเก็ตขาออก ถ้าตั้งก่อนเชื่อมต่อ MSS ที่แจ้งให้อีกฝั่งทราบก็จะเปลี่ยนด้วย - [Network maximum transmission unit (MTU) for your EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/network_mtu.html) · AWS · internet gateway และ VPN ใช้ MTU 1,500, PMTUD ต้องใช้ ICMP type 3 code 4 และถ้า security group หรือ network ACL บล็อกไว้จะรับไม่ได้ - [MTU considerations | Cloud VPN](https://cloud.google.com/network-connectivity/docs/vpn/concepts/mtu-considerations) · Google Cloud · MTU ของ Cloud VPN gateway คือ 1,460 ไบต์, payload MTU ของอุโมงค์ IPv4 คือ 1,406 ไบต์ (ผ่านอุโมงค์แล้วเหลือราว 1,400) - [Demonstrations of tcpretrans, the Linux eBPF/bcc version](https://raw.githubusercontent.com/iovisor/bcc/master/tools/tcpretrans_example.txt) · IO Visor · แสดงหนึ่งบรรทัดต่อการส่งซ้ำแต่ละครั้ง และ -s แสดง sequence number ของแพ็กเก็ตที่ส่งซ้ำด้วย - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · mss, pmtu (path MTU) และ backoff (จำนวนครั้งที่ RTO เพิ่มเป็นสองเท่า) ใน ss -i - [ping(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ping.8.html) · iputils · -M do เปิด DF และไม่ส่งแพ็กเก็ตที่ใหญ่กว่า path MTU ที่เคอร์เนลรู้, -s คือขนาดข้อมูล (ไม่รวม ICMP header 8 ไบต์) - [Display Filter Reference: Internet Control Message Protocol](https://www.wireshark.org/docs/dfref/i/icmp.html) · Wireshark · display filter icmp.type และ icmp.code #### rt-mapping · mapping ของ NAT/โหลดบาลานเซอร์หมดอายุกลางการเชื่อมต่อ · NAT / load balancer mapping expired mid-connection ถ้าอุปกรณ์กลางทางลบ mapping (รายการที่บันทึกว่าจะส่งต่อการเชื่อมต่อนี้ไปที่ไหน) ของการเชื่อมต่อที่ idle แพ็กเก็ตถัดไปที่ส่งจะไปไม่ถึง การเชื่อมต่อจะส่งซ้ำวนไปจนหลุด หรืออุปกรณ์ตอบกลับด้วยการปฏิเสธการเชื่อมต่อ (RST) แล้วหลุดทันที - ทำไม → ผลคือ → บนหน้าจอ: การเชื่อมต่อที่ไม่มีแพ็กเก็ตวิ่งอยู่พักหนึ่ง (AFK, อยู่ในล็อบบี้) → NAT ของเราเตอร์, CGNAT ของ ISP, ไฟร์วอลล์, โหลดบาลานเซอร์ หรือ security group บนคลาวด์ลบ mapping ที่ idle → พอกลับมาขยับ การส่งซ้ำต่อเนื่องจนหลุด หรือหลุดทันที - อาการ: หลุด, ค้าง / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: เราคนเดียว, บางพื้นที่/บาง ISP / เกิดเมื่อไร: หลังอยู่เฉย ๆ สักพัก - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), อินฟราเครือข่าย (ทีมอินฟรา), อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์: ส่ง heartbeat โดยเว้นช่วงไม่เกินครึ่งหนึ่งของ idle timeout ที่สั้นที่สุด (mapping ในเราเตอร์ของผู้เล่นและ CGNAT ของ ISP จะต่ออายุแน่นอนก็ต่อเมื่อมีแพ็กเก็ตขาออกจากฝั่งใน และเราเปลี่ยน timeout เหล่านั้นไม่ได้ ไคลเอนต์จึงต้องเป็นฝ่ายส่ง), เชื่อมต่อใหม่อัตโนมัติเมื่อหลุด เซิร์ฟเวอร์: ตอบ heartbeat และถ้าไม่ได้รับนานเกินกำหนดให้ปิดการเชื่อมต่อเองก่อน (ลดช่วง TCP keepalive ด้วย socket option เช่น TCP_KEEPIDLE, ตรวจจับให้เร็วด้วย TCP_USER_TIMEOUT), ใช้ session token เพื่อต่อเซสชันเดิม - งานฝั่งทีมอินฟรา: เครือข่าย: รวบรวม idle timeout ของไฟร์วอลล์และโหลดบาลานเซอร์บนเส้นทางแล้วแชร์ให้ทีมพัฒนาเกม, เพิ่มค่าในไฟร์วอลล์และโหลดบาลานเซอร์ของเราถ้าจำเป็น เครื่องเซิร์ฟเวอร์/OS: ตรวจเวลา connection tracking ของ security group บนคลาวด์แล้วแชร์ให้ทีมพัฒนาเกม - ตัวเลขที่ควรรู้: เวลาที่คง TCP mapping ไว้ต่างกันไปตามอุปกรณ์ ตั้งแต่ไม่กี่นาทีถึงหลายชั่วโมง ถ้า security group บนคลาวด์ตั้งให้ติดตามการเชื่อมต่อ อินสแตนซ์ประเภท AWS Nitro v6 จะลบรายการที่ติดตามหลัง 350 วินาทีโดยค่าเริ่มต้น (ประเภทอื่น 5 วัน ดูรายการ “connection tracking ของ security group บนคลาวด์หมดอายุ”) ค่าเริ่มต้นของ TCP keepalive ใน Linux คือ “ตรวจเมื่อ idle ครบ 2 ชั่วโมง” จึงช้ากว่าอุปกรณ์ส่วนใหญ่ - บนกราฟ: การเชื่อมต่อหลุดพร้อมกัน (จำนวนครั้งที่หลุด, ระยะเวลา idle ก่อนหลุด) - จุดที่ต้องดู: packet capture ฝั่งเซิร์ฟเวอร์ช่วงไม่กี่นาทีสุดท้ายก่อนการเชื่อมต่อหลุด การเชื่อมต่อที่ยังอยู่ดูเวลา idle จาก lastsnd และ lastrcv (ms ที่ผ่านไปนับจากส่งและรับครั้งล่าสุด) ของ ss -ti และดู TcpExtTCPAbortOnTimeout (จำนวนการเชื่อมต่อที่ยกเลิกเพราะตัวจับเวลาหมด) ใน nstat ด้วย - สัญญาณว่าใช่: การเชื่อมต่อที่หลุดทุกรายการมีเวลา idle ก่อนหลุดเกินค่าใกล้เคียงกัน (idle timeout ของอุปกรณ์บนเส้นทาง เช่น 350 วินาทีของ security group บนอินสแตนซ์ AWS Nitro v6) และตั้งแต่แพ็กเก็ตแรกหลัง idle มีแต่การส่งซ้ำโดยไม่มี ACK จนยกเลิก หรือได้ RST กลับมาทันที - สัญญาณว่าไม่ใช่: หลุดระหว่างเล่นด้วยโดยไม่เกี่ยวกับเวลา idle: เป็นสาเหตุอื่น (“เส้นทางเปลี่ยน/เส้นทาง ECMP ที่เสีย”, “ไฟร์วอลล์/connection tracking ทิ้งแพ็กเก็ต”) ถ้าการเชื่อมต่อมี heartbeat วิ่งโดยเว้นช่วงไม่เกินครึ่งหนึ่งของ idle timeout ที่สั้นที่สุด ให้ตัดสาเหตุนี้ออก - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 5382: NAT Behavioral Requirements for TCP](https://www.rfc-editor.org/rfc/rfc5382) · IETF · คำแนะนำว่า idle timeout ของการเชื่อมต่อใน TCP NAT ต้องไม่น้อยกว่า 2 ชั่วโมง 4 นาที (ตั้งอยู่บนสมมติฐานว่าอุปกรณ์อาจลบเซสชันที่ idle ไปก่อน) - [RFC 4787: Network Address Translation (NAT) Behavioral Requirements for Unicast UDP](https://www.rfc-editor.org/rfc/rfc4787) · IETF · NAT mapping ต้องต่ออายุด้วยแพ็กเก็ตขาออกจากฝั่งใน (REQ-6) ส่วนการต่ออายุด้วยแพ็กเก็ตขาเข้าจากฝั่งนอกเป็นทางเลือก (สำหรับ UDP) - [Amazon EC2 security group connection tracking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-connection-tracking.html) · AWS · ค่าเริ่มต้นของ TCP idle tracking timeout คือ 350 วินาทีสำหรับอินสแตนซ์ประเภท Nitro v6 และ 432,000 วินาที (5 วัน) สำหรับประเภทอื่น แนะนำ keepalive ที่ถี่กว่า 5 นาที - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_keepalive_time ค่าเริ่มต้น 2 ชั่วโมง - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · TCP_KEEPIDLE (เวลา idle ก่อนเริ่ม keepalive), TCP_USER_TIMEOUT (เวลาที่รอข้อมูลที่ยังไม่ได้รับการยืนยันก่อนปิดการเชื่อมต่อ) - [RFC 5482: TCP User Timeout Option](https://www.rfc-editor.org/rfc/rfc5482) · IETF · TCP user timeout: ข้อมูลที่ส่งไปยังไม่ได้รับการยืนยันนานเท่าไรจึงจะปิดการเชื่อมต่อ - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · lastsnd และ lastrcv ใน ss -i: เวลาที่ผ่านไปนับจากส่งและรับครั้งล่าสุด (ms) - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtTCPAbortOnTimeout: จำนวนการเชื่อมต่อที่ยกเลิกไปโดยไม่ส่ง RST เพราะตัวจับเวลาของ TCP หมด #### rt-path · เส้นทางเปลี่ยน/เส้นทาง ECMP ที่เสีย · Route change / bad ECMP member แพ็กเก็ตจะหายไปในช่วงไม่กี่วินาทีที่เส้นทางอินเทอร์เน็ตเปลี่ยน หรือในการเชื่อมต่อที่ถูกจัดให้วิ่งบนเส้นทางที่เสียจากหลายเส้นทาง ECMP - ทำไม → ผลคือ → บนหน้าจอ: BGP คำนวณเส้นทางใหม่ หรืออุปกรณ์/ลิงก์ของเส้นทางหนึ่งในหลายเส้นทาง (ECMP, LAG) เสีย → แพ็กเก็ตหายชั่วคราวระหว่างสลับเส้นทาง หรือเฉพาะการเชื่อมต่อที่วิ่งบนเส้นทางนั้นหายอยู่ตลอด → จู่ ๆ ก็ค้างไปไม่กี่วินาทีแล้วตามด้วยอาการกรอเร็ว หรือ “เชื่อมต่อใหม่แล้วดีขึ้น” (ถูกจัดไปเส้นทางอื่น) - อาการ: ค้าง, กรอเร็ว, วาร์ป / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: บางพื้นที่/บาง ISP / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), ภายนอก (ภายนอก) - งานฝั่งทีมพัฒนาเกม: เก็บสถิติการส่งซ้ำต่อการเชื่อมต่อ (TCP_INFO) ไว้เพื่อดึง IP, พอร์ต และเวลาของคนที่เจอปัญหาได้, อย่าตัดการเชื่อมต่อที่หยุดไปไม่กี่วินาทีทันที - งานฝั่งทีมอินฟรา: มอนิเตอร์อัตราการส่งซ้ำแยกตามพื้นที่/ISP, ตรวจว่าเชื่อมต่อใหม่แล้วเส้นทางเปลี่ยนหรือไม่, จัดหาลิงก์จากหลาย ISP, ตรวจหาลิงก์ที่เสียในเส้นทาง ECMP/LAG ของอุปกรณ์เรา, วัดเส้นทางด้วยพอร์ต TCP เดียวกับเกม (ใช้ mtr --tcp --port เพราะเส้นทางถูกกำหนดจากที่อยู่และพอร์ต ping ทั่วไปจึงอาจวิ่งไปอีกเส้นทางและดูปกติ) - งานฝั่งภายนอก: รายงานเส้นทางที่เสียให้ ISP โดยแนบผลวัดเส้นทางด้วยพอร์ต TCP เดียวกับเกมและผลเทียบก่อน/หลังเชื่อมต่อใหม่ - บนกราฟ: ขึ้นเป็นขั้นบันไดจากจุดหนึ่ง (RTT (ปิง), อัตราการส่งซ้ำแยกตามพื้นที่/ISP) - จุดที่ต้องดู: รวมการส่งซ้ำแยกตามการเชื่อมต่อด้วย bcc tcpretrans -c เพื่อดึงที่อยู่และพอร์ตของผู้เล่นที่เจอปัญหา แล้วรัน mtr ด้วยพอร์ต TCP เดียวกับเกม (mtr -T -P PORT) ทั้งจากเซิร์ฟเวอร์ไปหาผู้เล่นและจากผู้เล่นมาหาเซิร์ฟเวอร์แล้วเทียบกัน เทียบผลก่อนและหลังเชื่อมต่อใหม่ด้วย - สัญญาณว่าใช่: ตั้งแต่จุดเวลาหนึ่ง RTT ของพื้นที่หรือ ISP หนึ่งเปลี่ยนเป็นขั้นบันไดและแพ็กเก็ตหายกระจุกอยู่ไม่กี่วินาที หรือแม้ใน ISP เดียวกัน มีเพียงบางการเชื่อมต่อ (คู่ที่อยู่/พอร์ต) ที่ส่งซ้ำอยู่ตลอดและดีขึ้นเมื่อเชื่อมต่อใหม่ บางครั้ง ping ธรรมดาดูปกติ แต่เห็นแพ็กเก็ตหายเฉพาะใน TCP mtr - สัญญาณว่าไม่ใช่: ทุกการเชื่อมต่อของ ISP นั้นแย่ลงพร้อมกันช่วงพีคหัวค่ำ: น่าจะเป็น “คิวคอขวดล้น” ถ้าแย่อยู่คนเดียวและแพ็กเก็ตหายตั้งแต่ ping ไปถึงเราเตอร์ น่าจะเป็น “แพ็กเก็ตหายช่วงไร้สาย” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - กรณีจริง: cloudflare-2020 - แหล่งอ้างอิง: - [RFC 2991: Multipath Issues in Unicast and Multicast Next-Hop Selection](https://www.rfc-editor.org/rfc/rfc2991) · IETF · ใน multipath ผลจากเครื่องมือวินิจฉัยอย่าง ping และ traceroute เชื่อถือได้ยาก พร้อมอธิบายวิธีล็อกเส้นทางด้วยการ hash flow - [RFC 2992: Analysis of an Equal-Cost Multi-Path Algorithm](https://www.rfc-editor.org/rfc/rfc2992) · IETF · ECMP เลือกเส้นทางถัดไปจาก hash ของฟิลด์ใน header ที่ใช้แยก flow (flow เดียวกันไปเส้นทางเดียวกัน) - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · TCP_INFO: ดึงสถานะรายซ็อกเก็ต (struct tcp_info) - [mtr(8) manual page source](https://raw.githubusercontent.com/traviscross/mtr/master/man/mtr.8.in) · mtr · -T (--tcp) ใช้ TCP SYN แทน ICMP, -P (--port) ระบุพอร์ตปลายทาง - [Demonstrations of tcpretrans, the Linux eBPF/bcc version](https://raw.githubusercontent.com/iovisor/bcc/master/tools/tcpretrans_example.txt) · IO Visor · แสดงที่อยู่และพอร์ตของอีกฝั่งหนึ่งบรรทัดต่อการส่งซ้ำแต่ละครั้ง และ -c สรุปจำนวนการส่งซ้ำแยกตาม flow #### rt-spurious-delay · การส่งซ้ำโดยไม่จำเป็นเพราะความหน่วงพุ่ง · Spurious RTO from delay spikes แพ็กเก็ตยังอยู่ครบและแค่มาถึงช้ามากชั่วครู่ แต่ถ้าความหน่วงนั้นนานกว่า RTO ฝั่งส่งจะตัดสินว่าหายแล้วส่งซ้ำ - ทำไม → ผลคือ → บนหน้าจอ: bufferbloat, โหมดประหยัดพลังงานของ Wi-Fi, การเปลี่ยนสถานะวิทยุของมือถือ หรือ VM ถูกพักการทำงาน ทำให้ความหน่วงพุ่งชั่วขณะหลายร้อย ms → RTO หมดเวลาก่อนจึงส่งซ้ำ แล้วแพ็กเก็ตต้นฉบับก็มาถึงตามมา (ฝั่งรับได้รับซ้ำ) → อาการค้างและกรอเร็วมาจากความหน่วงพุ่งเอง การส่งซ้ำโดยไม่จำเป็นแทบไม่ทำให้ค้างนานขึ้น แค่ดันเมตริกการส่งซ้ำให้สูงจนถูกเข้าใจผิดว่าแพ็กเก็ตหาย - อาการ: ค้าง, กรอเร็ว, อินพุตดีเลย์ / ปัจจัย: ความหน่วง, จิตเตอร์ - ใครเจอ: เราคนเดียว, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, หลังอยู่เฉย ๆ สักพัก - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ไคลเอนต์ Android 10 ขึ้นไปให้ขอโหมด Wi-Fi ความหน่วงต่ำระหว่างเล่น (Wi-Fi lock แบบ WIFI_MODE_FULL_LOW_LATENCY มีผลเฉพาะเมื่อจอเปิดอยู่และเกมอยู่เบื้องหน้า) เพื่อลดความหน่วงพุ่งจากโหมดประหยัดพลังงาน - งานฝั่งทีมอินฟรา: หลีกเลี่ยงอินสแตนซ์แบบ burstable, อย่าลดค่าต่ำสุดของ RTO ต่ำเกินไป, คง F-RTO และ timestamps ไว้ (tcp_frto, tcp_timestamps), ดูเมตริกการส่งซ้ำคู่กับ TCPSpuriousRTOs และ TCPDSACKRecv ของ nstat เพื่อไม่ให้เข้าใจผิดว่าแพ็กเก็ตหาย - งานฝั่งภายนอก: แนะนำให้ผู้เล่นใช้ SQM บนเราเตอร์และปิดโหมดประหยัดพลังงานของ Wi-Fi เพื่อลดความหน่วงพุ่งที่ต้นเหตุ - ตัวเลขที่ควรรู้: Linux ตรวจจับ RTO ที่ไม่จำเป็นด้วย F-RTO และบางครั้งก็ย้อนการลดอัตราการส่งกลับคืน ตรวจได้จาก TCPSpuriousRTOs (จำนวนครั้งที่ตัดสินว่าเป็น RTO ที่ไม่จำเป็น) และ TCPDSACKRecv (จำนวนครั้งที่ฝั่งรับแจ้งว่า “ได้รับไปแล้ว”) ใน nstat - บนกราฟ: พุ่งแบบสุ่มเป็นครั้งคราว (RTT (ปิง), จำนวน RTO ที่ไม่จำเป็น) - จุดที่ต้องดู: ค่าที่เพิ่มขึ้นของ TcpExtTCPTimeouts (RTO หมดเวลา), TcpExtTCPSpuriousRTOs, TcpExtTCPDSACKRecv และ TcpExtTCPLostRetransmit จากการรัน nstat ทุก 1 นาที ดูไปพร้อมกัน ถ้ามี packet capture ใช้ตัวกรอง Wireshark tcp.analysis.spurious_retransmission - สัญญาณว่าใช่: เมื่อ RTO เพิ่ม TcpExtTCPSpuriousRTOs หรือ TcpExtTCPDSACKRecv ก็เพิ่มตาม และในเวลาเดียวกัน RTT พุ่งเป็นหลายร้อย ms ใน capture ฝั่งรับมีทั้งแพ็กเก็ตต้นฉบับและแพ็กเก็ตที่ส่งซ้ำมาถึง - สัญญาณว่าไม่ใช่: TcpExtTCPSpuriousRTOs และ DSACK ไม่ขยับ แต่ TcpExtTCPLostRetransmit (แพ็กเก็ตที่ส่งซ้ำก็หายอีก) เพิ่มขึ้น: เป็นแพ็กเก็ตหายจริง ถ้า RTT ไม่พุ่งแต่ DSACK สูงอยู่ตลอด น่าจะเป็น “fast retransmit โดยไม่จำเป็นเพราะแพ็กเก็ตสลับลำดับ” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 5682: Forward RTO-Recovery (F-RTO): An Algorithm for Detecting Spurious Retransmission Timeouts with TCP](https://www.rfc-editor.org/rfc/rfc5682) · IETF · F-RTO: ใช้ ACK ที่มาหลัง RTO แยกแยะว่าเป็น RTO ที่ไม่จำเป็นหรือไม่ - [RFC 3481: TCP over Second (2.5G) and Third (3G) Generation Wireless Networks](https://www.rfc-editor.org/rfc/rfc3481) · IETF · ความหน่วงพุ่งในเครือข่ายมือถือ (เช่น handover และการกู้คืนลิงก์) ทำให้เกิด TCP timeout และการส่งซ้ำที่ไม่จำเป็น รวมถึงการลด congestion window - [RFC 2883: An Extension to the Selective Acknowledgement (SACK) Option for TCP](https://www.rfc-editor.org/rfc/rfc2883) · IETF · DSACK: ฝั่งรับแจ้งว่าได้รับซ้ำ ฝั่งส่งจึงรู้ว่ามีการส่งซ้ำโดยไม่จำเป็น - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtTCPSpuriousRTOs (RTO ที่ไม่จำเป็นซึ่ง F-RTO ตรวจพบ), TcpExtTCPDSACKRecv (จำนวน DSACK ที่ได้รับ), TcpExtTCPLostRetransmit (จำนวนครั้งที่ SACK แจ้งว่าแพ็กเก็ตที่ส่งซ้ำหายอีก) - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_frto เปิดเป็นค่าเริ่มต้น (เป็นผลดีกับเครือข่ายไร้สายที่ RTT แกว่ง), tcp_timestamps ค่าเริ่มต้น 1 - [RFC 6298: Computing TCP's Retransmission Timer](https://www.rfc-editor.org/rfc/rfc6298) · IETF · หลักฐานว่าต้องใช้ค่าต่ำสุดของ RTO ที่สูงพอเพื่อกันการส่งซ้ำโดยไม่จำเป็น (แนะนำอย่างน้อย 1 วินาที) - [WifiManager](https://developer.android.com/reference/android/net/wifi/WifiManager#WIFI_MODE_FULL_LOW_LATENCY) · Android (Google) · WIFI_MODE_FULL_LOW_LATENCY (API 29, Android 10): Wi-Fi lock แบบความหน่วงต่ำที่มีผลเฉพาะเมื่อเชื่อมต่อ AP อยู่ จอเปิดอยู่ และแอปอยู่เบื้องหน้า - [net/ipv4/proc.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/proc.c?h=v6.12) · Linux kernel · ชื่อตัวนับที่ nstat แสดง: TCPTimeouts, TCPSpuriousRTOs, TCPDSACKRecv, TCPLostRetransmit - [net/ipv4/tcp_timer.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_timer.c?h=v6.12) · Linux kernel · TCPTimeouts เพิ่มขึ้นเมื่อตัวจับเวลาส่งซ้ำ (RTO) หมดเวลา - [nstat(8) — Linux manual page](https://man7.org/linux/man-pages/man8/nstat.8.html) · iproute2 · โดยค่าเริ่มต้น nstat แสดงค่าที่เพิ่มขึ้นนับจากการรันครั้งก่อน - [Display Filter Reference: Transmission Control Protocol](https://www.wireshark.org/docs/dfref/t/tcp.html) · Wireshark · display filter tcp.analysis.spurious_retransmission #### rt-reorder · fast retransmit โดยไม่จำเป็นเพราะแพ็กเก็ตสลับลำดับ · Reordering triggers spurious fast retransmit เมื่อแพ็กเก็ตสลับลำดับเพราะวิ่งผ่านหลายเส้นทางหรือลิงก์ที่รวมกัน ฝั่งรับจะแจ้งว่า “มีแพ็กเก็ตขาดหาย” ด้วย duplicate ACK และฝั่งส่งจะส่งแพ็กเก็ตที่ไม่ได้หายซ้ำ - ทำไม → ผลคือ → บนหน้าจอ: อุปกรณ์ที่แบ่งเส้นทางเป็นรายแพ็กเก็ต, LAG (การรวมลิงก์) ที่กระจายเป็นรายแพ็กเก็ต และจังหวะที่เส้นทางเปลี่ยน ทำให้ลำดับสลับกัน → แพ็กเก็ตหลังมาถึงก่อน duplicate ACK สะสมครบ 3 ตัว → fast retransmit → แพ็กเก็ตเกมที่วิ่งห่าง ๆ แทบไม่ได้รับผล อัปเดตใหญ่ในที่ที่คนเยอะและการดาวน์โหลดแพตช์ช้าลง และบางครั้งกระตุก - อาการ: กระตุก, อินพุตดีเลย์ / ปัจจัย: จิตเตอร์ - ใครเจอ: บางพื้นที่/บาง ISP, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา, ตอนคนแห่มารวมกัน - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: เครือข่าย: กระจายเป็นรายการเชื่อมต่อแทนรายแพ็กเก็ต (ใช้ hash ของที่อยู่และพอร์ตกับ ECMP/LAG) เครื่องเซิร์ฟเวอร์/OS: ใช้ RACK (ตัดสินการหายตามเวลา ทนต่อการสลับลำดับ และเมื่อตรวจพบการส่งซ้ำโดยไม่จำเป็นจาก DSACK จะขยายช่วงที่ยอมให้สลับลำดับเองอัตโนมัติ), ตรวจระดับการสลับลำดับที่ Linux ประเมินเองในแต่ละการเชื่อมต่อ (ค่า reordering ใน ss -ti ค่าเริ่มต้น tcp_reordering=3) - บนกราฟ: สูงตลอดตั้งแต่แรก (จำนวนครั้งที่ตรวจพบการสลับลำดับ, จำนวน DSACK ที่ได้รับ) - จุดที่ต้องดู: TcpExtTCPSACKReorder และ TcpExtTCPTSReorder (จำนวนครั้งที่ตรวจพบการสลับลำดับ) กับ TcpExtTCPDSACKRecv ใน nstat ส่วนแต่ละการเชื่อมต่อดู reordering (แสดงเมื่อไม่ใช่ 3) และ reord_seen ใน ss -ti ใน packet capture ใช้ตัวกรอง Wireshark tcp.analysis.out_of_order - สัญญาณว่าใช่: ตัวนับการสลับลำดับและ DSACK ขึ้นเรื่อย ๆ ไม่ขึ้นกับช่วงเวลา และค่า reordering ของการเชื่อมต่อที่ผ่านเส้นทางหรืออุปกรณ์หนึ่งสูงกว่า 3 ใน capture ฝั่งรับ แพ็กเก็ตหลังมาก่อน และแพ็กเก็ตก่อนหน้าก็ตามมาในไม่ช้า - สัญญาณว่าไม่ใช่: ตัวนับการสลับลำดับไม่ขยับ แต่ TcpExtTCPLostRetransmit เพิ่ม: เป็นแพ็กเก็ตหายจริง ถ้า DSACK เพิ่มเฉพาะจังหวะที่ RTT พุ่ง น่าจะเป็น “การส่งซ้ำโดยไม่จำเป็นเพราะความหน่วงพุ่ง” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 2991: Multipath Issues in Unicast and Multicast Next-Hop Selection](https://www.rfc-editor.org/rfc/rfc2991) · IETF · ถ้าแบ่งเส้นทางเป็นรายแพ็กเก็ต ลำดับจะสลับ และถ้าแพ็กเก็ตหลังตั้งแต่ 3 ตัวขึ้นไปมาถึงก่อน TCP จะทำ fast retransmit โดยไม่จำเป็น - [RFC 2992: Analysis of an Equal-Cost Multi-Path Algorithm](https://www.rfc-editor.org/rfc/rfc2992) · IETF · ECMP ที่เลือกเส้นทางจาก hash ของฟิลด์ใน header ที่ใช้แยก flow (กระจายเป็นราย flow) - [RFC 5681: TCP Congestion Control](https://www.rfc-editor.org/rfc/rfc5681) · IETF · ทำ fast retransmit เมื่อได้รับ duplicate ACK ตัวที่สาม - [RFC 8985: The RACK-TLP Loss Detection Algorithm for TCP](https://www.rfc-editor.org/rfc/rfc8985) · IETF · RACK ตัดสินการหายตามเวลา จึงทนต่อการสลับลำดับ และเมื่อได้รับ DSACK จะขยายช่วงเวลาที่ยอมให้สลับลำดับ (reo_wnd) - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_reordering ค่าเริ่มต้น 3 (ปรับอัตโนมัติต่อการเชื่อมต่อได้ถึง tcp_max_reordering), การตั้งค่า RACK ใน tcp_recovery - [misc/ss.c](https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/misc/ss.c?h=v6.12.0) · iproute2 · ss -ti แสดง reordering:ค่า เมื่อค่า reordering ของการเชื่อมต่อต่างจากค่าเริ่มต้น 3 และแสดง reord_seen:จำนวนครั้ง เมื่อเคยเจอการสลับลำดับ - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtTCPSACKReorder และ TcpExtTCPTSReorder (ตรวจพบการสลับลำดับ), TcpExtTCPDSACKRecv (จำนวน DSACK ที่ได้รับ), TcpExtTCPLostRetransmit (แพ็กเก็ตที่ส่งซ้ำหายอีก) - [include/uapi/linux/tcp.h](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/tcp.h?h=v6.12) · Linux kernel · tcpi_reord_seen ใน tcp_info: จำนวนครั้งที่การเชื่อมต่อเจอการสลับลำดับ - [Display Filter Reference: Transmission Control Protocol](https://www.wireshark.org/docs/dfref/t/tcp.html) · Wireshark · display filter tcp.analysis.out_of_order #### rt-ack-path · ACK มาช้าหรือหาย (อัปโหลดเต็ม) · ACK path congestion on asymmetric links ข้อมูลมาถึงเรียบร้อย แต่ถ้า ACK ที่บอกว่า “ได้รับแล้ว” ไปติดอยู่ในคิวอัปโหลดที่เต็มจนช้าหรือหาย ฝั่งส่งจะตัดสินว่าหายแล้วส่งซ้ำ - ทำไม → ผลคือ → บนหน้าจอ: อัปโหลดเต็มเพราะมีคนในบ้านอัปโหลดวิดีโอหรือสำรองข้อมูลขึ้นคลาวด์ → ACK ติดอยู่ในคิวของเราเตอร์ช้าไปหลายร้อย ms หรือถูกทิ้งเพราะคิวล้น → แพ็กเก็ตเกมที่เซิร์ฟเวอร์ส่งมาส่วนใหญ่มาตรงเวลา แต่อินพุตของเราที่กองอยู่ในคิวอัปโหลดเดียวกันไปช้า จึงเกิดอินพุตดีเลย์และดีดกลับ บางครั้งมีการส่งซ้ำโดยไม่จำเป็น - อาการ: อินพุตดีเลย์, ดีดกลับ / ปัจจัย: ความหน่วง, แพ็กเก็ตหาย - ใครเจอ: คนในบ้านเดียวกัน / เกิดเมื่อไร: สุ่มเป็นครั้งคราว, ช่วงพีคหัวค่ำ - ผู้รับผิดชอบหลัก: ภายนอก (ภายนอก) / ร่วมกับ: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: แสดงสถานะเครือข่ายบนจอเมื่อปิงพุ่ง, ขึ้นคำแนะนำ “ตรวจโปรแกรมที่กำลังอัปโหลด” - งานฝั่งภายนอก: แนะนำให้ผู้เล่นใช้ SQM บนเราเตอร์เพื่อให้คิวอัปโหลดสั้น, ให้แพ็กเก็ตเล็ก (ACK) ไปก่อน, จำกัดความเร็วอัปโหลด (อัปโหลดวิดีโอ, สำรองข้อมูลขึ้นคลาวด์) - ตัวเลขที่ควรรู้: ACK ตัวหลังยืนยันแทนตัวก่อนหน้าได้ ACK หายไปไม่กี่ตัวจึงมักไม่เป็นไร ปัญหาอยู่ที่ ACK ที่ติดอยู่ในคิวจนช้า - บนกราฟ: สูงเฉพาะบางกลุ่ม (RTT (ปิง) ต่อการเชื่อมต่อ) - จุดที่ต้องดู: ping ไปเซิร์ฟเวอร์เกมจาก PC ของผู้เล่น เทียบระหว่างตอนเปิดและปิดการอัปโหลด (อัปโหลดวิดีโอ, สำรองข้อมูลขึ้นคลาวด์) ฝั่งเซิร์ฟเวอร์ดู rtt ของการเชื่อมต่อผู้เล่นคนนั้นด้วย ss -ti - สัญญาณว่าใช่: ping ขึ้นเป็นหลายร้อย ms เฉพาะตอนอัปโหลด และเกิดอินพุตดีเลย์และดีดกลับ พอหยุดอัปโหลดก็กลับมาปกติในไม่ช้า ฝั่งเซิร์ฟเวอร์เห็น rtt ของการเชื่อมต่อนั้นขึ้นในช่วงเดียวกัน - สัญญาณว่าไม่ใช่: แพ็กเก็ตหายและความหน่วงเกิดโดยไม่เกี่ยวกับการอัปโหลด: น่าจะเป็น “แพ็กเก็ตหายช่วงไร้สาย” หรือสาเหตุฝั่งเส้นทาง ถ้าช้าเฉพาะทิศทางจากเซิร์ฟเวอร์ไปหาผู้เล่นและไม่เกี่ยวกับการอัปโหลด น่าจะเป็น “คิวคอขวดล้น” - วิธีตรวจ: ตรวจที่สภาพแวดล้อมฝั่งผู้เล่น - แหล่งอ้างอิง: - [RFC 3449: TCP Performance Implications of Network Path Asymmetry](https://www.rfc-editor.org/rfc/rfc3449) · IETF · บนเน็ตแบบอสมมาตรที่อัปโหลดแคบ ถ้า ACK ช้าหรือหาย ประสิทธิภาพ TCP จะลดลง, ACK เป็นการยืนยันแบบสะสม ถ้าบางตัวหาย ACK ตัวหลังก็ยืนยันแทนได้, มาตรการอย่างการจัดลำดับให้ ACK ไปก่อน - [Smart Queue Management](https://www.bufferbloat.net/projects/cerowrt/wiki/Smart_Queue_Management/) · Bufferbloat.net · ใช้การจัดการคิวและ shaping ที่เราเตอร์เพื่อรักษาคิวให้สั้น - [tc-cake(8) — Linux manual page](https://man7.org/linux/man-pages/man8/tc-cake.8.html) · iproute2 · CAKE แยก flow และลดความหน่วงของ flow ที่ส่งห่าง ๆ (sparse flow) ให้ต่ำที่สุด - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · rtt (เวลาไปกลับเฉลี่ย) และ rttvar (ส่วนเบี่ยงเบน) ใน ss -i #### rt-rto-setting · ค่า RTO ไม่เหมาะกับสภาพแวดล้อม · RTO min too low or too high ถ้าลดค่าต่ำสุดของ RTO มากเกินไป แค่ช้านิดเดียวก็เกิดการส่งซ้ำโดยไม่จำเป็น ส่วนค่าเริ่มต้น (200 ms) ก็นานเกินไปสำหรับเกม ทุกครั้งที่แพ็กเก็ตหายจึงหยุดนาน - ทำไม → ผลคือ → บนหน้าจอ: ลดค่าต่ำสุดของ RTO ลงมากเพื่อใช้ในดาต้าเซ็นเตอร์ หรือใช้ค่าเริ่มต้นตามเดิมกับเส้นทางอินเทอร์เน็ต → ถ้าต่ำ แค่ความหน่วงชั่วขณะก็ส่งซ้ำทะลัก ถ้าสูง หายทุกครั้งก็ต้องรอนาน → ถ้าใช้ค่าเริ่มต้น แพ็กเก็ตหายครั้งเดียวจะค้างหลายร้อย ms แล้วตามด้วยอาการกรอเร็ว ถ้าลดต่ำเกินไป อาการค้างจะลดลงแต่การส่งซ้ำโดยไม่จำเป็นพุ่งจนเปลืองแบนด์วิดท์ - อาการ: ค้าง, กรอเร็ว, อินพุตดีเลย์ / ปัจจัย: ความหน่วง - ใครเจอ: ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: ถ้าใช้ Linux 6.15 ขึ้นไป พิจารณาลดเพดาน RTO ของการเชื่อมต่อเกมด้วย TCP_RTO_MAX_MS (เวลาจนถึงการเลิกเชื่อมต่อก็สั้นลงด้วย จึงควรกำหนดเวลาตัดสินว่าการเชื่อมต่อขาดด้วย TCP_USER_TIMEOUT ไปพร้อมกัน), ลดค่าต่ำสุดของ RTO ด้วย socket option TCP_RTO_MIN_US (6.15 ขึ้นไป) เฉพาะการเชื่อมต่อภายในระหว่างเซิร์ฟเวอร์, พิจารณาใช้ socket option TCP_THIN_LINEAR_TIMEOUTS กับการเชื่อมต่อเกมเท่านั้น เพื่อไม่ให้ RTO ที่เกิดต่อเนื่องเพิ่มเป็นสองเท่า - งานฝั่งทีมอินฟรา: ลด rto_min แยกตามเส้นทางเฉพาะการเชื่อมต่อภายในระหว่างเซิร์ฟเวอร์, เส้นทางอินเทอร์เน็ตคงค่าเริ่มต้นไว้แต่เสริมด้วย RACK-TLP และการตั้งค่า thin stream (tcp_thin_linear_timeouts) - ตัวเลขที่ควรรู้: RTO ของ Linux = เวลาไปกลับ + max(200 ms, ส่วนเบี่ยงเบนของ RTT×4) ล้มเหลวแต่ละครั้งจะเพิ่มเป็นสองเท่า สูงสุด 120 วินาที Linux 6.15 ขึ้นไปลดเพดานนี้ลงได้ถึง 1 วินาทีด้วย TCP_RTO_MAX_MS - บนกราฟ: สูงตลอดตั้งแต่แรก (RTO ต่อการเชื่อมต่อ, จำนวน RTO ที่ไม่จำเป็น) - จุดที่ต้องดู: การตั้งค่าต่ำสุดของ RTO บนเซิร์ฟเวอร์ (rto_min ใน ip route show, Linux 6.11 ขึ้นไปดู sysctl net.ipv4.tcp_rto_min_us) กับ rto และ rtt ใน ss -ti และค่าที่เพิ่มขึ้นของ TcpExtTCPSpuriousRTOs ใน nstat - สัญญาณว่าใช่: บนเซิร์ฟเวอร์ที่ลดค่าต่ำสุดลง rto ของการเชื่อมต่ออินเทอร์เน็ตแนบชิด rtt และ TcpExtTCPSpuriousRTOs เพิ่มขึ้นมาก ถ้าใช้ค่าเริ่มต้นตามเดิม rto ของการเชื่อมต่อเกมสูงกว่า rtt ตั้งแต่ 200 ms ขึ้นไป และแพ็กเก็ตหายแต่ละครั้งจะหยุดนานเท่านั้น - สัญญาณว่าไม่ใช่: rto เป็นไปตามสูตรปกติ (rtt + ราว 200 ms) และ RTO ที่ไม่จำเป็นก็น้อย แต่การหยุดนานผิดปกติ: น่าจะเป็นฝั่งแพ็กเก็ตหายต่อเนื่องหรือวิธีกู้คืน (“thin stream กู้คืนช้า”, “อุปกรณ์กลางทางตัด TCP option ทิ้ง”) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [RFC 6298: Computing TCP's Retransmission Timer](https://www.rfc-editor.org/rfc/rfc6298) · IETF · RTO = SRTT + max(G, 4·RTTVAR), แนะนำอย่างน้อย 1 วินาที, เพิ่มเป็นสองเท่าทุกครั้งที่ล้มเหลว, ถ้าจะกำหนดค่าสูงสุดต้องไม่น้อยกว่า 60 วินาที - [include/net/tcp.h](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp.h?h=v6.12) · Linux kernel · TCP_RTO_MIN ของ Linux 200 ms, TCP_RTO_MAX 120 วินาที - [net/ipv4/tcp_input.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_input.c?h=v6.12) · Linux kernel · RTO ของ Linux คือ SRTT + rttvar และ rttvar จะไม่ต่ำกว่าค่าต่ำสุดของ RTO (ค่าเริ่มต้น 200 ms) - [tcp: add the ability to control max RTO](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=54a378f43425085d0684679d99735696b69165bc) · Linux kernel · เพิ่ม socket option TCP_RTO_MAX_MS (1–120 วินาที) ตั้งแต่ Linux 6.15 - [tcp: add sysctl_tcp_rto_min_us](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f086edef71be7174a16c1ed67ac65a085cda28b1) · Linux kernel · เพิ่ม tcp_rto_min_us ซึ่งเป็นค่าต่ำสุดของ RTO เริ่มต้นของทั้งเซิร์ฟเวอร์ ตั้งแต่ Linux 6.11 - [tcp: support TCP_RTO_MIN_US for set/getsockopt use](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f38805c5d26fe4af97837c10d58074a7496638bf) · Linux kernel · เพิ่ม socket option TCP_RTO_MIN_US สำหรับกำหนดค่าต่ำสุดของ RTO รายซ็อกเก็ต ตั้งแต่ Linux 6.15 - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_rto_min_us ค่าเริ่มต้น 200000 (ตัวเลือกเส้นทาง rto_min และ socket option TCP_RTO_MIN_US มีผลก่อน), tcp_rto_max_ms 1,000–120,000 (ค่าเริ่มต้น 120,000), tcp_thin_linear_timeouts - [ip-route(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ip-route.8.html) · iproute2 · ตัวเลือก rto_min รายเส้นทาง: ค่าต่ำสุดของ RTO เมื่อสื่อสารกับปลายทางนั้น - [Thin-streams and TCP](https://docs.kernel.org/networking/tcp-thin.html) · Linux kernel · ใช้ TCP_THIN_LINEAR_TIMEOUTS ปิด exponential backoff เฉพาะการเชื่อมต่อแบบ thin stream ได้ - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · TCP_USER_TIMEOUT: เวลาที่รอข้อมูลที่ยังไม่ได้รับการยืนยันก่อนปิดการเชื่อมต่อ - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · rto (ms) และ rtt ใน ss -i - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtTCPSpuriousRTOs: RTO ที่ไม่จำเป็นซึ่ง F-RTO ตรวจพบ #### rt-thin · thin stream กู้คืนช้า · Thin streams fall back to RTO ถ้าส่งแพ็กเก็ตเล็ก ๆ ห่าง ๆ แบบเกม RTO จะมาถึงก่อนที่ “แพ็กเก็ตที่ตามมา 3 ตัว” จะครบ เมื่อหายเท่ากัน จึงหยุดนานกว่าการส่งข้อมูลปริมาณมากอยู่มาก - ทำไม → ผลคือ → บนหน้าจอ: แพ็กเก็ตห่างกันราว 100 ms แพ็กเก็ตที่ยังไม่ได้รับ ACK (in-flight) จึงมีอยู่ไม่กี่ตัว → กว่า duplicate ACK จะครบ 3 ตัวต้องใช้เวลาเกิน 300 ms จึงเป็น RTO (ปิง + 200 ms) ที่ทำงานก่อน ถ้าหายต่อเนื่องก็เพิ่มเป็นสองเท่าทุกครั้ง → แพ็กเก็ตหายครั้งเดียวค้างราว 0.3 วินาที ถ้าตัวที่ส่งซ้ำหายอีก จะค้างเกือบ 1 วินาทีแล้วตามด้วยอาการกรอเร็ว - อาการ: ค้าง, กรอเร็ว / ปัจจัย: แพ็กเก็ตหาย, การหยุดชะงัก - ใครเจอ: เราคนเดียว, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: เปิด TCP_NODELAY (ถ้าเปิด Nagle ไว้ จะไม่มีแพ็กเก็ตที่ตามมาให้ RACK ใช้ตัดสิน), ส่งแพ็กเก็ตเรียลไทม์ด้วยการส่งซ้ำที่ทำเองบน UDP ไคลเอนต์: เปิด TCP_NODELAY, ส่งแพ็กเก็ตเรียลไทม์แบบเดียวกับเซิร์ฟเวอร์ (UDP) - งานฝั่งทีมอินฟรา: ใช้ RACK-TLP (ค่าเริ่มต้นของ Linux รุ่นใหม่), ใช้ tcp_thin_linear_timeouts เพื่อไม่ให้ RTO ที่เกิดต่อเนื่องเพิ่มเป็นสองเท่า - ตัวเลขที่ควรรู้: ถ้าแพ็กเก็ตห่างกัน 100 ms และปิง 60 ms กว่าจะถึง fast retransmit ใช้เวลาราว 360 ms (จนแพ็กเก็ตที่ตามมา 3 ตัวมาถึงและการยืนยันกลับมา) ส่วน RTO ประมาณ 260 ms ถ้าใช้ RACK จะส่งซ้ำทันทีที่การยืนยันของแพ็กเก็ตถัดไปกลับมา ที่ราว 160 ms ถ้าแพ็กเก็ตห่างกันเกิน 200 ms RACK ก็ไม่เร็วกว่า RTO - บนกราฟ: ขาดช่วงแล้วมารวดเดียว (ปริมาณที่รับต่อการเชื่อมต่อ, จำนวนครั้งที่ RTO หมดเวลา) - จุดที่ต้องดู: เทียบค่าที่เพิ่มขึ้นของ TcpExtTCPTimeouts (RTO หมดเวลา), TcpExtTCPFastRetrans (fast retransmit), TcpExtTCPLossProbes และ TcpExtTCPLossProbeRecovery (TLP) ใน nstat และดู rto กับ backoff ของการเชื่อมต่อเกมด้วย ss -ti ตรวจค่า net.ipv4.tcp_recovery, tcp_early_retrans และ tcp_sack ของเซิร์ฟเวอร์ด้วย - สัญญาณว่าใช่: ในบรรดาการส่งซ้ำ RTO หมดเวลามากกว่า fast retransmit และเห็นการเชื่อมต่อเกมที่ backoff มากกว่า 0 (กำลังเจอ RTO) บ่อย ระหว่างที่หยุด ปริมาณที่รับเป็น 0 พอกู้คืนได้ข้อมูลก็ทะลักมาพร้อมกัน - สัญญาณว่าไม่ใช่: การส่งข้อมูลปริมาณมากบนเซิร์ฟเวอร์เดียวกันก็หยุดนานพอกัน: เป็นปัญหาแพ็กเก็ตหายที่ไม่เกี่ยวกับรูปแบบการเชื่อมต่อ ถ้ากระจุกที่การเชื่อมต่อที่ไม่มี SACK หรือ timestamps น่าจะเป็น “อุปกรณ์กลางทางตัด TCP option ทิ้ง” - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: เดิม Linux เคยมีตัวเลือกส่งซ้ำเมื่อได้ duplicate ACK เพียง 1 ตัวสำหรับ thin stream (tcp_thin_dupack) แต่ถูกถอดออกในปี 2017 ปัจจุบัน RACK ทำหน้าที่นั้นแทน ถ้าเปิด Nagle อยู่ (ปิด TCP_NODELAY) ระหว่างรอการยืนยันของแพ็กเก็ตที่หาย TCP จะไม่ส่งแพ็กเก็ตใหม่ด้วย RACK จึงไม่มีแพ็กเก็ตที่ตามมาให้ใช้ตัดสิน และต้องรอจนถึง RTO - แหล่งอ้างอิง: - [Thin-streams and TCP](https://docs.kernel.org/networking/tcp-thin.html) · Linux kernel · thin stream ที่ส่งห่าง ๆ แบบเกม fast retransmit ทำงานได้ไม่ดี จึงต้องพึ่ง timeout ที่ยาว เกณฑ์คือแพ็กเก็ตที่ยังไม่ได้รับ ACK (in-flight) น้อยกว่า 4 ตัว - [RFC 5681: TCP Congestion Control](https://www.rfc-editor.org/rfc/rfc5681) · IETF · ทำ fast retransmit เมื่อได้รับ duplicate ACK ตัวที่สาม - [RFC 8985: The RACK-TLP Loss Detection Algorithm for TCP](https://www.rfc-editor.org/rfc/rfc8985) · IETF · RACK ตัดสินการหายจากการที่แพ็กเก็ตซึ่งส่งทีหลังส่งถึงแล้ว เวลารอของ TLP คือ 2·SRTT (ถ้ามีแพ็กเก็ตที่ยังไม่ได้รับการยืนยันเพียงตัวเดียว จะบวกเวลาเผื่อ delayed ACK) - [include/net/tcp.h](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp.h?h=v6.12) · Linux kernel · TCP_RTO_MIN 200 ms, เกณฑ์ thin stream (แพ็กเก็ต in-flight น้อยกว่า 4 ตัว) และการลองใหม่แบบ linear 6 ครั้ง - [tcp: remove thin_dupack feature](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4a7f6009441144783e5925551c72e3f2e1b0839b) · Linux kernel · ลบ thin_dupack ในเดือนมกราคม 2017 (Linux 4.11) พร้อมคำอธิบายว่า RACK ทำหน้าที่นั้นแทน - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_thin_linear_timeouts: ถ้าเป็น thin stream RTO จะไม่เพิ่มเป็นสองเท่าในช่วงสูงสุด 6 ครั้งแรก (ปิดเป็นค่าเริ่มต้น) - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · TCP_NODELAY ปิดอัลกอริทึม Nagle - [net/ipv4/proc.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/proc.c?h=v6.12) · Linux kernel · ชื่อตัวนับที่ nstat แสดง: TCPTimeouts, TCPFastRetrans, TCPLossProbes, TCPLossProbeRecovery - [net/ipv4/tcp_timer.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_timer.c?h=v6.12) · Linux kernel · TCPTimeouts เพิ่มขึ้นเมื่อตัวจับเวลาส่งซ้ำ (RTO) หมดเวลา - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtTCPFastRetrans (การส่งซ้ำขณะที่ไม่ได้อยู่ในสถานะ Loss), TcpExtTCPLossProbes (ส่ง TLP) และ TcpExtTCPLossProbeRecovery (กู้คืนการหายด้วย TLP) - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · rto (ms) และ backoff (จำนวนครั้งที่ RTO เพิ่มเป็นสองเท่า) ใน ss -i #### rt-sack-stripped · อุปกรณ์กลางทางตัด TCP option ทิ้ง · Middlebox strips TCP options ถ้าไฟร์วอลล์หรืออุปกรณ์เร่งความเร็วบางตัวลบหรือแก้ TCP option เมื่อแพ็กเก็ตหายหลายตัว จะกู้คืนได้แค่ตัวเดียวต่อหนึ่งรอบไปกลับ หรือ window (ปริมาณที่ส่งได้ในครั้งเดียว) จะเล็กลงจนช้า - ทำไม → ผลคือ → บนหน้าจอ: “TCP normalization” ของไฟร์วอลล์ หรืออุปกรณ์เร่งความเร็วรุ่นเก่าลบ option SACK, timestamps และ window scaling → ถ้าแพ็กเก็ตหายหลายตัว จะกู้คืนได้ทีละตัวต่อรอบไปกลับ, window ถูกจำกัดไว้ที่ 64 KB → ทุกครั้งที่หายจะค้างนานขึ้นมาก (ไม่มี SACK ก็ใช้ RACK-TLP ไม่ได้) แล้วตามด้วยอาการกรอเร็ว การส่งข้อมูลปริมาณมากอย่างแพตช์ก็ช้า - อาการ: ค้าง, กรอเร็ว / ปัจจัย: การหยุดชะงัก, ความหน่วง - ใครเจอ: บางพื้นที่/บาง ISP, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตลอดเวลา - ผู้รับผิดชอบหลัก: อินฟราเครือข่าย (ทีมอินฟรา) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมอินฟรา: เครือข่าย: ปิดการตั้งค่า TCP normalization ของอุปกรณ์นั้น, ตรวจ sequence randomization ของไฟร์วอลล์ด้วย, เทียบ option ใน SYN จาก packet capture ที่ปลายทั้งสองฝั่ง เครื่องเซิร์ฟเวอร์/OS: ตรวจว่าการเชื่อมต่อที่ไม่มี sack และ wscale ใน ss -ti กระจุกที่เส้นทางใดหรือไม่ (PC Windows บางเครื่องไม่ใช้ ts ตามการตั้งค่า การที่ขาดแค่ ts จึงอาจเป็นเรื่องปกติ), ตรวจว่า net.ipv4.tcp_sack ของเซิร์ฟเวอร์เป็น 1 - บนกราฟ: สูงตลอดตั้งแต่แรก (จำนวนการกู้คืนที่เริ่มโดยไม่มี SACK (TcpExtTCPRenoRecovery)) - จุดที่ต้องดู: ดูว่าแต่ละการเชื่อมต่อใน ss -ti มี sack และ wscale หรือไม่ ดูสัดส่วน TcpExtTCPRenoRecovery (การกู้คืนที่เริ่มโดยไม่มี SACK) ต่อ TcpExtTCPSackRecovery และ TcpExtTCPSACKDiscard (จำนวน SACK block ที่ทิ้งเพราะตัวเลขไม่สอดคล้องกัน) ใน nstat เส้นทางที่สงสัยให้ capture SYN ที่ปลายทั้งสองฝั่งแล้วเทียบ option (เช่น tcp.options.sack_perm ใน Wireshark) - สัญญาณว่าใช่: เฉพาะการเชื่อมต่อที่ผ่านเส้นทางหรืออุปกรณ์หนึ่งที่ไม่มี sack และ wscale และสัดส่วน TcpExtTCPRenoRecovery สูง option อนุญาต SACK ที่มีใน SYN ฝั่งส่งไม่มีใน SYN ฝั่งรับ ถ้าสาเหตุคือ sequence randomization option จะยังอยู่ แต่ TcpExtTCPSACKDiscard เพิ่มขึ้น - สัญญาณว่าไม่ใช่: ทุกการเชื่อมต่อไม่มี sack: ตรวจค่า net.ipv4.tcp_sack ของเซิร์ฟเวอร์ก่อน ถ้า option ครบและ TcpExtTCPSACKDiscard ไม่ขยับ การกู้คืนช้ามาจากที่อื่น (“thin stream กู้คืนช้า”) - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - รายละเอียดเพิ่มเติม: ถึง option ยังอยู่ SACK ก็อาจเสียได้ ถ้า sequence randomization ของไฟร์วอลล์เปลี่ยนแค่ sequence number ใน header แต่ไม่แก้ตัวเลขใน SACK ฝั่งส่งจะทิ้ง SACK ที่ตัวเลขไม่สอดคล้องกัน กรณีที่ปิด tcp_sack=0 บนเซิร์ฟเวอร์ไว้ตอนเกิดปัญหาความปลอดภัยของ SACK ในปี 2019 แล้วลืมเปิดคืน ก็ให้ผลแบบเดียวกัน - แหล่งอ้างอิง: - [RFC 2018: TCP Selective Acknowledgment Options](https://www.rfc-editor.org/rfc/rfc2018) · IETF · ถ้าไม่มี SACK และมีแค่ cumulative ACK จะรู้ได้แค่แพ็กเก็ตที่หายหนึ่งตัวต่อรอบไปกลับ - [RFC 7323: TCP Extensions for High Performance](https://www.rfc-editor.org/rfc/rfc7323) · IETF · ถ้าไม่มี option window scaling window จะใหญ่สุด 2^16 = 64 KiB - [RFC 8985: The RACK-TLP Loss Detection Algorithm for TCP](https://www.rfc-editor.org/rfc/rfc8985) · IETF · RACK-TLP ต้องใช้ SACK - [net/ipv4/tcp_output.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_output.c?h=v6.12) · Linux kernel · TLP ของ Linux ถูกตั้งเวลาเฉพาะในการเชื่อมต่อที่ใช้ SACK - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_sack ค่าเริ่มต้น 1 (เปิด) - [misc/ss.c](https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/misc/ss.c?h=v6.12.0) · iproute2 · ss -ti แสดง ts, sack, wscale:ส่ง,รับ ตาม option ที่การเชื่อมต่อใช้ - [tcp: limit payload size of sacked skbs](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3b4929f65b0d8249f19a50245cd88ed1a2f78cff) · Linux kernel · commit แก้ช่องโหว่การจัดการ SACK ปี 2019 (CVE-2019-11477) - [Linux and FreeBSD Kernel: Multiple TCP-based remote denial of service vulnerabilities (NFLX-2019-001)](https://raw.githubusercontent.com/Netflix/security-bulletins/master/advisories/third-party/2019-001.md) · Netflix · ตอนนั้นมีการแนะนำ tcp_sack=0 (ปิดการจัดการ SACK) เป็นมาตรการชั่วคราว - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtTCPRenoRecovery (เริ่มกู้คืนโดยไม่มี SACK) และ TcpExtTCPSackRecovery (เริ่มกู้คืนด้วย SACK), TcpExtTCPSACKDiscard (จำนวน SACK block ที่ไม่ถูกต้อง) - [Display Filter Reference: Transmission Control Protocol](https://www.wireshark.org/docs/dfref/t/tcp.html) · Wireshark · display filter tcp.options.sack_perm (option อนุญาต SACK ใน SYN) #### rt-zero-window · zero window (การหยุดที่ดูเหมือนการส่งซ้ำ) · Zero window, often mistaken for retransmission ถ้าโปรแกรมฝั่งรับอ่านซ็อกเก็ตไม่ทันจนบัฟเฟอร์เต็ม ฝั่งส่งจะหยุดส่งและส่งแค่ zero window probe ปัญหานี้ไม่ได้อยู่ที่เน็ต - ทำไม → ผลคือ → บนหน้าจอ: เฟรมของไคลเอนต์หยุดชะงักหรือเธรดของเซิร์ฟเวอร์ติดขัด จึงอ่านซ็อกเก็ตไม่ได้ → receive window เป็น 0 ฝั่งส่งจึงหยุดส่งและส่งแค่ probe (ช่วงห่างค่อย ๆ ยาวขึ้น) → ค้างแล้วตามด้วยอาการกรอเร็ว ใน packet capture เห็น “ZeroWindow” และไม่มีแพ็กเก็ตหาย - อาการ: ค้าง, กรอเร็ว / ปัจจัย: การหยุดชะงัก - ใครเจอ: เราคนเดียว, ทั้งเซิร์ฟเวอร์ / เกิดเมื่อไร: ตอนคนแห่มารวมกัน, สุ่มเป็นครั้งคราว - ผู้รับผิดชอบหลัก: พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) / ร่วมกับ: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา) - งานฝั่งทีมพัฒนาเกม: ตรวจจากฝั่งที่ส่ง ZeroWindow ใน packet capture ก่อน (ฝั่งที่อ่านซ็อกเก็ตไม่ทัน), อ่านข้อมูลขาเข้าจากเครือข่ายต่อเนื่องในเธรดแยก, ตั้งขนาด receive buffer ให้เหมาะสม ไคลเอนต์: แก้ต้นเหตุที่ทำให้เฟรมหยุดชะงัก เช่น การโหลดและ GC เซิร์ฟเวอร์: แก้ต้นเหตุที่ทำให้เธรดที่อ่านซ็อกเก็ตติดขัด - งานฝั่งทีมอินฟรา: เพิ่ม TcpExtTCPToZeroWindowAdv ใน nstat ของเซิร์ฟเวอร์ (จำนวนครั้งที่เซิร์ฟเวอร์แจ้ง receive window เป็น 0) เข้าในการมอนิเตอร์ (ถ้าเพิ่มขึ้นแปลว่าเป็นฝั่งเซิร์ฟเวอร์ ให้ส่งต่อฝ่ายพัฒนาเซิร์ฟเวอร์), จัดเตรียม packet capture ฝั่งเซิร์ฟเวอร์ให้ - บนกราฟ: ขาดช่วงแล้วมารวดเดียว (ปริมาณที่รับต่อการเชื่อมต่อ, จำนวนครั้งที่เกิด zero window) - จุดที่ต้องดู: หาฝั่งที่แจ้ง window เป็น 0 ใน packet capture ด้วยตัวกรอง Wireshark tcp.analysis.zero_window ส่วนใน nstat ของเซิร์ฟเวอร์ให้แยกดู TcpExtTCPToZeroWindowAdv (เซิร์ฟเวอร์แจ้ง window เป็น 0) กับ TcpExtTCPWinProbe (ส่ง probe ไปเพราะอีกฝั่งแจ้ง window เป็น 0) และดู Recv-Q ของซ็อกเก็ตเซิร์ฟเวอร์ (ไบต์ที่โปรแกรมยังไม่ได้อ่านใน ss) - สัญญาณว่าใช่: ระหว่างที่หยุด ไม่มีการส่งซ้ำ มีแต่ zero window กับ probe วิ่งไปมา ถ้า TcpExtTCPToZeroWindowAdv ของเซิร์ฟเวอร์หรือ Recv-Q ของซ็อกเก็ตเซิร์ฟเวอร์เพิ่มขึ้น แปลว่าเซิร์ฟเวอร์อ่านไม่ทัน ถ้า TcpExtTCPWinProbe เพิ่มขึ้น แปลว่าไคลเอนต์อ่านไม่ทัน - สัญญาณว่าไม่ใช่: ใน capture ไม่มี zero window และข้อมูลเดิมถูกส่งซ้ำ: น่าจะเป็นสาเหตุฝั่งแพ็กเก็ตหายหรือการส่งซ้ำโดยไม่จำเป็น - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - กรณีจริง: roblox-2021 - แหล่งอ้างอิง: - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · เมื่อ receive window เป็น 0 ฝั่งส่งจะส่ง zero window probe และเพิ่มช่วงห่างของ probe แบบ exponential - [7.5. TCP Analysis](https://www.wireshark.org/docs/wsug_html_chunked/ChAdvTCPAnalysis.html) · Wireshark · TCP ZeroWindow: แพ็กเก็ตที่ฝั่งรับแจ้ง window เป็น 0 เพื่อให้ฝั่งส่งหยุดส่ง - [Display Filter Reference: Transmission Control Protocol](https://www.wireshark.org/docs/dfref/t/tcp.html) · Wireshark · display filter tcp.analysis.zero_window และ tcp.analysis.zero_window_probe - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtTCPToZeroWindowAdv: จำนวนครั้งที่แจ้ง receive window จากค่าที่ไม่ใช่ 0 เป็น 0 - [net/ipv4/proc.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/proc.c?h=v6.12) · Linux kernel · ชื่อตัวนับที่ nstat แสดง: TCPToZeroWindowAdv, TCPWinProbe - [net/ipv4/tcp_output.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_output.c?h=v6.12) · Linux kernel · TCPWinProbe: เพิ่มขึ้นทุกครั้งที่ส่ง probe (tcp_send_probe0) เมื่อ receive window ของอีกฝั่งเป็น 0 - [net/ipv4/tcp_diag.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_diag.c?h=v6.12) · Linux kernel · Recv-Q ใน ss ของการเชื่อมต่อ คือจำนวนไบต์ที่รับมาแล้วแต่โปรแกรมยังไม่ได้อ่าน #### rt-syn · การส่งซ้ำคำขอเชื่อมต่อ (SYN) · SYN retransmission on connect ถ้าคำขอเชื่อมต่อหายไปเพราะคิวรอเชื่อมต่อ (backlog) ล้นหรือถูกไฟร์วอลล์บล็อก OS ฝั่งไคลเอนต์จะส่งใหม่ โดยเริ่มหลัง 1 วินาทีและเว้นช่วงห่างขึ้นเรื่อย ๆ - ทำไม → ผลคือ → บนหน้าจอ: การเชื่อมต่อทะลักทันทีหลังปิดปรับปรุงจนคิวรอเชื่อมต่อของเซิร์ฟเวอร์ล้น หรือไฟร์วอลล์/ระบบป้องกัน DDoS ทิ้ง SYN → OS ฝั่งไคลเอนต์ส่ง SYN ซ้ำตามช่วงที่กำหนด โดยเริ่มหลัง 1 วินาที (Linux รุ่นเก่า 1 วินาที → 2 วินาที → 4 วินาที) → หลังกดปุ่มเชื่อมต่อ ช้าไปเป็นวินาทีพอดี ๆ เช่น 1 วินาที, 3 วินาที และถ้าล้มเหลวต่อเนื่องจะเข้าเกมไม่ได้หรือโหลดไม่จบ - อาการ: เข้าเกมไม่ได้/โหลดไม่จบ / ปัจจัย: แพ็กเก็ตหาย - ใครเจอ: ทั้งเซิร์ฟเวอร์, บางพื้นที่/บาง ISP / เกิดเมื่อไร: หลังล็อกอิน/หลังปิดปรับปรุง - ผู้รับผิดชอบหลัก: พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม) / ร่วมกับ: อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา), อินฟราเครือข่าย (ทีมอินฟรา), พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) - งานฝั่งทีมพัฒนาเกม: เซิร์ฟเวอร์: เพิ่มอาร์กิวเมนต์ backlog ของ listen (พร้อมกับ somaxconn), ทำให้เซิร์ฟเวอร์เกมเรียก accept ได้ทัน, ใช้ระบบคิวล็อกอิน ไคลเอนต์: ยืดช่วงเวลาลองเชื่อมต่อใหม่ (สุ่มกระจาย) - งานฝั่งทีมอินฟรา: เครื่องเซิร์ฟเวอร์/OS: ตรวจคิวรอเชื่อมต่อของเซิร์ฟเวอร์ล้นจาก TcpExtListenOverflows และ TcpExtListenDrops ใน nstat และคำเตือน “Possible SYN flooding” ใน log, เพิ่ม somaxconn (พร้อมกับอาร์กิวเมนต์ของ listen), ใช้ SYN cookie เครือข่าย: ผ่อนการจำกัด SYN ของไฟร์วอลล์และระบบป้องกัน DDoS - ตัวเลขที่ควรรู้: SYN ที่ส่งซ้ำครั้งแรกของ Linux (รวมถึง Android) อยู่ที่ 1 วินาที เคอร์เนลรุ่นเก่าหลังจากนั้นจะเพิ่มช่วงห่างเป็นสองเท่าและส่งซ้ำที่วินาทีที่ 1, 3, 7, 15 … ส่วนตั้งแต่ 6.5 ขึ้นไปจะส่งซ้ำห้าครั้งที่ 1, 2, 3, 4, 5 วินาที แล้วจึงเพิ่มเป็นสองเท่า (7, 11, 19 วินาที …) ตามค่า tcp_syn_linear_timeouts=4 มือถือ Android จำนวนมากยังใช้เคอร์เนลตอนวางจำหน่ายแม้จะอัปเดต OS แล้ว จึงอาจต่างกันไปตามเครื่องแม้เป็น Android เวอร์ชันเดียวกัน ไม่ว่าแบบไหน ถ้าล้มเหลวทั้งหมดจะเลิกหลังประมาณ 2 นาที Windows เริ่มเพิ่มจาก 1 วินาทีหรือ 3 วินาที แล้วแต่เวอร์ชันและการตั้งค่า และส่งซ้ำแค่ 2–4 ครั้ง จึงเลิกภายใน 20–30 วินาที (ดูค่าของ PC เครื่องนั้นได้จาก Max SYN Retransmissions ใน netsh int tcp show global) - บนกราฟ: พุ่งทันทีหลังเปิดเซิร์ฟ/ปิดปรับปรุง (จำนวนครั้งที่พยายามเชื่อมต่อ, จำนวนครั้งที่คิวรอเชื่อมต่อล้น) - จุดที่ต้องดู: TcpExtListenOverflows และ TcpExtListenDrops ใน nstat ของเซิร์ฟเวอร์ กับคำเตือน “Possible SYN flooding on port” ใน dmesg และใช้ ss -lnt ดูว่า Recv-Q ของซ็อกเก็ตที่รอเชื่อมต่อ (จำนวนการเชื่อมต่อที่รอ accept) ชน Send-Q (ขีดจำกัด backlog) หรือไม่ ตรวจจาก capture ฝั่งเซิร์ฟเวอร์ว่า SYN มาถึงหรือไม่ และเซิร์ฟเวอร์ส่ง SYN-ACK กลับหรือไม่ - สัญญาณว่าใช่: ช่วงที่การเชื่อมต่อทะลักทันทีหลังปิดปรับปรุง TcpExtListenOverflows เพิ่มขึ้นและ Recv-Q ติดเพดาน Send-Q ใน capture เห็น SYN จากไคลเอนต์เดิมมาซ้ำเป็นช่วงห่างระดับวินาที แต่เซิร์ฟเวอร์ไม่ตอบ - สัญญาณว่าไม่ใช่: SYN ไม่มาถึงเซิร์ฟเวอร์และตัวนับของเซิร์ฟเวอร์ก็ไม่ขยับ: ไฟร์วอลล์หรือระบบป้องกัน DDoS ด้านหน้าทิ้งไป ให้ดูการจำกัด SYN และ log การดรอปของอุปกรณ์นั้น ถ้าเซิร์ฟเวอร์ส่ง SYN-ACK แล้วแต่ยังเชื่อมต่อช้า น่าจะเป็นแพ็กเก็ตหายในทิศทางขากลับ - วิธีตรวจ: ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม) - แหล่งอ้างอิง: - [include/net/tcp.h](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp.h?h=v6.12) · Linux kernel · RTO แรก TCP_TIMEOUT_INIT = 1 วินาที (ค่าเริ่มต้นตาม RFC 6298) - [RFC 6298: Computing TCP's Retransmission Timer](https://www.rfc-editor.org/rfc/rfc6298) · IETF · RTO เริ่มต้น 1 วินาที เพิ่มเป็นสองเท่าทุกครั้งที่ส่งซ้ำ - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_syn_retries ค่าเริ่มต้น 6, tcp_syn_linear_timeouts ค่าเริ่มต้น 4 (SYN RTO 1, 1, 1, 1, 1, 2, 4 …), ส่งซ้ำครั้งสุดท้ายที่ 67 วินาทีและเลิกที่ 131 วินาที, somaxconn ค่าเริ่มต้น 4096, tcp_syncookies ค่าเริ่มต้น 1 - [tcp: make the first N SYN RTO backoffs linear](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ccce324dabfe2143519daf50ed8b1ef1d0c542f7) · Linux kernel · commit ที่เปลี่ยนการส่ง SYN ซ้ำช่วงแรกให้เว้นช่วงคงที่ ตั้งแต่ Linux 6.5 (ค่าเริ่มต้น 4 ตามแบบของ macOS และ iOS) - [Android common kernels](https://source.android.com/docs/core/architecture/kernel/android-common) · Android (Google) · รองรับ common kernel 5.10–6.18 ไปพร้อมกัน และใช้เคอร์เนลของแพลตฟอร์มก่อนหน้า (เช่น android14-6.1) กับการวางจำหน่ายหรืออัปเกรดเครื่อง Android รุ่นใหม่ได้ - [TcpMaxConnectRetransmissions](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-2000-server/cc938209(v=technet.10)) · Microsoft · ค่าเริ่มต้นของ Windows รุ่นเก่า: ส่ง SYN ซ้ำ 2 ครั้ง รอครั้งแรก 3 วินาทีแล้วเพิ่มเป็นสองเท่า หลังครั้งสุดท้ายรออีกสองเท่าแล้วเลิก (3+6+12=21 วินาที) - [TCP/IP connectivity issues troubleshooting](https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/tcp-ip-connectivity-issues-troubleshooting) · Microsoft · จำนวนครั้งที่ส่ง SYN ซ้ำต่างกันไปตาม OS ตรวจได้จาก Max SYN Retransmissions ใน netsh int tcp show global - [listen(2) — Linux manual page](https://man7.org/linux/man-pages/man2/listen.2.html) · Linux man-pages · อาร์กิวเมนต์ backlog ของ listen ถูกตัดตาม somaxconn (ค่าเริ่มต้น 4096 ตั้งแต่ Linux 5.4 ก่อนหน้านั้น 128) - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · เมื่อคิว accept เต็ม SYN จะถูกทิ้ง และ TcpExtListenOverflows กับ TcpExtListenDrops เพิ่มขึ้นพร้อมกัน, TcpExtTCPSynRetrans - [net/ipv4/tcp_input.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_input.c?h=v6.12) · Linux kernel · ข้อความ log “Possible SYN flooding on port …” - [net/ipv4/tcp_diag.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_diag.c?h=v6.12) · Linux kernel · สำหรับซ็อกเก็ตที่รอเชื่อมต่อ Recv-Q ใน ss คือจำนวนการเชื่อมต่อที่รอ accept และ Send-Q คือขีดจำกัด backlog ## แหล่งอ้างอิงรายบท ### #l-client-game - [Slow rendering](https://developer.android.com/topic/performance/vitals/render) · Android (Google) · การจะได้ 60 FPS ต้องวาดหนึ่งเฟรมให้เสร็จภายใน 16 ms ถ้าช้ากว่านั้นเฟรมจะถูกข้ามและเห็นเป็นอาการกระตุก - [Interpolation and extrapolation (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/interpolation.html) · Unity · interpolation ที่วาดเชื่อมระหว่างสแนปช็อตที่มาห่าง ๆ และ extrapolation ที่ลากต่อไปในทิศทางและความเร็วเดิมเมื่อข้อมูลมาช้า พร้อมเพดานของ extrapolation - [Introduction to prediction (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/intro-to-prediction.html) · Unity · prediction ที่ไคลเอนต์ขยับตามอินพุตของตัวเองก่อนโดยไม่รอผลจากเซิร์ฟเวอร์ และแก้ตำแหน่งเมื่อไม่ตรงกับเซิร์ฟเวอร์ - [Peeking into VALORANT's Netcode](https://technology.riotgames.com/news/peeking-valorants-netcode) · Riot Games · ถ้าใช้บัฟเฟอร์เกลี่ยข้อมูลที่มาไม่สม่ำเสมอ ภาพจะลื่น แต่ความหน่วงก็เพิ่มขึ้นตามไปด้วย และถ้าคาดเดาผิด ตัวละครจะกระโดดหรือลื่นไถล - [Garbage collection modes](https://docs.unity3d.com/Manual/performance-incremental-garbage-collection.html) · Unity · GC spike ในการทดลอง: ถ้าปิด incremental GC เมนเธรดจะหยุดระหว่างตรวจทั้ง heap จนเกินเพดานเฟรม 16 ms - [Scripting.GarbageCollector.CollectIncremental](https://docs.unity3d.com/ScriptReference/Scripting.GarbageCollector.CollectIncremental.html) · Unity · time slice ของ incremental GC 3 ms ในการทดลอง: ค่าเริ่มต้นของ incrementalTimeSliceNanoseconds คือ 3 ms - [Shader loading](https://docs.unity3d.com/Manual/shader-loading.html) · Unity · โหลดแมพใหม่ในการทดลอง: ครั้งแรกที่ใช้ shader variant ไดรเวอร์ต้องสร้างเวอร์ชันสำหรับ GPU จึงอาจหยุดได้ - [Frame Pacing library](https://developer.android.com/games/sdk/frame-pacing) · Android (Google) · เลยรอบ V-Sync ในการทดลอง: จอ 60 Hz จะแสดงเฟรมก่อนหน้าซ้ำอีกครั้งเมื่อไม่มีเฟรมใหม่ - [Set fixed timestep to optimize physics simulation frequency](https://docs.unity3d.com/Manual/physics-optimization-cpu-frequency.html) · Unity · ไล่ตามด้วย fixed step ในการทดลอง: ถ้าเฟรมยาวกว่าช่วงห่างของ step จะต้องรัน step หลายครั้งในเฟรมเดียว ภาระจึงหนักขึ้น - [Handling variation in time](https://docs.unity3d.com/Manual/time-handling-variations.html) · Unity · จำกัดการไล่ตามในการทดลอง (การทดลองนี้ไม่เกิน 5 ครั้งต่อเฟรม): Unity จำกัดเวลาเกมในหนึ่งเฟรมไว้ไม่เกิน 1/3 วินาที เพื่อกันวงจรที่การไล่ตามทำให้ช้าลงไปอีก และนาฬิกาเกมจะช้าลงเท่ากับเวลาที่เกิน ### #l-client-os - [Multitasking](https://learn.microsoft.com/en-us/windows/win32/procthread/multitasking) · Microsoft · preemptive multitasking ที่ให้ time slice แก่แต่ละเธรด (ราว 20 ms ต่างกันตาม OS และ CPU) และเมื่อใช้หมดก็ส่งต่อให้เธรดถัดไป - [Scheduling Priorities](https://learn.microsoft.com/en-us/windows/win32/procthread/scheduling-priorities) · Microsoft · ในบรรดาเธรดที่พร้อมทำงาน เธรดที่มีลำดับความสำคัญสูงสุดจะได้ time slice ผลัดกันไป (round robin) - [Priority Boosts](https://learn.microsoft.com/en-us/windows/win32/procthread/priority-boosts) · Microsoft · ยกลำดับความสำคัญของโปรเซสที่มีหน้าต่างอยู่ด้านหน้า (foreground) ให้ไม่ต่ำกว่าโปรเซสเบื้องหลัง - [socket(7) — Linux manual page](https://man7.org/linux/man-pages/man7/socket.7.html) · Linux man-pages · ทุกซ็อกเก็ตมี receive buffer (SO_RCVBUF) และขนาดเริ่มต้นกับขนาดสูงสุดกำหนดจากการตั้งค่าระบบ - [Wi-Fi low-latency mode](https://source.android.com/docs/core/connect/wifi-low-latency) · Android (Google) · ในโหมด Wi-Fi ความหน่วงต่ำของ Android 10 ขึ้นไป เฟรมเวิร์กจะสั่งปิดโหมดประหยัดพลังงานของ Wi-Fi (doze) โดยตรง (เมื่อแอปอยู่เบื้องหน้าและจอเปิดอยู่) - [Cached apps freezer](https://source.android.com/docs/core/perf/cached-apps-freezer) · Android (Google) · Android 14 ขึ้นไปจะแช่แข็งแอปที่อยู่ในสถานะ cached หลัง 10 วินาที จนใช้ CPU ไม่ได้ - [Extending your app’s background execution time](https://developer.apple.com/documentation/uikit/extending-your-app-s-background-execution-time) · Apple · iOS จะพักการทำงานของแอปที่ไปอยู่เบื้องหลังหลังจากนั้นไม่กี่วินาที - [_WDF_TIMER_CONFIG (wdftimer.h)](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdftimer/ns-wdftimer-_wdf_timer_config) · Microsoft · ตัวจับเวลา 15.6 ms ในการทดลอง: ช่วงห่างเริ่มต้นของ system clock tick ใน Windows คือ 15.6 ms - [timeBeginPeriod function (timeapi.h)](https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod) · Microsoft · ตัวจับเวลา 1 ms ในการทดลอง: โปรแกรมขอเพิ่มความละเอียดของตัวจับเวลาได้ด้วย timeBeginPeriod - [Customize the Windows performance power slider](https://learn.microsoft.com/en-us/windows-hardware/customize/desktop/customize-power-slider) · Microsoft · โหมดประหยัดพลังงานในการทดลอง: โหมดพลังงานของ Windows ปรับการตั้งค่าพลังงานและ CPU ไปทางยืดเวลาแบตเตอรี่ โดยแลกกับประสิทธิภาพที่ลดลง - [Thermal API](https://developer.android.com/games/optimize/adpf/thermal) · Android (Google) · มือถือร้อนในการทดลอง: เครื่องรักษาประสิทธิภาพสูงได้แค่ช่วงเวลาจำกัด จากนั้นจะถูก throttle เพราะความร้อน ### #l-memory - [Designs, Lessons and Advice from Building Large Distributed Systems (LADIS 2009 keynote)](https://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf) · Google · ที่มาของตารางตัวเลขที่ควรรู้: แคช L1 ใช้ 0.5 ns, L2 7 ns, หน่วยความจำหลัก 100 ns, ไปกลับในดาต้าเซ็นเตอร์เดียวกัน 0.5 ms, disk seek 10 ms (ข้อมูลปี 2009 ตัวเลขแคชในตารางเป็นค่าประมาณที่ต่างจากนี้เล็กน้อย) - [Solidigm™ D7-P5520 and D7-P5620 Product Brief](https://www.solidigm.com/products/data-center/product-briefs/d7-p5520-p5620-product-brief.html) · Solidigm · ความหน่วง 99.99% (four-nines latency) ของ NVMe SSD สำหรับเซิร์ฟเวอร์อยู่ที่ 130 µs: หลักฐานว่าการอ่าน SSD และการอ่าน swap กลับในตารางอยู่ที่ราว 100 µs - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_rto_min_us ค่าเริ่มต้น 200,000 µs: เวลารอขั้นต่ำก่อน TCP ของ Linux ส่งซ้ำคือ 200 ms (แถวการส่งซ้ำ TCP ในตาราง) - [What is NUMA?](https://docs.kernel.org/mm/numa.html) · Linux kernel · หน่วยความจำฝั่ง CPU อีกตัว (remote) เข้าถึงช้ากว่าหน่วยความจำ local และมีแบนด์วิดท์ต่ำกว่า (แถว NUMA ในตาราง) - [JEP 439: Generational ZGC](https://openjdk.org/jeps/439) · OpenJDK · G1 หยุดหลาย ms ถึงหลายวินาที ส่วน ZGC หยุดไม่เกิน 1 ms (GC ทั้ง heap หลายร้อย ms ถึงหลายวินาทีในเนื้อหา และ GC ของ heap ใหญ่ 1 วินาทีในตาราง) - [Available Collectors](https://docs.oracle.com/en/java/javase/25/gctuning/available-collectors.html) · Oracle · ZGC ยอมเสีย throughput เล็กน้อยเพื่อให้การหยุดสูงสุดต่ำกว่า 1 ms และการหยุดไม่ขึ้นกับขนาด heap - [Garbage Collector Implementation](https://docs.oracle.com/en/java/javase/25/gctuning/garbage-collector-implementation.html) · Oracle · การเก็บแบบแยก generation: minor collection ที่เก็บเฉพาะ Young ใช้เวลาสั้น ส่วน major collection ที่เก็บทั้ง heap ใช้เวลานานกว่ามาก (โหมด generational ในการทดลอง GC) - [The Z Garbage Collector](https://docs.oracle.com/en/java/javase/25/gctuning/z-garbage-collector1.html) · Oracle · ZGC ทำงานที่หนักไปพร้อมกับแอป จึงไม่หยุดเกิน 1 ms แต่ถ้าเก็บคืนไม่ทัน แอปอาจต้องหยุดรอ GC (โหมด concurrent ในการทดลอง GC) - [A Guide to the Go Garbage Collector](https://go.dev/doc/gc-guide) · Go · ขั้น mark ของ GC ใช้ CPU 25% โปรแกรมจึงช้าลงในช่วงนั้น และถ้าจัดสรรหน่วยความจำมาก goroutine ต้องช่วย GC (assist) จนเกิดดีเลย์ (โหมด concurrent ในการทดลอง GC) - [Go 1.8 Release Notes](https://go.dev/doc/go1.8) · Go · การหยุดของ GC ใน Go ปกติน้อยกว่า 100 µs - [Debug a memory leak in .NET](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/debug-memory-leak) · .NET · แม้จะมี GC ถ้ายังอ้างอิงออบเจ็กต์ที่ไม่ใช้แล้วต่อไป ก็เกิดหน่วยความจำรั่วได้ - [Concepts overview](https://docs.kernel.org/admin-guide/mm/concepts.html) · Linux kernel · เมื่อหน่วยความจำไม่พอ ระบบจะเก็บคืน page cache และเพจที่ swap ได้ ถ้ายังไม่พอ OOM killer จะบังคับปิดโปรเซส (การทดลองหน่วยความจำรั่ว) ### #l-disk - [Exos X18 Data Sheet](https://www.seagate.com/www-content/datasheets/pdfs/exos-x18-mango-DS2045-1N-2007US-en_US.pdf) · Seagate · HDD 7,200 rpm อ่านสุ่ม 4K ได้ 170 IOPS, rotational latency เฉลี่ย 4.16 ms (HDD ราว 150 ครั้งในเนื้อหา และ HDD ในการทดลองดิสก์) - [D3-S4520 SSD](https://www.solidigm.com/products/data-center/d3/s4520.html) · Solidigm · SATA SSD อ่าน/เขียนสุ่ม 4KB สูงสุด 92K/48K IOPS (SSD หลายหมื่นครั้งในเนื้อหา) - [Solidigm™ D7-P5520 and D7-P5620 Product Brief](https://www.solidigm.com/products/data-center/product-briefs/d7-p5520-p5620-product-brief.html) · Solidigm · NVMe SSD อ่าน/เขียนสุ่ม 1,000K/200K IOPS (หลายแสนครั้งในเนื้อหา) - [Amazon EBS General Purpose SSD volumes](https://docs.aws.amazon.com/ebs/latest/userguide/general-purpose.html) · AWS · gp3 ค่าพื้นฐาน 3,000 IOPS ส่วน gp2 ใช้ I/O credit เพื่อ burst ได้ถึง 3,000 IOPS และเมื่อเครดิตหมดจะกลับไปที่ประสิทธิภาพพื้นฐาน - [Amazon EBS-optimized instance types](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html) · AWS · อินสแตนซ์ขนาดเล็กทำประสิทธิภาพ EBS สูงสุดได้แค่ 30 นาทีครั้งเดียวใน 24 ชั่วโมง แล้วกลับไปที่ประสิทธิภาพพื้นฐาน (เช่น t4g.2xlarge พื้นฐาน 4,000 สูงสุด 15,700 IOPS ซึ่งเป็นสมมติฐานของคลาวด์แบบ burst ในการทดลองดิสก์) - [Managed disk bursting](https://learn.microsoft.com/en-us/azure/virtual-machines/disk-bursting) · Microsoft Azure · ดิสก์และ VM ขนาดเล็กใช้เครดิตเพื่อ burst ได้สูงสุด 30 นาที - [Concepts overview](https://docs.kernel.org/admin-guide/mm/concepts.html) · Linux kernel · ข้อมูลที่เขียนลงไฟล์จะเข้าไปอยู่ใน page cache ก่อน ถูกทำเครื่องหมายเป็น dirty แล้วจึงเขียนลงดิสก์ภายหลัง - [Documentation for /proc/sys/vm/](https://docs.kernel.org/admin-guide/sysctl/vm.html) · Linux kernel · เมื่อการเขียนที่กองรอสะสมถึง dirty_ratio โปรเซสที่เขียนจะต้องรับหน้าที่เขียนลงดิสก์เอง (เพดานการเขียนของ OS ในการทดลองดิสก์) - [fsync(2) — Linux manual page](https://man7.org/linux/man-pages/man2/fsync.2.html) · Linux man-pages · fsync จะบล็อกจนกว่าอุปกรณ์จะแจ้งว่าเขียนเสร็จ ### #l-db - [How MySQL Uses Indexes](https://dev.mysql.com/doc/refman/8.4/en/mysql-indexes.html) · MySQL · ถ้าไม่มีอินเด็กซ์ จะอ่านทั้งตารางตั้งแต่แถวแรก (full table scan) - [InnoDB Locking](https://dev.mysql.com/doc/refman/8.4/en/innodb-locking.html) · MySQL · คำขอที่จะแก้แถวเดียวกันต้องรอจนทรานแซกชันที่ถือ row lock อยู่จบ (hot row) - [Number Of Database Connections](https://wiki.postgresql.org/wiki/Number_Of_Database_Connections) · PostgreSQL · เมื่อใช้ทรัพยากรของ DB หมดแล้ว การเพิ่มการเชื่อมต่อจะทำให้ throughput ลดลง (หลักฐานในการทดลอง DB ว่าขยาย pool แล้วถ้าคอร์ CPU ไม่พอ ทุกอย่างจะช้าลง) - [WAL Configuration (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/wal-configuration.html) · PostgreSQL · checkpoint เป็นงานหนักที่เขียน dirty page รวดเดียวทุก 5 นาทีโดยค่าเริ่มต้นหรือทุก WAL 1 GB และกระจายการเขียนเพื่อเลี่ยง I/O ทะลัก - [Semisynchronous Replication](https://dev.mysql.com/doc/refman/8.4/en/replication-semisync.html) · MySQL · ใน asynchronous replication ถ้า DB หลักล่ม ทรานแซกชันที่ commit แล้วอาจไม่มีใน DB สำรอง (โรลแบ็คหลัง failover) - [Log-Shipping Standby Servers (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/warm-standby.html) · PostgreSQL · streaming replication เป็นแบบ asynchronous โดยค่าเริ่มต้น จึงมีความล่าช้าระหว่างการ commit กับการสะท้อนไปที่ replica (เพิ่งเขียนไปแต่ยังไม่เห็นบน replica) - [Asynchronous Commit (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/wal-async-commit.html) · PostgreSQL · การรวบรวมแล้วค่อยเขียนลงทีหลังทำให้เร็วขึ้น แต่แลกกับการเสียการเปลี่ยนแปลงล่าสุดเมื่อเกิดเหตุขัดข้อง (โครงสร้างเดียวกับเซิร์ฟเวอร์เกมที่เซฟทุกไม่กี่นาที) ### #judge - [Service Level Objectives (Site Reliability Engineering, ch. 4)](https://sre.google/sre-book/service-level-objectives/) · Google · ดูรูปร่างและส่วนหางของการกระจายความหน่วงด้วยเปอร์เซ็นไทล์ (ลำดับที่ 50/95/99) แทนค่าเฉลี่ย - [The Tail at Scale](https://research.google/pubs/the-tail-at-scale/) · Google · ความหน่วงยาวที่เกิดเป็นครั้งคราว (tail latency) ยิ่งระบบใหญ่ยิ่งเป็นตัวกำหนดความรู้สึกของผู้ใช้ต่อทั้งบริการ - [RFC 3550: RTP, A Transport Protocol for Real-Time Applications](https://www.rfc-editor.org/rfc/rfc3550) · IETF · นิยามและวิธีคำนวณจิตเตอร์ของช่วงเวลาที่แพ็กเก็ตมาถึง (interarrival jitter) - [RFC 1812: Requirements for IP Version 4 Routers](https://www.rfc-editor.org/rfc/rfc1812) · IETF · เราเตอร์ต้องจำกัดความถี่ในการส่งข้อความ ICMP error เช่น Time Exceeded ได้ และจำกัด Echo Reply ได้ด้วย (ระวังเวลาตีความผล mtr และ ping) - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · icmp_ratelimit และ icmp_ratemask: Linux จำกัดการตอบ ICMP เช่น Time Exceeded และ Destination Unreachable เป็นค่าเริ่มต้น - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · ฟิลด์ rtt (เวลาไปกลับเฉลี่ย)/rttvar ใน ss -ti - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · pidstat -t: อัตราการใช้ CPU ต่อเธรด - [bcc tools: runqlat examples](https://raw.githubusercontent.com/iovisor/bcc/master/tools/runqlat_example.txt) · IO Visor · วัดการกระจายของ run queue latency (เวลาที่เธรดรอ CPU) - [RIPE Atlas documentation](https://atlas.ripe.net/docs/) · RIPE NCC · เครื่องมือ synthetic monitoring สาธารณะที่รัน ping และ traceroute จากจุดวัดทั่วโลก - [GeoLite2 Free Geolocation Data](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data) · MaxMind · ฐานข้อมูลสาธารณะที่ระบุประเทศและ ASN ให้ IP address - [View CloudWatch metrics for your instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html) · AWS · basic monitoring เก็บค่าทุก 5 นาที ส่วน detailed monitoring ทุก 1 นาที (ช่วงเวลารวมค่าซ่อนค่าพุ่งช่วงสั้นไว้) ### #l-nic - [NAPI](https://docs.kernel.org/networking/napi.html) · Linux kernel · เมื่ออุปกรณ์แจ้งแพ็กเก็ตใหม่ด้วยอินเทอร์รัปต์ เคอร์เนลจะดึงออกมาประมวลผลด้วย NAPI ส่วนการรวมอินเทอร์รัปต์ (coalescing) ปกติอุปกรณ์เป็นผู้ทำ - [Scaling in the Linux Networking Stack](https://docs.kernel.org/networking/scaling.html) · Linux kernel · โครงสร้างที่ใช้ RSS แบ่ง receive queue หลายคิวไปหลายคอร์ อินเทอร์รัปต์แยกต่อคิว และเลือกคิวด้วย hash - [Interface statistics](https://docs.kernel.org/networking/statistics.html) · Linux kernel · แพ็กเก็ตที่อุปกรณ์ทิ้งเพราะไม่มีบัฟเฟอร์ (rx_missed_errors) และสถิติรายไดรเวอร์ใน ethtool -S - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · ตั้งค่าและตรวจ ring buffer (-G), interrupt coalescing (-C), receive hash (-N) และสถิติ (-S) - [How to receive a million packets per second](https://blog.cloudflare.com/how-to-receive-a-million-packets/) · Cloudflare · ผลวัดที่คิวเดียวกับคอร์เดียวตันที่ราว 350,000–430,000 แพ็กเก็ตต่อวินาที และต้องเพิ่มคิวกับคอร์จึงจะรับได้ 1 ล้าน pps (การจำลองตั้งสมมติฐานว่าประมวลผลต่อคอร์ได้ 700,000 pps ซึ่งเผื่อไว้มากกว่านี้) - [Monitor network performance for ENA settings on your EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html) · AWS · ถ้าเกินขีดจำกัดแบนด์วิดท์, PPS หรือ connection tracking ของอินสแตนซ์คลาวด์ แพ็กเก็ตจะถูกเข้าคิวแล้วทิ้งที่นอกอินสแตนซ์ และมีตัวนับการเกินขีดจำกัด ### #l-server-os - [listen(2) — Linux manual page](https://man7.org/linux/man-pages/man2/listen.2.html) · Linux man-pages · คิวรอเชื่อมต่อ (backlog) และเพดาน somaxconn (ค่าเริ่มต้น 4,096 ตั้งแต่ 5.4 ก่อนหน้านั้น 128) - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · เมื่อคิว accept เต็ม Linux จะทิ้งคำขอเชื่อมต่อ (SYN) และเพิ่ม TcpExtListenOverflows - [listen function (winsock2.h)](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen) · Microsoft · เมื่อคิวเต็ม Windows จะตอบไคลเอนต์ด้วย WSAECONNREFUSED - [getrlimit(2) — Linux manual page](https://man7.org/linux/man-pages/man2/getrlimit.2.html) · Linux man-pages · RLIMIT_NOFILE: ขีดจำกัดจำนวน fd ที่โปรเซสเปิดได้ ถ้าเกินจะได้ EMFILE - [systemd-system.conf(5) — Linux manual page](https://man7.org/linux/man-pages/man5/systemd-system.conf.5.html) · systemd · ค่าเริ่มต้นของขีดจำกัด fd ของ service คือ 1024:524288 (กรณีตั้ง fd ไว้ 1,024 ผิดพลาดในการจำลอง) - [The /proc Filesystem](https://docs.kernel.org/filesystems/proc.html) · Linux kernel · OOM killer เลือกโปรเซสที่จะปิดจากคะแนน (badness) ที่คิดจากสัดส่วนการใช้หน่วยความจำ และปรับได้ด้วย oom_score_adj - [Control Group v2](https://docs.kernel.org/admin-guide/cgroup-v2.html) · Linux kernel · เมื่อชน memory.max และลดไม่ได้ OOM killer จะทำงานภายใน cgroup นั้น, จำกัด CPU ด้วย cpu.max - [proc_stat(5) — Linux manual page](https://man7.org/linux/man-pages/man5/proc_stat.5.html) · Linux man-pages · steal: เวลา CPU ที่ถูก OS อื่นแย่งไปในสภาพแวดล้อม virtualization - [Exponential Backoff And Jitter](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/) · AWS · exponential backoff อย่างเดียวยังทำให้การลองใหม่กระจุกกัน ต้องผสมความสุ่ม (jitter) จึงจะลดการแย่งกัน (วิธีลองใหม่ในการจำลอง) ### #l-socket - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · TCP เป็น byte stream ที่เชื่อถือได้และรักษาลำดับ, นิยามของ Nagle และ delayed ACK - [RFC 768: User Datagram Protocol](https://www.rfc-editor.org/rfc/rfc768) · IETF · UDP ไม่รับประกันการส่งถึงและการป้องกันข้อมูลซ้ำ - [RFC 8085: UDP Usage Guidelines](https://www.rfc-editor.org/rfc/rfc8085) · IETF · แอป UDP ที่ต้องการความน่าเชื่อถือและลำดับต้องทำเอง - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · TCP socket option เช่น TCP_NODELAY, TCP_USER_TIMEOUT และ keepalive - [socket(7) — Linux manual page](https://man7.org/linux/man-pages/man7/socket.7.html) · Linux man-pages · socket option SO_SNDBUF, SO_RCVBUF, SO_KEEPALIVE และ SO_LINGER - [RFC 5681: TCP Congestion Control](https://www.rfc-editor.org/rfc/rfc5681) · IETF · fast retransmit เมื่อได้ duplicate ACK 3 ตัว และหลังส่งซ้ำจากตัวจับเวลา congestion window จะเหลือ 1 segment (กฎในตำราที่ใช้ในการจำลอง) - [RFC 8985: The RACK-TLP Loss Detection Algorithm for TCP](https://www.rfc-editor.org/rfc/rfc8985) · IETF · RACK ที่ตัดสินการหายจากเวลาที่ส่งแทนการนับ duplicate ACK - [RFC 6298: Computing TCP's Retransmission Timer](https://www.rfc-editor.org/rfc/rfc6298) · IETF · แนะนำ RTO ต่ำสุด 1 วินาที และ backoff เพิ่มเป็นสองเท่าทุกครั้งที่หมดเวลา - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_rto_min_us ของ Linux ค่าเริ่มต้น 200 ms, ตรวจจับการหายด้วย RACK (tcp_recovery) - [include/net/tcp.h (Linux v6.12)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp.h?h=v6.12) · Linux kernel · delayed ACK 40 ms ของ Linux ในการจำลอง: TCP_DELACK_MIN (HZ/25 = 40 ms), สูงสุด TCP_DELACK_MAX (HZ/5 = 200 ms) - [Design issues - Sending small data segments over TCP with Winsock](https://learn.microsoft.com/en-us/previous-versions/troubleshoot/windows/win32/data-segment-tcp-winsock) · Microsoft · delayed ACK 200 ms ของ Windows ในการจำลอง: เมื่อได้รับข้อมูลจะตั้งตัวจับเวลา delayed ACK 200 ms และเมื่อประกบกับ Nagle แพ็กเก็ตเล็กจะต้องรอ ACK - [RFC 896: Congestion Control in IP/TCP Internetworks](https://www.rfc-editor.org/rfc/rfc896) · IETF · จุดประสงค์เดิมของกฎ Nagle: ปัญหา remote terminal ที่กดแป้นแต่ละครั้ง (1 ไบต์) ต้องส่งแพ็กเก็ต 41 ไบต์ออกไป - [send(2) — Linux manual page](https://man7.org/linux/man-pages/man2/send.2.html) · Linux man-pages · เมื่อ send buffer เต็ม send() แบบ blocking จะไม่คืนค่า ### #journey - [ITU-T G.114: One-way transmission time](https://www.itu.int/rec/T-REC-G.114-200305-I/en) · ITU · ความหน่วงในการส่งผ่านไฟเบอร์ออปติกคือ 5 µs/กิโลเมตร (1,000 กิโลเมตร ทางเดียว 5 ms) ถ้าทางเดียวไม่เกิน 150 ms แอปพลิเคชันส่วนใหญ่แทบไม่รู้สึก แต่งานที่โต้ตอบสูงได้รับผลแม้ต่ำกว่า 100 ms - [Azure network round-trip latency statistics](https://learn.microsoft.com/en-us/azure/networking/azure-network-latency) · Microsoft Azure · ขนาดของช่วงอินเทอร์เน็ตตามตำแหน่งเซิร์ฟเวอร์ในการทดลอง: จากโซล พื้นที่ปูซาน 8 ms, โตเกียว 30 ms, สิงคโปร์ 68 ms, สหรัฐฯ ฝั่งตะวันตก 124–136 ms, ยุโรป 234–244 ms (ค่ามัธยฐานแบบไปกลับ) - [The Internet at the Speed of Light (HotNets 2014)](https://conferences.sigcomm.org/hotnets/2014/papers/hotnets-XIII-final111.pdf) · ACM · ตัวคูณเส้นทาง 1.5 ในการทดลอง: เส้นทางผ่านเราเตอร์จริงยาวกว่าเส้นตรงของไฟเบอร์ออปติกราว 1.5 เท่า (ค่ามัธยฐาน) - [Report ITU-R M.2134: Requirements related to technical performance for IMT-Advanced radio interface(s)](https://www.itu.int/pub/R-REP-M.2134-2008) · ITU · ข้อกำหนดความหน่วงทางเดียวในช่วงวิทยุของ LTE-Advanced ต่ำกว่า 10 ms (กรณีไม่มีโหลดและแพ็กเก็ตเล็ก ค่า LTE ในการทดลองเป็นสมมติฐานที่บวกโหลดและเวลารอ scheduling เพิ่มเข้าไป) - [Report ITU-R M.2410: Minimum requirements related to technical performance for IMT-2020 radio interface(s)](https://www.itu.int/pub/R-REP-M.2410-2017) · ITU · ข้อกำหนดความหน่วงทางเดียวในช่วงวิทยุของ 5G (IMT-2020) คือ 4 ms (eMBB, กรณีไม่มีโหลด) - [An Experimental Study of Home Gateway Characteristics (IMC 2010)](https://conferences.sigcomm.org/imc/2010/papers/p260.pdf) · ACM · คิวเราเตอร์ในการทดลอง: เมื่ออัปโหลดและดาวน์โหลดพร้อมกัน เราเตอร์บ้านอาจมีเวลารอในคิวเพิ่มเป็นหลายร้อย ms (สูงสุดราว 400 ms) - [Maximum transmission unit and maximum segment size](https://developers.cloudflare.com/magic-transit/reference/mtu-mss/) · Cloudflare · ผ่านระบบป้องกัน DDoS ในการทดลอง: เฉพาะทราฟฟิกขาเข้าที่ผ่านเครือข่ายป้องกัน ส่วนการตอบกลับของเซิร์ฟเวอร์ออกสู่อินเทอร์เน็ตโดยตรง (DSR) ### #l-home - [RFC 7567: IETF Recommendations Regarding Active Queue Management](https://www.rfc-editor.org/rfc/rfc7567) · IETF · การที่คิวในอุปกรณ์สะสมเป็นสาเหตุหลักของความหน่วงบนอินเทอร์เน็ต และคำแนะนำให้ใช้การจัดการคิว (AQM) เป็นค่าเริ่มต้น - [RFC 8289: Controlled Delay Active Queue Management](https://www.rfc-editor.org/rfc/rfc8289) · IETF · เป้าหมายเวลารอของ CoDel 5 ms และช่วงสังเกต 100 ms (คิว SQM ราว 5 ms ในการทดลอง) - [RFC 8290: The Flow Queue CoDel Packet Scheduler and Active Queue Management Algorithm](https://www.rfc-editor.org/rfc/rfc8290) · IETF · FQ-CoDel: แยกคิวต่อ flow ตามที่อยู่และพอร์ต และส่ง flow เล็กที่ไม่สร้างคิวออกไปก่อน - [What Can I Do About Bufferbloat?](https://www.bufferbloat.net/projects/bloat/wiki/What_can_I_do_about_Bufferbloat/) · Bufferbloat.net · ใช้เราเตอร์ที่รองรับ SQM อย่าง cake และ fq_codel และปรับความเร็ว SQM โดยวัดความหน่วงขณะมีโหลดไปด้วย - [An Experimental Study of Home Gateway Characteristics (IMC 2010)](https://conferences.sigcomm.org/imc/2010/papers/p260.pdf) · ACM · วัดเราเตอร์บ้าน 34 รุ่น: เมื่ออัปโหลดและดาวน์โหลดพร้อมกัน เวลารอในคิวสูงสุดราว 400 ms, ค่ามัธยฐานของเวลาที่คง UDP mapping ไว้ 90 วินาที - [Ending the Anomaly: Achieving Low Latency and Airtime Fairness in WiFi (USENIX ATC 2017)](https://www.usenix.org/system/files/conference/atc17/atc17-hoiland-jorgensen.pdf) · USENIX · คิววิทยุของ Wi-Fi ก็มีความหน่วงหลายร้อย ms ขณะมีโหลด และอุปกรณ์ที่ช้ากินเวลาส่งของอุปกรณ์อื่นไปด้วย - [RFC 4787: Network Address Translation (NAT) Behavioral Requirements for Unicast UDP](https://www.rfc-editor.org/rfc/rfc4787) · IETF · ข้อกำหนดตัวจับเวลา UDP mapping ของ NAT (ไม่น้อยกว่า 2 นาที แนะนำค่าเริ่มต้นไม่น้อยกว่า 5 นาที) และการต่ออายุด้วยแพ็กเก็ตขาออกจากฝั่งใน - [RFC 8325: Mapping Diffserv to IEEE 802.11](https://www.rfc-editor.org/rfc/rfc8325) · IETF · Wi-Fi ใช้วิธี CSMA/CA คือถ้าช่องสัญญาณไม่ว่างจะเลื่อนออกไปและส่งหลัง random backoff - [Resolve Wi-Fi and Bluetooth issues caused by wireless interference](https://support.apple.com/en-us/102319) · Apple · ไมโครเวฟรบกวนในการทดลอง: ไมโครเวฟ, Bluetooth และอุปกรณ์อื่น ๆ รบกวน Wi-Fi 2.4 GHz ถ้าย้ายไป 5 GHz จะดีขึ้น - [RFC 9438: CUBIC for Fast and Long-Distance Networks](https://www.rfc-editor.org/rfc/rfc9438) · IETF · การปรับความเร็วอัปโหลดในการทดลอง: เมื่อเจอแพ็กเก็ตหาย CUBIC จะลด window เหลือ 0.7 เท่า (ลดราว 30%) แล้วค่อยเพิ่มขึ้นใหม่ ### #l-isp - [ITU-T G.114: One-way transmission time](https://www.itu.int/rec/T-REC-G.114-200305-I/en) · ITU · ความหน่วงในการส่งผ่านไฟเบอร์ออปติก 5 µs/กิโลเมตร: แสงในไฟเบอร์ออปติกเดินทางราว 200,000 กิโลเมตรต่อวินาที ไปกลับ 1,000 กิโลเมตรใช้อย่างน้อย 10 ms - [The Internet at the Speed of Light (HotNets 2014)](https://conferences.sigcomm.org/hotnets/2014/papers/hotnets-XIII-final111.pdf) · ACM · เส้นทางผ่านเราเตอร์จริงยาวกว่าเส้นตรงของไฟเบอร์ออปติกราว 1.5 เท่า (ค่ามัธยฐาน) ปิงต่ำสุดสูงเป็น 3.2 เท่าของค่าตามความเร็วแสง (ราว 2 เท่าของค่าตามเส้นตรงของไฟเบอร์ออปติก) - [Azure network round-trip latency statistics](https://learn.microsoft.com/en-us/azure/networking/azure-network-latency) · Microsoft Azure · ค่ามัธยฐานไปกลับที่วัดจริงจากโซล: โตเกียว 30 ms, สหรัฐฯ ฝั่งตะวันตก 124–136 ms, ยุโรป 234–244 ms (เกาหลี–ยุโรปยาวราว 2.8 เท่าของเส้นตรงของไฟเบอร์ออปติก) - [AAE-1 & SMW5 cable cuts impact millions of users across multiple countries](https://blog.cloudflare.com/aae-1-smw5-cable-cuts/) · Cloudflare · ทราฟฟิกยุโรป–เอเชียส่วนใหญ่ผ่านอียิปต์ และการซ่อมเคเบิลใต้น้ำใช้เวลาหลายวันถึงหลายสัปดาห์ - [Quantifying the Causes of Path Inflation (SIGCOMM 2003)](https://conferences.sigcomm.org/sigcomm/2003/papers/p113-spring.pdf) · ACM · นโยบาย peering ระหว่าง ISP และ routing ระหว่างโดเมนทำให้เส้นทางยาวขึ้นมาก - [Inferring Persistent Interdomain Congestion (SIGCOMM 2018)](https://www.caida.org/catalog/papers/2018_inferring_persistent_interdomain_congestion/inferring_persistent_interdomain_congestion.pdf) · ACM · ลิงก์เชื่อมระหว่าง ISP บางจุดแออัดซ้ำทุกวัน โดยความหน่วงและแพ็กเก็ตหายขึ้นทุกช่วงพีค - [RFC 4271: A Border Gateway Protocol 4 (BGP-4)](https://www.rfc-editor.org/rfc/rfc4271) · IETF · BGP ที่ใช้แลกเปลี่ยนข้อมูลเส้นทางบนอินเทอร์เน็ต, ค่าเริ่มต้นที่แนะนำของ hold time คือ 90 วินาที - [BGP updates in 2024](https://blog.apnic.net/2025/01/07/bgp-updates-in-2024/) · APNIC · ค่าเฉลี่ยรายวันของเวลาที่ routing กลับมานิ่งหลังเส้นทางเปลี่ยน คือ IPv4 25–35 วินาที, IPv6 40–50 วินาที - [Delayed Internet Routing Convergence (SIGCOMM 2000)](https://conferences.sigcomm.org/sigcomm/2000/conf/paper/sigcomm2000-5-2.pdf) · ACM · หลังเส้นทางขัดข้อง การ converge ใช้เวลาได้ถึงหลายนาที และระหว่างนั้นแพ็กเก็ตหายและความหน่วงเพิ่มขึ้น (การวัดในปี 2000) ### #l-dc-net - [High-Resolution Measurement of Data Center Microbursts (IMC 2017)](https://conferences.sigcomm.org/imc/2017/papers/imc17-final60.pdf) · ACM · ความหน่วงแบบ end-to-end ในเครือข่ายดาต้าเซ็นเตอร์ต่ำกว่า 1 ms และ burst ตั้งแต่ 70% ขึ้นไปจบภายในหลายสิบ µs จึงมองไม่เห็นจากอัตราการใช้งานเฉลี่ย - [Data Center TCP (DCTCP) (SIGCOMM 2010)](https://conferences.sigcomm.org/sigcomm/2010/papers/sigcomm/p63.pdf) · ACM · สวิตช์ทั่วไปให้หลายพอร์ตใช้บัฟเฟอร์ตื้นร่วมกัน เมื่อหลาย flow มารวมที่พอร์ตเดียวในชั่วขณะ บัฟเฟอร์จะล้นและแพ็กเก็ตหาย - [Netfilter Conntrack Sysfs variables](https://docs.kernel.org/networking/nf_conntrack-sysctl.html) · Linux kernel · ค่าเริ่มต้นของจำนวนรายการสูงสุดในตาราง connection tracking และระยะเวลาที่เก็บไว้ (UDP 30 วินาที, stream 120 วินาที, TCP ที่เชื่อมต่อสำเร็จแล้ว 5 วัน) - [Edit attributes for your Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/edit-load-balancer-attributes.html) · AWS · idle timeout ของ ALB ค่าเริ่มต้น 60 วินาที เมื่อครบเวลาโหลดบาลานเซอร์จะปิดการเชื่อมต่อ - [Network Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/network-load-balancers.html) · AWS · ค่าเริ่มต้นของโหลดบาลานเซอร์ในการทดลอง: NLB TCP 350 วินาที, UDP 120 วินาที (เปลี่ยนไม่ได้) หลัง idle จะหยุดติดตามแบบเงียบ ๆ - [Configure load balancer TCP reset and idle timeout](https://learn.microsoft.com/en-us/azure/load-balancer/load-balancer-tcp-idle-timeout) · Microsoft Azure · idle timeout ของ Azure Load Balancer ค่าเริ่มต้น 4 นาที - [Amazon EC2 security group connection tracking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-connection-tracking.html) · AWS · ค่าเริ่มต้นของ connection tracking ใน security group และคำอธิบายว่า TCP idle timeout ของโหลดบาลานเซอร์และไฟร์วอลล์มักอยู่ที่ 60–90 นาที - [Flow-Based Sessions](https://www.juniper.net/documentation/us/en/software/junos/flow-packet-processing/topics/topic-map/security-flow-based-session-for-srx-series-devices.html) · Juniper Networks · ไฟร์วอลล์บริษัทในการทดลอง: session timeout เริ่มต้นของไฟร์วอลล์ SRX คือ TCP 1,800 วินาที (30 นาที), UDP 60 วินาที - [An Experimental Study of Home Gateway Characteristics (IMC 2010)](https://conferences.sigcomm.org/imc/2010/papers/p260.pdf) · ACM · TCP 1 ชั่วโมงของเราเตอร์บ้านในการทดลอง: ค่ามัธยฐานของ TCP mapping ราว 60 นาที ส่วน UDP mapping ต่างกันตามอุปกรณ์ตั้งแต่ 30–691 วินาที โดยค่ามัธยฐานคือ 90 วินาทีสำหรับทิศทางเดียว และราว 180 วินาทีสำหรับสองทิศทาง - [A Multi-perspective Analysis of Carrier-Grade NAT Deployment (IMC 2016)](https://www.icir.org/vern/papers/cgn-imc16.pdf) · ACM · CGNAT UDP 30 วินาทีและเราเตอร์บ้าน UDP 1 นาทีในการทดลอง: ค่ามัธยฐานของ UDP mapping ใน CGN คือเครือข่ายแบบมีสาย 35 วินาที, เครือข่ายมือถือ 65 วินาที, NAT ที่วัดได้ 74% อยู่ที่ 1 นาทีหรือต่ำกว่า, NAT ของเราเตอร์บ้าน (CPE) ส่วนใหญ่อยู่ที่ 65 วินาที - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · TCP keepalive ในการทดลอง: ค่าเริ่มต้นคือหลัง idle 2 ชั่วโมง (7,200 วินาที) จะตรวจ 9 ครั้งทุก 75 วินาที ถ้าไม่มีการตอบกลับจะตัดการเชื่อมต่อ - [Cached apps freezer](https://source.android.com/docs/core/perf/cached-apps-freezer) · Android (Google) · มือถือเข้าเบื้องหลังในการทดลอง: Android 14 ขึ้นไปจะแช่แข็งโปรเซสของแอปที่เข้าสู่สถานะ cached หลัง 10 วินาที - [RFC 5880: Bidirectional Forwarding Detection (BFD)](https://www.rfc-editor.org/rfc/rfc5880) · IETF · BFD ที่ตรวจจับเส้นทางเสียได้เร็วกว่า Hello ระดับวินาทีของ routing protocol - [RFC 2923: TCP Problems with Path MTU Discovery](https://www.rfc-editor.org/rfc/rfc2923) · IETF · MTU black hole ที่ ICMP ถูกบล็อกจนเฉพาะแพ็กเก็ตใหญ่หายไป ### #basics - [Teletraffic Engineering Handbook (ITU-D Study Group 2 Question 16/2)](https://www.itu.int/dms_pub/itu-d/opb/stg/D-STG-SG02.16.2.1-2002-PDF-E.pdf) · ITU · เวลารอเฉลี่ยของ M/M/1 W = A·s/(1−A): ที่อัตราการใช้งาน 50%, 80% และ 90% เวลารอเท่ากับ 1 เท่า, 4 เท่า และ 9 เท่าของเวลาประมวลผล ที่อัตราการใช้งานเท่ากัน ยิ่งมี worker (server) มากและการมาถึงยิ่งสม่ำเสมอ เวลารอยิ่งสั้น - [CloudWatch metrics that are available for your instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html) · AWS · CPUUtilization ของ EC2 เป็นค่าของทั้งอินสแตนซ์ รวมค่าทุก 5 นาทีโดยค่าเริ่มต้น และทุก 1 นาทีเมื่อเปิด detailed monitoring - [Designs, Lessons and Advice from Building Large Distributed Systems (Jeff Dean, LADIS 2009 keynote)](https://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf) · Google · การอ่านหน่วยความจำหลัก (main memory reference) 100 ns, ไปกลับภายในดาต้าเซ็นเตอร์เดียวกัน 500,000 ns (0.5 ms) - [ITU-T G.114: One-way transmission time](https://www.itu.int/rec/T-REC-G.114-200305-I/en) · ITU · ความหน่วงในการแพร่สัญญาณในไฟเบอร์ออปติก 5 µs/กิโลเมตร (ฐานในการคำนวณขีดจำกัดความเร็วแสง) - [Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization (Yahn W. Bernier, GDC 2001)](https://web.cs.wpi.edu/~claypool/courses/4513-B03/papers/games/bernier.pdf) · Valve · ค่าเริ่มต้นของ Half-Life: อัปเดต 20 ครั้งต่อวินาที, interpolation 100 ms ถ้าเป็น 10 ครั้งต่อวินาที ใช้ interpolation 200 ms เพื่อทนอัปเดตหายหนึ่งครั้ง - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_rto_min_us ค่าเริ่มต้น 200000 (200 ms) - [Factors influencing the latency of simple reaction time (Frontiers in Human Neuroscience, 2015)](https://doi.org/10.3389/fnhum.2015.00131) · Frontiers · เวลาตอบสนองแบบง่าย (simple reaction time) เฉลี่ยราว 231 ms (213 ms เมื่อหักความหน่วงของอุปกรณ์) - [A Survey and Taxonomy of Latency Compensation Techniques for Network Computer Games (ACM Computing Surveys, 2022)](https://web.cs.wpi.edu/~claypool/papers/lag-taxonomy/paper.pdf) · ACM · ความหน่วงต่ำกว่า 100 ms ก็ส่งผลต่อผลการเล่นในเกม และในงานอย่างการลาก (drag) คนรับรู้ได้ตั้งแต่ราว 10 ms - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · ผู้เล่นที่ชำนาญรับรู้ความต่างได้ตั้งแต่ราว 10 ms ใน blind test - [Latency and Player Actions in Online Games (Communications of the ACM, 2006)](https://web.cs.wpi.edu/~claypool/papers/precision-deadline/final.pdf) · ACM · ความหน่วงที่ยอมรับได้ตามแนวเกม: มุมมองบุคคลที่ 1 ราว 100 ms, มุมมองบุคคลที่ 3 (RPG, MMO) ราว 500 ms, RTS ราว 1,000 ms - [JEP 333: ZGC: A Scalable Low-Latency Garbage Collector](https://openjdk.org/jeps/333) · OpenJDK · G1 บน heap 128 GB หยุดเฉลี่ย 157 ms สูงสุด 544 ms ส่วน ZGC อยู่ที่ราว 1–2 ms โดยไม่ขึ้นกับขนาด heap และขนาดข้อมูลที่ยังใช้อยู่ - [CommonNetworkParametersExtensions (Unity Transport 2.5)](https://docs.unity3d.com/Packages/com.unity.transport@2.5/api/Unity.Networking.Transport.CommonNetworkParametersExtensions.html) · Unity · disconnectTimeoutMS: ถ้าไม่ได้รับอะไรเลยตลอดช่วงเวลานี้จะตัดการเชื่อมต่อ (ค่าเริ่มต้น 30,000 ms) ### #lab - [Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization (Yahn W. Bernier, GDC 2001)](https://web.cs.wpi.edu/~claypool/courses/4513-B03/papers/games/bernier.pdf) · Valve · interpolation วาดภาพในอดีตแต่ได้ความลื่นมาแทน ส่วน extrapolation ไม่รู้ว่าจะเปลี่ยนทิศ ถ้าทายผิดภาพจะกระโดด ความคลาดเคลื่อนของ prediction แก้ด้วยผลจากเซิร์ฟเวอร์ - [UDP vs. TCP](https://gafferongames.com/post/udp_vs_tcp/) · Gaffer On Games · เมื่อ TCP ทำแพ็กเก็ตหายหนึ่งตัว ต่อให้ข้อมูลใหม่มาถึงแล้วก็จะไม่ส่งต่อให้แอปจนกว่าจะได้รับแพ็กเก็ตที่ส่งซ้ำ (ปกติ 2×RTT ขึ้นไป) - [Deterministic Lockstep](https://gafferongames.com/post/deterministic_lockstep/) · Gaffer On Games · ถ้าส่งอินพุตที่ยังไม่ได้รับการยืนยันซ้ำไปกับทุกแพ็กเก็ต ก็ไม่ต้องรอการส่งซ้ำ (แย่ที่สุดคืออินพุตปริมาณ 2 วินาที) - [Snapshot Interpolation](https://gafferongames.com/post/snapshot_interpolation/) · Gaffer On Games · ถ้าวาดทันทีที่ได้รับ จะกระตุกเพราะจิตเตอร์ ส่วน interpolation buffer แลกความหน่วงที่เพิ่มขึ้นเล็กน้อยกับความลื่น - [Understanding Networked Movement in the Character Movement Component for Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/understanding-networked-movement-in-the-character-movement-component-for-unreal-engine) · Epic Games · เมื่อการเคลื่อนที่ของไคลเอนต์หายหรือผิดเพราะปัญหาการเชื่อมต่อ เซิร์ฟเวอร์จะแก้ตำแหน่ง (ดีดกลับ) - [Source SDK 2013: player.cpp](https://raw.githubusercontent.com/ValveSoftware/source-sdk-2013/master/src/game/server/player.cpp) · Valve · จำกัดคำสั่งที่ทะลักเข้ามาด้วยงบคำสั่งที่สะสมทุกทิก ถ้าเข้มงวดเกินไป ผู้เล่นปกติก็กระตุก - [Introducing Time Dilation (TiDi)](https://www.eveonline.com/news/view/introducing-time-dilation-tidi) · CCP Games · การออกแบบที่ทำให้นาฬิกาเกมช้าลง (Time Dilation) เมื่อเซิร์ฟเวอร์โหลดเกิน ทุกอย่างจึงเดินช้าลง - [CommonNetworkParametersExtensions (Unity Transport 2.5)](https://docs.unity3d.com/Packages/com.unity.transport@2.5/api/Unity.Networking.Transport.CommonNetworkParametersExtensions.html) · Unity · inactivity timeout ที่ตัดการเชื่อมต่อเมื่อไม่ได้รับอะไรเลยเป็นระยะเวลาหนึ่ง ### #symptoms - [Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization (Yahn W. Bernier, GDC 2001)](https://web.cs.wpi.edu/~claypool/courses/4513-B03/papers/games/bernier.pdf) · Valve · เมื่ออัปเดตหาย ภาพจะหยุดที่ตำแหน่งล่าสุด (กระตุก) หรือ extrapolate ต่อแล้วกระโดด (วาร์ป) ต้องจำกัดเวลา extrapolation - [Understanding Networked Movement in the Character Movement Component for Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/understanding-networked-movement-in-the-character-movement-component-for-unreal-engine) · Epic Games · เมื่อการเคลื่อนที่ของไคลเอนต์หายหรือไม่ตรงกับการคำนวณของเซิร์ฟเวอร์ เซิร์ฟเวอร์จะส่งการแก้ไขมาดึงตำแหน่งกลับ (ดีดกลับ) - [UDP vs. TCP](https://gafferongames.com/post/udp_vs_tcp/) · Gaffer On Games · TCP จะเก็บข้อมูลที่ตามมาไว้จนกว่าจะได้รับแพ็กเก็ตที่หายจากการส่งซ้ำ แล้วจึงส่งต่อทีเดียว (กรอเร็ว) - [Deterministic Lockstep](https://gafferongames.com/post/deterministic_lockstep/) · Gaffer On Games · เมื่ออินพุตที่ล่าช้ามาถึงพร้อมกัน จะคำนวณหลายเฟรมรวดเดียวเพื่อไล่ตาม - [Introducing Time Dilation (TiDi)](https://www.eveonline.com/news/view/introducing-time-dilation-tidi) · CCP Games · Time Dilation (TiDi) ที่ทำให้นาฬิกาเกมช้าลงเมื่อเซิร์ฟเวอร์โหลดเกิน และปรากฏการณ์ที่งานเลื่อนออกไปทีละหลายวินาทีเมื่อโหลดเกิน - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · เวลา buffering ขึ้นกับทิกเรตของเซิร์ฟเวอร์และเฟรมเรนเดอร์ของไคลเอนต์ - [Using Gameplay Abilities in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/using-gameplay-abilities-in-unreal-engine) · Epic Games · เซิร์ฟเวอร์อาจกลับผลของ ability ที่รันแบบ predict ไว้ (กดไม่ติด/โรลแบ็ค) - [Actor Relevancy in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-relevancy-in-unreal-engine) · Epic Games · actor ที่เซิร์ฟเวอร์มองว่าไม่เกี่ยวข้องจะไม่ถูก replicate หรือถูกลบที่ไคลเอนต์ (มองไม่เห็น/ตัวผี) - [CommonNetworkParametersExtensions (Unity Transport 2.5)](https://docs.unity3d.com/Packages/com.unity.transport@2.5/api/Unity.Networking.Transport.CommonNetworkParametersExtensions.html) · Unity · เมื่อเกิน inactivity timeout จะตัดการเชื่อมต่อ (หลุด) ### #sync - [Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization (Yahn W. Bernier, GDC 2001)](https://web.cs.wpi.edu/~claypool/courses/4513-B03/papers/games/bernier.pdf) · Valve · หลักการของ authoritative server, client-side prediction และ reconciliation, interpolation และ lag compensation รวมถึงข้อแลกเปลี่ยนอย่าง “โดนยิงทั้งที่หลบเข้ามุมแล้ว” - [What Every Programmer Needs To Know About Game Networking](https://gafferongames.com/post/what_every_programmer_needs_to_know_about_game_networking/) · Gaffer On Games · พัฒนาการของ netcode จาก P2P lockstep สู่ client/server และ client-side prediction - [Using Gameplay Abilities in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/using-gameplay-abilities-in-unreal-engine) · Epic Games · ข้อแลกเปลี่ยนระหว่างความไวในการตอบสนองกับความแม่นยำของ Local Predicted และ Server Initiated - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · server authoritative, prediction, buffering, การตัดสินแบบย้อนเวลา (rewind) และเพดานของการย้อนเวลา - [NetworkTime and ticks (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/advanced-topics/networktime-ticks.html) · Unity · วิธีนัดเวลาอีเวนต์ให้ตรงกับเวลาของเซิร์ฟเวอร์แล้วเล่นตามนั้น - [1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond](https://www.gamedeveloper.com/programming/1500-archers-on-a-28-8-network-programming-in-age-of-empires-and-beyond) · Game Developer · lockstep: นัดเวลาคำสั่งไปอีกสองเทิร์นข้างหน้า, ความยาวเทิร์นที่ปรับตามคอมพิวเตอร์ที่ช้าที่สุด, ความหน่วงคงที่ 500 ms ยังพอรับได้ แต่ความหน่วงที่ขึ้นลงไม่สม่ำเสมอทำให้เล่นไม่สบาย - [Deterministic Lockstep](https://gafferongames.com/post/deterministic_lockstep/) · Gaffer On Games · lockstep จะเดินต่อได้ก็ต่อเมื่ออินพุตของทุกคนมาครบ และใช้ playout delay buffer ดูดซับจิตเตอร์ - [GGPO Rollback Networking SDK](https://www.ggpo.net/) · GGPO · rollback: เดินเกมต่อโดยคาดการณ์อินพุตของอีกฝ่าย ถ้าผิดก็ย้อนกลับไปคำนวณใหม่ - [8 Frames in 16ms: Rollback Networking in 'Mortal Kombat' and 'Injustice 2'](https://www.gdcvault.com/play/1025471/8-Frames-in-16ms-Rollback) · GDC · rollback ตัด local input delay ของ lockstep ออก และคำนวณใหม่ได้สูงสุด 8 เฟรม - [Latency and Player Actions in Online Games (Communications of the ACM, 2006)](https://web.cs.wpi.edu/~claypool/papers/precision-deadline/final.pdf) · ACM · ความหน่วงเท่ากันส่งผลต่างกันตามความแม่นยำและกำหนดเวลาของการกระทำ รวมถึงมุมมอง (บุคคลที่ 1, บุคคลที่ 3, มุมมองแบบพระเจ้า) - [Networking Overview for Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/networking-overview-for-unreal-engine) · Epic Games · ข้อได้เปรียบและภาระของโฮสต์ใน listen server - [Factors influencing the latency of simple reaction time (Frontiers in Human Neuroscience, 2015)](https://doi.org/10.3389/fnhum.2015.00131) · Frontiers · เวลาตอบสนองแบบง่ายของคนราว 0.23 วินาที ### #partial - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · ถ้าคนหนึ่งคลาดเคลื่อน จะแก้ให้เฉพาะคนนั้น ส่วนอีกเก้าคนยังเห็นภาพลื่นไหล และมีเพดานสำหรับการตัดสินแบบย้อนเวลา - [State Synchronization](https://gafferongames.com/post/state_synchronization/) · Gaffer On Games · แพ็กเก็ตมาถึงเป็นกระจุก เช่น เฟรมละ 2 ตัวบ้าง 0 ตัวบ้าง และใช้ jitter buffer เกลี่ยให้สม่ำเสมอ - [Source SDK 2013: player.cpp](https://raw.githubusercontent.com/ValveSoftware/source-sdk-2013/master/src/game/server/player.cpp) · Valve · งบคำสั่งที่แบ่งคำสั่งที่ทะลักเข้ามาไปประมวลผลตามทิก และผลข้างเคียงของการจำกัดที่เข้มงวด - [Source SDK 2013: player_lagcompensation.cpp](https://raw.githubusercontent.com/ValveSoftware/source-sdk-2013/master/src/game/server/player_lagcompensation.cpp) · Valve · เพดานการย้อนเวลาของ Source engine คือ sv_maxunlag ค่าเริ่มต้น 1 วินาที - [A Survey and Taxonomy of Latency Compensation Techniques for Network Computer Games (ACM Computing Surveys, 2022)](https://web.cs.wpi.edu/~claypool/papers/lag-taxonomy/paper.pdf) · ACM · “โดนยิงทั้งที่หลบเข้ามุมแล้ว” ของ lag compensation และ input delay ที่ทำให้อินพุตของทุกคนตรงกัน - [send(2) — Linux manual page](https://man7.org/linux/man-pages/man2/send.2.html) · Linux man-pages · เมื่อ send buffer เต็ม send() จะรอในโหมด blocking และคืนค่า EAGAIN ทันทีในโหมด non-blocking - [Networking Overview for Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/networking-overview-for-unreal-engine) · Epic Games · โฮสต์ของ listen server ได้เปรียบกว่าผู้เล่นคนอื่น - [Authority (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/terms-concepts/authority.html) · Unity · distributed authority: ไคลเอนต์แต่ละเครื่องรับผิดชอบคำนวณเอนทิตีบางส่วน - [Actor Relevancy in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-relevancy-in-unreal-engine) · Epic Games · เซิร์ฟเวอร์ส่งเฉพาะ actor ที่เกี่ยวข้องให้แต่ละการเชื่อมต่อ และเมื่อไม่เกี่ยวข้องแล้วจะลบออกจากไคลเอนต์ - [NetworkConfig class (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/api/Unity.Netcode.NetworkConfig.html) · Unity · ข้อความของเอนทิตีที่ยังไม่ถูกสร้างจะถูกพักไว้ และทิ้งเมื่อเลยเวลา (SpawnTimeout) - [RFC 8085: UDP Usage Guidelines](https://www.rfc-editor.org/rfc/rfc8085) · IETF · แพ็กเก็ต UDP ที่ถูก fragment จะหายไปทั้งก้อน แม้ fragment หายไปเพียงชิ้นเดียว - [Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE](https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse) · Microsoft · ถ้าสองซ็อกเก็ตใช้พอร์ตเดียวกัน จะไม่รู้ว่าฝั่งไหนจะได้รับแพ็กเก็ต - [RFC 6269: Issues with IP Address Sharing](https://www.rfc-editor.org/rfc/rfc6269) · IETF · ถ้าผู้ใช้หลายรายใช้ IP address ร่วมกัน จะแยกผู้ใช้ด้วย IP อย่างเดียวไม่ได้ - [Application.runInBackground](https://docs.unity3d.com/ScriptReference/Application-runInBackground.html) · Unity · พฤติกรรมเริ่มต้นที่แอปถูกพักการทำงานเมื่ออยู่เบื้องหลัง - [Actor Priority in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-priority-in-unreal-engine) · Epic Games · เมื่อแบนด์วิดท์เต็ม จะ replicate เฉพาะ actor บางตัวตามลำดับความสำคัญ ### #retrans - [RFC 6298: Computing TCP's Retransmission Timer](https://www.rfc-editor.org/rfc/rfc6298) · IETF · RTO = SRTT + max(G, 4·RTTVAR), ค่าเริ่มต้น 1 วินาที, แนะนำต่ำสุด 1 วินาที, เพิ่มเป็นสองเท่าทุกครั้งที่หมดเวลา, ถ้าจะกำหนดเพดานต้องไม่น้อยกว่า 60 วินาที, ไม่ใช้แพ็กเก็ตที่ส่งซ้ำเป็นตัวอย่าง RTT (Karn) - [RFC 5681: TCP Congestion Control](https://www.rfc-editor.org/rfc/rfc5681) · IETF · fast retransmit เมื่อได้ duplicate ACK ตัวที่สาม และหลัง RTO จะเริ่มใหม่จาก congestion window 1 segment (loss window) - [RFC 6675: A Conservative Loss Recovery Algorithm Based on Selective Acknowledgment (SACK) for TCP](https://www.rfc-editor.org/rfc/rfc6675) · IETF · การกู้คืนที่ใช้ข้อมูล SACK ตัดสินแพ็กเก็ตที่ขาดหาย (สัญญาณ duplicate ACK และ SACK สำหรับ fast retransmit) - [RFC 8985: The RACK-TLP Loss Detection Algorithm for TCP](https://www.rfc-editor.org/rfc/rfc8985) · IETF · ช่วงเผื่อการสลับลำดับของ RACK (min_RTT/4) และการปรับตาม DSACK, เวลารอ TLP 2·SRTT (ถ้ามีแพ็กเก็ตที่ยังไม่ได้รับการยืนยันเพียงตัวเดียว จะบวกเวลาเผื่อ delayed ACK), ตั้ง RTO ใหม่หลังส่ง TLP, ต้องใช้ SACK - [RFC 2018: TCP Selective Acknowledgment Options](https://www.rfc-editor.org/rfc/rfc2018) · IETF · SACK: ฝั่งรับแจ้งช่วงที่ขาดหายตรงกลาง - [RFC 2883: An Extension to the Selective Acknowledgement (SACK) Option for TCP](https://www.rfc-editor.org/rfc/rfc2883) · IETF · DSACK: แจ้งว่าได้รับสิ่งที่ได้รับไปแล้วซ้ำอีก ทำให้เห็นการส่งซ้ำโดยไม่จำเป็น - [RFC 5682: Forward RTO-Recovery (F-RTO): An Algorithm for Detecting Spurious Retransmission Timeouts with TCP](https://www.rfc-editor.org/rfc/rfc5682) · IETF · F-RTO: ตรวจจับ RTO ที่ไม่จำเป็น - [RFC 3522: The Eifel Detection Algorithm for TCP](https://www.rfc-editor.org/rfc/rfc3522) · IETF · ใช้ timestamps แยกแยะย้อนหลังว่าการกู้คืนไม่จำเป็นหรือไม่ (การย้อนคืน congestion window ในการจำลอง) - [RFC 7323: TCP Extensions for High Performance](https://www.rfc-editor.org/rfc/rfc7323) · IETF · option timestamps และ window scaling ถ้าไม่มี scaling window จะใหญ่สุด 64 KiB - [RFC 6937: Proportional Rate Reduction for TCP](https://www.rfc-editor.org/rfc/rfc6937) · IETF · PRR: ลดปริมาณที่ส่งระหว่างกู้คืนให้สอดคล้องกับปริมาณที่ส่งถึงใหม่ (เพดานการส่งระหว่างกู้คืนในการจำลอง) - [RFC 9438: CUBIC for Fast and Long-Distance Networks](https://www.rfc-editor.org/rfc/rfc9438) · IETF · CUBIC ลด congestion window เหลือ 0.7 เท่าเมื่อแพ็กเก็ตหาย (การลด 30% ในการจำลอง) - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · zero window probe: แม้ window เป็น 0 ก็ยังส่ง probe และเพิ่มช่วงห่างแบบ exponential - [RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport](https://www.rfc-editor.org/rfc/rfc9000) · IETF · ใน QUIC การหายจะบล็อกเฉพาะ stream ที่อยู่ในแพ็กเก็ตนั้น stream อื่นยังเดินต่อได้ (แยก flow) - [RFC 2475: An Architecture for Differentiated Services](https://www.rfc-editor.org/rfc/rfc2475) · IETF · นิยามว่า shaping หน่วงแพ็กเก็ต ส่วน policing ทิ้งส่วนที่เกิน - [RFC 3168: The Addition of Explicit Congestion Notification (ECN) to IP](https://www.rfc-editor.org/rfc/rfc3168) · IETF · ECN: แจ้งความแออัดโดยไม่ทิ้งแพ็กเก็ต - [Smart Queue Management](https://www.bufferbloat.net/projects/cerowrt/wiki/Smart_Queue_Management/) · Bufferbloat.net · SQM บนเราเตอร์: ใช้ scheduling แยกตาม flow, AQM และ shaping เพื่อลดคิวล้นและ bufferbloat - [RFC 1191: Path MTU discovery](https://www.rfc-editor.org/rfc/rfc1191) · IETF · หา path MTU ด้วย ICMP แจ้งเกินขนาด (type 3 code 4) - [RFC 1812: Requirements for IP Version 4 Routers](https://www.rfc-editor.org/rfc/rfc1812) · IETF · เราเตอร์จำกัดอัตราการส่งข้อความ ICMP error ได้ (ผล mtr ที่ดูเหมือนแพ็กเก็ตหายเฉพาะที่ hop เดียวกลางทาง) - [RFC 2991: Multipath Issues in Unicast and Multicast Next-Hop Selection](https://www.rfc-editor.org/rfc/rfc2991) · IETF · ใน multipath ผลวินิจฉัยอย่าง ping และ traceroute เชื่อถือได้ยาก และวิธีล็อกเส้นทางด้วย flow hash - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_recovery (ค่าเริ่มต้น 0x1 RACK ตั้งแต่ 6.17 RACK เป็นวิธีตรวจจับการหายเพียงวิธีเดียว ตั้งเป็น 0 ก็ไม่มีผล), tcp_early_retrans (ค่าเริ่มต้น 3, 0 คือปิด TLP), tcp_sack, tcp_dsack และ tcp_timestamps (เปิดเป็นค่าเริ่มต้น), tcp_thin_linear_timeouts (แพ็กเก็ต in-flight น้อยกว่า 4 ตัว, linear สูงสุด 6 ครั้ง), tcp_rto_max_ms, tcp_mtu_probing และ tcp_base_mss, tcp_rto_min_us, tcp_retries2 (ค่าเริ่มต้น 15, ราว 924.6 วินาที), tcp_syn_linear_timeouts, tcp_notsent_lowat - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpOutSegs ไม่นับรวมการส่งซ้ำ, ความหมายของ TCPLossProbes, TCPLossProbeRecovery, TCPLostRetransmit, TCPSpuriousRTOs, TCPDSACKRecv และ TCPSynRetrans - [net/ipv4/proc.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/proc.c?h=v6.12) · Linux kernel · ชื่อตัวนับที่ nstat แสดง (RetransSegs, TCPTimeouts, TCPLossProbes, TCPSpuriousRTOs, TCPDSACKRecv ฯลฯ) - [Thin-streams and TCP](https://docs.kernel.org/networking/tcp-thin.html) · Linux kernel · thin stream อย่างเกม fast retransmit ทำงานได้ไม่ดี จึงต้องพึ่ง timeout ที่ยาว, TCP_THIN_LINEAR_TIMEOUTS - [include/net/tcp.h](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp.h?h=v6.12) · Linux kernel · TCP_RTO_MIN 200 ms, TCP_RTO_MAX 120 วินาที, TCP_TIMEOUT_INIT 1 วินาที, delayed ACK 40–200 ms (TCP_DELACK_MIN, MAX), TCP_BASE_MSS 1,024, เกณฑ์ thin stream (แพ็กเก็ต in-flight น้อยกว่า 4 ตัว) - [net/ipv4/tcp_input.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_input.c?h=v6.12) · Linux kernel · RTO = SRTT + rttvar (ไม่ต่ำกว่าค่าต่ำสุดของ RTO), RACK ใช้เฉพาะการเชื่อมต่อที่มี SACK, ใช้ PRR ลด congestion window ระหว่างกู้คืน - [net/ipv4/tcp_output.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_output.c?h=v6.12) · Linux kernel · TLP ใช้เฉพาะการเชื่อมต่อที่มี SACK, รอ 2·RTT, ถ้ามีแพ็กเก็ตที่ยังไม่ได้รับการยืนยันเพียงตัวเดียวจะบวกค่าต่ำสุดของ RTO - [net/ipv4/tcp_timer.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_timer.c?h=v6.12) · Linux kernel · ถ้า RTO เกิดต่อเนื่องครบ tcp_retries1 ครั้ง จะถือว่าตรวจพบ black hole แล้วทำ MTU probing, linear timeout ของ thin stream และ SYN - [net/ipv4/tcp_recovery.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_recovery.c?h=v6.12) · Linux kernel · เวลาเผื่อของ RACK = min(min_RTT/4 × ขั้น, SRTT), การส่งซ้ำที่ได้รับการยืนยันเร็วกว่า RTT ต่ำสุดจะไม่ถูกนำมาใช้เป็นเกณฑ์ - [net/ipv4/tcp_ipv4.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_ipv4.c?h=v6.12) · Linux kernel · การกำหนดค่าเริ่มต้น: tcp_early_retrans 3, tcp_recovery RACK, tcp_syn_linear_timeouts 4, tcp_base_mss 1,024 (tcp_mtu_probing ไม่ได้กำหนดแยก จึงเป็น 0) - [net/ipv4/tcp_bbr.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp_bbr.c?h=v6.12) · Linux kernel · pacing_rate ของ BBR = pacing_gain × แบนด์วิดท์คอขวด (ใช้ pacing ส่งให้สม่ำเสมอ) - [tcp: use RACK to detect losses](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4f41b1c58a32537542f14c1150099131613a5e8a) · Linux kernel · เริ่มใช้ RACK และ tcp_recovery (Linux 4.4) ตอนแรกทำงานเป็นตัวเสริมของวิธีเดิม - [tcp: disable RFC6675 loss detection](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b38a51fec1c1f693f03b1aa19d0622123634d4b7) · Linux kernel · เปลี่ยน RACK เป็นวิธีตรวจจับการหายหลัก (ปี 2018, Linux 4.18) - [tcp: remove obsolete and unused RFC3517/RFC6675 loss recovery code](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1c120191dcec510cc17d587ece48a7ae875a90c5) · Linux kernel · ลบโค้ดการกู้คืนตาม RFC6675 (Linux 6.17) พร้อมคำอธิบายว่า RACK-TLP เป็นค่าเริ่มต้นมาตั้งแต่ปี 2018 - [tcp: make the first N SYN RTO backoffs linear](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ccce324dabfe2143519daf50ed8b1ef1d0c542f7) · Linux kernel · backoff 4 ครั้งแรกของ SYN RTO ไม่เพิ่มเป็นสองเท่า (หลัง RTO แรก 1 วินาที จะรออีก 4 ครั้ง ครั้งละ 1 วินาที แล้วจึงเป็น 2, 4 วินาที …, Linux 6.5) - [tcp: add sysctl_tcp_rto_min_us](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f086edef71be7174a16c1ed67ac65a085cda28b1) · Linux kernel · ค่าต่ำสุดของ RTO สำหรับทั้งเซิร์ฟเวอร์ tcp_rto_min_us (Linux 6.11) - [tcp: add the ability to control max RTO](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=54a378f43425085d0684679d99735696b69165bc) · Linux kernel · socket option TCP_RTO_MAX_MS, 1–120 วินาที (Linux 6.15) - [tcp: support TCP_RTO_MIN_US for set/getsockopt use](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f38805c5d26fe4af97837c10d58074a7496638bf) · Linux kernel · socket option TCP_RTO_MIN_US สำหรับกำหนดค่าต่ำสุดของ RTO รายการเชื่อมต่อ (Linux 6.15) - [Android common kernels](https://source.android.com/docs/core/architecture/kernel/android-common) · Android (Google) · Android common kernel ที่ยังรองรับอยู่คือ 5.10 ขึ้นไป (ใหม่กว่า 4.18 ที่ RACK กลายเป็นค่าเริ่มต้น) - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · TCP_NODELAY (ปิด Nagle), TCP_USER_TIMEOUT (ไม่เปลี่ยนจังหวะการส่งซ้ำ กำหนดแค่จุดที่เลิก), TCP_KEEPIDLE, TCP_INFO - [ip-route(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ip-route.8.html) · iproute2 · ตัวเลือก rto_min รายเส้นทาง - [tc-fq(8) — Linux manual page](https://man7.org/linux/man-pages/man8/tc-fq.8.html) · iproute2 · pacing ต่อการเชื่อมต่อของคิว fq และ SO_MAX_PACING_RATE - [iptables-extensions(8) — Linux manual page](https://man7.org/linux/man-pages/man8/iptables-extensions.8.html) · netfilter · TCPMSS --clamp-mss-to-pmtu: เลี่ยงช่วงที่ ICMP ถูกบล็อกด้วยการปรับ MSS ใน SYN - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · rto (ms), backoff (จำนวนครั้งของ exponential backoff), rtt/rttvar และ cwnd ใน ss -i - [misc/ss.c](https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/misc/ss.c?h=v6.12.0) · iproute2 · ผลลัพธ์ retrans:ปัจจุบัน/สะสม, lost, reordering, bytes_sent และ bytes_retrans ใน ss -ti - [nstat(8) — Linux manual page](https://man7.org/linux/man-pages/man8/nstat.8.html) · iproute2 · โดยค่าเริ่มต้น nstat แสดงค่าที่เพิ่มขึ้นนับจากการรันครั้งก่อน - [Demonstrations of tcpretrans, the Linux eBPF/bcc version](https://raw.githubusercontent.com/iovisor/bcc/master/tools/tcpretrans_example.txt) · IO Visor · แสดงที่อยู่ พอร์ต และสถานะ หนึ่งบรรทัดต่อการส่งซ้ำแต่ละครั้ง, -c สรุปแยกตาม flow, -l รวมความพยายามส่ง TLP ด้วย - [Interface statistics](https://docs.kernel.org/networking/statistics.html) · Linux kernel · ตรวจรายละเอียด error ได้ด้วย ip -s -s link, ความหมายของ rx_missed_errors และ rx_crc_errors, สถิติรายไดรเวอร์ใน ethtool -S - [drivers/net/ethernet/intel/igb/igb_ethtool.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/net/ethernet/intel/igb/igb_ethtool.c?h=v6.12) · Linux kernel · ตัวอย่างชื่อตัวนับรายไดรเวอร์: rx_missed_errors, rx_no_buffer_count, rx_crc_errors - [Ethtool counters](https://docs.kernel.org/networking/device_drivers/ethernet/mellanox/mlx5/counters.html) · Linux kernel · rx_out_of_buffer และ rx_discards_phy ของ mlx5 - [net/core/net-procfs.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/core/net-procfs.c?h=v6.12) · Linux kernel · /proc/net/softnet_stat มีหนึ่งบรรทัดต่อ CPU เป็นเลขฐาน 16 คอลัมน์ที่ 2 คือ dropped คอลัมน์ที่ 3 คือ time_squeeze - [Monitor network performance for ENA settings on your EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html) · AWS · ความหมายของ bw_in, bw_out, pps, conntrack และ linklocal_allowance_exceeded ถ้าจะดูใน CloudWatch ต้องติดตั้ง CloudWatch agent - [High-Resolution Measurement of Data Center Microbursts](https://research.facebook.com/publications/high-resolution-measurement-of-data-center-microbursts/) · Meta · burst ส่วนใหญ่จบภายในหลายสิบ µs จึงมองไม่เห็นสาเหตุของการดรอปจากอัตราการใช้งานเฉลี่ยรายนาที (IMC 2017) - [mtr(8) manual page source](https://raw.githubusercontent.com/traviscross/mtr/master/man/mtr.8.in) · mtr · -T (--tcp) ใช้ TCP SYN, -P (--port) ระบุพอร์ตปลายทาง - [pathping](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/pathping) · Microsoft · คำสั่งวินิจฉัยเส้นทางของ Windows ที่ส่งหลายครั้งเพื่อคำนวณแพ็กเก็ตหายและความหน่วงแยกตาม hop - [Display Filter Reference: Transmission Control Protocol](https://www.wireshark.org/docs/dfref/t/tcp.html) · Wireshark · display filter tcp.analysis.retransmission, fast_retransmission, spurious_retransmission, duplicate_ack, lost_segment และ zero_window - [7.5. TCP Analysis](https://www.wireshark.org/docs/wsug_html_chunked/ChAdvTCPAnalysis.html) · Wireshark · เงื่อนไขที่ใช้ตัดสินการส่งซ้ำ, fast retransmit, การส่งซ้ำโดยไม่จำเป็น และ ZeroWindow - [Network-Related Performance Counters](https://learn.microsoft.com/en-us/windows-server/networking/technologies/network-subsystem/net-sub-performance-counters) · Microsoft · TCPv4 Segments Retransmitted/sec·Segments Sent/sec, Network Interface Packets Received Discarded - [TCP/IP connectivity issues troubleshooting](https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/tcp-ip-connectivity-issues-troubleshooting) · Microsoft · ตรวจการตั้งค่า TCP ระดับ global (Max SYN Retransmissions) ด้วย netsh int tcp show global และใช้ capture ทั้งสองฝั่งพร้อมกันเพื่อยืนยันแพ็กเก็ตหายระหว่างทาง - [Packet Monitor (Pktmon)](https://learn.microsoft.com/en-us/windows-server/networking/technologies/pktmon/pktmon) · Microsoft · เครื่องมือในตัวที่แสดงตำแหน่งและเหตุผลที่แพ็กเก็ตถูกทิ้ง ณ หลายจุดใน network stack ของ Windows - [Pktmon command formatting](https://learn.microsoft.com/en-us/windows-server/networking/technologies/pktmon/pktmon-syntax) · Microsoft · มีมาในตัวเป็น pktmon.exe ใน Windows 10 และ Windows Server 2019 (1809 ขึ้นไป) - [TCP improvements in the Windows network stack (IETF 98 TCPM)](https://datatracker.ietf.org/meeting/98/materials/slides-98-tcpm-tcp-improvements-in-windows-01) · Microsoft · อัปเดตครบรอบ 1 ปีของ Windows 10 (1607) และ Server 2016 เปิด TLP และ RACK เป็นค่าเริ่มต้น (การเชื่อมต่อที่ RTT เกิน 10 ms) เมื่อเหลือแพ็กเก็ตเดียว TLP จะเผื่อเวลา delayed ACK 200 ms - [Algorithmic improvements boost TCP performance on the Internet](https://techcommunity.microsoft.com/blog/networkingblog/algorithmic-improvements-boost-tcp-performance-on-the-internet/2347061) · Microsoft · TLP เป็นค่าเริ่มต้นตั้งแต่ Windows Server 2016, RACK แบบใหม่ที่กู้คืนได้ถึงการส่งซ้ำที่หาย มาพร้อม Server 2022, PRR เป็นค่าเริ่มต้นตั้งแต่ Windows 10 1903 - [TcpMaxConnectRetransmissions](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-2000-server/cc938209(v=technet.10)) · Microsoft · การส่ง SYN ซ้ำของ Windows รุ่นเก่า: 2 ครั้ง เริ่มที่ 3 วินาทีแล้วเพิ่มเป็นสองเท่า ### #owners - [RFC 4787: Network Address Translation (NAT) Behavioral Requirements for Unicast UDP](https://www.rfc-editor.org/rfc/rfc4787) · IETF · NAT mapping ต้องต่ออายุด้วยแพ็กเก็ตขาออกจากฝั่งในเสมอ (REQ-6) ส่วนการต่ออายุด้วยแพ็กเก็ตขาเข้าจากฝั่งนอกเป็นทางเลือก (สำหรับ UDP) ไคลเอนต์จึงต้องเป็นฝ่ายส่ง heartbeat - [RFC 5382: NAT Behavioral Requirements for TCP](https://www.rfc-editor.org/rfc/rfc5382) · IETF · NAT ลบ TCP session ที่ idle ได้ และ idle timeout ที่แนะนำคือไม่น้อยกว่า 2 ชั่วโมง 4 นาที (การตั้งค่าอาจต่างกันไปตามอุปกรณ์) - [Amazon EC2 security group connection tracking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-connection-tracking.html) · AWS · TCP idle timeout ของ connection tracking ใน security group (อินสแตนซ์ประเภท Nitro v6 คือ 350 วินาที, ประเภทอื่น 5 วัน, ปรับได้ตั้งแต่ 60 วินาทีถึง 5 วัน), แนะนำ keepalive ที่ถี่กว่า 5 นาที, TCP ที่ผ่าน NLB คือ 350 วินาที - [Control subnet traffic with network access control lists](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html) · AWS · network ACL ไม่เก็บสถานะ (ไม่มี connection tracking) จึงต้องเขียนกฎอนุญาตทราฟฟิกขากลับแยกไว้ด้วย - [Monitor network performance for ENA settings on your EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html) · AWS · ตัวนับการเกินขีดจำกัด connection tracking และจำนวนแพ็กเก็ตต่อวินาทีของอินสแตนซ์ (conntrack_allowance_exceeded, pps_allowance_exceeded) - [listen(2) — Linux manual page](https://man7.org/linux/man-pages/man2/listen.2.html) · Linux man-pages · อาร์กิวเมนต์ backlog ของ listen คือขนาดคิวจริง และถ้ามากกว่า somaxconn จะถูกตัดเหลือค่านั้น (ค่าเริ่มต้น 4096 ตั้งแต่ 5.4) - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · somaxconn (เพดาน listen backlog), tcp_max_syn_backlog, tcp_syncookies (ค่าเริ่มต้น 1 เป็นมาตรการรองรับเมื่อคิว SYN ล้น), tcp_keepalive_time (ค่าเริ่มต้น 2 ชั่วโมง) - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · เมื่อคิว accept เต็ม SYN จะถูกทิ้ง และ TcpExtListenOverflows กับ TcpExtListenDrops เพิ่มขึ้น - [Netfilter Conntrack Sysfs variables](https://docs.kernel.org/networking/nf_conntrack-sysctl.html) · Linux kernel · คำนวณอัตราการใช้ conntrack จาก nf_conntrack_count และ nf_conntrack_max - [Control Group v2](https://docs.kernel.org/admin-guide/cgroup-v2.html) · Linux kernel · nr_throttled ใน cpu.stat: จำนวนครั้งที่คอนเทนเนอร์ถูก throttle เพราะขีดจำกัด CPU - [proc_stat(5) — Linux manual page](https://man7.org/linux/man-pages/man5/proc_stat.5.html) · Linux man-pages · steal ใน /proc/stat: เวลาที่ OS อื่นใช้ CPU ในสภาพแวดล้อม virtualization - [RFC 2992: Analysis of an Equal-Cost Multi-Path Algorithm](https://www.rfc-editor.org/rfc/rfc2992) · IETF · ECMP เลือกเส้นทางจาก hash ของฟิลด์ใน header ที่ใช้แยก flow (flow เดียวกันไปเส้นทางเดียวกัน flow ต่างกันอาจไปคนละเส้นทาง) - [mtr(8) manual page source](https://raw.githubusercontent.com/traviscross/mtr/master/man/mtr.8.in) · mtr · วัดเส้นทางด้วยโปรโตคอลและพอร์ตเดียวกับเกม (-T, -u, -P) ### #l-server-proc - [VALORANT's 128-Tick Servers](https://www.riotgames.com/en/news/valorants-128-tick-servers) · Riot Games · งบเวลาต่อทิก: ที่ 128 ทิก ต้องจบหนึ่งเฟรมภายใน 7.8125 ms, วัดเวลาต่อเฟรมแยกตามระบบย่อยแล้วแบ่งงบเวลาไปจัดการ - [Introducing Time Dilation (TiDi)](https://www.eveonline.com/news/view/introducing-time-dilation-tidi) · CCP Games · การจำลองฟิสิกส์ของ EVE Online อัปเดตหนึ่งครั้งใน 1 วินาที เมื่อโหลดเกินจะทำให้นาฬิกาเกมช้าลง เพื่อลดภาระที่ผูกกับเวลาลงตามสัดส่วน - [HED-GP Technical Retrospective: What a HED-ache](https://www.eveonline.com/news/view/what-a-hed-ache) · CCP Games · Time Dilation ต่ำสุด 10%, การส่งแบบ O(n²) ที่ต้องแจ้งการกระทำของ n คนให้ n คนรู้ คือปัจจัยจำกัดของการรบขนาดใหญ่ - [Handling variation in time](https://docs.unity3d.com/Manual/time-handling-variations.html) · Unity · โมเดลของการทดลองทิก: เมื่อการเดินแบบช่วงห่างคงที่ล่าช้า จะรันขั้นไล่ตามรวดเดียว (กรอเร็ว) และทิ้งเวลาที่เกินเพดาน เวลาเกมจึงช้าลง (สโลว์โมชั่น) - [Comparing Interest Management Algorithms for Massively Multiplayer Games](https://www.sable.mcgill.ca/~clump/papers/boulanger-06-comparing.pdf) · ACM · เปเปอร์ NetGames 2006 (ฉบับที่ผู้เขียนเผยแพร่): การเทียบระยะทุกคู่รับมือไม่ไหวเมื่อจำนวนคนเพิ่มขึ้น ถ้าแบ่งเป็นตาราง (grid) จะตรวจแค่เซลล์รอบ ๆ - [Replication Graph in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/replication-graph-in-unreal-engine) · Epic Games · ถ้าแบ่งโลกเป็นตารางแล้วเลือกเป้าหมายที่จะส่งจากรายการของแต่ละเซลล์ จะประหยัด CPU ของเซิร์ฟเวอร์ได้แม้มีผู้เล่นและ actor จำนวนมาก - [Amdahl's Law in the Multicore Era](https://research.cs.wisc.edu/multifacet/papers/ieeecomputer08_amdahl_multicore.pdf) · IEEE · เพดาน throughput ในการทดลองล็อก: ถ้าสัดส่วนของงานที่ทำได้ทีละอย่างเท่านั้นคือ 1−f การเร่งความเร็วจะไม่เกิน 1/(1−f) - [Runtime locking correctness validator](https://docs.kernel.org/locking/lockdep-design.html) · Linux kernel · เดดล็อกในการทดลองล็อก: ถ้าถือล็อกสองตัวในลำดับกลับกัน จะเกิดการรอวนเป็นวงจรจนเป็นเดดล็อก - [Liveness, Readiness, and Startup Probes](https://kubernetes.io/docs/concepts/workloads/pods/probes/) · Kubernetes · watchdog ในการทดลองล็อก: จับสถานะเดดล็อกด้วย liveness probe แล้วรีสตาร์ต ค่าเริ่มต้นคือตรวจทุก 10 วินาที และรีสตาร์ตเมื่อล้มเหลวติดกัน 3 ครั้ง (ราว 30 วินาที) - [ASP.NET Core Best Practices](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/best-practices) · Microsoft · synchronous call: เรียกการเข้าถึงข้อมูลและ I/O แบบ async เพราะ blocking call ทำให้ thread pool หมดและตอบสนองช้า ### #l-infra - [Site Reliability Engineering, Chapter 22: Addressing Cascading Failures](https://sre.google/sre-book/addressing-cascading-failures/) · Google · ความล้มเหลวแบบลูกโซ่ (cascading failure): กระบวนการที่ backend ที่ช้ากักเธรดและทรัพยากรของชั้นหน้าไว้ แล้วเหตุขัดข้องลามออกไปจากการลองใหม่, health check ล้มเหลว และการรีสตาร์ตทั้งที่แคชว่าง พร้อมวิธีรับมือ - [Circuit Breaker Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker) · Microsoft Azure · สถานะ closed, open และ half-open ของ circuit breaker และเกณฑ์จำนวนครั้งที่ล้มเหลว ถ้าไทม์เอาต์ยาว เธรดจะถูกผูกไว้จนกว่าจะถูกตัด - [Bulkhead Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/bulkhead) · Microsoft Azure · แยกทรัพยากรตามฟีเจอร์และปลายทางที่เรียก เพื่อไม่ให้เหตุขัดข้องจุดเดียวลามไป - [Timeouts, retries, and backoff with jitter](https://d1.awsstatic.com/builderslibrary/pdfs/timeouts-retries-and-backoff-with-jitter.pdf) · AWS · บทความจาก Amazon Builders' Library: ใช้ไทม์เอาต์เพื่อปล่อยทรัพยากร จำกัดจำนวนครั้งการลองใหม่ และเพิ่ม jitter - [The Unique Architecture behind Amazon Games’ Seamless MMO New World](https://aws.amazon.com/blogs/gametech/the-unique-architecture-behind-amazon-games-seamless-mmo-new-world/) · AWS · ตัวอย่างสถาปัตยกรรมเซิร์ฟเวอร์ MMO: เซิร์ฟเวอร์ทางเข้า, เซิร์ฟเวอร์จำลองแยกตามตาราง (hub), pool ของเซิร์ฟเวอร์ที่ใช้ร่วมกันสำหรับเซสชัน, DB เก็บสถานะ - [Working with DB instance read replicas](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ReadRepl.html) · AWS · การทำสำเนาข้อมูลล่าช้าในการทดลองแผนผังสถาปัตยกรรม: read replica อัปเดตแบบ asynchronous จึงอาจอ่านได้ข้อมูลเก่า - [Site Reliability Engineering, Chapter 20: Load Balancing in the Datacenter](https://sre.google/sre-book/load-balancing-datacenter/) · Google · การ deploy และรีสตาร์ต: เปลี่ยนเส้นทางคำขอใหม่ออกไปด้วยสถานะ lame duck แล้วจึงปิด และ warm up ทันทีหลังรีสตาร์ต - [Amazon EC2 Auto Scaling lifecycle hooks](https://docs.aws.amazon.com/autoscaling/ec2/userguide/lifecycle-hooks.html) · AWS · ตอนขยายหรือลดขนาด จะพักอินสแตนซ์ไว้ในสถานะรอเพื่อทำงานเตรียมและเก็บกวาดให้เสร็จ (ค่าเริ่มต้นนานสุด 1 ชั่วโมง) - [systemd.service(5) — Linux manual page](https://man7.org/linux/man-pages/man5/systemd.service.5.html) · systemd · watchdog ในการทดลองแผนผังสถาปัตยกรรม: ปิดเซอร์วิสที่สัญญาณบอกว่ายังทำงานอยู่ขาดหายไป แล้วรีสตาร์ตอัตโนมัติ ## รูปแบบกราฟ - **พุ่งเป็นรอบ** (`periodic`): ปกติอยู่ต่ำ แล้วพุ่งขึ้นเป็นระยะเท่า ๆ กัน เช่น ทุกไม่กี่วินาที ทุกไม่กี่นาที หรือตรงต้นชั่วโมง - **พุ่งแบบสุ่มเป็นครั้งคราว** (`random`): พุ่งขึ้นอย่างไม่เป็นจังหวะ แล้วไม่นานก็กลับมาปกติ - **ขึ้นเป็นขั้นบันไดจากจุดหนึ่ง** (`step`): ขยับขึ้นไปหนึ่งขั้นแล้วอยู่ที่ระดับนั้นต่อไป โดยเริ่มจากเวลาที่ชัดเจน เช่น ตอนลงแพตช์ เปลี่ยนคอนฟิก หรือเปลี่ยนเส้นทาง - **ค่อย ๆ สูงขึ้น** (`ramp`): ไต่ขึ้นทีละนิดตลอดหลายชั่วโมงหรือหลายวัน ยิ่งเปิดไว้นานยิ่งสูง - **ค่อย ๆ ขึ้นแล้วดิ่งลง** (`sawtooth`): ไต่ขึ้นช้า ๆ แล้วดิ่งลงทันทีตอนรีสตาร์ตหรือเก็บกวาด วนซ้ำแบบนี้ - **สูงเฉพาะบางช่วงเวลา** (`peak`): นูนขึ้นเป็นเนินเฉพาะช่วงเวลาเดิมของทุกวัน เช่น ช่วงพีคหัวค่ำ - **สูงตามจำนวนคนและโหลด** (`load`): เมื่อผู้เล่นออนไลน์พร้อมกันหรือคนที่รวมตัวอยู่จุดเดียวเพิ่มขึ้น กราฟจะชันขึ้นเร็วกว่าจำนวนคนที่เพิ่ม - **ชนเพดานแล้วแบนราบ** (`ceiling`): throughput หรือจำนวนการเชื่อมต่อแตะค่าหนึ่งแล้วขึ้นต่อไม่ได้ จากนั้นการรอและ error จะเพิ่มขึ้น - **สูงตลอดตั้งแต่แรก** (`high`): ไม่พุ่งขึ้นลง แต่อยู่ที่ค่าสูงตลอด เป็นกรณีที่มาจากโครงสร้าง เช่น ระยะทาง เส้นทาง หรือการออกแบบ - **สูงเฉพาะบางกลุ่ม** (`outlier`): ส่วนใหญ่ปกติ มีเพียงผู้เล่น พื้นที่ ISP หรืออุปกรณ์บางกลุ่มที่สูงแยกออกมา - **ขาดช่วงแล้วมารวดเดียว** (`gap`): ปริมาณที่รับได้เป็น 0 อยู่ช่วงหนึ่ง แล้วไหลเข้ามาพร้อมกันในทีเดียว - **การเชื่อมต่อหลุดพร้อมกัน** (`drop`): จำนวนผู้เชื่อมต่อดิ่งลง หรือจำนวนการหลุดพุ่งขึ้นในชั่วพริบตา - **พุ่งทันทีหลังเปิดเซิร์ฟ/ปิดปรับปรุง** (`surge`): พุ่งสูงทันทีหลังเปิดเซิร์ฟเวอร์หรือเริ่มอีเวนต์ แล้วค่อย ๆ ลดลง ## ขั้นตอนตามสถานการณ์ ### แลคหลังลงแพตช์ ใช้เมื่อการแจ้งปัญหาแลคเพิ่มขึ้นตั้งแต่แพตช์หรือ deploy ครั้งใดครั้งหนึ่ง เช่น มีการแจ้งว่า “ผิดปกติตั้งแต่อัปเดตรอบนี้” เข้ามาหลายราย หรือกราฟขึ้นเป็นขั้นบันไดจากเวลาหนึ่งแล้วคงอยู่ที่ระดับนั้น 1. **ระบุเวลาเริ่มให้แน่ชัด แล้วรวบรวมการเปลี่ยนแปลงทั้งหมดก่อนและหลังเวลานั้น**: หาเวลาที่การแจ้งปัญหาเริ่มเข้ามามาก และเวลาที่กราฟขึ้นเป็นขั้นบันได แล้วจดการเปลี่ยนแปลงที่ออกไปก่อนและหลังช่วงนั้นให้ครบ ดูให้ครอบคลุมทั้งแพตช์ไคลเอนต์, การ deploy เซิร์ฟเวอร์, การเปลี่ยนคอนฟิก, การเปลี่ยนสคีมา DB (DDL) และการรีสตาร์ต, งานเครือข่ายและไฟร์วอลล์ และการเปลี่ยนอินฟรา (ประเภทอินสแตนซ์, เคอร์เนล, ไดรเวอร์) ถ้าใช้ฟีเจอร์ annotation ของเครื่องมือมอนิเตอร์ขีดเส้นแนวตั้งไว้บนทุกกราฟทุกครั้งที่ deploy ขั้นตอนนี้จะเสร็จได้เร็ว ถ้าแพตช์เกมกับงานอินฟราออกไปในรอบปิดปรับปรุงเดียวกัน ให้เก็บไว้เป็นสาเหตุที่เป็นไปได้ทั้งคู่ เรียกใครก่อน: ทั้งทีมพัฒนาเกมและทีมอินฟราที่ออกการเปลี่ยนแปลงนั้น (สาเหตุ: in-deploy, so-os-update, db-ddl-lock, db-plan-flip, db-cold-cache) 2. **แยกขอบเขต: บิลด์, อุปกรณ์, เซิร์ฟเวอร์ และพื้นที่**: ดูว่าความผิดปกติกระจุกอยู่ในมิติไหน แล้วเริ่มสงสัยตามนี้: ถ้าแย่เฉพาะผู้เล่นที่ใช้บิลด์ใหม่ คือไคลเอนต์ ถ้าแย่เฉพาะบาง OS, การ์ดจอ หรืออุปกรณ์ คือประสิทธิภาพไคลเอนต์หรือไดรเวอร์ ถ้าแย่เฉพาะบางเซิร์ฟเวอร์ แชนแนล หรือโซน คือเซิร์ฟเวอร์ ถ้าแย่เฉพาะบางประเทศหรือบาง ISP คือเส้นทางเครือข่าย ถ้าทุกคนแย่พร้อมกัน คือทรัพยากรที่ใช้ร่วมกัน (DB, โหลดบาลานเซอร์, เกตเวย์) หรือการ deploy เซิร์ฟเวอร์ที่เพิ่งออกไป ถ้า telemetry ของไคลเอนต์มีเลขบิลด์ ให้วางปิง, FPS, frame spike และจำนวนครั้งที่หลุดของบิลด์เก่ากับบิลด์ใหม่ไว้เทียบกัน ถ้าปิงเท่าเดิมแต่ FPS แย่ลงอย่างเดียว น่าจะเป็นที่ประสิทธิภาพไคลเอนต์มากกว่าเครือข่าย เรียกใครก่อน: ถ้ากระจุกตามบิลด์หรืออุปกรณ์ คือทีมพัฒนาเกม (ไคลเอนต์) ถ้ากระจุกตามเซิร์ฟเวอร์หรือแชนแนล คือทีมพัฒนาเกม (เซิร์ฟเวอร์) เมื่อเมตริกของโฮสต์ปกติ และทีมอินฟรา (เครื่องเซิร์ฟเวอร์/OS) เมื่อผิดปกติ ถ้ากระจุกตามประเทศหรือ ISP คือทีมอินฟรา (เครือข่าย) (สาเหตุ: cg-hitch, cg-sync-load, co-vram, cg-crash) 3. **เทียบเวอร์ชันใหม่กับเวอร์ชันเก่าในช่วงเวลาเดียวกัน**: ถ้าเทียบแค่ก่อนกับหลัง deploy การเปลี่ยนแปลงตามวันในสัปดาห์ ช่วงเวลา และอีเวนต์จะปนเข้ามาจนตัดสินได้ยาก ถ้าทำได้ ให้ลงเวอร์ชันใหม่บนเซิร์ฟเวอร์บางส่วน (canary) ก่อน แล้วเทียบกับเซิร์ฟเวอร์เวอร์ชันเก่าในช่วงเวลาเดียวกัน (กลุ่มควบคุม) ทั้งเวลาต่อทิก p50 และ p99, จำนวนทิกที่เกินงบ, CPU, หน่วยความจำ และอัตรา error ถ้า deploy ไปทั้งหมดแล้ว ให้เทียบกับวันเดียวกันของสัปดาห์ก่อนในช่วงเวลาเดียวกัน ถ้าดูแค่ค่าเฉลี่ยรวมของทุกเซิร์ฟเวอร์ ปัญหาในบางเซิร์ฟเวอร์หรือบางโซนจะถูกกลบ จึงต้องแยกดูตามเซิร์ฟเวอร์และโซน เรียกใครก่อน: ทีมพัฒนาเกม (เซิร์ฟเวอร์) (สาเหตุ: sp-tick-overrun, mem-alloc, mem-leak, sp-broadcast) 4. **เทียบ fingerprint ของทราฟฟิกก่อนและหลังแพตช์**: แม้ไม่รู้โค้ดเซิร์ฟเวอร์ ก็ตรวจได้จากค่าที่เห็นฝั่งเครือข่ายว่าแพตช์เปลี่ยนรูปแบบทราฟฟิกหรือไม่ ให้เทียบก่อนและหลังทั้งจำนวนแพ็กเก็ตต่อวินาทีต่อผู้เล่น (pps) และจำนวนไบต์, ขนาดแพ็กเก็ตเฉลี่ยและสูงสุด, จำนวนการเชื่อมต่อ และขนาด burst ขาออกที่ส่งออกไปพร้อมกันทุกทิก ถ้าแพ็กเก็ต UDP เริ่มใหญ่เกิน path MTU (ปกติ 1,500 ไบต์) จะเกิด IP fragmentation แค่ fragment เดียวหาย แพ็กเก็ตทั้งก้อนก็หาย และ NAT หรือไฟร์วอลล์บางตัวก็ทิ้ง fragment ไปเลย ผู้เล่นที่เส้นทางผ่านช่วงที่ MTU เล็ก (tunnel, VPN) จะเจอเฉพาะแพ็กเก็ตใหญ่ที่หายไป ถ้า pps เพิ่มขึ้น ให้ดูว่าชนขีดจำกัด PPS ของอินสแตนซ์บนคลาวด์ หรือขีดจำกัดการประมวลผลของไฟร์วอลล์และอุปกรณ์ป้องกัน DDoS หรือไม่ เรียกใครก่อน: ถ้า fingerprint เปลี่ยน ให้แนบหลักฐานส่งทีมพัฒนาเกม (เซิร์ฟเวอร์) ถ้า fingerprint เท่าเดิมแต่แพ็กเก็ตหายและการส่งซ้ำเพิ่มขึ้นอย่างเดียว คือทีมอินฟรา (เครือข่าย) (สาเหตุ: sp-patch-traffic, sk-fragment, rt-mtu, nic-cloud-pps, rt-appliance-pps, rt-burst) 5. **เทียบประเภทและจำนวนครั้งของคิวรี DB ก่อนและหลังแพตช์**: ถ้าความหน่วงของ DB สูงขึ้น ให้ดูก่อนว่าจำนวนคิวรี (QPS) สูงขึ้นด้วยหรือไม่ pg_stat_statements ของ PostgreSQL และสรุปแบบ digest ของ MySQL Performance Schema จะรวมคิวรีที่ต่างกันแค่ค่าไว้เป็นประเภทเดียว แล้วสะสมจำนวนครั้งที่รันและเวลารวมไว้ให้ เมื่อเทียบรายการคิวรีอันดับต้น ๆ ก่อนและหลังแพตช์ จะเห็นคิวรีที่เพิ่งเกิดขึ้นใหม่, คิวรีที่จำนวนครั้งเพิ่มขึ้นหลายเท่า (N+1) และคิวรีที่อ่านทั้งตารางโดยไม่ใช้อินเด็กซ์ (ใน MySQL ดูคอลัมน์ SUM_NO_INDEX_USED) เรียกใครก่อน: ถ้า QPS หรือรูปแบบคิวรีเปลี่ยน คือทีมพัฒนาเกม (เซิร์ฟเวอร์) ถ้าคิวรีเหมือนเดิมแต่ความหน่วงเพิ่มขึ้นอย่างเดียว คือทีมอินฟรา (DB: query plan, IOPS, ล็อก) (สาเหตุ: db-no-index, db-login-storm, db-plan-flip, db-cache-stampede) 6. **แยกชั้นด้วยเมตริกของโฮสต์และโปรเซสเซิร์ฟเวอร์**: ใช้ค่าที่เห็นจาก OS โดยไม่ต้องใช้โค้ด แยกว่าปัญหาอยู่ภายในตัวเซิร์ฟเวอร์หรือที่โฮสต์ ถ้า receive queue (Recv-Q) ของซ็อกเก็ตเซิร์ฟเวอร์สะสม แปลว่าโปรเซสเซิร์ฟเวอร์อ่านไม่ทัน (ทิกหยุดชะงัก, GC, ล็อก) ถ้าเธรดเดียวใช้ CPU 100% คือคอขวดที่เธรดเดียว ถ้าเวลาหยุดใน GC log เพิ่มขึ้น แปลว่ารูปแบบการใช้หน่วยความจำเปลี่ยนไป ดูด้วยว่า deploy ไปทั้งที่ยังตั้ง log level ไว้สูงจนการเขียน log เพิ่มขึ้นหรือไม่ ในทางกลับกัน ถ้า CPU steal, throttling หรือ NIC drop เพิ่มขึ้น ให้ดูอินฟราที่เปลี่ยนในเวลาเดียวกัน (ประเภทอินสแตนซ์, เคอร์เนล, ขีดจำกัดของคอนเทนเนอร์) เรียกใครก่อน: ถ้าเป็นสัญญาณจากภายในโปรเซส คือทีมพัฒนาเกม (เซิร์ฟเวอร์) ถ้าเป็นสัญญาณจากโฮสต์ คือทีมอินฟรา (เครื่องเซิร์ฟเวอร์/OS) (สาเหตุ: mem-gc, sp-hotzone, dk-sync-log, so-cpu-quota, so-steal, so-os-update) 7. **ย้อนกลับเพื่อยืนยัน แล้วบันทึกผลไว้**: ย้อนการเปลี่ยนแปลงที่น่าสงสัยที่สุดเฉพาะบางเซิร์ฟเวอร์หรือบางผู้เล่น (โรลแบ็ค, ปิด feature flag) หรือคืนคอนฟิกเป็นค่าเดิม แล้วดูว่าอาการหายไปด้วยหรือไม่ ถ้าดีขึ้นเฉพาะฝั่งที่ย้อนกลับ ก็ยืนยันสาเหตุได้ การย้อนกลับเองก็อาจทำให้ช้าไปชั่วครู่จากการรีสตาร์ตและ cold cache ถ้าไม่เร่งด่วนให้ทำในช่วงที่คนน้อย บันทึกผลพร้อม ID สาเหตุลงในบันทึกเหตุขัดข้อง และย้ายขีดจำกัดของขนาดแพ็กเก็ต, จำนวนคิวรี และเวลาต่อทิก ไปเป็นรายการตรวจก่อน deploy ของแพตช์ถัดไป เรียกใครก่อน: ทีมที่ออกการเปลี่ยนแปลงนั้น (สาเหตุ: in-deploy, db-cold-cache) ### การขยายบริการไปประเทศหรือภูมิภาคใหม่ ใช้เมื่อเปิดให้บริการในประเทศใหม่ หรือเพิ่มรีเจียนหรือดาต้าเซ็นเตอร์ใหม่ ใช้ได้ทั้งตอนตรวจก่อนเปิด และตอนแยกแยะการแจ้งปัญหาแบบ “ในเกาหลีปกติดี มีแต่ผู้เล่นประเทศใหม่ที่แลค” 1. **วัดคุณภาพเส้นทางแยกตาม ISP ท้องถิ่นก่อนเปิดบริการ**: วัดการกระจายของเวลาไปกลับ (RTT), จิตเตอร์ และแพ็กเก็ตหาย จาก ISP หลัก (ASN) แต่ละรายของประเทศเป้าหมายไปยังที่ตั้งเซิร์ฟเวอร์เกมที่เป็นตัวเลือก ค่าเฉลี่ยตัวเดียวจะกลบความต่างระหว่าง ISP จึงต้องดูค่ามัธยฐานและเปอร์เซ็นไทล์ที่ 95 แยกตาม ISP และแยกช่วงพีคหัวค่ำกับช่วงเช้ามืด เครือข่ายวัดผลสาธารณะ RIPE Atlas ให้เลือกประเทศหรือ ASN แล้วส่ง ping และ traceroute จาก probe ทั่วโลกได้ หรือจะเปิด VM ชั่วคราวในรีเจียนที่เป็นตัวเลือกเพื่อวัดก็ได้ อุปกรณ์ระหว่างทางบางตัวจำกัดการตอบ ICMP ถ้าเป็นไปได้จึงควรวัดด้วยโปรโตคอลและพอร์ตเดียวกับเกมด้วย ถ้ามีแต่ ISP บางรายที่อ้อมผ่านเมืองที่อยู่ไกลผิดปกติ คือปัญหา peering หรือเส้นทาง ISP เลือกเส้นทางโดยดูต้นทุนก่อนความหน่วง ปลายทางที่อยู่ใกล้จึงอาจวิ่งอ้อมไปไกลได้ เรียกใครก่อน: ทีมอินฟรา (เครือข่าย) ถ้าเส้นทางเป็นเรื่องฝั่ง ISP คือภายนอก (ISP/IX) (สาเหตุ: isp-distance, isp-routing, isp-peak, isp-cable) 2. **เทียบค่าที่วัดได้กับขีดจำกัดที่การออกแบบเกมรับได้**: นำ RTT และจิตเตอร์ที่วัดได้ไปเทียบกับช่วงเวลาตัดสินของเกม (เวลาตอบสนองสำหรับการหลบหรือการแพร์รี), ขีดจำกัดของ lag compensation, ความยาวของ interpolation buffer และขนาดบัฟเฟอร์อินพุต ตัวอย่างเช่น ถ้าช่วงเวลาตัดสินการแพร์รีคือ 0.2 วินาที ผู้เล่นที่ใช้ ISP ซึ่งดีเลย์ไปกลับรวมกับ interpolation buffer นานกว่านั้น จะช้าเกินไปแม้กดทันเวลา ถ้าขยาย lag compensation เพื่อชดเชย คราวนี้ฝั่งที่โดนโจมตีจะแจ้งเข้ามามากขึ้นว่า “หลบหลังกำแพงแล้วยังโดน” ถ้ามี ISP จำนวนมากที่เกินขีดจำกัด ทีมอินฟราควรพิจารณาวางรีเจียนหรือ edge PoP ให้ใกล้ขึ้น ส่วนทีมพัฒนาเกมควรทบทวนค่าการตัดสินผล, interpolation และ lag compensation บท “รูปแบบการซิงก์” ในคู่มือนี้ใช้เป็นตารางอ้างอิงได้ เรียกใครก่อน: ทีมพัฒนาเกม (เซิร์ฟเวอร์/ไคลเอนต์: ขีดจำกัดของการออกแบบ), ทีมอินฟรา (เครือข่าย: ที่ตั้งรีเจียนและ PoP) (สาเหตุ: sy-short-window, sy-no-lagcomp, sy-lagcomp-overreach, cg-no-buffer) 3. **ตรวจ MTU และดูว่า UDP ผ่านได้หรือไม่**: ตรวจว่าแพ็กเก็ตที่ใหญ่ที่สุดของเกมผ่านเครือข่ายท้องถิ่นไปได้ครบทั้งก้อนหรือไม่ ส่งปิงที่ตั้งแฟล็ก Don't Fragment (DF) ไปหลายขนาดเพื่อวัด path MTU และดูว่ามีช่วงที่เล็กกว่า 1,500 ไบต์ เช่น PPPoE, tunnel หรือเครือข่ายมือถือหรือไม่ มาตรฐานสำหรับการส่งแบบ datagram อย่าง UDP (RFC 8899) แนะนำขนาดพื้นฐานที่ผ่านเส้นทางส่วนใหญ่ได้บน IPv4 ไว้ที่ 1,200 ไบต์ ถ้าแพ็กเก็ตใหญ่สุดของเกมเกินค่านี้ ให้ตกลงกับทีมพัฒนาเกมว่าจะลดขนาดหรือแบ่งส่ง ตรวจด้วยว่า Wi-Fi สาธารณะ เครือข่ายบริษัท หรือ ISP บางรายบล็อก UDP หรือพอร์ตเกม หรือจำกัดความเร็วหรือไม่ และดูว่ามีเส้นทางสำรองไว้ใช้ตอนโดนบล็อก (TCP, พอร์ต 443) หรือไม่ เรียกใครก่อน: ทีมอินฟรา (เครือข่าย) และทีมพัฒนาเกม (เซิร์ฟเวอร์: ขนาดแพ็กเก็ต) (สาเหตุ: dc-mtu, rt-mtu, sk-fragment, isp-udp-block, hn-captive, isp-shaping) 4. **วัด idle timeout ของ NAT/CGNAT แล้วปรับช่วงห่างของ heartbeat ให้เข้ากัน**: วัดว่าเราเตอร์ตามบ้านและเครือข่ายมือถือ (CGNAT) ในประเทศนั้นลบ mapping ของการเชื่อมต่อ UDP ที่ idle ภายในเวลาเท่าไร ในการทดสอบแต่ละรอบ ให้อุปกรณ์ทดสอบส่งแพ็กเก็ตหนึ่งตัวไปยังเซิร์ฟเวอร์เพื่อสร้าง mapping จากนั้นอุปกรณ์ไม่ส่งอะไรเลย แล้วให้เซิร์ฟเวอร์ส่งแพ็กเก็ตกลับไปหาอุปกรณ์เมื่อครบเวลาที่กำหนดไว้ (30 วินาที, 60 วินาที, 120 วินาที …) เวลาที่อุปกรณ์เริ่มไม่ได้รับแพ็กเก็ตนั้นคือ idle timeout ของเครือข่ายนั้น มาตรฐาน (RFC 4787) กำหนดว่า UDP mapping ต้องไม่หมดอายุก่อน 2 นาที และแนะนำค่าเริ่มต้นตั้งแต่ 5 นาทีขึ้นไป แต่ค่าจริงต่างกันมากตามอุปกรณ์ และบางอุปกรณ์ลบเร็วกว่านั้น mapping จะต่ออายุได้แน่นอนก็ต่อเมื่อมีแพ็กเก็ตขาออกจากอุปกรณ์เท่านั้น ไคลเอนต์จึงต้องเป็นฝ่ายส่ง heartbeat และต้องดูว่าช่วงห่างนั้นไม่เกินครึ่งหนึ่งของค่าที่สั้นที่สุดในบรรดาค่าที่วัดได้ และ idle timeout ของโหลดบาลานเซอร์และ security group บนคลาวด์ เรียกใครก่อน: ทีมพัฒนาเกม (ไคลเอนต์: ช่วงห่างของ heartbeat, เซิร์ฟเวอร์: ค่าไทม์เอาต์), ทีมอินฟรา (คอนฟิกโหลดบาลานเซอร์และ security group) (สาเหตุ: hn-nat, isp-cgnat, rt-mapping, dc-lb-idle, dc-cloud-conntrack) 5. **ตรวจบริการภายนอกและอุปกรณ์ความปลอดภัยที่ทราฟฟิกในประเทศนั้นต้องผ่าน**: ตรวจว่าการล็อกอินผ่านแพลตฟอร์มท้องถิ่น, การชำระเงิน และการยืนยันตัวตนตอบกลับด้วยความเร็วปกติหรือไม่, DNS ท้องถิ่นแปลงที่อยู่ของเซิร์ฟเวอร์ล็อกอินและเซิร์ฟเวอร์แพตช์ได้ถูกต้องหรือไม่ และ CDN ส่งแพตช์จากจุดให้บริการที่อยู่ใกล้ประเทศนั้นหรือไม่ ดูว่าช่วง IP ของประเทศใหม่ไม่ติดกฎบล็อกตามประเทศหรือการจำกัดอัตราของระบบป้องกัน DDoS และไฟร์วอลล์ โดยเฉพาะช่วง IP ของ CGNAT ที่ผู้ใช้บริการหลายรายใช้ IP เดียวร่วมกัน ต้องไม่ถูกบล็อกไปทั้งก้อน เรียกใครก่อน: ทีมอินฟรา (อุปกรณ์ความปลอดภัย, DNS, CDN), ภายนอก (แพลตฟอร์ม, ผู้ให้บริการชำระเงิน, ISP) (สาเหตุ: in-external, isp-dns, dc-ddos, isp-cgnat) 6. **หลังเปิดบริการ ให้แยกดูตามประเทศและ ASN**: ใส่ประเทศและ ASN ให้ IP ของไคลเอนต์ใน log การเชื่อมต่อและ log ของโหลดบาลานเซอร์ แล้วดู RTT, การส่งซ้ำ, จำนวนครั้งที่หลุด และเหตุผลที่หลุด (heartbeat ไทม์เอาต์, RST, เซิร์ฟเวอร์เตะออก) แยกตามประเทศและ ISP ฐานข้อมูลฟรีอย่าง MaxMind GeoLite ASN แปลง IP เป็น ASN และชื่อองค์กรได้ และให้เก็บ IP แบบย่อเป็นระดับ /24 หรือ ASN ตามกฎข้อมูลส่วนบุคคลของประเทศนั้น ถ้ากระจุกอยู่ที่ ASN เดียว ให้ดูเส้นทางของ ISP นั้น (ทีมอินฟรา, ภายนอก) ถ้าแย่ทั้งประเทศใหม่ ให้ดูระยะทางและขีดจำกัดของการออกแบบ (ทีมอินฟรา, ทีมพัฒนาเกม) ถ้าแย่เฉพาะช่วงหัวค่ำ ให้ดู peering ที่แออัดก่อน ถ้าผู้เล่นบางส่วนปิงสูงตลอด ให้ดูร่วมกับทีมพัฒนาเกม (เซิร์ฟเวอร์) ว่าถูกจัดไปรีเจียนที่ไกลเพราะ GeoIP ผิด, VPN หรือการจัดรีเจียนตามหัวปาร์ตี้หรือไม่ ถ้า synthetic monitoring ปกติแต่ค่าที่ผู้เล่นจริงเจอแย่ แปลว่าเป็นที่สภาพแวดล้อมของผู้เล่นหรือฝั่งไคลเอนต์ (สาเหตุ: isp-peak, isp-routing, rt-queue-drop, pt-isp-validation, in-region-match) 7. **ตรวจผลกระทบที่ผู้เล่นจากพื้นที่ไกลมีต่อผู้เล่นคนอื่น**: เมื่อผู้เล่นที่เชื่อมต่อจากที่ไกลมีมากขึ้น ผลกระทบไม่ได้หยุดอยู่แค่หน้าจอของคนนั้น อินพุตของคนที่ช้ามาถึงเป็นก้อน ตัวละครของคนนั้นจึงขยับแบบกรอเร็วบนหน้าจอคนอื่น และติดการตรวจความเร็วหรือคูลดาวน์ของเซิร์ฟเวอร์ จนเกิดอาการดีดกลับหรือสกิลถูกปฏิเสธ ในกิมมิคที่ต้องเล่นเป็นปาร์ตี้ การตอบสนองที่ช้าของคนเดียวทำให้ทั้งปาร์ตี้ล้มเหลว และในแบบ lockstep ทุกคนต้องรอคนที่ช้าที่สุด หลังเปิดประเทศใหม่ ให้ดูว่าผู้เล่นเดิมแจ้งปัญหาแบบ “เห็นตัวละครบางตัวผิดปกติ” มากขึ้นหรือไม่ แล้วตกลงกับทีมพัฒนาเกมเรื่องบัฟเฟอร์อินพุต, ค่าที่ยอมให้คลาดเคลื่อนในการตรวจ และการแยกพื้นที่ในการจับคู่ เรียกใครก่อน: ทีมพัฒนาเกม (เซิร์ฟเวอร์) (สาเหตุ: pt-slow-burst, pt-isp-validation, pt-raid-member, sy-lockstep) ## กรณีเหตุขัดข้องจริง ### eve-hedgp-2014 · CCP Games 2014: เซิร์ฟเวอร์ EVE Online โหลดเกินในศึกกองยานขนาดใหญ่ที่ HED-GP - เกิดอะไรขึ้น: ในศึกกองยานขนาดใหญ่ที่ระบบดาว HED-GP ซึ่งบทวิเคราะห์ย้อนหลังเมื่อมกราคม 2014 กล่าวถึง เซิร์ฟเวอร์โหลดเกินอย่างหนัก Time Dilation (ฟีเจอร์ที่ทำให้เวลาในเกมช้าลงเมื่อโหลดเกิน) ลดลงไปแตะขีดต่ำสุดที่ 10% จนทั้งสนามรบกลายเป็นสโลว์โมชั่น แต่หลังจากนั้นโหลดก็ยังสะสมต่อไป ความล่าช้าของงานที่ประมวลผลการหยุดและการทำงานซ้ำของโมดูล (Dogma Lateness) สูงสุดถึง 193 วินาทีในเวลาเกม หรือประมาณ 32 นาทีในเวลาจริง ส่วนศึก 6VDT เมื่อกรกฎาคม 2013 ซึ่งมีขนาดใกล้เคียงกัน สูงสุดอยู่ที่ 42 วินาที (เวลาจริงประมาณ 7 นาที) - สาเหตุ: CCP ออกตัวไว้ก่อนว่ายังไม่แน่ใจ เพราะเครื่องมือวิเคราะห์ประสิทธิภาพก็เพิ่มโหลดในตัวเอง จึงไม่ได้รันในสถานการณ์แบบนี้ แล้วชี้สาเหตุที่น่าจะเป็นไว้สองอย่าง อย่างแรกคือโหลดที่ประมวลผลไม่ทันสะสมต่อเนื่องเมื่อการรบยืดเยื้อ อย่างที่สองคือการใช้โดรนที่เพิ่มขึ้น จำนวนโดรนที่ถูกปล่อยออกมาระหว่างการรบ (ไม่นับซ้ำ) อยู่ที่ 21,123 ลำใน 6VDT และ 38,852 ลำใน HED-GP มากกว่ากัน 84% การส่งข้อมูลที่ต้องแจ้งการกระทำของคนหนึ่งให้ทุกคนที่มองเห็นรู้ จะเพิ่มขึ้นตามกำลังสองของจำนวนคน (O(n²)) และโดรนสร้างข้อความต่อการโจมตีหนึ่งครั้งมากกว่า นอกจากนี้โค้ดที่โดรนใช้เลือกเป้าหมายก็มักไล่ดูเป้าหมายที่โจมตีได้ทั้งหมดในสนามรบเดียวกัน ต้นทุนจึงเพิ่มขึ้นเกือบเป็น n² - บทเรียน: เมื่อปริมาณงานในพื้นที่เดียวที่คนแห่มารวมกันเกินขีดจำกัด ทั้งพื้นที่นั้นจะเป็นสโลว์โมชั่น และยิ่งการรบยืดเยื้อ งานที่ประมวลผลไม่ทันก็ยิ่งสะสมจนอินพุตดีเลย์มากขึ้น สัญญาณที่ใช้ยืนยันคือเวลาต่อทิก, ปริมาณงานที่กองรอ, จำนวนผู้เล่น และจำนวนเอนทิตีของเซิร์ฟเวอร์ (โหนด) ที่ดูแลพื้นที่นั้น โดยมีจุดสังเกตคือพื้นที่อื่นยังปกติ ผู้รับผิดชอบหลักคือทีมพัฒนาเกม (เซิร์ฟเวอร์) จุดที่ต้องแก้คือขอบเขตของผู้รับที่ต้องแจ้งการกระทำหนึ่งครั้ง และต้นทุนการค้นหาเป้าหมายของ AI การออกแบบที่ทำให้เวลาในเกมช้าลงกำจัดการโหลดเกินไม่ได้ แต่ทำให้ทุกคนช้าลงในอัตราเดียวกัน จึงป้องกันไม่ให้การกระทำบางอย่างถูกเลื่อนออกไปไม่รู้จบ - สาเหตุที่เกี่ยวข้อง: sp-broadcast, sp-tick-overrun, sp-queue, sp-hotzone - ต้นฉบับ: [CCP Games](https://www.eveonline.com/news/view/what-a-hed-ache) ### riot-direct-2015 · Riot Games 2015: ทราฟฟิก League of Legends ที่วิ่งอ้อมไปไกล และ Riot Direct - เกิดอะไรขึ้น: บทความเทคนิคที่ Riot Games อธิบายว่าทำไมอินเทอร์เน็ตจึงไม่เหมาะกับเกมแบบเรียลไทม์ ทราฟฟิกจริงที่ผู้เล่น League of Legends แจ้งเข้ามาควรวิ่งจากซานฟรานซิสโกไปพอร์ตแลนด์โดยตรง แต่อ้อมผ่านลอสแอนเจลิส, เดนเวอร์ และซีแอตเทิล จึงใช้เวลา 70 ms ทั้งที่ถ้าไปตรงใช้แค่ 14 ms Riot อธิบายว่าเมื่อเราเตอร์ล้นจนแพ็กเก็ตถูกทิ้ง จะเห็นแชมเปี้ยนตัวอื่นกระโดดไปมาบนหน้าจอ และ projectile ดูเหมือนวาร์ป - สาเหตุ: Riot ชี้ว่าต้นเหตุคือเส้นทางและเราเตอร์ ผู้ให้บริการแบ็กโบนและ ISP ให้ความสำคัญกับต้นทุนก่อนความหน่วง จึงส่งทราฟฟิกตามเส้นทางที่ถูกที่สุด และเมื่อเส้นทางที่ BGP เลือกอ้อมไปไกล จำนวนเราเตอร์ที่ต้องผ่านก็เพิ่มขึ้น เราเตอร์รับภาระประมวลผลตามจำนวนแพ็กเก็ตโดยไม่ขึ้นกับขนาด แพ็กเก็ตเกมมีขนาดราว 55 ไบต์ ถ้าปริมาณข้อมูลเท่ากัน จำนวนแพ็กเก็ตจะมากกว่าแพ็กเก็ต 1,500 ไบต์ถึง 27 เท่า และเติมบัฟเฟอร์ขาเข้าของเราเตอร์ได้เร็วขึ้นตามไปด้วย Riot อธิบายว่าเราเตอร์จำนวนมากเลือกทิ้งแพ็กเก็ต UDP ก่อนเมื่อโหลดเกิน ทางแก้ของ Riot คือสร้างเครือข่ายของตัวเองชื่อ Riot Direct โดยวางเราเตอร์ไว้ที่ศูนย์กลางอินเทอร์เน็ตขนาดใหญ่ 10 แห่งในสหรัฐฯ และเชื่อมต่อตรง (peering) กับ ISP ให้ได้มากที่สุด ตามบทความตอนที่ 2 สัดส่วนผู้เล่นที่เล่นด้วยปิงต่ำกว่า 80 ms เพิ่มจาก 31% เป็น 50% ในเวลา 9 เดือนเศษ และหลังย้ายเซิร์ฟเวอร์เกมไปชิคาโก ก็กลายเป็น 80% ภายในคืนเดียว - บทเรียน: ถ้าในประเทศเดียวกัน มีแต่ผู้เล่นที่ใช้ ISP บางรายที่ปิงสูงผิดปกติ ให้สงสัยเส้นทาง สัญญาณที่ใช้ยืนยันคือการกระจายของ RTT แยกตาม ISP (ASN) และเมืองที่ทราฟฟิกผ่านซึ่งเห็นใน traceroute ผู้รับผิดชอบหลักคือทีมอินฟรา (เครือข่าย) แก้ด้วยการทำ peering ตรงกับ ISP, การเชื่อมต่อ IX และการเลือกที่ตั้งเซิร์ฟเวอร์ ส่วนนโยบายเส้นทางฝั่ง ISP ต้องประสานกับภายนอก (ISP) กรณีนี้ยังแสดงให้เห็นว่าแค่ย้ายเซิร์ฟเวอร์ไปใกล้จุดศูนย์กลางของกลุ่มผู้เล่นก็ได้ผลมาก - สาเหตุที่เกี่ยวข้อง: isp-routing, isp-distance, rt-queue-drop - ต้นฉบับ: [Riot Games](https://www.riotgames.com/en/news/fixing-internet-real-time-applications-part-i) ### riot-edge-2020 · Riot Games 2020: โฮสต์ edge ของเซิร์ฟเวอร์ League of Legends ยุโรปและบราซิลโหลดเกิน - เกิดอะไรขึ้น: ปลายเดือนกุมภาพันธ์ 2020 เซิร์ฟเวอร์ EUW, EUNE และ BR ของ League of Legends ขัดข้องหลายครั้ง จำนวนเกมที่เริ่มใหม่ลดลงอย่างมาก บริการ backend อย่างระบบจับคู่และเซิร์ฟเวอร์เกมมีสถานะปกติทั้งหมด แต่แทบไม่มีทราฟฟิกเข้ามา Riot เลื่อนกำหนดการโหมดทัวร์นาเมนต์ (Clash) ออกไปหนึ่งสัปดาห์ เพื่อไม่ให้เปิดบนคลัสเตอร์ที่อาจไม่เสถียร บทวิเคราะห์ย้อนหลังไม่ได้ระบุว่าเหตุขัดข้องแต่ละครั้งกินเวลานานเท่าไร - สาเหตุ: มีสามเรื่องเกิดซ้อนกัน คำขอที่ส่งไปยังบริการหนึ่งถูกสร้างผิดรูปแบบ ในบางกรณีจึงล้มเหลวและถูกลองใหม่ไม่หยุด จนปริมาณคำขอพุ่งสูง ปัญหาความเข้ากันได้ที่รู้กันอยู่แล้วระหว่างระบบคอนเทนเนอร์กับเวอร์ชัน OS ทำให้หน่วยความจำภายใน OS รั่วอยู่ การอัปเกรดเสร็จแค่ประมาณ 60% ของสภาพแวดล้อมคอนเทนเนอร์ทั้งหมดของ Riot และคลัสเตอร์ยุโรปกับละตินอเมริกายังอยู่ระหว่างดำเนินการ คอนเทนเนอร์ edge ที่รับทราฟฟิกจากอินเทอร์เน็ต กรองแล้วส่งต่อไปยัง backend ถูกจัดวางให้อยู่แยกกันภายในชาร์ด (กลุ่มเซิร์ฟเวอร์) เดียวกัน แต่ไม่มีกลไกกันไว้ระหว่างต่างชาร์ด ทุกครั้งที่ขัดข้อง คอนเทนเนอร์ edge ของอย่างน้อยสามชาร์ดจึงไปกระจุกอยู่บนโฮสต์เครื่องเดียว การลองใหม่ที่พุ่งสูงมาซ้อนทับที่โฮสต์นั้น และหน่วยความจำรั่วก็ทำให้โฮสต์นั้นหยุดทำงาน - บทเรียน: ถ้าบริการ backend ทุกตัวตอบว่า “ปกติดี แต่ไม่มีทราฟฟิกเข้ามา” ให้ดูส่วนที่อยู่ด้านหน้า (edge, เกตเวย์, โหลดบาลานเซอร์) สัญญาณที่ใช้ยืนยันคือจำนวนการเชื่อมต่อขาเข้าแยกตามโฮสต์ว่ากระจุกอยู่ที่บางเครื่องหรือไม่ และอัตราความล้มเหลวและการลองใหม่ของคำขอบางประเภท ผู้รับผิดชอบหลักคือทีมพัฒนาเกม (เซิร์ฟเวอร์: คำขอที่ผิดรูปแบบและวิธีลองใหม่) ส่วนกฎการจัดวางคอนเทนเนอร์, การอัปเกรด OS และ alert เมื่อโหลดกระจุก เป็นหน้าที่ของทีมอินฟรา (เครื่องเซิร์ฟเวอร์/OS) Riot แก้โค้ดของคำขอ ปรับไม่ให้การลองใหม่พุ่งขึ้นฉับพลัน และตั้ง alert เมื่อโหลดกระจุกไว้จนกว่าจะพัฒนาการกระจายข้ามชาร์ดเสร็จ - สาเหตุที่เกี่ยวข้อง: in-gateway, in-cascade - ต้นฉบับ: [Riot Games](https://www.leagueoflegends.com/en-gb/news/riot-games/incident-report-recent-outages-in-europe-brazil/) ### riot-euw-2021 · Riot Games 2021: League of Legends EUW ขัดข้อง 5 ชั่วโมง: DB เสริมตัวเดียวทำให้ทั้งเซิร์ฟเวอร์หยุด - เกิดอะไรขึ้น: วันที่ 22 มกราคม 2021 เซิร์ฟเวอร์ EUW ของ League of Legends ทำงานผิดปกตินานกว่า 5 ชั่วโมงเล็กน้อย เมตริกจำนวนผู้เล่นที่ล็อกอินอยู่และจำนวนผู้เล่นที่อยู่ในเกมขาดหายไปพร้อมกัน และในช่วงระหว่างการรีสตาร์ตสองครั้ง แม้จำนวนคนล็อกอินจะเพิ่มขึ้น แต่แทบไม่มีเกมไหนเริ่มได้เลย - สาเหตุ: เซิร์ฟเวอร์หลักของ DB ที่ดูแลฟีเจอร์ที่ไม่สำคัญเกิดฮาร์ดแวร์เสีย และ DB นั้นไม่ได้ตั้งค่า failover อัตโนมัติไปยังเซิร์ฟเวอร์สำรอง แม้ connection pool จะแยกกันตาม DB แต่ทุก pool ใช้ thread pool เดียวกัน งานที่ส่งไปยัง DB ที่เสียไม่จบและถือครองเธรดไว้ จนเธรดที่ทั้งระบบต้องใช้หมด ท่ามกลาง alert ที่ถาโถมเข้ามา ทีมสงสัยการโจมตีเครือข่ายแบบมุ่งร้ายที่เพิ่งเจอเมื่อไม่นานมานี้และงานฮาร์ดแวร์ในภูมิภาคอื่นก่อน กว่าจะเห็น alert ของ DB ที่เสียก็ผ่านไปราว 1 ชั่วโมง ระบบทั้งหมดรันอยู่ใน JVM เดียว เมื่อ GC หยุดโปรเซสครั้งละหลายวินาทีท่ามกลางโหลดจากการเชื่อมต่อใหม่หลังรีสตาร์ต การเก็บเมตริกก็ขาดช่วงไปมาก คิวล็อกอินก็ไม่ได้จำกัดตามค่าที่ตั้งไว้ คนจึงไหลเข้ามาไม่สม่ำเสมอ - บทเรียน: แม้แต่ DB เสริมตัวเดียวที่คิดว่าไม่สำคัญ ก็ทำให้ทั้งระบบหยุดได้ผ่านทรัพยากรที่ใช้ร่วมกันอย่าง thread pool สัญญาณที่ใช้ยืนยันคือจำนวนคำขอที่รออยู่แยกตาม DB, อัตราการใช้งาน thread pool และสัดส่วนจำนวนเกมที่เริ่มได้ซึ่งต่ำเกินไปเมื่อเทียบกับจำนวนการล็อกอิน ผู้รับผิดชอบคือทีมพัฒนาเกม (เซิร์ฟเวอร์: แยก thread pool และตั้งไทม์เอาต์) และทีมอินฟรา (DB: failover อัตโนมัติ) เมื่อ alert ถาโถมเข้ามา มักสงสัยปัญหาที่เพิ่งเจอ (เช่น การโจมตี) ก่อน จึงควรตัดทีละอย่างตามลำดับการชี้สาเหตุ (ขอบเขต → ช่วงเวลา → ชั้น) หลังรีสตาร์ตให้ดูด้วยว่าคิวล็อกอินจำกัดคนที่เข้ามาตามค่าที่ตั้งไว้หรือไม่ - สาเหตุที่เกี่ยวข้อง: sp-threadpool, db-failover, in-cascade, mem-gc - ต้นฉบับ: [Riot Games](https://www.riotgames.com/en/news/keeping-legacy-software-alive-case-study) ### roblox-2021 · Roblox 2021: Roblox ล่ม 73 ชั่วโมง: การแย่งทรัพยากรในคลัสเตอร์ service discovery (Consul) - เกิดอะไรขึ้น: เหตุเริ่มในช่วงบ่ายวันที่ 28 ตุลาคม 2021 (เวลาแปซิฟิก) จาก CPU โหลดสูงบนเซิร์ฟเวอร์ Consul เครื่องหนึ่ง เวลา 16:35 จำนวนผู้เล่นที่ออนไลน์อยู่ลดลงเหลือครึ่งหนึ่งของปกติ แล้วบริการทั้งหมดก็หยุด ผู้เล่นทุกคนกลับเข้ามาได้อีกครั้งก็ต่อเมื่อ 16:45 วันที่ 31 ตุลาคม รวมเวลา 73 ชั่วโมงนับจากเริ่มเกิดเหตุ Roblox ระบุว่ามีผู้ใช้งาน 50 ล้านคนทุกวัน - สาเหตุ: Roblox ใช้ HashiCorp Consul ทำ service discovery (ให้บริการต่าง ๆ หาที่อยู่ของกันและกัน), health check และ KV store โดยคลัสเตอร์ Consul ชุดเดียวรับหลายเวิร์กโหลดร่วมกัน ต้นเหตุมีสองอย่าง อย่างแรก ฟีเจอร์ streaming ตัวใหม่ของ Consul ที่ทยอยขยายการใช้งานมาหลายเดือน ถูกเปิดใช้กับบริการ routing ทราฟฟิกด้วยในวันก่อนเกิดเหตุ พร้อมเพิ่มจำนวนโหนดของบริการนั้นอีก 50% ภายใต้โหลดที่ทั้งอ่านและเขียนสูงมาก ฟีเจอร์นี้ทำให้เกิดการแย่งทรัพยากรที่ใช้ร่วมกันตัวหนึ่ง (Go channel) และเซิร์ฟเวอร์แบบ dual socket (NUMA) ที่มีคอร์มากกว่าซึ่งเปลี่ยนเข้าไประหว่างเกิดเหตุ ยิ่งทำให้การแย่งรุนแรงขึ้น อย่างที่สอง การจัดการรายการหน้าว่าง (freelist) ของ BoltDB ที่ Consul ใช้เก็บ Raft log ช้าลงอย่างผิดปกติ ทุกครั้งที่เพิ่มข้อมูลไม่เกิน 16 kB จะเขียนลงดิสก์ 7.8 MB ค่ามัธยฐานของความหน่วงในการเขียน KV ซึ่งปกติต่ำกว่า 300 ms กลายเป็น 2 วินาที และบนเซิร์ฟเวอร์ leader ที่ช้ายังพบ zero window ที่บัฟเฟอร์ TCP เต็มด้วย ระบบ telemetry พึ่ง Consul อยู่ เมตริกที่ต้องใช้หาสาเหตุจึงหายไปด้วย - บทเรียน: เมื่อระบบพื้นฐานที่หลายบริการพึ่งพาร่วมกัน (service discovery, ที่เก็บคอนฟิก, ระบบยืนยันตัวตน) ช้าลง ทุกฟีเจอร์จะหยุดพร้อมกัน สัญญาณที่ใช้ยืนยันคือความหน่วงในการเขียน, การเปลี่ยน leader และ CPU ของระบบนั้น รวมถึงการเปลี่ยนคอนฟิกก่อนเกิดเหตุ ผู้รับผิดชอบมีทั้งทีมพัฒนาเกม (เซิร์ฟเวอร์) และทีมอินฟรา (เครื่องเซิร์ฟเวอร์/OS) ต้องแยกระบบมอนิเตอร์ไม่ให้พึ่งระบบที่ถูกเฝ้าดู จึงจะยังเห็นเมตริกได้ระหว่างเกิดเหตุ ตอนกู้คืน แคชยังว่างอยู่ ถ้ารับคนเข้ามาพร้อมกันทั้งหมดระบบอาจล่มซ้ำ Roblox จึงใช้ DNS คุมสัดส่วนผู้เล่นที่ให้เข้ามา และเพิ่มทีละประมาณ 10% - สาเหตุที่เกี่ยวข้อง: in-cascade, sp-lock, rt-zero-window, db-cold-cache - ต้นฉบับ: [Roblox](https://about.roblox.com/newsroom/2022/01/roblox-return-to-service-10-28-10-31-2021) ### ffxiv-2021 · Square Enix 2021: FINAL FANTASY XIV แออัดช่วงเปิดตัวภาคเสริม และ error ในคิวล็อกอิน - เกิดอะไรขึ้น: ตั้งแต่ช่วง early access ของภาคเสริม Endwalker ในเดือนธันวาคม 2021 ทุกเวิลด์แออัดอย่างหนัก คิวล็อกอินยาวขึ้น และมักเกิด Error 2002 ตอนล็อกอินจากหน้าเลือกตัวละครหรือระหว่างรอในคิว นอกจากนี้ยังมีบางเวิลด์และบางโซนล่ม (Error 3001) และคิวไทม์เอาต์ (Error 4004) ด้วย ณ วันที่ประกาศ 11 ธันวาคม ซึ่งเป็นวันที่ 8 ของ early access ความแออัดก็ยังไม่หมดไป - สาเหตุ: Error 2002 เกิดได้สองกรณี กรณีแรกคือเมื่อจำนวนคนรอในแต่ละ logical data center เกิน 17,000 คน ซึ่งเป็นเพดานที่ตั้งไว้เพื่อกันไม่ให้คิวยาวจนเซิร์ฟเวอร์ล็อกอินล่ม ในกรณีนี้ไคลเอนต์จะถูกปิดไปเลย วันที่ 7 ธันวาคม มีการนำเครื่องสำรองสำหรับงานพัฒนามาเสริมเซิร์ฟเวอร์ล็อบบี้เพื่อยกเพดานขึ้น error นี้จึงลดลง แต่คิวกลับยาวขึ้น กรณีที่สองคือเมื่อเน็ตของผู้เล่นที่กำลังรอไม่เสถียร พอรอนานขึ้น การเชื่อมต่อที่ขาดไปชั่วครู่เพราะแพ็กเก็ตหายบนเส้นทางอินเทอร์เน็ตหรือ Wi-Fi ไม่เสถียรก็เกิดบ่อยขึ้น เซิร์ฟเวอร์ล็อบบี้จะรอการเชื่อมต่อใหม่ให้ราวหลายสิบวินาทีถึง 1 นาที ถ้าเชื่อมต่อกลับมาได้ในช่วงนั้นจะได้ต่อคิวจากตำแหน่งเดิม แต่ถ้าเกินจะต้องไปต่อท้ายคิว Square Enix ระบุว่าการแจ้งปัญหาส่วนใหญ่เป็นกรณีนี้ นอกจากนี้ยังเพิ่มเวิลด์ทันทีไม่ได้เพราะขาดแคลนเซมิคอนดักเตอร์ - บทเรียน: ยิ่งคิวยาว เน็ตที่ขาดช่วงสั้น ๆ ของผู้เล่นที่กำลังรอก็ยิ่งกลายเป็น error ในการเชื่อมต่อ แม้ความแออัดจะเท่ากัน error ก็ไปกระจุกที่คนที่ใช้ Wi-Fi หรือเน็ตไม่เสถียร จนกลายเป็นปัญหาที่ “เกิดกับบางคนเท่านั้น” สัญญาณที่ใช้ยืนยันคือความยาวคิว, เวลารอ และสัดส่วนการหลุดระหว่างรอคิวเมื่อแยกตามเหตุผลที่หลุด ผู้รับผิดชอบหลักคือทีมพัฒนาเกม (เซิร์ฟเวอร์: เพดานคิวและช่วงผ่อนผันการเชื่อมต่อใหม่) ส่วนการเพิ่มเซิร์ฟเวอร์ล็อบบี้และเซิร์ฟเวอร์เวิลด์ ทีมอินฟราทำร่วมกัน ถ้าตั้งช่วงผ่อนผันการเชื่อมต่อใหม่ให้นานพอ จะลดโอกาสที่เน็ตขาดช่วงสั้น ๆ ของผู้เล่นลุกลามจนเสียลำดับคิว - สาเหตุที่เกี่ยวข้อง: in-login-queue, hn-wifi, rt-wireless - ต้นฉบับ: [Square Enix](https://na.finalfantasyxiv.com/lodestone/news/detail/6a94b30182b6d963994fdc0b789264ac9f24986f) ### cloudflare-2020 · Cloudflare 2020: คอนฟิกแบ็กโบนของ Cloudflare ผิดพลาด ทำให้ทราฟฟิกบางเมืองหายไป - เกิดอะไรขึ้น: เป็นเหตุขัดข้องด้านอินฟราประเภทที่เกมได้รับผลกระทบไปด้วย เพราะเกมจำนวนมากฝากเว็บ, API และการป้องกัน DDoS ไว้กับผู้ให้บริการ CDN วันที่ 17 กรกฎาคม 2020 ตั้งแต่ 21:12 ถึง 21:39 (UTC) รวม 27 นาที ทราฟฟิกรวมของเครือข่าย Cloudflare ลดลงประมาณ 50% ผลกระทบจำกัดอยู่ที่จุดให้บริการในบางเมืองของสหรัฐฯ, ยุโรป, รัสเซีย และบราซิลที่เชื่อมกับแบ็กโบน ส่วนจุดอื่นยังปกติ - สาเหตุ: ช่วงแบ็กโบนนิวอาร์ก–ชิคาโกขัดข้อง ทำให้ช่วงแอตแลนตา–วอชิงตันแออัด วิศวกรจึงแก้คอนฟิกเราเตอร์เพื่อลดทราฟฟิกแบ็กโบนที่แอตแลนตา สิ่งที่ต้องทำคือปิดทั้งรายการนโยบาย (term) แต่กลับปิดแค่เงื่อนไขข้างใน (prefix-list) เราเตอร์แอตแลนตาจึงประกาศเส้นทาง BGP ทั้งหมดด้วยลำดับความสำคัญที่สูงกว่า (local-preference 200) ไปทั่วแบ็กโบน ขณะที่แต่ละจุดให้บริการตั้งลำดับความสำคัญของเส้นทางไปยังเซิร์ฟเวอร์ของตัวเองไว้ที่ 100 ทราฟฟิกของทุกจุดที่เชื่อมกับแบ็กโบนจึงแห่ไปที่แอตแลนตา แอตแลนตาโหลดเกิน ส่วนจุดที่ได้รับผลกระทบแทบไม่เหลือทราฟฟิกให้ประมวลผล เมื่อถอดเราเตอร์แอตแลนตาออกจากแบ็กโบน ระบบก็กลับมาปกติ Cloudflare ระบุว่าเหตุนี้ไม่เกี่ยวกับการโจมตีหรือการเจาะระบบ - บทเรียน: ถ้าผู้เล่นในบางเมืองหรือบางพื้นที่เท่านั้นเจออาการหลุดหรือเข้าเกมไม่ได้/โหลดไม่จบพร้อมกัน ขณะที่ที่อื่นปกติ ให้สงสัยการเปลี่ยนคอนฟิกเส้นทางที่เพิ่งทำไปก่อน บนกราฟ CPU และทราฟฟิกจะพุ่งที่จุดเดียว ส่วนจุดที่ได้รับผลกระทบกลับดิ่งลงเกือบ 0 ผู้รับผิดชอบหลักคือทีมอินฟรา (เครือข่าย) ถ้าเป็นเหตุขัดข้องฝั่งผู้ให้บริการก็เป็นภายนอก Cloudflare ตัดสินใจตั้งเพดานจำนวนเส้นทางที่รับได้ (maximum-prefix) บนเซสชัน BGP ของแบ็กโบน และปรับลำดับความสำคัญไม่ให้จุดหนึ่งดึงทราฟฟิกของจุดอื่นไปได้ - สาเหตุที่เกี่ยวข้อง: isp-bgp, rt-path - ต้นฉบับ: [Cloudflare](https://blog.cloudflare.com/cloudflare-outage-on-july-17-2020/) ### fastly-2021 · Fastly 2021: Fastly CDN เกิด error ทั่วโลก - เกิดอะไรขึ้น: เป็นเหตุขัดข้องด้านอินฟราประเภทที่เกมได้รับผลกระทบไปด้วย เพราะเกมจำนวนมากส่งไฟล์แพตช์, launcher และหน้าเว็บผ่าน CDN วันที่ 8 มิถุนายน 2021 ตั้งแต่ 09:47 (UTC) เครือข่ายของ Fastly 85% ตอบกลับเป็น error ภายใน 49 นาที เครือข่าย 95% กลับมาปกติ และเหตุขัดข้องคลี่คลายเมื่อ 12:35 - สาเหตุ: ซอฟต์แวร์ที่เริ่ม deploy เมื่อ 12 พฤษภาคม มีบั๊กที่จะทำงานเมื่อคอนฟิกบางแบบของลูกค้าเจอเงื่อนไขบางอย่าง วันที่ 8 มิถุนายน ลูกค้ารายหนึ่งส่งการเปลี่ยนคอนฟิกที่ถูกต้องตามปกติขึ้นไป ทำให้เงื่อนไขนั้นครบพอดี Fastly ตรวจพบความผิดปกติภายใน 1 นาที และเมื่อหาคอนฟิกของลูกค้าที่เป็นต้นเหตุเจอแล้วปิดไป ระบบก็เริ่มกลับมา การ deploy ตัวแก้บั๊กเริ่มในวันเดียวกันเวลา 17:25 - บทเรียน: แม้โค้ดที่ deploy ไปหลายสัปดาห์แล้ว ถ้าเจอเงื่อนไขที่เกิดยาก ก็กลายเป็นเหตุขัดข้องทั่วโลกได้ในพริบตา สัญญาณที่ใช้ยืนยันฝั่งเกมคืออัตรา HTTP error ของคำขอแพตช์, launcher และเว็บที่สูงขึ้นพร้อมกันทุกภูมิภาค และหน้าสถานะของผู้ให้บริการ CDN จุดสังเกตคือ ถ้าการเชื่อมต่อเกมที่เข้าอยู่แล้วไม่ผ่าน CDN ก็ยังปกติ มีแค่การเชื่อมต่อใหม่, การดาวน์โหลดแพตช์ และการล็อกอินผ่านเว็บที่ใช้ไม่ได้ ผู้รับผิดชอบหลักคือภายนอก (ผู้ให้บริการ CDN) ส่วนทีมพัฒนาเกมและทีมอินฟราควรเตรียมเส้นทางสำรองไว้ เช่น ใช้ CDN ตั้งแต่สองเจ้าขึ้นไป หรือดึงตรงจากเซิร์ฟเวอร์ต้นทาง - สาเหตุที่เกี่ยวข้อง: in-external - ต้นฉบับ: [Fastly](https://www.fastly.com/blog/summary-of-june-8-outage) ### meta-2021 · Meta 2021: คำสั่งเดียวบนแบ็กโบนของ Facebook ทำให้ DNS หายไปด้วย - เกิดอะไรขึ้น: เป็นเหตุขัดข้องด้านอินฟราที่ใช้เป็นบทเรียนกับเครือข่ายและ DNS ของบริษัทเกมเองได้ตรง ๆ วันที่ 4 ตุลาคม 2021 บริการของ Facebook (ปัจจุบันคือ Meta) เข้าใช้งานไม่ได้ทั่วโลก แบ็กโบนที่เชื่อมดาต้าเซ็นเตอร์ขาดทั้งหมด และฝั่งอินเทอร์เน็ตหาเซิร์ฟเวอร์ DNS ของ Facebook ไม่เจอ บทวิเคราะห์ย้อนหลังไม่ได้ระบุว่าเหตุขัดข้องกินเวลานานเท่าไร - สาเหตุ: ระหว่างงานบำรุงรักษาตามรอบ คำสั่งที่รันเพื่อตรวจความจุของแบ็กโบนทั่วโลกกลับตัดการเชื่อมต่อทั้งหมดของแบ็กโบนโดยไม่ได้ตั้งใจ และเครื่องมือ audit ที่ควรกันคำสั่งแบบนี้ก็กันไม่ได้เพราะมีบั๊ก เซิร์ฟเวอร์ DNS ในจุดให้บริการขนาดเล็กถูกออกแบบให้ถือว่าตัวเองผิดปกติและถอนการประกาศเส้นทาง BGP เองเมื่อสื่อสารกับดาต้าเซ็นเตอร์ไม่ได้ จึงเข้าถึงจากอินเทอร์เน็ตไม่ได้ทั้งที่เซิร์ฟเวอร์ DNS ยังทำงานอยู่ ทั้งเส้นทางเข้าถึงปกติและการเข้าถึงแบบ out-of-band ขาดหมด เครื่องมือภายในก็ใช้ DNS ไม่ได้ จึงต้องส่งวิศวกรไปที่ดาต้าเซ็นเตอร์ด้วยตัวเอง และขั้นตอนด้านความปลอดภัยทำให้ใช้เวลานานขึ้นอีก ตอนกู้คืน การใช้ไฟฟ้าของแต่ละดาต้าเซ็นเตอร์ลดลงไปหลายสิบ MW ทีมประเมินว่าถ้าคืนโหลดทั้งหมดพร้อมกัน อาจเสี่ยงตั้งแต่ระบบไฟฟ้าไปจนถึงแคช จึงค่อย ๆ เพิ่มโหลดทีละขั้น - บทเรียน: ถ้าเกิดอาการเข้าเกมไม่ได้/โหลดไม่จบพร้อมกันในทุกพื้นที่และทุก ISP ให้ดู DNS และเส้นทาง BGP ก่อนเซิร์ฟเวอร์เกม ตรวจจากนอกบริษัทได้ด้วยการคิวรี DNS จากภายนอกและข้อมูลเส้นทาง BGP ที่เปิดเผยสาธารณะ ผู้รับผิดชอบหลักคือทีมอินฟรา (เครือข่าย) ควรตรวจล่วงหน้าว่าเส้นทางเข้าถึงแบบ out-of-band ที่จะใช้ตอนเกิดเหตุและเครื่องมือภายในไม่ได้พึ่ง DNS และเครือข่ายชุดเดียวกัน และตอนกู้คืนให้ค่อย ๆ เพิ่มโหลดทีละขั้นเพื่อไม่ให้การเชื่อมต่อใหม่ทะลักเข้ามาพร้อมกัน - สาเหตุที่เกี่ยวข้อง: isp-bgp, isp-dns - ต้นฉบับ: [Meta](https://engineering.fb.com/2021/10/05/networking-traffic/outage-details/) ### aws-2021 · AWS 2021: เครือข่ายภายในของ AWS us-east-1 แออัด - เกิดอะไรขึ้น: เป็นเหตุขัดข้องด้านอินฟราประเภทที่เกมได้รับผลกระทบไปด้วย เพราะเกมจำนวนมากวางเซิร์ฟเวอร์, ระบบล็อกอิน และข้อมูลไว้บนคลาวด์สาธารณะ วันที่ 7 ธันวาคม 2021 เวลา 7:30 (เวลามาตรฐานแปซิฟิก) เครือข่ายภายในของรีเจียนเวอร์จิเนียเหนือ (us-east-1) เริ่มแออัด ตั้งแต่ 7:33 error และความหน่วงของ EC2 API เพิ่มขึ้นจนเปิดอินสแตนซ์ใหม่ได้ยาก (การเปิดอินสแตนซ์กลับมาได้เมื่อ 14:40) ตามมาด้วยล็อกอินคอนโซลไม่สำเร็จ, เปลี่ยนคอนฟิก Route 53 ไม่ได้ และเมตริก CloudWatch ล่าช้าและหายไปบางส่วน อุปกรณ์เครือข่ายกลับมาปกติเต็มที่เมื่อ 14:22 อินสแตนซ์ EC2 ที่รันอยู่แล้วและการตอบ DNS ที่มีอยู่เดิมไม่ได้รับผลกระทบ - สาเหตุ: งานอัตโนมัติที่เพิ่มความจุของบริการหนึ่งบนเครือข่ายหลัก ทำให้ไคลเอนต์จำนวนมากบนเครือข่ายภายในทำงานแบบที่ไม่คาดคิด จนความพยายามเชื่อมต่อพุ่งสูง อุปกรณ์ที่เชื่อมเครือข่ายภายในกับเครือข่ายหลักรับไม่ไหว การสื่อสารจึงล่าช้า และความล่าช้านั้นก็ยิ่งเพิ่มความพยายามเชื่อมต่อและการลองใหม่ ความแออัดจึงลากยาว ไคลเอนต์มีกลไก backoff ที่เพิ่มช่วงห่างระหว่างคำขอเมื่อแออัดแบบนี้ แต่ข้อบกพร่องที่แฝงอยู่ทำให้กลไกนั้นทำงานไม่ถูกต้อง ระบบมอนิเตอร์ภายในก็พึ่งเครือข่ายเดียวกัน ทีมปฏิบัติการจึงไม่มีเมตริกแบบเรียลไทม์ให้ดู ต้องอาศัย log ในการรับมือ - บทเรียน: ถ้าการลองใหม่ไม่ยืดช่วงห่างออกไป ความแออัดสั้น ๆ ก็กลายเป็นเหตุขัดข้องที่ยาวหลายชั่วโมง มองจากฝั่งเกม แม้เซิร์ฟเวอร์เกมที่รันอยู่แล้วจะปกติ แต่การเพิ่มเซิร์ฟเวอร์ใหม่ (autoscaling), ล็อกอิน, การจับคู่ และการชำระเงินที่ใช้ API ของคลาวด์ รวมถึงระบบมอนิเตอร์ ก็อาจใช้ไม่ได้ไปพร้อมกัน สัญญาณที่ใช้ยืนยันคือหน้าสถานะของผู้ให้บริการคลาวด์, อัตรา error ของ API คลาวด์ และการเปิดอินสแตนซ์ที่ล้มเหลว ผู้รับผิดชอบหลักคือภายนอก (ผู้ให้บริการคลาวด์) ทีมพัฒนาเกมควรใส่ exponential backoff ที่สุ่มช่วงห่างและจำกัดจำนวนครั้งให้การลองใหม่ทุกจุด ส่วนทีมอินฟราควรเตรียมความจุสำรองที่พอรับมือได้แม้เพิ่มเครื่องไม่ได้ และทางเลือกในรีเจียนอื่น - สาเหตุที่เกี่ยวข้อง: in-cascade, in-autoscale, in-external - ต้นฉบับ: [AWS](https://aws.amazon.com/message/12721/) ### cloudflare-dns-2025 · Cloudflare 2025: DNS สาธารณะ 1.1.1.1 ของ Cloudflare ขัดข้อง - เกิดอะไรขึ้น: เป็นเหตุขัดข้องของ DNS resolver สาธารณะที่ผู้เล่นตั้งค่าเองในอุปกรณ์หรือเราเตอร์ เป็นประเภทที่ทุกเกมและทุกบริการใช้ไม่ได้พร้อมกันเฉพาะกับผู้เล่นที่ใช้ค่านั้น วันที่ 14 กรกฎาคม 2025 ตั้งแต่ 21:52 ถึง 22:54 (UTC) รวม 62 นาที resolver 1.1.1.1 ไม่ตอบกลับทั่วโลก Cloudflare ระบุว่าสำหรับผู้ใช้จำนวนมาก นี่หมายถึงแทบใช้บริการอินเทอร์เน็ตอะไรไม่ได้เลย การคิวรีผ่าน UDP, TCP และ DNS over TLS ได้รับผลกระทบ ส่วน DNS over HTTPS ซึ่งเชื่อมต่อด้วยชื่อโดเมนค่อนข้างเสถียร - สาเหตุ: วันที่ 6 มิถุนายน ระหว่างเตรียม service topology (คอนฟิกที่กำหนดว่าจะประกาศช่วง IP จากจุดให้บริการใด) ของบริการอื่นที่จะใช้ในอนาคต ช่วง IP ของ resolver 1.1.1.1 ถูกผูกรวมเข้าไปในคอนฟิกนั้นโดยไม่ตั้งใจ วันที่ 14 กรกฎาคม เมื่อมีการเปลี่ยนคอนฟิกของบริการนั้น จุดที่ประกาศช่วง IP ของ resolver ก็ลดจากทุกจุดเหลือเพียงจุดเดียวซึ่งออฟไลน์อยู่ เส้นทาง BGP จึงถูกถอนทั่วโลก การเปลี่ยนแปลงนี้กระจายไปทุกดาต้าเซ็นเตอร์ทันทีโดยไม่ผ่าน canary deploy เมื่อย้อนคอนฟิกกลับเวลา 22:20 ทราฟฟิกกลับมาราว 77% แต่ระหว่างนั้นคอนฟิก IP ที่จำเป็นบนเซิร์ฟเวอร์ edge ราว 23% ถูกลบไป ต้องตั้งค่าใหม่ จึงกลับมาปกติเมื่อ 22:54 Cloudflare ระบุว่าเป็นความผิดพลาดของคอนฟิกภายใน และไม่เกี่ยวกับการโจมตีหรือ BGP hijacking - บทเรียน: ถ้าเซิร์ฟเวอร์เกมและผู้เล่นคนอื่นปกติ แต่ผู้เล่นบางส่วนเท่านั้นเจออาการเข้าเกมไม่ได้/โหลดไม่จบกับเซิร์ฟเวอร์ล็อกอินหรือเซิร์ฟเวอร์แพตช์ ให้สงสัย DNS ที่ผู้เล่นกลุ่มนั้นใช้ จุดสังเกตคือเซสชันที่เชื่อมต่ออยู่แล้วยังอยู่ได้ มีแค่การเชื่อมต่อใหม่ที่ล้มเหลว ให้ผู้เล่นลองเปลี่ยนการตั้งค่า DNS หรือคิวรีที่อยู่เซิร์ฟเวอร์เองดู ก็แยกได้ทันที ผู้รับผิดชอบหลักคือภายนอก (ผู้ดูแล DNS/ISP) ถ้าทีมพัฒนาเกม (ไคลเอนต์) แยกข้อความแจ้งกรณีแปลงชื่อเป็น IP ไม่สำเร็จออกจาก error อื่น ฝ่ายบริการลูกค้าก็ชี้สาเหตุได้ทันที - สาเหตุที่เกี่ยวข้อง: isp-dns, isp-bgp - ต้นฉบับ: [Cloudflare](https://blog.cloudflare.com/cloudflare-1-1-1-1-incident-on-july-14-2025/) ### aws-2025 · AWS 2025: DNS ของ DynamoDB ใน AWS us-east-1 ขัดข้อง และการกู้คืนที่ยืดเยื้อ - เกิดอะไรขึ้น: เป็นเหตุขัดข้องด้านอินฟราประเภทที่เกมได้รับผลกระทบไปด้วย เพราะเกมจำนวนมากวางเซิร์ฟเวอร์, ระบบล็อกอิน และข้อมูลไว้บนคลาวด์สาธารณะ ตั้งแต่ 23:48 วันที่ 19 ตุลาคม 2025 ถึง 14:20 วันที่ 20 (เวลาออมแสงแปซิฟิก) รีเจียนเวอร์จิเนียเหนือได้รับผลกระทบต่อเนื่องเป็นสามช่วง error ของ DynamoDB API เพิ่มขึ้นจนถึง 2:40 วันที่ 20, การเปิดอินสแตนซ์ EC2 ใหม่ล้มเหลวตั้งแต่ 2:25 ถึง 10:36 (ปัญหาการเชื่อมต่อของอินสแตนซ์ใหม่บางตัวคลี่คลายเมื่อ 13:50) และ error การเชื่อมต่อของ Network Load Balancer (NLB) บางตัวเพิ่มขึ้นตั้งแต่ 5:30 ถึง 14:09 - สาเหตุ: ระบบอัตโนมัติที่จัดการ DNS ของ DynamoDB มี race condition แฝงอยู่ ในบรรดาตัวรันที่นำแผน DNS ไปใช้ (DNS Enactor) ซึ่งทำงานอยู่ใน availability zone ต่างกัน มีตัวหนึ่งที่ช้าผิดปกติแล้วเขียนแผนเก่าทับแผนใหม่ ทันทีหลังจากนั้น งานเก็บกวาดของตัวรันอีกตัวก็ลบแผนเก่านั้นทิ้ง DNS record ของ endpoint ระดับรีเจียน (dynamodb.us-east-1.amazonaws.com) จึงกลายเป็นค่าว่าง ระบบอัตโนมัติแก้เองไม่ได้ ต้องให้คนกู้คืนด้วยมือ ระบบจัดการเซิร์ฟเวอร์จริง (physical server) ของ EC2 พึ่ง DynamoDB อยู่ ระหว่างนั้น lease ที่ถือไว้กับเซิร์ฟเวอร์จริงแต่ละเครื่องจึงหมดอายุ หลัง DynamoDB กลับมา เซิร์ฟเวอร์จริงมีมากเกินไป งานต่อ lease ใหม่จึงไทม์เอาต์ก่อนเสร็จ และงานที่ต้องลองใหม่ก็สะสมขึ้นอีก จนตกอยู่ในสภาพ “ล่มเพราะความแออัด (congestive collapse)” การตั้งค่าเครือข่ายของอินสแตนซ์ที่เพิ่งเปิดกระจายไปช้า health check ของ NLB จึงสลับไปมาระหว่างสำเร็จกับล้มเหลว และแม้แต่โหนดที่ปกติก็ถูกถอดออกจาก DNS แล้วกลับเข้ามาซ้ำไปมา - บทเรียน: DNS record ที่ผิดพลาดเพียงจุดเดียวลุกลามไปยังบริการอื่นที่พึ่งบริการนั้น และแม้ต้นเหตุจะคลี่คลายแล้ว งานที่กองรออยู่กับ health check ที่แกว่งไปมาก็ทำให้การกู้คืนใช้เวลาอีกหลายชั่วโมง มองจากฝั่งเกม เซิร์ฟเวอร์ที่รันอยู่แล้วอาจยังไหว แต่เปิดเซิร์ฟเวอร์ใหม่ไม่ได้ autoscaling จึงหยุด และเมื่อ health check แกว่ง โหลดบาลานเซอร์ก็อาจถอดเซิร์ฟเวอร์ที่ปกติออก สัญญาณที่ใช้ยืนยันคือหน้าสถานะของคลาวด์, อัตรา error ของ API บริการแบบ managed, การเปิดอินสแตนซ์ที่ล้มเหลว และจำนวน target ที่ปกติของโหลดบาลานเซอร์ ผู้รับผิดชอบหลักคือภายนอก (ผู้ให้บริการคลาวด์) ส่วนทีมอินฟราควรจำกัดจำนวนเซิร์ฟเวอร์ที่ถูกถอดออกพร้อมกันเพราะ health check ล้มเหลว และเตรียมทางเลือกในรีเจียนอื่น - สาเหตุที่เกี่ยวข้อง: in-external, in-cascade, in-autoscale, dc-lb-imbalance, isp-dns - ต้นฉบับ: [AWS](https://aws.amazon.com/message/101925/) ## คำศัพท์ - **ปิง** (Ping, RTT): เวลาที่สัญญาณจากเราเดินทางไปถึงเซิร์ฟเวอร์แล้วกลับมา (ไปกลับ) ปิงที่เกมแสดงบางครั้งก็รวมเวลารอประมวลผลบนเซิร์ฟเวอร์ไว้ด้วย - **ความหน่วง** (Latency): เวลาตั้งแต่แพ็กเก็ตออกเดินทางจนถึงปลายทาง มักหมายถึงทางเดียว จึงประมาณครึ่งหนึ่งของปิง - **จิตเตอร์** (Jitter): ความไม่สม่ำเสมอของช่วงเวลาที่แพ็กเก็ตมาถึง ต่อให้ปิงเฉลี่ยเท่ากัน ถ้าจิตเตอร์สูง ภาพก็จะกระตุก - **แพ็กเก็ต** (Packet): ก้อนข้อมูลที่ส่งผ่านเครือข่ายในครั้งเดียว ปกติใหญ่สุด 1,500 ไบต์ ส่วนอัปเดตของเกมมีขนาดหลักสิบถึงหลักร้อยไบต์ - **แพ็กเก็ตหาย** (Packet loss): แพ็กเก็ตที่ส่งออกไปแล้วหายระหว่างทาง ไปไม่ถึงปลายทาง เกมที่ใช้ TCP แค่ 1% ก็รู้สึกได้ว่าหยุดแวบทุกไม่กี่วินาทีถึงสิบกว่าวินาที ส่วนเกม UDP ที่มี interpolation และส่งอินพุตซ้ำ อาจกลบได้ถึงระดับไม่กี่เปอร์เซ็นต์ - **แบนด์วิดท์** (Bandwidth): ปริมาณข้อมูลสูงสุดที่ลิงก์ส่งได้ใน 1 วินาที (Mbps) เป็นคนละเรื่องกับว่าข้อมูลไปถึงเร็วแค่ไหน (ความหน่วง) - **ทิก** (Tick): หน่วยที่เซิร์ฟเวอร์คำนวณสถานะเกมหนึ่งรอบ เซิร์ฟเวอร์ 20 ทิกคำนวณ 20 ครั้งใน 1 วินาที หรือทุก 50 ms - **ทิกเรต** (Tick rate): จำนวนทิกใน 1 วินาที ยิ่งสูงยิ่งตอบสนองเร็ว แต่ค่าเซิร์ฟเวอร์และปริมาณข้อมูลที่ส่งก็เพิ่มขึ้น บางเกมตั้งความถี่ในการส่งแพ็กเก็ตให้ต่ำกว่าทิกเรตเพื่อประหยัดปริมาณข้อมูล - **งบเวลาต่อทิก** (Tick budget): เวลาสูงสุดที่ต้องทำหนึ่งทิกให้เสร็จ ถ้าเกิน ทิกถัดไปจะช้าลงและช่วงห่างระหว่างทิกจะยืดออก - **FPS** (Frames per second): จำนวนครั้งที่วาดภาพใน 1 วินาที ที่ 60 FPS หนึ่งเฟรมใช้เวลา 16.7 ms - **เฟรมไทม์** (Frame time): เวลาที่ใช้วาดหนึ่งเฟรม เฟรมที่พุ่งเป็นบางครั้งส่งผลต่อความรู้สึกมากกว่า FPS เฉลี่ย - **สแนปช็อต** (Snapshot): สรุป “สถานะเกมตอนนี้” ที่เซิร์ฟเวอร์ส่งมาทุกทิก มีตำแหน่ง, HP, สถานะ ฯลฯ ส่วนใหญ่จะคัดส่งเฉพาะส่วนที่ต่างจากสถานะที่ฝั่งรับมีอยู่แล้ว (delta compression) - **อินเทอร์โพเลชัน** (Interpolation): เทคนิคที่วาดเชื่อมระหว่างสแนปช็อตสองอันที่ได้รับ ให้ภาพดูลื่น แลกกับการแสดงภาพย้อนหลังไปเล็กน้อย - **บัฟเฟอร์อินเทอร์โพเลชัน** (Interpolation buffer): เวลาที่จงใจวาดภาพช้าลงเพื่อทำ interpolation เป็นเวลาเผื่อสำหรับดูดซับจิตเตอร์และแพ็กเก็ตหายหนึ่งสองตัว ปกติเป็น 2 เท่าของระยะห่างระหว่างแพ็กเก็ต (ถ้ารับ 20 ครั้งใน 1 วินาทีคือ 100 ms) บางเกมจะเพิ่มค่านี้เองเมื่อจิตเตอร์สูงขึ้น - **เอ็กซ์ทราโพเลชัน** (Extrapolation, Dead reckoning): เทคนิคที่เมื่อไม่มีแพ็กเก็ตใหม่ จะใช้ความเร็วล่าสุดคาดเดาว่าวัตถุจะไปอยู่ตรงไหนแล้ววาดตามนั้น ถ้าเดาผิดจะดูเหมือนวาร์ป เกมจำนวนมากจึงเดาไว้แค่ราว 0.25 วินาทีแล้วหยุด (ค่าเริ่มต้นของ Source Engine คือ 0.25 วินาที) - **การคาดการณ์ฝั่งไคลเอนต์** (Client-side prediction): เทคนิคที่ขยับตัวละครของเราให้เห็นก่อน โดยไม่รอเซิร์ฟเวอร์ยืนยัน - **การแก้ตำแหน่งตามเซิร์ฟเวอร์** (Reconciliation): เมื่อผลจากเซิร์ฟเวอร์มาถึง จะเทียบกับที่คาดการณ์ไว้แล้วแก้ตำแหน่งตัวละครของเราให้ถูก โดยเริ่มจากตำแหน่งที่เซิร์ฟเวอร์ยืนยันแล้ว และนำอินพุตที่ยังไม่ได้รับการยืนยันมาคำนวณซ้ำ ถ้าต่างกันมากจะเห็นเป็นอาการดีดกลับ - **การชดเชยแลค** (Lag compensation): เทคนิคที่เซิร์ฟเวอร์ย้อนเวลากลับไปยังจังหวะที่ผู้โจมตีเห็นอยู่ แล้วค่อยตรวจว่าโดนหรือไม่ มีการจำกัดระยะย้อนสูงสุดไว้ เพื่อไม่ให้ฝ่ายที่โดนยิงรู้สึกไม่ยุติธรรม เกมยิงแข่งขันมักตั้งไว้ราว 0.2–0.25 วินาที และบางกรณีย้อนได้ถึง 1 วินาทีเหมือนค่าเริ่มต้นของ Source Engine - **เซิร์ฟเวอร์ผู้ตัดสิน** (Authoritative server): การออกแบบให้เซิร์ฟเวอร์เป็นผู้ตัดสินผลสุดท้ายแต่ผู้เดียว ช่วยกันการโกงได้ แต่ทุกผลลัพธ์ต้องรอไปกลับเซิร์ฟเวอร์ จึงต้องใช้ prediction และการแสดงผลล่วงหน้ากลบเวลารอ - **ล็อกสเต็ป** (Deterministic lockstep): วิธีที่ทุกคนส่งกันแค่อินพุต แล้วคำนวณเหมือนกันทุกประการในเทิร์นเดียวกัน อินพุตจะมีดีเลย์คงที่ติดมาด้วย และถ้าอินพุตของใครสักคนมาช้า ทุกคนต้องรอ - **บัฟเฟอร์อินพุตฝั่งเซิร์ฟเวอร์** (Server-side input buffer): บัฟเฟอร์ที่เซิร์ฟเวอร์เก็บอินพุตของแต่ละคนไว้เล็กน้อย แล้วดึงมาใช้ทีละหนึ่งอินพุตต่อทิก คนที่จิตเตอร์สูงก็ดูลื่นในสายตาคนอื่น แต่การกระทำของคนนั้นจะถูกยืนยันบนเซิร์ฟเวอร์ช้าลงตามไปด้วย - **ลิสเซินเซิร์ฟเวอร์** (Listen server): วิธีที่ PC ของผู้เล่นคนหนึ่งทั้งเล่นเกมและทำหน้าที่เป็นเซิร์ฟเวอร์ไปพร้อมกัน หัวห้องจะมีปิงเป็น 0 แต่ถ้าเน็ตหรือ PC ของหัวห้องช้า ทุกคนจะแลคไปด้วย - **เฟสซิง** (Phasing): ฟีเจอร์ที่แสดง NPC และภูมิประเทศต่างกันตามความคืบหน้าของเควสต์ แม้จะอยู่ที่เดียวกัน ถ้าความคืบหน้าของสองตัวละครต่างกัน การที่ฝั่งหนึ่งไม่เห็น NPC ถือเป็นเรื่องปกติ - **โรลแบ็คเน็ตโค้ด** (Rollback netcode (GGPO)): วิธีที่คาดเดาอินพุตของอีกฝ่ายแล้วเดินเกมไปก่อน ถ้าอินพุตจริงไม่ตรงกับที่เดา จะย้อนกลับไปยังเฟรมในอดีตแล้วคำนวณใหม่ นิยมใช้ในเกมต่อสู้ คำนี้เป็นคนละความหมายกับการโรลแบ็คของ DB - **การกดล่วงหน้า** (Input buffer, spell queue): การรับอินพุตถัดไปที่กดไว้ก่อนคูลดาวน์หรือท่าจะจบเล็กน้อย แล้วสั่งให้ทำงานทันทีที่จบ ช่วยไม่ให้มีเวลาไปกลับแทรกอยู่ระหว่างคอมโบ - **การแสดงผลล่วงหน้า** (Client-side feedback): การเล่นแอนิเมชัน เสียง และเอฟเฟกต์ไปก่อนโดยไม่รอเซิร์ฟเวอร์ยืนยัน เฉพาะผลที่ต้องยืนยัน เช่น ดาเมจหรือของรางวัล จึงรอคำตอบจากเซิร์ฟเวอร์ ถ้าเซิร์ฟเวอร์ปฏิเสธ ต้องย้อนสิ่งที่แสดงไปแล้วกลับคืน - **TCP** (Transmission Control Protocol): โปรโตคอลที่ส่งข้อมูลให้ครบและตามลำดับ ถ้าแพ็กเก็ตหาย จะไม่ส่งแพ็กเก็ตถัดไปให้เกมจนกว่าจะได้แพ็กเก็ตที่หายกลับมา - **UDP** (User Datagram Protocol): โปรโตคอลที่ส่งตามที่ได้รับโดยไม่รับประกันอะไรเลย ไม่ต้องรอ แต่เกมต้องจัดการเรื่องแพ็กเก็ตหายและลำดับเอง - **UDP แบบเชื่อถือได้** (Reliable UDP (KCP, ENet…)): วิธีที่เขียนการส่งซ้ำและการรับประกันลำดับเองบน UDP เฉพาะเท่าที่จำเป็น - **HOL blocking** (Head-of-line blocking): ปรากฏการณ์ที่ตัวหน้าสุดติดอยู่ ทำให้ทุกตัวที่ตามหลังต้องรอ เป็นต้นเหตุของอาการกรอเร็วใน TCP - **RTO** (Retransmission timeout): ตัวจับเวลาส่งซ้ำ คือเวลาที่ TCP รอก่อนจะตัดสินว่าแพ็กเก็ตหายแล้วส่งใหม่ Linux ใช้ปิง + 200 ms ขึ้นไป และเพิ่มเป็นสองเท่าทุกครั้งที่ล้มเหลว - **อัลกอริทึม Nagle** (Nagle’s algorithm): ฟีเจอร์ของ TCP ที่เก็บข้อมูลเล็ก ๆ ไว้จนกว่า ACK ของข้อมูลก่อนหน้าจะมาถึง แล้วส่งรวดเดียวเพื่อลดจำนวนแพ็กเก็ต ในเกมส่วนใหญ่ควรปิด - **TCP_NODELAY** (TCP_NODELAY): socket option สำหรับปิดอัลกอริทึม Nagle ข้อความเล็ก ๆ จะถูกส่งทันที - **ACK แบบหน่วงเวลา** (Delayed ACK): ฟีเจอร์ที่ส่งการยืนยันว่าได้รับข้อมูลช้าลงเล็กน้อย โดยรวมไปกับข้อมูลอื่น Linux ปกติใช้ 40 ms (สูงสุด 200 ms) ส่วน Windows รุ่นเก่าใช้ 200 ms และรุ่นใหม่ใช้ 40 ms - **บัฟเฟอร์ซ็อกเก็ต** (SO_SNDBUF / SO_RCVBUF): ขนาดพื้นที่รอส่งและรอรับที่ OS กันไว้ให้แต่ละซ็อกเก็ต เล็กเกินไปจะล้น ใหญ่เกินไปข้อมูลเก่าจะกองรออยู่ - **keepalive** (SO_KEEPALIVE): ฟีเจอร์ของ TCP ที่ตรวจว่าการเชื่อมต่อที่ idle อยู่ยังใช้ได้หรือไม่ ค่าเริ่มต้นปิดอยู่ และต่อให้เปิด ค่าเริ่มต้นก็จะตรวจหลังผ่านไป 2 ชั่วโมง - **RST** (TCP reset): สัญญาณของ TCP ที่ตัดการเชื่อมต่อทันที ข้อมูลที่ยังส่งไม่ออกจะถูกทิ้ง - **ฮาร์ตบีต** (Heartbeat): สัญญาณ “ยังอยู่” ที่เกมส่งเองเป็นระยะ ใช้ตรวจจับการเชื่อมต่อที่ขาดไปแล้ว และรักษาการเชื่อมต่อในอุปกรณ์ระหว่างทางไว้ - **ไทม์เอาต์** (Timeout): เกณฑ์ที่ถือว่าล้มเหลวถ้าไม่มีการตอบกลับภายในเวลานี้ สั้นเกินไปจะตัดสินผิด ยาวเกินไปจะตรวจพบช้า - **NAT** (Network Address Translation): ฟีเจอร์ที่เราเตอร์ใช้พาอุปกรณ์หลายเครื่องในบ้านออกอินเทอร์เน็ตด้วย public IP เดียว และบันทึกการเชื่อมต่อไว้ในตาราง NAT - **CGNAT** (Carrier-grade NAT): NAT ขนาดใหญ่ที่ ISP ใช้แบ่ง IP เดียวให้ลูกค้าหลายราย - **MTU** (Maximum Transmission Unit): ขนาดแพ็กเก็ตใหญ่สุดที่ส่งได้ในครั้งเดียว ปกติ 1,500 ไบต์ และจะเล็กกว่านั้นในช่วงที่ผ่าน VPN หรือ PPPoE - **บัฟเฟอร์โบลต** (Bufferbloat): ปรากฏการณ์ที่อุปกรณ์เก็บคิวไว้ยาวเกินไป จนความหน่วงเพิ่มเป็นหลายร้อย ms - **SQM** (Smart Queue Management (fq_codel, CAKE)): ฟีเจอร์ของเราเตอร์ที่รักษาคิวให้สั้นและปล่อยข้อมูลแต่ละ flow อย่างเป็นธรรม เป็นวิธีแก้ bufferbloat - **QoS** (Quality of Service): ฟีเจอร์ที่ให้ลำดับความสำคัญกับทราฟฟิกสำคัญเพื่อให้ส่งออกไปก่อน - **พีริง** (Peering): จุดที่ ISP เชื่อมเครือข่ายของกันและกัน มักแออัดช่วงหัวค่ำ - **BGP** (Border Gateway Protocol): กติกาที่ ISP ใช้บอกกันว่าจะส่งข้อมูลไปทางเส้นทางไหนบนอินเทอร์เน็ต ถ้าเปลี่ยน เส้นทางและปิงก็เปลี่ยนตาม - **DDoS** (Distributed Denial of Service): การโจมตีที่ส่งทราฟฟิกปริมาณมหาศาลจากหลายแหล่งเพื่อทำให้บริการใช้การไม่ได้ - **สครับบิงเซ็นเตอร์** (DDoS scrubbing center): จุดให้บริการของผู้ให้บริการป้องกัน DDoS ที่รับทราฟฟิกซึ่งมุ่งไปยังเซิร์ฟเวอร์ไว้ก่อนระหว่างถูกโจมตี กรองการโจมตีออก แล้วส่งต่อเฉพาะทราฟฟิกปกติ ถ้าจุดนี้อยู่ไกล เส้นทางก็จะยาวขึ้น - **ไฟร์วอลล์** (Firewall): อุปกรณ์หรือโปรแกรมที่ยอมให้เฉพาะการเชื่อมต่อที่อนุญาตผ่านไปได้ และติดตามการเชื่อมต่อด้วยตารางเซสชัน - **โหลดบาลานเซอร์** (Load balancer): อุปกรณ์ที่กระจายการเชื่อมต่อขาเข้าไปยังเซิร์ฟเวอร์หลายเครื่อง - **ตารางเซสชัน** (Session table, conntrack): ตารางที่อุปกรณ์หรือ OS ใช้ติดตามการเชื่อมต่อที่มีอยู่ในขณะนั้น ขนาดมีขีดจำกัด - **ไมโครเบิสต์** (Microburst): ปรากฏการณ์ที่ค่าเฉลี่ยต่ำ แต่ทราฟฟิกกระจุกตัวในช่วงสั้นมาก ๆ ไม่ถึง 1 ms - **NIC** (Network Interface Card): การ์ดเครือข่ายของเซิร์ฟเวอร์ - **ริงบัฟเฟอร์** (Ring buffer): บัฟเฟอร์ที่เก็บแพ็กเก็ตที่ NIC รับมาไว้จนกว่า CPU จะดึงไปประมวลผล ใช้สล็อตจำนวนคงที่วนซ้ำไปเรื่อย ๆ เมื่อสล็อตเต็มหมด แพ็กเก็ตใหม่จะถูกทิ้ง - **อินเทอร์รัปต์** (Interrupt): สัญญาณที่อุปกรณ์ใช้บอก CPU ว่า “มีงานเข้ามาแล้ว” - **RSS** (Receive Side Scaling): ฟีเจอร์ของ NIC ที่กระจายแพ็กเก็ตที่ได้รับไปยังหลาย receive queue เพื่อให้ CPU หลายคอร์ช่วยกันประมวลผล - **PPS** (Packets per second): จำนวนแพ็กเก็ตต่อวินาที เซิร์ฟเวอร์เกมมักชนขีดจำกัดของตัวเลขนี้ก่อนแบนด์วิดท์ - **เคอร์เนล** (Kernel): แกนกลางของระบบปฏิบัติการ ดูแลเครือข่าย หน่วยความจำ และการแบ่ง CPU - **backlog** (Listen backlog): คิวที่คำขอเชื่อมต่อใหม่รออยู่ระหว่างที่เซิร์ฟเวอร์ยังไม่รับไป เมื่อคิวเต็ม Linux จะทิ้งคำขอใหม่เงียบ ๆ ส่วน Windows จะส่งการตอบกลับว่าปฏิเสธ - **TIME_WAIT** (TIME_WAIT): สถานะที่ฝั่งซึ่งปิดการเชื่อมต่อก่อนจะเก็บคู่พอร์ตนั้นไว้ชั่วครู่ (Linux 60 วินาที) เผื่อแพ็กเก็ตที่มาถึงช้า - **CPU steal** (Steal time): เวลาที่ VM อยากใช้ CPU แต่ต้องรอ เพราะเครื่องจริงกำลังให้ CPU กับ VM ตัวอื่น ดูได้จากค่า st ใน top - **CPU throttling** (CFS throttling): กลไกที่บังคับให้คอนเทนเนอร์หยุดจนถึงรอบถัดไป เมื่อใช้ CPU ครบโควตา (quota) ภายในรอบเวลาที่กำหนด (CFS period ปกติ 100 ms) - **ไฟล์ดิสคริปเตอร์** (File descriptor): หมายเลข (fd) ที่ติดกับไฟล์หรือการเชื่อมต่อแต่ละตัวที่โปรเซสเปิดไว้ จำนวนมีขีดจำกัด - **เธรด** (Thread): หน่วยงานย่อยที่ทำงานอย่างอิสระภายในโปรแกรม หลายเธรดทำงานพร้อมกันได้ - **การสลับคอนเท็กซ์** (Context switch): การที่ CPU สลับจากเธรดที่กำลังทำงานไปเป็นอีกเธรดหนึ่ง มีต้นทุน - **ล็อก** (Lock, Mutex): กลไกที่กันไม่ให้หลายเธรดใช้ข้อมูลที่ใช้ร่วมกันพร้อมกัน ให้ใช้ได้ทีละเธรด - **เดดล็อก** (Deadlock): สภาพที่เธรดต่าง ๆ รอล็อกที่อีกฝ่ายถืออยู่ จนหยุดนิ่งไปตลอดกาล - **เธรดพูล** (Thread pool): กลุ่ม worker thread ที่สร้างเตรียมไว้ล่วงหน้า ถ้าทุกตัวไม่ว่าง งานใหม่ต้องรอ - **I/O แบบอะซิงโครนัส** (epoll, IOCP, io_uring): วิธีที่ไม่รอ I/O แต่ไปทำงานอื่นก่อน แล้วรับแจ้งเตือนเมื่อ I/O เสร็จ - **AOI** (Area of Interest): “ระยะที่มองเห็นได้” ของผู้เล่นแต่ละคน ส่งเฉพาะความเปลี่ยนแปลงภายในระยะนี้เพื่อลดปริมาณข้อมูล เพื่อลดต้นทุนการหาว่าใครอยู่ในระยะบ้าง ปกติจะแบ่งแมพเป็นตาราง (grid) แล้วดูเฉพาะเซลล์ที่อยู่ใกล้ - **บรอดแคสต์** (Broadcast, fan-out): การส่งความเปลี่ยนแปลงหนึ่งอย่างไปให้ทุกคนที่มองเห็นได้ ถ้าคนที่รวมตัวกันต่างมองเห็นกันหมด ปริมาณที่ต้องส่งจะเพิ่มตามกำลังสองของจำนวนคน - **GC** (Garbage collection): ฟีเจอร์ที่เก็บคืนหน่วยความจำที่ใช้แล้วทิ้งโดยอัตโนมัติ ระหว่าง GC โปรแกรมอาจหยุดทำงาน - **ฮีป** (Heap): พื้นที่หน่วยความจำที่โปรแกรมขอจัดสรรมาใช้ตามต้องการระหว่างทำงาน - **หน่วยความจำรั่ว** (Memory leak): บั๊กที่ไม่คืนหน่วยความจำที่ใช้เสร็จแล้ว ทำให้การใช้งานเพิ่มขึ้นเรื่อย ๆ ต่อให้มี GC ก็เกิดได้ ถ้ายังมีที่ใดอ้างอิงอ็อบเจกต์ที่ใช้เสร็จแล้วอยู่ - **สวอป** (Swap, paging): การย้ายหน่วยความจำบางส่วนไปไว้บนดิสก์เมื่อแรมไม่พอ ถ้าจะกลับมาใช้หน่วยความจำส่วนนั้นอีก จะช้ากว่าแรมกว่า 1,000 เท่า - **OOM killer** (Out-of-memory killer): ฟีเจอร์ของ Linux ที่เมื่อหน่วยความจำหมด จะเลือกโปรเซสที่ใช้หน่วยความจำมากที่สุดแล้วบังคับปิด สำหรับคอนเทนเนอร์ แค่แตะขีดจำกัดหน่วยความจำก็ทำงานแล้ว - **แคชมิส** (Cache miss): การที่ข้อมูลไม่อยู่ในแคชที่ใกล้ CPU ต้องไปอ่านถึงหน่วยความจำที่ช้ากว่า - **IOPS** (I/O operations per second): จำนวนครั้งที่ดิสก์อ่านหรือเขียนได้ใน 1 วินาที ดิสก์บนคลาวด์มีขีดจำกัดตามที่จ่ายเงิน - **fsync** (fsync): คำสั่งที่รอจนกว่าข้อมูลจะถูกบันทึกลงดิสก์จริง ๆ การเขียนปกติจะไปอยู่ในหน่วยความจำของ OS ก่อนแล้วค่อยถูกเขียนลงดิสก์ภายหลัง ถ้าไฟดับระหว่างนั้น ข้อมูลอาจหายได้ fsync ปลอดภัยแต่ช้า - **เบิสต์เครดิต** (Burst credits): เครดิตสะสมที่ทำให้ดิสก์หรือเซิร์ฟเวอร์บนคลาวด์ทำงานได้เกินประสิทธิภาพพื้นฐานชั่วคราว เมื่อใช้จนหมดจะตกกลับไปที่ประสิทธิภาพพื้นฐาน - **อินเด็กซ์** (Index): ดัชนีของ DB ถ้าไม่มี ต้องอ่านทั้งตาราง - **ฟูลสแกน** (Full table scan): คิวรีที่ไล่อ่านทุกแถวในตารางโดยไม่ใช้อินเด็กซ์ - **แผนการรันคิวรี** (Query plan): วิธีที่ DB เลือกว่าจะรันคิวรีตามลำดับใดและใช้อินเด็กซ์ใด ต่อให้โค้ดไม่เปลี่ยน ถ้า DB เปลี่ยนแผน คิวรีเดิมก็อาจช้าลงกะทันหันได้ - **ทรานแซกชัน** (Transaction): งานใน DB ที่ผูกรวมเป็นหนึ่งเดียวแบบ “สำเร็จทั้งหมดหรือไม่สำเร็จเลย” การเทรดต้องทำในทรานแซกชันเสมอ ระหว่างที่ยังไม่จบจะล็อกแถวที่แก้ไว้ จึงยิ่งสั้นยิ่งดี - **คอนเนกชันพูล** (Connection pool): กลุ่มการเชื่อมต่อ DB ที่เปิดเตรียมไว้ล่วงหน้า ถ้าถูกใช้หมด คำขอใหม่ต้องรอ - **ฮอตโรว์** (Hot row): แถวเดียวที่คำขอจำนวนมากพยายามแก้พร้อมกัน เป็นต้นเหตุของการแย่งล็อก - **การทำสำเนาข้อมูลล่าช้า** (Replication lag): ระยะเวลาที่ DB สำเนาตามไม่ทัน DB หลัก - **โรลแบ็ค** (Rollback): การที่การบันทึกถูกยกเลิกแล้วกลับไปเป็นสถานะก่อนหน้า ผู้เล่นจะรู้สึกว่า “ไอเทมหาย” - **แคช** (Cache (Redis etc.)): การคัดลอกข้อมูลที่ใช้บ่อยไปเก็บในที่ที่เร็ว ช่วยลดโหลดของ DB - **เช็กพอยต์** (Checkpoint): งานที่ DB เขียนการเปลี่ยนแปลงที่สะสมไว้ในหน่วยความจำลงดิสก์ทีเดียวเป็นระยะ ขณะนั้นการบันทึกและการดึงข้อมูลอาจช้าลงชั่วครู่ - **เฟลโอเวอร์** (Failover): การสลับไปใช้ฝั่งสำรองเมื่อเซิร์ฟเวอร์หลักหรือ DB หลักล่ม ระหว่างสลับจะบันทึกไม่ได้ชั่วครู่ และถ้า replication ตามไม่ทัน ข้อมูลช่วงสุดท้ายอาจหาย - **MVCC** (Multi-version concurrency control): วิธีที่ DB เก็บเวอร์ชันเก่าไว้ชั่วคราว เพื่อไม่ให้คนอ่านกับคนแก้ขวางกัน ถ้ามีทรานแซกชันที่เปิดทิ้งไว้นาน เวอร์ชันเก่าจะสะสมจนช้าลง - **แคชสแตมปีด** (Cache stampede): ปรากฏการณ์ที่แคชว่างลงพร้อมกัน จนคำขอแห่ไปที่ต้นทาง (DB) - **เกตเวย์** (Gateway): เซิร์ฟเวอร์ตัวกลางที่รับการเชื่อมต่อจากไคลเอนต์แล้วส่งต่อไปยังเซิร์ฟเวอร์เกมด้านหลัง - **เซอร์กิตเบรกเกอร์** (Circuit breaker): กลไกที่ตัดการเรียกเซอร์วิสที่ล้มเหลวต่อเนื่องไว้ชั่วคราวและตอบกลับเป็นล้มเหลวทันที เพื่อกัน cascading failure พอผ่านไปสักพักจะลองเรียกหนึ่งสองครั้ง ถ้ากลับมาใช้ได้แล้วจึงเปิดให้เรียกตามปกติอีกครั้ง - **ความล้มเหลวแบบลูกโซ่** (Cascading failure): การที่เหตุขัดข้องในจุดหนึ่งลามไปยังเซอร์วิสอื่นตาม call chain - **ออโตสเกลลิง** (Autoscaling): ฟีเจอร์ที่เพิ่มหรือลดจำนวนเซิร์ฟเวอร์อัตโนมัติตามโหลด การเพิ่มเครื่องต้องใช้เวลา - **วอตช์ด็อก** (Watchdog): ตัวจับเวลาที่เฝ้าดูว่าเซิร์ฟเวอร์หยุดทำงานหรือไม่ ถ้าลูปของเกมหยุดนานเกินเวลาที่กำหนด (หลายวินาทีถึงหลายสิบวินาที) จะบันทึกสถานะ (dump) ไว้ แล้วบังคับปิดเซิร์ฟเวอร์เพื่อให้เปิดใหม่ - **อัตราการใช้งาน** (Utilization): สัดส่วนเวลาที่ worker (ตัวที่ประมวลผลคำขอ เช่น คอร์ CPU, เธรด หรือการเชื่อมต่อ DB) ไม่ว่าง ถ้าเกิน 80–90% การรอจะเพิ่มขึ้นอย่างรวดเร็ว - **p99** (99th percentile): ค่าที่ 99 ครั้งใน 100 ครั้งเร็วกว่านี้ และราว 1 ครั้งช้ากว่านี้ สะท้อนแลคที่ผู้เล่นรู้สึกได้ดีกว่าค่าเฉลี่ย - **V-Sync** (Vertical sync): ฟีเจอร์ที่ส่งเฟรมออกให้ตรงกับรอบการรีเฟรชของจอ ช่วยแก้ภาพฉีก (screen tearing) แต่ทำให้เกิดอินพุตดีเลย์ และถ้า FPS ตกต่ำกว่าอัตรารีเฟรช ภาพอาจกระตุกเพราะสลับไปมาระหว่าง 60 กับ 30 - **อัตรารีเฟรชแบบแปรผัน** (VRR, G-Sync, FreeSync): ฟีเจอร์ที่ให้จอเปลี่ยนภาพตามจังหวะที่เฟรมพร้อม ช่วยลดอาการกระตุกจากการสลับไปมาระหว่าง 60 กับ 30 ของ V-Sync และลดอินพุตดีเลย์ - **แอนตี้ชีต** (Anti-cheat): โมดูลความปลอดภัยที่ป้องกันการแฮ็กเกม ถ้าการตรวจสอบเป็นระยะหรือ heartbeat กับเซิร์ฟเวอร์ล้มเหลว ก็อาจทำให้เกิดอาการกระตุกหรือหลุดได้ - **โอเวอร์เลย์** (Overlay): ฟีเจอร์ที่โปรแกรมแชต โปรแกรมอัดหน้าจอ หรือโปรแกรมแสดง FPS วาดภาพซ้อนทับบนหน้าจอเกม โปรแกรมเหล่านี้แทรกเข้าไปในขั้นตอนการวาดภาพของเกม จึงทำให้กระตุกได้ - **การคอมไพล์เชเดอร์** (Shader compilation): งานแปลงโปรแกรมเอฟเฟกต์กราฟิกให้ใช้กับ GPU ได้ ถ้าไม่ทำไว้ล่วงหน้า ภาพจะหยุดแวบตอนเห็นเอฟเฟกต์นั้นครั้งแรก และเมื่ออัปเดตไดรเวอร์การ์ดจอ ผลที่เก็บไว้จะใช้ไม่ได้ ต้องคอมไพล์ใหม่ - **เมนเธรด** (Main thread, Game thread): เธรดหลักของเกมที่ไล่ทำการคำนวณกติกาเกมและการเตรียมภาพตามลำดับ ถ้ามีงานใดใช้เวลานานแม้แค่งานเดียว ภาพจะหยุดไปตลอดช่วงนั้น - **ความละเอียดของตัวจับเวลา** (Timer resolution): ช่วงเวลาสั้นที่สุดที่ระบบปฏิบัติการปลุกโปรแกรมที่หลับอยู่ได้ ค่าเริ่มต้นของ Windows คือ 15.6 ms ถ้าโปรแกรมไม่เปลี่ยนค่านี้เอง ต่อให้สั่ง “ปลุกในอีก 1 ms” ก็จะตื่นช้ากว่านั้น - **การลดความเร็วเพราะความร้อน** (Thermal throttling): ฟีเจอร์ป้องกันที่ลดความเร็ว CPU/GPU ลงเองเมื่อเครื่องร้อน บนมือถือเกิดบ่อยหลังเล่นเกมไปหลายนาทีถึงหลายสิบนาที - **VRAM** (Video memory): หน่วยความจำเฉพาะบนการ์ดจอ เท็กซ์เจอร์และโมเดลจะถูกโหลดขึ้นไปไว้ที่นี่ก่อนวาด ถ้าไม่พอ จะต้องรับส่งกับหน่วยความจำของ PC ผ่านเส้นทางที่ช้า ภาพจึงกระตุก - **เน็ตกราฟ** (Net graph): หน้าจอสำหรับพัฒนาและดีบั๊กที่แสดงปิง แพ็กเก็ตหาย FPS และทิกเป็นกราฟแบบเรียลไทม์บนหน้าจอเกม ถ้าติดมาในคลิปแจ้งแลคด้วย จะหาสาเหตุได้ง่ายขึ้นมาก - **อัตราการส่งซ้ำ** (Retransmission rate): สัดส่วนแพ็กเก็ต TCP ที่ต้องส่งซ้ำจากที่ส่งไปทั้งหมด ไม่มีเกณฑ์ที่เป็นทางการ แต่ถ้าค่าเฉลี่ยของทั้งเซิร์ฟเวอร์ต่ำกว่า 0.1% ถือว่าอยู่ในเกณฑ์ดี และถ้าเกิน 1% ผู้เล่นจำนวนมากจะเริ่มรู้สึกแลค ควรดูด้วยว่าเพิ่มขึ้นจากค่าปกติกี่เท่า - **SACK** (Selective ACK): ฟีเจอร์ของ TCP ที่ฝั่งรับแจ้งอย่างละเอียดว่า “ได้ช่วงนี้แล้ว ขาดแค่ส่วนนี้” ต่อให้หายหลายตัวก็กู้คืนได้ในรอบเดียว - **RACK-TLP** (Recent ACK, Tail Loss Probe): ฟีเจอร์ของ TCP ที่ใช้เวลาเป็นเกณฑ์ตัดสินว่าแพ็กเก็ตหาย และถ้าไม่มี ACK อยู่ช่วงหนึ่ง จะส่งแพ็กเก็ตสุดท้ายซ้ำอีกครั้งเพื่อเร่งการกู้คืน เป็นค่าเริ่มต้นใน Linux และ Android รุ่นใหม่ ฝั่ง Windows เปิด TLP และ RACK เป็นค่าเริ่มต้นตั้งแต่ Windows 10 (1607) และ Server 2016 ส่วน RACK รุ่นใหม่ที่กู้คืนได้แม้แต่การส่งซ้ำที่หายไป มีตั้งแต่ Server 2022 ทำงานเฉพาะการเชื่อมต่อที่เปิด SACK - **การส่งซ้ำโดยไม่จำเป็น** (Spurious retransmission): การส่งซ้ำแพ็กเก็ตที่ไม่ได้หาย แค่มาช้าหรือมาสลับลำดับจนถูกเข้าใจว่าหาย ทำให้เปลืองแบนด์วิดท์และลดอัตราการส่งลงโดยไม่จำเป็น - **ซีโรวินโดว์** (Zero window): สถานะที่บัฟเฟอร์ฝั่งรับเต็มจนแจ้งว่า “หยุดส่งก่อน” ดูเหมือนการส่งซ้ำ แต่เน็ตไม่ได้มีปัญหา โปรแกรมฝั่งรับแค่อ่านข้อมูลไม่ทัน - **thin stream** (Thin stream): การเชื่อมต่อที่ส่งแพ็กเก็ตเล็ก ๆ ห่าง ๆ แบบเกม สัญญาณสำหรับ fast retransmit สะสมได้ยาก เมื่อแพ็กเก็ตหายจึงหยุดนาน - **โพลิเซอร์** (Policer): วิธีจำกัดความเร็วที่ทิ้งแพ็กเก็ตส่วนที่เกินความเร็วที่กำหนดทันทีโดยไม่พักไว้ในคิว ส่วนวิธีที่พักไว้ในคิวแล้วค่อย ๆ ปล่อยออกเรียกว่า shaper - **เพซซิง** (Pacing): การกระจายแพ็กเก็ตที่จะส่งให้ออกไปอย่างสม่ำเสมอตามเวลา แทนการส่งออกไปทีเดียวทั้งหมด ช่วยกันไม่ให้บัฟเฟอร์ขนาดเล็กล้น - **ECN** (Explicit Congestion Notification): ฟีเจอร์ที่เมื่อเครือข่ายแออัด จะติดเครื่องหมาย “แออัด” ให้แพ็กเก็ตแทนการทิ้ง เพื่อให้ฝั่งส่งลดความเร็วลง บอกความแออัดได้โดยไม่ต้องมีแพ็กเก็ตหาย ต้องรองรับทั้งปลายทั้งสองฝั่งและอุปกรณ์ในช่วงที่แออัดจึงจะได้ผล - **MSS** (Maximum Segment Size): ขนาดข้อมูลสูงสุดที่ TCP ใส่ในแพ็กเก็ตหนึ่ง ปกติ 1,460 ไบต์ ถ้าลดลงให้เข้ากับช่วงที่ผ่านอุโมงค์ (tunnel) จะป้องกัน MTU black hole ได้ - **แฮนด์โอเวอร์** (Handover): การที่มือถือซึ่งกำลังเคลื่อนที่เปลี่ยนสถานีฐานที่เชื่อมต่ออยู่ - **เปอร์เซ็นไทล์** (Percentile (p50, p95, p99)): ค่าที่อยู่ในตำแหน่งกี่เปอร์เซ็นต์เมื่อเรียงค่าจากน้อยไปมาก p50 คือค่ามัธยฐาน p99 คือค่าที่อยู่แถว ๆ ครั้งที่ช้าที่สุด 1 ครั้งใน 100 ครั้ง ช่วยเผยค่าที่พุ่งซึ่งค่าเฉลี่ยซ่อนไว้ - **ความหน่วงส่วนหาง** (Tail latency): ความหน่วงนาน ๆ ที่เกิดเป็นบางครั้ง ทั้งที่ส่วนใหญ่เร็ว แทบไม่ปรากฏในค่าเฉลี่ย แต่เป็นส่วนที่ผู้เล่นจำได้ว่าแลค - **การวัดแบบสังเคราะห์** (Synthetic monitoring): การใช้อุปกรณ์หรือเซิร์ฟเวอร์สำหรับวัดส่ง ping, traceroute ฯลฯ จากจุดที่กำหนดเป็นระยะแทนผู้เล่นจริง เพื่อวัดคุณภาพเส้นทาง RIPE Atlas เป็นเครื่องมือสาธารณะที่เป็นที่รู้จัก - **ช่วงเวลารวมค่า** (Aggregation interval): จุดหนึ่งบนกราฟรวมค่าของกี่วินาทีหรือกี่นาที ยิ่งช่วงยาว ค่าที่พุ่งสั้น ๆ จะถูกเฉลี่ยจนจางลง - **การวิเคราะห์หลังเกิดเหตุ** (Postmortem): เอกสารที่สรุปหลังเหตุขัดข้องจบลงว่าเกิดอะไรขึ้น ทำไมจึงเกิด และจะเปลี่ยนอะไร เขียนขึ้นเพื่อป้องกันไม่ให้เกิดซ้ำมากกว่าเพื่อหาคนผิด - **C-state** (CPU idle state): สถานะประหยัดพลังงานที่ CPU เข้าไปเมื่อว่าง ยิ่งลึกยิ่งประหยัดไฟ แต่ต้องใช้เวลาตื่นกลับมานานขึ้น - **ไลฟ์ไมเกรชัน** (Live migration): การที่คลาวด์ย้าย VM ที่กำลังทำงานอยู่ไปยังโฮสต์อื่น เช่น เพื่อซ่อมบำรุงโฮสต์ ขณะย้ายอาจหยุดไปชั่วครู่ - **SNAT** (Source NAT): NAT ที่เปลี่ยน IP ต้นทางของแพ็กเก็ตขาออกเป็น public IP จำนวนพอร์ตที่ public IP หนึ่งตัวใช้ได้มีขีดจำกัด ถ้าใช้หมด การเชื่อมต่อใหม่จะล้มเหลว - **NAT เกตเวย์** (NAT gateway): อุปกรณ์บนคลาวด์ที่ให้เซิร์ฟเวอร์ในเครือข่ายส่วนตัวใช้ public IP ร่วมกันหนึ่งตัวเวลาออกอินเทอร์เน็ต จำนวนการเชื่อมต่อพร้อมกันต่อปลายทางมีขีดจำกัด - **อินเทอร์เน็ตดาวเทียมวงโคจรต่ำ** (LEO satellite internet): อินเทอร์เน็ตที่เชื่อมต่อผ่านดาวเทียมจำนวนมากที่ความสูงหลักร้อยถึงหลักพันกิโลเมตร ความหน่วงสั้นกว่าดาวเทียมวงโคจรค้างฟ้ามาก แต่เมื่อเปลี่ยนดาวเทียมที่เชื่อมต่อ ความหน่วงอาจพุ่งขึ้นได้ - **GeoIP** (IP geolocation): ฐานข้อมูลที่ใช้ IP address ประมาณประเทศ เมือง และ ISP บางรายการผิดหรือล้าสมัย จึงอาจเป็นสาเหตุให้ถูกส่งไปเซิร์ฟเวอร์ในภูมิภาคที่อยู่ไกล - **ใบรับรอง TLS** (TLS certificate): เอกสารอิเล็กทรอนิกส์ที่ยืนยันว่าเซิร์ฟเวอร์เป็นตัวจริง มีวันหมดอายุ ถ้าหมดอายุ การเชื่อมต่อแบบเข้ารหัสจะล้มเหลวจนเข้าใช้งานไม่ได้ - **การสร้างเฟรม** (Frame generation): เทคโนโลยีที่การ์ดจอแทรกเฟรมที่คาดการณ์ขึ้นระหว่างเฟรมที่วาดจริง เพื่อเพิ่ม FPS ภาพลื่นขึ้น แต่ความหน่วงตั้งแต่อินพุตจนถึงภาพบนจออาจเพิ่มขึ้น