完整版:https://jungrok5.github.io/mmo-lag-anatomy/zh-tw/ 原因頁面:https://jungrok5.github.io/mmo-lag-anatomy/zh-tw/c/[原因 ID].html # 遊戲 Lag 白皮書知識資料 自動產生(2026-10-03,`node tools/export.cjs`)。請勿手動修改;修改 src/js/ 後重新匯出。 原因 228 個,術語 135 個。原因以 **ID** 指稱。在網站網址後面加上 `#c-ID` 即可前往該原因卡片。 ## 負責代碼 | 代碼 | 團隊 | 負責單位 | 範圍 | |---|---|---|---| | cli | 遊戲開發團隊 | 用戶端開發 | 遊戲用戶端程式碼:畫格、GC、載入,內插、外插、預測,用戶端的網路處理(包含送出心跳封包、自動重新連線) | | srv | 遊戲開發團隊 | 伺服器開發 | 遊戲伺服器程式碼:tick、執行緒、鎖,同步設計,連線處理(accept 迴圈、listen 參數),心跳回應與斷線連線的清理,socket 選項,查詢與 transaction 設計 | | net | 基礎設施團隊 | 網路基礎設施 | 線路與 IDC 網路設備(交換器、路由器、防火牆、負載平衡器、DDoS 防護),雲端網路 ACL、VPC 路由、負載平衡器,電信業者與 peering | | sys | 基礎設施團隊 | 伺服器基礎設施 | 伺服器設備與雲端執行個體(包含安全群組、連線追蹤),OS 與 kernel 設定,NIC,部署與監控環境 | | dba | 基礎設施團隊 | DB 基礎設施 | DB 伺服器與儲存裝置,DB 設定、複寫、備份,快取伺服器 | | ext | 外部 | 外部 | 玩家的電腦與家用網路、電信業者路段(不在我方合約範圍內)、雲端供應商。無法直接修正,以公告說明、提出請求、繞道等方式因應 | 各原因的「主要負責」是消除根本原因的單位,「協同」是實際也有事要做的單位。 ## 症狀 - **卡頓**(`stutter`,其他說法:卡卡的、一頓一頓、像在掉幀):動作不流暢,不斷短暫停住又繼續動。ping 值正常時,多半是自己電腦的畫格問題(用戶端、OS);ping 忽高忽低時,多半是 Wi-Fi 或線路的抖動。不過遊戲內顯示的 ping 通常是在每個畫格跑一次的遊戲迴圈裡量的,所以畫格時間飆高時,ping 數字也可能跟著跳。 - **瞬移**(`teleport`,其他說法:瞬間移動、角色亂跳、停一下又突然跳走):角色沒有移動過程,一下子就出現在很遠的位置。通常代表封包斷了一段時間。要查封包遺失、線路短暫中斷、伺服器停住、外插失敗。其他人都正常、只有一個人在跳的話,先懷疑那個人的線路。 - **拉回**(`rubber`,其他說法:被拉回原位、橡皮筋效應(rubber banding)、位置回溯):自己的角色往前走到一半,被拉回剛剛經過的位置。自己畫面上的預測和伺服器判定對不上。可能是自己的輸入沒送到伺服器(封包遺失)、伺服器的移動驗證把它擋掉,或是兩邊的移動計算不一致。 - **快轉**(`burst`,其他說法:唰唰唰、像按了快轉、一口氣全部處理完):停住的畫面重新動起來時,積壓的移動、打擊和傷害一口氣快速跑完。封包在某處堆積,然後一次放行。典型的例子是 TCP 等待重傳、伺服器追進度、用戶端處理積壓。 - **慢動作**(`slowmo`,其他說法:整個世界變慢、整體遲鈍):所有東西都變慢,技能施放和怪物移動看起來像被拉長。依伺服器設計不同,也可能速度不變,改以卡頓或瞬移的形式出現。伺服器無法在時限內跑完 tick。線路本身正常,所以在遊戲外量的 ping 不變;遊戲內的 ping 若含有伺服器處理的等待時間,可能會稍微升高。要查人數暴增、視野計算、廣播、記憶體不足。 - **輸入延遲**(`delay`,其他說法:反應慢半拍、操作遲鈍、沒有手感):按下去之後要等一段時間才看到結果。畫面本身可能很流暢。往返時間(ping)很長,或是某處的佇列堆積起來。要查距離、分享器佇列、Nagle(把小封包湊在一起再送的 TCP 功能)、伺服器佇列。ping 很低卻總是遲鈍的話,就查垂直同步(V-Sync)、FPS 偏低這類自己電腦端的因素,或是每個動作都要等伺服器確認的設計(見「同步方式」一章)。 - **定格**(`freeze`,其他說法:畫面凍結、完全不動、沒有回應):畫面裡所有東西停住一下(0.5 秒到數秒),然後又開始動。可能是整台伺服器停住(GC、死結、同步呼叫)、線路短暫中斷,或是自己的電腦停住。 - **吃指令/回檔**(`dropped`,其他說法:技能被吃、道具被收回、交易失敗):明明做了的動作變成沒發生過,或是結果過了好一陣子才被推翻。可能是請求遺失(封包遺失、佇列滿溢)、伺服器的判定和自己的畫面不同(判定時間點不同、先行演出後被拒絕),或是存檔途中失敗(DB 鎖定或故障、伺服器當機)。 - **斷線**(`disconnect`,其他說法:被踢出遊戲、連線中斷、與伺服器的連線已中斷):遊戲中連線中斷,被送回登入畫面或重新連線視窗。在逾時時間內一個封包都沒收到。要查線路長時間中斷、閒置逾時、伺服器當機或重啟,以及停住時間超過逾時的伺服器或自己的電腦(載入太久)。若沒有任何提示、遊戲直接關掉,先查用戶端被強制結束(閃退、記憶體不足),再查連線。 - **連不上/無限讀取**(`noconnect`,其他說法:登不進去、讀取跑不完):進不了遊戲,或是停在讀取、進場畫面。負責接受新連線的地方(伺服器的連線等待佇列、防火牆、登入伺服器、DB)滿了。維護剛結束時特別常見。 - **看不見/幽靈物件**(`invisible`,其他說法:NPC 不見了、隱形角色、早就死掉的怪還站著):應該存在的 NPC、怪物、玩家只在自己的畫面上消失,或是早已消失的物件只留在自己的畫面上。多半是少了某個封包,或是繪製失敗,和速度快慢關係不大。要查頻道或相位不同、出現/消失通知遺失、載入期間被丟棄、資源載入失敗。離開視野再回來後會不會出現,是最關鍵的線索。 ## 四個因素 - **延遲**(`lat`,Latency):因為距離、佇列與處理時間,所有封包都固定晚一段時間才抵達。遊戲的因應:用預測和先行演出先把自己的動作呈現出來,判定則由伺服器回溯到過去的時間點來對齊(延遲補償)。 - **抖動**(`jit`,Jitter):平均值沒問題,但有的封包來得快、有的來得慢。常見來源是 Wi-Fi、壅塞的線路和忙碌的 CPU。遊戲的因應:先在內插緩衝裡存一點,再以固定速度取出繪製。抖動大於緩衝時就遮不住了。 - **遺失**(`loss`,Packet loss):佇列滿溢、無線干擾、故障的設備都會丟棄封包。線路短暫中斷也等於連續遺失。遊戲的因應:UDP 遊戲會用內插、外插補上空缺,自己的輸入則重複夾帶送出,掉了一兩個也補得回來。TCP 在重新收到遺失的封包之前,不會把後面的封包交給遊戲。 - **停滯**(`stall`,Stall):伺服器的 tick 延後或停住(GC、鎖、同步呼叫、過載),自己電腦的畫格也會停住。線路完全正常時也會發生。遊戲的因應:對於停住期間積壓的計算,遊戲會一口氣追完、直接跳過,或是讓時間放慢流動。 ## 原因 ### L1 用戶端遊戲程式(16 個原因) #### cg-hitch · 畫格時間尖峰 · Frame hitch 某一個畫格的計算時間比平常多出好幾倍,畫面短暫停住。 - 為什麼 → 於是 → 畫面上:技能特效暴增、大量生成(spawn)、整個 UI 重新整理,全擠在同一個畫格 → 無法在 16.7ms 內完成,花了 50~300ms → 畫面頓一下,下一個畫格所有東西一次移動到位 - 症狀:卡頓, 定格/因素:停滯 - 誰會遇到:只有我/何時:人潮湧入時, 做特定動作時, 偶爾隨機發生 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:把繁重的工作分散到多個畫格、用 profiler 找出飆高的畫格、設定特效數量上限。 - 數值參考:60FPS 時一個畫格是 16.7ms。只要出現一個超過 50ms 的畫格,就很容易讓人覺得「卡了一下」。 - 圖表上:偶爾隨機飆高(畫格時間) - 查看位置:用 PresentMon 錄下遊戲中的畫格時間(FrameTime),以及 CPU、GPU 在該畫格花費的時間(CPUBusy、GPUBusy)。行動裝置看 Android vitals 的慢速工作階段(slow session)與渲染緩慢指標 - 符合的跡象:平常在 16.7ms 左右的畫格時間往上衝到超過 50ms 的瞬間,與技能演出、大量生成、整個 UI 重新整理重疊,而當下 ping 沒有變化 - 不符合的跡象:畫格時間平穩、只有其他角色頓一下時,屬於「沒有內插緩衝或緩衝太短」這類網路端問題。飆高的間隔規律時,先查「用戶端垃圾回收」 - 確認方式:在玩家端環境確認 - 出處: - [Slow rendering](https://developer.android.com/topic/performance/vitals/render) · Android (Google) · 要達到 60FPS,一個畫格必須在 16ms 內畫完;來不及就會跳過畫格,看起來像卡頓(jank) - [Slow Sessions (games only)](https://developer.android.com/topic/performance/issues/slow-session) · Android (Google) · Android vitals 把超過 50ms(20FPS)或 34ms(30FPS)的遊戲畫格視為慢速畫格 - [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 · 用戶端垃圾回收 · Client GC (Unity C#, Unreal, Lua) 回收用過即丟的記憶體(垃圾)期間,整個遊戲會暫停。特徵是以規律的間隔出現卡頓。 - 為什麼 → 於是 → 畫面上:每個畫格都建立再丟棄暫時性的字串、陣列、List → 垃圾累積起來後,GC 暫停主執行緒進行回收 → 每隔幾秒~幾十秒規律地出現卡頓 - 症狀:卡頓, 定格/因素:停滯 - 誰會遇到:只有我/何時:固定週期, 人潮湧入時 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:減少記憶體配置(避免字串串接、LINQ、lambda 捕捉)、使用物件池、開啟漸進式 GC(Unity 2020 以後為預設值)、趁讀取畫面這類暫停也無妨的時機先執行 GC。 - 數值參考:一般每次數 ms~100ms,低階手機或記憶體用量大的遊戲會更久(實驗中約 150~170ms)。Unity 的 GC 每次執行都會檢查整個 heap,所以遊戲使用中的記憶體越多,暫停就越久。 - 圖表上:固定週期飆高(畫格時間、GC 執行時間點) - 查看位置:在開發版本中查看 Unity Profiler 的 GC.Collect、GC.Alloc 標記。Unreal 則看 stat GC 與 stat Hitches(把超過 t.HitchFrameTimeThreshold 所設時間的畫格記進 log) - 符合的跡象:每個飆高的畫格都有 GC.Collect 區段,長度與飆高的長度相近,間隔為幾秒~幾十秒且規律。在人多的地方,每個畫格的 GC.Alloc 會增加 - 不符合的跡象:飆高的畫格沒有 GC 區段時,是「主執行緒同步載入、著色器編譯」或「大量角色同畫面的渲染負載」 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:在 Unity 這類使用 C# 的用戶端很常見。主要元凶是每個畫格都重新產生戰鬥紀錄字串、傷害數字、UI 文字的程式碼。如果只在人多的地方卡頓,代表有程式碼產生的垃圾會隨人數成比例增加。漸進式 GC 會把回收工作分散到每個畫格各做一點(Unity 預設 3ms),但產生垃圾的速度一旦快過回收速度,最後還是會一次暫停。Unreal Engine 也有自己的 GC,負責清理不再使用的遊戲物件;雖然依引擎版本與設定而不同,預設設定下約每 1 分鐘執行一次,有時會造成每隔 1 分鐘頓一下。遊戲規則用 Lua 等腳本撰寫的用戶端,該腳本的 GC 也會另外執行。 - 出處: - [Garbage collection modes](https://docs.unity3d.com/Manual/performance-incremental-garbage-collection.html) · Unity · 漸進式 GC 為預設值,會分散到多個畫格回收;關閉時,檢查整個 heap 的期間主執行緒會暫停,可能長達數百 ms - [Scripting.GarbageCollector.CollectIncremental](https://docs.unity3d.com/ScriptReference/Scripting.GarbageCollector.CollectIncremental.html) · Unity · 漸進式 GC 每次使用的時間(時間片段)預設目標為 3ms(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 · Unreal GC 依固定間隔(Time Between Purging Pending Kill Objects,單位為秒)執行的設定。這份文件沒有寫出預設值 - [Profiler markers reference](https://docs.unity3d.com/Manual/profiler-markers.html) · Unity · GC.Collect:垃圾回收期間程式碼暫停的區段(不到 1ms~數百 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(垃圾回收統計)、stat Hitches(把超過 t.HitchFrameTimeThreshold 的畫格記錄到 log) #### cg-sync-load · 主執行緒同步載入、著色器編譯 · Synchronous asset load, shader compile 要繪製第一次出現的地圖、怪物、特效之前,得先讀取檔案並建立著色器,畫面因此停住。 - 為什麼 → 於是 → 畫面上:進入新地圖,或第一次出現的技能、裝備、怪物登場 → 主執行緒等待讀取檔案與著色器編譯 → 只有第一次停住 0.1~1 秒,第二次起就正常 - 症狀:定格, 卡頓/因素:停滯 - 誰會遇到:只有我/何時:移動中/切換地圖時, 做特定動作時 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:非同步載入、預先載入(prewarming)、在讀取畫面或首次執行時預先編譯著色器、善用讀取畫面。 - 數值參考:編譯一個著色器需要數十 ms,久的話超過 100ms。貼圖載入依儲存裝置速度需要數十~數百 ms。 - 圖表上:剛開服或維護結束後暴增(畫格尖峰次數(遊戲或驅動程式剛更新時)) - 查看位置:用 PresentMon 把同一條路線錄兩次,比較第一次與第二次經過。開發版本在 Unreal 開啟 r.PSOPrecache.Validation,查看 stat PSOPrecache 與 log 中的「PSO PRECACHING MISS」;Unity 則在 Profiler 的 Timeline 查看飆高畫格中的載入與著色器區段 - 符合的跡象:只在第一次去的地方、第一次用的技能飆高 0.1~1 秒,第二次就消失。遊戲或顯示卡驅動程式剛更新時回報集中,之後逐漸減少 - 不符合的跡象:同一個地方每次都飆高時,與著色器快取無關。只有儲存裝置慢的電腦每次移動都重複發生時,是「儲存裝置太慢,資源串流跟不上」;專用 GPU 記憶體已滿時,是「顯示記憶體(VRAM)不足」 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:在電腦上,建立過一次的著色器會由顯示卡驅動程式存進著色器快取,之後重複使用。因此在更新顯示卡驅動程式或遊戲更新剛推出時,這個快取會失效,原本正常的人也會有一段時間再度卡頓。典型的回報是「更新後每到一個沒去過的地方就頓一下」。 - 出處: - [Shader loading](https://docs.unity3d.com/Manual/shader-loading.html) · Unity · 第一次使用某個著色器變體時,顯示卡驅動程式要替 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 · 在需要的當下才建立管線狀態(PSO)可能花上 100ms 以上,必須預先建立 - [Direct3D 12 Return Codes](https://learn.microsoft.com/en-us/windows/win32/direct3d12/d3d12-graphics-reference-returnvalues) · Microsoft · D3D12_ERROR_DRIVER_VERSION_MISMATCH:用不同驅動程式版本建立的 PSO 快取無法重複使用(驅動程式更新後需重新編譯) - [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 後,可用 stat PSOPrecache 查看漏掉的 PSO 統計,並在 log 留下「PSO PRECACHING MISS」;執行階段建立 PSO 超過預設的 20ms 就計為一次 hitch #### cg-asset-stream · 儲存裝置太慢,資源串流跟不上 · Slow storage stalls asset streaming 在 HDD 這類較慢的儲存裝置上,讀取開放世界貼圖與模型的速度跟不上移動,物件會晚出現,或遊戲為了等待讀取而卡頓。 - 為什麼 → 於是 → 畫面上:騎坐騎、傳送等快速移動,或進入人多的地方,一次需要大量新的貼圖與模型 → HDD 等較慢的儲存裝置無法以需要的速度讀取,讀取請求越積越多,部分載入還要主執行緒等到完成為止 → 貼圖有一段時間是模糊的,建築與角色晚出現;等待讀取的瞬間出現卡頓、定格 - 症狀:看不見/幽靈物件, 卡頓, 定格/因素:停滯 - 誰會遇到:只有我/何時:移動中/切換地圖時, 人潮湧入時 - 主要負責:遊戲開發團隊(用戶端開發)/協同:外部(外部) - 遊戲開發團隊要做的事:採用主執行緒不必等待讀取的非同步串流、依移動方向與速度預先讀取、先顯示低解析度(mipmap)與簡化模型再替換、快速移動時串流跟不上就暫時限制移動速度或改用讀取畫面、在最低與建議配備中註明是否需要 SSD。 - 外部要做的事:引導玩家把遊戲安裝在 SSD 上,並請玩家確認同一顆磁碟上是否正在下載或執行防毒掃描。 - 數值參考:根據 Microsoft 的說明,舊式硬碟每秒可讀取數十 MB,NVMe SSD 每秒可讀取數 GB,而上一世代的遊戲串流每秒大約使用 50MB。以 SSD 速度為前提、讀取量遠多於此的遊戲放在 HDD 上執行,讀取很容易跟不上移動。 - 圖表上:只有部分偏高(畫格尖峰次數(依儲存裝置類型)、磁碟讀取延遲) - 查看位置:快速移動時,同時記錄 Windows 效能監視器的 PhysicalDisk\Avg. Disk sec/Read(單次讀取的平均時間)、Current Disk Queue Length 與 PresentMon 畫格時間。開發版本看 Unreal 的 stat Streaming、stat AsyncLoad,以及 Unity Profiler 的 AssetBundle.asset/allAssets 警告(在載入完成前就要求結果,主執行緒只好等待) - 符合的跡象:快速移動時磁碟讀取延遲與佇列暴增,同一時間畫格飆高,或貼圖、物件晚出現。同一場景改在 SSD 上執行就消失 - 不符合的跡象:磁碟很閒、貼圖卻模糊時,是「顯示記憶體(VRAM)不足」;同一個地方第二次去就正常時,是「主執行緒同步載入、著色器編譯」 - 確認方式:在玩家端環境確認 - 深入了解:如果只有第一次停住、之後就正常,比較接近「主執行緒同步載入、著色器編譯」;只有儲存裝置慢的電腦每次移動都重複發生,就是這個原因。顯示記憶體不夠、貼圖被反覆移出又載入的「顯示記憶體(VRAM)不足」也會造成貼圖模糊,所以要同時查看磁碟讀取等待與顯示記憶體用量。防毒軟體的即時掃描會在遊戲每次開啟檔案時介入,也可能讓讀取更慢。 - 出處: - [DirectStorage is coming to PC](https://devblogs.microsoft.com/directx/directstorage-is-coming-to-pc/) · Microsoft · 舊式硬碟每秒數十 MB,NVMe SSD 每秒數 GB,上一世代遊戲的資源串流預算約每秒 50MB;開放世界遊戲會在移動時即時讀入遠方景物,用完就丟棄 - [Texture and mesh loading](https://docs.unity3d.com/Manual/LoadingTextureandMeshData.html) · Unity · 同步上傳在主執行緒的一個畫格內讀取並上傳,造成明顯的停頓;非同步上傳則分散在多個畫格串流 - [Texture Streaming Overview for Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/texture-streaming-overview-for-unreal-engine) · Epic Games · 串流器依視點調高或調低貼圖解析度(mip),大部分計算在非同步工作執行緒進行,並優先載入畫面上看得到的 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(串流貼圖的記憶體與數量)、stat AsyncLoad(非同步載入統計) - [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.1ms,300 人就是 6~30ms。60FPS 的畫格預算是 16.7ms,光這部分就用掉 1/3 以上,多的時候直接超出預算。 - 圖表上:隨人數/負載上升(畫格時間、畫面內角色數) - 查看位置:比較攻城戰、世界王前後的 PresentMon 畫格時間與 CPUBusy、GPUBusy。開發版本看 Unreal 的 stat Unit(遊戲執行緒、渲染執行緒、GPU 時間) - 符合的跡象:畫面內人數越多,畫格時間跟著上升;開啟顯示人數限制或特效簡化選項後立刻改善 - 不符合的跡象:與人數無關也會飆高時,是「畫格時間尖峰」或「用戶端垃圾回收」。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 · 動態減少骨架網格(skeletal mesh)的動畫更新(tick),把花在動畫上的時間限制在預算內 - [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,對照周圍人數查看 - 符合的跡象:人多的地方剩餘封包數與套用延遲持續增加,同一時間 ping 與伺服器的傳送間隔正常 - 不符合的跡象:沒有套用延遲、封包本身卻晚到時,問題在網路區段。畫格時間大幅上升時,是「大量角色同畫面的渲染負載」 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Actor Priority in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-priority-in-unreal-engine) · Epic Games · 頻寬不足時,依 actor 與觀看者的距離、距上次複製(replication)經過的時間排定優先順序,不會每次都複製全部 actor - [Replication Graph in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/replication-graph-in-unreal-engine) · Epic Games · 連線人數與複製對象多的遊戲(MMORPG 等)要依位置分組、只傳送需要的對象,才能避免伺服器 CPU 瓶頸 #### cg-no-buffer · 沒有內插緩衝或緩衝太短 · Missing/short interpolation buffer 一收到伺服器封包就立刻繪製時,抖動(封包抵達間隔忽長忽短)會原封不動地出現在畫面上。 - 為什麼 → 於是 → 畫面上:收到位置就直接繪製,或緩衝比抖動還短 → 封包晚到多久就停多久,一次湧入多少就跳多少 → 其他角色走走停停,一頓一頓地移動 - 症狀:卡頓/因素:抖動 - 誰會遇到:只有我/何時:一直都有 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:加入內插緩衝,依線路狀態自動調整緩衝長度。伺服器:套用延遲補償(回溯判定),讓玩家看著緩衝時間之前的畫面射擊,判定也正確。 - 數值參考:一般會保留約為伺服器封包傳送間隔 2 倍的緩衝(每秒收到 20 次時為 100ms)。 - 圖表上:一開始就一直偏高(封包抵達間隔、內插緩衝變空的次數) - 查看位置:在用戶端記錄伺服器封包的抵達間隔分布,以及因為沒有下一個可內插的快照而停住或改用外插的畫格數 - 符合的跡象:抵達間隔的波動經常超過內插緩衝長度,每次緩衝都會變空、其他角色頓一下。加長緩衝後減少 - 不符合的跡象:緩衝足夠卻仍會頓一下時,確認伺服器的傳送間隔本身是否不規律(tick 延遲) - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:加長緩衝會更流暢,但看到的對手也會落後同樣的時間。因此攻擊判定會搭配延遲補償,由伺服器回溯到「那位玩家當時看到的過去」來確認。 - 出處: - [Interpolation and extrapolation (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/interpolation.html) · Unity · 緩衝內插為了等待晚到的封包而刻意延後繪製;緩衝越大越準確,延遲也跟著增加 - [Struct ClientTickRate (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/api/Unity.NetCode.ClientTickRate.html) · Unity · 內插緩衝預設值 InterpolationTimeNetTicks = 2(伺服器傳送 2 次的份量) - [Physics (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/physics.html) · Unity · 延遲補償:伺服器找出用戶端在那個 tick 看到的碰撞世界,據此判定是否命中 #### cg-extrap · 過度外插(dead reckoning) · Over-extrapolation / dead reckoning 封包沒來的期間,照最後的速度繼續顯示移動,等發現猜錯再拉回來。 - 為什麼 → 於是 → 畫面上:收不到封包,就照最後的方向與速度繼續移動 → 實際上對方已經停下或改變方向 → 對方角色走了好一段後一下子移到真正的位置,或穿牆而過。封包抵達間隔忽長忽短時,會反覆衝過頭又被拉回,看起來像在顫動 - 症狀:瞬移, 卡頓/因素:遺失, 抖動 - 誰會遇到:只有我/何時:偶爾隨機發生 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:設定外插時間上限(例如 200~250ms),猜錯時平滑地收斂到正確位置。 - 數值參考:以 6m/s 的速度移動時,只要猜錯 300ms 就會偏差 1.8m。 - 圖表上:偶爾隨機飆高(外插時間、位置校正距離) - 查看位置:記錄以外插繪製其他角色的時間,以及新封包到達後修正位置的距離 - 符合的跡象:每次封包中斷的區段,外插時間都毫無上限地拉長,之後的校正距離達到數 m - 不符合的跡象:外插很短就停止、卻仍出現瞬移時,是封包遺失或延遲本身太大,要查線路與路由 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Interpolation and extrapolation (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/interpolation.html) · Unity · 下一個快照沒有準時到達時,沿同一方向與速度繼續移動的外插經常猜錯,因此設有上限(Unity 預設 20 tick,60Hz 下約 1/3 秒) - [Peeking into VALORANT's Netcode](https://technology.riotgames.com/news/peeking-valorants-netcode) · Riot Games · 用推測填補晚到或遺失的資料,一旦猜錯就會與伺服器不一致,角色會跳動或像滑行般被校正 #### cg-predict · 用戶端預測不一致 · Prediction mismatch / reconciliation 自己的用戶端已經先顯示移動,伺服器卻算出不同的結果時,自己的角色就會被拉回去。 - 為什麼 → 於是 → 畫面上:用戶端在伺服器確認前先移動(預測) → 伺服器對碰撞、移動速度、buff 的計算結果不同,或沒收到指令 → 確認到達時,自己的角色被往回拉 - 症狀:拉回/因素:遺失, 延遲 - 誰會遇到:只有我/何時:移動中/切換地圖時, 偶爾隨機發生 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:使用與伺服器相同的移動程式碼、重複傳送輸入、平滑地校正。伺服器:使用與用戶端相同的移動程式碼,重複收到的輸入依輸入編號過濾,只處理一次。 - 數值參考:被拉回的距離是「偏差時間 × 移動速度」。只要掉了幾個指令,就會差 1~3m。 - 圖表上:偶爾隨機飆高(伺服器校正(預測失敗)次數) - 查看位置:記錄伺服器送出的位置校正次數與校正距離。Unreal 計算伺服器的 ClientAdjustPosition 校正次數,Unity Netcode for Entities 計算因預測失敗而回溯重算的次數 - 符合的跡象:校正集中在回報拉回的時間點,在特定 buff、地形、位移技能下校正距離反覆偏大 - 不符合的跡象:校正只在封包遺失嚴重時集中出現,屬於輸入封包遺失(線路)。沒有校正、只有其他角色看起來被拉回時,是「過度外插(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 · 用戶端與伺服器以相同的模擬程式碼預測;與伺服器狀態不同(預測失敗)時回溯重算,畫面上會看到校正 - [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 · 為了因應封包遺失,連同最新輸入重複送出前幾個 tick 的輸入 - [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 catch-up / spiral of death 停過一次之後集中補算落後的部分,又因為這些計算而再度落後。 - 為什麼 → 於是 → 畫面上:以固定間隔執行的遊戲模擬停了一次 → 把積欠的每一步集中在一個畫格內算完 → 長畫格接連出現而飆高,或碰到上限使整個世界變慢 - 症狀:卡頓, 快轉, 慢動作/因素:停滯 - 誰會遇到:只有我/何時:偶爾隨機發生, 人潮湧入時 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:設定每個畫格的追趕上限,剩餘的時間以內插處理。 - 圖表上:偶爾隨機飆高(畫格時間、每畫格的固定步數) - 查看位置:在開發版本的 profiler 同時查看一個畫格內固定步長執行了幾次(Unity 看 FixedBehaviourUpdate 等 FixedUpdate 階段標記的數量)與畫格時間 - 符合的跡象:一個長畫格之後,接連出現多次執行步長的長畫格;碰到上限(Unity 的 Maximum Allowed Timestep)時,遊戲時間比實際時間走得慢 - 不符合的跡象:長畫格只出現一次就結束時,是「畫格時間尖峰」或「用戶端垃圾回收」 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:Unity 的物理計算(FixedUpdate)是典型的固定步長(預設 0.02 秒,每秒 50 次)。Time 設定中的 Maximum Allowed Timestep(一個畫格最多追趕的時間,預設約 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 次);畫格一長,一個畫格內就要執行好幾次物理步長,負擔變大 - [Handling variation in time](https://docs.unity3d.com/Manual/time-handling-variations.html) · Unity · Maximum Allowed Timestep 預設值 1/3 秒(0.3333333);即使停了 1 秒,遊戲時間也只前進 0.333 秒,這個上限用來防止追趕步長再拖慢畫格的惡性循環 - [Profiler markers reference](https://docs.unity3d.com/Manual/profiler-markers.html) · Unity · FixedBehaviourUpdate:MonoBehaviour.FixedUpdate 的執行區段;物理標記在 FixedUpdate 階段呼叫 #### cg-clock · 時鐘同步誤差 · Clock sync error 用戶端推估的伺服器時間不準時,內插時間點與冷卻判定就會出現偏差。 - 為什麼 → 於是 → 畫面上:只在連線時對時一次,ping 改變了也不重新校正 → 內插時間點與冷卻結束時間跟伺服器不一致 → 對手偶爾頓一下;冷卻明明結束了,技能卻被拒絕 - 症狀:卡頓, 吃指令/回檔/因素:延遲 - 誰會遇到:只有我/何時:開越久越嚴重, 偶爾隨機發生 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:定期做時間同步(量測往返時間後校正)、逐步校正以免時間突然跳動、用 monotonic clock 量測經過時間,不依賴電腦的系統時間。 - 圖表上:緩慢爬升(推估伺服器時間的誤差) - 查看位置:定期記錄用戶端推估的伺服器時間,與伺服器放在封包裡送來的伺服器時間(tick 編號)之間的差距 - 符合的跡象:誤差在連線後隨時間越來越大,或在電腦時鐘被校正的瞬間一次跳開,同一時期技能被拒、頓一下的回報增加 - 不符合的跡象:誤差一直很小、技能仍被拒絕時,屬於伺服器判定或延遲的問題 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:用電腦的日期時間(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 裡,或直接傳給著色器 → 開著的時間越久,float 能表示的最小差距就越大 → 只有連續開了好幾天的用戶端,角色、動畫、流動特效會不停顫動,重新開啟就恢復正常 - 症狀:卡頓/因素:抖動 - 誰會遇到:只有我/何時:開越久越嚴重 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:經過時間以 double(64 位元)或整數保存、傳給著色器的時間定期歸零重算、進行連續數天的長時間自動化測試。 - 數值參考:32 位元 float 的有效位數只有 7 位多一點,開了一天(約 86,400 秒)時間解析度約為 8ms,大約是 60FPS 一個畫格(16.7ms)的一半;一週則約 60ms,比一個畫格還大。 - 圖表上:緩慢爬升(依開啟時長統計的顫動回報) - 查看位置:收集顫動回報時一併取得用戶端已開啟的時間,並比較重新啟動前後。開發端則把遊戲啟動時間的值設成已經過好幾天的值來測試 - 符合的跡象:只在開了好幾天的用戶端顫動,重新啟動就消失,開得越久越嚴重 - 不符合的跡象:一開啟就顫動時,是「沒有內插緩衝或緩衝太短」或「計時器解析度」 - 確認方式:在玩家端環境確認 - 深入了解:在開著自動打怪、一連好幾天都不關的手機 MMO 特別常見。Unity 的 Time.time 也是 float,因此 Unity 另外提供 double 型別的 Time.timeAsDouble,並建議改用它。 - 出處: - [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 張畫格 → 輸入反映到畫面上也要多花這麼多時間 → ping 很低,操作卻沉重遲鈍 - 症狀:輸入延遲, 卡頓/因素:延遲 - 誰會遇到:只有我/何時:一直都有 - 主要負責:遊戲開發團隊(用戶端開發)/協同:外部(外部) - 遊戲開發團隊要做的事:支援低延遲模式、縮短畫格佇列、提供略低於更新率的畫格上限選項、手機開啟 frame pacing 功能。 - 外部要做的事:引導玩家搭配可變更新率螢幕,把畫格上限設得略低於更新率,並開啟顯示卡驅動程式的低延遲模式。 - 數值參考:60Hz 下每張 16.7ms。CPU 比 GPU 或螢幕週期快、佇列的三張(DirectX 11 預設值)全部塞滿時,會多出 50ms。在雙緩衝 V-Sync 下,花了 17ms 的畫格要等到下一次畫面更新的時間點(33.3ms),這段期間上一個畫格會再顯示一次。 - 圖表上:一開始就一直偏高(從輸入到畫面的延遲) - 查看位置:切換 V-Sync、低延遲模式、畫格上限,比較 PresentMon 的 MsClickToPhotonLatency、MsAllInputToPhotonLatency(從滑鼠、鍵盤輸入到送出至螢幕)與 DisplayLatency。MsPCLatency(電腦收到輸入後到送往螢幕)只有在遊戲送出 PC Latency 事件時才會記錄 - 符合的跡象:開啟 V-Sync 或沒有畫格上限時,這段延遲增加一兩個畫格(數十 ms),在低延遲模式或略低於更新率的畫格上限下減少。ping 沒有變化 - 不符合的跡象:電腦內部的延遲很低、操作仍慢半拍時,是「顯示器、輸入裝置與畫格生成的延遲」;ping 很高時,屬於網路端 - 確認方式:在玩家端環境確認 - 深入了解:垂直同步(V-Sync)是只在螢幕更新畫面的瞬間才送出新畫格的設定。畫面撕裂會消失,但等待那個瞬間會讓輸入延遲,而且 FPS 掉到 60 以下時,會在 60 與 30 之間來回切換而出現卡頓。可變更新率(VRR)螢幕會配合畫格完成的時機更新畫面,縮短這段等待。手機也會發生同樣的情況。30FPS 的遊戲如果無法把畫格平均地配合 60Hz 螢幕送出,雖然平均是 30FPS,每張畫格停留的時間卻像 49、16、33ms 這樣忽長忽短,造成卡頓(Android 開發文件的例子)。可以用 Android 的 Frame Pacing 程式庫(讓畫格輸出間隔保持均勻)或引擎中的同類選項來改善。 - 出處: - [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) · 60Hz 螢幕沒有新畫格時會再顯示上一個畫格;以 30FPS 遊戲的畫格時間變成 49、16、33ms 這樣忽長忽短為例 - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · MsPCLatency(電腦收到輸入到送往螢幕)、MsClickToPhotonLatency(滑鼠點擊到畫面)、MsAllInputToPhotonLatency(鍵盤、滑鼠輸入到畫面)、DisplayLatency(提交畫格到送往螢幕) - [PresentMon Console Application (README-ConsoleApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-ConsoleApplication.md) · Intel · MsPCLatency 要應用程式送出 PC Latency 事件才會記錄(--track_pc_latency),MsAllInputToPhotonLatency 以鍵盤、滑鼠輸入為準 #### cg-leak · 用戶端記憶體洩漏 · Client memory leak 開得越久,記憶體用量越大,遊戲越來越慢,最後被強制關閉。 - 為什麼 → 於是 → 畫面上:切換地圖時,貼圖、UI、特效沒有釋放 → GC 越來越頻繁,OS 記憶體不足而發生 swap → 玩了幾個小時後越來越卡頓,最後強制結束(在玩家看來像斷線) - 症狀:卡頓, 斷線/因素:停滯 - 誰會遇到:只有我/何時:開越久越嚴重 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:在切換地圖時量測記憶體用量、找出沒有釋放的貼圖、UI、特效並修正、進行長時間自動化測試(soak test)。 - 圖表上:緩慢爬升(遊戲處理程序的記憶體) - 查看位置:用效能監視器記錄數小時的 Process(遊戲)\Private Bytes。行動裝置看 Android ApplicationExitInfo 的結束原因(REASON_LOW_MEMORY)與 iOS jetsam 報告 - 符合的跡象:每次切換地圖記憶體都上升且不會下降,開得越久,卡頓與強制結束越多 - 不符合的跡象:記憶體持平、只是開得越久越顫動時,是「float 時間精度損失」 - 確認方式:在玩家端環境確認 - 深入了解:手機主要靠壓縮記憶體撐住。即使如此記憶體仍不夠時,OS 會直接把遊戲關掉(閃退)。RAM 越小的裝置越先被關掉。 - 出處: - [Memory allocation among processes](https://developer.android.com/topic/performance/memory-management) · Android (Google) · Android 把記憶體壓縮到 zRAM 撐住,不夠時由 low memory killer 結束處理程序;前景 App 被結束時看起來像閃退 - [Identifying high-memory use with jetsam event reports](https://developer.apple.com/documentation/xcode/identifying-high-memory-use-with-jetsam-event-reports) · Apple · iOS 在記憶體壓力無法解除時會強制結束 App(jetsam),App 超過各自的記憶體上限就會成為結束對象 - [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 結束了 App 處理程序(不支援的裝置會回報為 REASON_SIGNALED、SIGKILL) #### cg-crash · 用戶端閃退 · Client crash 遊戲因未處理的錯誤而關閉。在玩家看來像斷線,但伺服器是正常的。 - 為什麼 → 於是 → 畫面上:null 參考、記憶體不足、顯示卡驅動程式錯誤 → 遊戲處理程序被強制結束 → 回報「閃退了」。同一時間其他人都正常 - 症狀:斷線/因素:停滯 - 誰會遇到:只有我/何時:做特定動作時, 偶爾隨機發生 - 主要負責:遊戲開發團隊(用戶端開發)/協同:外部(外部) - 遊戲開發團隊要做的事:收集 crash report、依裝置與驅動程式統計、從最集中的錯誤開始修正。 - 外部要做的事:集中在特定顯示卡驅動程式版本時,引導玩家更新驅動程式。 - 圖表上:只有部分偏高(閃退次數(依裝置、顯示卡驅動程式、build)) - 查看位置:依裝置、驅動程式、build 查看 crash report 與 Android vitals 的當機率。玩家電腦則查看事件檢視器「應用程式」log 中的事件識別碼 1000(發生錯誤的模組名稱)與「Display driver stopped responding and has recovered」紀錄 - 符合的跡象:回報斷線的時間點有閃退紀錄,同一時間同一伺服器的其他玩家正常。集中在特定裝置、驅動程式版本、模組 - 不符合的跡象:沒有閃退紀錄、只是連線中斷時,是「NAT mapping 過期」或線路問題 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Crashes](https://developer.android.com/topic/performance/vitals/crash) · Android (Google) · 當機(crash)指 App 因未處理的例外或訊號(SIGSEGV 等)意外結束,由 Play Console 的 Android vitals 彙總 - [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 · 「應用程式」log 的事件識別碼 1000 是實際的當機紀錄,包含發生錯誤的應用程式與模組名稱(Faulting module name) #### cg-anticheat · 遊戲安全模組(反作弊)檢查 · Anti-cheat scan and heartbeat 為了防範外掛而與遊戲一起執行的安全模組會定期檢查。檢查太重,或與安全伺服器往來的心跳封包(定期確認連線存活的訊號)延遲時,就會卡頓或斷線。 - 為什麼 → 於是 → 畫面上:安全模組定期檢查遊戲記憶體、執行中的程式與驅動程式 → 檢查期間遊戲執行緒停住,或心跳封包沒能準時送出 → 以固定間隔頓一下,嚴重時跳出安全錯誤訊息並斷線 - 症狀:卡頓, 定格, 斷線/因素:停滯 - 誰會遇到:只有我/何時:固定週期, 剛登入/維護剛結束, 偶爾隨機發生 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:把重量級檢查移到遊戲執行緒之外分批執行、比較各安全模組版本的卡頓與閃退統計(剛更新後集中發生時轉交安全模組廠商)。伺服器:容許心跳封包延遲一兩次。 - 數值參考:輕量的檢查通常不到 1ms,但在遊戲執行緒上執行的重量級檢查,依實作不同,有時一次會占用數十~數百 ms。 - 圖表上:固定週期飆高(畫格時間、反作弊踢出次數) - 查看位置:從 PresentMon 畫格時間量出飆高的間隔,並把伺服器收到的反作弊踢出原因(EOS 為 ClientActionReason 的 AuthenticationFailed / Authentication Timed Out 等)依安全模組版本與配備彙總 - 符合的跡象:與遊戲狀況無關、以固定間隔反覆短暫停住;安全模組剛更新後,特定配備的卡頓與驗證逾時踢出增加 - 不符合的跡象:與安全模組版本無關、所有配備都以相同間隔發生時,是「用戶端垃圾回收」或「背景處理程序占用 CPU」 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:安全模組以驅動程式的形式深入 OS,有時會與防毒軟體、overlay、其他遊戲的安全模組衝突。安全模組剛更新後、只有特定配備集中出現卡頓或閃退回報時,要先懷疑這個原因。 - 出處: - [Using the Anti-Cheat Interfaces](https://dev.epicgames.com/docs/epic-online-services/trust-and-safety/anti-cheat-interfaces/using-anti-cheat) · Epic Games · 伺服器在規定時間(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 核心 → 遊戲執行緒等待排程 → 畫格延遲,已接收封包的處理也跟著變慢 - 症狀:卡頓, 快轉/因素:停滯 - 誰會遇到:只有我/何時:偶爾隨機發生, 固定週期 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:調整遊戲執行緒的優先順序、在出現卡頓時的 log 中一併記錄整台電腦的 CPU 使用率,以分辨是否為其他程式造成。 - 外部要做的事:引導玩家開啟 Windows 遊戲模式,並在遊戲中關閉不必要的程式(防毒掃描、Windows Update、直播軟體、瀏覽器影片)。 - 數值參考:Windows 通常一次把核心交給一個執行緒數 ms~數十 ms。排程上只要被擠掉一次,就會少掉一個畫格。 - 圖表上:偶爾隨機飆高(整台電腦的 CPU 使用率、畫格時間) - 查看位置:把工作管理員「處理程序」索引標籤的 CPU 欄、效能監視器的 Processor Information(_Total)\% Processor Time 與 PresentMon 畫格時間一起記錄。懷疑是防毒軟體時,用 New-MpPerformanceRecording 錄製,再用 Get-MpPerformanceReport 查看掃描時間長的檔案與處理程序 - 符合的跡象:卡頓時其他程式(防毒掃描、更新、直播軟體)的 CPU 使用往上衝,或遊戲資料夾的檔案排在掃描時間的前幾名。關掉該程式或將其設為排除項目後就消失 - 不符合的跡象:CPU 使用率很低,整個畫面卻頓一下且聲音出現雜音時,是「網路卡省電、驅動程式問題」(DPC 延遲) - 確認方式:在玩家端環境確認 - 深入了解:Windows 會給前景視窗的程式稍高的優先順序,但要做的工作比核心還多時,遊戲也得等。比起占用 CPU,防毒軟體更常透過「即時監控」介入。遊戲每次開啟檔案都會被掃描,讀取資源當下的停頓就會拉長。 - 出處: - [Multitasking](https://learn.microsoft.com/en-us/windows/win32/procthread/multitasking) · Microsoft · Windows 給每個執行緒一段時間片段,用完就交給下一個執行緒;時間片段約 20ms(依 OS 與 CPU 而異) - [Priority Boosts](https://learn.microsoft.com/en-us/windows/win32/procthread/priority-boosts) · Microsoft · 前景視窗的處理程序,優先順序會調到背景處理程序以上 - [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 查看對掃描時間影響最大的檔案、路徑與處理程序 - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · 以 FrameTime(畫格之間的 CPU 時間)錄下每個畫格的時間 #### co-power · 省電模式、過熱降頻 · Power saving, thermal throttling 筆電的電池模式、手機的省電模式、裝置過熱,都會讓 CPU、GPU 速度下降。過熱的特徵是一開始正常,過了一段時間才變慢。 - 為什麼 → 於是 → 畫面上:處於電池或省電模式,或裝置發燙 → 依裝置不同,CPU、GPU 時脈降低 30~50% → 省電模式一開就發生,過熱則在玩了幾分鐘~20 分鐘左右後,FPS 下降並出現卡頓 - 症狀:卡頓, 輸入延遲/因素:停滯 - 誰會遇到:只有我/何時:開越久越嚴重, 一直都有 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:自動調整畫質選項、以畫格上限控制發熱、依 OS 回報的熱狀態等級(iOS 的 thermalState、Android 的熱狀態 API)提前調低選項、在有兩顆顯示晶片的筆電上於執行檔中標示使用獨立顯示卡(匯出 NvOptimusEnablement、AmdPowerXpressRequestHighPerformance)。 - 外部要做的事:引導玩家關閉省電模式、筆電建議接上電源;遇到「筆電規格很好 FPS 卻很低」的回報時,確認遊戲在哪一顆顯示晶片上執行,並引導玩家在 Windows 的圖形設定中把遊戲指定為高效能 GPU。 - 圖表上:緩慢爬升(FPS、CPU 與 GPU 時脈) - 查看位置:用 PresentMon 把 CPUFrequency、GPUFrequency、CPUTemperature、GPUTemperature 與畫格時間一起記錄 20~30 分鐘,並用工作管理員「處理程序」索引標籤的「GPU 引擎」欄確認遊戲在哪一顆顯示晶片上執行。行動裝置則把 Android 的熱狀態 API(getThermalHeadroom、熱狀態)與 iOS thermalState 和 FPS 一起記錄 - 符合的跡象:溫度上升、時脈開始下降的時間點起 FPS 跟著下降,或只有在電池、省電模式下時脈偏低。也可能是遊戲正在內建顯示晶片上執行 - 不符合的跡象:時脈與溫度不變、FPS 卻下降時,是「背景處理程序占用 CPU」或「用戶端記憶體洩漏」 - 確認方式:在玩家端環境確認 - 深入了解:有兩顆顯示晶片的筆電,有時會為了省電讓遊戲在較慢的內建顯示晶片上執行。遇到「筆電規格很好 FPS 卻很低」的回報,先確認遊戲在哪一顆顯示晶片上執行。 - 出處: - [Thermal API](https://developer.android.com/games/optimize/adpf/thermal) · Android (Google) · 裝置只能在有限時間內維持高效能,之後會因發熱而降頻;建議依熱狀態提前降低負載 - [thermalState](https://developer.apple.com/documentation/foundation/processinfo/thermalstate-swift.property) · Apple · iOS 回報的目前熱狀態等級,等級升高時 App 應減少資源使用 - [Selecting the Best Graphics Device to Run a 3D Intensive Application](https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/) · AMD · 在有兩顆顯示晶片的筆電上以內建顯示晶片執行時,60FPS 的遊戲可能掉到 30FPS;匯出 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 · 工作管理員有欄位顯示各處理程序的 GPU 使用率,以及該數值屬於哪個 GPU 與引擎 #### co-timer · 計時器解析度 · Timer resolution (Windows 15.6ms) Windows 的預設計時器以 15.6ms 為單位,所以「只休息 1ms」實際上會等到下一個計時器週期,最長拉長到 15.6ms。 - 為什麼 → 於是 → 畫面上:用 Sleep(短暫等待)的方式實作畫格限制與封包傳送 → OS 只以 15.6ms 為單位喚醒 → 畫格間隔與輸入傳送間隔忽長忽短 - 症狀:卡頓/因素:抖動 - 誰會遇到:只有我/何時:一直都有 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:使用高解析度計時器、改以事件或 V-Sync 為基礎控制節奏,取代 Sleep 等待。 - 數值參考:以 15.6ms 為單位無法對準 16.7ms 的間隔,畫格間隔會在 15.6ms 與 31.2ms 之間來回跳動。 - 圖表上:一開始就一直偏高(畫格間隔分布) - 查看位置:查看 PresentMon 的 MsBetweenPresents(畫格間隔)分布,以及 powercfg /energy 報告中的「Platform Timer Resolution」項目(變更過計時器解析度的處理程序) - 符合的跡象:畫格間隔集中在 15.6ms、31.2ms 這類 15.6ms 的倍數,且遊戲沒有要求更高的計時器解析度 - 不符合的跡象:間隔平均分散時,比起計時器,更可能是「背景處理程序占用 CPU」或畫格負載 - 確認方式:在玩家端環境確認 - 深入了解:以前的 Windows 只要有一個程式把計時器改成 1ms,就會套用到所有程式,所以才有「開著瀏覽器遊戲會變順」的說法。從 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 · 一般計時器的精確度是系統時鐘 tick 間隔,預設 15.6ms;高解析度計時器為 1ms - [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 · 高解析度可等候計時器的旗標 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 · 預設系統計時器解析度 15.6ms;可在能源報告的「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 切到背景 · App suspended in background 為了看通知而把 App 暫時切到背景時,OS 會在幾秒後暫停(suspend)App,這段期間伺服器就會切斷玩家的連線。 - 為什麼 → 於是 → 畫面上:為了看訊息、接電話把遊戲切到背景 → 遊戲引擎暫停遊戲進行,OS 很快也會停止 App 與網路 → 回到遊戲時早已斷線,只能重新連線 - 症狀:斷線/因素:停滯, 遺失 - 誰會遇到:只有我/何時:閒置一段時間後, 做特定動作時 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:回到前景時不要等待已中斷的連線,直接用 session token 自動重新連線(不必重新登入就能接續),並一次取得最新狀態同步。伺服器:心跳封包中斷時先清理連線,但角色 session 在短暫的寬限時間內保留(不立刻踢出),在這段時間內重新連線就用 session token 接續。 - 數值參考:遊戲引擎通常在切到背景的瞬間就暫停。iOS 會在幾秒後暫停 App,即使申請了額外時間,通常也在數十秒內暫停;Android 14 以上則會在 App 離開畫面約 10 秒後將其凍結(freeze)。 - 圖表上:連線同時大量中斷(斷線次數(心跳逾時)、App 暫停紀錄) - 查看位置:以 session ID 對照用戶端 log 中 App 暫停與恢復的時間(Unity 為 OnApplicationPause)和伺服器端的斷線原因與時間。Android 也一併查看 ApplicationExitInfo 留下的處理程序結束原因(REASON_LOW_MEMORY 等) - 符合的跡象:伺服器因心跳逾時斷線之前,用戶端剛進入暫停狀態,恢復後隨即重新連線 - 不符合的跡象:App 在前景時就斷線的話,是「NAT mapping 過期」、「電信業者共用 IP(CGNAT)」或「Wi-Fi ↔ LTE/5G 切換」 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:手機記憶體不足時,有時會直接結束切到背景的遊戲。這就是切去相機或付款、驗證 App 再回來時,遊戲會從頭重新啟動的原因。越低階的裝置越常見。 - 出處: - [Extending your app’s background execution time](https://developer.apple.com/documentation/uikit/extending-your-app-s-background-execution-time) · Apple · 切到背景時 applicationDidEnterBackground 有 5 秒可用,之後很快就會暫停;需要更多時間時以 beginBackgroundTask 申請(剩餘時間為 backgroundTimeRemaining) - [Cached apps freezer](https://source.android.com/docs/core/perf/cached-apps-freezer) · Android (Google) · Android 14 以上會在 App 處理程序進入快取狀態 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 · App 暫停或恢復執行時,對所有 MonoBehaviour 送出 OnApplicationPause(true/false) - [ApplicationExitInfo](https://developer.android.com/reference/android/app/ApplicationExitInfo) · Android (Google) · REASON_LOW_MEMORY:系統的 low memory killer 結束了 App 處理程序(不支援的裝置會回報為 REASON_SIGNALED、SIGKILL) #### co-netswitch · Wi-Fi ↔ LTE/5G 切換 · Network switch changes IP 走出家門時 Wi-Fi 斷掉、改用 LTE/5G,自己的 IP 位址會改變,原本的連線就此失效。 - 為什麼 → 於是 → 畫面上:Wi-Fi 訊號變弱,切換到行動網路 → 自己的 IP 位址改變,用舊位址建立的連線無法再收送資料 → 短暫停住後斷線,或重新連線 - 症狀:定格, 斷線/因素:遺失 - 誰會遇到:只有我/何時:移動中/切換地圖時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發), 基礎設施團隊(網路基礎設施) - 遊戲開發團隊要做的事:伺服器:位址改變時也用 session token 接續成同一位玩家,並立刻清理舊位址的連線;評估位址改變也能延續連線的協定(QUIC 的連線遷移等)。用戶端:偵測到網路切換時,不要等心跳逾時,直接用 session token 重新連線。 - 基礎設施團隊要做的事:使用 QUIC 連線遷移時,把負載平衡器設定成依連線 ID 選擇伺服器,不依位址與 port(依位址與 port 選擇時,位址改變後的封包會被送到別台伺服器)。 - 圖表上:連線同時大量中斷(斷線與重新連線次數、重新連線時改變的 IP) - 查看位置:在伺服器連線 log 中找出同一個 session token 從不同 IP 重新連線的紀錄,並對照用戶端預設網路變更回呼(registerDefaultNetworkCallback)的時間 - 符合的跡象:斷線後重新連線的 IP 從 Wi-Fi(家用線路)網段變成行動電信業者網段,或反過來,且在那之前剛收到網路切換回呼 - 不符合的跡象:IP 沒變卻斷線時,是「基地台換手(移動中)」或「行動網路訊號弱、收訊死角」 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Read network state](https://developer.android.com/training/basics/network-ops/reading-network-state) · Android (Google) · 預設網路改變後,新連線會走新網路,舊網路上的連線最後會被強制中斷;用 registerDefaultNetworkCallback 偵測切換 - [RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport](https://www.rfc-editor.org/rfc/rfc9000) · IETF · 以連線 ID 讓 IP 位址與 port 改變時連線仍能維持(第 9 章);只依位址與 port 分流的負載平衡器,可能把位址改變後的封包送到別台伺服器(5.2.3 節) - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · TCP 連線由兩端的 socket(位址、port)組合來識別 #### co-security · 安全軟體的封包檢查 · Antivirus / firewall inspection 防毒軟體、防火牆檢查每一個封包時延遲會增加,太過頭還會把遊戲誤判為攻擊而封鎖。 - 為什麼 → 於是 → 畫面上:安全軟體逐一檢查收送的封包 → 每個封包都多出延遲,檢查來不及時封包會被丟棄 → ping 不規則地飆高,或連線被封鎖 - 症狀:卡頓, 連不上/無限讀取/因素:抖動, 遺失 - 誰會遇到:只有我/何時:一直都有, 剛登入/維護剛結束 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:維護安全軟體相容性清單,安裝時在 Windows 防火牆加入遊戲的例外規則。 - 外部要做的事:引導玩家在安全軟體中把遊戲加入例外;遊戲被誤判為攻擊時,請安全軟體廠商修正誤判。 - 數值參考:正常情況下,封包檢查所花的時間通常不到 1ms。問題出在檢查模組塞車或有 bug 時,以及把遊戲通訊誤判為攻擊時。 - 圖表上:只有部分偏高(RTT、連線失敗(依玩家)) - 查看位置:暫時關閉安全軟體,或把遊戲加入例外後比較。Windows 在稽核原則中開啟 Audit Filtering Platform Connection、Audit Filtering Platform Packet Drop 後,「安全性」log 會留下 5157(連線遭封鎖)、5152(封包遭封鎖);丟棄的封包數用效能監視器的 WFPv4\Packets Discarded/sec 查看 - 符合的跡象:留下前往遊戲伺服器位址的連線或封包遭封鎖的紀錄,或關閉安全軟體後 ping 飆高、無法連線的情況消失 - 不符合的跡象:與安全軟體無關、同一個家裡的其他裝置也一樣時,問題在分享器或線路 - 確認方式:在玩家端環境確認 - 出處: - [About Windows Filtering Platform](https://learn.microsoft.com/en-us/windows/win32/fwp/about-windows-filtering-platform) · Microsoft · 透過 Windows 網路堆疊中的 hook 與篩選引擎允許或封鎖封包的架構,外部安全廠商可以插入自己的篩選模組(callout) - [Windows Firewall Rules](https://learn.microsoft.com/en-us/windows/security/operating-system-security/network-security/windows-firewall/rules) · Microsoft · 預設會封鎖連入連線,所以 App 需要例外規則,通常由 App 安裝程式建立規則 - [Address false positives/negatives in Microsoft Defender for Endpoint](https://learn.microsoft.com/en-us/defender-endpoint/defender-endpoint-false-positives-negatives) · Microsoft · 正常程式被誤判為威脅(誤判)時,設定例外並提交給 Microsoft 分析的流程 - [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 · 事件 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 · 事件 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 · 接收緩衝區溢位 · Socket receive buffer overflow 遊戲太忙,太晚從 socket(OS 提供的網路收送介面)取出封包時,OS 緩衝區就會溢位。 - 為什麼 → 於是 → 畫面上:畫格延誤,遊戲太晚讀取 socket → OS 接收緩衝區滿了,UDP 直接丟棄,TCP 則縮小接收視窗讓對方停止傳送 → 瞬移(UDP)或快轉(TCP) - 症狀:瞬移, 快轉/因素:遺失, 停滯 - 誰會遇到:只有我/何時:人潮湧入時 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:專用的接收執行緒、調整緩衝區大小(SO_RCVBUF)。 - 數值參考:預設接收緩衝區依 OS 與設定為數十~數百 KB。人多的地方,更新量有時每秒可達數百 KB。 - 圖表上:隨人數/負載上升(UDP 接收緩衝區丟棄數、畫格時間) - 查看位置:把 Windows 效能監視器的 Microsoft Winsock BSP\Dropped Datagrams(因 socket 接收緩衝區不足而丟棄的 UDP 數)與 UDPv4\Datagrams Received Errors 跟畫格時間一起記錄,遊戲端則計算收到封包序號中的缺漏 - 符合的跡象:人多的地方或長畫格剛結束時 Dropped Datagrams 增加,同一瞬間遊戲序號出現缺漏。同一時間線路端沒有封包遺失 - 不符合的跡象:Dropped Datagrams 沒變、只有序號缺漏時,是傳輸路徑上的封包遺失 - 確認方式:在玩家端環境確認 - 出處: - [socket(7) — Linux manual page](https://man7.org/linux/man-pages/man7/socket.7.html) · Linux man-pages · SO_RCVBUF 是 socket 接收緩衝區的最大大小,預設值由 rmem_default、最大值由 rmem_max 決定(Android 也是 Linux kernel) - [SOL_SOCKET Socket Options (Winsock2.h)](https://learn.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options) · Microsoft · Windows SO_RCVBUF:每個 socket 保留給接收用的緩衝區空間 - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · 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 · Microsoft Winsock BSP 計數器集的 Dropped Datagrams、Dropped Datagrams/sec:UDP 抵達速度快過 App 處理速度,或接收 socket 緩衝區不足而丟棄的數量 - [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 - 症狀:定格, 卡頓/因素:停滯 - 誰會遇到:只有我/何時:移動中/切換地圖時, 偶爾隨機發生 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:減少記憶體用量,剩餘記憶體不多時顯示警告。 - 外部要做的事:告知玩家最低配備,並引導玩家在遊戲中關閉其他程式(瀏覽器分頁等)。 - 圖表上:偶爾隨機飆高(硬分頁錯誤、記憶體用量) - 查看位置:把效能監視器的 Memory\Pages Input/sec(為了解決硬分頁錯誤而從磁碟讀入的分頁數)與工作管理員「效能」索引標籤的記憶體用量、已認可量跟畫格時間一起記錄 - 符合的跡象:停住的瞬間 Pages Input/sec 往上衝,記憶體幾乎全滿。關掉瀏覽器等其他程式後就消失 - 不符合的跡象:記憶體還有餘裕、Pages Input/sec 也很平靜時,是「主執行緒同步載入、著色器編譯」或「儲存裝置太慢,資源串流跟不上」 - 確認方式:在玩家端環境確認 - 出處: - [Introduction to the page file](https://learn.microsoft.com/en-us/troubleshoot/windows-client/performance/introduction-to-the-page-file) · Microsoft · 分頁檔是磁碟上的檔案,用來把不常用、已修改過的記憶體分頁從 RAM 移出 - [Working Set](https://learn.microsoft.com/en-us/windows/win32/memory/working-set) · Microsoft · 存取不在 RAM 中的分頁時會發生分頁錯誤;硬分頁錯誤要從分頁檔等磁碟位置讀回來才能解決 - [Chapter 12 - Detecting Memory Bottlenecks](https://learn.microsoft.com/en-us/previous-versions/cc749872(v=technet.10)) · Microsoft · Memory\Pages Input/sec:為了解決分頁錯誤而從磁碟讀入的分頁數(硬分頁錯誤) #### co-vram · 顯示記憶體(VRAM)不足 · VRAM over-commit 畫質選項需要的記憶體比顯示卡記憶體還大時,OS 會把貼圖移到電腦的主記憶體再搬回來,因此出現卡頓。 - 為什麼 → 於是 → 畫面上:高貼圖選項,加上人多的地方各式各樣的裝備與特效,讓顯示卡記憶體全滿 → OS 把暫時沒用到的貼圖移到電腦的主記憶體,需要時再透過較慢的 PCIe 匯流排搬回來 → 每次出現新場景或新角色都會頓一下,貼圖有一段時間是模糊的 - 症狀:卡頓, 定格/因素:停滯 - 誰會遇到:只有我/何時:人潮湧入時, 移動中/切換地圖時 - 主要負責:遊戲開發團隊(用戶端開發)/協同:外部(外部) - 遊戲開發團隊要做的事:依顯示卡記憶體大小設定選項預設值、超出記憶體預算時自動調低貼圖品質、人多的地方簡化角色貼圖。 - 外部要做的事:引導玩家調低貼圖選項,同時開兩個用戶端時要把選項調得更低。 - 數值參考:顯示卡記憶體每秒可讀取數百 GB,但與電腦主記憶體之間往來的 PCIe 匯流排依世代約為每秒 16~64GB,慢了十倍以上。 - 圖表上:碰到上限後持平(專用 GPU 記憶體、共用 GPU 記憶體) - 查看位置:把工作管理員「效能」索引標籤 GPU 項目中的專用 GPU 記憶體、共用 GPU 記憶體圖表(也可以在「詳細資料」索引標籤加上各處理程序的欄位)跟 PresentMon 畫格時間一起查看 - 符合的跡象:專用 GPU 記憶體貼著上限持平、共用 GPU 記憶體持續增加的期間經常頓一下,調低貼圖選項後就消失 - 不符合的跡象:專用記憶體還有餘裕時,是「儲存裝置太慢,資源串流跟不上」或「主執行緒同步載入、著色器編譯」 - 確認方式:在玩家端環境確認 - 深入了解:Windows 工作管理員的 GPU 項目中,「專用 GPU 記憶體」全滿、「共用 GPU 記憶體」增加,就是這個狀態。在同一台電腦上開兩個用戶端會更快用滿(見「記憶體/VRAM 不足導致串流失敗」項目)。 - 出處: - [Residency](https://learn.microsoft.com/en-us/windows/win32/direct3d12/residency) · Microsoft · 處理程序可使用的顯示記憶體有預算,超出時 kernel 會把獨立 GPU 的部分 heap 移到電腦主記憶體(這是最後手段,建議做好預算管理) - [GPUs in the task manager](https://devblogs.microsoft.com/directx/gpus-in-the-task-manager/) · Microsoft · 工作管理員的專用 GPU 記憶體是顯示卡的 VRAM,共用 GPU 記憶體是 GPU 與 CPU 共用的電腦主記憶體 - [CUDA C++ Best Practices Guide](https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html) · NVIDIA · 顯示記憶體頻寬(V100 為 898GB/s)遠大於 PCIe x16 第 3 代(16GB/s),因此建議減少與電腦主記憶體之間的傳輸 - [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 → 搜尋期間收送短暫停止 → ping 以非常固定的間隔(例如每 60 秒)飆高 - 症狀:卡頓, 瞬移/因素:抖動 - 誰會遇到:只有我/何時:固定週期 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:在遊戲中要求減少無線掃描的模式(Android 為低延遲 Wi-Fi 模式 WIFI_MODE_FULL_LOW_LATENCY,Windows 為 WlanSetInterface 的媒體串流模式。依裝置與驅動程式不同,有時沒有效果)。 - 外部要做的事:引導玩家改用有線連線、調整定位服務與 Wi-Fi 自動搜尋設定、更新無線網路驅動程式。 - 數值參考:通常每次數十~數百 ms。規律得太過頭時,先懷疑這個原因。 - 圖表上:固定週期飆高(到分享器的 RTT) - 查看位置:在遊戲中用 ping /t 量測分享器位址(ipconfig 顯示的預設閘道)幾分鐘,記下飆高的間隔。再改用有線做同樣的量測 - 符合的跡象:到分享器的 ping 以非常固定的間隔(例如 60 秒)飆高數十~數百 ms,改用有線就消失 - 不符合的跡象:飆高的間隔不規則時,是「Wi-Fi 干擾與訊號減弱」。到分享器都正常、只有分享器之後的區段飆高時,問題在線路或電信業者區段 - 確認方式:在玩家端環境確認 - 出處: - [WDI low latency connection quality](https://learn.microsoft.com/en-us/windows-hardware/drivers/network/wdi-low-latency-connection-quality) · Microsoft · 掃描與漫遊會讓無線晶片離開目前連線的頻道,所以低延遲模式會限制離開頻道的時間與掃描 - [WlanSetInterface function (wlanapi.h)](https://learn.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlansetinterface) · Microsoft · 在 Windows 上開啟或關閉背景掃描(wlan_intf_opcode_background_scan_enabled)與媒體串流模式的 API - [Wi-Fi low-latency mode](https://source.android.com/docs/core/connect/wifi-low-latency) · Android (Google) · 低延遲模式會關閉 Wi-Fi 省電,掃描與漫遊設定的最佳化則依裝置製造商的實作而不同 - [ping](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ping) · Microsoft · /t:持續送出 echo 要求,直到手動停止 - [ipconfig](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ipconfig) · Microsoft · 不加參數執行時,會顯示各介面卡的 IPv4、IPv6 位址與預設閘道 #### co-driver · 網路卡省電、驅動程式問題 · NIC power saving, driver bugs 有線網路卡或 Wi-Fi 晶片在封包與封包之間進入省電狀態時,要重新喚醒需要時間。 - 為什麼 → 於是 → 畫面上:網路裝置的省電功能開著,或驅動程式太舊 → 喚醒(wake-up)延遲,偶爾裝置會重新啟動 → 不規則的延遲,少數情況會停住數秒 - 症狀:卡頓, 定格/因素:抖動, 遺失 - 誰會遇到:只有我/何時:閒置一段時間後, 偶爾隨機發生 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:Android 用戶端在遊戲中要求低延遲 Wi-Fi 模式(WIFI_MODE_FULL_LOW_LATENCY),關閉 Wi-Fi 省電。 - 外部要做的事:引導玩家更新網路驅動程式、在裝置管理員中關閉網路裝置的省電;整個畫面頓一下且聲音出現雜音時,引導玩家用 LatencyMon 找出造成問題的驅動程式。 - 圖表上:偶爾隨機飆高(DPC 與 ISR 時間、到分享器的 RTT) - 查看位置:用 Windows 效能記錄器(WPR)錄製,在 Windows 效能分析器(WPA)的 DPC/ISR 圖表中找出執行時間長的驅動程式(Module 欄),並在裝置管理員中確認網路介面卡的電源管理(省電)設定 - 符合的跡象:頓一下的時間點,網路驅動程式的 DPC、ISR 連續執行好幾 ms,或關閉省電後不規則的延遲消失 - 不符合的跡象:DPC 很短、關閉省電後仍一樣時,是「Wi-Fi 干擾與訊號減弱」或「Wi-Fi 背景掃描」 - 確認方式:在玩家端環境確認 - 深入了解:驅動程式為了處理中斷(interrupt)而長時間占用 CPU 時(Windows 稱為 DPC 延遲),遊戲執行緒在這段期間也用不到該核心。這時 CPU 使用率很低,整個畫面卻會頓一下,聲音也會出現雜音。用 LatencyMon 這類工具可以找出是哪個驅動程式,Wi-Fi 與有線網路驅動程式是常見的原因。 - 出處: - [Introduction to NDIS Selective Suspend](https://learn.microsoft.com/en-us/windows-hardware/drivers/network/ndis-selective-suspend) · Microsoft · Windows 可以讓閒置的網路介面卡進入低耗電狀態(選擇性暫停) - [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) · Android 低延遲 Wi-Fi 模式會由框架明確關閉 Wi-Fi 省電 - [CPU Analysis](https://learn.microsoft.com/en-us/windows-hardware/test/wpt/cpu-analysis) · Microsoft · WPA 的 DPC/ISR 圖表:DPC、ISR 不間斷執行的各區段時間,以及該函式所在的模組(Module) #### co-other-apps · 同一台裝置上的其他 App 占用頻寬 · Other apps saturating the link 雲端同步、大型下載、遊戲更新在同一台電腦上執行時,遊戲封包得在佇列中等待。 - 為什麼 → 於是 → 畫面上:其他 App 把上傳、下載用到滿 → 遊戲封包堆積在電腦與分享器的佇列中 → ping 暴增、輸入延遲、快轉 - 症狀:輸入延遲, 快轉/因素:延遲, 抖動 - 誰會遇到:只有我, 同一個家/何時:偶爾隨機發生 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:我方的啟動器與更新程式在遊戲中暫停背景下載或限制速度。 - 外部要做的事:引導玩家限制下載速度,並在遊戲中關閉自動更新。 - 圖表上:隨人數/負載上升(RTT、電腦收送量) - 查看位置:把效能監視器的 Network Interface\Bytes Sent/sec、Bytes Received/sec 與 ping 一起記錄。做法與開著 ping、刻意進行大量傳輸的 bufferbloat 測試相同 - 符合的跡象:下載或上傳接近線路速度的期間,ping 上升數十~數百 ms,停止傳輸後馬上恢復 - 不符合的跡象:電腦收送量很低、ping 卻上升時,是同一個家裡其他裝置造成的「Bufferbloat(分享器佇列)」或電信業者區段 - 確認方式:在玩家端環境確認 - 出處: - [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 的下載(傳遞最佳化)預設會依可用頻寬動態調整,也可以設定背景與前景下載的頻寬上限 - [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 · 開著 ping 的同時用測速把線路塞滿,ping 上升就是 bufferbloat #### co-unfocused · 視窗最小化或非作用中時的處理限制 · Minimized / unfocused window throttling 切到其他視窗或把遊戲最小化時,遊戲與 Windows 會為了省電讓遊戲跑得比較慢。回到遊戲時,累積的封包會一口氣湧入,或者早已斷線。 - 為什麼 → 於是 → 畫面上:用 Alt+Tab 切到其他視窗,或把遊戲最小化 → 遊戲看不見的期間,FPS 會大幅降低或停止,Windows 也會調低看不見的程式的優先順序 → 切回來的瞬間出現快轉,切出去太久則會斷線 - 症狀:快轉, 卡頓, 斷線/因素:停滯 - 誰會遇到:只有我/何時:做特定動作時, 閒置一段時間後 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:視窗看不見時,封包接收與心跳封包仍在獨立執行緒中持續處理;確認引擎的「在背景執行」設定;切回來時一次同步到最新狀態。 - 數值參考:視窗看不見時把 FPS 降到 5~10,一個畫格就是 100~200ms。每個畫格處理一次封包的遊戲,讀取封包也會晚同樣的時間。 - 圖表上:中斷後一次湧入(畫格間隔(切換視窗前後)、已處理的封包數) - 查看位置:開著 PresentMon 試著按 Alt+Tab 或最小化,查看視窗看不見時的畫格間隔。在遊戲 log 中記錄視窗焦點改變的時間,與斷線原因對照 - 符合的跡象:視窗看不見的期間畫格間隔拉長到 100ms 以上,或紀錄中斷;切回來的瞬間一次處理累積的封包而出現快轉。切出去太久時因心跳逾時而斷線 - 不符合的跡象:視窗一直開在前景也一樣時,是「背景處理程序占用 CPU」或網路端問題 - 確認方式:在玩家端環境確認 - 深入了解:Windows 11 不保證最小化或完全被遮住、也沒有發出聲音的視窗程式能取得 1ms 計時器。以電池運作的筆電會把這類程式降到最省電的速度,若 CPU 混用不同種類的核心,有時也會把它們放到較慢的效率核心上執行。同一台電腦的兩個用戶端中只有背景那一個異常時,請一併參考「背景視窗的處理限制」項目。 - 出處: - [Quality of Service](https://learn.microsoft.com/en-us/windows/win32/procthread/quality-of-service) · Microsoft · 既看不見也聽不到的視窗程式屬於 Low QoS,使用電池時會以最有效率的 CPU 速度與效率核心排程 - [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 · overlay 程式干擾 · Overlays and screen hooks 通訊軟體、啟動器、錄影、FPS 顯示程式為了在遊戲畫面上疊加自己的 UI,會介入遊戲的渲染流程(hook)。每個畫格的工作量因此增加,偶爾還會與遊戲衝突,造成頓一下或遊戲被強制關閉。 - 為什麼 → 於是 → 畫面上:通訊軟體、遊戲啟動器、顯示卡工具、錄影程式的 overlay 開著 → 每次把畫格輸出到畫面時,overlay 都會介入疊加自己的 UI → 畫格一點一點變慢,通知跳出的瞬間頓一下,或出現畫面錯誤、強制關閉(玩家看起來像斷線) - 症狀:卡頓, 定格, 斷線/因素:停滯 - 誰會遇到:只有我/何時:一直都有, 偶爾隨機發生 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:在閃退報告與卡頓 log 中一併收集執行中的 overlay 清單。 - 外部要做的事:收到回報時,引導玩家關閉所有 overlay 再測一次。 - 圖表上:只有部分偏高(畫格時間、閃退次數(開啟 overlay 的玩家)) - 查看位置:關閉所有 overlay,比較同一場景的 PresentMon 畫格時間;有閃退時,查看事件檢視器中事件識別碼 1000 的錯誤模組名稱(Faulting module name) - 符合的跡象:關閉 overlay 後頓一下、畫面錯誤消失,或閃退的錯誤模組是 overlay 程式的 DLL - 不符合的跡象:關閉所有 overlay 後仍一樣時,是顯示卡驅動程式或「用戶端閃退」 - 確認方式:在玩家端環境確認 - 深入了解:只有特定的人卡頓或遊戲被關掉,又無法用配備解釋時,先懷疑 overlay 與遊戲安全模組的衝突。 - 出處: - [Steam Overlay (Steamworks Documentation)](https://partner.steamgames.com/doc/features/overlay) · Valve · Steam overlay 會自動 hook 到透過 Steam 啟動的遊戲中,這種方式有時會讓遊戲在渲染 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 · 「應用程式」log 中的事件識別碼 1000 含有錯誤模組名稱(Faulting module name),有時其他模組的損壞會讓 Windows 模組被記為錯誤模組 #### co-display-input · 顯示器、輸入裝置與畫格生成的延遲 · Display, input device and frame generation latency ping 正常、操作卻很沉重時,可能是電視的影像處理、無線控制器或畫格生成功能在輸入與畫面之間增加了延遲。 - 為什麼 → 於是 → 畫面上:電視的遊戲模式沒開、使用藍牙或無線控制器,或開啟了畫格生成(DLSS、FSR 畫格生成) → 電視在進行畫質處理時會延後輸出畫格,無線輸入會因傳輸週期與干擾而晚到,畫格生成則要等下一個畫格才能做出中間的畫格 → ping 與 FPS 數字都很好,按下按鍵後卻要過一會兒才反映在畫面上,形成輸入延遲 - 症狀:輸入延遲/因素:延遲 - 誰會遇到:只有我/何時:一直都有 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:把畫格生成設為可選選項,並說明開啟後輸入延遲可能增加;使用畫格生成時一併整合顯示卡廠商的低延遲功能(NVIDIA Reflex、AMD Anti-Lag 2);在遊戲中顯示電腦端從輸入到畫面的延遲;Android TV 與機上盒版本用 Window.setPreferMinimalPostProcessing(true) 要求電視進入低延遲模式(ALLM)。 - 外部要做的事:引導玩家開啟電視、螢幕的遊戲模式(ALLM),競技類內容改用有線控制器並關閉畫格生成,藍牙裝置放近一點,Wi-Fi 改用 5GHz。 - 數值參考:60Hz 畫面光是傳送一個畫格就要 16.7ms,120Hz 則要 8.3ms。舊款 Xbox 控制器每 8ms 讀取並傳送一次輸入。電視影像處理增加的延遲因機種而異,很難用一個數字說明,遊戲模式就是減少這些處理的設定。畫格生成方面,AMD 建議在生成前的畫格率達 60FPS 以上時使用。 - 圖表上:一開始就一直偏高(從輸入到畫面的延遲) - 查看位置:開啟、關閉畫格生成,比較 PresentMon 的 MsAllInputToPhotonLatency(從鍵盤、滑鼠輸入到送往螢幕為止),並用 FrameType(只在驅動程式或 SDK 回報時才會記錄)查看是否混有生成的中間畫格。這個值不包含控制器的無線區段與電視內部的處理,這部分要切換電視遊戲模式、有線控制器來比較 - 符合的跡象:ping 正常,關閉畫格生成後從輸入到畫面的延遲減少,或改用電視遊戲模式、有線控制器後體感延遲消失 - 不符合的跡象:這些設定全部改過仍一樣,且 ping 偏高或飆高時,是網路端問題。電腦端延遲因垂直同步或畫格佇列而偏高時,是「垂直同步(V-Sync)與渲染佇列」 - 確認方式:在玩家端環境確認 - 深入了解:網路延遲可以從 ping 看出來,這種延遲卻不會反映在 ping 上。因此遇到「ping 很低卻很 lag」的回報時,要先確認這裡。畫格生成會讓畫面上的 FPS 數字提高到約兩倍,但要做出中間的畫格,就得等下一個真正的畫格,所以輸入反映到畫面所需的時間會變長(AMD 表示這在設計上就會增加延遲)。藍牙裝置與 Wi-Fi 使用同樣的 2.4GHz 頻段,受到干擾時輸入可能中斷或跳動。會加大電腦內部延遲的垂直同步與渲染佇列,請參考「垂直同步(V-Sync)與渲染佇列」項目。 - 出處: - [Auto Low Latency Mode (ALLM)](https://www.hdmi.org/spec21sub/autolowlatencymode) · HDMI Licensing Administrator · ALLM 讓裝置自動把顯示器切換到低延遲模式(通常是遊戲模式);低延遲模式下,電視會停止部分影像處理以減少延遲 - [Xbox Series X: What’s the Deal with Latency?](https://news.xbox.com/en-us/2020/03/16/xbox-series-x-latency/) · Microsoft · 輸入延遲是控制器 → 主機 → HDMI → 電視這整條路徑的總和;舊款控制器每 8ms 讀取並傳送一次輸入;透過 HDMI 傳送一個畫格的時間在 60Hz 為 16.6ms、120Hz 為 8.3ms;透過 ALLM 自動切換電視遊戲模式 - [AMD FSR 3 game integrations out now + more details for developers](https://gpuopen.com/news/fsr3-in-games-technical-details/) · AMD · 畫格內插在設計上會增加延遲;建議在內插前畫格率 60 以上時使用;60FPS 輸入最高可輸出 120FPS - [AMD FSR Frame Generation](https://gpuopen.com/amd-fsr-framegeneration/) · AMD · 畫格生成建議內插前達 60FPS 以上(應避免低於 30FPS);AMD Radeon Anti-Lag 2 會協調 CPU 與 GPU 的工作,降低系統延遲 - [NVIDIA DLSS](https://developer.nvidia.com/rtx/dlss) · NVIDIA · DLSS 畫格生成的設計是搭配 NVIDIA Reflex(低延遲功能)維持反應速度 - [Resolve Wi-Fi and Bluetooth issues caused by wireless interference](https://support.apple.com/en-us/102319) · Apple · 無線干擾會造成 Wi-Fi 與藍牙裝置斷斷續續、效能下降,而藍牙與 Wi-Fi 使用同樣的 2.4GHz 頻段 - [PresentMon Capture Application (README-CaptureApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-CaptureApplication.md) · Intel · MsAllInputToPhotonLatency(從輸入到畫面的延遲)、DisplayLatency(從提交畫格到送往螢幕)、FrameType(區分 App 繪製的畫格與驅動程式或 SDK 內插的畫格) - [PresentMon Console Application (README-ConsoleApplication.md)](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README-ConsoleApplication.md) · Intel · MsAllInputToPhotonLatency 以鍵盤、滑鼠輸入為準;FrameType 要由 App 或驅動程式送出 Intel-PresentMon 事件才會記錄(--track_frame_type) - [Window.setPreferMinimalPostProcessing](https://developer.android.com/reference/android/view/Window#setPreferMinimalPostProcessing(boolean)) · Android (Google) · 遊戲這類延遲很重要的視窗會要求顯示器進行最少的影像處理;以 HDMI 連接時會送出 ALLM、Game Content Type 訊號,把電視切換到低延遲模式 ### L3 家用網路(10 個原因) #### hn-wifi · Wi-Fi 干擾與訊號減弱 · Wi-Fi interference, weak signal 訊號弱或有干擾時,無線區段要反覆重送好幾次,封包抵達的時間就會忽快忽慢。 - 為什麼 → 於是 → 畫面上:牆壁、距離、微波爐、藍牙、鄰居的分享器讓無線訊號品質變差 → 無線區段傳送失敗 → 重傳好幾次 → 封包抵達忽快忽慢(抖動),角色走走停停,嚴重時封包遺失而出現瞬移 - 症狀:卡頓, 瞬移, 拉回/因素:抖動, 遺失 - 誰會遇到:只有我, 同一個家/何時:偶爾隨機發生, 一直都有 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:依線路狀態自動調整內插緩衝長度,抖動或封包遺失嚴重時在畫面上顯示網路狀態。 - 外部要做的事:引導玩家改用有線連線、使用 5GHz/6GHz、移動分享器的位置。 - 數值參考:每重傳一次約多花 1~4ms。訊號弱時會以較低速率重送好幾次,還要等頻道空出來,有時會飆高 50~200ms。平均 ping 看起來正常,這正是容易誤判的地方。 - 圖表上:偶爾隨機飆高(到分享器的 RTT) - 查看位置:用 ping /t 量測到分享器位址(ipconfig 顯示的預設閘道)幾分鐘,並用 netsh wlan show networks mode=bssid 查看自己分享器的訊號強度與頻道。在同一個位置改用有線比較 - 符合的跡象:從到分享器的 ping 開始就不規則地飆高數十~數百 ms,偶爾有封包遺失,且訊號強度偏低。改用有線或靠近分享器就消失 - 不符合的跡象:到分享器都很平穩、只有分享器之後的區段飆高時,問題在線路或電信業者區段。只以固定間隔飆高時,是「Wi-Fi 背景掃描」 - 確認方式:在玩家端環境確認 - 深入了解:Mesh Wi-Fi 的分享器(節點)之間以無線相連(無線回程,backhaul)時,負責中繼的節點在接收期間無法傳送,還要和使用同一頻道的前後區段分享傳送機會,壅塞時處理量可能下降、延遲增加。有回程專用無線頻段的產品影響可能較小;節點之間改用有線(乙太網路)連接,這一段就不再走無線。電力線網路橋接器(PLC)也和 Wi-Fi 一樣,採用先確認媒介空閒再傳送的方式(CSMA/CA),品質會隨家電產生的雜訊與家電的開關而隨時變化,可能造成重傳與抖動。 - 實際案例:ffxiv-2021 - 出處: - [Resolve Wi-Fi and Bluetooth issues caused by wireless interference](https://support.apple.com/en-us/102319) · Apple · 微波爐、無線電話等干擾來源;Wi-Fi 與藍牙使用同樣的 2.4GHz 頻段;建議改用 5GHz 並選擇干擾較少的頻道 - [RFC 8325: Mapping Diffserv to IEEE 802.11](https://www.rfc-editor.org/rfc/rfc8325) · IETF · 802.11 的 CSMA/CA:只在頻道空閒時傳送,忙碌時延後到空出來,再額外等待一段隨機退避時間 - [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,中位數仍超過 200ms - [ping](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ping) · Microsoft · /t:持續送出 echo 要求,直到手動停止 - [ipconfig](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ipconfig) · Microsoft · 不加參數執行時,會顯示各介面卡的 IPv4、IPv6 位址與預設閘道 - [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:顯示每個看得到的 Wi-Fi 的 BSSID、訊號強度、頻道與無線標準 - [Capacity of Ad Hoc Wireless Networks (MobiCom 2001)](https://pdos.csail.mit.edu/papers/grid:mobicom01/paper.pdf) · ACM · 以 802.11 無線多次中繼時,節點在接收期間無法傳送,前後區段也會互相干擾,單線中繼路徑的處理量理論上會降到 3 分之 1(模擬中約 7 分之 1) - [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)的商用設備以類似 Wi-Fi 的 CSMA/CA 傳送,短時間的不公平可能讓抖動變大;頻道品質會隨家電產生的雜訊與家電的開關(以數分鐘~數小時為單位)而變化 #### hn-channel · Wi-Fi 頻道壅塞 · Crowded Wi-Fi channel 在公寓大樓這類有數十台分享器的地方,大家共用同一個頻道,只能等待傳送機會。 - 為什麼 → 於是 → 畫面上:數十台分享器使用同一個 2.4GHz 頻道 → 要傳送就得等其他裝置傳完、頻道空出來 → 大家回到家的晚間時段抖動(封包抵達間隔忽長忽短)增加,出現卡頓 - 症狀:卡頓, 輸入延遲/因素:抖動, 延遲 - 誰會遇到:同一個家/何時:晚間尖峰時段 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:抖動增加時自動加長內插緩衝。 - 外部要做的事:引導玩家使用 5GHz/6GHz、較不擁擠的頻道或有線連線。 - 圖表上:只在特定時段偏高(到分享器的 RTT 與抖動) - 查看位置:用 netsh wlan show networks mode=bssid 查看周圍 Wi-Fi 的頻道與訊號強度,並在晚上與白天量測到分享器的 ping 進行比較 - 符合的跡象:在 2.4GHz 同一頻道上偵測到很多周圍的分享器,到分享器的抖動只在晚間時段變大。改用 5GHz/6GHz 或較不擁擠的頻道後減少 - 不符合的跡象:與時段無關也會飆高時,是「Wi-Fi 干擾與訊號減弱」。到分享器正常、只有晚上分享器之後的區段變差時,是「尖峰時段 peering 區段壅塞」 - 確認方式:在玩家端環境確認 - 出處: - [Recommended settings for Wi-Fi routers and access points](https://support.apple.com/en-us/102766) · Apple · 使用同一頻道的其他分享器與裝置是干擾來源;2.4GHz 建議使用 20MHz 頻寬,5GHz/6GHz 比較不用擔心干擾 - [RFC 8325: Mapping Diffserv to IEEE 802.11](https://www.rfc-editor.org/rfc/rfc8325) · IETF · 802.11 在頻道忙碌時會延後傳送到空出來,經過隨機退避後才送出(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:顯示每個看得到的 Wi-Fi 的 BSSID、訊號強度、頻道與無線標準 - [ping](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ping) · Microsoft · /t:持續送出 echo 要求,直到手動停止 #### hn-bufferbloat · Bufferbloat(分享器佇列) · Bufferbloat 家人上傳影片或下載大型檔案時,分享器佇列會堆積相當於數百 ms 的封包,遊戲封包也得排在後面等待。 - 為什麼 → 於是 → 畫面上:家人上傳影片、雲端備份、自己的直播推流、大量下載把線路塞滿 → 分享器或數據機把滿出來的封包堆進很大的佇列 → 遊戲封包也排在佇列後面等待,ping 暴增到數百 ms - 症狀:輸入延遲, 快轉, 瞬移/因素:延遲, 抖動 - 誰會遇到:同一個家, 只有我/何時:偶爾隨機發生, 晚間尖峰時段 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:ping 突然升到數百 ms 時在畫面上顯示網路狀態(提示可能有同一條線路上的大量傳輸)。 - 外部要做的事:引導玩家使用有 SQM(fq_codel、CAKE)或 QoS 的分享器,把 SQM 速率設為線路速度的 90~95%(這樣佇列才會產生在分享器內,才有效果),並限制上傳速度。 - 數值參考:上傳 10Mbps 的線路配上 1MB 緩衝區,佇列最長會累積到相當於 800ms 的量。 - 圖表上:隨人數/負載上升(RTT、線路上傳與下載用量) - 查看位置:開著 ping 用測速把線路塞滿,或使用量測負載下延遲的網頁測試(Bufferbloat.net 的說明)。與分享器介面上的上傳、下載用量一起查看 - 符合的跡象:上傳或下載塞滿線路的期間 ping 升到數百 ms,傳輸結束後恢復(負載下延遲超過 50ms 就值得懷疑)。開啟 SQM 後消失 - 不符合的跡象:線路很閒 ping 卻飆高時,是「Wi-Fi 干擾與訊號減弱」或「線路品質不良」 - 確認方式:在玩家端環境確認 - 深入了解:上傳方向特別容易塞住,因為 Cable 寬頻與行動網路的上傳頻寬往往比下載窄得多。光纖頻寬充足的家庭則是 Wi-Fi 區段成為瓶頸,同樣的情況會發生在分享器的無線佇列。遊戲封包很小,幾乎不占頻寬,但同樣得在佇列中等待。只有上傳方向塞住時,延遲的只有自己的輸入,其他人的動作正常。手機上,同一支手機的照片備份與 App 更新會塞滿手機數據機與基地台的佇列,造成同樣的情況。 - 出處: - [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%),把瓶頸從電信業者設備移到分享器內才有效果 - [SQM (Smart Queue Management)](https://openwrt.org/docs/guide-user/network/traffic-shaping/sqm) · OpenWrt · 下載、上傳速度輸入實測值的 90%,佇列演算法建議用 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 的延遲;改善無線佇列後,負載下的延遲約降為 10 分之 1 - [Tests for Bufferbloat](https://www.bufferbloat.net/projects/bloat/wiki/Tests_for_Bufferbloat/) · Bufferbloat.net · 開著 ping 用測速把線路塞滿,ping 上升就是 bufferbloat;負載下延遲超過 50ms(或評等低於 B)時建議採取對策 #### hn-nat · NAT mapping 過期 · NAT mapping timeout 分享器會把一段時間沒有封包往來的閒置連線從 NAT 表中刪除。這是閒置一段時間後一有動作就斷線的常見原因。 - 為什麼 → 於是 → 畫面上:分享器把「內部裝置 ↔ 外部伺服器」的連線記錄在 NAT 表(位址轉換表)中 → 一段時間沒有封包就從表中刪除(UDP 通常為 30~120 秒) → 伺服器的封包進不了家中網路,造成斷線 - 症狀:斷線/因素:遺失 - 誰會遇到:只有我, 同一個家/何時:閒置一段時間後 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:以最短閒置逾時一半以下的間隔送出心跳封包(UDP mapping 只有從家中送出的封包才能確實更新,所以由用戶端送),斷線時自動重新連線。伺服器:回應心跳封包,一段時間沒收到就先清理連線;mapping 被刪除導致外部位址、port 改變時,也要用 session token(連線時取得的識別碼)確認是同一位玩家並接續。 - 圖表上:連線同時大量中斷(斷線次數(心跳逾時)、斷線前的閒置時間) - 查看位置:收集伺服器端的斷線原因,以及斷線前該連線最後一次封包往來後經過的時間(閒置時間),查看其分布。測試時把 UDP 封包間隔逐步拉長到 30 秒、60 秒、120 秒,量出回應中斷的間隔 - 符合的跡象:只有閒置中的連線會斷,且閒置時間集中在 30~120 秒這類特定值之後。把心跳間隔縮得比這更短就消失 - 不符合的跡象:移動中也會斷線時,問題在線路或路由。只在特定行動電信業者集中於較短的值時,是「電信業者共用 IP(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 分鐘以上;由內往外的封包必須能更新 mapping,由外往內的封包更新則為選用 - [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、BT(torrent)開啟數千條連線 → 分享器的 CPU 與 session 表飽和 → 封包處理延遲、封包遺失,新連線失敗 - 症狀:卡頓, 連不上/無限讀取, 斷線/因素:遺失, 抖動 - 誰會遇到:同一個家/何時:開越久越嚴重, 偶爾隨機發生 - 主要負責:外部(外部) - 外部要做的事:引導玩家重新啟動分享器(暫時解法)、更換分享器、整理會開大量連線的程式(P2P、BT)。 - 圖表上:碰到上限後持平(分享器 CPU 與連線數、到分享器的 RTT) - 查看位置:在分享器管理介面查看 CPU 使用率、連線(session)數、連線中的裝置數(分享器有支援時),並比較重新啟動前後到分享器本身的 ping - 符合的跡象:連線數多時,從到分享器的 ping 開始就飆高或遺失封包,新連線也失敗。重新啟動後正常一陣子,之後又變差 - 不符合的跡象:到分享器正常、只有分享器之後的區段變差時,問題在線路或電信業者區段 - 確認方式:在玩家端環境確認 - 出處: - [An Experimental Study of Home Gateway Characteristics (IMC 2010)](https://conferences.sigcomm.org/imc/2010/papers/p260.pdf) · ACM · 家用分享器對單一伺服器 port 允許的 TCP 連線數為 16~約 1,024 條(中位數 135),低價設備的處理量有時只有數 Mbps - [Netfilter Conntrack Sysfs variables](https://docs.kernel.org/networking/nf_conntrack-sysctl.html) · Linux kernel · Linux 連線追蹤表的最大項目數(nf_conntrack_max)與各狀態保留時間的預設值 #### hn-handover · 基地台換手(移動中) · Cellular handover 搭公車、捷運移動時,切換基地台的期間通訊會中斷。 - 為什麼 → 於是 → 畫面上:移動中連線的基地台改變 → 通常只空白數十 ms,但訊號差導致切換失敗時,有時會中斷數百 ms~數秒 → 停住後瞬移,時間長的話會斷線 - 症狀:定格, 瞬移, 斷線/因素:遺失 - 誰會遇到:只有我/何時:移動中/切換地圖時 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發), 遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:設定能撐過短暫中斷的逾時,快速重新連線。伺服器:設定中斷數秒也不立刻踢出的逾時,重新連線時接續同一個 session。 - 外部要做的事:向玩家說明移動中(公車、捷運)斷線是基地台切換造成的。 - 圖表上:中斷後一次湧入(已接收封包數、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 · 換手期間無法收送資料時間的需求值:同頻 27.5ms,異頻 40~60ms - [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 · 商用網路實測的換手延遲:4G↔4G 平均 30ms,5G(NSA)cell 之間平均 108ms #### hn-rrc · RRC 狀態切換延遲(行動網路無線省電) · Radio state promotion (RRC) 手機一段時間沒有通訊時,會把無線連線降到低耗電狀態,下一個封包要送出時得重新拉高,因此變慢。 - 為什麼 → 於是 → 畫面上:短暫沒有通訊時,手機把無線連線切換到省電狀態 → 要送出下一個封包,必須重新拉高連線 → 閒置一段時間後的第一個動作特別慢 - 症狀:輸入延遲/因素:延遲 - 誰會遇到:只有我/何時:閒置一段時間後 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:以輕量的週期性傳送維持活躍狀態(以耗電為代價)。 - 數值參考:LTE 通常在約 10 秒沒有通訊後進入省電,重新拉高需要數十~數百 ms(實測例:約 0.3~0.6 秒)。3G 則要 1 秒以上。 - 圖表上:只有部分偏高(閒置後第一個請求的 RTT(行動網路)) - 查看位置:把遊戲內的 RTT 依與前一次通訊的間隔分組查看。比較在行動網路上閒置超過 10 秒後送出的第一個封包,與連續送出的封包的 RTT - 符合的跡象:在行動網路上,只有閒置後送出的第一個封包慢了數百 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 · 實測 LTE 網路的省電切換計時器(tail)為 10 秒,從省電重新拉高的延遲中位數 435ms(25~75%:319~558ms),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 秒,待機 → 全功率 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 · 從待機狀態切換到活躍狀態的控制平面延遲需求值為不到 100ms(不含傳呼與有線區段) #### hn-weak-cell · 行動網路訊號弱、收訊死角 · Weak cellular signal 在電梯、地下室、建築物深處,重傳會增加、速度下降,最後斷線。 - 為什麼 → 於是 → 畫面上:移動到訊號弱的地方 → 無線重傳增加、速度下降、瞬間斷訊 → 抖動與封包遺失造成卡頓、瞬移,最後斷線 - 症狀:卡頓, 瞬移, 斷線/因素:抖動, 遺失 - 誰會遇到:只有我/何時:移動中/切換地圖時 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:改善重新連線流程,顯示網路品質。 - 外部要做的事:向玩家說明這是在訊號弱的地方(電梯、地下室、建築物深處)發生的問題。 - 圖表上:只有部分偏高(RTT、封包遺失(依行動網路玩家)) - 查看位置:確認回報斷線時的地點(電梯、地下室、建築物內)與手機的訊號顯示,並在訊號好的地方重複同樣的動作比較 - 符合的跡象:只有在訊號弱的地方 RTT 與封包遺失增加並斷線,移到訊號好的地方就消失 - 不符合的跡象:訊號良好也一樣時,問題在電信業者區段或伺服器端 - 確認方式:在玩家端環境確認 - 出處: - [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 用實體層與 MAC 層的重傳掩蓋無線區段的遺失;可用頻寬會依訊號強度等因素以秒為單位大幅變動 #### hn-5g-flip · 5G↔LTE 頻繁切換(5G 涵蓋邊緣) · 5G NSA / LTE switching 在 5G 訊號弱的建築物內或 5G 涵蓋邊緣,手機會頻繁在 5G 與 LTE 之間切換,每次切換 ping 都會飆高或通訊短暫中斷。 - 為什麼 → 於是 → 畫面上:身處 5G 訊號時好時壞的地方(建築物內、5G 涵蓋邊緣) → 手機隨時在 5G 與 LTE 之間切換,每次都會出現短暫空白 → 即使靜止不動,ping 也會不規則地飆高,偶爾定格、瞬移 - 症狀:卡頓, 瞬移, 定格/因素:抖動, 遺失 - 誰會遇到:只有我/何時:偶爾隨機發生, 移動中/切換地圖時 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:抖動增加時自動加長內插緩衝,在出現延遲時的 log 中一併記錄網路類型(5G、LTE)的變化以區分原因。 - 外部要做的事:引導玩家在設定中改成 LTE 優先模式比較看看,並建議使用 Wi-Fi。 - 數值參考:每次切換數十~數百 ms。韓國的 5G 大多採用與 LTE 搭配使用的方式(NSA),因此很容易只有 5G 這一側時連時斷。 - 圖表上:偶爾隨機飆高(RTT、網路類型(5G、LTE)變化) - 查看位置:把手機設定改成 LTE 優先,在同一個位置比較。用戶端把 Android TelephonyDisplayInfo 的網路類型顯示(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 · NSA 5G 把控制交給 LTE,切換 5G cell 時要先放掉 5G、經由 LTE 再重新連上:平均 108ms(4G→5G 為 80ms),涉及 5G 的切換剛結束時 TCP 處理量減少 73~83% - [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 上,且可以或已經與 5G(NR)雙連結(EN-DC)時的網路類型顯示 #### hn-captive · 公共 Wi-Fi/公司網路限制 · Captive portal, restrictive network 咖啡廳 Wi-Fi 的登入頁面或公司防火牆會擋掉遊戲連線。 - 為什麼 → 於是 → 畫面上:尚未通過登入頁面認證,或防火牆封鎖遊戲 port 與 UDP → 連線嘗試本身被擋,或只有部分通過 → 無法連線,或能登入卻進不了遊戲 - 症狀:連不上/無限讀取/因素:遺失 - 誰會遇到:只有我/何時:剛登入/維護剛結束 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發), 遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:被擋時提示原因(尚未通過登入頁面認證、UDP 遭封鎖等),UDP 被擋時自動切換到替代路徑。伺服器:提供 TCP 443 這類替代路徑。 - 外部要做的事:引導玩家使用公共 Wi-Fi 時先完成登入頁面認證,在公司網路這類受限的地方改用其他網路。 - 圖表上:只有部分偏高(連線失敗次數(依網路)) - 查看位置:請連線失敗的玩家改用行動數據等其他網路連線看看,並在伺服器連線 log 中查看 UDP 的第一個封包是否抵達,以及能否透過 TCP 443 替代路徑連上 - 符合的跡象:只在特定 Wi-Fi(咖啡廳、公司)失敗,換其他網路就馬上連上。尚未通過登入頁面認證,或只有 UDP 到不了伺服器 - 不符合的跡象:任何網路都失敗時,問題在帳號、伺服器或「DNS 故障與延遲」。特定國家或電信業者整體都失敗時,是「國家/電信業者層級的 UDP 限制與封包檢測」 - 確認方式:在玩家端環境確認 - 出處: - [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 為基礎的 App 必須準備 TCP(TLS)替代路徑 ### L4 網際網路線路(14 個原因) #### isp-distance · 傳播延遲(物理距離) · Propagation delay 光在光纖中 1 秒也只能前進約 20 萬 km。伺服器在遠方,效能再好也會慢。 - 為什麼 → 於是 → 畫面上:伺服器位在遠方(海外伺服器、其他大陸) → 距離越遠,往返時間越長(每 1,000km 至少 10ms) → 所有動作都有固定的輸入延遲,判定上也吃虧 - 症狀:輸入延遲/因素:延遲 - 誰會遇到:特定地區/電信業者/何時:一直都有 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:基礎設施團隊(網路基礎設施), 遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:物理定律無法用程式碼改變,只能緩解:提供地區選擇讓玩家挑選較近的地區伺服器,並用延遲補償(回溯)減少判定上的劣勢。 - 基礎設施團隊要做的事:伺服器設備/OS:在玩家多的地區設置各地區的伺服器。網路:在玩家附近設置接入據點(edge),選擇繞路較少的線路與路由。 - 數值參考:首爾–東京約 30ms,首爾–新加坡約 75ms,首爾–美國西岸約 140ms,首爾–歐洲約 230~270ms(往返,以實際路徑為準)。歐洲方向在直線上幾乎沒有大型海底電纜,必須繞經東南亞與蘇伊士或繞經美國,所以延遲比距離推算的長得多。 - 圖表上:一開始就一直偏高(RTT(依國家/地區)) - 查看位置:依連線 IP 標上國家,查看各國的 RTT 分布;再從該地區的雲端區域(region)VM 或 RIPE Atlas probe(依國家、ASN 挑選)用 ping、traceroute 量測到伺服器的延遲 - 符合的跡象:遠方國家的 RTT 不分時段一直偏高,且數值接近依距離算出的最小延遲(每 1,000km 往返 10ms)與公開的延遲統計 - 不符合的跡象:遠高於距離能解釋的數值時是「繞遠路的路由」,只在晚上升高時是「尖峰時段 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(秒速約 20 萬 km,1,000km 往返 10ms) - [Azure network round-trip latency statistics](https://learn.microsoft.com/en-us/azure/networking/azure-network-latency) · Microsoft Azure · 以首爾(Korea Central)為起點的實測往返中位數:東京 30ms、新加坡 68ms、美國西岸 124~136ms、歐洲 234~244ms - [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 · 依國家、地區、ASN、位址區段挑選 RIPE Atlas 量測的 probe,執行 ping、traceroute #### isp-satellite · 衛星網路(低軌/同步軌道) · Satellite internet (LEO, GEO) 衛星網路的電波必須往返太空。同步軌道衛星光是往返就超過 0.5 秒;Starlink 這類低軌衛星平時很快,但在重新分配路徑的瞬間延遲會忽高忽低,有時還會短暫中斷。 - 為什麼 → 於是 → 畫面上:在家中、船上或飛機上,透過同步軌道衛星、低軌衛星網路,或使用衛星的機上 Wi-Fi 連線 → 同步軌道衛星高度約 36,000km,往返距離本身就很長;低軌衛星則以很短的週期重新分配終端設備、衛星、地面站之間的路徑,在那一瞬間會短暫出現延遲與封包遺失 → 同步軌道衛星讓所有動作都有很大的輸入延遲;低軌衛星平時正常,但會每隔固定時間出現卡頓、瞬移 - 症狀:輸入延遲, 卡頓, 瞬移/因素:延遲, 抖動, 遺失 - 誰會遇到:只有我, 同一個家, 特定地區/電信業者/何時:一直都有, 固定週期 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發), 遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:依抖動自動拉長內插緩衝,重複傳送輸入,以撐過短暫的封包遺失,顯示連線品質。伺服器:訂定判定區間與延遲補償上限時考慮衛星線路的延遲;逾時設定不要因 1 秒左右的空白就立刻把玩家踢出。 - 外部要做的事:告知玩家衛星網路的延遲可能很大或週期性飆高,建議競技類內容盡量使用地面有線網路。 - 數值參考:同步軌道(高度 36,000km)光是電波穿越太空的單程就要 260ms,往返超過 520ms(ITU-T G.114)。低軌的 Starlink 依官方資料(15 秒平均值),美國尖峰時段的中位數為 33ms,最差的 1%(p99)也低於 65ms(2024 年);量測研究則發現,每 15 秒重新分配路徑的瞬間延遲會改變,並出現不到 1 秒的短暫中斷。2018 年的機上網路量測中,衛星方式的往返延遲平均為 750ms。 - 圖表上:只有部分偏高(RTT、抖動(依衛星網路業者 ASN)) - 查看位置:查看連線 IP 的 ASN 是否屬於衛星網路業者,另外畫出這些業者玩家的 RTT 分布與時間序列。從該 ASN 的 RIPE Atlas probe 持續 ping 伺服器幾分鐘,或請玩家開著 ping 量測飆高的間隔 - 符合的跡象:同步軌道業者的 RTT 一直超過 500ms;低軌業者平時為數十 ms,但約每 15 秒 RTT 改變一次或短暫中斷 - 不符合的跡象:不是衛星業者但 RTT 一直偏高時,是「傳播延遲(物理距離)」或「繞遠路的路由」;不規則飆高時,查 Wi-Fi、行動網路訊號 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:低軌衛星離地面近(Starlink 單段 1.8~3.6ms),平時延遲可能與地面線路相近。但地面站連上網際網路的出口(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 · 衛星區段的單程傳播延遲規劃值:高度 400km 為 12ms、14,000km 為 110ms、36,000km(同步軌道)為 260ms - [Improving Starlink’s Latency](https://starlink.com/public-files/StarlinkLatency.pdf) · Starlink · 美國尖峰時段中位數 48.5ms→33ms,最慢的 1%(p99)超過 150ms→低於 65ms(2024 年),衛星單段傳播 1.8~3.6ms,經雷射鏈路繞行時延遲增加,地面站到網際網路接入點(PoP)的距離也是延遲因素 - [A Multifaceted Look at Starlink Performance (WWW 2024)](https://www.nitindermohan.com/documents/2024/pubs/starlinkWWW2024.pdf) · ACM · Starlink 每 15 秒在全球同一時刻重新分配路徑,在交界處延遲與處理量會波動,也會發生不到 1 秒的短暫中斷(與衛星之間的切換無關),終端設備↔衛星↔地面站區段延遲約 40ms - [Mile High WiFi: A First Look at In-Flight Internet Connectivity (WWW 2018)](https://aqualab.cs.northwestern.edu/publication/2018/jrula-www18/) · ACM · 機上網路 45 小時量測:往返延遲平均為地面基地台方式 200ms、衛星方式 750ms,衛星方式的遺失率中位數 7% - [Probe Selection (RIPE Atlas REST API)](https://atlas.ripe.net/docs/apis/rest-api-manual/measurements/creating-measurements/probe-selection/) · RIPE NCC · 依國家、地區、ASN、位址區段挑選 RIPE Atlas 量測的 probe,執行 ping、traceroute #### isp-routing · 繞遠路的路由 · Suboptimal routing 受電信業者之間互連合約的影響,連到附近的伺服器也可能繞到遠處再回來。 - 為什麼 → 於是 → 畫面上:自己的電信業者與伺服器端的電信業者沒有直接互連 → 經過其他國家或其他城市,距離與經過的設備都增加 → 只有特定電信業者的使用者 ping 特別高 - 症狀:輸入延遲/因素:延遲 - 誰會遇到:特定地區/電信業者/何時:一直都有 - 主要負責:基礎設施團隊(網路基礎設施)/協同:外部(外部) - 基礎設施團隊要做的事:與多家電信業者互連(multihoming),監控各電信業者的 ping 以找出繞路的業者,並與電信業者協調路由。 - 外部要做的事:請該電信業者調整路由。 - 數值參考:即使在同一個國家內,ping 也可能因路徑不同而相差兩三倍。 - 圖表上:一開始就一直偏高(RTT(依電信業者/ASN)) - 查看位置:比較各電信業者(ASN)的 RTT,並用較慢業者的 RIPE Atlas probe 或向玩家取得的 traceroute、mtr,查看路徑經過哪些國家與城市。IPv4 與 IPv6 分開量測(mtr -4、-6) - 符合的跡象:同一地區只有特定電信業者一直偏高,路徑中有經過其他國家或遠方城市的區段。或只有其中一種位址類型(IPv4 或 IPv6)偏高 - 不符合的跡象:所有電信業者都差不多高時是「傳播延遲(物理距離)」,只在晚上偏高時是「尖峰時段 peering 區段壅塞」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:IPv4 與 IPv6 的路徑是分開決定的,同一台伺服器也可能只有其中一邊繞遠路而變慢(2016 年 APNIC 量測:同一家電信業者內,出現了 IPv6 比 IPv4 慢 15、25、75ms 的不同使用者群)。採用 Happy Eyeballs(RFC 8305,IPv6 與 IPv4 哪邊先連上就用哪邊)的應用程式會先嘗試 IPv6,只要 IPv6 在建議值 250ms 內連上,就不會再嘗試 IPv4。因此即使 IPv6 稍慢,也很容易走那條路徑。只有特定電信業者 ping 偏高時,請把 IPv4 與 IPv6 分開量測。 - 實際案例:riot-direct-2015 - 出處: - [Quantifying the Causes of Path Inflation (SIGCOMM 2003)](https://conferences.sigcomm.org/sigcomm/2003/papers/p113-spring.pdf) · ACM · 分析 65 家 ISP:ISP 之間的 peering 政策與跨網域路由讓路徑大幅拉長 - [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 · 依國家、地區、ASN、位址區段挑選 RIPE Atlas 量測的 probe,執行 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 · 位址或位址類型(IPv4、IPv6)可能因網路而被封鎖、故障或變慢,先嘗試 IPv6,到下一次連線嘗試前建議等待 250ms - [IPv6 Performance – Revisited](https://blog.apnic.net/2016/08/22/ipv6-performance-revisited/) · APNIC · 比較同一批雙堆疊(dual stack)使用者的 IPv6 與 IPv4 往返時間:接取網路有時會以完全不同的方式處理 IPv6 封包,同一家電信業者內出現 IPv6 慢 15、25、75ms 的群組 #### isp-peak · 尖峰時段 peering 區段壅塞 · Peak-hour congestion at peering 晚上 9~11 點左右影音流量暴增,電信業者之間的互連區段(peering)容易壅塞。 - 為什麼 → 於是 → 畫面上:晚上串流與下載的流量集中湧入 → peering 區段出現排隊與封包遺失 → 只在晚上,特定電信業者的使用者出現卡頓、瞬移 - 症狀:卡頓, 瞬移, 拉回/因素:抖動, 遺失, 延遲 - 誰會遇到:特定地區/電信業者/何時:晚間尖峰時段 - 主要負責:基礎設施團隊(網路基礎設施)/協同:外部(外部) - 基礎設施團隊要做的事:增加與該電信業者的直接互連,避開壅塞路徑,監控各電信業者晚間的封包遺失與 ping。 - 外部要做的事:請該電信業者擴充 peering 區段的容量。 - 圖表上:只在特定時段偏高(RTT、遺失(依電信業者)) - 查看位置:依時段畫出各電信業者(ASN)的 RTT 與遺失,並從該業者的 RIPE Atlas probe 或玩家那裡分別取得晚間與白天的 mtr,找出開始遺失的區段 - 符合的跡象:只有特定電信業者每天晚上 9~11 點左右 RTT 與遺失上升,mtr 顯示從電信業者之間的互連區段一路到目的地都有遺失與延遲 - 不符合的跡象:所有電信業者一起升高時,問題在我方線路或伺服器端。只有同一個家裡的連線在晚上變差時是「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 · 部分電信業者之間的互連區段每天尖峰時段反覆壅塞、延遲升高,壅塞時段遺失率也上升 - [Probe Selection (RIPE Atlas REST API)](https://atlas.ripe.net/docs/apis/rest-api-manual/measurements/creating-measurements/probe-selection/) · RIPE NCC · 依國家、地區、ASN、位址區段挑選 RIPE Atlas 量測的 probe,執行 ping、traceroute #### isp-cable · 海底電纜/國際線路故障 · Submarine cable fault 海底電纜一旦斷裂,修復前的幾週(長則幾個月)流量都得繞遠路,剩下的線路也會壅塞。 - 為什麼 → 於是 → 畫面上:電纜被切斷或設備故障 → 流量集中到繞遠路的路徑與剩下的線路上 → 海外玩家的 ping 暴增並出現封包遺失,持續幾天到幾週 - 症狀:輸入延遲, 瞬移/因素:延遲, 遺失 - 誰會遇到:特定地區/電信業者/何時:一直都有 - 主要負責:外部(外部)/協同:基礎設施團隊(網路基礎設施) - 基礎設施團隊要做的事:準備走不同路徑的線路,故障時把流量移到那條路徑。 - 外部要做的事:向海外玩家公告原因與預計修復時間,並請線路業者確認修復時程。 - 圖表上:從某個時間點起階梯式上升(RTT(依海外國家)) - 查看位置:在各國 RTT、遺失圖表中找出升高的時間點,對照 Cloudflare Radar 的網路中斷摘要與海底電纜業者的公告,再用 traceroute 確認路徑是否繞經其他大陸 - 符合的跡象:從某個時間點起,特定海外地區的 RTT 升高一階並維持幾天到幾週,同期有電纜故障報告。路徑改走與平常不同、繞遠路的路線 - 不符合的跡象:幾天內恢復原狀且沒有故障報告時,是「BGP 路由變更與收斂」或電信業者區段的問題 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [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 年 2 月受損的紅海電纜到 7 月仍在修復(位於衝突地區),5 月 EASSy、Seacom 的斷裂則在 19 天後修復 - [Q1 2024 Internet disruption summary](https://blog.cloudflare.com/q1-2024-internet-disruption-summary/) · Cloudflare · 西非電纜斷裂(3 月 14 日)在 3~6 週後修復,期間把流量移到其他電纜 #### isp-bgp · BGP 路由變更與收斂 · Route change / BGP convergence 網際網路的路由資訊改變後重新收斂的幾秒到幾十秒(偶爾幾分鐘)之間,封包會遺失。 - 為什麼 → 於是 → 畫面上:某個電信業者區段的路由資訊改變 → 幾秒到幾十秒之間封包消失,或切換到新路徑 → 突然停住幾秒,之後 ping 值改變(例:40 → 70ms) - 症狀:定格, 瞬移/因素:遺失, 延遲 - 誰會遇到:特定地區/電信業者/何時:偶爾隨機發生 - 主要負責:基礎設施團隊(網路基礎設施)/協同:遊戲開發團隊(伺服器開發), 外部(外部) - 遊戲開發團隊要做的事:設定能撐過短暫中斷的逾時(停住幾秒的連線不要立刻切斷)。 - 基礎設施團隊要做的事:監控路由(監看我方 IP 區段的路由與 ping 變化);我方線路故障用 BFD 在 1 秒內偵測並切換(BGP 預設 hold time 為 90~180 秒);若改走遠路後遲遲沒有恢復,就把流量移到其他線路。 - 外部要做的事:路由經常變動的電信業者區段,請該電信業者查明原因。 - 圖表上:從某個時間點起階梯式上升(RTT、traceroute 路徑) - 查看位置:比較 RTT 改變前後的 traceroute、mtr 路徑,並用 RIPEstat BGPlay 查看我方位址區段(prefix)的 BGP 路由變更紀錄 - 符合的跡象:停住幾秒的同時 RTT 移到另一個數值,同一時間有 BGP 更新與 AS 路徑變更 - 不符合的跡象:沒有路由變更紀錄、只在晚上升高時是「尖峰時段 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 秒(這段時間內沒有收到對方訊息就切斷 session) - [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 · 路由故障後收斂最長要數分鐘,期間遺失與延遲增加(2000 年當時的量測) - [BGPlay (RIPEstat Data API)](https://stat.ripe.net/docs/data-api/api-endpoints/bgplay) · RIPE NCC · 顯示位址區段(prefix)在起始時間點的 BGP 路由、該期間觀測到的 BGP 更新,以及路徑上的 AS 資訊 #### isp-ecmp · ECMP 其中一條路徑異常 · ECMP / link bundle member fault 電信業者與資料中心會為同一個目的地準備多條路徑,並替每條連線指定其中一條傳送。只要其中一條路徑故障,被分配到那條路徑的人就會持續遇到 lag。 - 為什麼 → 於是 → 畫面上:在多條線路綁在一起的區段中,其中一條線路或一台設備異常或壅塞 → 路徑由位址與 port 的組合(雜湊)決定,只有被分配到那條路徑的連線出現封包遺失與延遲 → 同一地區、同一電信業者,卻只有部分人持續瞬移。重新連線後有時就恢復正常 - 症狀:瞬移, 拉回, 卡頓/因素:遺失, 抖動 - 誰會遇到:只有我, 特定地區/電信業者/何時:一直都有 - 主要負責:基礎設施團隊(網路基礎設施)/協同:遊戲開發團隊(伺服器開發), 外部(外部) - 遊戲開發團隊要做的事:記錄每條連線的遺失與重傳統計,讓受影響者的 IP、port 與時間點能被撈出來(TCP 用 TCP_INFO 的重傳次數,UDP 用缺漏的封包序號計算)。 - 基礎設施團隊要做的事:收集受影響者的 IP、port 與時間點,提供給電信業者與資料中心;監控各路徑的遺失;量測路徑時使用與遊戲相同的協定與 port(mtr --tcp、--udp 搭配 --port);若是我方設備的路徑,就把異常線路或設備從綁定中移除。 - 外部要做的事:請電信業者確認並更換異常路徑,並引導玩家以重新連線暫時避開(重新連線會換 port 的情況)。 - 數值參考:有 4 條路徑時,只有約 1/4 的使用者會遇到。量測 ping 時可能走的是和遊戲不同的路徑,結果看起來正常。 - 圖表上:只有部分偏高(每條連線的遺失與重傳(依 IP/port)) - 查看位置:依來源 IP、port 分開查看每條連線的遺失與重傳;用 UDP(-u)把 mtr 送往遊戲 port(-P),並指定來源 port(-L)量測,再換不同來源 port 重複多次。只給 -P 不給 -L 時,每次請求的來源 port 都會變,多條路徑的結果會混在一起 - 符合的跡象:同一地區、同一電信業者內,只有特定來源 port(或位址)的組合持續遺失,重新連線換了 port 後就恢復正常 - 不符合的跡象:換了 port 仍然全部都差時,是整個區段的壅塞或故障 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:為了不打亂同一條連線的封包順序,設備會依位址與 port(依設備設定,也可能只看位址)算出的值,替每條連線固定一條路徑(ECMP、LAG)。只看位址的地方,重新連線後仍是同一條路徑,不會改善。因此同時收到「ping 正常,只有遊戲 lag」、「重新連線後就好了」這類回報時,就要懷疑是這個原因。 - 出處: - [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 指定一條鏈路,保持封包順序(flow→鏈路為多對一對應) - [RFC 2991: Multipath Issues in Unicast and Multicast Next-Hop Selection](https://www.rfc-editor.org/rfc/rfc2991) · IETF · 區分 flow 的依據因實作而異(只看目的位址、位址對、連 port 也看),多路徑環境下 ping、traceroute 的結果難以採信 - [mtr(8) manual page source](https://raw.githubusercontent.com/traviscross/mtr/master/man/mtr.8.in) · mtr · -u(UDP)、-P(目的 port)、-L(UDP 來源 port)選項,只給 -P 時會把請求序號放進來源 port,每次請求都不同 #### isp-shaping · 電信業者限速與流量管理 · Traffic shaping, data caps 在用量超過上限或會管理特定流量的資費方案中,封包會被延後或丟棄。 - 為什麼 → 於是 → 畫面上:資費方案的行動數據用完後被限速,或特定流量受到限制 → 封包排隊等待或被丟棄 → 用到一定用量之後開始 lag,行動網路特別常見 - 症狀:輸入延遲, 瞬移/因素:延遲, 遺失 - 誰會遇到:只有我, 特定地區/電信業者/何時:一直都有, 晚間尖峰時段 - 主要負責:外部(外部)/協同:遊戲開發團隊(伺服器開發), 基礎設施團隊(網路基礎設施) - 遊戲開發團隊要做的事:減少遊戲流量(壓縮、只傳必要的資料)。 - 基礎設施團隊要做的事:若只有特定電信業者的遊戲流量被延後或丟棄,整理資料後向電信業者提報(escalation)。 - 外部要做的事:引導玩家確認資費方案的數據是否用完、是否被限速,以及同一支手機上其他 App 的使用情況;請電信業者確認是否限制遊戲流量。 - 數值參考:韓國國內的行動資費方案在數據用完後通常限速為 1~5Mbps,便宜的方案限速為數百 kbps。遊戲本身流量不大,但同一支手機上的其他 App 一用網路,限速設備前就會出現排隊。 - 圖表上:碰到上限後持平(處理量、RTT) - 查看位置:請玩家在電信業者 App 確認剩餘數據與是否限速,並用測速查看最高速度。伺服器端比較各電信業者的遺失與 RTT - 符合的跡象:處理量卡在 1~5Mbps 或數百 kbps 這類固定數值上不去,從那時起同一支手機的其他 App 一用網路,RTT 與遺失就增加。加購數據或改用 Wi-Fi 後消失 - 不符合的跡象:沒有限速卻只有特定電信業者變差時,是「尖峰時段 peering 區段壅塞」或「繞遠路的路由」 - 確認方式:在玩家端環境確認 - 出處: - [SKT, 고객 선택권과 혜택 강화한 신규 5G 요금제 출시](https://news.sktelecom.com/180213) · SK텔레콤 · 5G 資費方案基本數據用完後的限速範例:最高 400kbps、1Mbps、3Mbps - [SKT, 요금제 개편](https://news.sktelecom.com/225487) · SK텔레콤 · 基本提供的數據用完後仍可以最高 400kbps 繼續使用(「全民安心數據」) #### isp-udp-block · 國家/電信業者層級的 UDP 限制與封包檢測 · UDP blocking, throttling and inspection by networks 部分網路會封鎖特定 UDP 位址與 port,或限制 UDP 速度;封包檢測設備也會過濾掉無法辨識的協定。用 UDP 通訊的遊戲在這類網路上會連不上或經常斷線。 - 為什麼 → 於是 → 畫面上:從限制 UDP 速度的部分電信業者網路,或設有國家/電信業者層級流量檢測(審查)設備的網路連線 → 封鎖特定 UDP 位址與 port、在壅塞時段限制 UDP 速度、過濾不在允許清單上的 port 與協定,或只放行前幾個封包後就封鎖 → 只有特定國家或電信業者的玩家連不上/無限讀取,或連上後很快斷線,壅塞時段因封包遺失而瞬移 - 症狀:連不上/無限讀取, 斷線, 瞬移/因素:遺失 - 誰會遇到:特定地區/電信業者/何時:剛登入/維護剛結束, 一直都有, 晚間尖峰時段 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發), 遊戲開發團隊(伺服器開發), 基礎設施團隊(網路基礎設施) - 遊戲開發團隊要做的事:用戶端:UDP 在幾秒內連不上時,自動切換到 TCP/TLS 443 替代路徑;也要偵測一開始連上、很快又斷掉的情況,改用替代路徑重試;在 log 中記錄是走哪條路徑連上的。伺服器:同一套遊戲協定也透過 TCP 443(TLS)接收;替代路徑的延遲可能增加,需調整逾時。 - 基礎設施團隊要做的事:新增海外國家之前,先在當地電信業者網路量測 UDP 是否能連通與尖峰時段的遺失;在當地附近設置接收 TCP 443 替代路徑的中繼(relay)或閘道;監看各國家、ASN 的 UDP 與 TCP 連線成功率;確認有 UDP 限速的電信業者,整理資料後提報(escalation)。 - 外部要做的事:向該電信業者或機關詢問 UDP 限制的標準與放寬方式,並引導玩家改從其他網路連線比較看看。 - 數值參考:根據 IETF 文件引用的量測,3~5% 的網路會完全封鎖 UDP。Google 在 2016 年檢視 QUIC(以 UDP 為基礎)的使用結果,發現 4.4% 的用戶端因 UDP 或 QUIC 被封鎖,或路徑 MTU 太小而無法使用,其中大多位於企業防火牆後方,沒有觀察到整個電信業者全面封鎖的情況。另有 0.3% 位於尖峰時段封包遺失大幅增加、看起來在限制 UDP 速度的網路上;Google 請電信業者處理後,這個比例已從 2015 年的 1% 降下來。 - 圖表上:只有部分偏高(UDP 連線成功率(依國家/ASN)) - 查看位置:依國家、ASN 分開查看 UDP 連線成功率與 TCP 443 替代路徑的成功率。從該電信業者網路上的雲端 VM 或玩家電腦,分別對遊戲 UDP port 與 TCP 443 做連線測試,並用 mtr -u -P(遊戲 port)與 mtr -T -P 443 比較從哪個區段開始沒有回應 - 符合的跡象:只有特定國家、ASN 的 UDP 收不到第一個回應或幾秒內就斷線,同一地點的 TCP 443 則正常。若是限速,只在尖峰時段 UDP 遺失明顯增加,TCP 受影響較小 - 不符合的跡象:TCP 也一起失敗時,查路徑故障、IP 封鎖或「DNS 故障與延遲」。所有國家都一樣時,查我方伺服器與防火牆設定;不分 UDP、TCP,只在瞬間傳輸量大時才遺失,是「Policer 丟棄超額流量」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:封包檢測設備可能依位址、port、協定挑出 UDP flow 加以封鎖,也可能採取只放行允許的協定、其餘全部封鎖的允許清單方式(IRTF 調查文件)。設備若只看封包的部分欄位來判斷,協定稍有改動就可能被擋。QUIC 早期曾有一款防火牆在標頭的 1 個位元改變後,只放行前幾個封包、之後的封包全部封鎖,導致用戶端改用 TCP 連線的邏輯無法運作。新增海外國家時,這個問題可能以「韓國沒問題,只有該國部分電信業者連不上」的回報形式浮現。如果只在咖啡廳、公司這類特定場所的網路被擋,請參考「公共 Wi-Fi/公司網路限制」。 - 出處: - [RFC 9308: Applicability of the QUIC Transport Protocol](https://www.rfc-editor.org/rfc/rfc9308) · IETF · 量測研究顯示 3~5% 的網路完全封鎖 UDP,因此以 UDP 為基礎的應用程式必須接受連線失敗,或準備 TCP(TLS)替代路徑;未對應已註冊服務的 port 可能被防火牆封鎖 - [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 被封鎖或路徑 MTU 太小,主要位於企業防火牆後方,未觀察到整個電信業者全面封鎖),0.3% 位於看起來在限制 UDP 速度的網路(尖峰時段遺失增加,請電信業者處理後從 2015 年的 1% 下降),曾有防火牆在標頭 1 個位元改變後只放行前幾個封包、之後全部封鎖,使 TCP 替代邏輯失效 - [RFC 9505: A Survey of Worldwide Censorship Techniques](https://www.rfc-editor.org/rfc/rfc9505) · IRTF · 網路上的檢測設備可以依位址、port、協定挑出 TCP、UDP flow 加以封鎖(QUIC 曾觀測到 UDP 端點被封鎖),只放行允許協定的方式會造成過度封鎖,也有限制特定流量速度的做法 - [mtr(8) manual page source](https://raw.githubusercontent.com/traviscross/mtr/master/man/mtr.8.in) · mtr · 用 -u 送 UDP、用 -T 送 TCP SYN,並以 -P 指定目的 port,用與遊戲相同的協定與 port 量測路徑 #### isp-line · 線路品質不良 · Faulty last-mile line / modem 接頭接觸不良、老舊線路或數據機異常,會造成持續的封包遺失與週期性的線路中斷。 - 為什麼 → 於是 → 畫面上:纜線受損、接觸不良,或數據機、光纖終端設備異常 → 位元錯誤導致封包被丟棄,有時線路會重新連線,因而中斷數秒到 1 分鐘左右 → 持續少量的封包遺失,偶爾定格數秒或斷線 - 症狀:瞬移, 定格, 斷線/因素:遺失 - 誰會遇到:同一個家/何時:偶爾隨機發生 - 主要負責:外部(外部) - 外部要做的事:引導玩家確認其他遊戲與視訊通話是否也會斷,若是,請玩家向電信業者申請檢修。 - 圖表上:偶爾隨機飆高(遺失率、線路重新連線紀錄) - 查看位置:用 pathping(或 mtr)量測幾分鐘到電信業者第一段的遺失,並在分享器管理頁面的網際網路(WAN)連線紀錄中查看重新連線的時間 - 符合的跡象:線路空閒時也從電信業者第一段起持續遺失,分享器紀錄的線路重新連線時間與定格、斷線的時間重疊。其他遊戲與視訊通話也一起斷 - 不符合的跡象:遺失從到分享器之間的無線區段開始時是「Wi-Fi 干擾與訊號減弱」,從電信業者較遠的區段開始時是電信業者路徑的問題 - 確認方式:在玩家端環境確認 - 出處: - [RFC 3635: Definitions of Managed Objects for the Ethernet-like Interface Types](https://www.rfc-editor.org/rfc/rfc3635) · IETF · 未通過訊框檢查(FCS)的訊框計為 FCS 錯誤(dot3StatsFCSErrors),並計入輸入錯誤(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,算出各路由器與鏈路的遺失率,顯示遺失發生在哪個區段 #### isp-dns · DNS 故障與延遲 · DNS failure / slowness 負責把伺服器名稱轉成位址的 DNS 一旦變慢或失敗,就找不到登入伺服器與更新伺服器。 - 為什麼 → 於是 → 畫面上:電信業者的 DNS 故障或設定錯誤 → 找不到登入伺服器、更新伺服器的位址 → 按下連線按鈕後要等很久,或連不上。已經連上的人不受影響 - 症狀:連不上/無限讀取/因素:延遲, 遺失 - 誰會遇到:特定地區/電信業者, 只有我/何時:剛登入/維護剛結束 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:位址快取(記住最後一次成功連線的伺服器位址),準備多個 DNS(一處失敗時改向其他 DNS 重新查詢)。 - 外部要做的事:引導玩家把 DNS 改成公共 DNS 等其他服務試試看。 - 圖表上:只有部分偏高(登入失敗次數(依電信業者)、DNS 查詢時間) - 查看位置:用 Resolve-DnsName -Server(或 nslookup)分別向電信業者 DNS 與公共 DNS 查詢登入伺服器名稱,比較回應時間與結果 - 符合的跡象:只有電信業者 DNS 沒有回應或花很久,改用公共 DNS 後立刻連上。已連上的玩家不受影響 - 不符合的跡象:不論用哪個 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 分鐘,查不到名稱的使用者幾乎無法使用任何網路服務 - [RFC 8767: Serving Stale Data to Improve DNS Resiliency](https://www.rfc-editor.org/rfc/rfc8767) · IETF · 連不上權威 DNS 伺服器時,繼續使用已過期的快取紀錄撐過故障的做法(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 針對遊戲公司,或同一網路上其他對象的大量攻擊,會塞滿共用的線路。 - 為什麼 → 於是 → 畫面上:出現大量攻擊流量 → 連走同一條線路的正常流量也被擠壓、丟棄 → 許多人同時瞬移、斷線、連不上 - 症狀:瞬移, 斷線, 連不上/無限讀取/因素:遺失, 延遲 - 誰會遇到:整個伺服器, 特定地區/電信業者/何時:偶爾隨機發生, 人潮湧入時 - 主要負責:基礎設施團隊(網路基礎設施)/協同:外部(外部) - 基礎設施團隊要做的事:使用 DDoS 防護服務,攻擊時把流量改道,隱藏伺服器位址(放在防護設備後方,不暴露真實位址)。 - 外部要做的事:若攻擊是針對同一網路上的其他對象,請電信業者在上游區段阻擋。 - 圖表上:碰到上限後持平(線路接收量(bps、pps)、介面丟棄數) - 查看位置:把我方線路與設備的介面接收量、丟棄封包數,以及 DDoS 防護服務的攻擊偵測紀錄,對照斷線集中的時間點一起看 - 符合的跡象:線路接收量貼齊線路容量而持平,丟棄數增加,同一時間多個地區、多家電信業者的玩家一起瞬移、斷線 - 不符合的跡象:線路還有餘裕、只有部分電信業者變差時,是電信業者區段的壅塞或路由問題 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Infrastructure layer attacks](https://docs.aws.amazon.com/whitepapers/latest/aws-best-practices-ddos-resiliency/infrastructure-layer-attacks.html) · AWS · UDP 反射、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 · 在來源伺服器(origin)前方放置 CloudFront、負載平衡器等邊緣服務,減少直接暴露 - [RFC 7999: BLACKHOLE Community](https://www.rfc-editor.org/rfc/rfc7999) · IETF · 透過 BGP 請相鄰電信業者丟棄送往特定位址流量的 BLACKHOLE community #### isp-cgnat · 電信業者共用 IP(CGNAT) · Carrier-grade NAT 行動網路與部分電信業者讓多位用戶共用一個 IP,並在短時間內清除閒置連線的 NAT mapping。 - 為什麼 → 於是 → 畫面上:電信業者的設備要管理大量用戶的 session 表 → session 表有上限,閒置逾時很短 → 閒置一陣子後斷線;共用同一個 IP 的人被誤判而一起封鎖 - 症狀:斷線, 連不上/無限讀取/因素:遺失 - 誰會遇到:特定地區/電信業者/何時:閒置一段時間後 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發), 基礎設施團隊(網路基礎設施) - 遊戲開發團隊要做的事:用戶端:以最短閒置逾時(行動網路有時只有 30 秒左右)一半以下的間隔送出心跳封包(電信業者的 CGNAT mapping 只有從內部送出的封包才能確實更新,所以由用戶端送),斷線時自動重新連線。伺服器:回應心跳封包,一段時間沒收到就先清理連線;mapping 改變導致位址、port 不同時,也要用 session token 接續成同一位玩家;一個 IP 可能由多人共用,以 IP 為單位的封鎖政策要謹慎(搭配帳號、裝置一起判斷)。 - 基礎設施團隊要做的事:依電信業者共用 IP 的情況,調整防火牆、DDoS 防護設備的每 IP 連線數與每秒新連線數限制(行動電信業者的位址區段提高門檻或列為例外)。 - 數值參考:行動網路的 UDP 閒置逾時有時只有 30 秒左右。 - 圖表上:連線同時大量中斷(斷線次數(心跳逾時)、斷線前的閒置時間(依電信業者)) - 查看位置:在連線 log 中查看同一個 IP 同時連上的帳號數與電信業者(ASN),並依電信業者彙整閒置後才斷線的連線,統計其閒置時間。玩家端請確認分享器管理頁面上的網際網路(WAN)位址 - 符合的跡象:行動電信業者的位址區段中同一個 IP 連上多個帳號,斷線前的閒置時間集中在 30~60 秒左右的短時間。分享器的 WAN 位址是 100.64.0.0/10(電信業者 NAT 用的共用位址),或與伺服器看到的位址不同 - 不符合的跡象:與電信業者無關、集中在使用家用分享器的玩家時是「NAT mapping 過期」 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [A Multi-perspective Analysis of Carrier-Grade NAT Deployment (IMC 2016)](https://www.icir.org/vern/papers/cgn-imc16.pdf) · ACM · 實測 NAT 的 UDP mapping 保留時間為 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 必須支援每位用戶的外部 port 數限制與新 mapping 的建立速率限制 - [RFC 6269: Issues with IP Address Sharing](https://www.rfc-editor.org/rfc/rfc6269) · IETF · 多人共用位址時,以 IP 為單位的封鎖(penalty box)會連帶封鎖同一位址的其他用戶 - [RFC 6598: IANA-Reserved IPv4 Prefix for Shared Address Space](https://www.rfc-editor.org/rfc/rfc6598) · IETF · 100.64.0.0/10 是電信業者 NAT(CGN)設備與用戶分享器之間使用的共用位址區段 #### isp-vpn · 經由 VPN/遊戲加速器 · VPN / game accelerator detour 開啟 VPN 或遊戲加速器後,封包會經過該公司的中繼伺服器。中繼伺服器很遠或壅塞時,反而會變慢。 - 為什麼 → 於是 → 畫面上:VPN 或加速器把遊戲封包全部轉送到中繼伺服器 → 加上到中繼伺服器的距離與壅塞,通道標頭也讓 MTU(一次能傳送的封包大小)變小 → ping 升高並出現封包遺失;與使用同一中繼位址的人一起被封鎖而連不上 - 症狀:輸入延遲, 瞬移, 連不上/無限讀取/因素:延遲, 遺失 - 誰會遇到:只有我/何時:一直都有, 剛登入/維護剛結束 - 主要負責:外部(外部)/協同:基礎設施團隊(網路基礎設施), 遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:UDP 封包維持在 1,200 位元組以下(即使通道標頭讓 MTU 變小也不會被分段);以 IP 為單位的封鎖要考量 VPN、加速器的公用中繼位址,搭配帳號、裝置一起判斷。 - 基礎設施團隊要做的事:海外玩家多時自行設置附近的接入據點;對「開了加速器就變好」回報集中的電信業者檢查路由。 - 外部要做的事:引導玩家關閉 VPN、加速器後比較看看。 - 數值參考:中繼伺服器在附近時增加數 ms,繞經其他國家時會增加數十~100ms 以上。 - 圖表上:只有部分偏高(RTT(依玩家)、連線 IP 所屬業者) - 查看位置:查看連線 IP 的 ASN 是否屬於 VPN、加速器或主機代管業者,並請玩家關閉 VPN、加速器後比較 ping 與 traceroute - 符合的跡象:只有開啟 VPN、加速器時 RTT 與遺失增加或被擋在外面,traceroute 中可以看到經過中繼伺服器的區段 - 不符合的跡象:開關都一樣時,是線路或電信業者區段的問題。開了反而變好時,是原本電信業者路徑(「繞遠路的路由」、「尖峰時段 peering 區段壅塞」)的問題 - 確認方式:在玩家端環境確認 - 深入了解:反過來說,電信業者的路徑不好時,加速器繞經較好的路徑,ping 有時反而會下降。「開了加速器就變好」的回報,是繞遠路的路由或晚間壅塞等電信業者路徑問題的線索。 - 出處: - [RFC 4459: MTU and Fragmentation Issues with In-the-Network Tunneling](https://www.rfc-editor.org/rfc/rfc4459) · IETF · 網路中途的通道因封裝標頭而使可傳送的大小變小,造成的分段與路徑 MTU 問題 - [RFC 8899: Packetization Layer Path MTU Discovery for Datagram Transports](https://www.rfc-editor.org/rfc/rfc8899) · IETF · UDP 等 datagram 傳輸的預設安全大小(BASE_PLPMTU)建議為 1,200 位元組 - [Azure network round-trip latency statistics](https://learn.microsoft.com/en-us/azure/networking/azure-network-latency) · Microsoft Azure · 經過其他國家據點時增加的往返延遲規模:首爾–東京 30ms、首爾–香港 39ms、首爾–新加坡 68ms ### L5 資料中心網路設備(11 個原因) #### dc-firewall · 防火牆 session 表飽和 · Firewall session table exhaustion 防火牆會把放行的每條連線記錄在 session 表中追蹤。表一旦滿了,就無法接受新連線。 - 為什麼 → 於是 → 畫面上:連線暴增或遭受攻擊,使 session 數達到上限 → 沒有空的項目可以記錄新連線,因此拒絕連線 → 想新連進來的人連不上/無限讀取,部分既有連線也會斷線 - 症狀:連不上/無限讀取, 斷線/因素:遺失 - 誰會遇到:整個伺服器/何時:剛登入/維護剛結束, 人潮湧入時 - 主要負責:基礎設施團隊(網路基礎設施)/協同:遊戲開發團隊(伺服器開發), 遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:用登入排隊系統調節一次湧入的連線;重複使用連線,避免反覆建立短連線;心跳封包中斷的連線要主動先清理(避免死掉的連線長時間佔用 session 表)。用戶端:以最短閒置逾時一半以下的間隔送出心跳封包;斷線時自動重新連線,但要逐步拉長重試間隔並隨機分散(避免大家又同時湧入)。 - 基礎設施團隊要做的事:加大 session 表;盡快清理很快就結束的連線(縮短已結束 session 的逾時);縮短閒置 session 逾時時,要把數值告知遊戲團隊以配合調整心跳封包間隔;阻擋攻擊;設定 session 使用率警示。 - 圖表上:碰到上限後持平(防火牆 session 數、新連線失敗數) - 查看位置:把防火牆設備的同時 session 數與 session 上限畫在同一張圖上,並在設備 log 中找出無法建立 session 而丟棄的紀錄。若是 Linux 防火牆,比較 nf_conntrack_count 與 nf_conntrack_max,並查看 dmesg 中的「nf_conntrack: table full, dropping packet」;若是 AWS 執行個體,查看 ethtool -S 的 conntrack_allowance_exceeded - 符合的跡象:session 數在上限處持平的時間點起,新連線失敗增加,session 建立失敗紀錄或丟棄計數器也一起增加 - 不符合的跡象:session 數遠低於上限卻連不上時,是「連線等待佇列(backlog)溢位」或登入伺服器端的問題。只有閒置連線被切斷時是「雲端安全群組的連線追蹤過期」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Netfilter Conntrack Sysfs variables](https://docs.kernel.org/networking/nf_conntrack-sysctl.html) · Linux kernel · 連線追蹤表的最大項目數(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 · 超過每個執行個體可追蹤的連線數時,新連線的封包會被丟棄,閒置連線可能耗盡追蹤表 - [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 · 連線追蹤表滿了時會留下「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:超過執行個體連線追蹤上限而被丟棄的封包數,用 ethtool -S 確認 #### dc-ddos · DDoS 防護導流與誤判 · DDoS scrubbing latency, false positives 為了擋下攻擊而把流量導向清洗中心時,路徑會變長,有時還會把正常使用者誤判為攻擊而封鎖。 - 為什麼 → 於是 → 畫面上:偵測到攻擊後(或常態性地)把進來的流量導向清洗中心 → 路徑變長,部分正常封包被判定為攻擊 → 整體 ping 上升,只有特定地區/電信業者連不上 - 症狀:輸入延遲, 連不上/無限讀取, 瞬移/因素:延遲, 遺失 - 誰會遇到:整個伺服器, 特定地區/電信業者/何時:人潮湧入時, 偶爾隨機發生 - 主要負責:基礎設施團隊(網路基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:整理遊戲流量模式(port、封包大小、每秒封包數)並提供給基礎設施團隊,UDP 封包維持在 1,200 位元組以下。 - 基礎設施團隊要做的事:制定符合遊戲流量模式的防護規則,設置各地區的清洗據點,在通道區段縮小 TCP 封包大小(MSS 調整),以各地區、各電信業者的連線失敗率確認是否誤判。 - 數值參考:清洗據點在同一個國家時增加數 ms,經過其他國家的據點時會增加 30~100ms 以上。通常只有進來的方向會繞道,伺服器的回應則直接送出。若清洗後的流量透過通道送回,一次能傳送的大小(MTU)也會變小,有時會演變成只有大封包消失的問題。 - 圖表上:從某個時間點起階梯式上升(RTT(ping)、各地區/電信業者的連線失敗率) - 查看位置:把防護設備或服務的導流(清洗)開始與結束紀錄、封鎖 log,與 RTT 圖表、各地區與電信業者的連線失敗率放在同一條時間軸上比對。在問題地區用 mtr、traceroute 確認路徑中是否夾著清洗據點 - 符合的跡象:導流開啟的時間點 RTT 升高一階並維持,關閉後恢復。或封鎖 log 中出現正常玩家的位址,且只有該地區、該電信業者的連線失敗率上升 - 不符合的跡象:沒有導流、封鎖紀錄的時段 RTT 仍升高時,是「繞遠路的路由」或「BGP 路由變更與收斂」。只有大封包消失時是「MTU 不一致(只有大封包消失)」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Maximum transmission unit and maximum segment size](https://developers.cloudflare.com/magic-transit/reference/mtu-mss/) · Cloudflare · 進來的流量清洗後經 GRE 通道(MTU 1,476)轉交,出去的回應直接走網際網路(DSR),建議把 TCP MSS 限制在 1,436 以下,不調整的話大封包會被丟棄或分段 - [Azure network round-trip latency statistics](https://learn.microsoft.com/en-us/azure/networking/azure-network-latency) · Microsoft Azure · 依據點位置的往返延遲規模:首爾–釜山地區 8ms、首爾–東京 30ms、首爾–新加坡 68ms #### dc-lb-idle · 負載平衡器閒置逾時 · Load balancer idle timeout 負載平衡器會在一段時間後清除閒置連線。遊戲還以為連線仍然維持著,結果就斷線了。 - 為什麼 → 於是 → 畫面上:玩家有一段時間完全沒送出任何封包(開著對話視窗、暫離) → 負載平衡器清理閒置連線(常見的預設值為 60~350 秒) → 再次移動的瞬間斷線 - 症狀:斷線/因素:遺失 - 誰會遇到:只有我, 整個伺服器/何時:閒置一段時間後 - 主要負責:基礎設施團隊(網路基礎設施)/協同:遊戲開發團隊(用戶端開發), 遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:以最短閒置逾時一半以下的間隔送出心跳封包(位於 60 秒逾時的 ALB 後方時,就是 30 秒以下),斷線時自動重新連線。伺服器:回應心跳封包,一段時間沒收到就先清理連線,用 session token 接續連線。 - 基礎設施團隊要做的事:確認路徑上負載平衡器的閒置逾時數值並提供給遊戲團隊,必要時調長。 - 數值參考:AWS ALB 的預設值是 60 秒,NLB 是 TCP 350 秒、UDP 120 秒,Azure Load Balancer 是 TCP 4 分鐘。ALB 與 NLB 的 TCP 數值可以修改,但 NLB 的 UDP 120 秒無法修改。ALB 時間到時也會關閉伺服器端的連線;NLB 則是默默清除,伺服器很容易在不知情的狀態下留著連線。 - 圖表上:連線同時大量中斷(斷線次數、斷線前的閒置時間) - 查看位置:確認路徑上負載平衡器的閒置逾時設定值,並彙整每條斷線連線從最後一個封包到斷線所經過的時間。若是 AWS NLB,也查看 CloudWatch 的 TCP_ELB_Reset_Count(負載平衡器送出的 RST 數) - 符合的跡象:斷線連線的閒置時間集中在設定值(ALB 60 秒、NLB TCP 350 秒等)剛過之後,閒置超過該時間再移動就能重現。NLB 在該時間點 TCP_ELB_Reset_Count 增加 - 不符合的跡象:與閒置時間無關的斷線,就不是這個原因。沒有經過負載平衡器、直接連線的伺服器集中在 350 秒附近斷線時是「雲端安全群組的連線追蹤過期」,問題在玩家家中的分享器時是「NAT mapping 過期」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Edit attributes for your Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/edit-load-balancer-attributes.html) · AWS · ALB 閒置逾時預設 60 秒(1~4,000 秒),用戶端與目標的連線在這段時間內都沒有流量時,負載平衡器會關閉連線 - [Network Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/network-load-balancers.html) · AWS · NLB TCP 閒置預設 350 秒(60~6,000 秒),超過後只停止追蹤,之後有資料進來就回 RST,UDP flow 的 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 · Azure Load Balancer 閒置逾時預設 4 分鐘(4~100 分鐘),超過後不保證維持 session,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 · 雲端安全群組的連線追蹤過期 · Cloud security group connection tracking timeout 雲端伺服器上的防火牆(安全群組)也會追蹤連線,閒置連線的追蹤項目會在一定時間後過期。即使是沒有經過負載平衡器、直接連線的伺服器,靜止不動的玩家也可能斷線。 - 為什麼 → 於是 → 畫面上:安全群組處於會追蹤遊戲連線的設定(只允許特定位址、限制輸出規則、經由 NLB 等) → 連線閒置一段時間後追蹤項目過期,之後進來的封包被安全群組默默丟棄 → 暫離後再移動時沒有反應,接著斷線。伺服器程式很長一段時間都沒察覺 - 症狀:斷線/因素:遺失 - 誰會遇到:只有我, 整個伺服器/何時:閒置一段時間後 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(用戶端開發), 遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:以最短閒置逾時一半以下的間隔送出心跳封包(TCP 350 秒時為 175 秒以下,UDP 串流 180 秒時為 90 秒以下),斷線時自動重新連線。伺服器:回應心跳封包,一段時間沒收到就先清理連線,用 session token 接續連線。 - 基礎設施團隊要做的事:確認執行個體的連線追蹤時間(TcpEstablishedTimeout),必要時調長(UDP 最多 180 秒,無法再調長);檢討不會產生追蹤的安全群組配置(遊戲 port 允許所有位址、輸出規則全部允許;經由 NLB 的連線仍會被追蹤);換到新世代執行個體時進行閒置測試。 - 數值參考:以 AWS 為例,Nitro v6 執行個體類型預設在 350 秒後清除閒置 TCP 連線的追蹤項目(其他類型為 5 天)。UDP 的預設值為:請求與回應往返多次的 flow(串流)180 秒,只往單一方向送出或請求與回應只有一次的 flow 30 秒。 - 圖表上:連線同時大量中斷(斷線次數、斷線前的閒置時間) - 查看位置:確認執行個體的連線追蹤時間設定與安全群組規則(是否為會產生追蹤的配置),並彙整斷線連線的閒置時間。剛斷線時在伺服器用 ss -tnoi 查看該連線是否仍以 ESTABLISHED 殘留、重傳計時器(timer:(on,…))在跑且 backoff 變大 - 符合的跡象:斷線連線的閒置時間集中在 TCP 350 秒、UDP 串流 180 秒、UDP 單向 30 秒剛過之後,伺服器端的 socket 沒偵測到斷線、仍以 ESTABLISHED 殘留(伺服器有資料要送時只會一直重傳) - 不符合的跡象:安全群組處於不追蹤的配置(遊戲 port 允許所有位址、輸出規則全部允許、不經過 NLB)時,就不是這個原因。經過 NLB 時,與「負載平衡器閒置逾時」的數值比較 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Amazon EC2 security group connection tracking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-connection-tracking.html) · AWS · TCP 閒置追蹤預設 350 秒(Nitro v6,其他為 43 萬 2,000 秒=5 天),UDP 單向 30 秒、串流 180 秒(最多 180),允許所有位址的規則不追蹤,經由 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 · NLB 閒置逾時比目標執行個體的連線追蹤時間長時,執行個體端會先默默丟掉連線狀態 - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · -o 的 timer:(on,…) 是重傳計時器,-i 的 backoff 是重傳等待時間加倍的次數 #### dc-nat-gateway · 雲端 NAT 閘道的連線與 port 上限 · Cloud NAT gateway connection / port limits 私有子網路中的伺服器對外(平台驗證、付款、外部 API)的連線,由 NAT 閘道轉換位址與 port 後送出。送往同一目的地的同時連線超過閘道的 port 上限時,新連線就會失敗。 - 為什麼 → 於是 → 畫面上:伺服器對平台驗證、付款這類同一個外部位址大量開啟短連線,或長時間開著連線 → NAT 閘道無法再分配該目的地可用的來源 port,新連線失敗 → 遊戲內一切正常,只有登入、付款、發放獎勵這類呼叫外部的功能失敗或變慢(連不上/無限讀取、吃指令/回檔) - 症狀:連不上/無限讀取, 吃指令/回檔/因素:遺失, 延遲 - 誰會遇到:只有特定功能, 整個伺服器/何時:剛登入/維護剛結束, 晚間尖峰時段, 人潮湧入時 - 主要負責:基礎設施團隊(網路基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:外部 API 要重複使用連線(HTTP keep-alive、連線池),不要每次請求都開新連線;留在池中的閒置連線,要以比 NAT 閒置逾時(AWS 350 秒)更短的間隔送出 keepalive,或先行關閉;失敗時逐步拉長重試間隔並隨機分散;記錄各外部呼叫的失敗率與延遲。 - 基礎設施團隊要做的事:為 NAT 閘道增加 IP 位址(AWS 公用 NAT 閘道預設只能掛 2 個彈性 IP,超過需申請提高配額);依可用區域、子網路拆分閘道;為 port 分配失敗指標(AWS ErrorPortAllocation、Azure SNAT Connection Count 的 Failed、Google Cloud dropped_sent_packets_count 的 OUT_OF_RESOURCES)設定警示;Google Cloud NAT 可提高每個 VM 的最少 port 數,或改用動態 port 分配。 - 數值參考:AWS NAT 閘道每個 IP 位址對同一目的地(IP、port、協定)最多可開 5 萬 5 千條同時連線,最多可掛 8 個 IP 來擴充。350 秒沒有流量的連線會被清除,之後再從這條連線送出的封包會收到 RST。Azure NAT Gateway 每個公用 IP 有 64,512 個 SNAT port(IP 最多 16 個)。Google Cloud NAT 把每個 NAT IP 的 64,512 個 port 分給各 VM,但每個 VM 的最少 port 數預設為 64 個(靜態分配),所以預設設定下,一台 VM 對同一目的地能同時開的連線通常被限制在 64 條。 - 圖表上:碰到上限後持平(NAT 閘道同時連線數、port 分配失敗數) - 查看位置:AWS 查看 CloudWatch 中的 NAT 閘道指標 ErrorPortAllocation、ActiveConnectionCount、PacketsDropCount(Azure 為以 Failed 狀態篩選的 SNAT Connection Count 與 Dropped Packets,Google Cloud 為 dropped_sent_packets_count 中 reason 為 OUT_OF_RESOURCES 的值),與遊戲伺服器外部呼叫失敗的時間點並排比對 - 符合的跡象:外部呼叫失敗的時間點 ErrorPortAllocation(Azure 為 Failed 狀態的 SNAT Connection Count,Google Cloud 為 OUT_OF_RESOURCES 丟棄)大於 0,失敗集中在驗證、付款伺服器這類連線大量集中的一兩個目的地 - 不符合的跡象:port 分配失敗為 0,但遊戲伺服器的 connect 以 EADDRNOTAVAIL 失敗、TIME_WAIT 數接近臨時 port 範圍時,是「伺服器間連線的臨時 port 耗盡」。能連上但只有回應很慢時,是「依賴外部服務」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:與單台伺服器的臨時 port 用完的「伺服器間連線的臨時 port 耗盡」不同,這個上限卡在 NAT 閘道上,由閘道後方的伺服器共用(Google Cloud NAT 則分給各 VM)。伺服器端的 TIME_WAIT 與臨時 port 範圍都還有餘裕,卻只有外部呼叫失敗時,就是這個原因。已關閉連線的 port 也不會立刻再用於同一目的地(Azure 有冷卻時間,Google Cloud 在 TIME_WAIT 期間無法使用),所以越常反覆建立短連線,越快碰到上限。 - 出處: - [NAT gateway basics](https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-basics.html) · AWS · 每個 IPv4 位址對同一目的地(目的 IP、port、協定)5 萬 5 千條同時連線,最多掛 8 個 IP 擴充(公用 NAT 閘道的彈性 IP 預設 2 個,申請提高配額後可增加),頻寬從 5Gbps 自動擴充到 100Gbps、處理量從每秒 100 萬個封包自動擴充到 1,000 萬個,超過上限就丟棄 - [NAT gateway metrics and dimensions](https://docs.aws.amazon.com/vpc/latest/userguide/metrics-dimensions-nat-gateway.html) · AWS · ErrorPortAllocation:無法分配來源 port 的次數(大於 0 表示同時連線過多),ActiveConnectionCount,IdleTimeoutCount(因閒置 350 秒而被清理的連線),PacketsDropCount - [Troubleshoot NAT gateways](https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-troubleshooting.html) · AWS · 閒置 350 秒後連線過期,之後繼續送出會收到 RST,建議 keepalive 間隔短於 350 秒,碰到連線上限時依可用區域增設閘道、增加 IP、減少連線數 - [Source Network Address Translation (SNAT) with Azure NAT Gateway](https://learn.microsoft.com/en-us/azure/nat-gateway/nat-gateway-snat) · Microsoft Azure · 每個公用 IP 有 64,512 個 SNAT port(IP 最多 16 個),送往同一目的地的每條連線都需要不同的 port,已關閉的 port 再用於同一目的地之前有冷卻時間 - [Metrics and alerts for Azure NAT Gateway](https://learn.microsoft.com/en-us/azure/nat-gateway/nat-metrics) · Microsoft Azure · 以 Failed 狀態篩選的 SNAT Connection Count 大於 0 時,可能是 SNAT port 耗盡,Dropped Packets - [IP addresses and ports](https://cloud.google.com/nat/docs/ports-and-addresses) · Google Cloud · 每個 NAT IP 的 TCP、UDP 各有 64,512 個 port,每個 VM 最少 port 數預設 64(靜態分配)、32(動態分配),VM 預留的 port 數限制了對同一目的地的同時連線數,已關閉的連線在 TIME_WAIT 期間無法使用 - [Logs and metrics](https://cloud.google.com/nat/docs/monitoring) · Google Cloud · dropped_sent_packets_count 的 reason OUT_OF_RESOURCES:因 NAT IP 或 port 不足而丟棄的封包 #### dc-lb-imbalance · 負載平衡器分配不均與健康檢查誤判 · LB imbalance, bad health checks 連線全集中到一台伺服器,或持續把人送往已經掛掉的伺服器。 - 為什麼 → 於是 → 畫面上:分配規則不合適,或健康檢查(health check)看不到實際狀態 → 只有一台伺服器過載,或連線被送往掛掉的伺服器 → 只有部分頻道、部分人出現慢動作,或連不上/無限讀取 - 症狀:慢動作, 連不上/無限讀取/因素:停滯, 遺失 - 誰會遇到:特定地點/頻道/何時:剛登入/維護剛結束, 人潮湧入時 - 主要負責:基礎設施團隊(網路基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:實作健康檢查,依實際遊戲狀態(tick 是否推進、DB 連線)回應負載平衡器的檢查請求,並一併回報伺服器負載值。 - 基礎設施團隊要做的事:把健康檢查改成確認實際遊戲回應的方式,依伺服器負載分配,監控各伺服器連線數的差距。 - 圖表上:只有部分偏高(各伺服器的連線數與 CPU 使用率) - 查看位置:把負載平衡器後方每台伺服器的連線數(ss -s)與 CPU 使用率疊在同一張圖上,並將負載平衡器的目標健康狀態(AWS 為 CloudWatch 的 HealthyHostCount、UnHealthyHostCount)與遊戲伺服器的實際狀態比較 - 符合的跡象:只有一兩台伺服器的連線數與 CPU 明顯高於其他伺服器,或 tick 已停住的伺服器仍以健康狀態「正常」留著,持續接收新連線 - 不符合的跡象:各伺服器連線數平均、只有一個頻道變慢時,是該頻道內部的負載問題(「單執行緒區域過載(熱點)」) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 實際案例:aws-2025 - 出處: - [Load Balancing in the Datacenter](https://sre.google/sre-book/load-balancing-datacenter/) · Google · 單純的 round robin 會讓各工作之間的 CPU 使用量相差最多 2 倍,後端把負載附在回應與健康檢查中回傳的加權分配,表示不再接收請求的 lame duck 狀態 - [Health checks for Network Load Balancer target groups](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/target-group-health-checks.html) · AWS · 健康檢查預設間隔 30 秒、失敗 2 次即移出,UDP 服務是用 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:判定為健康、不健康的目標數 #### dc-microburst · 交換器 microburst · Switch microburst drops 多台伺服器在同一瞬間同時對數千人送出封包時,這些流量匯集的交換器 port 上的小緩衝區,不到 1ms 就會滿溢。 - 為什麼 → 於是 → 畫面上:世界王出現、大範圍技能,或多台伺服器的 tick 在同一瞬間對齊,同時送出封包 → 多個 port 匯入一個 port,或從快速 port 轉到慢速 port 的地方,緩衝區(每個 port 數百 KB~數 MB)瞬間塞滿 → 部分封包被丟棄,許多人同時瞬移、技能被吃 - 症狀:瞬移, 吃指令/回檔/因素:遺失 - 誰會遇到:特定地點/頻道/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(網路基礎設施), 基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:在一個 tick 內平均分散傳送(pacing),讓各伺服器的 tick 開始時間稍微錯開。 - 基礎設施團隊要做的事:網路:採用緩衝區較大的交換器,分散流量(把伺服器分散接到多台交換器、多個 port),監看各交換器 port 的丟棄計數器。伺服器設備/OS:為整台伺服器設定傳送速率上限(Linux tc shaper)。 - 數值參考:10Gbps 的 port 在 1ms 內能送出的量約為 1.25MB。兩個 port 的流量同時湧入一個 port 時,每 1ms 就會累積 1.25MB。即使 1 秒平均使用率只有 10%,以 1ms 為單位來看仍可能滿溢。 - 圖表上:隨人數/負載上升(交換器 port 輸出丟棄數) - 查看位置:以盡可能短的間隔收集伺服器所接交換器 port,以及其流量匯集 port 的輸出丟棄計數器(ifOutDiscards,依設備可能為 output drops),對照世界王出現、大規模戰鬥的時間點。只看 1 秒、1 分鐘平均使用率圖表是看不出來的 - 符合的跡象:平均使用率很低,但每當人潮集中到一處的瞬間輸出丟棄就增加,同時多位玩家回報瞬移、技能被吃 - 不符合的跡象:丟棄在平均使用率高的時段持續增加時是「資料中心線路飽和」。輸入錯誤(CRC)增加時是「纜線不良與 port 錯誤」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [High-Resolution Measurement of Data Center Microbursts (IMC 2017)](https://conferences.sigcomm.org/imc/2017/papers/imc17-final60.pdf) · ACM · 資料中心突發流量有 70% 以上在數十 µs 內結束,平均使用率約 9% 的 port 也會因突發流量丟棄封包 - [Data Center TCP (DCTCP) (SIGCOMM 2010)](https://conferences.sigcomm.org/sigcomm/2010/papers/sigcomm/p63.pdf) · ACM · 一般商用交換器的緩衝區很淺(48 個 port 共用 4MB,單一 port 最多約用到 700KB),多條 flow 在極短時間內湧入同一個 port 時就會遺失 - [RFC 2863: The Interfaces Group MIB](https://www.rfc-editor.org/rfc/rfc2863) · IETF · ifOutDiscards:沒有錯誤,卻因為要騰出緩衝區空間等原因而無法送出、被丟棄的封包數 #### dc-uplink · 資料中心線路飽和 · Uplink saturation 更新檔發布、log 傳送、備份與遊戲共用同一條線路時,線路會被塞滿。 - 為什麼 → 於是 → 畫面上:大量傳輸佔用同一條線路 → 線路上的排隊與封包遺失增加 → 整個伺服器的 ping 上升並出現瞬移 - 症狀:輸入延遲, 瞬移/因素:延遲, 遺失 - 誰會遇到:整個伺服器/何時:固定週期, 人潮湧入時 - 主要負責:基礎設施團隊(網路基礎設施)/協同:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:網路:遊戲流量優先處理(QoS),把大量傳輸的線路分開,設定線路使用率警示。伺服器設備/OS:替備份、log 傳送、部署加上速率限制,並在離峰時段執行。 - 圖表上:碰到上限後持平(線路使用率、RTT(ping)) - 查看位置:把資料中心線路(上行鏈路)介面的使用率(以 SNMP ifHCInOctets、ifHCOutOctets 計算)與輸出丟棄(ifOutDiscards),和備份、部署、log 傳送排程放在同一條時間軸上比對 - 符合的跡象:線路使用率貼齊頻寬上限而持平的時間點,整個伺服器的 RTT 與丟棄上升,且該時間點與大量傳輸作業重疊 - 不符合的跡象:分鐘級使用率遠低於上限卻有丟棄時是「交換器 microburst」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [RFC 4594: Configuration Guidelines for DiffServ Service Classes](https://www.rfc-editor.org/rfc/rfc4594) · IETF · 把遊戲這類即時互動流量與備份這類大量傳輸,分成不同的服務等級處理 - [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 資訊沒有同步時,連線會被重置 → 伺服器上的所有玩家同時定格,大量斷線 - 症狀:定格, 斷線/因素:遺失 - 誰會遇到:整個伺服器/何時:偶爾隨機發生 - 主要負責:基礎設施團隊(網路基礎設施)/協同:遊戲開發團隊(伺服器開發), 遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:設定能撐過短暫中斷(數秒)的逾時,斷線後重新連線時,用 session token 接續 session。用戶端:斷線時自動重新連線(隨機分散重試間隔,避免同時湧入)。 - 基礎設施團隊要做的事:採用共享連線狀態的備援架構,用 BFD 在 1 秒內偵測故障,定期進行切換測試。 - 數值參考:設備立刻偵測到故障時約 1~3 秒。沒有快速故障偵測(BFD)、只靠 BGP 預設計時器時,相鄰設備偵測到之前,路由可能中斷 90~180 秒。 - 圖表上:連線同時大量中斷(連線數、整個伺服器的收發量) - 查看位置:查看路由器、防火牆的事件 log(VRRP 角色切換、BFD 與 BGP session down、容錯移轉紀錄),以及同一時間整個伺服器的連線數與收發量 - 符合的跡象:在設備 log 的切換時間點,該設備後方所有伺服器的流量有幾秒降為 0,或連線數同時下降 - 不符合的跡象:只有一台伺服器的連線數下降時,是「伺服器當機」或「NIC 驅動程式/韌體問題」。設備 log 很乾淨、停住的是一台雲端虛擬機器時,是「雲端主機維護與即時遷移」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [RFC 5880: Bidirectional Forwarding Detection (BFD)](https://www.rfc-editor.org/rfc/rfc5880) · IETF · 路由協定的 Hello 機制偵測故障要 1 秒以上,BFD 就是為了在更短時間內偵測而設計 - [RFC 7938: Use of BGP for Routing in Large-Scale Data Centers](https://www.rfc-editor.org/rfc/rfc7938) · IETF · 只靠 BGP keepalive 時收斂很慢,若立即接收鏈路中斷訊號並切斷 session,就能以 ms 級偵測並重新收斂 - [RFC 5798: Virtual Router Redundancy Protocol (VRRP) Version 3 for IPv4 and IPv6](https://www.rfc-editor.org/rfc/rfc5798) · IETF · VRRP 通告預設 1 秒,備援設備在通告中斷超過約 3 倍間隔時接手角色(預設設定下約 3 秒多) #### dc-bad-cable · 纜線不良與 port 錯誤 · Bad cable / optics (CRC errors) 光模組或纜線不良時,經過該路徑的封包會有一定比例損毀。 - 為什麼 → 於是 → 畫面上:光模組或纜線不良造成位元錯誤 → 損毀的封包被設備默默丟棄 → 只有使用該路徑的部分伺服器與使用者因持續的封包遺失而瞬移、拉回 - 症狀:瞬移, 拉回/因素:遺失 - 誰會遇到:特定地點/頻道/何時:一直都有 - 主要負責:基礎設施團隊(網路基礎設施) - 基礎設施團隊要做的事:監控 port 錯誤(CRC)計數器並設定警示,更換光模組、纜線等零件,更換前先把問題鏈路移出、改走其他路徑。 - 圖表上:只有部分偏高(各 port 的 CRC 錯誤數、各伺服器/路徑的遺失率) - 查看位置:查看鏈路兩端的 CRC 計數器。交換器看 port 的 FCS 錯誤(dot3StatsFCSErrors)與輸入錯誤(ifInErrors),伺服器看 ip -s -s link 的 RX errors 中的 crc(kernel 統計 rx_crc_errors) - 符合的跡象:某個 port 的 CRC 錯誤不分流量大小與時段持續增加,且只有經過該 port 的伺服器與玩家有遺失 - 不符合的跡象:沒有 CRC 錯誤、只有輸出丟棄增加時是壅塞(「交換器 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 · 分析資料中心 35 萬條鏈路:損毀原因為不良光模組、受損光纖、髒污接頭,損毀率與使用量無關、始終穩定存在,在維持路徑數量的前提下把問題鏈路移出修理 - [RFC 3635: Definitions of Managed Objects for the Ethernet-like Interface Types](https://www.rfc-editor.org/rfc/rfc3635) · IETF · 訊框檢查(FCS)失敗計數器(dot3StatsFCSErrors),這類錯誤計入輸入錯誤(ifInErrors) - [Interface statistics](https://docs.kernel.org/networking/statistics.html) · Linux kernel · rx_crc_errors:收到但 CRC 錯誤的封包數,用 ip -s -s link 依錯誤種類確認 #### dc-mtu · MTU 不一致(只有大封包消失) · MTU black hole 中途區段的 MTU(一次能傳送的大小)變小,而大小超過的通知又被擋掉時,只有大封包會一直消失。 - 為什麼 → 於是 → 畫面上:在通道或 VPN 區段中 MTU 變小 → 大小超過的通知(ICMP)被防火牆擋下,傳送端不知道 → 只有打開背包、角色清單這類大畫面時,畫面會停住,接著斷線 - 症狀:定格, 斷線, 連不上/無限讀取/因素:遺失 - 誰會遇到:特定地區/電信業者, 只有我/何時:做特定動作時, 剛登入/維護剛結束 - 主要負責:基礎設施團隊(網路基礎設施)/協同:基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:要在伺服器端直接調低,須設定 socket 的最大區段大小 TCP_MAXSEG(光在遊戲程式碼裡把訊息切小是擋不住的),UDP 封包維持在 1,200 位元組以下。 - 基礎設施團隊要做的事:網路:在通道區段縮小 TCP 封包大小(MSS 調整),在防火牆與雲端網路 ACL 允許大小超過的通知(ICMP)。伺服器設備/OS:在伺服器防火牆與雲端安全群組也允許大小超過的通知(ICMP),開啟伺服器 kernel 的 MTU 探索 tcp_mtu_probing=1(這是要停住幾秒後才會作動的最後一道防線)。 - 數值參考:一般為 1,500 位元組,經過通道後會縮小到 1,400 左右。 - 圖表上:只有部分偏高(各地區/電信業者的斷線、大型回應失敗) - 查看位置:從問題玩家的電腦對伺服器送出開啟禁止分段旗標(DF)的 ping,並改變大小。Windows 用 ping /f /l 1472 SERVER_IP,Linux 用 ping -M do -s 1472 SERVER_IP(1,472 是 MTU 1,500 減去 IP 標頭 20 位元組與 ICMP 標頭 8 位元組的值)。逐步縮小大小找出能通過的最大值,並確認伺服器端安全群組與防火牆是否允許 ICMP 大小超過通知(Fragmentation Needed) - 符合的跡象:小的 ping 能通,1,472 位元組的 DF ping 卻失敗(沒有回應,或出現需要分段的錯誤),能通過的最大值只有 1,400 左右。同一地區的玩家只有在打開大畫面時畫面停住 - 不符合的跡象:1,472 位元組的 DF ping 也能順利通過時,就不是路徑 MTU 的問題。連小的 ping 都不通時,是 ICMP 本身被擋,無法用這個方法判斷 - 確認方式:在玩家端環境確認 - 出處: - [RFC 2923: TCP Problems with Path MTU Discovery](https://www.rfc-editor.org/rfc/rfc2923) · IETF · 防火牆擋掉 ICMP(Fragmentation Needed)時,路徑 MTU 探索會失敗,只有大封包一直消失(黑洞),ping 與小型通訊正常,因此難以診斷 - [Maximum transmission unit and maximum segment size](https://developers.cloudflare.com/magic-transit/reference/mtu-mss/) · Cloudflare · 網際網路路徑 MTU 1,500,經過 GRE 通道後為 1,476,建議把 TCP MSS 限制在 1,436 以下 - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_mtu_probing=1 平時關閉,偵測到 ICMP 黑洞時才開啟 TCP 路徑 MTU 探索 - [ping(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ping.8.html) · iputils · -M do 開啟 DF 旗標並拒絕大於路徑 MTU 的封包,-s 指定資料大小(預設 56 位元組,另加 ICMP 標頭 8 位元組) - [ping](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/ping) · Microsoft · /f 開啟 DF 旗標,用來找出路徑 MTU 問題,/l 指定資料大小 ### L6 伺服器網路卡(9 個原因) #### nic-irq · NIC 中斷集中在單一核心 · Single-queue NIC / no RSS NIC 只把封包到達的中斷(interrupt)送給一個 CPU 核心時,那個核心就會成為瓶頸。 - 為什麼 → 於是 → 畫面上:只有一個接收佇列,或是負責分散到多個核心的 RSS 被關閉 → 單一核心達到 100%,無法及時取出封包 → 人潮湧入時,整個伺服器出現封包遺失與延遲(瞬移、輸入延遲) - 症狀:瞬移, 拉回, 輸入延遲/因素:遺失, 延遲 - 誰會遇到:整個伺服器/何時:人潮湧入時 - 主要負責:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:設定 RSS(由 NIC 分散)與 RPS(由 kernel 分散),把中斷分散到多個核心;讓 UDP 連同 port 一起計算來分配佇列(ethtool -N 的 rx-flow-hash udp4 sdfn);把處理中斷的核心與遊戲 tick 執行緒的核心分開;監看各核心的 %soft。 - 數值參考:一個核心經由 kernel 能處理的量,依封包大小與設定而定,大約是每秒數十萬個封包。如果各核心的使用率中,接收處理所占的比例(mpstat 的 %soft)只集中在一個核心,就是這種情況。 - 圖表上:碰到上限後持平(各核心 %soft、每秒接收封包數) - 查看位置:用 mpstat -P ALL 1 查看各核心的 %soft(軟體中斷處理比例),用 /proc/interrupts 確認各 NIC 佇列的中斷送往哪個核心,用 ethtool -l 確認佇列數,用 ethtool -S 確認各佇列的封包數(名稱依驅動程式而異) - 符合的跡象:只有一個核心的 %soft 貼近 100%,其餘核心很閒,中斷與封包集中在單一佇列。從那時起每秒接收封包數無法再往上 - 不符合的跡象:%soft 平均分散在多個核心時,就不是這個原因。CPU 很閒卻有封包遺失時,是「超過雲端 PPS 上限」或「Ring buffer 不足」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:即使有多個佇列,如果大部分流量來自閘道、proxy 這類少數幾個位址,還是會集中到同一個佇列。部分 NIC 的預設設定只看位址來分配 UDP 的佇列,要改成連同 port 一起看,才會平均分散。 - 出處: - [Scaling in the Linux Networking Stack](https://docs.kernel.org/networking/scaling.html) · Linux kernel · RSS(由 NIC 分散到多個接收佇列)、RPS(由 kernel 分散),每個佇列各自有中斷並分配到多個核心的設定,接收中斷處理成為瓶頸時建議使用 RSS - [How to receive a million packets per second](https://blog.cloudflare.com/how-to-receive-a-million-packets/) · Cloudflare · 單一接收佇列只送往一個核心時,實測該核心在每秒約 35 萬~43 萬個封包就卡住,也有 NIC 只用 IP 位址對 UDP 做雜湊、全部集中到一個佇列的案例 - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · 用 ethtool -N rx-flow-hash udp4 把 port(f、n)也納入 UDP 雜湊的選項 - [mpstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/mpstat.1.html) · sysstat · %soft:CPU 用於軟體中斷處理的時間比例,-P ALL 顯示各核心 #### nic-ring · Ring buffer 不足 · RX ring buffer overflow NIC 暫存封包的 ring buffer 太小時,封包瞬間湧入就會讓緩衝區滿溢而被丟棄。 - 為什麼 → 於是 → 畫面上:ring buffer 仍是預設值(依驅動程式為 256~2,048 個 slot),容量偏小 → 突發流量來時,CPU 還沒取走封包,緩衝區就滿了 → 只在突發流量的瞬間出現封包遺失(瞬移、技能被吃)。遊戲伺服器 log 中看不到任何痕跡 - 症狀:瞬移, 吃指令/回檔/因素:遺失 - 誰會遇到:整個伺服器/何時:人潮湧入時 - 主要負責:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:加大 ring buffer(ethtool -G),監看丟棄(drop)計數器(ethtool -S 的 rx_missed_errors 等,名稱依驅動程式而異)。 - 數值參考:每秒湧入 100 萬個封包時,1,024 個 slot 約 1ms 就會填滿。這段期間 CPU 只要晚來一次就會滿溢。大部分 NIC 可以加大到數千個。 - 圖表上:偶爾隨機飆高(NIC 接收丟棄計數器) - 查看位置:以短間隔收集 ethtool -S 的接收丟棄計數器(rx_missed_errors、rx_fifo_errors 等,名稱依驅動程式而異)與 ip -s -s link 的 missed,並用 ethtool -g 確認目前的 ring 大小與最大值 - 符合的跡象:突發流量的瞬間丟棄計數器增加,目前的 ring 大小遠小於最大值。加大 ring 後丟棄減少 - 不符合的跡象:丟棄計數器沒有變化卻仍有封包遺失時,問題在 kernel 的下一個階段(「Kernel socket 緩衝區不足」)或網路區段。單一核心的 %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 · e1000 接收描述子(ring slot)預設 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 · 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 為了減輕 CPU 負擔,NIC 把封包累積起來再一次通知 CPU(中斷合併,interrupt coalescing)時,累積多久就會晚多久。 - 為什麼 → 於是 → 畫面上:NIC 累積一定時間或一定數量的封包後才通知 → 累積期間,封包只能等待 → 延遲些微增加。通常很小,設定過度時會達到 ms 等級 - 症狀:輸入延遲/因素:延遲 - 誰會遇到:整個伺服器/何時:一直都有 - 主要負責:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:使用自適應合併,調整成適合遊戲伺服器的數值(ethtool -C)。 - 數值參考:通常為數十~數百 µs。對遊戲來說大多可以忽略,但設定過度時會放大到 ms 等級。 - 圖表上:一開始就一直偏高(同一資料中心內的往返時間) - 查看位置:用 ethtool -c 查看目前的合併設定(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 · 預設使用自適應中斷調節,每秒 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(約 67ms),數值越大,接收延遲越長 - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · ethtool -C 的 adaptive-rx、rx-usecs、rx-frames 設定 #### nic-cloud-pps · 超過雲端 PPS 上限 · Cloud PPS / bandwidth allowance 雲端伺服器依類型各有每秒封包數與頻寬的上限,超過時會默默丟棄。 - 為什麼 → 於是 → 畫面上:同時上線人數增加,每秒封包數超過執行個體上限 → 雲端網路丟棄超出的部分 → 找不出原因的封包遺失造成瞬移、技能被吃。伺服器 CPU 還有餘裕 - 症狀:瞬移, 吃指令/回檔/因素:遺失 - 誰會遇到:整個伺服器/何時:人潮湧入時, 晚間尖峰時段 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發), 外部(外部) - 遊戲開發團隊要做的事:合併封包(把一個 tick 的訊息放進一個封包),避免頻繁送出很小的封包。 - 基礎設施團隊要做的事:確認超過上限的計數器(AWS 為 pps_allowance_exceeded、conntrack_allowance_exceeded 等)並設定警示,改用更大的執行個體,連線追蹤上限則用不會產生追蹤的安全群組配置來避開。 - 外部要做的事:向雲端供應商詢問各執行個體類型的每秒封包數與連線追蹤上限。 - 數值參考:上限依執行個體大小而異,每秒封包數的上限多半不公開。小型執行個體的「最高 10Gbps」只是額度(credit)還有剩時(通常 5~60 分鐘)才能使用的突發速度,平時的基準速度低得多。 - 圖表上:碰到上限後持平(每秒封包數、allowance 超限計數器) - 查看位置:以短間隔收集 ethtool -S 的 ENA 計數器 pps_allowance_exceeded、bw_in_allowance_exceeded、bw_out_allowance_exceeded、conntrack_allowance_exceeded,與每秒封包數一起看。也可以用 CloudWatch agent 上傳這些計數器並設定警示 - 符合的跡象:遺失發生的時間點 allowance 超限計數器增加,每秒封包數卡在某個數值上不去。伺服器 CPU 還有餘裕 - 不符合的跡象:超限計數器沒有變化時,就不是這個原因。單一核心的 %soft 達到 100% 時是「NIC 中斷集中在單一核心」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:conntrack_allowance_exceeded 表示連線追蹤表已滿、新連線被丟棄。如果追蹤表還有餘裕,只是閒置連線的追蹤過期而斷線,請參考「雲端安全群組的連線追蹤過期」。 - 出處: - [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、連線追蹤上限,超過時先放進佇列再丟棄,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 · 16 vCPU 以下執行個體的「最高 N Gbps」是消耗網路 I/O 額度的突發速度(通常 5~60 分鐘),額度用完就回到基準頻寬 #### nic-saturate · NIC 頻寬飽和 · NIC bandwidth saturation 把 1Gbps、10Gbps 網路卡用到極限時,傳送佇列會變長,最後封包被丟棄。 - 為什麼 → 於是 → 畫面上:廣播增加,使傳輸量達到網路卡的極限 → 傳送佇列變長,滿了就丟棄 → 整個伺服器出現延遲與封包遺失(輸入延遲、瞬移) - 症狀:輸入延遲, 瞬移/因素:延遲, 遺失 - 誰會遇到:整個伺服器/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:減少傳輸量(AOI、壓縮、只送變更的部分)。 - 基礎設施團隊要做的事:升級網路卡(更快的 NIC,雲端則換更大的執行個體),設定 NIC 使用率警示。 - 圖表上:碰到上限後持平(NIC 傳送量、傳送丟棄) - 查看位置:把 sar -n DEV 1 的 txkB/s 與 %ifutil(相對於介面速度的使用率)和 NIC 速度、執行個體頻寬比較,並一起查看 ip -s link 的 TX dropped - 符合的跡象:傳送量在 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 · -n DEV 的 rxkB/s、txkB/s 與 %ifutil(相對於介面速度的使用率) #### nic-noisy · 虛擬化額外開銷與 noisy neighbor · Noisy neighbors in virtualization 同一台實體伺服器上的其他虛擬機器大量使用網路或 CPU 時,自己伺服器的處理會不規則地被延後。 - 為什麼 → 於是 → 畫面上:同一台實體伺服器上的其他虛擬機器大量使用資源 → 自己虛擬機器的封包處理不規則地延遲 → 沒有明顯原因,偶爾出現抖動(封包抵達間隔忽長忽短)而卡頓 - 症狀:卡頓/因素:抖動 - 誰會遇到:整個伺服器/何時:偶爾隨機發生 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:外部(外部) - 基礎設施團隊要做的事:使用專用主機或效能有保障的執行個體;抖動持續的執行個體,先停止再啟動,以搬到其他主機。 - 外部要做的事:向雲端供應商回報有問題的主機。 - 圖表上:偶爾隨機飆高(同一資料中心內往返時間的抖動、%steal) - 查看位置:持續對同一資料中心的其他伺服器送出 ping,記錄往返時間的抖動,並連同 mpstat 的 %steal 與相同配置的其他執行個體比較 - 符合的跡象:只有這台執行個體的往返時間抖動或 %steal 不規則地飆高,相同配置的其他執行個體很平穩。先停止再啟動、搬到其他主機後就消失 - 不符合的跡象:相同配置的執行個體都一樣飆高時,就不是主機的問題。改查遊戲伺服器端的負載或網路區段 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [How EC2 instance stop and start works](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/how-ec2-instance-stop-start-works.html) · AWS · 執行個體停止後再啟動,大多會搬到新的主機(專用主機除外) - [mpstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/mpstat.1.html) · sysstat · %steal:hypervisor 執行其他虛擬 CPU 時,這個虛擬 CPU 被迫等待的時間比例 #### nic-host-maintenance · 雲端主機維護與即時遷移 · Cloud host maintenance / live migration 雲端供應商維護實體伺服器(主機)時,會把虛擬機器搬到其他主機(即時遷移),或暫時停住。這段期間整台伺服器都會停住,停住的時間一長,連線就會中斷。 - 為什麼 → 於是 → 畫面上:供應商因主機維護或故障預測,把虛擬機器搬到其他主機,或短暫暫停 → 搬移期間 CPU、記憶體、網路變慢,最後虛擬機器會短暫完全停住(依供應商與方式,從不到 1 秒到 30 秒左右) → 伺服器上所有人的畫面同時停住,接著快轉、瞬移;停住的時間比逾時長時,會大量斷線 - 症狀:定格, 快轉, 瞬移, 斷線/因素:停滯, 遺失 - 誰會遇到:整個伺服器/何時:偶爾隨機發生 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發), 外部(外部) - 遊戲開發團隊要做的事:設定能撐過數秒停頓的逾時,停住後追趕的 tick 數要設上限,經過時間用 monotonic clock 計算,收到維護通知時要有儲存進度並把玩家移到其他伺服器的流程。 - 基礎設施團隊要做的事:訂閱維護通知並設定警示(Google Cloud maintenance-event、AWS 排程事件與 AWS Health、Azure Scheduled Events);收到通知後,趁玩家少的時段先替換伺服器;供應商允許時調整維護時間(Azure Maintenance Configuration,AWS 排程事件依類型而定);把維護紀錄與事故紀錄對照。 - 外部要做的事:向雲端供應商確認維護時程與影響範圍,同一執行個體反覆停頓時提出回報。 - 數值參考:Google Compute Engine 表示即時遷移的停頓通常遠短於 1 秒,停頓期間系統時鐘最多可能往前跳 5 秒。中繼資料的 maintenance-event 值會在搬移前 60 秒改變(前提是之前至少查詢過一次這個值)。Azure 表示不需重新開機的維護幾乎都在 10 秒以內,少數情況(一般 VM 大小每 18 個月不超過一次)會停頓約 30 秒,即時遷移通常不超過 5 秒。Azure Scheduled Events 至少會在 15 分鐘前通知這類停頓(Freeze)。不過主機硬體突然故障時,會不經通知直接開始復原。 - 圖表上:中斷後一次湧入(伺服器收發封包數、tick 間隔) - 查看位置:把停住的時間點與供應商紀錄對照。Google Cloud 看稽核 log 中的 compute.instances.migrateOnHostMaintenance,AWS 看 describe-instance-status 與 AWS Health 的排程事件,Azure 看活動 log(Activity Log)中的 Microsoft.Compute/virtualMachines/liveMigration/action,以及 VM 可用性指標(VmAvailabilityMetric)降為 0 的時間點。在伺服器內部查看停住期間指標與 log 是否出現空白,以及之後時鐘是否跳動(時間同步 log) - 符合的跡象:整台伺服器停住的時間點與供應商記錄的維護或遷移時間重疊,且那幾秒內伺服器內部的指標與 log 全部空白 - 不符合的跡象:供應商紀錄中沒有、短暫停頓頻繁反覆時是「CPU steal(虛擬機器)」。kernel log 中有 NIC 重設紀錄時是「NIC 驅動程式/韌體問題」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:AWS 以排程事件通知。system-reboot 表示會重新開機並搬到新主機,system-maintenance 表示可能因網路、電源維護而短暫受影響。即使只停住幾秒,這段期間對伺服器送出封包卻沒收到 ACK 的用戶端,會把重傳等待時間一次次加倍,所以恢復之後 TCP 連線可能停住更久(「TCP RTO 與指數退避」)。停住後醒來時時鐘會跳動,有時會引發「系統時鐘跳動(NTP step)」,負載平衡器的健康檢查也可能失敗,把這台伺服器暫時移出。無法搬移的執行個體(Google Cloud 的裸機執行個體等)在維護時會被停止或重新啟動。 - 出處: - [Live migration process during maintenance events](https://cloud.google.com/compute/docs/instances/live-migration-process) · Google Cloud · 即時遷移的停頓通常遠短於 1 秒,停頓期間系統時鐘最多往前跳 5 秒,搬移期間磁碟、CPU、記憶體、網路效能會暫時下降,不做即時遷移的 VM 在維護時會被關閉(裸機執行個體不支援) - [Query metadata server for maintenance event notices](https://cloud.google.com/compute/docs/metadata/getting-live-migration-notice) · Google Cloud · maintenance-event 中繼資料值在即時遷移前 60 秒改變(限設定為即時遷移,且上次維護之後至少查詢過一次這個值的情況) - [Monitor and plan for a host maintenance event](https://cloud.google.com/compute/docs/instances/monitor-plan-host-maintenance-event) · Google Cloud · 維護時稽核 log 會留下 compute.instances.migrateOnHostMaintenance 系統事件 - [Scheduled events for Amazon EC2 instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-instances-status-check_sched.html) · AWS · 排程事件類型(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 秒以內停頓,少數情況(一般 VM 大小每 18 個月不超過一次)約 30 秒,即時遷移通常 5 秒以下,停頓後時鐘自動同步,長時間的 TCP 連線可能中斷,或對方以指數退避重傳送往停住 VM 的資料而使復原更慢,負載平衡器健康檢查約在 10 秒內判定為不健康,用活動 log 的 Microsoft.Compute/virtualMachines/liveMigration/action 與停頓期間降為 0 的 VmAvailabilityMetric 確認,用 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 驅動程式 bug 或功能異常讓網路卡停住並重新啟動,這段期間所有收發都會中斷。 - 為什麼 → 於是 → 畫面上:驅動程式 bug、offload 功能異常 → NIC 停住後重新啟動(數秒) → 那台伺服器上所有人的畫面一起停住,接著瞬移或斷線 - 症狀:定格, 斷線/因素:遺失 - 誰會遇到:整個伺服器/何時:偶爾隨機發生, 開越久越嚴重 - 主要負責:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:確認 kernel log 中的「transmit queue … timed out」、「Link is Down」紀錄並設定警示,更新驅動程式與韌體,關閉有問題的功能(offload 等)。 - 圖表上:中斷後一次湧入(伺服器收發封包數) - 查看位置:用 dmesg 在 kernel log 中找「NETDEV WATCHDOG … transmit queue N timed out」、驅動程式的重設,以及「Link is Down」、「Link is Up」紀錄,並查看該時間點伺服器的收發封包數 - 符合的跡象:停住的時間點 kernel log 中有傳送佇列逾時或鏈路 down/up 紀錄,且那幾秒內收發封包數為 0 - 不符合的跡象:kernel log 很乾淨、交換器端的 port 也正常時,是遊戲伺服器處理程序的停頓(「伺服器 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 · 傳送佇列停住時,kernel 看門狗會留下「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 或 kernel 把到達的封包合併後處理 → 開啟硬體合併(LRO)或合併等待時間設定時,會短暫等待下一個封包 → 延遲些微增加(大多在數十 µs 以下) - 症狀:輸入延遲/因素:延遲 - 誰會遇到:整個伺服器/何時:一直都有 - 主要負責:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:依遊戲流量調整(關閉 LRO,確認合併等待時間設定),效果通常很小,排在其他原因之後確認。 - 圖表上:一開始就一直偏高(同一資料中心內的往返時間) - 查看位置:用 ethtool -k 確認 lro、gro 狀態,確認裝置的 sysfs 設定 gro_flush_timeout 值,並比較修改前後同一資料中心內小封包的往返時間 - 符合的跡象: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 · ethtool -K 的 gro、lro on|off 設定 ### L7 伺服器 OS(kernel)(14 個原因) #### so-backlog · 連線等待佇列(backlog)溢位 · Listen backlog / SYN queue overflow 維護剛結束時數萬人同時連線,kernel 的連線等待佇列(backlog)會溢位,連線請求因此被丟棄。 - 為什麼 → 於是 → 畫面上:維護一結束,連線湧入的速度就超過遊戲伺服器用 accept 處理連線的速度 → kernel 的連線等待佇列(backlog,取伺服器程式碼傳給 listen 的值與 kernel 上限中較小者)已滿 → 連線請求被丟棄後不斷重試,結果連不上/無限讀取 - 症狀:連不上/無限讀取/因素:遺失 - 誰會遇到:整個伺服器/何時:剛登入/維護剛結束 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:調高程式碼傳給 listen 的值、避免負責接受連線的執行緒被其他工作卡住、導入登入排隊系統。用戶端:拉長重試間隔(加上隨機值分散重試)。 - 基礎設施團隊要做的事:調高 kernel 的 somaxconn(要和伺服器程式碼的 listen 值一起調高才有效)、保持 SYN cookie 開啟、監控溢位次數(nstat 的 TcpExtListenOverflows)。 - 數值參考:Linux kernel 的上限(somaxconn)從 5.4 起預設為 4,096(之前是 128),但伺服器程式碼傳給 listen 的值較小時,就以該值為上限。佇列滿了之後,Linux 會默默丟棄連線請求,不回傳任何錯誤。用戶端 OS 會從 1 秒後開始重送幾次,所以玩家看到的是遲遲跑不完的讀取畫面,很少直接看到「連線失敗」。Windows 伺服器則會回傳拒絕回應,用戶端馬上就會看到「連線失敗」。 - 圖表上:剛開服或維護結束後暴增(連線等待佇列溢位次數(ListenOverflows)、連線嘗試次數) - 查看位置:看 nstat -az 的 TcpExtListenOverflows、TcpExtListenDrops 增加量,並用 ss -ltn 比較 listen socket 的 Recv-Q(等待 accept 的連線數)與 Send-Q(backlog 上限) - 符合的跡象:連線湧入的時間點 ListenOverflows 增加,listen socket 的 Recv-Q 貼齊 Send-Q 的值 - 不符合的跡象:ListenOverflows 沒有變化就不是這個原因。連線已建立但讀取跑不完時是「登入暴增與 N+1 查詢」;剛好卡在固定人數時是「檔案描述子上限」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [listen(2) — Linux manual page](https://man7.org/linux/man-pages/man2/listen.2.html) · Linux man-pages · listen 的 backlog 超過 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:因 accept 佇列已滿而丟棄連線請求(SYN)的次數,此時 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 · ss 的 Recv-Q、Send-Q 值:listen socket 是等待 accept 的連線數與 backlog 上限;已連線的 socket 是應用程式尚未讀取的位元組數與尚未收到 ACK 的已送出位元組數 #### so-fd · 檔案描述子上限 · File descriptor limit (ulimit) 每個連線都需要一個檔案描述子(fd,OS 為開啟的檔案或 socket 指定的編號),而一個處理程序能開啟的 fd 數量有上限。 - 為什麼 → 於是 → 畫面上:同時上線人數達到處理程序的檔案描述子上限 → 伺服器無法接受新連線(Too many open files),開啟 log 檔與 DB 連線也跟著失敗 → 從某個固定人數起誰都進不來,出現連不上/無限讀取 - 症狀:連不上/無限讀取/因素:遺失 - 誰會遇到:整個伺服器/何時:剛登入/維護剛結束, 人潮湧入時 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:結束連線時確實關閉 socket(防止 fd 洩漏);accept 因 EMFILE(fd 不足)失敗時,暫停接受連線一段時間,或用事先保留的備用 fd 接受後立刻關閉(避免反覆處理同一個連線通知而浪費 CPU)。 - 基礎設施團隊要做的事:確認 ulimit 與服務設定(systemd 的 LimitNOFILE),接近上限時發出警示。 - 數值參考:Linux 若沒有另外設定服務,上限仍常是 1,024。遊戲伺服器通常會調高到數萬~數十萬。Windows 沒有這麼低的預設上限。 - 圖表上:碰到上限後持平(處理程序開啟的 fd 數、同時上線人數) - 查看位置:用 pidstat -v 看遊戲伺服器處理程序的 fd-nr(開啟的檔案描述子數),用 /proc/PID/limits 看開啟檔案數上限,並在伺服器 log 中找 accept 失敗(EMFILE, Too many open files) - 符合的跡象:fd 數在上限值持平,從那個時間點起 accept 以 EMFILE 失敗 - 不符合的跡象:fd 數遠低於上限就不是這個原因。連線請求在 kernel 被丟棄時是「連線等待佇列(backlog)溢位」;問題出在連線追蹤時是「伺服器 conntrack 表飽和」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:沒被接受的連線會一直留在 kernel 的連線等待佇列(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(軟性上限 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 · Windows Winsock 只以可用記憶體限制 socket 數量 - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -v 的 fd-nr:處理程序開啟的檔案描述子數 - [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 列出每個處理程序各項資源上限的軟性與硬性值 #### so-sockbuf · Kernel socket 緩衝區不足 · Small socket buffers 傳送與接收緩衝區太小時,一遇到突發流量,UDP 收到的封包就會被丟棄,TCP 則因緩衝區沒有空間而無法傳送。 - 為什麼 → 於是 → 畫面上:SO_SNDBUF、SO_RCVBUF 維持預設值或設得太小 → 突發流量或接收執行緒短暫停住時,UDP 接收緩衝區溢位而丟棄封包;TCP 則因傳送緩衝區沒有空間而等待 → 瞬移(UDP 遺失)或快轉(TCP 等待) - 症狀:瞬移, 快轉/因素:遺失, 停滯 - 誰會遇到:整個伺服器/何時:人潮湧入時 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:在程式碼中依流量設定緩衝區大小(SO_SNDBUF、SO_RCVBUF);注意 TCP 一旦直接指定大小,Linux 的自動調整(autotuning)就會關閉;設得太大會讓舊資料堆在緩衝區裡、延遲增加,所以大小要適中;避免接收執行緒停住。 - 基礎設施團隊要做的事:調整 kernel 上限(rmem_max、wmem_max,程式碼設定的緩衝區大小也不能超過這個值)與預設值(rmem_default),監控緩衝區溢位計數器(RcvbufErrors)。 - 數值參考:Linux 的 UDP 接收緩衝區預設值約 208KB。即使是小封包,每個在 kernel 記憶體中占用的空間也遠大於實際大小,數十~數百個就會塞滿。每秒接收 10 萬個封包的伺服器,接收執行緒只要停住幾 ms 就會溢位。 - 圖表上:偶爾隨機飆高(UDP 接收緩衝區溢位(UdpRcvbufErrors)) - 查看位置:看 nstat -az 的 UdpRcvbufErrors 增加量與 ss -uamn 的 skmem(rb 是接收緩衝區大小,d 是無法放進 socket 而丟棄的封包數);TCP 則看 ss -tm 的 skmem 中,傳送等待記憶體(w)是否碰到傳送緩衝區大小(tb) - 符合的跡象:突發流量或接收執行緒停住的時間點 UdpRcvbufErrors(或 socket 的 d)增加,rb 在預設值(約 208KB)附近。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,設定值會被 kernel 加倍 - [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 · 預設 socket 緩衝區定義為含 sk_buff 額外開銷的 256 位元組封包 256 個份(SKB_TRUESIZE(256)×256);小訊框也以 sk_buff+MTU 計算(約 208KB 是 x86-64 上的計算值) - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_rmem、tcp_wmem:直接設定 SO_RCVBUF、SO_SNDBUF 時,該 socket 的自動調整會關閉 - [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 接收佇列超過 socket 緩衝區大小時會直接丟棄,並增加 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 顯示的計數器名稱:Udp 群組的 RcvbufErrors、SndbufErrors - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · -m 的 skmem:rb 接收緩衝區大小,tb 傳送緩衝區大小,w 傳送等待記憶體,d 放進 socket 前就丟棄的封包數 #### so-context · 執行緒過多與 context switch · Thread oversubscription, context switching 執行緒數量遠多於 CPU 核心時,OS 會把 CPU 花在輪流切換執行緒上。 - 為什麼 → 於是 → 畫面上:例如每個連線各開一個執行緒,讓執行緒多達數百~數千個 → context switch(切換執行中的執行緒)的成本與快取未命中增加 → CPU 很忙,處理量卻很低,tick 忽長忽短,出現卡頓、慢動作 - 症狀:卡頓, 慢動作/因素:停滯, 抖動 - 誰會遇到:整個伺服器/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:執行緒數對應 CPU 核心數,使用非同步 I/O(epoll、IOCP)。 - 基礎設施團隊要做的事:監控 context switch 次數與等待執行的執行緒數(vmstat 的 cs、r)。 - 數值參考:一次 context switch 要數 µs,再加上之後的快取未命中成本會更高。 - 圖表上:隨人數/負載上升(每秒 context switch 次數、等待執行的執行緒數) - 查看位置:將 vmstat 1 的 cs(每秒 context switch 次數)、r(正在執行或等待 CPU 的數量)與 CPU 核心數比較,並用 pidstat -w -t 看遊戲伺服器各執行緒的自願(cswch/s)與非自願(nvcswch/s)context switch - 符合的跡象:同時上線人數增加時,r 遠高於核心數,cs 也跟著往上衝,有數百個執行緒出現大量非自願 context switch - 不符合的跡象:r 維持在核心數以下就不是這個原因。只有自願切換偏多時,是執行緒在等鎖或 I/O(「鎖競爭」、「阻塞式 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 · 以預先建立的執行緒池與 IOCP 處理大量非同步 I/O,並讓同時執行的執行緒數符合 CPU 的並行能力 - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -w 的 cswch/s 是為了等待資源而主動讓出的自願 context switch,nvcswch/s 是用完時間片段而被強制切換的非自願 context switch;加 -t 可看各執行緒 #### so-steal · CPU steal(虛擬機器) · CPU steal time 實體伺服器(hypervisor)把虛擬機器的 CPU 時間暫時讓給其他虛擬機器(CPU steal)時,遊戲伺服器會停住。 - 為什麼 → 於是 → 畫面上:同一台主機上的其他虛擬機器大量使用 CPU → 自己的虛擬機器每次失去數 ms~數十 ms 的執行機會 → tick 時間莫名暴增,出現卡頓、定格 - 症狀:卡頓, 定格/因素:停滯 - 誰會遇到:整個伺服器/何時:偶爾隨機發生 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:外部(外部) - 基礎設施團隊要做的事:監控 steal 指標(top、vmstat 的 st);使用專用核心或專用主機;避免 CPU 額度用完就變慢的突發型(burstable)執行個體;steal 持續偏高的執行個體先停止再啟動,移到其他主機。 - 外部要做的事:向雲端供應商回報 steal 持續偏高的主機。 - 圖表上:偶爾隨機飆高(%steal、伺服器 tick 時間) - 查看位置:把 mpstat -P ALL 1 的 %steal 和伺服器 tick 時間放在同一條時間軸上看 - 符合的跡象:tick 飆高的時間點 %steal 也一起飆高,停止再啟動移到其他主機後就減少 - 不符合的跡象:%steal 接近 0 但 tick 仍飆高時,是遊戲伺服器內部的原因(「伺服器 GC 全面暫停」、「鎖競爭」)。在容器上時是「容器 CPU 節流(CFS 配額)」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [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 · 突發型執行個體會消耗額度來超出基準效能,額度用完後 CPU 使用率就降回基準水準 - [How EC2 instance stop and start works](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/how-ec2-instance-stop-start-works.html) · AWS · 執行個體停止後再啟動,大多會移到新的主機 - [mpstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/mpstat.1.html) · sysstat · %steal:hypervisor 執行其他虛擬 CPU 時,這個虛擬 CPU 被迫等待的時間比例 #### so-cpu-quota · 容器 CPU 節流(CFS 配額) · Container CPU throttling (CFS quota) 容器設了 CPU 上限時,只要在固定週期(通常是 100ms)內用完配額,剩下的時間就會被強制暫停(節流)。 - 為什麼 → 於是 → 畫面上:在 Kubernetes 等平台上為遊戲伺服器容器設定 CPU 上限(limit) → tick 計算集中的瞬間用完配額,暫停數十 ms 直到下一個週期 → 平均 CPU 很低,tick 卻週期性飆高,出現卡頓、慢動作 - 症狀:卡頓, 慢動作/因素:停滯, 抖動 - 誰會遇到:整個伺服器/何時:人潮湧入時, 偶爾隨機發生 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:讓工作執行緒數符合 CPU 上限(避免 runtime 依主機的全部核心數建立執行緒)。 - 基礎設施團隊要做的事:把 CPU 上限設得寬鬆一些,或拿掉上限改配置專用核心;監控被節流的次數(nr_throttled)。 - 數值參考:上限 2 核心的伺服器若有 8 個執行緒同時工作,會在 25ms 內用完 100ms 週期的配額,然後暫停 75ms。 - 圖表上:隨人數/負載上升(節流次數(nr_throttled)、伺服器 tick 時間) - 查看位置:將容器 cgroup 的 cpu.stat 中 nr_throttled、throttled_usec(cgroup v1 為 nr_throttled、throttled_time)的增加量與伺服器 tick 時間一起看 - 符合的跡象:平均 CPU 使用率低於上限,nr_throttled、throttled_usec 卻持續增加,且與 tick 飆高的時間點重疊 - 不符合的跡象:nr_throttled 沒有增加就不是這個原因。虛擬機器本身分不到 CPU 時是「CPU steal(虛擬機器)」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [CFS Bandwidth Control](https://docs.kernel.org/scheduler/sched-bwc.html) · Linux kernel · 用完每個週期分到的配額後,執行緒會暫停到下一個週期(節流);預設週期 100ms;nr_throttled 統計 - [Control Group v2](https://docs.kernel.org/admin-guide/cgroup-v2.html) · Linux kernel · cpu.max 的格式為「$MAX $PERIOD」(配額、週期),預設值為「max 100000」(100ms 週期) - [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) · Kubernetes · 容器的 CPU limit 是 kernel 以 CPU 節流強制執行的硬性上限 #### so-cstate · 伺服器電源管理(C-state、頻率調整)造成延遲飆高 · CPU power management latency (C-states, frequency scaling) 閒置的 CPU 核心為了省電,會進入深層省電狀態(C-state)並降低頻率。封包或計時器到來時,喚醒核心並拉高頻率需要時間,處理小封包時就會多出延遲。 - 為什麼 → 於是 → 畫面上:OS 的頻率調整策略(governor)或 BIOS 電源設定允許深層 C-state 與低頻率 → 閒置核心每次從深層省電狀態喚醒都會慢上最多數百 µs;頻率被鎖在低檔時,tick 計算本身也會變慢 → 通常很難察覺,但伺服器之間的呼叫一多就會累積起來,變成人少時回應反而變慢的輸入延遲。頻率被鎖在低檔時,人潮湧入就會讓 tick 延後,出現慢動作 - 症狀:輸入延遲, 慢動作/因素:延遲, 抖動, 停滯 - 誰會遇到:整個伺服器/何時:一直都有, 偶爾隨機發生 - 主要負責:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:BIOS 電源設定改為效能優先,OS governor 設為 performance(cpufreq 的 scaling_governor);對延遲敏感的伺服器限制深層 C-state(tuned latency-performance 設定檔、PM QoS 的 /dev/cpu_dma_latency、kernel 參數 intel_idle.max_cstate);變更後一併比較同一資料中心內的往返時間、tick 時間抖動與耗電量。 - 數值參考:依 Linux 6.12 的 intel_idle 驅動程式表格,Intel 伺服器 CPU 從淺層 C1 喚醒需要 1~2µs,從深層 C6 需要 133µs(Skylake-SP)~290µs(Sapphire Rapids)。單次很短,但一個請求經過好幾台伺服器時就會跟著累積。kernel 預期的閒置時間越長,就越會選擇深層狀態,所以在封包零星到來的冷清伺服器上更常出現。通用 cpufreq 的 powersave governor 會把頻率固定在允許範圍內的最低值(intel_pstate 中同名的演算法則會依負載調整)。 - 圖表上:一開始就一直偏高(同一資料中心內的往返時間、核心頻率) - 查看位置:用 cpupower monitor 看各核心停留在各 C-state 的比例與實際頻率,確認 /sys/devices/system/cpu/cpu0/cpuidle/ 底下每個 state 的 name、latency(喚醒所需的 µs)、usage 與 cpufreq 的 scaling_governor,並用 tuned-adm active 確認目前的設定檔 - 符合的跡象:核心閒置時長時間停在最深的 C-state,或頻率被鎖在最低值附近;改用 performance governor 與淺層 C-state 後,小請求的往返時間與抖動減少 - 不符合的跡象:變更後差異在數十 µs 以內,這個原因可以忽略。以 ms 為單位飆高時是「CPU steal(虛擬機器)」或其他層 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:IDC 機房的裸機(bare metal)伺服器要同時檢查 BIOS(韌體)的電源設定與 OS 設定。在雲端上,只有部分執行個體類型能讓 OS 變更 C-state 與頻率,而 AWS 的預設設定偏向最高效能,大多維持原樣即可。Red Hat 系列的 tuned latency-performance 設定檔會把 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),並依預期閒置時間選擇深層狀態;sysfs 中各 state 的 latency、usage、time;以 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 · Intel 伺服器 CPU 各 C-state 的喚醒時間: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 · 以 scaling_governor 確認與變更 governor;performance 會要求允許範圍內的最高頻率,powersave 則要求最低頻率 - [intel_pstate CPU Performance Scaling Driver](https://docs.kernel.org/admin-guide/pm/intel_pstate.html) · Linux kernel · intel_pstate 的 powersave 演算法和通用的 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 在記憶體耗盡時,會挑出使用最多記憶體的處理程序強制終止,通常就是遊戲伺服器。 - 為什麼 → 於是 → 畫面上:洩漏或用量暴增導致記憶體耗盡,或容器達到記憶體上限 → kernel 強制終止遊戲伺服器處理程序 → 該伺服器上的所有人同時斷線,最近的進度可能回檔 - 症狀:斷線, 吃指令/回檔/因素:停滯 - 誰會遇到:整個伺服器/何時:開越久越嚴重, 人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:修正洩漏;設定記憶體用量上限,並建立接近上限時先存檔再正常關閉的流程。 - 基礎設施團隊要做的事:設定記憶體警示、依實際用量設定容器記憶體上限、調整優先終止的順序(oom_score_adj)。 - 數值參考:kernel log(dmesg)會留下「Out of memory: Killed process」,在 Kubernetes 上則顯示為 OOMKilled。Windows 沒有 OOM killer,多半是記憶體配置失敗,伺服器因錯誤而當掉。 - 圖表上:連線同時大量中斷(連線數、記憶體用量) - 查看位置:將 dmesg 的「Out of memory: Killed process」紀錄、Kubernetes 上 Pod 狀態的 OOMKilled、cgroup v2 上 memory.events 的 oom_kill 增加,與斷線的時間點對照 - 符合的跡象:連線同時大量中斷的時間點,有終止遊戲伺服器處理程序的紀錄,而且在那之前記憶體用量一路升到上限 - 不符合的跡象:沒有 OOM 紀錄但處理程序死掉時,要查「伺服器當機」的當機 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)後,認可記憶體的配置會失敗,可能導致應用程式錯誤或系統故障 - [Control Group v2](https://docs.kernel.org/admin-guide/cgroup-v2.html) · Linux kernel · memory.events 的 oom_kill:這個 cgroup 中被 OOM killer 終止的處理程序數 #### so-reclaim · 記憶體回收與壓縮(compaction)造成的暫停 · Memory compaction / reclaim stalls (THP) OS 為了組出大分頁(huge page)而壓縮記憶體,或回收可用記憶體時,處理程序會停住。 - 為什麼 → 於是 → 畫面上:可用記憶體減少,或大分頁功能(THP)執行記憶體壓縮 → 要求記憶體的執行緒一直等到回收或壓縮結束 → 不規則的伺服器暫停(數 ms~數百 ms) - 症狀:定格, 卡頓/因素:停滯 - 誰會遇到:整個伺服器/何時:開越久越嚴重, 偶爾隨機發生 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:減少執行中的大型記憶體配置(啟動時預先配置並重複使用)。 - 基礎設施團隊要做的事:將大分頁(THP)設為只在需要的地方使用(madvise),調高可用記憶體的門檻(vm.min_free_kbytes 等)。 - 圖表上:偶爾隨機飆高(伺服器 tick 時間、記憶體 PSI) - 查看位置:將 /proc/pressure/memory 的 some、full(為等待記憶體而停住的時間比例)與 /proc/vmstat 的 compact_stall 增加量和伺服器 tick 時間一起看,並確認 /sys/kernel/mm/transparent_hugepage/defrag 設定 - 符合的跡象:tick 飆高的時間點記憶體 PSI 上升、compact_stall 增加。defrag 為 always - 不符合的跡象:PSI 與 compact_stall 沒有變化就不是這個原因。swap 用量增加時是「Swap」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Transparent Hugepage Support](https://docs.kernel.org/admin-guide/mm/transhuge.html) · Linux kernel · defrag=always 時,THP 配置失敗會當場回收、壓縮記憶體而停住;madvise 則只有提出要求的區域會這樣做 - [Documentation for /proc/sys/vm/](https://docs.kernel.org/admin-guide/sysctl/vm.html) · Linux kernel · min_free_kbytes:kernel 要保留的最低可用記憶體(watermark)門檻 - [PSI - Pressure Stall Information](https://docs.kernel.org/accounting/psi.html) · Linux kernel · /proc/pressure/memory 的 some(部分工作為等待記憶體而停住的時間比例)、full(所有工作都停住的時間比例) #### so-timejump · 系統時鐘跳動(NTP step) · Wall-clock jump (NTP step) 伺服器時鐘一次被往前或往後調整幾秒時,依賴系統時鐘的計時器會同時觸發或停住。 - 為什麼 → 於是 → 畫面上:時間同步一次大幅調整時鐘 → 計時器集中觸發或停住,逾時判定出錯 → buff 與冷卻時間異常、大家同時斷線、快轉 - 症狀:快轉, 斷線, 吃指令/回檔/因素:停滯 - 誰會遇到:整個伺服器/何時:偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:經過時間、逾時、冷卻時間都用不會跳動也不會倒退的單調時鐘(monotonic clock)計算,wall clock 只用於顯示與記錄。 - 基礎設施團隊要做的事:時鐘採漸進調整(chrony 的 makestep 只在剛啟動時使用),監控時間同步狀態(時鐘誤差)。 - 數值參考:ntpd 在差距超過 0.128 秒時會一次校正,差距較小時則慢慢校正,速度是消除 1 秒差距要花 30 多分鐘。現在常用的 chrony 在建議設定(makestep)下,只在剛啟動時一次校正幾次,之後就慢慢校正。虛擬機器短暫暫停後恢復時,時鐘也會跳動。 - 圖表上:偶爾隨機飆高(計時器觸發次數與斷線次數、時鐘調整紀錄) - 查看位置:在時間同步服務的 log 中找出一次大幅調整時鐘的紀錄,與發生異常的時間點對照。chrony 調整幅度超過 logchange 設定值(預設 1 秒)時會寫入 syslog - 符合的跡象:buff 與冷卻時間異常、同時斷線、快轉發生的時間點有時鐘調整紀錄,且調整幅度與異常的大小相近 - 不符合的跡象:沒有時鐘調整紀錄就不是這個原因。在虛擬機器上時,也要查是否暫停後恢復(「雲端主機維護與即時遷移」) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [ntpd - Network Time Protocol (NTP) daemon](https://www.ntp.org/documentation/4.2.8-series/ntpd/) · Network Time Foundation · 差距超過 step 門檻 128ms 時一次校正,較小時慢慢校正;每秒校正 0.5ms,所以校正 1 秒需要 2,000 秒(約 33 分鐘) - [chrony – Frequently Asked Questions](https://chrony-project.org/faq.html) · chrony · 建議像 makestep 1 3 這樣只在啟動後允許幾次 step;暫停後恢復的虛擬機器可能帶著錯誤的時間醒來 - [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 · 排程工作 · Cron jobs (log rotation, backup, scans) 每天固定時間執行的 log 壓縮、備份、安全掃描會占用 CPU 與磁碟。 - 為什麼 → 於是 → 畫面上:OS 工作在排定的時間執行 → 與遊戲伺服器共用 CPU 與磁碟 → 像每天凌晨 4 點這樣的固定時間出現卡頓、慢動作 - 症狀:卡頓, 慢動作/因素:停滯 - 誰會遇到:整個伺服器/何時:固定週期 - 主要負責:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:錯開工作時間、降低優先順序(nice、ionice)、與遊戲伺服器分開(在另一台伺服器上執行)。 - 圖表上:固定週期飆高(CPU 使用率、磁碟佇列、伺服器 tick 時間) - 查看位置:用 crontab 與 systemctl list-timers 整理排程工作的執行時間,並在 tick 飆高的時間點用 pidstat -u -d 看是哪個處理程序在使用 CPU 與磁碟 - 符合的跡象:tick 每天(或每小時)在同一時間飆高,那個時間點排程工作的處理程序占用 CPU 與磁碟 - 不符合的跡象:飆高的時間點和每天的固定時間對不上就不是這個原因。每隔幾秒或幾分鐘飆高時是「伺服器 GC 全面暫停」、「計時器集中同時觸發」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [ionice(1) — Linux manual page](https://man7.org/linux/man-pages/man1/ionice.1.html) · util-linux · idle 類別的工作只在其他程式沒有使用磁碟時才能取得 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 依處理程序顯示磁碟 I/O #### so-os-update · OS、kernel、驅動程式、韌體更新後的效能變化 · Performance regression after OS / kernel / driver / firmware update 遊戲程式碼沒變,但伺服器 OS、kernel、驅動程式、韌體更新後就變慢。更新有時會改變預設值、排程器、CPU 漏洞緩解措施(mitigations)與驅動程式的行為。 - 為什麼 → 於是 → 畫面上:定期安全性更新或新的伺服器映像檔讓 kernel、驅動程式、韌體有所改變 → 預設值或排程器改變,或啟用了新的漏洞緩解措施,同樣的工作要花更多 CPU 時間,執行緒分配到 CPU 的順序也跟著改變 → 原本正常的伺服器從更新那天起一直都慢一點,出現輸入延遲;人潮湧入時出現卡頓、慢動作 - 症狀:輸入延遲, 卡頓, 慢動作/因素:延遲, 停滯, 抖動 - 誰會遇到:整個伺服器/何時:一直都有, 人潮湧入時 - 主要負責:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:更新先套用在部分伺服器上,將 tick 時間、延遲、CPU 使用率與前一版比較後再擴大範圍;與遊戲更新錯開日期部署;記錄更新前後的 kernel、驅動程式、韌體版本與主要 sysctl 值;出問題時改用前一版 kernel 開機確認;是否關閉緩解措施(mitigations=off)要評估資安風險後再決定。 - 數值參考:kernel 版本改變,預設行為也會跟著改變。例如 Linux 從 6.6 起開始把排程器從 CFS 換成 EEVDF,連線等待佇列上限(somaxconn)的預設值也從 5.4 起由 128 改為 4,096。CPU 漏洞緩解措施會增加額外工作,例如從 kernel 返回程式時(每次系統呼叫結束時)以及 context switch、虛擬機器切換時清空 CPU 內部緩衝區,所以每個封包都要發出系統呼叫的網路伺服器受到的影響更大。有些漏洞要完全防堵就必須關閉 SMT(讓一個核心當成兩個執行緒使用的功能),而關閉 SMT 後,依工作性質效能可能大幅下降。kernel 參數 mitigations=off 會關閉所有緩解措施、找回效能,但系統會暴露在漏洞之下。 - 圖表上:從某個時間點起階梯式上升(伺服器 tick 時間、CPU 使用率、相同負載下的延遲) - 查看位置:將套件管理工具的更新紀錄與重新開機時間、uname -r 顯示的 kernel 版本、ethtool -i 顯示的 NIC 驅動程式資訊,與延遲上升的時間點對照。在相同負載下用 mpstat、pidstat 比較已更新與未更新的伺服器,也比較 /sys/devices/system/cpu/vulnerabilities/ 的緩解狀態 - 符合的跡象:延遲與 CPU 使用率從更新後重新開機的時間點起上升一階並維持不變,在相同負載下只有已更新的伺服器偏高。改用前一版 kernel 或驅動程式開機就恢復 - 不符合的跡象:在相同負載下已更新與未更新的伺服器一樣慢就不是這個原因。同一天也部署了遊戲更新,且每位玩家的封包數或大小有變時是「更新改變了流量模式」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:緩解狀態可在 /sys/devices/system/cpu/vulnerabilities/ 底下的檔案確認。預設值(mitigations=auto)會在保持 SMT 開啟的情況下進行緩解;設為 auto,nosmt 時,會在有漏洞的 CPU 上關閉 SMT,升級 kernel 後邏輯核心數可能減半。在遊戲更新的同一天更新 OS 會難以分辨原因,所以要分開部署。 - 出處: - [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 · 緩解措施會在從 kernel 返回使用者空間時與進入虛擬機器時清空 CPU 緩衝區;以 /sys/devices/system/cpu/vulnerabilities/ 底下的檔案確認漏洞與緩解狀態;許多 CPU 必須關閉 SMT 才能完全防堵,而關閉 SMT 依工作性質可能對效能影響很大 - [Spectre Side Channels](https://docs.kernel.org/admin-guide/hw-vuln/spectre.html) · Linux kernel · 為了緩解,會在 context switch 與虛擬機器切換時清空分支預測緩衝區;較強的緩解措施會讓所有程式都多出額外開銷 - [EEVDF Scheduler](https://docs.kernel.org/scheduler/sched-eevdf.html) · Linux kernel · Linux 從 6.6 起開始由 CFS 改為 EEVDF 排程器 - [listen(2) — Linux manual page](https://man7.org/linux/man-pages/man2/listen.2.html) · Linux man-pages · somaxconn 預設值從 Linux 5.4 起由 128 改為 4,096 - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · 以 ethtool -i 查詢網路裝置的驅動程式資訊 #### so-conntrack · 伺服器 conntrack 表飽和 · conntrack table full Linux 防火牆會把每條連線記錄在連線追蹤(conntrack)表中,這個表碰到上限時就會丟棄新封包。 - 為什麼 → 於是 → 畫面上:連線暴增或反覆建立短連線,讓連線紀錄增加 → 表已滿,新連線與部分封包被丟棄 → 連不上,或因原因不明的遺失出現瞬移 - 症狀:連不上/無限讀取, 瞬移/因素:遺失 - 誰會遇到:整個伺服器/何時:剛登入/維護剛結束, 人潮湧入時 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發), 遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:減少短連線(伺服器之間的呼叫重複使用連線)。用戶端:連線失敗或中斷時,逐步拉長重試間隔並加上隨機值分散。 - 基礎設施團隊要做的事:加大表的大小(nf_conntrack_max)、讓遊戲 port 不做追蹤(raw 表的 NOTRACK)、設定用量警示。 - 數值參考:預設上限依伺服器記憶體約為 6 萬~26 萬筆。溢位時 kernel log 會出現「nf_conntrack: table full, dropping packet」。 - 圖表上:碰到上限後持平(conntrack 項目數(nf_conntrack_count)) - 查看位置:把 sysctl 的 net.netfilter.nf_conntrack_count(目前項目數)與 nf_conntrack_max 放在同一張圖表上,並在 dmesg 中找「nf_conntrack: table full, dropping packet」 - 符合的跡象:nf_conntrack_count 在 max 持平,從那個時間點起 kernel log 出現 table full - 不符合的跡象:項目數遠低於 max 就不是這個原因。AWS 執行個體本身的連線追蹤上限,要看「超過雲端 PPS 上限」中提到的 conntrack_allowance_exceeded - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Netfilter Conntrack Sysfs variables](https://docs.kernel.org/networking/nf_conntrack-sysctl.html) · Linux kernel · nf_conntrack_max 預設值等於雜湊 bucket 數(nf_conntrack_buckets),bucket 數由記憶體大小決定 - [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 · 預設大小在記憶體超過 1GB 時為 65,536,超過 4GB(64 位元)時為 262,144;表滿時會記錄「nf_conntrack: table full, dropping packet」並丟棄封包 - [iptables-extensions(8) — Linux manual page](https://man7.org/linux/man-pages/man8/iptables-extensions.8.html) · netfilter · 以 raw 表的 CT --notrack 排除連線追蹤 #### so-ports · 伺服器間連線的臨時 port 耗盡 · Ephemeral port exhaustion (TIME_WAIT) 遊戲伺服器頻繁地對 DB 或其他伺服器建立又關閉短連線時,已關閉的連線會占用 port 一段時間,導致無法開啟新連線。 - 為什麼 → 於是 → 畫面上:每個請求都開啟再關閉一條新連線 → 先關閉的一方會以 TIME_WAIT 狀態占用 port 約 60 秒(Linux),可用的 port 因此耗盡 → 內部請求失敗,導致存檔失敗、功能異常 - 症狀:吃指令/回檔, 連不上/無限讀取/因素:遺失 - 誰會遇到:整個伺服器, 只有特定功能/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:重複使用連線(連線池),不要每個請求都重新開關連線。 - 基礎設施團隊要做的事:擴大 port 範圍(ip_local_port_range)、評估對外連線重複使用 TIME_WAIT(Linux 的 tcp_tw_reuse)、監控 TIME_WAIT 數量。 - 數值參考:Linux 的預設 port 範圍(32768~60999)約有 2 萬 8 千個。對同一個目的位址每秒建立超過 470 次新連線就會耗盡。Windows 的預設 port 約 1 萬 6 千個(49152~65535),TIME_WAIT 也更長,所以更快耗盡。 - 圖表上:碰到上限後持平(TIME_WAIT socket 數、內部連線失敗次數) - 查看位置:用 ss -tan state time-wait 依目的位址計算 TIME_WAIT socket 數,並在遊戲伺服器 log 中找 connect 失敗(EADDRNOTAVAIL) - 符合的跡象:連往同一目的地(DB 等)的 TIME_WAIT 在臨時 port 範圍(預設約 2 萬 8 千個)附近持平,connect 以 EADDRNOTAVAIL 失敗 - 不符合的跡象:TIME_WAIT 不多,卻只有連往外部的連線失敗時是「雲端 NAT 閘道的連線與 port 上限」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:Linux 的 TIME_WAIT 60 秒是寫死在 kernel 裡的值。調低名稱相似的 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):約 60 秒的 TIME_WAIT 是 kernel 常數 - [TCP/IP port exhaustion troubleshooting](https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/tcp-ip-port-exhaustion-troubleshooting) · Microsoft · Windows 動態 port 預設為 49152~65535,已關閉的連線預設會以 TIME_WAIT 占用 port 4 分鐘 - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · 以狀態篩選 state time-wait 只列出 TIME_WAIT socket - [connect(2) — Linux manual page](https://man7.org/linux/man-pages/man2/connect.2.html) · Linux man-pages · EADDRNOTAVAIL:臨時 port 範圍內的 port 全都在使用中,無法開啟連線 ### L8 Socket 與協定(14 個原因) #### sk-hol · TCP HOL 阻塞 · Head-of-line blocking TCP 為了維持順序,在重新收到遺失的那一個封包之前,不會把之後到達的封包交給遊戲。 - 為什麼 → 於是 → 畫面上:一個封包遺失 → 後面的封包都已抵達,卻只能在接收緩衝區等待 → 停住之後一口氣全部放行,出現快轉 - 症狀:定格, 快轉/因素:遺失, 停滯 - 誰會遇到:只有我/何時:偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:即時位置改用 UDP,只有真正必要的資料才用可靠傳輸,並拆成多條串流。用戶端:網路處理改成與伺服器相同的方式(UDP、分開通道)。 - 數值參考:遺失一個封包至少會停住一個往返時間再多一點,連重傳的封包也遺失時會停住數百 ms~數秒。 - 圖表上:中斷後一次湧入(每條連線的接收量、重傳次數) - 查看位置:用伺服器端的封包擷取(tcpdump、Wireshark)看該玩家連線上的重傳封包及其前後的空白;整個伺服器則看 nstat -az 的 TcpRetransSegs 增加量 - 符合的跡象:停住的區間從某一個封包的重傳開始,重傳封包一抵達,積壓的資料就一口氣處理完(接收量先是 0,接著暴增) - 不符合的跡象:用 UDP 通訊的遊戲不適用。沒有重傳卻停住時,要查伺服器 tick(「超出 tick 預算」) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · TCP 是可靠且保證順序的位元組串流服務 - [RFC 5681: TCP Congestion Control](https://www.rfc-editor.org/rfc/rfc5681) · IETF · 以 3 個重複 ACK 偵測到遺失時進行快速重傳,否則等待重傳計時器 - [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 顯示的計數器名稱:Tcp 群組的 RetransSegs(重傳的區段數) #### sk-rto · TCP RTO 與指數退避 · RTO and exponential backoff 每次重傳又失敗,等待時間就加倍,於是線路短暫中斷會變成長時間停住。 - 為什麼 → 於是 → 畫面上:線路短暫中斷,重傳也接連失敗 → 到下一次嘗試的等待時間以 0.3 → 0.6 → 1.2 → 2.4 秒逐次加倍(以 ping 100ms 計) → 線路只斷了 1 秒,遊戲卻停住 2 秒以上。斷得更久最後就會斷線 - 症狀:定格, 斷線/因素:遺失, 停滯 - 誰會遇到:只有我/何時:偶爾隨機發生, 移動中/切換地圖時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:回應心跳封包,一段時間沒收到就主動清理連線(用 TCP_USER_TIMEOUT 縮短放棄連線前的等待時間),用 session token 接續連線,採用可靠 UDP。用戶端:以短間隔送出心跳封包,沒有回應時不要等 TCP 重傳,儘快重新連線。 - 數值參考:Linux 的 RTO(重傳等待時間)最小為「ping + 200ms」,建立連線時從 1 秒開始。預設設定(tcp_retries2=15)下,即使重傳一直失敗,也要約 15 分鐘後才會放棄連線。 - 圖表上:中斷後一次湧入(每條連線的 RTO 與 backoff、RTO 到期次數) - 查看位置:用 ss -ti 看停住的連線的 rto(重傳等待 ms)與 backoff(連續到期次數);整個伺服器則看 nstat -az 的 TcpExtTCPTimeouts(重傳計時器到期次數)增加量 - 符合的跡象:停住的連線 backoff 為 1 以上,rto 已變大到以秒計,那個時間點 TCPTimeouts 增加 - 不符合的跡象:重傳以快速重傳完成、沒有 RTO 到期時,停住的時間很短。這時是「TCP HOL 阻塞」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [RFC 6298: Computing TCP's Retransmission Timer](https://www.rfc-editor.org/rfc/rfc6298) · IETF · 初始 RTO 1 秒,每次計時器到期就把 RTO 加倍(指數退避) - [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 · Linux 的 RTO = 平滑 RTT + RTT 變動值,而變動值的下限為 tcp_rto_min(200ms),所以 RTO 至少是 RTT+200ms - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_rto_min_us 預設 200ms;連線請求的初始 RTO 為 1 秒;tcp_retries2=15 時至少 924.6 秒(約 15 分鐘)才放棄 - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · -i 的 rto(重傳計時器,ms)與 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 顯示的計數器名稱:TcpExt 群組的 TCPTimeouts - [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 演算法 + 延遲 ACK · Nagle + delayed ACK (TCP_NODELAY off) 把小封包集中起來送的 Nagle 演算法,與延後送出 ACK 的延遲 ACK 互相牽制,每次把訊息分段寫入就會延遲 40~200ms。 - 為什麼 → 於是 → 畫面上:沒有開啟 TCP_NODELAY 就分段寫入小訊息 → 傳送端在等 ACK,接收端卻延後送出 ACK → 線路 ping 很低,每個動作卻都固定慢半拍,出現輸入延遲 - 症狀:輸入延遲/因素:延遲 - 誰會遇到:整個伺服器, 只有我/何時:一直都有, 做特定動作時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:開啟 TCP_NODELAY;把一個 tick 內的訊息集中起來一次寫入;關閉接收端延遲 ACK 的方法(Linux 的 TCP_QUICKACK 只維持一小段時間,Windows 要逐台電腦修改登錄檔)遊戲無法確實掌控,不要依賴。用戶端:開啟 TCP_NODELAY,把一個畫格內的訊息集中起來一次寫入。 - 數值參考:Linux 的延遲 ACK 通常是 40ms(視情況最多 200ms)。Windows 舊版是 200ms,現在的版本是 40ms(Windows Server 2019 預設範本為 40ms)。延遲 ACK 由接收端 OS 決定,所以伺服器在 Nagle 開啟的狀態下分段送出訊息時,依接收端電腦不同,每次可能延遲 40~200ms。 - 圖表上:一開始就一直偏高(動作回應時間(遊戲內 RTT)) - 查看位置:在伺服器端的封包擷取(tcpdump、Wireshark)中看請求與回應之間的間隔,並確認伺服器與用戶端程式碼是否開啟 TCP_NODELAY - 符合的跡象:線路 ping 很低,小封包之間卻反覆出現 40ms(Windows 舊版為 200ms)左右的空白,且空白在對方的 ACK 到達後隨即結束。開啟 TCP_NODELAY 後消失 - 不符合的跡象:回應間隔與線路 ping 相近就不是這個原因。遊戲伺服器產生回應太慢時,問題在伺服器處理(「訊息佇列積壓」) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · 有尚未收到 ACK 的資料時,Nagle 會把小資料先累積起來;必須能逐條連線關閉;延遲 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 · Linux 延遲 ACK 最小為 TCP_DELACK_MIN(HZ/25 = 40ms),最大為 TCP_DELACK_MAX(HZ/5 = 200ms) - [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 · Windows 預設的延遲 ACK 逾時改為 40ms(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 40ms、MaxSynRetransmissions 2、InitialRto 3000ms - [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 · 舊版 Windows TCP 收到資料時會啟動 200ms 的延遲 ACK 計時器,而 Nagle 預設開啟,小封包因此要等 ACK;以 TCP_NODELAY 解決 #### sk-block-send · 慢速用戶端造成的阻塞式傳送 · Blocking send on a full socket 線路很慢的某位玩家傳送緩衝區已滿時,若用阻塞方式(緩衝區有空間之前呼叫不會返回的傳送)送出,伺服器執行緒就會一直等那一個人。 - 為什麼 → 於是 → 畫面上:慢速用戶端的傳送緩衝區已滿 → 由於是阻塞式傳送,伺服器執行緒會等到緩衝區有空間為止 → 該執行緒負責的所有人都出現定格、慢動作 - 症狀:定格, 慢動作/因素:停滯 - 誰會遇到:特定地點/頻道, 整個伺服器/何時:偶爾隨機發生, 人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:改用非阻塞式傳送、為每個用戶端設定傳送佇列上限、丟棄過時的狀態更新。 - 圖表上:偶爾隨機飆高(伺服器 tick 時間、每條連線的 Send-Q) - 查看位置:用 ss -tn 找出 Send-Q(尚未收到 ACK 或尚未送出的位元組)已塞滿傳送緩衝區的連線,並在 tick 飆高的當下看遊戲伺服器的 thread dump(堆疊)中是否有停在 send 呼叫的執行緒 - 符合的跡象:有 Send-Q 塞滿的慢速連線時,負責該連線的執行緒停在 send,且只有同一執行緒負責的玩家一起停住 - 不符合的跡象:停住的執行緒在 send 以外的地方(鎖、DB 呼叫)等待時是「鎖競爭」、「遊戲執行緒上的同步呼叫」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [send(2) — Linux manual page](https://man7.org/linux/man-pages/man2/send.2.html) · Linux man-pages · 傳送緩衝區沒有空間時 send() 會阻塞,非阻塞模式下則立刻以 EAGAIN 返回 - [send function (winsock2.h)](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send) · Microsoft · Winsock 在緩衝區空間不足時,除非是非阻塞模式,send 也會阻塞 - [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 · ss 的 Recv-Q、Send-Q 值:listen socket 是等待 accept 的連線數與 backlog 上限;已連線的 socket 是應用程式尚未讀取的位元組數與尚未收到 ACK 的已送出位元組數 #### sk-slow-client · 慢速用戶端(slow consumer)的處理策略 · Slow-consumer policy 對於待傳送資料不斷累積的用戶端,伺服器會丟棄過時的狀態更新,或直接切斷連線。 - 為什麼 → 於是 → 畫面上:用戶端的線路跟不上伺服器送出的資料量 → 伺服器丟棄過時的狀態更新,或在超過上限時切斷連線 → 只有那個人出現瞬移或斷線 - 症狀:瞬移, 斷線/因素:遺失 - 誰會遇到:只有我/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:減少傳送量(依距離調整更新頻率)、降低品質但持續傳送、減少堆在 kernel 裡的資料量(Linux 的 TCP_NOTSENT_LOWAT)。 - 數值參考:傳送緩衝區為 256KB 時,在 30KB/s 的線路上會累積超過 8 秒的積壓資料。Linux 有時還會自動把這個緩衝區放大到數 MB。 - 圖表上:只有部分偏高(每條連線的 Send-Q、每個用戶端丟棄的狀態更新數) - 查看位置:看遊戲伺服器記錄的各用戶端傳送佇列長度、丟棄的狀態更新數與斷線原因,並在伺服器上用 ss -tni 一併確認該連線的 Send-Q 與 cwnd - 符合的跡象:只有瞬移或斷線的人的連線 Send-Q 一直是滿的,遊戲 log 中記錄了該玩家的狀態更新遭丟棄,或因超過傳送佇列上限而斷線 - 不符合的跡象:Send-Q 是空的卻仍瞬移時,問題不在伺服器傳送端。要查該玩家線路的封包遺失(「無線區段的封包遺失」)或畫面內插 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_wmem:自動調整的傳送緩衝區上限預設為 64KB~4MB(依記憶體而定);以 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 · ss 的 Recv-Q、Send-Q 值:listen socket 是等待 accept 的連線數與 backlog 上限;已連線的 socket 是應用程式尚未讀取的位元組數與尚未收到 ACK 的已送出位元組數 #### sk-keepalive · keepalive 預設值 2 小時 · TCP keepalive defaults 對方沒有送出關閉訊號就消失時,TCP 要過很久才會察覺。keepalive(確認閒置連線是否仍存活的 TCP 功能)預設是關閉的,即使開啟,也要閒置 2 小時才開始確認。 - 為什麼 → 於是 → 畫面上:用戶端因斷電或線路中斷,沒有送出關閉訊號就消失 → 伺服器認為連線仍存活(keepalive 預設 7,200 秒;若有正在傳送的資料,約 15 分鐘後才放棄重傳) → 留下幽靈角色,重新連線時出現「帳號已登入」錯誤 - 症狀:連不上/無限讀取, 看不見/幽靈物件/因素:遺失 - 誰會遇到:只有我/何時:閒置一段時間後, 剛登入/維護剛結束 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發), 基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:伺服器:回應心跳封包,一段時間沒收到就主動清理連線(調整 TCP_KEEPIDLE、TCP_USER_TIMEOUT);重新連線時用 session token 取代既有 session 並接續。用戶端:以數秒~數十秒的間隔(最短閒置逾時的一半以下)送出遊戲層級的心跳封包,斷線時自動重新連線。 - 基礎設施團隊要做的事:針對程式碼沒有自行設定的 socket,調低 kernel 預設值(tcp_keepalive_time 等)。只對開啟 SO_KEEPALIVE 的 socket 有效。 - 數值參考:Linux 的預設值是閒置 7,200 秒後開始確認,每隔 75 秒送出一次探測封包(probe),共 9 次,始終沒有回應就切斷連線。加起來約 2 小時 11 分鐘。Windows 預設也要閒置 2 小時才開始確認。 - 圖表上:只有部分偏高(每條連線距上次接收的經過時間) - 查看位置:用 ss -tnoi 看每條連線的 lastrcv(距上次接收的經過 ms)與 keepalive 計時器(timer:(keepalive,…)),並與遊戲伺服器「帳號已登入」的拒絕紀錄對照 - 符合的跡象:仍留有 lastrcv 達數分鐘~數小時的 ESTABLISHED 連線,且該帳號重新連線時以「帳號已登入」遭拒 - 不符合的跡象:沒有長時間沉寂的連線卻仍出現「帳號已登入」時,要查遊戲伺服器清理 session 的程式碼 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · 閒置 7,200 秒後每隔 75 秒探測 9 次(約再多 11 分鐘);只對開啟 SO_KEEPALIVE 的 socket 有效;TCP_KEEPIDLE、TCP_USER_TIMEOUT - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · keepalive 預設必須關閉,閒置間隔的預設值須在 2 小時以上 - [SO_KEEPALIVE socket option](https://learn.microsoft.com/en-us/windows/win32/winsock/so-keepalive) · Microsoft · Windows TCP keepalive 預設逾時 2 小時 - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · -i 的 lastrcv(距上次接收的經過 ms)、-o 的 timer:(keepalive,…) #### sk-fragment · UDP 封包的 IP 分段 · IP fragmentation of large UDP 超過 MTU(一次能傳送的最大大小)的 UDP 封包會在 IP 層被分段(fragmentation),只要遺失其中一個分段,整個封包就會被丟棄。 - 為什麼 → 於是 → 畫面上:人多之處的快照超過 1,500 位元組 → 拆成多個分段傳送,只要遺失一個就整個丟棄 → 封包越大,遺失率高出好幾倍。只在人多的地方出現瞬移 - 症狀:瞬移/因素:遺失 - 誰會遇到:特定地點/頻道, 特定地區/電信業者/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:自行把封包切成 1,200 位元組以下、只傳送變更的部分。 - 數值參考:在遺失率 2% 的線路上,拆成 4 個分段的封包約有 8% 會消失。也有防火牆或電信業者會直接丟棄分段的封包,這些玩家就完全收不到大封包。 - 圖表上:隨人數/負載上升(IP 分段數(IpFragCreates)、快照大小) - 查看位置:在伺服器上看 nstat -az 的 IpFragCreates(傳送時產生的分段數)增加量,接收端則看 IpReasmFails(重組失敗次數)。用遊戲伺服器 log 或封包擷取確認 UDP 封包大小的分布 - 符合的跡象:人潮聚集的地方 IpFragCreates 增加,出現超過 1,500 位元組的 UDP 封包,同時瞬移的回報也變多 - 不符合的跡象:IpFragCreates 沒有增加時,伺服器傳送端沒有發生分段 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [RFC 8085: UDP Usage Guidelines](https://www.rfc-editor.org/rfc/rfc8085) · IETF · 遺失一個分段就無法重組,整個封包都會遺失;UDP 應用程式應避免 IP 分段 - [RFC 8900: IP Fragmentation Considered Fragile](https://www.rfc-editor.org/rfc/rfc8900) · IETF · 防火牆與部分網路丟棄 IP 分段的案例 - [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 顯示的計數器名稱:Ip 群組的 FragCreates(產生的分段數)、ReasmFails(重組失敗次數) #### sk-reliable-udp · 可靠 UDP 的重傳設定 · Reliable-UDP tuning (KCP, ENet…) 在 UDP 上自行實作的重傳規則太保守時復原會變慢,太積極時反而讓線路更壅塞。 - 為什麼 → 於是 → 畫面上:重傳間隔、次數、視窗大小的設定與線路不相符 → 復原太慢,或重複傳送讓壅塞惡化 → 技能被吃、快轉、壅塞時 lag 更嚴重 - 症狀:吃指令/回檔, 快轉/因素:遺失, 延遲 - 誰會遇到:只有我/何時:偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:依實測的往返時間決定重傳時機,並依重要程度分開通道。用戶端:套用與伺服器相同的重傳與通道設定。 - 圖表上:偶爾隨機飆高(可靠 UDP 的重傳比例、遊戲內 RTT) - 查看位置:在伺服器與用戶端記錄所用函式庫的每條連線統計(重傳次數、估計往返時間、重傳等待時間),並與同一玩家實際的線路遺失率(用 mtr 測得的值)比較 - 符合的跡象:重傳比例比實際線路遺失率高出好幾倍時是設定太積極;重傳等待時間是實測往返時間的好幾倍時是設定太保守 - 不符合的跡象:重傳比例與線路遺失率相近、等待時間也符合往返時間時,就不是設定問題。要查線路遺失本身 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [RFC 8085: UDP Usage Guidelines](https://www.rfc-editor.org/rfc/rfc8085) · IETF · 重傳可能加劇壅塞,因此應納入壅塞控制;往返時間以多次量測的平均(EWMA)估計,初始值 1 秒;計時器到期時降低傳送速率 #### sk-slowstart · 閒置後的慢啟動(slow start) · Slow start after idle 連線閒置一段時間後,TCP 會再次縮小壅塞視窗(一次能送出的量),所以突然要送大量資料時,得分成好幾次送出。 - 為什麼 → 於是 → 畫面上:透過原本閒置的連線送出進入城鎮等大量資料 → 壅塞視窗已縮小,只能分成多個往返傳送 → 剛進入時,周圍的角色與 NPC 晚了好幾個往返才出現(伺服器越遠越明顯) - 症狀:輸入延遲, 看不見/幽靈物件/因素:延遲 - 誰會遇到:只有我/何時:移動中/切換地圖時, 閒置一段時間後 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:減少進入時的資料量(先送真正必要的部分)。 - 基礎設施團隊要做的事:關閉 tcp_slow_start_after_idle(Linux,整台伺服器的設定)。 - 數值參考:閒置超過 RTO 時壅塞視窗就開始縮小,閒置很久的話會降到約 14KB(10 個封包)。這時 100KB 無法一次送出,要分成 3 個往返傳送。 - 圖表上:只有部分偏高(剛進入時的傳送時間(RTT 較長的玩家)) - 查看位置:確認 sysctl net.ipv4.tcp_slow_start_after_idle 的值,並在玩家閒置後進入某個區域的瞬間,看該連線 ss -ti 中的 cwnd(壅塞視窗)是否變小 - 符合的跡象:設定為 1(預設),閒置後進入的瞬間 cwnd 降到 10 左右,傳送被拆成多個往返。RTT 越長的玩家,周圍物件出現得越晚,改成 0 後就不再發生 - 不符合的跡象:cwnd 維持在高檔仍然很晚出現時,要查伺服器端的進入處理(「進入密集區域時的生成暴增」) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_slow_start_after_idle 預設開啟,閒置達一個 RTO 就縮小壅塞視窗(RFC 2861 的做法) - [RFC 5681: TCP Congestion Control](https://www.rfc-editor.org/rfc/rfc5681) · IETF · 超過 RTO 沒有送出資料時,把壅塞視窗縮小到重新啟動視窗 min(IW, cwnd) 以下,並重新慢啟動 - [RFC 6928: Increasing TCP's Initial Window](https://www.rfc-editor.org/rfc/rfc6928) · IETF · 初始視窗 10 個區段,最多 14,600 位元組 - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · -i 的 cwnd(壅塞視窗)、ssthresh(慢啟動門檻) #### sk-congestion · 壅塞控制造成傳送量驟降 · Congestion control backoff TCP 把封包遺失視為壅塞的訊號,會把傳送速率降低 30~50%。對 Wi-Fi 造成的遺失也會做出同樣的反應。 - 為什麼 → 於是 → 畫面上:要送的資料量大時,Wi-Fi 或線路上發生少量封包遺失 → TCP 大幅降低傳送速率後慢慢回升(Linux、Windows 預設的 CUBIC 會降低 30%) → 人多的地方狀態更新積壓,出現快轉、輸入延遲 - 症狀:快轉, 輸入延遲/因素:延遲, 停滯 - 誰會遇到:只有我/何時:人潮湧入時 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:減少傳送量(只送 AOI 內、只送變更部分),分散傳送,避免一次全部送出。 - 基礎設施團隊要做的事:改用 BBR 等壅塞控制演算法(tcp_congestion_control)。 - 圖表上:緩慢爬升後驟降(每條連線的壅塞視窗(cwnd)與傳送速率) - 查看位置:對狀態更新積壓的玩家連線多次執行 ss -ti,看 cwnd、ssthresh 的變化與壅塞控制演算法名稱(cubic、bbr),並一併看 Send-Q 是否累積 - 符合的跡象:每次遺失後 cwnd 大幅縮小再慢慢回升,反覆發生;縮小期間 Send-Q 累積,與快轉、輸入延遲的回報時間重疊 - 不符合的跡象:cwnd 很充足仍然積壓時,要查接收端視窗(「Zero window(看起來像重傳的停頓)」)或伺服器傳送端 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [RFC 9438: CUBIC for Fast and Long-Distance Networks](https://www.rfc-editor.org/rfc/rfc9438) · IETF · 遺失時 CUBIC 把視窗乘以 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 · 以遺失為依據的壅塞控制,即使遺失與壅塞無關也會大幅降低傳送速率;BBR 依傳遞速率與 RTT 判斷 - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · 以 tcp_congestion_control 選擇新連線的壅塞控制演算法 - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · -i 的 cwnd、ssthresh 與壅塞控制演算法名稱 - [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 · ss 的 Recv-Q、Send-Q 值:listen socket 是等待 accept 的連線數與 backlog 上限;已連線的 socket 是應用程式尚未讀取的位元組數與尚未收到 ACK 的已送出位元組數 #### sk-linger · RST 強制關閉造成最後的資料遺失 · SO_LINGER, abrupt RST 伺服器倉促切斷連線時,最後送出的通知或存檔完成訊號會消失。 - 為什麼 → 於是 → 畫面上:伺服器以強制關閉(RST)結束連線。把 SO_LINGER 設為 0 秒,或沒讀完收到的資料就關閉時會發生 → 仍在傳送中的踢出原因與最後的資料被丟棄 → 沒來由地出現「因不明錯誤中斷連線」 - 症狀:斷線/因素:遺失 - 誰會遇到:只有我/何時:偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:送出原因後先只關閉傳送方向(shutdown),把收到的資料一直讀到對方關閉為止再關閉 socket;避免把 SO_LINGER 設為 0 秒。 - 圖表上:偶爾隨機飆高(以 RST 結束的連線數) - 查看位置:看 nstat -az 的 TcpExtTCPAbortOnData(還有資料要送就以 RST 關閉,SO_LINGER 0 秒)、TcpExtTCPAbortOnClose(還有未讀資料就關閉)增加量,並在斷線瞬間的伺服器端封包擷取中,看原本該送 FIN 的地方是否送出了 RST - 符合的跡象:在「因不明錯誤中斷連線」的回報時間,伺服器送出 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 只關閉傳送方向,收到對方的關閉通知後再關閉 socket 的順序 - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtTCPAbortOnData:因 SO_LINGER 0 秒等原因,在還有資料要送時以 RST 關閉;TcpExtTCPAbortOnClose:在還有未讀資料時關閉並送出 RST #### sk-blocking-io · 阻塞式 I/O 架構 · Blocking I/O model 執行緒在等待某個 socket 時無法做其他事的架構,玩家越多整體就越慢。 - 為什麼 → 於是 → 畫面上:每條連線各自等待讀寫的方式 → 一條連線的延遲擴散到同一執行緒的其他連線 → 同時上線人數越多,所有人都出現慢動作、輸入延遲 - 症狀:慢動作, 輸入延遲/因素:停滯 - 誰會遇到:整個伺服器/何時:人潮湧入時, 晚間尖峰時段 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:改用以 epoll、IOCP、io_uring 為基礎的非同步 I/O。 - 圖表上:隨人數/負載上升(回應時間、執行緒數) - 查看位置:用 pidstat -w -t 看遊戲伺服器的執行緒數與各執行緒的自願 context switch(cswch/s,為等待資源而停下的次數),並與隨同時上線人數變化的回應時間比較 - 符合的跡象:同時上線人數越多,回應時間上升得越陡;隨連線數增加的執行緒大多只有自願切換很多,幾乎不使用 CPU(在等 socket) - 不符合的跡象:執行緒沒有等待、持續使用 CPU 時,是運算過載(「超出 tick 預算」) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [epoll(7) — Linux manual page](https://man7.org/linux/man-pages/man7/epoll.7.html) · Linux man-pages · 可擴展到同時監看大量 fd 的 I/O 事件通知 - [I/O Completion Ports](https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports) · Microsoft · 以預先建立的執行緒池處理大量非同步 I/O 的 Windows 做法 - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -w 的 cswch/s:為等待資源而主動讓出的自願 context switch;加 -t 可看各執行緒 #### sk-reuseport · SO_REUSEPORT 分配不均 · SO_REUSEPORT imbalance, stuck worker 多個處理程序共用同一個 port 接收時,kernel 會依位址雜湊為每個連線指定負責的處理程序,之後不再更換。其中一個處理程序停住時,只有分配給它的人要等待。 - 為什麼 → 於是 → 畫面上:閘道或登入伺服器用 SO_REUSEPORT 啟動多個處理程序 → 即使某個處理程序因 GC 或過載停住,分配給它的新連線與 UDP 封包也不會轉給其他處理程序 → 只有部分人連不上或定格。在處理程序數量改變的重新啟動時,部分 UDP session 會中斷 - 症狀:連不上/無限讀取, 定格, 斷線/因素:停滯, 遺失 - 誰會遇到:只有我, 整個伺服器/何時:剛登入/維護剛結束, 偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:確保接收執行緒絕不停住;實作重新啟動時交接 session 的流程。 - 基礎設施團隊要做的事:監控各處理程序的連線等待佇列(ss 的 Recv-Q);部署時若要改變處理程序數量,必須遵循 session 交接流程。 - 圖表上:只有部分偏高(每個 listen socket 的連線等待佇列(Recv-Q)) - 查看位置:用 ss -ltnp 看同一 port 上每個 listen socket 的 Recv-Q(等待 accept 的連線數)與負責的處理程序,並比較各處理程序的處理量 - 符合的跡象:同一 port 的多個 socket 中,只有一個的 Recv-Q 持續累積,且該處理程序已停住或處理量接近 0 - 不符合的跡象:所有 socket 的 Recv-Q 都平均累積時,是整體過載(「連線等待佇列(backlog)溢位」) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [socket(7) — Linux manual page](https://man7.org/linux/man-pages/man7/socket.7.html) · Linux man-pages · 透過 SO_REUSEPORT,多個 socket 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 程式時,依群組內的 socket 數分配封包雜湊值來選出負責的 socket - [Why does one NGINX worker take all the load?](https://blog.cloudflare.com/the-sad-state-of-linux-socket-balancing/) · Cloudflare · SO_REUSEPORT 以簡單的雜湊為每個 worker 分配佇列,所以一個 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 · ss 的 Recv-Q、Send-Q 值:listen socket 是等待 accept 的連線數與 backlog 上限;已連線的 socket 是應用程式尚未讀取的位元組數與尚未收到 ACK 的已送出位元組數 #### sk-udp-connreset · Windows UDP socket 的 WSAECONNRESET 錯誤 · WSAECONNRESET on a Windows UDP socket Windows 伺服器向已經離開的用戶端送出 UDP 時,會收到「port 無法到達」(ICMP)的通知。這個通知會讓下一次接收呼叫以錯誤結束;如果伺服器程式碼把這個錯誤當成 socket 本身故障來處理,所有使用該 socket 的人都會受到影響。 - 為什麼 → 於是 → 畫面上:持續向剛離開的用戶端位址送出 UDP,收到「port 無法到達」(ICMP)通知 → Windows 讓下一次接收呼叫以 WSAECONNRESET(10054)錯誤結束,伺服器程式碼因此停止接收或關閉 socket → 使用該 socket 的所有人同時定格、斷線 - 症狀:斷線, 定格/因素:遺失, 停滯 - 誰會遇到:整個伺服器/何時:偶爾隨機發生, 剛登入/維護剛結束 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用 WSAIoctl 關閉 SIO_UDP_CONNRESET,不再接收這個通知;發生接收錯誤時只記 log,繼續接收。 - 圖表上:連線同時大量中斷(連線數、接收錯誤 log) - 查看位置:在遊戲伺服器 log 中找 UDP 接收錯誤碼(WSAECONNRESET,10054),以及接收迴圈停止或關閉 socket 的紀錄,並用伺服器端的封包擷取看之前是否剛收到 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 開啟或關閉 UDP「port 無法到達」(PORT_UNREACHABLE)通知的回報 - [recvfrom function (winsock.h)](https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom) · Microsoft · UDP socket 上的 WSAECONNRESET 表示先前的傳送收到了 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 預算 · Tick overrun 一個 tick 內要做的事超出預算時,伺服器的 tick 週期會拉長,整個區域的遊戲進行變慢,或出現卡頓。 - 為什麼 → 於是 → 畫面上:一個 tick(例如 50ms)要處理的事超出預算 → 每秒應計算 20 次的遊戲狀態只算了 8 次 → 整個區域出現慢動作(依伺服器設計也可能是卡頓),技能反應變慢 - 症狀:慢動作, 輸入延遲, 卡頓/因素:停滯 - 誰會遇到:特定地點/頻道, 整個伺服器/何時:人潮湧入時, 晚間尖峰時段 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:減少昂貴的運算、把 tick 拆給多個執行緒處理、分散人數(頻道)、把 tick 處理時間記錄為指標。 - 基礎設施團隊要做的事:把 tick 時間與各核心的 CPU 使用率加入監控與警示;評估單核心效能(時脈)較高的 CPU 或執行個體。 - 數值參考:20 tick 伺服器的預算是 50ms,30 tick 是 33ms,60 tick 是 16.7ms。為了因應人潮突然湧入,平時保留餘裕、只用到預算的一半左右比較安全。 - 圖表上:隨人數/負載上升(伺服器 tick 時間、各 zone/頻道的人數、遊戲執行緒 CPU) - 查看位置:把伺服器記錄的 tick 處理時間(p99)、tick 超出預算的次數與各 zone/頻道的人數放在同一張圖表上看。沒有 tick 指標時,用 pidstat -t 1 看單一遊戲執行緒的 CPU 使用率 - 符合的跡象:人潮湧入時 tick 時間超出預算(20 tick 時為 50ms),這段期間遊戲執行緒的 CPU 使用率一直貼近 100% - 不符合的跡象:tick 超出預算但遊戲執行緒 CPU 偏低時,是等待類的原因(GC 暫停、鎖、同步呼叫)。bcc runqlat 的 run queue 延遲很長時,表示執行緒分配不到 CPU,是 CPU 不足或執行緒過多 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:伺服器 tick 延遲時呈現的樣子依伺服器設計而不同。每個 tick 讓遊戲狀態前進固定時間(例如 50ms)的伺服器,遊戲時間本身會變慢,形成慢動作。依實際經過的時間一次移動到位的伺服器能維持遊戲進行的速度,但封包變得稀疏、每次移動幅度很大,看起來就是卡頓或瞬移。無論哪一種,輸入反應都會變慢。一個遊戲執行緒負責整台伺服器時,整台伺服器都會變慢;每個區域各有執行緒時,只有那個區域變慢。也有像 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 tick 伺服器必須在 7.8125ms 內完成一個畫格;依子系統量測伺服器畫格時間,並分配預算管理 - [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 · 固定間隔的模擬落後時,會集中執行追趕的步驟,並捨棄超過上限的時間,因此遊戲時間比實際時間流逝得慢 - [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 延遲(工作等待分配到 CPU 的時間) #### sp-aoi · 視野(AOI)計算量暴增(N²) · Area-of-interest explosion 若要判斷誰能看到誰而讓所有人兩兩比較,人數變成 10 倍時,運算量會變成 100 倍。 - 為什麼 → 於是 → 畫面上:所有角色兩兩比較距離,或即使切成格狀(grid),一個格子附近仍擠了數百人 → 100 人約比較 1 萬次,1,000 人約 100 萬次 → 在世界王、攻城戰等人潮聚集的地方 tick 時間暴增,出現慢動作、卡頓 - 症狀:慢動作, 卡頓/因素:停滯 - 誰會遇到:特定地點/頻道, 整個伺服器/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:以格狀或區塊切分、只比較附近的對象;遠處的對象降低更新頻率;限制一個人能看到的人數上限。 - 數值參考:假設距離比較加上「看得見/看不見」清單的更新,每兩人一對要花 0.1µs(千萬分之一秒),1,000 人(約 100 萬對)時一個 tick 要花 100ms。這是 20 tick 預算(50ms)的 2 倍。 - 圖表上:隨人數/負載上升(伺服器 tick 時間、聚集在同一處的人數) - 查看位置:把各 zone/頻道的人數與 tick 時間放在同一張圖表上,並看 tick 中視野計算所花時間的單獨量測值。沒有單獨量測時,用 perf top -p 看遊戲處理程序各函式的 CPU 占比 - 符合的跡象:同一處的人數變成 2 倍時 tick 時間增加將近 4 倍,視野與距離計算函式占了大部分 CPU 時間 - 不符合的跡象:tick 時間與人數成正比增加,或傳送、序列化函式的占比很大時,是「廣播量暴增」或「序列化與壓縮的成本」 - 確認方式:需要遊戲伺服器/用戶端的 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 逐一檢查所有連線的預設方式,在人數與 actor 很多時會成為伺服器 CPU 瓶頸;MMORPG 等會把世界切成格狀,重複使用每個格子的清單 - [perf-top(1) — Linux manual page](https://man7.org/linux/man-pages/man1/perf-top.1.html) · perf · 依函式(symbol)即時顯示執行中處理程序(-p)或執行緒(-t)的 CPU 使用占比 #### sp-broadcast · 廣播量暴增 · Broadcast fan-out (N×N) 把一個人的移動送給所有看得到他的人時,要送出的狀態更新數量會是聚集人數的平方。 - 為什麼 → 於是 → 畫面上:把一個人的變化傳送給所有看得到的人 → 1,000 人互相看得到時,每個 tick 有 100 萬筆狀態更新 → 傳送佇列與頻寬飽和,造成延遲與遺失(輸入延遲、快轉、瞬移) - 症狀:輸入延遲, 瞬移, 快轉/因素:延遲, 遺失 - 誰會遇到:特定地點/頻道, 整個伺服器/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:依距離與重要程度降低更新頻率(近處的敵人每個 tick 更新,遠處的人每秒幾次);為送給每個人的資料量設上限,從重要的開始填入;把多筆狀態更新打包進一個封包;限制顯示人數上限。 - 基礎設施團隊要做的事:將各伺服器的傳送頻寬與每秒封包數與 NIC、執行個體的網路上限比較並設警示;大型活動前確認餘裕。 - 數值參考:1,000 人 × 1,000 人 × 20 tick = 每秒 2,000 萬筆。每筆 40 位元組時,整台伺服器約 6.4Gbps,每位接收者約 6.4Mbps。把可見人數限制在 150 人時,整體約 1Gbps,每人約 1Mbps。 - 圖表上:隨人數/負載上升(伺服器傳送的封包數與位元組數、聚集在同一處的人數) - 查看位置:把 sar -n DEV 1 的 txpck/s、txkB/s(伺服器 NIC 每秒送出的封包數、KB)與人數圖表一起看。雲端執行個體則看 ethtool -S 的超過上限計數器(AWS ENA 為 bw_out_allowance_exceeded、pps_allowance_exceeded) - 符合的跡象:聚集人數增加時,傳送的封包數與位元組數比人數增加得更陡(接近平方),從碰到上限的時間點起,超過上限計數器或傳送丟棄數增加 - 不符合的跡象:傳送量不變、只有 tick 時間增加時,問題在視野計算或遊戲邏輯 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 實際案例:eve-hedgp-2014 - 出處: - [HED-GP Technical Retrospective: What a HED-ache](https://www.eveonline.com/news/view/what-a-hed-ache) · CCP Games · n 個人的行動必須讓 n 個人看到的 O(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 的更新頻率,依優先順序傳送,連線飽和時其餘的延到下一個 tick - [sar(1) — Linux manual page](https://man7.org/linux/man-pages/man1/sar.1.html) · sysstat · sar -n DEV 的 rxpck/s、txpck/s(每秒接收、傳送的封包數),rxkB/s、txkB/s(每秒接收、傳送的 KB) - [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 · 單執行緒區域過載(熱點) · Single-threaded hot zone 在每個區域由一個執行緒負責的架構中,人潮擠到同一處時,只有那一個核心會到 100%。 - 為什麼 → 於是 → 畫面上:一個區域(頻道)由一個執行緒負責 → 人潮擠到同一處時只有那個核心飽和,其餘核心還有餘裕 → 只有那個區域 lag,其他區域正常 - 症狀:慢動作, 輸入延遲/因素:停滯 - 誰會遇到:特定地點/頻道/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:分散到多個頻道、區域內部平行化、設定人數上限。 - 基礎設施團隊要做的事:把各核心的 CPU 使用率加入監控與警示(單一核心的飽和會被整台伺服器的平均值掩蓋)。 - 數值參考:在 16 核心的伺服器上,即使一個核心達到 100%,整台伺服器的 CPU 使用率看起來也只有約 6%。要看各核心的使用率才找得到。 - 圖表上:隨人數/負載上升(各核心的 CPU 使用率、各執行緒的 CPU) - 查看位置:用 mpstat -P ALL 1 看各核心的使用率、用 pidstat -t 1 看遊戲處理程序各執行緒的 CPU,並與最忙的執行緒所負責 zone 的人數比較 - 符合的跡象:整台伺服器的 CPU 很低,只有一個執行緒(一個核心)貼近 100%,而當時該執行緒負責的 zone 擠滿了人 - 不符合的跡象:多個核心平均偏高時是整台伺服器過載。只有一個核心的 %soft(接收處理)偏高時,是「NIC 中斷集中在單一核心」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:其他區域正常,前提是每個區域各自執行 tick。如果各區域的執行緒每個 tick 都要互相等待、再一起進入下一個 tick,最忙的那個區域就會拖慢整台伺服器的 tick。 - 實際案例:eve-hedgp-2014 - 出處: - [Time Dilation – How’s That Going?](https://www.eveonline.com/news/view/time-dilation-hows-that-going) · CCP Games · EVE Online 的 Time Dilation 以節點為單位,所以同一節點上的遙遠星系也會一起變慢;大型戰鬥改在只放 4 個星系的強化節點上處理 - [mpstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/mpstat.1.html) · sysstat · 分別顯示各處理器的使用率與整體平均(-P ALL);%soft 是處理軟體中斷所花的時間比例 - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · 加 -t 可一併顯示處理程序所屬各執行緒的統計(CPU 使用率等) #### sp-lock · 鎖競爭 · Lock contention 多個執行緒為了寫入同一份資料而等待同一個鎖時,執行緒再多也只能一次執行一個。 - 為什麼 → 於是 → 畫面上:拍賣場、公會倉庫這類共用資料由多個執行緒同時使用 → 拿到鎖的執行緒完成之前,其餘執行緒都在等待 → 只有特定功能變慢,嚴重時整個 tick 延遲 - 症狀:輸入延遲, 定格/因素:停滯 - 誰會遇到:只有特定功能, 整個伺服器/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:把鎖切得更細、減少在鎖內做的事、改用訊息導向的架構(每份資料指定負責的執行緒,其他執行緒只透過訊息送出請求)。 - 數值參考:鎖內的工作占整體的 20% 時,執行緒再怎麼增加,處理量最多也只有單一執行緒的 5 倍;占 40% 時停在 2.5 倍。 - 圖表上:隨人數/負載上升(請求處理時間、各執行緒的 CPU 與 context switch) - 查看位置:用 pidstat -w -t 1 看各執行緒的自願 context switch(cswch/s,為等待資源而停下的次數),用 bcc offcputime -p 看執行緒離開 CPU 後在哪裡等待(各呼叫堆疊的等待時間)。.NET 則看 dotnet-counters 的鎖競爭次數(.NET 9 以後為 dotnet.monitor.lock_contentions,8 以前為 Monitor Lock Contention Count) - 符合的跡象:負載增加時 CPU 使用率仍維持在低檔,處理時間卻變長;大部分等待時間集中在嘗試取得鎖的呼叫堆疊,鎖競爭次數也一起上升 - 不符合的跡象:CPU 滿載時是運算量問題(超出 tick 預算、單執行緒區域過載)。等待的地方是 DB 或檔案呼叫時,是遊戲執行緒上的同步呼叫 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:這發生在多個執行緒共同修改遊戲資料的架構中。每個區域或功能由一個執行緒負責、只透過訊息溝通的架構幾乎沒有鎖,但要留意工作集中在單一執行緒的問題(單執行緒區域過載)。遊戲執行緒若在等待緩慢的存檔作業所持有的鎖,那整個 tick 都會停住。 - 實際案例: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 時,CPU 核心再怎麼增加,加速倍數也不會超過 1/(1−f)(阿姆達爾定律) - [Request scheduling](https://learn.microsoft.com/en-us/dotnet/orleans/grains/request-scheduling) · Microsoft · Orleans 的 grain(actor)採用一次把一個請求處理完的單執行緒執行模型,因此不會同時修改狀態;grain 互相等待對方回應時可能發生死結 - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -w 的 cswch/s 是為了等待資源而停下的自願 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)依呼叫堆疊加總,-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 鎖時發生競爭的次數 - [.NET runtime metrics](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/built-in-metrics-runtime) · .NET · .NET 9 起的 dotnet.monitor.lock_contentions:處理程序啟動後,嘗試取得 monitor 鎖時發生競爭的次數 #### sp-deadlock · 死結 · Deadlock 兩個執行緒各自等待對方持有的鎖時,就會永遠停住。 - 為什麼 → 於是 → 畫面上:執行緒 A 持有鎖 1 並等待鎖 2,B 持有鎖 2 並等待鎖 1 → 兩者都永遠停住,相關的執行緒也接連停住 → 整台伺服器停止運作,看門狗(watchdog)重新啟動時所有人斷線 - 症狀:定格, 斷線/因素:停滯 - 誰會遇到:整個伺服器, 只有特定功能/何時:偶爾隨機發生, 人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:制定取鎖順序規則、使用有逾時的鎖、加入看門狗並在停住的瞬間留下 thread dump。 - 圖表上:連線同時大量中斷(連線數、伺服器傳送量) - 查看位置:停住期間擷取所有執行緒的呼叫堆疊。JVM 用 jstack(會自動找出並標示死結),.NET 用 dotnet-stack,原生伺服器用 gdb 的 thread apply all bt,或先用 gcore 擷取 core 檔案,重新啟動後再分析 - 符合的跡象:兩個以上的執行緒停在等待對方所持有鎖的堆疊上,這段期間處理程序的 CPU 使用率接近 0 - 不符合的跡象:停住期間有一個執行緒以 CPU 100% 在跑時是無窮迴圈。執行緒都在等 DB 或外部回應時,是同步呼叫或執行緒池耗盡 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Runtime locking correctness validator](https://docs.kernel.org/locking/lockdep-design.html) · Linux kernel · 以相反順序取得兩個鎖時,會因循環等待而死結(lock inversion deadlock);Linux kernel 會檢查取鎖順序並事先警告 - [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 會輸出執行中 JVM 所有執行緒的堆疊,也會找出並標示死結(Found one Java-level deadlock) - [dotnet-stack diagnostic tool - .NET CLI](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-stack) · Microsoft · 擷取並輸出 .NET 處理程序所有執行緒的 managed 堆疊 - [Threads (Debugging with GDB)](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Threads.html) · GNU Project · 以 thread apply all 對所有執行緒執行同一個指令(bt:輸出呼叫堆疊) - [gcore(1) — Linux manual page](https://man7.org/linux/man-pages/man1/gcore.1.html) · gdb · 為執行中的程式產生 core 檔案,產生後程式仍繼續執行 #### sp-sync-call · 遊戲執行緒上的同步呼叫 · Synchronous DB / file I/O on the game loop 在 tick 進行中等待 DB 回應或檔案寫入時,伺服器上整個遊戲進行就會停住同樣長的時間。 - 為什麼 → 於是 → 畫面上:在 tick 內等待 DB 查詢與儲存、log 寫入、外部 API 呼叫 → DB 花 100ms,tick 也停住 100ms → 每當 DB 或磁碟變慢,整個野外就頓一下 - 症狀:定格, 卡頓/因素:停滯 - 誰會遇到:特定地點/頻道, 整個伺服器/何時:做特定動作時, 偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:把慢的工作(DB 查詢與儲存、log 寫入、外部 API 呼叫)全部改成非同步,結果在下一個 tick 反映(只設逾時的話,等待期間仍會停住)。 - 數值參考:即使同一資料中心內的 DB 往返只要 0.5ms,一個 tick 呼叫 100 次就是 50ms,單靠這一項就用光 20 tick 的預算。 - 圖表上:偶爾隨機飆高(伺服器 tick 時間、DB 查詢延遲) - 查看位置:把 tick 時間圖表與 DB 查詢延遲(slow query log 等)、磁碟延遲放在同一條時間軸上看。沒有 tick 指標時,用 bcc offcputime -p 看遊戲執行緒在哪裡等待 - 符合的跡象:tick 飆高的時間點與 DB、檔案延遲飆高的時間點重疊,遊戲執行緒的等待時間集中在接收 DB 回應、寫入檔案的呼叫堆疊 - 不符合的跡象:DB 與磁碟延遲平穩、tick 卻飆高時,是 GC 暫停或鎖競爭 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Designs, Lessons and Advice from Building Large Distributed Systems](https://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf) · Google · LADIS 2009 主題演講(Jeff Dean)。同一資料中心內的往返約 0.5ms(500,000ns) - [ASP.NET Core Best Practices](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/best-practices) · Microsoft · 資料存取、I/O 與耗時的工作應以非同步呼叫;同步阻塞呼叫會導致執行緒池耗盡與回應延遲 - [Demonstrations of offcputime, the Linux eBPF/bcc version](https://raw.githubusercontent.com/iovisor/bcc/master/tools/offcputime_example.txt) · IO Visor · 把執行緒停住、離開 CPU 的時間(off-CPU)依呼叫堆疊加總,-p 指定處理程序 #### sp-queue · 訊息佇列積壓 · Mailbox / job queue backlog 請求進來的速度比處理速度快而在佇列中累積時,排在後面的請求要幾秒後才會處理,或被丟棄。 - 為什麼 → 於是 → 畫面上:請求抵達的速度比處理速度快 → 佇列變長,超過上限就丟棄 → 技能、交易反應變慢或被吃掉 - 症狀:輸入延遲, 吃指令/回檔/因素:延遲, 遺失 - 誰會遇到:特定地點/頻道, 只有特定功能/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:監控佇列長度、採用優先丟棄舊請求的策略、平行化處理。 - 圖表上:碰到上限後持平(佇列長度與最舊訊息的停留時間、每秒處理數) - 查看位置:伺服器記錄的各佇列長度、最舊訊息的停留時間,以及每秒進入數、處理數、丟棄數。沒有程式碼指標時,用 ss(或 netstat)看遊戲 socket 的 Recv-Q(kernel 已收下但處理程序尚未讀取的量) - 符合的跡象:進入數超過處理數的期間,處理數卡在某個值上不去,佇列長度、停留時間與丟棄數持續增加 - 不符合的跡象:佇列很短、停留時間也很短,反應卻很慢時,是線路延遲或 tick 本身的延遲 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 實際案例:eve-hedgp-2014 - 出處: - [Avoiding insurmountable queue backlogs](https://d1.awsstatic.com/builderslibrary/pdfs/avoiding-insurmountable-queue-backlogs.pdf) · AWS · Amazon Builders' Library。以等待中訊息的停留時間監控積壓;即時系統先處理新資料(接近 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 · 顯示 socket 統計的工具(資訊與 netstat 類似),加 -p 顯示使用該 socket 的處理程序 - [netstat(8) — Linux manual page](https://man7.org/linux/man-pages/man8/netstat.8.html) · net-tools · Recv-Q:已連線的 socket 中,使用者程式尚未取走的位元組數 #### sp-timer-burst · 計時器集中同時觸發 · Synchronized timers 所有怪物重生、所有 buff 到期、整點獎勵都擠在同一個 tick 時,那一個 tick 的負擔會變成平常的數十倍。 - 為什麼 → 於是 → 畫面上:重生、到期、獎勵、自動存檔的計時器都設在同一時刻 → 那一個 tick 的工作量是平常的數十倍 → 每到固定時刻就頓一下 - 症狀:定格, 卡頓/因素:停滯 - 誰會遇到:特定地點/頻道, 整個伺服器/何時:固定週期 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:讓計時器時刻隨機錯開一些,並分散到多個 tick 處理。 - 圖表上:固定週期飆高(伺服器 tick 時間) - 查看位置:收集 tick 時間飆高的時間點並看其間隔(整點、每 5 分鐘等)。與同一時刻執行的重生、buff 到期、獎勵、自動存檔計時器清單對照 - 符合的跡象:tick 每次都在相同時刻或以相同間隔飆高,且那個時刻有同時觸發的遊戲計時器工作 - 不符合的跡象:有週期性,但與 GC log 的暫停時間點或伺服器 cron、備份的時間重疊時,是伺服器 GC 全面暫停或排程工作 - 確認方式:需要遊戲伺服器/用戶端的 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 storms 數百隻怪物同時追擊玩家並計算路徑時,會耗用大量 CPU。 - 為什麼 → 於是 → 畫面上:拉一大群怪或大量生成時,許多怪物同時追擊玩家 → 每隻怪物各自計算尋路 → 只有那個練功區出現慢動作 - 症狀:慢動作/因素:停滯 - 誰會遇到:特定地點/頻道/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:快取路徑、限制計算次數上限、分散到多個 tick 處理。 - 圖表上:隨人數/負載上升(伺服器 tick 時間、各 zone 的怪物數) - 查看位置:各 zone 正在追擊玩家的怪物數與 tick 時間。沒有另外計數時,用 perf top -p 看遊戲處理程序各函式的 CPU 占比 - 符合的跡象:拉一大群怪或大量生成時 tick 時間增加,尋路(路徑搜尋)函式占了很大比例的 CPU 時間 - 不符合的跡象:怪物少、只有玩家多時 tick 增加,是視野計算或廣播 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [AI.NavMesh.pathfindingIterationsPerFrame](https://docs.unity3d.com/ScriptReference/AI.NavMesh-pathfindingIterationsPerFrame.html) · Unity · 每個畫格只處理固定數量節點的尋路,分散到多個畫格;即使路徑很長或請求同時湧入,遊戲也能保持流暢 - [perf-top(1) — Linux manual page](https://man7.org/linux/man-pages/man1/perf-top.1.html) · perf · 依函式(symbol)即時顯示執行中處理程序(-p)的 CPU 使用占比 #### sp-serialize · 序列化與壓縮的成本 · Serialization / compression cost 把要送出的資料轉成位元組並壓縮也要耗用 CPU,人多時這項成本會暴增。 - 為什麼 → 於是 → 畫面上:每筆狀態更新都要把結構轉成位元組並壓縮 → 成本與人數的平方成正比增加 → 傳送變慢,出現輸入延遲 - 症狀:輸入延遲/因素:停滯, 延遲 - 誰會遇到:特定地點/頻道/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:把做好一次的封包重複用於多人、改用輕量格式。 - 圖表上:隨人數/負載上升(伺服器 CPU 使用率、產生封包的執行緒的 CPU) - 查看位置:用 perf top -p 看遊戲處理程序的 CPU 時間中,序列化、壓縮、加密函式(包含 zlib、LZ4、OpenSSL 等函式庫的函式)所占的比例,並比較人少與人多時的差異 - 符合的跡象:人越多,序列化、壓縮、加密函式的占比越大,產生封包的執行緒 CPU 最先飽和 - 不符合的跡象:這些函式的占比很小時,問題在視野計算或遊戲邏輯 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:如果連線會加密封包(TLS、DTLS 等),加密與解密也要耗用 CPU。加密是每條連線分別進行,所以即使把做好一次的封包重複用於多人,加密成本仍與接收人數成正比。AES-GCM 這類對稱加密快到一個核心每秒能處理數 GB,平時占比很小,但速度會隨一次加密的單位(record)大小而有很大差異,像遊戲這樣小封包很多時,每位元組的成本就會變高。每次連線進行一次的交握(handshake)中,伺服器要用憑證金鑰簽章並計算金鑰交換(ECDHE)。一個核心每秒的簽章次數約 1,100 次(RSA 2048)到 1 萬 8 千次(ECDSA P-256),金鑰交換約 9 千次,登入湧入時會形成負擔。 - 出處: - [Introduction to Iris in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/introduction-to-iris-in-unreal-engine) · Epic Games · 把要複製(replication)的狀態保存為一份量化後的副本,減少昂貴的運算,並讓多條連線共用這份成果 - [VALORANT's 128-Tick Servers](https://www.riotgames.com/en/news/valorants-128-tick-servers) · Riot Games · 每個畫格為每個用戶端比較複製變數、打包變更值的方式,需要四處讀取記憶體,速度慢,會耗用大量伺服器 CPU - [How "expensive" is crypto anyway?](https://blog.cloudflare.com/how-expensive-is-crypto-anyway/) · Cloudflare · BoringSSL 量測:AES-128-GCM 每秒約 3.7GB(依 record 大小差異很大);一個核心每秒可做 RSA 2048 簽章 1,120 次、ECDSA P-256 簽章 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 · 依函式(symbol)即時顯示執行中處理程序(-p)的 CPU 使用占比 #### sp-crash · 伺服器當機 · Server process crash 伺服器處理程序因未處理的錯誤而終止時,該伺服器上所有人會同時斷線。 - 為什麼 → 於是 → 畫面上:指向不存在對象的錯誤(null 參照)、錯誤的資料、記憶體不足等致命錯誤 → 伺服器(或 zone)處理程序結束 → 所有人同時斷線,上次存檔後的進度可能回檔 - 症狀:斷線, 吃指令/回檔/因素:停滯 - 誰會遇到:特定地點/頻道, 整個伺服器/何時:偶爾隨機發生, 做特定動作時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:分析 crash dump 並修正原因、提高存檔頻率。 - 基礎設施團隊要做的事:處理程序自動重新啟動、建置 crash dump 的收集與保存環境、伺服器停機時立即警示。 - 圖表上:連線同時大量中斷(連線數、處理程序重新啟動次數) - 查看位置:coredumpctl list 的 core dump 紀錄(時間、PID、終止訊號)與服務管理器(systemd)的異常結束、重新啟動紀錄。Windows 伺服器則看 WER 留下的 dump 檔案 - 符合的跡象:連線數瞬間掉到接近 0 的時間點,遊戲伺服器處理程序有異常結束與 core dump - 不符合的跡象:處理程序一直存活、連線卻中斷時,是網路設備或閒置逾時。若是長時間停住後由看門狗重新啟動的紀錄,是無窮迴圈或死結 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Collecting User-Mode Dumps](https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps) · Microsoft · 設定 Windows 錯誤報告(WER),在使用者模式程式當機時於本機收集完整或迷你 dump - [systemd.service(5) — Linux manual page](https://man7.org/linux/man-pages/man5/systemd.service.5.html) · systemd · Restart=on-failure 會在異常結束、因訊號終止(包含 core dump)、看門狗逾時時自動重新啟動服務,建議用於長時間執行的服務 - [coredumpctl(1) — Linux manual page](https://man7.org/linux/man-pages/man1/coredumpctl.1.html) · systemd · 以 list 查詢 systemd-coredump 儲存的 core dump,顯示當機時間、PID 與造成當機的訊號 #### sp-threadpool · 執行緒池耗盡 · Thread pool starvation 負責處理工作的工作執行緒全被慢的工作綁住時,新請求只能無止盡地等待。 - 為什麼 → 於是 → 畫面上:工作執行緒都被綁在等待外部 API 或 DB 回應上 → 沒有執行緒可以接新請求 → 登入、商城等特定功能出現無限讀取 - 症狀:連不上/無限讀取, 輸入延遲, 定格/因素:停滯 - 誰會遇到:只有特定功能, 整個伺服器/何時:人潮湧入時, 剛登入/維護剛結束 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:為慢的呼叫設逾時、依功能分開執行緒池、改為非同步。 - 圖表上:碰到上限後持平(執行緒池的執行緒數與佇列長度、請求處理時間) - 查看位置:.NET 看 dotnet-counters monitor 的執行緒池執行緒數與佇列長度(.NET 9 以後為 dotnet.thread_pool.thread.count、dotnet.thread_pool.queue.length,8 以前為 ThreadPool Thread Count、ThreadPool Queue Length),並用 dotnet-stack 確認工作執行緒在哪裡等待。JVM 與原生伺服器則用 thread dump 確認同樣的內容 - 符合的跡象:CPU 使用率遠低於 100%,執行緒數卻緩慢持續增加或卡在上限,佇列不斷累積,大部分 worker 都在等同一個外部呼叫(DB、HTTP)的回應 - 不符合的跡象:佇列是空的卻仍然很慢時,是被呼叫的對象本身慢,要查連鎖故障或依賴外部服務 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:若封包接收與遊戲邏輯共用同一個工作執行緒池,只要幾個慢的工作占滿所有 worker,整台伺服器的封包處理就會停住。 - 實際案例:riot-euw-2021 - 出處: - [Debug ThreadPool Starvation](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/debug-threadpool-starvation) · Microsoft · 池中沒有剩餘執行緒、新工作只能等待時,回應就會變慢,原因是占住執行緒的阻塞程式碼。在 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's law)。每秒 100 筆時,延遲從 100ms 增加到 10 秒,執行緒就從 10 個增加到 1,000 個,池因此耗盡 - [Bulkhead Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/bulkhead) · Microsoft Azure · 為每個被呼叫的對象分別建立連線池與執行緒池,一個對象故障時只會卡住它自己的池 - [.NET runtime metrics](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/built-in-metrics-runtime) · .NET · dotnet.thread_pool.thread.count(執行緒池執行緒數)、dotnet.thread_pool.queue.length(等待中的工作數)自 .NET 9 起提供 - [Well-known EventCounters in .NET](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/available-counters) · Microsoft · .NET 8 以前的 ThreadPool Thread Count(threadpool-thread-count)、ThreadPool Queue Length(threadpool-queue-length) #### sp-infinite-loop · 無窮迴圈、邏輯失控 · Infinite loop / runaway logic 因 bug 導致一個 tick 結束不了時,伺服器就會停住,看門狗會強制重新啟動。 - 為什麼 → 於是 → 畫面上:條件寫錯導致迴圈結束不了,或遞迴失控 → tick 結束不了,伺服器停止 → 定格後所有人斷線 - 症狀:定格, 斷線/因素:停滯 - 誰會遇到:特定地點/頻道, 整個伺服器/何時:做特定動作時, 偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:設定迴圈次數上限、看門狗、撰寫重現問題輸入的測試。 - 圖表上:連線同時大量中斷(連線數、各執行緒的 CPU) - 查看位置:停住期間用 pidstat -t 1 看各執行緒的 CPU,並用 perf top -t(執行緒 ID)或 gdb 確認以 100% 在跑的執行緒卡在哪個函式。已經重新啟動時,看看門狗逾時紀錄(systemd 的 WatchdogSec、Kubernetes liveness probe 失敗) - 符合的跡象:伺服器停住期間,一個遊戲執行緒 CPU 貼在 100%,堆疊一直在同一個函式或迴圈內打轉 - 不符合的跡象:停住期間 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 · 依函式(symbol)即時顯示執行中執行緒(-t)或處理程序(-p)的 CPU 使用占比 #### sp-hot-entity · 集中在單一目標的戰鬥(世界王) · Hot entity / combat event fan-out 數百人同時攻擊同一隻王時,這隻王的運算全集中在一處,打擊資訊也要傳送給所有看得到的人。 - 為什麼 → 於是 → 畫面上:數百人不停地對同一隻王施放技能、buff、debuff → 王的血量、仇恨列表、debuff 計算集中在一處,每次打擊都要把傷害數字與特效封包傳送給所有看得到的人 → 技能延遲命中,傷害數字一口氣跳出來,只有王的周圍出現慢動作 - 症狀:輸入延遲, 快轉, 慢動作/因素:停滯, 延遲 - 誰會遇到:特定地點/頻道/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:其他玩家的傷害數字與特效合併或省略、限制單一目標身上的 debuff 數量上限、把打擊處理分散到多個 tick。 - 數值參考:800 人每秒各打 2 下,就是每秒 1,600 次打擊。全部通知給看著的 800 人,就是每秒 128 萬則訊息。 - 圖表上:隨人數/負載上升(伺服器 tick 時間、傳送訊息數) - 查看位置:把打王時段的 tick 時間與傳送封包數,和王周圍的人數一起看;可以的話再看各目標每秒的事件數(打擊、buff、debuff) - 符合的跡象:王周圍的人數增加時,tick 時間與傳送量急遽上升,這隻王每秒的事件數是其他目標的數十倍 - 不符合的跡象:與王無關,只要人聚在一處就同樣變慢時,是視野計算或廣播量暴增 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [HED-GP Technical Retrospective: What a HED-ache](https://www.eveonline.com/news/view/what-a-hed-ache) · CCP Games · 即使只是一次攻擊,也必須通知所有看得到的用戶端,因此產生 n 人通知 n 人的 O(n²) 負擔;訊息量大的無人機攻擊會讓這個負擔增加得更快 #### sp-spawn-burst · 進入密集區域時的生成暴增 · Spawn burst when entering a crowd 用傳送移動到擠滿人的城鎮時,伺服器必須一次送出數百位新進入視野者的外觀、裝備與狀態。 - 為什麼 → 於是 → 畫面上:因傳送移動、登入或切換頻道,突然出現在人多的地方 → 伺服器一次產生並送出數百人份的完整資訊,自己的電腦也要一次載入 → 剛抵達時畫面短暫停住,角色一個一個慢慢出現,輸入也有延遲 - 症狀:定格, 輸入延遲, 快轉/因素:停滯, 延遲 - 誰會遇到:只有我, 特定地點/頻道/何時:移動中/切換地圖時, 剛登入/維護剛結束 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:依距離由近到遠分成多個 tick 送出、快取外觀資訊。用戶端:在讀取畫面期間預先接收,把收到的角色分散到多個畫格生成。 - 數值參考:一個人的外觀、裝備、buff 資訊若為 300 位元組,500 人就約 150KB。相當於平常一個 tick 傳送量(數 KB)的數十倍在一瞬間湧入。 - 圖表上:剛開服或維護結束後暴增(每條連線的傳送位元組數、用戶端畫格時間) - 查看位置:抵達人多的地方後幾秒內,透過該連線送出的位元組數與封包數(伺服器 log),以及用戶端畫格時間(net graph、用戶端 log) - 符合的跡象:剛抵達時該連線的傳送量衝到平常一個 tick 的數十倍後回落,同一時間用戶端畫格時間也飆高 - 不符合的跡象:移動到人少的地方也一樣停住時,是切換 zone(跨伺服器轉移)或用戶端載入 - 確認方式:需要遊戲伺服器/用戶端的 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 延到下一個 tick - [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 該消失的地上道具、召喚物、已結束的計時器沒有清理而不斷累積時,伺服器開得越久,每個 tick 要做的事就越多。 - 為什麼 → 於是 → 畫面上:地上道具、召喚物、已到期的計時器、空隊伍資訊沒有及時刪除 → 每個 tick 要走訪的清單一天比一天長 → 維護剛結束時正常,過了幾天後只有那台伺服器或那個區域越來越遲鈍 - 症狀:慢動作, 卡頓, 輸入延遲/因素:停滯 - 誰會遇到:整個伺服器, 特定地點/頻道/何時:開越久越嚴重 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:把各區域的物件數記錄為指標並觀察趨勢、為每種物件設定存活時間與數量上限、定期清理。 - 數值參考:每個 tick 都把所有物件走訪一遍的伺服器,物件數變成 2 倍時,那部分的 tick 時間也會變成 2 倍。 - 圖表上:緩慢爬升後驟降(各 zone 的物件數、伺服器 tick 時間) - 查看位置:以比維護週期更長的期間(幾週)看各 zone/伺服器的物件數(地上道具、召喚物、計時器)與 tick 時間 - 符合的跡象:物件數與 tick 時間在維護後從低點開始逐日上升,到維護或重新啟動時驟降,反覆發生,這段期間記憶體仍很充足 - 不符合的跡象:tick 不變、只有記憶體持續上升時,是記憶體洩漏 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:和記憶體洩漏一樣開越久越嚴重,差別在於記憶體還很充足,只有 tick 時間增加。物件數圖表隨每次維護週期呈鋸齒狀時,就是這個情況。 - 出處: - [Actor Ticking in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-ticking-in-unreal-engine) · Epic Games · actor 與 component 若沒有另外設定間隔,每個畫格都會 tick 一次,不需要時可以關閉 tick - [AActor::SetLifeSpan](https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Engine/AActor/SetLifeSpan) · Epic Games · 為 actor 設定存活時間(lifespan)後,到期時會自動銷毀 #### sp-patch-traffic · 更新改變了流量模式 · Patch changes traffic pattern 新內容、特效、同步項目讓封包變大、變頻繁時,原本運作正常的伺服器在更新後就會碰到 MTU、頻寬、封包數上限。 - 為什麼 → 於是 → 畫面上:更新增加了新的技能特效、同步項目、道具資訊,封包變大或變頻繁 → 大封包超過 MTU 而被分段,增加的流量碰到頻寬、雲端 PPS 上限與傳送緩衝區的限制 → 更新後人多的地方開始出現瞬移、技能被吃、輸入延遲。基礎設施沒有任何變更,封包遺失卻增加 - 症狀:瞬移, 吃指令/回檔, 輸入延遲/因素:遺失, 延遲 - 誰會遇到:整個伺服器, 特定地點/頻道, 特定地區/電信業者/何時:人潮湧入時, 晚間尖峰時段, 一直都有 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施), 基礎設施團隊(網路基礎設施) - 遊戲開發團隊要做的事:自行把封包切成 1,200 位元組以下;新的同步項目只送變更部分,並依距離與重要程度降低頻率;部署前在測試伺服器上將每位玩家每秒的封包數、位元組數與最大封包大小和前一個 build 比較;在流量指標中記錄 build 版本。 - 基礎設施團隊要做的事:伺服器設備/OS:在圖表上標示部署時間,比較部署前後每位玩家每秒的封包數、位元組數與平均封包大小;為執行個體的超過上限計數器設警示;必要時改用更大的執行個體。網路:檢查防火牆、負載平衡器、DDoS 防護設備的處理上限,以及是否會阻擋分段。 - 數值參考:UDP 封包在 1,200 位元組以下比較安全;網際網路的路徑 MTU 通常是 1,500 位元組,經過通道時更小(GRE 通道為 1,476 位元組)。超過路徑 MTU 的封包會被分段或丟棄,而分段的封包只要遺失一個分段就整個遺失。每位玩家每秒的封包數增加 20%,整台伺服器也會增加 20%,原本就接近上限的執行個體會立刻超出。 - 圖表上:從某個時間點起階梯式上升(每位玩家每秒的封包數與位元組數、平均封包大小) - 查看位置:把部署前後伺服器 NIC 每秒的封包數與位元組數(sar -n DEV 的 rxpck/s、txpck/s、rxkB/s、txkB/s,EC2 為 NetworkPacketsOut、NetworkOut)除以同時上線人數後比較。平均封包大小為位元組數 ÷ 封包數,大小分布則用 Wireshark 的 Packet Lengths 統計分析封包擷取 - 符合的跡象:部署後每位玩家的封包數、位元組數或平均封包大小階梯式上升並維持在高檔,同一時間起伺服器產生的分段數(sar -n IP 的 fragcrt/s)或執行個體超過上限計數器(AWS ENA 的 pps_allowance_exceeded、bw_out_allowance_exceeded)增加 - 不符合的跡象:部署前後流量模式相同,只有延遲與封包遺失增加時,要查同一時間的基礎設施變更(設定、路由、設備、OS/kernel 更新) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:收到「更新前都很正常」的回報時,這是要和基礎設施變更一起優先確認的遊戲端原因。即使更新說明裡沒有網路方面的變更,一個新特效或同步項目在人多的地方也會被乘上數百人份。增加的流量實際卡在哪裡,在「UDP 封包的 IP 分段」、「超過雲端 PPS 上限」、「NIC 頻寬飽和」、「Kernel socket 緩衝區不足」、「中間設備超過處理上限(防火牆、IPS、DDoS 防護)」等項目中說明。本項目處理的是讓流量碰到這些上限的起點為遊戲更新的情況,所以在提高上限之前,要先減少更新所增加的流量。如果同一時間也更新了 OS 或 kernel,就以每位玩家的流量是否改變,來和「OS、kernel、驅動程式、韌體更新後的效能變化」區分。 - 出處: - [RFC 8085: UDP Usage Guidelines](https://www.rfc-editor.org/rfc/rfc8085) · IETF · UDP 應用程式不應(SHOULD NOT)送出超過路徑 MTU 的 datagram;遺失一個分段就會失去整個分段的封包,部分 NAT 與防火牆會丟棄所有分段 - [RFC 8899: Packetization Layer Path MTU Discovery for Datagram Transports](https://www.rfc-editor.org/rfc/rfc8899) · IETF · UDP 等 datagram 傳輸的預設安全大小(BASE_PLPMTU)建議為 1,200 位元組 - [Maximum transmission unit and maximum segment size](https://developers.cloudflare.com/magic-transit/reference/mtu-mss/) · Cloudflare · 網際網路路徑 MTU 1,500,經過 GRE 通道時為 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 · sar -n DEV 的 rxpck/s、txpck/s(每秒封包數)與 rxkB/s、txkB/s(每秒 KB),sar -n IP 的 fragcrt/s(每秒產生的 IP 分段數,ipFragCreates) - [CloudWatch metrics that are available for your instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html) · AWS · NetworkPacketsOut(執行個體經由所有網路介面送出的封包數)、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# 伺服器為了回收垃圾而暫停所有執行緒(stop-the-world)的期間,整個伺服器都會停住。 - 為什麼 → 於是 → 畫面上:heap 滿了,開始 GC → 暫停所有遊戲執行緒進行回收(存活資料越多,暫停越久) → 伺服器上的所有人同時定格,接著出現快轉 - 症狀:定格, 快轉/因素:停滯 - 誰會遇到:整個伺服器/何時:固定週期, 開越久越嚴重 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:在啟動選項中明確指定暫停較短的 GC(ZGC、Shenandoah、調低暫停目標的 G1)、減少記憶體配置、調整 heap 大小。 - 基礎設施團隊要做的事:選用記憶體足以讓 heap 保有充裕空間的執行個體;容器至少配置 2 個 CPU 與約 1.8GB 記憶體(JDK 26 以下若低於此值,會預設選用 Serial GC);監控 GC 暫停時間。 - 數值參考:只回收新物件(Young 區)的 Minor GC 約需數~數十 ms。回收整個 heap(存活資料達數 GB)的 Full GC 有時會超過 1 秒。ZGC 的暫停幾乎與 heap 大小無關,不到 1ms;Shenandoah 的暫停也不會隨 heap 大小成比例增加,因此同樣很短。 - 圖表上:固定週期飆高(伺服器 tick 時間、GC 暫停時間) - 查看位置:開啟 GC log,把暫停的時間點與長度疊在伺服器 tick 時間圖表上。Java 看啟動選項 -Xlog:gc*(JDK 8 以下為 -XX:+PrintGCDetails)輸出的 Pause 行;.NET 看 dotnet-counters 的 GC 暫停指標(.NET 9 以後為 dotnet.gc.pause.time,8 以下為 % Time in GC since last GC);Go 看 GODEBUG=gctrace=1 每次 GC 輸出的一行 - 符合的跡象:tick 飆高的時間點與 GC 暫停重疊,暫停長度與飆高長度相近。伺服器所有 zone、頻道在同一瞬間飆高 - 不符合的跡象:GC log 沒有長暫停、tick 仍飆高時,是鎖、同步呼叫、磁碟寫入等其他原因。只有一個 zone 飆高時,是腳本引擎的 GC(mem-script-gc)或該 zone 的負載 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:Java 的 G1 預設單次暫停目標為 200ms,在 20 tick 伺服器上相當於 4 個 tick。容器若只分到不到 2 個 CPU 或不到約 1.8GB 記憶體,JDK 26 以下的 Java 會預設選用以單一執行緒回收的 Serial GC,暫停時間會長得多。C#(.NET)伺服器通常會開啟伺服器 GC 與背景 GC,但存放新物件的第 0、1 代(Gen0、1)回收,以及伴隨壓縮的 Full GC,仍會暫停所有執行緒。Go 的暫停通常不到 1ms,但配置量大時,要求記憶體的一方必須分擔 GC 工作,tick 因此變慢。不論哪種方式,只要配置速度快過回收速度,遊戲執行緒最後都會停住。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 暫停目標預設值 200ms(MaxGCPauseMillis);回收期間記憶體耗盡時,會轉為暫停並壓縮整個 heap 的 Full GC - [JEP 523: Make G1 the Default Garbage Collector in All Environments](https://openjdk.org/jeps/523) · OpenJDK · 過去在只有 1 個 CPU 或記憶體不到 1792MB 時會預設選用 Serial GC;從 JDK 27 起,所有環境都預設使用 G1 - [JEP 439: Generational ZGC](https://openjdk.org/jeps/439) · OpenJDK · ZGC 暫停在 1ms 以下且與 heap 大小無關,G1 暫停為數 ms~數秒。配置快過回收時,有發生配置暫停(allocation stall)的風險 - [JEP 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)](https://openjdk.org/jeps/189) · OpenJDK · 不論 heap 是 200MB 還是 200GB,Shenandoah 的暫停時間都差不多 - [Background garbage collection](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/background-gc) · .NET · 背景 GC 只適用於第 2 代回收;第 0、1 代回收(前景 GC)會暫停所有受控執行緒 - [A Guide to the Go Garbage Collector](https://go.dev/doc/gc-guide) · Go · Go GC 大多與程式同時執行,只有短暫的全面暫停;配置量大時,goroutine 須分擔 GC 工作(assist),因而產生延遲 - [JEP 271: Unified GC Logging](https://openjdk.org/jeps/271) · OpenJDK · 從 JDK 9 起以統一 logging(-Xlog)重新實作 GC log;-Xlog:gc 與舊的 -XX:+PrintGC 一樣,每次 GC 輸出一行 - [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 以後以 System.Runtime meter(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 時間,以及 GC 開始、結束時的 heap 大小與目標 heap #### mem-script-gc · 腳本引擎的 GC 暫停 · Scripting VM GC (Lua, etc.) 即使是 C++ 伺服器,只要任務、AI、技能是用 Lua 等腳本執行,腳本引擎進行 GC 的期間,該 zone 就會停住。 - 為什麼 → 於是 → 畫面上:每個 zone 的腳本引擎執行任務、AI、事件時,大量產生暫時物件 → 腳本引擎的 GC 一次回收大量物件時,該 zone 的 tick 會停住 → 只在特定 zone 或特定活動期間週期性地頓一下 - 症狀:卡頓, 定格/因素:停滯 - 誰會遇到:特定地點/頻道/何時:人潮湧入時, 固定週期 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:設定漸進式或分代 GC、每個 tick 只推進一小段 GC、減少腳本產生的暫時物件。 - 數值參考:腳本 heap 長到數百 MB 時,一次集中進行的回收(關閉漸進式回收,或分代模式下的完整回收)有時要花數十~數百 ms。 - 圖表上:固定週期飆高(各 zone 的 tick 時間、腳本引擎記憶體) - 查看位置:每個 tick 記錄各 zone 的 tick 時間與該 zone 腳本引擎的記憶體用量(Lua 用 collectgarbage("count")),疊在同一張圖表上看 - 符合的跡象:腳本記憶體驟降的瞬間(一次集中回收)與該 zone 的 tick 飆高重疊,其他 zone 正常 - 不符合的跡象:腳本記憶體沒有變化卻飆高時,是該 zone 的負載或鎖。伺服器所有 zone 同時飆高時,是伺服器 GC(mem-gc)或 swap(mem-swap) - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Lua 5.4 Reference Manual](https://www.lua.org/manual/5.4/manual.html) · Lua.org · 漸進式模式把回收拆成小步驟,穿插在程式執行之間(步驟設得太大就會全面暫停);分代模式的 major 回收會走訪所有物件,屬於全面暫停;collectgarbage("count") 回傳 Lua 使用的記憶體總量(KB) #### mem-alloc · 記憶體配置暴增 · Allocation storms 活動期間大量產生暫時物件時,GC 執行的頻率會比平常高出許多。 - 為什麼 → 於是 → 畫面上:道具掉落、戰鬥 log、活動獎勵讓暫時物件暴增 → GC 頻率增加數倍,還沒來得及變成垃圾的物件被移到 Old 區,Full GC 也跟著提早發生 → 只在活動期間週期性地頓一下 - 症狀:卡頓, 定格/因素:停滯 - 誰會遇到:整個伺服器, 特定地點/頻道/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:物件池、可重複使用的緩衝區、記憶體配置剖析(profiling)。 - 圖表上:隨人數/負載上升(GC 次數、配置速度) - 查看位置:用 GC log(Java -Xlog:gc、Go GODEBUG=gctrace=1)計算每分鐘 GC 次數;.NET 看 dotnet-counters 的配置量與 GC 次數(.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 區滿了就進行 minor GC,部分存活物件會移到 Old 區;Old 區滿了就回收整個 heap(比 minor GC 久得多);-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 暴增、swap 或處理程序被強制終止。 - 為什麼 → 於是 → 畫面上:已登出角色的資料、事件處理常式(event handler)沒有被釋放 → 可用記憶體在幾天內逐漸減少 → 維護剛結束時一切正常,之後一天比一天 lag,最後伺服器當機 - 症狀:慢動作, 定格, 斷線/因素:停滯 - 誰會遇到:整個伺服器/何時:開越久越嚴重, 晚間尖峰時段 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:分析 heap dump、進行長時間負載測試。 - 基礎設施團隊要做的事:監控各處理程序記憶體用量的趨勢並設定警示。 - 圖表上:緩慢爬升(處理程序記憶體(RSS)、GC 後的 heap) - 查看位置:以數天為單位觀察遊戲伺服器處理程序的記憶體(pidstat -r 的 RSS);有 GC 的伺服器則看 GC 剛結束時剩下的 heap。Java 看 -Xlog:gc 行中 GC 前後用量裡的 GC 後數值;.NET 看 dotnet-counters 的 GC 後 heap 大小(.NET 9 以後為 dotnet.gc.last_collection.heap.size,8 以下為 GC Heap Size) - 符合的跡象:GC 剛結束時剩下的 heap(基線)在重新啟動後每天上升,人少的凌晨也不會降下來 - 不符合的跡象:heap 基線持平、只有 RSS 上升時,是碎片化(mem-fragment)或原生記憶體(native memory)的問題。隨人數起伏時是正常用量 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:如果每週定期維護都會重新啟動,洩漏就會被掩蓋,很久都不會被發現。常見的情況是某次維護延後,或活動讓人數增加時,問題才突然浮現。 - 出處: - [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 中的記憶體)與分頁錯誤 #### mem-gc-thrash · GC thrashing(heap 可用空間不足) · GC thrashing (heap nearly full) 存活資料逼近 heap 上限時,每次 GC 幾乎回收不到東西,GC 就會不停反覆執行。 - 為什麼 → 於是 → 畫面上:活動湧入的人潮或記憶體洩漏,讓存活資料逼近 heap 上限 → GC 只能回收一點點,馬上又進行 Full GC,CPU 大部分都被 GC 占用 → 整個伺服器在幾分鐘內反覆出現慢動作與定格,最後因記憶體不足而終止 - 症狀:慢動作, 定格, 斷線/因素:停滯 - 誰會遇到:整個伺服器/何時:晚間尖峰時段, 人潮湧入時, 開越久越嚴重 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:把 heap 設得比尖峰時的存活資料寬裕(通常 2 倍以上)、減少長期被參考的資料與洩漏。 - 基礎設施團隊要做的事:設定 GC 時間比例警示;發生時不要硬撐,儘快重新啟動;選用記憶體充裕、可以加大 heap 的執行個體。 - 數值參考:GC 占用超過 10% 的執行時間,通常就視為危險訊號。Java 的部分 GC 若花了 98% 的時間做 GC 卻幾乎回收不到記憶體,就會丟出記憶體不足錯誤。 - 圖表上:碰到上限後持平(GC 後的 heap、GC 時間比例) - 查看位置:看 GC 剛結束時剩下的 heap 有多接近最大 heap,以及花在 GC 的時間比例。Java 看 -Xlog:gc 行的「GC 後(heap 大小)」與 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%)。這段期間整個伺服器的 tick 一起變慢 - 不符合的跡象:GC 後 heap 仍有餘裕、只有暫停很長時,是 GC 種類或設定的問題(mem-gc) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [The Parallel Collector](https://docs.oracle.com/en/java/javase/25/gctuning/parallel-collector1.html) · Oracle · Parallel GC 若花超過總時間 98% 做 GC,卻只回收不到 2% 的 heap,就會丟出 OutOfMemoryError - [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% 以下;因 heap 占用率過高而發生的 Full GC,可從 log 中的 Pause Full (G1 Compaction Pause) 找出 - [A Guide to the Go Garbage Collector](https://go.dev/doc/gc-guide) · Go · 預設 GOGC=100 時,目標 heap 約為存活 heap 的 2 倍;貼近記憶體上限時,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 · Swap · Swapping 記憶體不足、OS 把部分內容移到磁碟後,每次用到那塊記憶體,都得等待慢上 1,000 倍以上的磁碟。 - 為什麼 → 於是 → 畫面上:使用中的記憶體超過實體 RAM → OS 把一部分移到磁碟,需要時再讀回來 → tick 暴增到數百 ms,伺服器上所有玩家都遇到慢動作與定格 - 症狀:慢動作, 定格/因素:停滯 - 誰會遇到:整個伺服器/何時:開越久越嚴重, 晚間尖峰時段 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:為處理程序的記憶體用量設定上限(heap 大小等),並檢查洩漏。 - 基礎設施團隊要做的事:將遊戲伺服器設定為不使用 swap,並以記憶體警示因應;關閉 swap 後,記憶體一不足就會被強制終止(OOM),因此要準備比尖峰用量更充裕的 RAM。 - 數值參考:讀取 RAM 約 100ns,從 SSD 讀回約 100µs(1,000 倍),透過網路存取的雲端磁碟約 1ms(1 萬倍),HDD 則是 10ms(10 萬倍)。 - 圖表上:緩慢爬升(swap 用量、swap in/out) - 查看位置:把 vmstat 1 的 si、so 欄(每秒從 swap 讀回、移出到 swap 的量)、/proc/pressure/memory 的 some、full(因等待記憶體而停住的時間比例)、遊戲伺服器處理程序的 pidstat -r majflt/s(必須從磁碟讀入的分頁錯誤)與 tick 時間疊在一起看 - 符合的跡象:lag 發生時 si 大於 0,遊戲伺服器的 majflt/s 與 memory 的 full 值一起上升 - 不符合的跡象:si、so 為 0 且 memory 壓力(PSI)也接近 0 時,原因不在 swap。沒有 swap 但 majflt/s 與 PSI 上升時,代表記憶體耗盡、正在重新讀入程式碼分頁,要先確保有足夠的記憶體 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:使用 GC 的伺服器在回收時會讀遍整個 heap,因此只要 heap 有一部分被 swap 出去,單次 GC 就可能拉長到數秒~數十秒。關閉 swap 後,不會經過因 swap 而變慢的階段,會直接進入強制終止(OOM),所以必須先確保有足夠的可用記憶體。即使沒有 swap,記憶體快用完時,OS 也會把執行檔的程式碼分頁移出記憶體再重新讀入,因此在被強制終止前,整個伺服器可能會有一段時間嚴重變慢。 - 出處: - [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」:主記憶體存取 100ns、磁碟尋軌 10ms(以 2009 年為準) - [Documentation for /proc/sys/vm/](https://docs.kernel.org/admin-guide/sysctl/vm.html) · Linux kernel · swappiness:swap 與回收檔案分頁的相對成本;swap 屬於隨機 I/O,成本較高 - [Concepts overview](https://docs.kernel.org/admin-guide/mm/concepts.html) · Linux kernel · 回收在磁碟上有原始資料的頁面快取與可 swap 的分頁,仍不夠時由 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 · 伺服器用 NVMe SSD 的 99.99% 延遲(four-nines latency)為 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:每秒從 swap 讀回的記憶體;so:每秒移出到 swap 的記憶體 - [PSI - Pressure Stall Information](https://docs.kernel.org/accounting/psi.html) · Linux kernel · /proc/pressure/memory 的 some(部分工作停住的時間比例)與 full(所有工作同時停住的時間比例) - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -r:majflt/s(必須從磁碟讀入分頁的錯誤次數) #### mem-cache-miss · 快取未命中 · CPU cache misses 資料散落在記憶體各處時,CPU 每次都必須到較慢的 RAM 讀取並等待資料。 - 為什麼 → 於是 → 畫面上:物件透過指標散落各處,存取順序雜亂 → CPU 快取裡沒有,每次都要從 RAM 讀取(慢 100 倍左右) → 做同樣的事,tick 成本卻高出數倍,嚴重時出現慢動作 - 症狀:慢動作/因素:停滯 - 誰會遇到:整個伺服器/何時:一直都有, 人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:把經常一起使用的資料連續排列(資料導向設計)。 - 圖表上:一開始就一直偏高(tick 時間、CPU 使用率) - 查看位置:對遊戲伺服器處理程序執行 perf stat -d -p PID,量測每週期指令數(insn per cycle)與 L1、LLC 快取未命中,並與 tick 時間、CPU 使用率一起看 - 符合的跡象:CPU 一直很忙,但 insn per cycle 偏低、LLC 未命中很多。改變資料布局的 build 在相同人數下 tick 時間大幅縮短,即可確定 - 不符合的跡象:CPU 使用率低但 tick 很慢時,是鎖、I/O 等待這類在 CPU 之外等待的原因 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [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.5ns、L2 快取 7ns、主記憶體 100ns(以 2009 年為準):到 RAM 讀取比快取慢一到兩個數量級 - [perf-stat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/perf-stat.1.html) · perf · -p 會計算執行中處理程序的硬體事件並顯示 insn per cycle;-d 會加上 L1、LLC 資料快取事件 #### mem-fragment · 記憶體碎片化 · Heap fragmentation 反覆配置與釋放,讓閒置空間被切得零碎時,占用的記憶體會遠多於實際使用量。 - 為什麼 → 於是 → 畫面上:多個執行緒長時間配置、釋放大小不一的記憶體 → 閒置空間零散分布,無法歸還給 OS,用量像洩漏一樣持續增加 → 開越久越會因 swap 與記憶體不足而變慢,最後被強制終止 - 症狀:慢動作, 斷線/因素:停滯 - 誰會遇到:整個伺服器/何時:開越久越嚴重 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:依大小分類的記憶體池、不易碎片化的記憶體配置器(jemalloc、mimalloc 等)。 - 圖表上:緩慢爬升(處理程序記憶體(RSS)) - 查看位置:用同一個 build 啟動兩台伺服器,只在其中一台以環境變數 MALLOC_ARENA_MAX 減少 glibc arena 數量,或換成 jemalloc 等其他配置器,比較數天內 pidstat -r 的 RSS - 符合的跡象:人數與物件數相近,只有調整過的伺服器 RSS 停止增加或增幅大減 - 不符合的跡象:換了配置器仍同樣上升時,是沒有釋放的記憶體(mem-leak) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:表現和洩漏一模一樣,但分析 heap 找不到洩漏點。Linux 的預設配置器(glibc)在執行緒多的伺服器上特別嚴重,有時光是換掉配置器,用量就會大幅下降。 - 出處: - [mallopt(3) — Linux manual page](https://man7.org/linux/man-pages/man3/mallopt.3.html) · Linux man-pages · glibc malloc 為了減少執行緒競爭,會建立多達 CPU 數倍數的 arena,arena 越多記憶體用量越大(以 M_ARENA_MAX 限制,也可用環境變數 MALLOC_ARENA_MAX 設定) - [jemalloc memory allocator](https://jemalloc.net/) · jemalloc · 重視避免碎片化與並行擴展性的通用 malloc 實作 - [pidstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/pidstat.1.html) · sysstat · -r:各處理程序的 RSS(實際位於 RAM 中的記憶體) #### mem-numa · NUMA 遠端記憶體 · Remote NUMA access 在有兩顆 CPU 的伺服器上,使用接在另一顆 CPU 上的記憶體時,存取會變慢。 - 為什麼 → 於是 → 畫面上:執行緒與記憶體位於不同的 CPU 插槽(socket) → 記憶體存取變慢(依設備不同,約慢 1.5~2 倍) → 規格相同,各處理程序的效能卻有落差 - 症狀:慢動作/因素:停滯 - 誰會遇到:整個伺服器/何時:一直都有 - 主要負責:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:把處理程序與記憶體綁定在同一個插槽(numactl);有兩個插槽時,每個插槽各自執行遊戲伺服器處理程序。 - 圖表上:只有部分偏高(各處理程序的 tick 時間、各節點的記憶體) - 查看位置:用 numastat -p PID 查看遊戲伺服器處理程序的記憶體位於哪個 NUMA 節點、numastat 的 numa_miss、other_node 是否增加,並與該處理程序執行所在 CPU 的節點比較 - 符合的跡象:只有慢的處理程序大部分記憶體位於與其執行 CPU 不同的節點,用 numactl 把 CPU 與記憶體綁定在同一節點重新啟動後,差距消失 - 不符合的跡象:節點分布與快的處理程序相同卻仍然慢時,是 noisy neighbor、CPU 節流、該處理程序本身的負載等其他原因 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [What is NUMA?](https://docs.kernel.org/mm/numa.html) · Linux kernel · 同一個 cell 的記憶體較快、頻寬較大,其他(遠端)cell 的記憶體存取較慢 - [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 logging 遊戲執行緒每寫一行 log 都要等磁碟寫完的話,磁碟一忙,遊戲進行也會跟著停住。 - 為什麼 → 於是 → 畫面上:在遊戲執行緒上直接把戰鬥、交易 log 寫入檔案 → 要求確實寫入(fsync),或 OS 的寫入緩衝區(頁面快取)達到上限時,磁碟一忙,一次寫入就要數十 ms → log 量大的戰鬥中會頓一下 - 症狀:卡頓, 定格/因素:停滯 - 誰會遇到:特定地點/頻道, 整個伺服器/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:改用非同步 log(記憶體緩衝區 + 獨立執行緒)、減少 log 量、不在遊戲執行緒上呼叫 fsync。 - 基礎設施團隊要做的事:以較低的 I/O 優先順序執行 log 輪替(rotation)與壓縮、把 log 放在與資料不同的磁碟、監控磁碟寫入延遲。 - 圖表上:偶爾隨機飆高(伺服器 tick 時間、磁碟寫入延遲) - 查看位置:把 iostat -x 1 的 w_await、aqu-sz 與 tick 時間疊在一起看,再用 perf trace -p PID --duration 10 找出遊戲伺服器中耗時超過 10ms 的 write、fsync 呼叫及其執行緒 - 符合的跡象:tick 飆高的時間點,遊戲執行緒的 write、fsync 呼叫耗時數十 ms,同一時刻磁碟寫入延遲也往上衝。常與 log 輪替、壓縮的時間重疊 - 不符合的跡象:遊戲執行緒上沒有耗時很久的系統呼叫、tick 卻飆高時,是 GC、鎖、超出 tick 預算等其他原因。只有 log 專用執行緒耗時很久時,不影響遊戲進行 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:OS 通常會先把寫入的資料收進記憶體(頁面快取),之後再寫回磁碟,所以寫一行 log 大多會立刻完成。會停住的情況有三種:用 fsync 要求確實寫入時、積壓的寫入超過上限而使 OS 阻塞(block)寫入呼叫時、輪替或壓縮 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 · 以 idle I/O 優先順序執行的工作,只有在其他程式沒有使用磁碟時才分得到磁碟時間 - [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 追蹤執行中處理程序的系統呼叫,用 --duration 只顯示耗時超過指定 ms 的呼叫 #### dk-fsync · fsync 暴增 · fsync storms 要求把資料「確實」寫入磁碟時,依磁碟不同,每次需要 0.1ms~數十 ms;請求一多,佇列就會變長。 - 為什麼 → 於是 → 畫面上:定期存檔、登出潮使確實寫入的請求大量湧入 → 磁碟佇列變長 → 每到存檔時間就 lag,登出、切換頻道變慢 - 症狀:卡頓, 輸入延遲/因素:停滯, 延遲 - 誰會遇到:整個伺服器/何時:固定週期, 人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施), 基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:把多次存檔合併成一次(多筆存檔只呼叫一次 fsync)、分散存檔時間。 - 基礎設施團隊要做的事:伺服器設備/OS:採用具斷電保護的伺服器用 SSD,監控磁碟佇列長度與 fsync 延遲。DB 設備:存檔寫入 DB 時,DB 的 log 磁碟也換成同類 SSD,並監控 commit 延遲。 - 數值參考:每次所需時間依設備而異,大致是:伺服器用 SSD(具斷電保護)0.1ms、一般 SSD 1~數 ms、雲端磁碟 1~2ms、HDD 10ms 以上。由單一執行緒一個一個等待的話,HDD 每秒連 100 次都做不到。 - 圖表上:固定週期飆高(磁碟佇列長度、flush 與寫入延遲) - 查看位置:把 iostat -x 1 的 f/s、f_await(磁碟處理的 flush 次數與耗時)以及 w/s、aqu-sz、w_await,與定期存檔、登出的時間疊在一起看。舊版 sysstat 把 aqu-sz 顯示為 avgqu-sz。雲端磁碟則看 EBS 的 VolumeQueueLength、VolumeAvgWriteLatency - 符合的跡象:每到存檔時間或登出潮,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 在 16KiB I/O 下平均不到 500µs - [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 · HDD 尋軌一次 10ms - [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 用完,效能驟降回基準值 → 每天晚上過了幾個小時後開始 lag - 症狀:卡頓, 慢動作, 輸入延遲/因素:停滯, 延遲 - 誰會遇到:整個伺服器/何時:晚間尖峰時段, 開越久越嚴重 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:基礎設施團隊(DB 基礎設施) - 基礎設施團隊要做的事:伺服器設備/OS:改用保證效能的磁碟(gp3、佈建 IOPS 型),設定額度剩餘量警示,並一併確認執行個體的磁碟頻寬突發上限與 CPU 額度。DB 設備:包含受管 DB 在內的 DB 磁碟也改為保證效能型,並設定額度剩餘量警示。 - 數值參考:AWS gp2 100GB 磁碟平常為 300 IOPS,突發時可達 3,000 IOPS,額度全滿時約可撐 30 分鐘。gp3 沒有額度機制,一直都是 3,000 IOPS。Azure 的小容量 Premium SSD 也能靠額度突發,最多 30 分鐘。 - 圖表上:碰到上限後持平(IOPS、burst credit 剩餘量) - 查看位置:看 CloudWatch 的 EBS BurstBalance(gp2、st1、sc1)、執行個體的 EBSIOBalance%、EBSByteBalance%(部分會突發的執行個體)、突發型(burstable)執行個體的 CPUCreditBalance。Azure 則看 Data Disk Used Burst IO Credits Percentage 等 burst credit 使用率指標 - 符合的跡象:從剩餘量降到接近 0 的時間點起,IOPS(VolumeReadOps、VolumeWriteOps)在基準效能處持平,VolumeQueueLength 與 lag 一起增加。在尖峰持續幾個小時後才開始 - 不符合的跡象:各項剩餘量都很充足、IOPS 卻持平時,是磁碟區或執行個體的固定上限(dk-iops) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:即使磁碟本身正常,小規格的虛擬伺服器在執行個體的磁碟頻寬上也有突發上限(例如每天至少 30 分鐘),會出現同樣的圖形。使用 CPU 額度的低價伺服器,額度用完時也會慢到只剩基準效能。 - 出處: - [Amazon EBS General Purpose SSD volumes](https://docs.aws.amazon.com/ebs/latest/userguide/general-purpose.html) · AWS · gp2 的基準效能為每 GiB 3 IOPS(最低 100),可靠 I/O 額度突發到 3,000 IOPS,540 萬點額度至少可撐 30 分鐘。gp3 沒有突發,一直是 3,000 IOPS - [Managed disk bursting](https://learn.microsoft.com/en-us/azure/virtual-machines/disk-bursting) · Microsoft Azure · Premium SSD P20 以下採用以額度為基礎的突發,額度全滿時能以最大突發速度維持 30 分鐘 - [Amazon EBS-optimized instance types](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html) · AWS · 部分執行個體每 24 小時只能維持一次 30 分鐘的 EBS 最大效能,之後回到基準效能 - [Standard mode for burstable performance instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances-standard-mode.html) · AWS · 標準模式的突發型執行個體在 CPU 額度用完時,會把 CPU 使用率降到基準水準(逐步降低,不會驟降) - [Amazon CloudWatch metrics for Amazon EBS](https://docs.aws.amazon.com/ebs/latest/userguide/using_cloudwatch_ebs.html) · AWS · BurstBalance:gp2 的 I/O 額度、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%:每 24 小時突發一次 30 分鐘的部分執行個體的 EBS 額度剩餘量;CPUCreditBalance:突發型執行個體的 CPU 額度剩餘量 - [Disk metrics](https://learn.microsoft.com/en-us/azure/virtual-machines/disks-metrics) · Microsoft Azure · Data Disk Used Burst IO Credits Percentage 等磁碟與 VM 的 burst credit 使用率(5 分鐘間隔) #### dk-iops · IOPS 上限與佇列飽和 · IOPS limit / queue saturation 請求數超過磁碟每秒能處理的數量時,佇列會變長,延遲急遽增加。 - 為什麼 → 於是 → 畫面上:讀寫請求接近磁碟的處理能力 → 佇列變長(通常在使用率 90% 以上時急遽增加) → 存檔、載入變慢;若是同步呼叫則會定格 - 症狀:輸入延遲, 定格/因素:延遲, 停滯 - 誰會遇到:整個伺服器/何時:人潮湧入時, 晚間尖峰時段 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發), 基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:合併請求、經常讀取的資料放進快取、改為非同步,讓遊戲執行緒不必等待磁碟。 - 基礎設施團隊要做的事:伺服器設備/OS:換用更快的磁碟、確認各執行個體類型的磁碟頻寬與 IOPS 上限、設定磁碟使用率與佇列警示、大檔案複製排在離峰時段。DB 設備:DB 磁碟也設定 IOPS 與處理量使用率警示,確認 DB 執行個體規格的磁碟上限。 - 數值參考:HDD 約 150 IOPS、SATA SSD 數萬、NVMe 數十萬 IOPS。雲端預設磁碟(AWS gp3)為 3,000 IOPS、每秒 125MiB;每秒傳輸量上限與 IOPS 是分開的,大檔案複製把它占滿時,連小筆寫入也會被卡住。 - 圖表上:碰到上限後持平(IOPS、磁碟佇列長度) - 查看位置:看 iostat -x 1 的 r/s、w/s、rkB/s、wkB/s、aqu-sz、r_await、w_await。雲端則看 EBS 的 VolumeReadOps、VolumeWriteOps、VolumeQueueLength,是否超過上限的 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 · 7,200rpm 伺服器用 HDD 的 4K 隨機讀取為 170 IOPS(QD16) - [D3-S4520 SSD](https://www.solidigm.com/products/data-center/d3/s4520.html) · Solidigm · 伺服器用 SATA SSD 的 4KB 隨機讀取/寫入最高 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 與 125MiB/s,兩者是可以分別調高的獨立上限 - [Amazon EBS-optimized instance types](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html) · AWS · 每種執行個體類型各自有 EBS 頻寬、處理量、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:磁碟區試圖超過 IOPS 或處理量上限時為 1(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:執行個體試圖超過 EBS IOPS 或處理量上限時為 1 #### dk-full · 磁碟已滿 · Disk full log 與 dump 越積越多、把磁碟塞滿時,寫入會失敗;沒有防範措施的話,伺服器會當機。 - 為什麼 → 於是 → 畫面上:log、dump、暫存檔累積到 100% → 寫入失敗。沒有錯誤處理就當機,有的話則存檔失敗 → 斷線、進度回檔 - 症狀:斷線, 吃指令/回檔/因素:停滯 - 誰會遇到:整個伺服器/何時:開越久越嚴重 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:基礎設施團隊(DB 基礎設施), 遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:處理寫入失敗,改為重試存檔並發出警示,避免當機;減少不必要的 log 與 dump。 - 基礎設施團隊要做的事:伺服器設備/OS:log 輪替、容量警示、log 與資料分放不同磁碟。DB 設備:監看 DB 的 transaction log(WAL、binlog)是否因複寫中斷或漏做 log 備份而持續累積。 - 圖表上:緩慢爬升(磁碟使用率) - 查看位置:看 df -h 的使用率與 df -i 的 inode 使用率,並在伺服器與 DB 的 log 中找 ENOSPC 錯誤。DB 方面,看 PostgreSQL pg_replication_slots 中 active 為 false 的複寫槽、MySQL SHOW BINARY LOGS 的檔案數與大小、SQL Server sys.databases 的 log_reuse_wait_desc,RDS 則看 FreeStorageSpace - 符合的跡象:使用率在幾天內持續上升,達到 100% 的時間點與當機、存檔失敗的時間重疊,log 中留有 ENOSPC - 不符合的跡象:空間充足但寫入仍失敗時,是權限、檔案大小上限等其他原因 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:複本停止或漏做 log 備份時,DB 的 transaction log(WAL、binlog 等)不會被刪除,會一直累積。這顆磁碟滿了以後,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 · 複寫槽在複本收到之前不會刪除 WAL,因此可能塞滿 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 備份、複寫延遲、長時間的 transaction 是妨礙 log 清理的常見原因;被什麼卡住可由 sys.databases 的 log_reuse_wait_desc 確認 - [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:該複寫槽目前是否正在串流;wal_status:複寫槽保留的 WAL 是否已超過 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 → 每天同一時間 lag - 症狀:卡頓, 輸入延遲/因素:停滯, 延遲 - 誰會遇到:整個伺服器/何時:固定週期 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:基礎設施團隊(DB 基礎設施) - 基礎設施團隊要做的事:伺服器設備/OS:調低備份、壓縮、掃描作業的 I/O 優先順序,並錯開執行時間。DB 設備:從複本進行備份。 - 圖表上:固定週期飆高(磁碟使用率、磁碟等待時間) - 查看位置:用 sar -d 把過去幾天紀錄(/var/log/sa 的每日檔案,sadc 須以 -S DISK 一併收集磁碟項目)的 %util、await、aqu-sz 按日期疊在一起看,在那個時間點用 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 · 停下複本來備份,不會影響主 DB 的運作 - [sar(1) — Linux manual page](https://man7.org/linux/man-pages/man1/sar.1.html) · sysstat · -d:每日紀錄檔(預設 /var/log/sa)中各裝置的 await、aqu-sz、%util;磁碟項目須以 sadc 的 -S DISK 選項收集 - [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 on the server 伺服器在副本、地圖資料第一次被請求時才從磁碟讀取的話,那個 tick 期間所有人都會停住。 - 為什麼 → 於是 → 畫面上:有人第一次進入副本或地圖 → 伺服器在遊戲執行緒上從磁碟讀取資料 → 那台伺服器上的所有人短暫定格 - 症狀:定格/因素:停滯 - 誰會遇到:特定地點/頻道, 整個伺服器/何時:移動中/切換地圖時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:在伺服器啟動時預先載入、改為非同步載入。 - 基礎設施團隊要做的事:剛從快照建立的伺服器,在投入服務前先預熱磁碟(把所有區塊讀過一次),或使用快速快照還原功能。 - 圖表上:偶爾隨機飆高(伺服器 tick 時間、磁碟讀取) - 查看位置:把停住的時間點與遊戲伺服器 log 中副本、地圖的首次進入紀錄對照,看那個瞬間遊戲伺服器的磁碟讀取(pidstat -d 的 kB_rd/s),並用 perf trace --duration 找耗時很久的 read、open 呼叫。若是新開的雲端伺服器,把 EBS 的 VolumeAvgReadLatency 與舊伺服器比較 - 符合的跡象:只在第一次進入的瞬間停住,第二次進入同一個地方時不會停住。停住期間遊戲執行緒在等待讀檔 - 不符合的跡象:已載入的地圖也一樣停住時,是超出 tick 預算、GC 等其他原因 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:在雲端上,剛從快照(磁碟副本)建立的伺服器,每個第一次讀取的區塊都要從遠端儲存裝置取回,比平常慢得多。如果只有自動擴展新開出來的伺服器第一次進入特別久,就要懷疑是這個原因。 - 出處: - [Initialize Amazon EBS volumes](https://docs.aws.amazon.com/ebs/latest/userguide/ebs-initialize.html) · AWS · 從快照建立的磁碟區在從 S3 取回區塊期間延遲增加、效能下降;可用 dd、fio 讀取所有區塊預先初始化 - [Amazon EBS fast snapshot restore](https://docs.aws.amazon.com/ebs/latest/userguide/ebs-fast-snapshot-restore.html) · AWS · 快速快照還原會提供建立時就已初始化完成的磁碟區,消除首次存取的延遲 - [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:只顯示耗時超過指定 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 大小(OS 的 core dump 設定)、使用快速的磁碟、讓重新啟動與 dump 分開(dump 的壓縮、上傳在重新啟動後另外處理)。 - 圖表上:連線同時大量中斷(連線數、伺服器重新啟動時間) - 查看位置:把當機時間、core 檔大小(coredumpctl list、info,或 core_pattern 指向位置的檔案)、檔案寫完的時間、服務重新啟動的時間並排對照,並看這段期間 iostat -x 的 wkB/s - 符合的跡象:當機後寫入數 GB 的 core 檔期間,磁碟寫入一直貼近上限,寫完之後才開始重新啟動 - 不符合的跡象:core dump 已關閉或檔案很小,重新啟動卻仍然很慢時,是地圖載入、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 只包含當機 dump 資訊中有用的部分,因此產生得快、檔案也小 - [coredumpctl(1) — Linux manual page](https://man7.org/linux/man-pages/man1/coredumpctl.1.html) · systemd · list:journal 中留下的 core dump 清單(TIME 為 kernel 回報的當機時間);info:各 dump 的詳細資訊與寫入磁碟的大小 - [iostat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/iostat.1.html) · sysstat · -x:wkB/s(每秒磁碟寫入量) #### dk-hdd · HDD 尋軌延遲 · HDD seek latency HDD 的讀寫頭必須在碟片上移動(尋軌,seek),所以讀寫分散各處的資料時,每次要花將近 10ms。 - 為什麼 → 於是 → 畫面上:老舊伺服器或低價儲存裝置使用 HDD → 每次分散的讀寫約 10ms → 存檔、載入全面變慢 - 症狀:輸入延遲/因素:延遲 - 誰會遇到:整個伺服器/何時:一直都有 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:基礎設施團隊(DB 基礎設施), 遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:以循序寫入為主進行設計。 - 基礎設施團隊要做的事:伺服器設備/OS:換成 SSD(從分散讀寫較多的資料磁碟開始)。DB 設備:先把分散讀寫最多的 DB 磁碟換成 SSD。 - 圖表上:一開始就一直偏高(磁碟讀寫延遲(r_await、w_await)) - 查看位置:用 lsblk -d -o NAME,ROTA 確認是否為旋轉式磁碟(HDD),並看 iostat -x 1 的 r/s、w/s 與 r_await、w_await。虛擬伺服器則在雲端或儲存裝置的規格中確認磁碟種類 - 符合的跡象:是旋轉式磁碟,每秒請求只有數十~一百多個,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 · 磁碟尋軌一次 10ms,從磁碟循序讀取 1MB 需 20ms - [Exos X18 Data Sheet](https://www.seagate.com/www-content/datasheets/pdfs/exos-x18-mango-DS2045-1N-2007US-en_US.pdf) · Seagate · 7,200rpm HDD 平均旋轉延遲 4.16ms,4K 隨機讀取 170 IOPS - [lsblk(8) — Linux manual page](https://man7.org/linux/man-pages/man8/lsblk.8.html) · util-linux · -o:選擇輸出欄位;裝置拓撲欄位包含 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 沒有索引時,要找出符合條件的資料列,就必須讀完整張資料表(全表掃描)。 - 為什麼 → 於是 → 畫面上:部署新功能時,加入了沒有索引的條件查詢 → 掃描數百萬筆資料列,一個查詢就要數百 ms~數秒 → 信箱、交易紀錄載入變慢,連線被占住,連其他請求也要等待 - 症狀:輸入延遲, 連不上/無限讀取/因素:延遲, 停滯 - 誰會遇到:只有特定功能, 整個伺服器/何時:做特定動作時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:新查詢在部署前先檢查執行計畫、加上索引,並確認修改資料的查詢(UPDATE、DELETE)也有用到索引。 - 基礎設施團隊要做的事:監看慢查詢 log,找出全表掃描的查詢並提供給遊戲開發團隊;營運中新增索引時,採用鎖定時間短的線上(online)方式。 - 數值參考:有索引時只要數 ms;沒有索引時會隨資料量等比例變慢,在大型資料表上會慢到數百~數萬倍。 - 圖表上:從某個時間點起階梯式上升(DB 查詢延遲、讀取的資料列數) - 查看位置:MySQL 看 slow query log(開啟 log_queries_not_using_indexes 後,沒用到索引的查詢也會記錄)的 Rows_examined、Rows_sent,以及 performance_schema events_statements_summary_by_digest 的 SUM_NO_INDEX_USED、SUM_ROWS_EXAMINED,再執行 EXPLAIN。PostgreSQL 看 pg_stat_user_tables 的 seq_scan、seq_tup_read,再執行 EXPLAIN - 符合的跡象:部署後新出現的查詢,讀取的資料列(Rows_examined)比回傳的(Rows_sent)多出數千倍,EXPLAIN 顯示全表掃描(MySQL type ALL、PostgreSQL Seq Scan)。大型資料表的 seq_tup_read 從部署時間點起急遽增加 - 不符合的跡象:有用到索引仍然很慢時,是鎖定等待(db-hot-row、db-ddl-lock)或執行計畫改變(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 · pg_stat_user_tables 的 seq_scan(循序掃描次數)、seq_tup_read(循序掃描讀取的資料列數) - [Using EXPLAIN (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/using-explain.html) · PostgreSQL · Seq Scan:依序讀取資料表所有資料列的執行計畫 #### db-hot-row · 熱點資料列鎖定競爭 · Hot row lock contention 所有人都要修改同一筆資料列(公會倉庫、拍賣場熱門道具、全伺服器共用的計數器)時,一次只有一個請求能取得鎖定。 - 為什麼 → 於是 → 畫面上:活動、熱門道具讓修改集中在同一筆資料列 → 請求一直等到取得鎖定為止 → 交易失敗、「請稍後再試」、逾時 - 症狀:吃指令/回檔, 輸入延遲/因素:停滯, 延遲 - 誰會遇到:只有特定功能/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:拆分資料列(分片計數器,sharded counter)、縮短 transaction、先在記憶體中彙總再一次寫入。 - 基礎設施團隊要做的事:監控資料列鎖定的等待時間與次數,找出競爭集中的資料列並分享結果。 - 數值參考:一個請求持有鎖定 10ms 的話,這筆資料列每秒最多只能修改 100 次。transaction 中若夾著與其他伺服器的往返,次數還會再減少。 - 圖表上:隨人數/負載上升(資料列鎖定等待次數與時間) - 查看位置:MySQL 看 Innodb_row_lock_waits、Innodb_row_lock_time 的增加量與 Innodb_row_lock_current_waits,並用 sys.innodb_lock_waits 找出誰在等誰。PostgreSQL 看 pg_stat_activity 中 wait_event_type 為 Lock 的 session、pg_locks 中 granted 為 false 的請求;開啟 log_lock_waits(預設關閉)後,等待很久的鎖定會記錄在 log 中 - 符合的跡象:鎖定等待隨活動、人數急遽增加,等待中的請求大多指向同一張資料表的同一筆資料列(同一個 key) - 不符合的跡象:等待平均分散在多張資料表、多筆資料列時,是磁碟或 CPU 飽和的問題。單一 session 長時間持有鎖定不放時,是長時間未結束的 transaction(db-long-tx) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [InnoDB Locking](https://dev.mysql.com/doc/refman/8.4/en/innodb-locking.html) · MySQL · 一個 transaction 鎖定資料列(索引記錄)後,其他 transaction 無法修改該資料列,只能等待 - [How to Minimize and Handle Deadlocks](https://dev.mysql.com/doc/refman/8.4/en/innodb-deadlocks-handling.html) · MySQL · 建議讓 transaction 保持小而短,相關變更完成後立刻 commit,以減少衝突 - [Server Status Variables](https://dev.mysql.com/doc/refman/8.4/en/server-status-variables.html) · MySQL · 用 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)、造成阻擋的 session(blocking_pid)、等待時間(wait_age) - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · pg_stat_activity 的 wait_event_type:為 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:等待鎖定的時間超過 deadlock_timeout 時寫入 log,預設關閉 #### db-deadlock · DB 死結 · Database deadlock 兩個 transaction(當成一個整體處理的一組 DB 操作)互相等待對方鎖住的資料列時,DB 會強制取消其中一方。 - 為什麼 → 於是 → 畫面上:交易 A 依道具 → 貨幣的順序鎖定,交易 B 依貨幣 → 道具的順序鎖定 → DB 偵測到死結,回滾其中一方 → 交易、製作偶爾失敗,道具被退回 - 症狀:吃指令/回檔, 輸入延遲/因素:遺失, 停滯 - 誰會遇到:只有特定功能/何時:人潮湧入時, 做特定動作時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:統一鎖定順序、縮短 transaction、失敗時自動重試。 - 基礎設施團隊要做的事:保持死結偵測開啟,收集並分享死結紀錄;關閉偵測的 MySQL 伺服器要調低鎖定等待上限(預設 50 秒)。 - 數值參考:偵測所需時間:MySQL(InnoDB)幾乎即時,PostgreSQL 預設 1 秒,SQL Server 最多約 5 秒。這段期間兩個請求都會卡住。如果是因同時請求極多而關閉 MySQL 死結偵測的伺服器,會一直等到鎖定等待上限(預設 50 秒)。 - 圖表上:偶爾隨機飆高(死結次數、交易失敗次數) - 查看位置:MySQL 看 SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK(只有最近 1 筆)、開啟 innodb_print_all_deadlocks 後寫入錯誤 log 的所有死結,以及 INFORMATION_SCHEMA.INNODB_METRICS 的 lock_deadlocks。PostgreSQL 看 pg_stat_database 的 deadlocks,SQL Server 看預設開啟的 system_health session 的 xml_deadlock_report。遊戲伺服器端的錯誤碼為 MySQL 1213、PostgreSQL 40P01、SQL Server 1205 - 符合的跡象:交易、製作失敗的時間點死結次數增加,紀錄中的兩個 transaction 以相反的順序鎖定相同的資料表 - 不符合的跡象:死結次數不變卻仍失敗時,是超過鎖定等待上限(MySQL 錯誤 1205)或熱點資料列(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 秒,死結頻繁時會縮短到 100ms;預設開啟的 system_health session 會收集 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:最近一次死結的兩個 transaction、各自持有與等待的鎖定、被回滾的一方 - [InnoDB INFORMATION_SCHEMA Metrics Table](https://dev.mysql.com/doc/refman/8.4/en/innodb-information-schema-metrics-table.html) · MySQL · INNODB_METRICS 的 lock_deadlocks 計數器(預設啟用) - [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 · pg_stat_database 的 deadlocks:此 DB 偵測到的死結次數 - [PostgreSQL Error Codes (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/errcodes-appendix.html) · PostgreSQL · 40P01 deadlock_detected #### db-pool · 連線池耗盡 · Connection pool exhaustion 與 DB 建立的連線數是固定的,慢查詢占住連線時,其餘請求只能等待。 - 為什麼 → 於是 → 畫面上:慢查詢或請求暴增,使所有連線都在使用中 → 新請求要等到有連線空出來 → 登入時無限讀取、存檔變慢、逾時 - 症狀:連不上/無限讀取, 輸入延遲/因素:停滯, 延遲 - 誰會遇到:整個伺服器, 只有特定功能/何時:剛登入/維護剛結束, 人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:消除慢查詢、調整連線池大小與等待逾時(不要一味加大連線池)、依功能拆分連線池。 - 基礎設施團隊要做的事:確認 DB 的最大連線數與 CPU、IOPS 餘裕;擴充伺服器或自動擴展前,確認伺服器數 × 連線池大小在最大連線數以內;把連線等待與鎖等待指標加入監控。 - 數值參考:需要的連線數可用「每秒請求數 × 每筆請求占用連線的時間」估算。每秒 2,000 筆、每筆 5ms 時,平均隨時有 10 條連線在忙。考量請求湧入的情況,通常會準備兩三倍。查詢變慢到 150ms 時,同樣的請求量就需要 300 條。 - 圖表上:碰到上限後持平(使用中的 DB 連線數、連線等待時間) - 查看位置:在 DB 端依遊戲伺服器統計連線狀態。MySQL 看 SHOW PROCESSLIST 的 Host、Command(閒置連線為 Sleep)、Time,以及 Threads_connected、Threads_running 與被拒絕的連線數 Connection_errors_max_connections。PostgreSQL 把 pg_stat_activity 依 client_addr、state 分組統計。遊戲伺服器的連線池函式庫有輸出等待數與等待時間時,一併查看 - 符合的跡象:某台遊戲伺服器的連線達到連線池大小且全部都在執行查詢、閒置連線為 0 的期間,登入與存檔都在等待。或是 DB 的總連線數碰到 max_connections,新連線被拒絕 - 不符合的跡象:閒置連線充足卻仍然很慢時,是查詢本身的延遲(db-no-index、db-hot-row)或 DB 資源飽和的問題 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:一味加大連線池,只會增加 DB 的 CPU 負載與鎖定競爭,讓所有人一起變慢。此外,伺服器數 × 連線池大小超過 DB 的最大連線數時,新增或重新啟動的伺服器甚至無法建立連線。這在自動擴展或維護剛結束時很常見。 - 出處: - [Number Of Database Connections](https://wiki.postgresql.org/wiki/Number_Of_Database_Connections) · PostgreSQL · DB 資源用盡之後,增加連線反而會降低處理量;讓活躍連線數配合資源、其餘放進佇列,延遲與處理量都會比較好 - [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(閒置 session 為 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、讀取走複本的架構下,複本跟得慢時,剛寫入的內容就會看不到。 - 為什麼 → 於是 → 畫面上:寫入集中湧向主 DB,複本落後數秒 → 從複本讀取剛存檔的內容時,資料還不存在 → 剛買的道具看不到、交易所價格還是舊的、重複發放的 bug - 症狀:吃指令/回檔/因素:延遲 - 誰會遇到:只有特定功能/何時:人潮湧入時, 晚間尖峰時段 - 主要負責:基礎設施團隊(DB 基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:剛寫入的資料從主 DB 讀取;確認是否已發放與實際發放,在主 DB 上以同一個 transaction 完成(用唯一鍵或條件式 UPDATE 防止重複)。 - 基礎設施團隊要做的事:設定複寫延遲警示、讓複本規格不低於主 DB 並啟用平行複寫、大量刪除分成小批執行、管理在複本上長時間執行的彙總查詢。 - 圖表上:隨人數/負載上升(複寫延遲(秒)) - 查看位置:MySQL 在複本上看 SHOW REPLICA STATUS 的 Seconds_Behind_Source(8.0.22 之前的版本用 SHOW SLAVE STATUS)。PostgreSQL 看主伺服器 pg_stat_replication 的 write_lag、flush_lag、replay_lag,RDS 則看 ReplicaLag - 符合的跡象:回報「看不到」的時間點延遲達數秒以上,延遲消除後再看就正常。寫入暴增、大量刪除或複本上執行長時間彙總查詢時,延遲變大 - 不符合的跡象:延遲接近 0 卻仍看不到時,是遊戲伺服器的快取或同步的問題 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:即使寫入不多也可能落後。在主 DB 上花了 10 分鐘的一次大量刪除,在複本上重新執行的期間,也會讓複本落後同樣的時間;在複本上長時間執行的彙總查詢,也會拖慢複本追上的速度。 - 出處: - [SHOW REPLICA STATUS Statement](https://dev.mysql.com/doc/refman/8.4/en/show-replica-status.html) · MySQL · Seconds_Behind_Source:複本目前正在套用的事件,與該事件在主 DB 上寫入的時間之差(複寫延遲) - [Replica Server Options and Variables](https://dev.mysql.com/doc/refman/8.4/en/replication-options-replica.html) · MySQL · 用 replica_parallel_workers 讓多個執行緒平行套用 transaction(預設 4;設為 0 時由單一執行緒依序套用) - [Log-Shipping Standby Servers (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/warm-standby.html) · PostgreSQL · 串流複寫預設為非同步,commit 與複本套用之間有延遲(複本跟得上時通常不到 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 · pg_stat_replication 的 write_lag、flush_lag、replay_lag:主伺服器寫入 WAL 之後,到複本分別回報已寫入、已寫回磁碟、已套用為止所花的時間 - [Amazon CloudWatch metrics for Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-metrics.html) · AWS · ReplicaLag:讀取複本落後來源的時間(秒) #### db-checkpoint · 檢查點與 log flush · Checkpoint / log flush stalls DB 定期把記憶體中的變更一次大量寫入磁碟的瞬間,查詢會變慢。 - 為什麼 → 於是 → 畫面上:變更累積後定期寫入磁碟 → 那一刻磁碟變忙,查詢變慢 → 存檔、載入週期性變慢 - 症狀:輸入延遲, 卡頓/因素:延遲 - 誰會遇到:整個伺服器, 只有特定功能/何時:固定週期 - 主要負責:基礎設施團隊(DB 基礎設施) - 基礎設施團隊要做的事:把檢查點的寫入切細、平均分散,transaction log(redo log、WAL)配置充足,使用快速的磁碟。 - 圖表上:固定週期飆高(DB 查詢延遲、磁碟寫入量) - 查看位置:PostgreSQL 看 log_checkpoints(新版本預設開啟)log 中的檢查點時間與寫入的緩衝區數、檢查點次數(17 起為 pg_stat_checkpointer 的 num_timed、num_requested,16 以下為 pg_stat_bgwriter 的 checkpoints_timed、checkpoints_req),以及 checkpoint_warning 警告。MySQL 看 SHOW ENGINE INNODB STATUS 的 LOG 區段中 Log sequence number 與 Last checkpoint at 的差距。一併疊上伺服器的磁碟寫入量與寫入延遲 - 符合的跡象:查詢延遲飆高的時間點與檢查點時間重疊,此時磁碟寫入量與寫入延遲往上衝。PostgreSQL 中由請求觸發的檢查點(num_requested)遠多於定時檢查點(num_timed)時,可判斷是 WAL 經常碰到 max_wal_size,使檢查點提前執行 - 不符合的跡象:以與檢查點時間無關的週期飆高時,是備份或批次作業(dk-backup、db-batch) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:記錄變更的 transaction log(MySQL 的 redo log、PostgreSQL 的 WAL)設得太小時,每次 log 寫滿,DB 都得匆忙集中執行檢查點,寫入處理量會短暫大幅下降。 - 出處: - [WAL Configuration (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/wal-configuration.html) · PostgreSQL · 檢查點預設每 5 分鐘或每產生 1GB WAL(max_wal_size)執行一次,要寫出所有 dirty page,成本很高。用 checkpoint_completion_target 分散寫入,避免 I/O 暴增。檢查點間隔短於 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)檢查點會讓處理量短暫下降;以自適應 flush(adaptive flushing)平均分散寫入 - [Error Reporting and Logging (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/runtime-config-logging.html) · PostgreSQL · log_checkpoints:每次檢查點都在 log 中記錄寫入的緩衝區數與耗時,預設開啟 - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · pg_stat_checkpointer 的 num_timed(時間到而執行的檢查點)、num_requested(被請求的檢查點) - [PostgreSQL 17 Release Notes](https://www.postgresql.org/docs/release/17.0/) · PostgreSQL · 新增 pg_stat_checkpointer,把檢查點相關欄位從 pg_stat_bgwriter 移過來 - [The Cumulative Statistics System (PostgreSQL 16 Documentation)](https://www.postgresql.org/docs/16/monitoring-stats.html) · PostgreSQL · 16 以前為 pg_stat_bgwriter 的 checkpoints_timed、checkpoints_req - [InnoDB Standard Monitor and Lock Monitor Output](https://dev.mysql.com/doc/refman/8.4/en/innodb-standard-monitor.html) · MySQL · LOG 區段:目前的 log 序號與最後一次檢查點的位置 #### db-cold-cache · 冷快取(剛重新啟動時) · Cold buffer pool after restart DB 重新啟動後記憶體快取是空的,有一段時間所有查詢都要從磁碟讀取。 - 為什麼 → 於是 → 畫面上:因維護而重新啟動 DB → 常用資料不在記憶體中,只能從磁碟讀取 → 維護剛結束的一段時間內,登入、載入很慢 - 症狀:連不上/無限讀取, 輸入延遲/因素:延遲 - 誰會遇到:整個伺服器/何時:剛登入/維護剛結束 - 主要負責:基礎設施團隊(DB 基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:逐步開放(用登入排隊分階段增加登入人數)。 - 基礎設施團隊要做的事:重新啟動後預熱快取(warm-up,確認 buffer pool 的儲存與還原設定),從快照還原的 DB 也要預先讀取磁碟。 - 圖表上:剛開服或維護結束後暴增(磁碟讀取、緩衝快取命中率) - 查看位置:MySQL 看 Innodb_buffer_pool_reads(不在 buffer pool 中而從磁碟讀取的次數)與 Innodb_buffer_pool_read_requests 的比例,以及預熱進度 Innodb_buffer_pool_load_status。PostgreSQL 看 pg_stat_database 的 blks_read、blks_hit。也一併看 DB 伺服器的磁碟讀取次數 - 符合的跡象:剛重新啟動時磁碟讀取往上衝、命中率偏低,之後隨時間逐漸恢復,這段期間登入、載入很慢 - 不符合的跡象:命中率與平常相同、維護剛結束卻很慢時,是登入暴增與 N+1(db-login-storm)或連線池(db-pool) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:MySQL 關閉時會儲存 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 · 為了縮短重新啟動後的預熱時間,關閉時儲存最近使用的頁面清單(預設 25%),啟動時重新讀回;兩者預設都開啟 - [pg_prewarm — preload relation data into buffer caches](https://www.postgresql.org/docs/current/pgprewarm.html) · PostgreSQL · 定期記錄共享緩衝區(shared buffers)的內容,重新啟動後再載回(autoprewarm) - [Initialize Amazon EBS volumes](https://docs.aws.amazon.com/ebs/latest/userguide/ebs-initialize.html) · AWS · 從快照建立的磁碟區,在取回所有區塊之前延遲增加、效能下降 - [Server Status Variables](https://dev.mysql.com/doc/refman/8.4/en/server-status-variables.html) · MySQL · Innodb_buffer_pool_reads(不在 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 · pg_stat_database 的 blks_read(從磁碟讀取的區塊數)、blks_hit(在緩衝快取中找到的區塊數) #### db-login-storm · 登入暴增與 N+1 查詢 · Login storm, N+1 queries 載入一個角色要分開查詢數十次的話,數萬人同時登入就會變成數百萬個查詢。 - 為什麼 → 於是 → 畫面上:載入角色時,道具、技能、任務各自分開查詢 → 維護剛結束時大量同時登入,查詢暴增 → 登入時無限讀取,連正在遊戲中的玩家存檔都被拖慢 - 症狀:連不上/無限讀取, 輸入延遲/因素:停滯, 延遲 - 誰會遇到:整個伺服器/何時:剛登入/維護剛結束 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:合併成一次查詢、登入排隊、快取,並檢查 ORM 延遲載入產生的查詢數。 - 基礎設施團隊要做的事:列出呼叫次數最多的查詢排行並分享,監控維護剛結束的登入時段的查詢數與連線數。 - 圖表上:剛開服或維護結束後暴增(DB 每秒查詢數、登入數) - 查看位置:把維護剛結束時的登入數與 DB 每秒查詢數(MySQL 為 Questions 的增加量)疊在一起看,計算每次登入的查詢數。呼叫次數最多的查詢,可從 MySQL events_statements_summary_by_digest 的 COUNT_STAR、PostgreSQL pg_stat_statements 的 calls 列出 - 符合的跡象:每次登入有數十個查詢,排行前面的都是以單一角色 ID 查詢、形式相同的短查詢。更新後每次登入的查詢數增加的話,就從那次更新查起 - 不符合的跡象:每次登入的查詢數不多、但每個查詢都很慢時,是冷快取(db-cold-cache)或索引(db-no-index) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:ORM(代為產生 DB 查詢的函式庫)的延遲載入,會在開發者沒察覺的情況下產生這類查詢。開發伺服器上只有幾個角色,看不出差異,要到正式環境大量同時登入時才會浮現。 - 出處: - [Efficient Querying](https://learn.microsoft.com/en-us/ef/core/performance/efficient-querying) · .NET · ORM 的延遲載入會造成每個項目多送一次查詢的 N+1 問題,使效能大幅下降;建議一次合併載入(eager loading) - [pg_stat_statements — track statistics of SQL planning and execution](https://www.postgresql.org/docs/current/pgstatstatements.html) · PostgreSQL · 收集每個 SQL 陳述式的執行次數(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 jobs during service 在營運時段執行排行榜彙總、信件批次發送、舊資料清理時,會占用鎖定與磁碟。 - 為什麼 → 於是 → 畫面上:在營運時段執行大量作業 → 大範圍鎖定,占用磁碟與 CPU → 特定時段交易、存檔失敗,載入變慢 - 症狀:輸入延遲, 吃指令/回檔/因素:延遲, 停滯 - 誰會遇到:只有特定功能, 整個伺服器/何時:固定週期 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:切成小批分次執行,彙總改在複本上進行。 - 基礎設施團隊要做的事:提供彙總用的複本,把批次作業排在離峰時段,監看鎖定擴大與 gap lock 等待。 - 圖表上:固定週期飆高(DB 查詢延遲、鎖定等待) - 查看位置:找出 lag 發生時正在執行的長查詢。MySQL 看 slow query log,PostgreSQL 看 pg_stat_activity 的 query_start、query,再與同一時間的鎖定等待指標及批次排程(cron、DB 事件排程器)比對。SQL Server 用 lock_escalation 擴充事件記錄鎖定擴大 - 符合的跡象:每次都在同一時間執行大量 UPDATE、DELETE 或彙總查詢,期間鎖定等待與磁碟使用率一起上升 - 不符合的跡象:那個時間沒有長查詢時,是檢查點(db-checkpoint)或伺服器備份(dk-backup) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:SQL Server 在單一陳述式持有超過約 5,000 個資料列鎖定時,會改成資料表鎖定(鎖定擴大)。那一刻所有使用同一張資料表的請求都會停住。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 擴充事件記錄 - [InnoDB Locking](https://dev.mysql.com/doc/refman/8.4/en/innodb-locking.html) · MySQL · 在 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:每個 session 目前執行中的查詢(query)與開始時間(query_start) #### db-failover · DB 容錯移轉 · Database failover 主 DB 故障、切換到備援 DB 的期間無法寫入,最後一批還沒複寫過去的資料可能會遺失。 - 為什麼 → 於是 → 畫面上:主 DB 故障,備援 DB 升格為主 DB → 切換期間有數秒~幾分鐘無法寫入;若是非同步複寫,尚未複寫的資料可能遺失 → 短時間內所有存檔失敗,道具、經驗值回檔 - 症狀:吃指令/回檔, 定格, 斷線, 連不上/無限讀取/因素:停滯, 遺失 - 誰會遇到:整個伺服器/何時:偶爾隨機發生 - 主要負責:基礎設施團隊(DB 基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:讓存檔可以重試;設定連線池與 DNS 快取,快速捨棄已中斷的連線並以新位址重新建立連線;在容錯移轉演練時確認能重新連線。 - 基礎設施團隊要做的事:採用同步或半同步複寫(代價是寫入延遲增加)、進行容錯移轉演練、監控切換時間與複寫延遲。 - 數值參考:受管 DB 的自動容錯移轉通常需要數十秒~2 分鐘。若是非同步複寫,可能會遺失相當於複寫延遲(不到 1 秒~數秒)的最近存檔。 - 圖表上:連線同時大量中斷(DB 連線數、寫入錯誤數) - 查看位置:把 DB 端的容錯移轉紀錄(RDS 為事件 RDS-EVENT-0013 開始切換、RDS-EVENT-0049 切換完成,自行維運的 DB 為升格 log)與遊戲伺服器的 DB 連線數、連線錯誤數放在同一張圖表上。若是非同步複寫,也要看故障前一刻的複寫延遲(RDS ReplicaLag、PostgreSQL pg_stat_replication 的 replay_lag) - 符合的跡象:存檔失敗集中在一段期間,且與容錯移轉開始到完成之間重疊。回檔的量與故障前一刻的複寫延遲相近。切換結束後仍持續出錯的遊戲伺服器,是還在使用連到舊位址的連線 - 不符合的跡象:沒有容錯移轉紀錄的時間發生的連線中斷,是網路或 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 容錯移轉通常需要 60~120 秒,切換後必須重新建立連線,建議 JVM 的 DNS 快取 TTL 設為 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 · 非同步複寫在主 DB 故障時,已 commit 的 transaction 可能不在複本上;半同步複寫會等待一個複本確認收到來降低這種風險,代價是延遲增加 - [Log-Shipping Standby Servers (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/warm-standby.html) · PostgreSQL · log 傳送(log shipping)是非同步的,主伺服器故障時,尚未送出的 transaction 會遺失;串流複寫的延遲通常不到 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 容錯移轉開始;RDS-EVENT-0049:Multi-AZ 容錯移轉完成 - [Amazon CloudWatch metrics for Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-metrics.html) · AWS · ReplicaLag:讀取複本落後來源的時間(秒) - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · pg_stat_replication 的 replay_lag:主伺服器寫入 WAL 之後,到複本回報已套用為止所花的時間 #### db-save-interval · 存檔週期過長造成的進度遺失 · Periodic save window 為了減輕負載而每隔幾分鐘才存檔一次的話,伺服器在兩次存檔之間當機時,進度就會消失。 - 為什麼 → 於是 → 畫面上:每隔幾分鐘才儲存一次角色狀態 → 在兩次存檔之間發生伺服器當機或故障 → 重新連線後回到幾分鐘前的狀態(回檔) - 症狀:吃指令/回檔/因素:遺失 - 誰會遇到:整個伺服器, 特定地點/頻道/何時:偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:重要事件(交易、取得稀有道具)立即存檔,並記錄變更 log。 - 基礎設施團隊要做的事:確認 DB 的 IOPS 與 CPU 有足夠餘裕,能承受縮短存檔週期後增加的寫入量。 - 圖表上:連線同時大量中斷(連線數、回檔回報數) - 查看位置:把當機、故障的時間,與回報回檔的角色最後一次存檔的時間(遊戲伺服器的存檔 log 或 DB 的修改時間欄位)並排對照 - 符合的跡象:回檔回到的時間點與當機前最後一次存檔的時間一致,遺失的時間短於存檔週期 - 不符合的跡象:遊戲伺服器 log 顯示存檔已完成卻仍回檔時,是 DB 容錯移轉造成的資料遺失(db-failover)或從複本讀到的舊值(db-replica-lag) - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Asynchronous Commit (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/wal-async-commit.html) · PostgreSQL · 把寫入集中起來延後寫回能提高處理量,代價是故障時最近的 transaction 可能遺失(同樣的取捨) - [Redis persistence](https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/) · Redis · 每隔幾分鐘建立一次 RDB 快照的話,就得接受異常關閉時遺失最後幾分鐘資料的風險 #### db-cache-stampede · Cache stampede · Cache stampede / thundering herd 熱門資料的快取同時過期時,數千個請求會一口氣湧向 DB。 - 為什麼 → 於是 → 畫面上:存放在 Redis 等快取中的熱門資料同時過期 → 要重新產生同一筆資料的請求一口氣湧向 DB → DB 過載,多項功能接連變慢甚至停住 - 症狀:輸入延遲, 定格, 連不上/無限讀取/因素:停滯, 延遲 - 誰會遇到:整個伺服器/何時:固定週期, 人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:把過期時間隨機分散;只讓一個請求更新,其餘請求繼續使用舊值。 - 基礎設施團隊要做的事:為 Redis 建置複本與自動容錯移轉,避免重新啟動或故障時整個快取被清空;確認 DB 有足夠餘裕,即使快取全空也撐得住。 - 圖表上:固定週期飆高(快取命中率、DB 每秒查詢數) - 查看位置:把 Redis INFO 的 keyspace_hits、keyspace_misses(命中率)、expired_keys、是否重新啟動(uptime_in_seconds)與 DB 每秒查詢數疊在一起看,並統計那個瞬間 DB 上同一個查詢同時有幾個在執行(MySQL SHOW PROCESSLIST、PostgreSQL pg_stat_activity) - 符合的跡象:快取未命中瞬間暴衝時,DB 查詢數也一起往上衝,同時執行的查詢大多是讀取同一筆資料的同一個查詢。與熱門 key 的過期週期或 Redis 重新啟動時間重疊 - 不符合的跡象:快取未命中與平常相同、只有 DB 查詢增加時,是登入暴增(db-login-storm)或批次作業(db-batch) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:Redis 重新啟動或因故障導致快取整個清空時,也會發生同樣的事。越是仰賴快取而把 DB 規格壓小的架構,風險越高。 - 出處: - [Scaling Memcache at Facebook (NSDI '13)](https://www.usenix.org/conference/nsdi13/technical-sessions/presentation/nishtala) · USENIX · 常用的 key 失效時大量讀取湧向 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 · 主伺服器故障時把複本升格的自動容錯移轉 - [INFO](https://redis.io/docs/latest/commands/info/) · Redis · keyspace_hits、keyspace_misses(key 查詢成功、失敗次數)、expired_keys(過期的 key 數)、uptime_in_seconds(啟動後經過的時間) - [SHOW PROCESSLIST Statement](https://dev.mysql.com/doc/refman/8.4/en/show-processlist.html) · MySQL · 每個 session 正在執行的陳述式(Info)與停留在目前狀態的時間(Time,秒) - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · pg_stat_activity:每個 session 目前執行中的查詢(query) #### db-long-tx · 長時間未結束的 transaction · Long-running transaction / MVCC purge lag 一個 transaction 長時間不結束時,會一直持有鎖定,DB 也無法清理(purge)舊版本的資料,整體會越來越慢。 - 為什麼 → 於是 → 畫面上:開著 transaction 等待其他伺服器的回應,或在營運中於主 DB 上執行長時間的彙總查詢 → 持有的鎖定一直不釋放,待清理的舊版本資料持續累積 → 使用那些資料列的功能逾時,存檔與查詢在幾個小時內全面變慢 - 症狀:輸入延遲, 吃指令/回檔/因素:延遲, 停滯 - 誰會遇到:只有特定功能, 整個伺服器/何時:開越久越嚴重, 偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:不在 transaction 中等待網路呼叫或使用者輸入,彙總查詢改在複本上執行。 - 基礎設施團隊要做的事:對長時間未結束的 transaction 發出警示並強制終止、提供彙總用的複本、監看 undo log 與失效資料列的增加。 - 圖表上:緩慢爬升(undo log 長度(History list length)、失效資料列數) - 查看位置:MySQL 用 INFORMATION_SCHEMA.INNODB_TRX 的 trx_started 找出最舊的 transaction,並看 SHOW ENGINE INNODB STATUS 的 TRANSACTIONS 區段中的 History list length(尚未清理的 undo log 量)。PostgreSQL 看 pg_stat_activity 的 xact_start、state 為 idle in transaction 的 session,以及 pg_stat_user_tables 的 n_dead_tup - 符合的跡象:存在已開啟幾分鐘~幾小時的 transaction,期間 History list length 或 n_dead_tup 持續上升,結束該 transaction 後隨著清理(purge、VACUUM)執行而下降 - 不符合的跡象:沒有長時間的 transaction 卻全面變慢時,是檢查點(db-checkpoint)或磁碟的問題 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:DB 會保留舊版本,讓讀取端能看到修改前的樣子(MVCC)。這些紀錄要等最舊的 transaction 結束後才能刪除,所以一個 transaction 開著好幾個小時的話,MySQL 會累積 undo log,PostgreSQL 會累積 VACUUM 清不掉的失效資料列(dead tuple)。SQL Server 則可能因 transaction log 無法縮減而塞滿磁碟。 - 出處: - [InnoDB Multi-Versioning](https://dev.mysql.com/doc/refman/8.4/en/innodb-multi-versioning.html) · MySQL · 只要還有能看到舊版本的 transaction,update undo log 就無法丟棄,rollback segment 會越來越大;建議即使是唯讀的 transaction 也要經常 commit - [Routine Vacuuming (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/routine-vacuuming.html) · PostgreSQL · 舊的資料列版本在其他 transaction 還看得到時無法刪除,長時間未結束的 transaction 必須結束或終止其 session - [Client Connection Defaults (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/runtime-config-client.html) · PostgreSQL · idle_in_transaction_session_timeout:中斷開著 transaction 卻閒置的 session,避免長時間持有鎖定 - [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 · 長時間執行中的活躍 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:transaction 的開始時間 - [Purge Configuration](https://dev.mysql.com/doc/refman/8.4/en/innodb-purge-configuration.html) · MySQL · purge 負責清理已 commit 的 transaction 的 undo log 清單(history list),積壓量顯示在 SHOW ENGINE INNODB STATUS 的 TRANSACTIONS 區段的 History list length - [The Cumulative Statistics System (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/monitoring-stats.html) · PostgreSQL · pg_stat_activity 的 xact_start(transaction 開始時間)與 state(idle in transaction)、pg_stat_user_tables 的 n_dead_tup(失效資料列的估計值) #### db-redis-block · Redis 慢指令 · Redis blocking commands (single-threaded) Redis 一次只處理一個指令,因此一個慢指令就會擋住後面所有的請求。 - 為什麼 → 於是 → 畫面上:營運中用 KEYS 搜尋全部 key,或一次讀取、刪除含數百萬個元素的排行榜或清單 → 在該指令結束前,其他所有請求都要等待(數十 ms~數秒) → 使用 session、排行榜、快取的功能同時頓一下,登入變慢 - 症狀:定格, 輸入延遲, 連不上/無限讀取/因素:停滯, 延遲 - 誰會遇到:整個伺服器, 只有特定功能/何時:偶爾隨機發生, 固定週期 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:用 SCAN 取代 KEYS、拆分大型 key、刪除改用 UNLINK(背景刪除)、分散集中在同一秒的過期時間。 - 基礎設施團隊要做的事:監看慢指令紀錄(SLOWLOG)、在正式環境伺服器上封鎖 KEYS 等危險指令、定期檢查大型 key、關閉 THP 並保留 fork 所需的記憶體餘裕、RDB 與 AOF 持久化改在複本上進行。 - 數值參考:一般指令不到 1ms。一次處理數百萬個元素時,有時要花數百 ms 到數秒。 - 圖表上:偶爾隨機飆高(Redis 回應延遲、慢指令數) - 查看位置:用 SLOWLOG GET 看超過 slowlog-log-slower-than 的指令;用 CONFIG SET latency-monitor-threshold 開啟延遲監控(預設關閉)後,以 LATENCY LATEST、LATENCY DOCTOR 看 fork、expire-cycle 等各事件的延遲。也用 INFO 的 latest_fork_usec 與 redis-cli --bigkeys 確認 fork 時間與大型 key - 符合的跡象:停住的時間點,SLOWLOG 中留有 KEYS 或一次處理整個大型 key 的指令,或 LATENCY 在同一時間記錄到數十 ms 以上的 fork、expire-cycle 事件 - 不符合的跡象:SLOWLOG、LATENCY 都是空的,只有從遊戲伺服器端看很慢時,是網路或遊戲伺服器內部的等待(SLOWLOG 只量指令執行時間,不含與用戶端往來的時間) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:為了建立持久化檔案(RDB 快照)或重寫 AOF 而複製處理程序(fork)的瞬間也會停住。在現今的伺服器上,每 1GB 記憶體約需 10ms,30GB 就約 300ms。開啟大分頁(THP)時,fork 之後每次寫入都要整個複製一個大分頁(copy-on-write),暫停時間與記憶體用量都會大增,所以通常會關閉 THP,並保留充足的記憶體餘裕。同一秒內過期的 key 非常多時,Redis 也會為了刪除它們而短暫停住。 - 出處: - [Diagnosing latency issues](https://redis.io/docs/latest/operate/oss_and_stack/management/optimization/latency/) · Redis · 由單一執行緒依序處理請求,慢指令會擋住後面所有請求;用 SCAN 取代 KEYS;fork 在實體伺服器與新型 VM 上實測每 1GB 約 9~13ms;THP 會因 fork 後的複製造成延遲與記憶體暴增;同一秒大量過期時會停住 - [KEYS](https://redis.io/docs/latest/commands/keys/) · Redis · 在正式環境中務必極度謹慎使用,在大型 DB 上可能嚴重拖垮效能(入門級筆電上 100 萬個 key 需 40ms) - [UNLINK](https://redis.io/docs/latest/commands/unlink/) · Redis · 立即移除 key、在其他執行緒回收記憶體的非同步刪除 - [SLOWLOG](https://redis.io/docs/latest/commands/slowlog/) · Redis · 記錄超過 slowlog-log-slower-than 之指令的慢指令 log,執行時間不含與用戶端往來的 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 找出大型 key #### db-plan-flip · 執行計畫改變造成的查詢延遲 · Query plan regression (stats, parameter sniffing) 程式碼沒變,DB 卻改變了處理同一個查詢的方式(執行計畫)時,昨天只要 2ms 的查詢,今天會變成數百 ms。 - 為什麼 → 於是 → 畫面上:統計資訊自動更新、DB 重新啟動或資料分布改變,使 DB 重新建立執行計畫 → 選中了不走索引的計畫,同一個查詢慢了數十~數百倍,連線被占住 → 明明沒有部署,特定功能的載入卻突然變慢,連其他請求也要等待 - 症狀:輸入延遲, 連不上/無限讀取/因素:延遲, 停滯 - 誰會遇到:只有特定功能, 整個伺服器/何時:偶爾隨機發生 - 主要負責:基礎設施團隊(DB 基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:結果筆數會因參數值而差異很大的查詢,拆開使用或評估加上計畫提示(hint);設計確定會走索引的查詢。 - 基礎設施團隊要做的事:監看慢查詢與執行計畫的歷史紀錄、固定好的計畫(SQL Server 的查詢存放區等)、管理統計資訊的更新時間。 - 圖表上:從某個時間點起階梯式上升(各查詢的平均執行時間) - 查看位置:定期收集相同形式查詢的平均時間,觀察變化趨勢。MySQL 為 events_statements_summary_by_digest 的 AVG_TIMER_WAIT,PostgreSQL 為 pg_stat_statements 的 mean_exec_time(12 以下為 mean_time)。變慢前後的執行計畫用 EXPLAIN 或 PostgreSQL auto_explain 比較,SQL Server 則用查詢存放區的「迴歸查詢」(Regressed Queries)畫面比較 - 符合的跡象:在沒有部署的時間點,某個查詢的平均時間階梯式上升數十倍,該時間點與統計資訊更新或 DB 重新啟動重疊,且執行計畫已經改變 - 不符合的跡象:執行計畫沒變卻變慢時,是資料量增加、鎖定等待(db-hot-row)或磁碟的問題 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:SQL Server 會重複使用依第一次傳入的值所建立的計畫(參數探查,parameter sniffing)。依只有幾個道具的新角色建立的計畫,用在擁有數萬個道具的老角色上時會大幅變慢,反過來的情況也很常見。重新啟動清掉計畫後會暫時恢復正常,之後又可能再度變差。 - 出處: - [Query Processing Architecture Guide](https://learn.microsoft.com/en-us/sql/relational-databases/query-processing-architecture-guide) · Microsoft SQL Server · 參數探查(parameter sniffing):依編譯或重新編譯時傳入的參數值建立執行計畫 - [Parameter Sensitive Plan Optimization](https://learn.microsoft.com/en-us/sql/relational-databases/performance/parameter-sensitive-plan-optimization) · Microsoft SQL Server · 資料分布不均時,快取中的單一計畫無法適用所有參數值 - [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 · 統計資訊、schema、索引的變化會改變計畫,而計畫快取只保留最新的計畫;可用查詢存放區的強制計畫固定好的計畫,並在「迴歸查詢」(Regressed Queries)畫面比較變慢的查詢與其計畫 - [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 · 把耗時超過 auto_explain.log_min_duration 的查詢的執行計畫寫入 log #### db-ddl-lock · 營運中 schema 變更(DDL)的鎖定 · Schema change lock (DDL / metadata lock) 在服務中替資料表新增欄位或索引時,可能因為一個只需要短暫持有的鎖定,讓所有使用該資料表的請求都在等待。 - 為什麼 → 於是 → 畫面上:以 hotfix 替營運中的資料表新增欄位或索引 → schema 變更在等待先前開啟的長 transaction,後面進來的所有請求又在等待這個 schema 變更 → 使用該資料表的功能(背包、信件等)整個停住並逾時 - 症狀:輸入延遲, 吃指令/回檔, 連不上/無限讀取/因素:停滯 - 誰會遇到:只有特定功能, 整個伺服器/何時:偶爾隨機發生, 剛登入/維護剛結束 - 主要負責:基礎設施團隊(DB 基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:含 schema 變更的 hotfix 要與 DB 基礎設施協調時程;先部署在沒有新欄位時也能運作的程式碼。 - 基礎設施團隊要做的事:把鎖定等待上限設短、失敗就重試,在沒有長 transaction 時執行,使用線上 schema 變更工具,大型資料表留到維護時間處理。 - 圖表上:從某個時間點起階梯式上升(等待鎖定的 session 數、該資料表的查詢延遲) - 查看位置:MySQL 統計 SHOW PROCESSLIST 中 State 為 Waiting for table metadata lock 的 session 數,並用 sys.schema_table_lock_waits 找出造成阻擋的 session(blocking_pid)。PostgreSQL 看 pg_locks 中 granted 為 false 的請求與 AccessExclusiveLock,並用 pg_blocking_pids() 找出造成阻擋的 session - 符合的跡象:從開始 schema 變更的時間點起,所有使用該資料表的查詢都卡在鎖定等待,最前面是尚未結束的 transaction 或 schema 變更陳述式 - 不符合的跡象:等待只集中在特定資料列、同一張資料表的其他資料列處理正常時,是熱點資料列(db-hot-row) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:MySQL 變更 schema 時會短暫取得中繼資料鎖定(metadata lock),PostgreSQL 則會短暫取得最強的資料表鎖定。即使變更本身瞬間就完成,只要前面有一個尚未結束的 transaction,後面所有請求都得等待。 - 出處: - [Online DDL Performance and Concurrency](https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-performance.html) · MySQL · online DDL 在收尾時也需要短暫的排他中繼資料鎖定;有長 transaction 時會等待,而這個等待中的鎖定請求會擋住後面所有的 transaction - [Server System Variables](https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html) · MySQL · lock_wait_timeout:中繼資料鎖定的等待上限,預設值 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:等待中繼資料鎖定的執行緒狀態 - [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 · 等待中繼資料鎖定的 session(waiting_query)與造成阻擋的 session(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():阻擋指定 session 取得鎖定的 session 清單 ### L13 伺服器架構與維運(13 個原因) #### in-gateway · 經由閘道/proxy · Gateway / proxy hop 在用戶端與遊戲伺服器之間放一台中介伺服器時,每經過一次就多一段處理時間,而且這台伺服器會成為單點故障。 - 為什麼 → 於是 → 畫面上:用戶端 ↔ 閘道 ↔ 遊戲伺服器的架構 → 多了中介伺服器的處理與等待時間,過載時影響所有人 → 整體 ping 上升;閘道故障時,經過它的玩家全部斷線 - 症狀:輸入延遲, 斷線/因素:延遲, 停滯 - 誰會遇到:整個伺服器/何時:人潮湧入時, 一直都有 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:做成閘道可以擴充到多台的架構,並讓閘道掛掉時改連另一台閘道後角色能直接接續(session 重新連線)。用戶端:與閘道的連線中斷時自動重新連線。 - 基礎設施團隊要做的事:閘道水平擴展(增加台數),監控每台閘道的 CPU、連線數與處理延遲。 - 數值參考:因為在同一個資料中心內,平常每經過一次不到 1ms。閘道過載時會增加到數十~數百 ms。 - 圖表上:隨人數/負載上升(閘道處理延遲、閘道的 CPU 與連線數) - 查看位置:閘道的 CPU、連線數與閘道 socket 的 Recv-Q(ss、netstat),以及經過閘道前後的延遲差。若是經過 service mesh 的 HTTP、gRPC 呼叫,將 Istio 標準指標 istio_request_duration_milliseconds 分成發送端(reporter=source)與接收端(reporter=destination)比較 - 符合的跡象:遊戲伺服器的處理時間不變,只有閘道區段的延遲增加,且同一時間閘道 CPU 飽和或 Recv-Q 堆積 - 不符合的跡象:不經過閘道的路徑(直接連線、其他閘道)也一樣慢時,是線路或遊戲伺服器端的問題 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:使用 service mesh(Istio 等)時,每台伺服器旁邊附掛的 sidecar proxy(Envoy)也會多出一層。服務之間的請求會依序經過發送端的 sidecar 與接收端的 sidecar,proxy 上加的功能(例如收集 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 的用戶端連到 4 台具有公用位址的入口伺服器(REP)之一,再與後方的模擬伺服器(hub)通訊 - [Designs, Lessons and Advice from Building Large Distributed Systems](https://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf) · Google · LADIS 2009 主題演講(Jeff Dean)。同一資料中心內往返約 0.5ms - [Site Reliability Engineering, Chapter 22: Addressing Cascading Failures](https://sre.google/sre-book/addressing-cascading-failures/) · Google · 過載使佇列變長時,等待時間會變成處理時間的好幾倍(處理 100ms、佇列為執行緒數的 10 倍時為 1.1 秒) - [Performance and Scalability](https://istio.io/latest/docs/ops/deployment/performance-and-scalability/) · Istio · sidecar 模式下請求依序經過發送端與接收端的 sidecar proxy;功能加得越多,proxy 內的處理路徑越長,收集遙測資料(telemetry)也會拉長下一個請求的等待時間 - [What is Envoy](https://www.envoyproxy.io/docs/envoy/latest/intro/what_is_envoy) · Envoy · Envoy 是在每台應用程式伺服器旁邊獨立執行的處理程序,應用程式透過 localhost 上的 Envoy 收發資料 - [Istio Standard Metrics](https://istio.io/latest/docs/reference/config/metrics/) · Istio · istio_request_duration_milliseconds(HTTP、gRPC 請求處理時間分布),以 reporter 標籤區分發送端(source)與接收端(destination)的 proxy - [netstat(8) — Linux manual page](https://man7.org/linux/man-pages/man8/netstat.8.html) · net-tools · Recv-Q:已連線的 socket 中,使用者程式尚未取走的位元組數 #### in-zone-transfer · 切換 zone(跨伺服器轉移) · Zone / server handoff 進入其他地圖或副本時,把角色資料交給另一台伺服器的過程中會產生延遲與失敗。 - 為什麼 → 於是 → 畫面上:進入副本、移動到其他大陸,負責的伺服器因此改變 → 儲存 → 傳遞 → 載入;目標伺服器擁擠或沒有空的副本實例時要等待 → 載入很久、進場失敗、移動途中斷線 - 症狀:連不上/無限讀取, 定格, 斷線, 拉回/因素:延遲, 停滯 - 誰會遇到:只有我, 特定地點/頻道/何時:移動中/切換地圖時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:減少轉移的資料量、預先保留目標伺服器、失敗時回到原位。 - 基礎設施團隊要做的事:監控副本、zone 伺服器的空實例餘量,尖峰前先備好台數。 - 圖表上:隨人數/負載上升(切換 zone 耗時、失敗次數) - 查看位置:伺服器記錄的各轉移階段耗時(儲存、傳遞、載入)與失敗原因,以及目標伺服器的人數與空實例數 - 符合的跡象:回報載入很久、進場失敗的時間點,轉移時間變長或失敗集中,且目標伺服器擁擠或空實例已用完 - 不符合的跡象:轉移很快完成、抵達後才停住時,是進入密集區域時的生成暴增或用戶端載入的問題 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:沒有讀取畫面、連成一片的無縫世界,跨越伺服器邊界時負責的伺服器同樣會改變。在邊界附近可能會頓一下,或出現拉回。 - 出處: - [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 · 把無讀取畫面的世界切成格狀,由多台伺服器(hub)分擔,玩家移動時把狀態從一個 hub 交給另一個 hub;session 型模式則從共用池取用空閒的伺服器 #### in-cascade · 連鎖故障 · Cascading failure 一個服務變慢時,呼叫它的伺服器會卡在等待回應,連不相關的功能也跟著停擺。 - 為什麼 → 於是 → 畫面上:DB、認證等某一個服務變慢 → 呼叫端伺服器的執行緒與連線卡在等待回應,失敗請求的重試又加重負載 → 看似無關的功能也全部變慢或停擺 - 症狀:定格, 輸入延遲, 連不上/無限讀取/因素:停滯 - 誰會遇到:整個伺服器/何時:人潮湧入時, 偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(網路基礎設施) - 遊戲開發團隊要做的事:所有呼叫都設逾時、斷路器、依功能隔離(bulkhead),重試要逐步拉長間隔並限制次數,健康檢查的回應與忙碌的工作分開處理。 - 基礎設施團隊要做的事:負載平衡器的健康檢查在失敗次數與間隔上保留餘裕,避免把只是暫時變慢的伺服器立刻移出;限制同時移出的伺服器數量。 - 圖表上:碰到上限後持平(各服務的回應時間與錯誤率、執行緒與連線使用數) - 查看位置:把各服務的回應時間、錯誤率、重試次數對齊時間軸放在同一個畫面,找出最先變慢的地方。若在負載平衡器後方,看目標回應時間(AWS ALB 為 TargetResponseTime)、目標的 5xx 數(HTTPCode_Target_5XX_Count)、判定異常而移出的目標數(UnHealthyHostCount) - 符合的跡象:某個服務的延遲先上升,接著呼叫該服務一方的執行緒、連線使用數貼齊上限,錯誤擴散到其他服務,重試次數與移出的目標數一起增加 - 不符合的跡象:多個服務在同一瞬間一起變慢時,先查共用資源(DB、網路、主機)的故障 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:健康檢查(確認伺服器是否存活的檢查)也會讓連鎖擴大。忙碌的伺服器太晚回應檢查時,負載平衡器會把其實正常的伺服器移出,它的流量湧向剩下的伺服器,下一台伺服器也跟著變慢。 - 實際案例: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 · 過載的伺服器健康檢查失敗被移出後,負載集中到剩下的伺服器,重試又放大負載;建議限制重試次數、使用隨機化的指數退避並設定截止時間(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(請求離開負載平衡器到目標開始回應所花的時間)、HTTPCode_Target_5XX_Count(目標產生的 5xx 數)、UnHealthyHostCount(異常目標數) #### in-subservice · 附屬伺服器故障 · Auxiliary service outage 聊天、隊伍、拍賣場這類與遊戲伺服器分開運作的伺服器故障時,只有那個功能無法運作。 - 為什麼 → 於是 → 畫面上:功能專用伺服器變慢或掛掉 → 只有該功能的請求沒有回應 → 無法聊天、組隊邀請沒反應、交易所無限讀取(戰鬥正常) - 症狀:吃指令/回檔, 連不上/無限讀取/因素:停滯, 遺失 - 誰會遇到:只有特定功能/何時:偶爾隨機發生, 人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:設計成即使失敗遊戲也能繼續、顯示各功能的狀態、不把多個功能集中在一台中央伺服器。 - 基礎設施團隊要做的事:每台附屬伺服器的健康檢查與警示、備援與自動重新啟動。 - 圖表上:連線同時大量中斷(各功能的請求成功率、附屬伺服器的連線數與健康檢查) - 查看位置:聊天、隊伍、拍賣場等各附屬伺服器的健康檢查、處理程序狀態與連線數,各功能的請求成功率與回應時間。若在負載平衡器後方,看目標群組的 UnHealthyHostCount - 符合的跡象:只有負責被回報功能的伺服器出現健康檢查失敗或連線數驟降,遊戲伺服器的 tick 與戰鬥正常 - 不符合的跡象:多個功能同時停擺時,是一起轉送這些功能的中央伺服器或連鎖故障的問題 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:如果隊伍、公會、密語、跨伺服器移動全都由一台中央伺服器(world/manager 伺服器)轉送,這台伺服器一變慢,多個功能就會同時停擺。 - 出處: - [Bulkhead Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/bulkhead) · 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。設計成依賴對象故障時,核心功能仍能用稍舊的資料或替代資料繼續運作 - [CloudWatch metrics for your Network Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-cloudwatch-metrics.html) · AWS · UnHealthyHostCount:健康檢查判定為異常的目標數 #### in-deploy · 部署與重新啟動 · Deploy / rolling restart 為了更新而重新啟動伺服器時,如果不先把連線移走,原本在那台伺服器上的人就會斷線,關閉前的存檔與重新連線也會同時湧入。 - 為什麼 → 於是 → 畫面上:部署 hotfix,依序重新啟動伺服器 → 沒有把連線移到其他伺服器就關閉,那台伺服器上所有玩家的存檔同時湧向 DB → 沒有公告就斷線、重新連線暴增 - 症狀:斷線, 連不上/無限讀取, 輸入延遲/因素:停滯 - 誰會遇到:整個伺服器, 特定地點/頻道/何時:偶爾隨機發生, 剛登入/維護剛結束 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:drain 功能(只擋新連線,等現有的人離開),把角色移到其他伺服器,關閉前分批存檔,重新啟動後完成快取載入與 JIT 暖機再回報準備完成,熱重載(hot reload)在另一個執行緒先讀好,再於 tick 之間一次替換。 - 基礎設施團隊要做的事:部署工具逐台等 drain 完成後再重新啟動,重新啟動的伺服器確認準備完成(暖機結束)後才接流量,公告部署時間。 - 數值參考:一台伺服器有 5,000 人時,關閉前幾秒內會有 5,000 筆存檔湧向 DB。 - 圖表上:連線同時大量中斷(各伺服器連線數、DB 寫入次數) - 查看位置:把部署工具的作業紀錄(各伺服器重新啟動時間)以垂直線(註記)疊在連線數、斷線次數、DB 寫入、登入請求的圖表上看 - 符合的跡象:各伺服器的連線數在重新啟動時間點逐台依序驟降,驟降前 DB 寫入往上衝,驟降後登入請求往上衝 - 不符合的跡象:斷線時間點與部署、重新啟動紀錄不重疊時,是伺服器當機或網路設備的問題 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:剛重新啟動後的幾分鐘也會比較慢。快取是空的,DB 查詢集中湧入;Java、C# 伺服器在執行中最佳化程式碼的過程(JIT 暖機)尚未結束,同樣的工作要花更多時間。不關伺服器、直接重新讀取腳本與資料表的方式(熱重載)也一樣,讀取期間 tick 會停住,造成短暫定格。 - 出處: - [Site Reliability Engineering, Chapter 20: Load Balancing in the Datacenter](https://sre.google/sre-book/load-balancing-datacenter/) · Google · 收到 SIGTERM 的伺服器進入 lame duck 狀態,把新請求轉給其他伺服器,只完成進行中的請求;剛重新啟動的幾分鐘尚未完成 JIT 最佳化、耗用更多資源,所以暖機後才接流量 - [Liveness, Readiness, and Startup Probes](https://kubernetes.io/docs/concepts/workloads/pods/probes/) · Kubernetes · 用 readiness 檢查,在建立連線、載入檔案、快取暖機完成之前不送流量 - [Edit target group attributes for your Network Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/edit-target-group-attributes.html) · AWS · 取消註冊目標後不再送新連線,現有連線則 drain(預設 300 秒) #### in-autoscale · 自動擴展延遲 · Autoscaling lag 人潮湧入時會自動增加伺服器,但準備要花幾分鐘,這段期間既有的伺服器處於過載。 - 為什麼 → 於是 → 畫面上:活動開始,連線急增 → 新伺服器從啟動到準備好需要數分鐘 → 活動剛開始的幾分鐘內出現慢動作、連不上 - 症狀:慢動作, 連不上/無限讀取/因素:停滯 - 誰會遇到:整個伺服器/何時:人潮湧入時, 剛登入/維護剛結束 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:分散頻道(已在擁擠頻道裡的人無法移到新伺服器),縮短新伺服器的啟動與資料載入時間。 - 基礎設施團隊要做的事:活動前預先擴展、準備已暖機的備用伺服器,縮減時等留下的人離開後再關閉。 - 數值參考:偵測到負載要 1~數分鐘(因為指標是取數分鐘的平均來看),啟動新伺服器、讀取遊戲資料、填滿快取又要數分鐘。 - 圖表上:剛開服或維護結束後暴增(執行個體數、CPU 使用率、連線等待) - 查看位置:把自動擴展的活動紀錄(決定擴展的時間、新執行個體開始服務的時間)疊在 CPU 使用率、連線數圖表上看。AWS 看 Auto Scaling 群組指標(要先啟用才看得到)GroupDesiredCapacity(目標台數)、GroupPendingInstances(準備中)、GroupInServiceInstances(服務中) - 符合的跡象:連線急增後幾分鐘內只有目標台數與準備中的執行個體增加,既有伺服器的 CPU 貼齊上限,直到服務中的執行個體增加的時間點才緩解 - 不符合的跡象:新執行個體加入後仍然很慢時,是伺服器台數以外的原因(DB 等共用資源、連鎖故障) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:自動擴展主要用在登入、閘道、副本這類只要讓新伺服器接新玩家就好的地方。縮減時也會出問題。清晨人少時縮減伺服器,如果沒等留下的人離開就關機,這些人會斷線。 - 實際案例: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 分鐘間隔(啟用詳細監控為 1 分鐘),要快速反應建議使用 1 分鐘以下間隔的指標 - [Amazon CloudWatch metrics for Amazon EC2 Auto Scaling](https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-metrics.html) · AWS · 群組指標要啟用後才會以 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 · 配合可預測的負載變化,在排定的時間預先增減容量 - [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 · 開機很久的應用程式,用預先初始化的執行個體池(warm pool)縮短擴展延遲 #### in-monitoring · log 與監控過載 · Logging / monitoring overhead 發生故障時 log 會暴增,以同步方式送出 log 的伺服器會因為 log 變得更慢。 - 為什麼 → 於是 → 畫面上:發生錯誤,log 與指標的傳送量暴增 → log 收集器消化不及,同步傳送的伺服器只能等待 → 故障時的卡頓、定格因為 log 變得更嚴重 - 症狀:卡頓, 定格/因素:停滯 - 誰會遇到:整個伺服器/何時:人潮湧入時, 偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:非同步傳送、取樣、緩衝區滿了就丟棄、相同的錯誤 log 合併後再送。 - 基礎設施團隊要做的事:以故障時的暴增量為基準準備 log 收集器容量,收集器積壓時發出警示。 - 圖表上:偶爾隨機飆高(log 傳送量、log 收集器佇列) - 查看位置:把伺服器每秒的 log 行數與位元組數、log 收集 agent 的佇列與丟棄數,和 tick 時間一起看。有執行緒停住時,用 bcc offcputime -p 看是否在等寫入或傳送 log - 符合的跡象:tick 飆高的時間點 log 量往上衝到平常的數十倍,遊戲執行緒的等待時間集中在寫入、傳送 log 的呼叫堆疊 - 不符合的跡象:log 量與平常相同,或遊戲執行緒沒有在 log 那邊等待時,log 暴增只是故障的結果,要另外找最先出錯的原因 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Logging in C#](https://learn.microsoft.com/en-us/dotnet/core/extensions/logging) · Microsoft · .NET 的 log 方法是同步的,儲存位置很慢時建議不要直接寫入,先寫到快速的儲存位置再搬移 - [Asynchronous loggers](https://logging.apache.org/log4j/2.x/manual/async.html) · Apache Software Foundation · 非同步 log 用佇列吸收短暫的暴增,但輸出持續很慢時佇列會滿,速度降到最慢的輸出速度,或依政策丟棄 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)依呼叫堆疊加總,-p 指定處理程序 #### in-clock-skew · 伺服器之間的時鐘差異 · Clock skew between servers 各伺服器的時鐘各差一點時,冷卻時間、buff、活動開始的判定就會在伺服器之間對不上。 - 為什麼 → 於是 → 畫面上:時間同步停止的伺服器,時鐘與其他伺服器相差數百 ms~數秒 → 在伺服器之間傳遞 buff 結束時間這類絕對時間,判定就會對不上 → 一移動 buff 就消失,或冷卻重新開始計算 - 症狀:吃指令/回檔/因素:延遲 - 誰會遇到:只有我/何時:移動中/切換地圖時 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:伺服器之間以剩餘時間取代絕對時間來傳遞。 - 基礎設施團隊要做的事:監控時間同步(NTP、chrony),伺服器之間時鐘差異過大時發出警示。 - 數值參考:時間同步(NTP、chrony)正常時,同一資料中心的伺服器之間通常在數 ms 以內。同步停止,或虛擬機長時間暫停後恢復時,會拉大到數百 ms~數秒。 - 圖表上:緩慢爬升(各伺服器的時鐘偏移(offset)) - 查看位置:收集各伺服器 chronyc tracking 的 System time(系統時鐘與 NTP 時鐘的差)、Last offset 與 Ref time(最後一次套用時間來源測量值的時間)來比較 - 符合的跡象:問題伺服器的偏移比其他伺服器大了數百 ms 以上,或 Ref time 停在很久以前,且判定錯亂只發生在進出那台伺服器的移動 - 不符合的跡象:所有伺服器的偏移都在數 ms 內時,是遊戲端的時間計算或用戶端時鐘同步誤差的問題 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:單一伺服器的時鐘一下子往前或往後跳的情況,在伺服器 OS 層的「系統時鐘跳動(NTP step)」說明。 - 出處: - [RFC 5905: Network Time Protocol Version 4: Protocol and Algorithms Specification](https://www.rfc-editor.org/rfc/rfc5905) · IETF · 快速 LAN 上的 NTP 用戶端通常可對準到數百 µs 以內 - [chrony – Frequently Asked Questions](https://chrony-project.org/faq.html) · chrony · 一般電腦時鐘的漂移(drift)未滿 100ppm,但虛擬機可能更大;暫停後恢復的虛擬機時間會偏掉,可能需要 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 · chronyc tracking 的 System time(NTP 時鐘與系統時鐘的差)、Last offset(最後一次校正時估計的偏移)、Ref time(套用時間來源最後測量值的時間) #### in-bots · 巨集/機器人過多 · Bots and macros 機器人送出請求的頻率遠高於真人,會吃掉伺服器的處理量。 - 為什麼 → 於是 → 畫面上:大量連線的機器人不停重複打怪、移動、交易 → 伺服器處理量與 DB 負載增加 → 特定練功區或整個伺服器變慢(慢動作、輸入延遲) - 症狀:慢動作, 輸入延遲/因素:停滯 - 誰會遇到:整個伺服器, 特定地點/頻道/何時:一直都有, 晚間尖峰時段 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(網路基礎設施) - 遊戲開發團隊要做的事:偵測機器人,依帳號、角色限制請求頻率。 - 基礎設施團隊要做的事:依 IP 限制連線與請求頻率(網咖、行動網路是多人共用一個 IP,要放寬),用防火牆、WAF 封鎖機器人的 IP 區段。 - 圖表上:只有部分偏高(各帳號/IP 的每秒請求數) - 查看位置:用遊戲伺服器 log 查看各帳號、角色每秒請求數的分布與前幾名清單。沒有程式碼指標時,看防火牆、WAF 的各 IP 請求數 - 符合的跡象:少數帳號、IP 以真人不可能達到的頻率不停送出請求,限制它們之後伺服器負載明顯下降 - 不符合的跡象:請求平均分散在各帳號時,是正常人數增加的問題(超出 tick 預算、自動擴展延遲) - 確認方式:需要遊戲伺服器/用戶端的 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 等基準計算請求數,在設定的時間窗內過多時進行速率限制 - [RFC 6269: Issues with IP Address Sharing](https://www.rfc-editor.org/rfc/rfc6269) · IETF · 多個用戶共用一個 IP 時,依 IP 封鎖或限制會連其他使用者一起擋掉 #### in-external · 依賴外部服務 · External dependencies (auth, billing, platform) 平台登入、付款、身分驗證這類外部服務變慢或停擺時,會卡在那個步驟。 - 為什麼 → 於是 → 畫面上:外部認證、付款服務故障或延遲 → 在該步驟等待回應 → 無法登入、付款失敗。已在遊戲中的人正常 - 症狀:連不上/無限讀取, 吃指令/回檔/因素:停滯 - 誰會遇到:整個伺服器, 只有特定功能/何時:剛登入/維護剛結束, 做特定動作時 - 主要負責:外部(外部)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:外部呼叫設逾時並提供清楚的說明,快取認證結果,建立付款重試與補償流程。 - 外部要做的事:向認證、付款、平台業者確認故障並要求修復,告知玩家是外部服務故障。 - 圖表上:從某個時間點起階梯式上升(外部呼叫的回應時間與錯誤率、登入成功數) - 查看位置:平台登入、付款、身分驗證等各外部呼叫的回應時間、錯誤率、逾時次數,以及業者的狀態頁面 - 符合的跡象:從登入、付款失敗集中的時間點起,只有特定外部呼叫的錯誤與逾時升高一階並維持,業者狀態頁面上同一時間有故障 - 不符合的跡象:外部呼叫正常但登入卡住時,是登入伺服器本身(執行緒池耗盡、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 只在保證冪等性時才重試 - [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 · 配對與區域分配錯誤 · Wrong region assignment (matchmaking / GeoDNS) 沒被分到近的區域、卻被分到遠方區域的伺服器時,即使線路正常,也只有那位玩家的 ping 一直偏高。 - 為什麼 → 於是 → 畫面上:GeoIP 資料錯誤、VPN、以隊友平均 ping 分配整個隊伍、人數不足時擴大到遠方區域的規則、依 DNS 解析器(resolver)位置分配 → 明明有近的區域,卻連到海外區域的伺服器 → 在多個區域設有伺服器的遊戲中,只有我(或只有我們隊伍)ping 一直偏高,出現輸入延遲、拉回、技能被吃 - 症狀:輸入延遲, 拉回, 吃指令/回檔/因素:延遲 - 誰會遇到:只有我, 特定地區/電信業者/何時:一直都有, 剛登入/維護剛結束 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發), 基礎設施團隊(網路基礎設施), 外部(外部) - 遊戲開發團隊要做的事:伺服器:以用戶端實測的各區域 ping 取代 GeoIP 來分配,擴大到遠方區域的規則要設 ping 上限,隊伍除了平均值也要看最高的隊友 ping,把分配的區域與當時的 ping 記錄到 log。用戶端:用 UDP 測量各區域 ping 並隨配對請求一起送出,在畫面上顯示連線的區域與 ping,提供自行選擇區域的選項。 - 基礎設施團隊要做的事:若用 DNS 選擇區域,確認權威 DNS 是否支援 EDNS Client Subnet(玩家使用的解析器不傳送時,會依解析器位置分配),定期更新 GeoIP 資料庫,在各區域伺服器的連線 log 加上 GeoIP 國家與 ASN,找出被導向遠方區域的國家與電信業者。 - 外部要做的事:請玩家關掉 VPN、遊戲加速器後重新連線看看,請使用公司或海外 DNS 的玩家改用電信業者的 DNS 試試,向 GeoIP 業者申請更正錯誤的位置。 - 數值參考:首爾玩家沒被分到東京、而被分到美國西部區域時,ping 會從約 30ms 增加到約 130ms。GeoIP 以國家為單位約 99.8% 正確,但以城市為單位,即使在美國,落在 50km 內的比例也只有約 66%;使用 VPN 時,查到的會是 VPN 伺服器的位置。 - 圖表上:只有部分偏高(各玩家的 RTT(ping)、分配到的區域分布) - 查看位置:在各區域伺服器的連線紀錄(負載平衡器存取 log、VPC Flow Logs)中的用戶端 IP 加上 GeoIP 國家與 ASN,依國家、電信業者統計連到哪個區域。若是單一玩家,比較該玩家實際連線的區域,以及到近的區域實測的 ping(由玩家測量,或從該區域伺服器對玩家 IP 執行 mtr) - 符合的跡象:RTT 高的玩家或國家沒連到近的區域、而是連到遠方區域,對近的區域實測的 ping 很低 - 不符合的跡象:已正確分配到近的區域但 ping 仍高時,是繞遠路的路由或該玩家的線路、Wi-Fi 問題 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:用 DNS 選擇區域的方式(依地理位置、延遲的 DNS),推測位置時看的是玩家所用 DNS 解析器的位址。解析器若不支援傳遞部分玩家位址的 EDNS Client Subnet,使用公司 DNS 或遠方 DNS 的玩家就會以解析器所在地為準來分配。配對系統也可能以隊友 ping 的平均值判斷整個隊伍,或在等待太久時放寬 ping 標準,分配到遠方區域。AWS GameLift Servers 對隊伍 ping 的預設基準也是平均值,並舉了把 ping 上限從 50ms 放寬到 100ms、200ms 的設定為例。開啟 VPN 的玩家可能同時遇到經過中繼伺服器而增加的延遲(「經由 VPN/遊戲加速器」)與被分配到遠方區域,可以看關掉 VPN 重新連線後分配到的區域是否改變來區分。附近根本沒有區域、只能連到遠方區域的情況,在「傳播延遲(物理距離)」說明。 - 出處: - [RFC 7871: Client Subnet in DNS Queries](https://www.rfc-editor.org/rfc/rfc7871) · IETF · 依位置給出不同回答的 DNS 會以送出查詢的解析器位址推測位置,使用離玩家很遠的集中式解析器時會得到不適當的回答。以 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 · 解析器不支援 edns-client-subnet 時,會以解析器的位址推測玩家位置,依解析器位置回答(地理位置路由與延遲路由皆同) - [Geolocation accuracy](https://support.maxmind.com/knowledge-base/articles/maxmind-geolocation-accuracy) · MaxMind · 國家層級約 99.8%、美國城市層級(50km 內)約 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 · 放到所有玩家平均延遲最低的位置,但延遲極端的玩家也會被放入;把 ping 上限從 50ms 放寬到 100ms、200ms 的政策範例 - [Amazon GameLift Servers UDP ping beacons](https://docs.aws.amazon.com/gameliftservers/latest/developerguide/reference-udp-ping-beacons.html) · AWS · 遊戲用戶端對各託管位置的 UDP 端點測量延遲,用於放置與配對,比 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)29ms、美國西部 124~136ms - [Flow log records](https://docs.aws.amazon.com/vpc/latest/userguide/flow-log-records.html) · AWS · VPC Flow Logs 紀錄的 srcaddr:傳入流量時為送出端的 IP 位址 #### in-cert · TLS 憑證過期/設定錯誤 · TLS certificate expiry / misconfiguration 登入、API、更新伺服器的憑證過期或缺少中繼憑證時,從那一刻起新連線的用戶端 TLS 連線都會失敗。 - 為什麼 → 於是 → 畫面上:憑證超過有效期限、伺服器送出時少了中繼憑證,或玩家裝置的日期時間錯誤 → 用戶端憑證驗證失敗,中斷 TLS 連線 → 在登入、更新階段連不上/無限讀取,只有商城等 HTTPS 功能失敗。已經連線的人大多正常 - 症狀:連不上/無限讀取, 吃指令/回檔/因素:停滯 - 誰會遇到:整個伺服器, 只有特定功能, 只有我/何時:剛登入/維護剛結束, 做特定動作時 - 主要負責:基礎設施團隊(網路基礎設施)/協同:基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:把憑證錯誤記錄成與其他連線失敗區分開的錯誤碼並提示玩家;若是日期錯誤,提示玩家把裝置的日期時間設為自動;使用憑證釘選(pinning)時一併放入備用金鑰,並與基礎設施團隊協調憑證更換時程。 - 基礎設施團隊要做的事:網路:在負載平衡器、CDN 終止 TLS 時,監控受管憑證的自動續約狀態,剩餘天數不足時發出警示(ACM 為 DaysToExpiry),並保留驗證用的 DNS 記錄。伺服器設備/OS:在伺服器終止 TLS 時,自動化續約並在續約後重新載入設定,設定包含中繼憑證的憑證鏈,從外部定期檢查每個登入、API、更新網址的剩餘有效期並發出警示。 - 數值參考:Let’s Encrypt 的憑證效期為 90 天,建議每 60 天續約;AWS Certificate Manager 會在到期前 45 天檢查以 DNS 驗證的憑證並自動續約。自動續約默默失敗時,會剛好在到期時間點讓新連線一起被擋。 - 圖表上:連線同時大量中斷(登入成功數、TLS 交握錯誤數) - 查看位置:用 openssl s_client -connect HOST:443 -showcerts 查看伺服器實際送出的憑證清單,並用 openssl x509 -noout -enddate 確認每張憑證的到期日(notAfter)。若在負載平衡器終止 TLS,看 TLS 協商錯誤數(AWS ALB、NLB 為 ClientTLSNegotiationErrorCount)與登入成功數 - 符合的跡象:到期日已過,或伺服器送出的清單中缺少中繼憑證,且錯誤開始增加的時間點與到期時間或更換憑證的時間重疊 - 不符合的跡象:憑證清單與到期日都正常、只有部分玩家失敗時,查看這些玩家裝置的日期時間,或舊版 OS 的根憑證清單 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:缺少中繼憑證的設定,用電腦瀏覽器開啟時可能看起來正常。瀏覽器會記住從其他網站取得的中繼憑證來補上缺口,但像 Android App 這樣沒有這種記憶的用戶端就會失敗。有效期也在縮短。Let’s Encrypt 預計把預設有效期在 2027 年縮短為 64 天、2028 年縮短為 45 天,固定每 60 天續約的設定遇到 64 天的憑證只剩四天餘裕,遇到 45 天的憑證則會超過到期日。AWS Certificate Manager 也不會自動續約匯入(import)的憑證,刪除驗證用的 DNS 記錄也會導致續約失敗。登入被擋的樣子和「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,路徑驗證會確認鏈中每張憑證在目前時間是否仍在有效期內(驗證端時鐘錯誤就會失敗) - [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 · 預設有效期於 2027 年 2 月縮短為 64 天、2028 年 2 月縮短為 45 天,固定 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 記錄是否存在,並自動續約;無法驗證時在到期前 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 App 會以 SSLHandshakeException 失敗,但電腦瀏覽器可能用已取得的中繼憑證補上而不報錯;用 openssl s_client 確認伺服器送出的憑證鏈 - [Network security configuration](https://developer.android.com/privacy-and-security/security-config) · Android (Google) · 使用憑證釘選時,必須一併放入備用金鑰以因應金鑰更換、CA 變更,否則在 App 更新前連線都會中斷 - [openssl-s_client](https://docs.openssl.org/3.0/man1/openssl-s_client/) · OpenSSL · -showcerts:依伺服器送出的順序原樣顯示憑證清單(未經驗證的憑證鏈) - [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 接聽程式(listener)之間協商失敗的 TLS 交握數 #### in-login-queue · 登入排隊上限與重新連線寬限不足 · Login queue cap / no reconnect grace 上市或維護剛結束時連線湧入,登入排隊碰到上限就拒絕新的排隊,正在排隊的玩家只要短暫斷線就會失去位置,回到隊伍最後面。 - 為什麼 → 於是 → 畫面上:想連線的人比登入伺服器一次能接受的人數多,所以設排隊;排隊太長時,為了保護伺服器會拒絕新的排隊 → 排隊越長等待時間越久,這段期間 Wi-Fi、行動網路只要短暫斷線就會失去排隊位置 → 連不上/無限讀取、排隊中出現錯誤並關閉遊戲、又要從最後面重新排隊 - 症狀:連不上/無限讀取, 斷線/因素:停滯 - 誰會遇到:整個伺服器, 只有我/何時:剛登入/維護剛結束, 晚間尖峰時段 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發), 基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:伺服器:把排隊上限配合登入伺服器實際能處理的量,排隊中斷線的玩家保留位置一段時間(重新連線寬限),顯示順位與預估等待時間,把排隊長度、拒絕數、排隊中斷線次數記為指標。用戶端:排隊中斷線時不關閉遊戲,自動重新連線回到原來的位置,重試間隔以指數退避加上抖動(jitter)打散。 - 基礎設施團隊要做的事:伺服器設備/OS:上市前以負載測試量出登入、大廳伺服器的處理上限,上市時準備好可隨時加上的備用設備,把排隊指標與連線嘗試數放在同一張圖表上看。 - 數值參考:2021 年 FINAL FANTASY XIV 資料片上市時,每個邏輯資料中心排隊人數超過 17,000 人就會拒絕新的排隊(Error 2002)。排隊中斷線時,大廳伺服器會等待數十秒~1 分鐘,在這段時間內重新連上就能從排隊中段接續。 - 圖表上:碰到上限後持平(登入排隊長度、因達上限而拒絕的數量、排隊中斷線次數) - 查看位置:把登入、大廳伺服器記錄的排隊長度、平均等待時間、因達上限而拒絕的數量、排隊中斷線次數,與連線嘗試數放在同一張圖表上看 - 符合的跡象:上市或維護剛結束時,排隊長度碰到上限而持平,同時拒絕數增加,排隊中斷線集中在 Wi-Fi、行動網路的玩家 - 不符合的跡象:排隊很短但登入很慢時,是 DB(db-login-storm)或作業系統連線等待佇列(so-backlog)的問題 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:登入湧入讓 DB 變慢的情況在「登入暴增與 N+1 查詢」說明,作業系統的連線等待佇列滿出來的情況在「連線等待佇列(backlog)溢位」說明。本項談的是遊戲刻意設計的登入排隊在設計上的問題。排隊上限是保護登入伺服器的安全機制,無法拿掉。因為要及早拒絕超出的請求,才能持續處理還處理得了的請求。重點在於減少拒絕與斷線對玩家造成的損失;排隊越長,錯誤就越集中在 Wi-Fi、行動網路這類線路不穩定的玩家身上。 - 實際案例:ffxiv-2021 - 出處: - [Response to Congestion (as of Dec. 11)](https://na.finalfantasyxiv.com/lodestone/news/detail/6a94b30182b6d963994fdc0b789264ac9f24986f) · Square Enix · 每個邏輯資料中心排隊人數超過 17,000 人時,為避免登入伺服器當機而拒絕新的排隊(Error 2002);排隊中斷線時大廳伺服器等待數十秒~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 (no client-side feedback) 按下按鈕後,在伺服器回應之前既沒有動畫也沒有音效。ping 直接就是反應速度。 - 為什麼 → 於是 → 畫面上:技能、移動、撿取都等伺服器確認後才播放 → 從按下的瞬間起,在往返時間 + tick 等待這段期間毫無反應 → ping 150ms 時,每個動作都慢 0.2 秒 - 症狀:輸入延遲/因素:延遲 - 誰會遇到:只有我/何時:一直都有, 做特定動作時 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:動畫、音效、特效在按下時立即開始(先行演出),只有結果(傷害、獎勵)等伺服器確定後才顯示;移動與普通攻擊先預測並立即反映;收到伺服器的位置校正時,從該位置重新套用尚未獲得確認的輸入。伺服器:用收到的輸入自行計算移動,只在與用戶端預測位置的差距超過門檻時才送出校正值。 - 數值參考:反應時間 ≈ ping + tick 間隔的一半 + 一個畫格。20 tick、ping 150ms 時約 190ms。 - 圖表上:一開始就一直偏高(從輸入到開始演出的時間、RTT(ping)) - 查看位置:在開發版本的用戶端 log 記錄按鈕輸入時間、第一個動畫/音效開始時間、伺服器回應抵達時間,並與遊戲內 RTT 對照。用引擎的網路模擬(Unreal NetEmulation.PktLag)或測試伺服器上 Linux 的 tc netem 加入延遲,改變 ping 反覆測量 - 符合的跡象:演出開始的時間點總是與伺服器回應抵達同一瞬間,從輸入到演出的時間約為 RTT + tick 等待,且隨加入的延遲等量增加 - 不符合的跡象:按下立即開始演出、只有傷害數字等結果較晚出現的話,是正常設計。ping 低的地方也慢了 tick 間隔以上時,是雙重 tick 等待或用戶端畫格的問題 - 確認方式:需要遊戲伺服器/用戶端的 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 · 只等伺服器結果的用戶端,延遲 500ms 時所有動作都要 500ms 後才看得到。以用戶端預測與伺服器校正解決 - [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 沒有預測,施放者會看到延遲 - [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 一次操作需要依序與伺服器往返好幾次時,ping 會乘上這個次數。 - 為什麼 → 於是 → 畫面上:開啟商店 → 請求清單 → 確認價格 → 購買 → 更新背包,每一步各自請求 → 收到前一個請求的回應後才送出下一個請求 → ping 150ms 時買一次東西要將近 1 秒。載入特別久 - 症狀:輸入延遲, 連不上/無限讀取/因素:延遲 - 誰會遇到:只有特定功能, 只有我/何時:做特定動作時, 剛登入/維護剛結束 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:修改協定,把多個步驟合併成一次請求與回應(例:在購買回應中一併放入更新後的背包)。用戶端:預先取得需要的資料,採用不必等待結果的 UI。 - 數值參考:耗時 ≈ 往返次數 ×(ping + 伺服器處理 + tick 等待)。5 次的話,ping 150ms 時約 0.85~1 秒。 - 圖表上:一開始就一直偏高(各功能的完成時間、一次操作的往返次數) - 查看位置:在伺服器端的封包擷取(Wireshark)中,用測試帳號執行一次商店購買、登入之類的操作,計算期間請求與回應交替往返幾次及其間隔。有伺服器請求 log 的話,以 session ID 分組,查看請求數與每個請求的抵達、回應時間 - 符合的跡象:一次操作中,請求要等前一個回應後才依序往返多次,完成時間約為往返次數 × RTT,且 ping 越高的地區,玩家使用同一功能就越慢,大致成正比 - 不符合的跡象:往返只有一兩次、卻是某個回應花很久時,是伺服器處理或 DB 端的原因。與 ping 無關、所有玩家都一樣慢時,要查伺服器負載 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [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 必須收到伺服器確認前一個技能已結束才能按下一個技能時,每次連段之間都會夾著一段往返時間。 - 為什麼 → 於是 → 畫面上:只在「前一個技能確定後」才接受下一個技能的輸入 → 每個技能之間都空出一段 ping 長度的時間 → 連段之間出現空檔,ping 越高 DPS 越低 - 症狀:輸入延遲, 吃指令/回檔/因素:延遲 - 誰會遇到:只有我, 只有特定功能/何時:做特定動作時 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:設定預輸入容許時間,冷卻結束前一段時間(例:0.3~0.4 秒)內的輸入也接受並立即送給伺服器。伺服器:稍微提早抵達的輸入不要拒絕,在冷卻結束的瞬間執行。 - 數值參考:冷卻 1 秒的連段在 ping 150ms 時,每個技能之間會空出 0.15 秒以上,同樣時間內施放的技能數減少超過 13%。 - 圖表上:一開始就一直偏高(技能之間的空檔、RTT(ping)) - 查看位置:在伺服器 log 中依角色記錄技能冷卻結束時間、下一個技能請求抵達時間與執行時間,把中間的空檔與玩家 RTT 比較 - 符合的跡象:從冷卻結束到下一個技能執行之間總是空出約 RTT 的時間,ping 越高的玩家空檔越長、同樣時間內施放的技能越少 - 不符合的跡象:空檔與 ping 無關、固定不變時,是公共冷卻或動畫長度的設計。空檔只是偶爾大幅飆高時,要查抖動、封包遺失或超出 tick 預算 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:例如《魔獸世界》設有預輸入容許時間,並讓玩家可以在設定中調整。容許時間比往返時間長的話,連段之間就幾乎不會再夾著 ping 的等待。 - 出處: - [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→500ms 時約 2 分鐘的戰鬥多出 5 秒) #### sy-short-window · 被 ping 吃掉的短判定區間 · Timing window too short for latency + reaction 閃避、格擋、防禦這類需要反應的時間很短時,ping 會把這段時間吃掉,出現躲不掉的攻擊。 - 為什麼 → 於是 → 畫面上:像王的攻擊預兆 0.5 秒、格擋判定 0.2 秒這樣短的判定區間 → 預兆較晚看到(下行延遲 + 內插),自己的輸入也較晚抵達(上行延遲 + tick 等待) → 明明躲開了卻被打中,格擋被吃掉 - 症狀:吃指令/回檔, 輸入延遲/因素:延遲 - 誰會遇到:只有我, 只有特定功能/何時:做特定動作時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發), 基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:伺服器:以伺服器時間排程攻擊預兆並提前送出,把判定區間延長 ping 的長度(延遲補償)。用戶端:把收到的預兆配合排定的伺服器時間播放。 - 基礎設施團隊要做的事:在玩家多的地區附近部署伺服器(地區伺服器),從根本降低 ping。 - 數值參考:ping 150ms、內插 100ms 時,預兆出現在自己畫面上約需 0.18 秒,自己的輸入抵達伺服器約需 0.1 秒。再加上人的反應時間 0.25 秒,0.5 秒的預兆幾乎不可能躲開。 - 圖表上:只有部分偏高(閃避、格擋失敗率(依 ping 區間)) - 查看位置:在伺服器 log 記錄判定區間的開始與結束時間、玩家輸入抵達伺服器的時間、該玩家的 RTT,並把失敗率依 ping 區間(例:每 50ms 一級)分開來看 - 符合的跡象:ping 越高的區間失敗率明顯越高,失敗的輸入在判定區間結束後不久(RTT 加內插時間以內)才抵達 - 不符合的跡象:失敗率與 ping 區間無關、差不多時,是招式難度問題。輸入在判定區間內抵達卻仍判定失敗時,要查判定程式碼或伺服器驗證 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Factors influencing the latency of simple reaction time (Frontiers in Human Neuroscience, 2015)](https://doi.org/10.3389/fnhum.2015.00131) · Frontiers · 簡單反應時間平均約 231ms(校正設備延遲後為 213ms),近年的大規模研究為 233~400ms - [Latency and Player Actions in Online Games (Communications of the ACM, 2006)](https://web.cs.wpi.edu/~claypool/papers/precision-deadline/final.pdf) · ACM · 越精準、期限越短的動作對延遲越敏感(第一人稱約 100ms、第三人稱約 500ms、上帝視角約 1,000ms 為極限) - [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 · 沒有延遲補償的判定 · Server-now hit validation 伺服器只用「伺服器上現在的位置」判定命中時,判定會與自己看到的畫面不一致。 - 為什麼 → 於是 → 畫面上:自己畫面上的對手是約 0.2 秒前的位置(ping 150ms、內插 100ms 時) → 伺服器以目前位置判定,對手早已不在自己瞄準的地方 → 明明打中了卻判定落空。射擊移動中的目標要預留提前量 - 症狀:吃指令/回檔/因素:延遲 - 誰會遇到:只有我/何時:做特定動作時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:回溯到攻擊者當時看到的時間點再判定(延遲補償),或改成指定目標的方式。用戶端:攻擊時一併送出自己當時看到的時間點(正在內插的伺服器時間)。 - 圖表上:只有部分偏高(移動目標的命中率(依 ping 區間)) - 查看位置:在伺服器判定 log 中一併記錄攻擊時間、攻擊者畫面上的目標位置(用戶端送來的值)、判定時使用的伺服器端目標位置、攻擊者 RTT。在開發版本中把伺服器判定用的位置疊畫在用戶端畫面上,一眼就能看出 - 符合的跡象:落空的判定中,兩個位置的差距約為目標速度 ×(攻擊者 RTT + 內插時間),且 ping 越高,只有移動目標的命中率下降 - 不符合的跡象:靜止的目標也打不中時,是判定框(hitbox)或碰撞檢測的問題。有做回溯卻仍不一致時,要查用戶端是否把內插時間錯誤告知伺服器 - 確認方式:需要遊戲伺服器/用戶端的 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 · 沒有延遲補償時,必須依延遲預留提前量射擊。由伺服器回溯延遲加內插時間後再判定的延遲補償 - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · 伺服器回溯到射擊瞬間玩家看到的遊戲狀態來判定命中,用戶端一併送出自己看到的模擬時間 - [Source SDK 2013: player_lagcompensation.cpp](https://raw.githubusercontent.com/ValveSoftware/source-sdk-2013/master/src/game/server/player_lagcompensation.cpp) · Valve · Source 引擎的回溯時間 = 網路延遲 + 內插時間 #### sy-lagcomp-overreach · 延遲補償過度 · Excessive lag compensation 以攻擊者為準回溯得太遠時,被打的一方明明已經躲好了還是會中彈。 - 為什麼 → 於是 → 畫面上:為了 ping 高的攻擊者,伺服器大幅回溯後判定 → 在被打的人的畫面上,早已躲進掩體 → 「躲在牆後還被打中」,ping 高的人占優勢 - 症狀:吃指令/回檔/因素:延遲 - 誰會遇到:只有我, 特定地區/電信業者/何時:做特定動作時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:設定回溯上限(例:200~250ms),ping 比這更高的攻擊者只回溯到上限,其餘由攻擊者自己預留提前量。 - 圖表上:只有部分偏高(每次命中的回溯時間(依攻擊者 ping)) - 查看位置:在伺服器判定 log 中為每次命中記錄回溯時間、攻擊者 RTT、被打方進入掩體的伺服器時間。在開發版本把回溯後的判定框畫到畫面上(Source 引擎為 sv_showlagcompensation) - 符合的跡象:「躲在牆後還被打中」回報中的命中集中在回溯時間長的攻擊者,且回溯時間沒有上限、隨攻擊者 ping 增加 - 不符合的跡象:回溯時間短的命中也出現牆後中彈時,是判定框或碰撞檢測的問題。被打方 ping 高時,是那個人的移動太晚抵達伺服器造成的 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:回溯判定是「射擊者優先」。也有人提出例外做法:被打的一方在自己畫面上已經進入安全處時就不回溯,也就是「被打者優先」。 - 出處: - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · 回溯沒有上限時,延遲 500ms 的人在對方躲進掩體 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 會在畫面上顯示回溯後的判定框 #### sy-client-auth · 用戶端權威 · Client-authoritative results 各自決定自己的結果時,自己的畫面很順暢,但結果會與其他人的畫面不一致,也容易被外掛利用。 - 為什麼 → 於是 → 畫面上:位置、命中由用戶端決定,伺服器只負責轉送 → 兩個人都聲稱自己先打中,伺服器無法驗證 → 對手瞬移、穿牆,「我明明打中卻沒中」 - 症狀:瞬移, 吃指令/回檔/因素:延遲 - 誰會遇到:整個伺服器/何時:一直都有 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:重要的結果(命中等)由伺服器自行驗證,移動要檢查速度與距離。用戶端:收到伺服器拒絕或校正的結果時,改回伺服器給的值。 - 圖表上:一開始就一直偏高(不可能的移動速度、互相矛盾的命中回報數) - 查看位置:在伺服器端原樣記錄用戶端回報的位置與命中,用連續的位置回報計算移動速度,統計超過最大速度的回報,以及兩人都說自己先打中的回報 - 符合的跡象:伺服器未經驗證就把回報轉送給其他用戶端,不可能的速度或互相矛盾的命中回報持續出現,與更新、地區無關 - 不符合的跡象:伺服器自行計算或驗證結果時,就不是這個原因。這時的瞬移要查封包遺失或內插緩衝 - 確認方式:需要遊戲伺服器/用戶端的 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 · 由用戶端回報結果的方式,只有在用戶端可信時才可行。因為擔心外掛而採用權威伺服器 - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · 伺服器權威模型:伺服器絕不信任用戶端看到的遊戲狀態 - [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 · 把權限分給用戶端會讓作弊變容易,也不再有一個掌管所有物件的單一模擬 #### sy-lockstep · Lockstep 中等待最慢的玩家 · Lockstep waits for the slowest peer 在所有人一起計算同一回合的架構中,只要一個人的輸入晚到,所有人都要等。 - 為什麼 → 於是 → 畫面上:每回合要收齊所有玩家的輸入才能計算 → 某個人的輸入因抖動或封包遺失而晚到 → 所有人同時頓一下,嚴重時跳出「等待玩家中」視窗 - 症狀:定格, 卡頓, 輸入延遲/因素:抖動, 遺失, 停滯 - 誰會遇到:特定地點/頻道/何時:偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:依 ping 自動調整輸入延遲,只讓落後的人暫時脫隊,其他人不必等待、繼續進行。用戶端:套用設定好的輸入延遲;若是沒有中繼伺服器的 P2P,輸入延遲的調整與落後者的處理也由主機用戶端負責。 - 數值參考:輸入延遲設得比「輸入抵達對方的時間 + 抖動」短時,停頓會變頻繁。抵達時間在雙方直接傳送時約為 ping 的一半,經過中繼伺服器時約為兩人 ping 相加的一半。 - 圖表上:偶爾隨機飆高(回合等待時間、各玩家的輸入抵達延遲) - 查看位置:每回合記錄各玩家的輸入抵達時間與回合停下等待的時間,查看停住的回合在等誰的輸入。有中繼伺服器的話,也可以從伺服器端封包擷取中各玩家輸入封包的抵達間隔來看 - 符合的跡象:每次停住的回合,都是同一個人的輸入比輸入延遲還晚抵達,且當時那個人的抖動、封包遺失飆高 - 不符合的跡象:輸入都準時到卻仍停住時,是最慢那台電腦的計算時間或伺服器處理問題。沒有停住、只有兩邊畫面的結果不同時,是計算結果不一致(desync),要查指令同步的路徑計算不一致 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Deterministic Lockstep](https://gafferongames.com/post/deterministic_lockstep/) · Gaffer On Games · 第 n 畫格的輸入要全部到齊才能計算,所以晚到就得等。吸收抖動的播放延遲緩衝太小時會頓一下 - [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 · 把指令排程到兩回合後執行,並依最慢的電腦與 ping 調整回合長度(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 · 把輸入延遲(incoming delay)設為「A→伺服器 + 伺服器→B」的延遲,讓所有人在同一瞬間套用 #### sy-rollback · Rollback netcode 預測失敗 · Rollback misprediction 先預測對手的輸入並呈現出來,猜錯時就回溯重新計算。ping 越大,回溯的幅度越大。 - 為什麼 → 於是 → 畫面上:對手改變輸入(與預測不同) → 實際輸入晚了 ping 的一半才抵達,於是回溯這段時間重新計算 → 對手的動作跳過幾個畫格或突然改變 - 症狀:瞬移/因素:延遲, 抖動 - 誰會遇到:只有我/何時:做特定動作時, 偶爾隨機發生 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:搭配 1~3 個畫格的輸入延遲以縮小回溯幅度,並設定回溯上限。 - 數值參考:ping 100ms(單向 50ms)時,在 60fps 下約回溯 3 個畫格。加上 2 個畫格的輸入延遲,就減少到 1 個畫格。 - 圖表上:偶爾隨機飆高(回溯的畫格數、RTT(ping)) - 查看位置:由用戶端在每次回溯時記錄回溯的畫格數、當時的 RTT、輸入延遲設定、回溯與重新計算花費的時間 - 符合的跡象:回報對手動作亂跳的瞬間,回溯畫格數很大;平均回溯幅度約為(單向延遲 − 輸入延遲)÷ 畫格時間,ping 越大幅度越大 - 不符合的跡象:回溯幅度小卻仍出現卡頓時,是重新計算超過一個畫格時間的效能問題。回溯後兩邊畫面的結果仍持續不同時,是計算結果不一致(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 消除 lockstep 的本地輸入延遲,在 16ms 內最多回溯 8 個畫格並重新計算 #### sy-no-timestamp · 沒有時間戳記、一到就播放 · Events played on arrival (no timestamps) 伺服器事件沒有附上發生時間、一收到就播放時,網路抖動會讓演出時機跟著忽快忽慢。 - 為什麼 → 於是 → 畫面上:「開始攻擊」、「播放特效」事件一抵達就執行 → 每個封包的抵達時間不同,間隔忽長忽短 → 連續攻擊動作忽快忽慢,王的招式時機每次都不一樣 - 症狀:卡頓, 快轉/因素:抖動 - 誰會遇到:只有我/何時:一直都有 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:依事件附帶的時間播放(事件排程、內插緩衝)。伺服器:在事件上附上發生時間(伺服器時間)再送出。 - 圖表上:偶爾隨機飆高(事件播放間隔、封包抵達間隔) - 查看位置:用事件編號對齊伺服器 log 的事件發生時間與用戶端 log 的抵達、播放時間,比較間隔。在開發版本加入抖動(tc netem 的抖動值、Unreal 網路模擬的最小/最大延遲)來重現 - 符合的跡象:伺服器上的發生間隔固定,播放間隔卻完全跟著抵達間隔忽長忽短 - 不符合的跡象:抵達間隔均勻、播放卻忽快忽慢時,是用戶端畫格的問題(畫格時間尖峰)。伺服器的發生間隔本身就不穩時,是超出 tick 預算 - 確認方式:需要遊戲伺服器/用戶端的 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 · 每個更新都附上伺服器時間,以目前時間減去內插時間(100ms)的目標時間點位置來繪製 - [Snapshot Interpolation](https://gafferongames.com/post/snapshot_interpolation/) · Gaffer On 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 · 在 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 · 雙重 tick 等待 · Double tick quantization 請求先累積到下一個 tick 才處理,結果又等到再下一個 tick 才送出時,tick 間隔會被加上兩次。 - 為什麼 → 於是 → 畫面上:收到的請求在下一個 tick 處理 → 處理結果也累積到下一個傳送 tick 才送出 → 線路 ping 很低,反應卻固定慢了約 tick 間隔的 1.5 倍。10 tick 伺服器平均 0.15 秒,最差 0.2 秒 - 症狀:輸入延遲/因素:延遲 - 誰會遇到:整個伺服器/何時:一直都有 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:在處理的那個 tick 立即送出回應、提高 tick rate、重要的回應立即傳送。 - 數值參考:10 tick 伺服器一個 tick 是 100ms,光是 tick 等待平均就多出 150ms,最差多出 200ms。只等一次的話平均是 50ms。 - 圖表上:一開始就一直偏高(從請求抵達到送出回應的時間) - 查看位置:在伺服器端的封包擷取中,讓測試帳號重複做同一個動作(例:使用道具),測量請求封包抵達時間與對應回應封包送出時間的間隔。有伺服器 log 的話,查看請求抵達時間、處理的 tick 編號、送出回應的時間 - 符合的跡象:伺服器內部花費的時間平均約為 tick 間隔的 1.5 倍、最多約 2 倍,且與 RTT 無關、固定不變 - 不符合的跡象:伺服器內部時間平均在 tick 間隔的一半左右時,tick 等待只有一次。比 tick 間隔長且忽長忽短時,要查超出 tick 預算 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · 抵達的輸入最多要等一個 tick 才到 tick 邊界,套用與傳送又要再花一個畫格。tick rate 越高越短 - [VALORANT's 128-Tick Servers](https://www.riotgames.com/en/news/valorants-128-tick-servers) · Riot Games · 延遲有一部分來自網路,一部分來自伺服器 tick rate - [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 的變更會累積到每個網路 tick 再一起傳送 #### sy-strict-check · 過於嚴格的伺服器驗證 · Over-strict server validation 伺服器對移動速度、冷卻時間、射程檢查得太嚴格時,連因抖動而擠在一起抵達的正常輸入也會被拒絕。 - 為什麼 → 於是 → 畫面上:「一個 tick 內可移動的距離」、「冷卻時間容許誤差 0ms」這類嚴格標準 → 因抖動使兩個指令擠在同一個 tick 抵達時,就被判定為違規 → 拉回,冷卻好了技能卻被拒絕 - 症狀:拉回, 吃指令/回檔/因素:抖動 - 誰會遇到:只有我/何時:偶爾隨機發生, 移動中/切換地圖時 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:以累積容許量(token bucket)方式檢查,保留 ping 與抖動的餘裕。 - 圖表上:偶爾隨機飆高(伺服器驗證拒絕、位置校正次數) - 查看位置:在伺服器 log 中,每次驗證拒絕或位置校正都記錄原因、該 tick 抵達的該玩家指令數、與前一個指令的抵達間隔 - 符合的跡象:拒絕與校正集中在一個 tick 內擠進 2 個以上指令的時刻,而以數秒為單位加總的移動量、使用次數都在規則範圍內 - 不符合的跡象:以數秒為單位加總仍超出規則時,可能真的是加速或作弊。拒絕集中在特定電信業者與晚間時段時,是集中在特定電信業者玩家的驗證誤判 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Source SDK 2013: player.cpp](https://raw.githubusercontent.com/ValveSoftware/source-sdk-2013/master/src/game/server/player.cpp) · Valve · 以每個 tick 累積的指令處理預算(最多 sv_maxusrcmdprocessticks 24 tick)容許擠在一起抵達的指令。開發註解提到擋得更嚴格時,連正常玩家也會出現卡頓 - [RFC 2697: A Single Rate Three Color Marker](https://www.rfc-editor.org/rfc/rfc2697) · IETF · token bucket:以平均速率(CIR)與一次容許的突發量(CBS)判定 #### sy-host · 主機(房主)架構 · Listen server / host advantage 由某個玩家的電腦擔任伺服器時,那個人的線路與電腦效能決定了所有人的體感。 - 為什麼 → 於是 → 畫面上:房主的電腦擔任伺服器(P2P、listen server) → 房主的線路或電腦慢時會影響所有人,房主本人 ping 為 0 → 只有房主占優勢,房主離開時所有人定格、斷線 - 症狀:卡頓, 定格, 斷線/因素:延遲, 停滯 - 誰會遇到:特定地點/頻道/何時:偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發), 基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:伺服器:改用負責判定的專用伺服器;在那之前,配對時選線路與電腦較好的人當房主。用戶端:支援主機轉移(migration),配對時測量與其他參與者之間的 ping、上傳速度與電腦效能並回傳。 - 基礎設施團隊要做的事:準備專用伺服器用的伺服器設備與執行個體,部署在玩家多的地區附近。 - 圖表上:只有部分偏高(各房主的 lag 回報、斷線次數) - 查看位置:在對戰 log 記錄房主(主機)的上傳速度、各參與者與房主之間的 RTT、房主電腦的畫格時間、房主離開的時間,並把 lag 與斷線回報依房主分組來看。玩家也可以讓同一批人只換房主再玩一次來確認 - 符合的跡象:lag 與斷線集中在特定房主的房間,該房主上傳速度低或畫格時間長時所有參與者一起變差;沒有主機轉移時,房主離開的瞬間所有人都斷線 - 不符合的跡象:與房主無關、只有同一地區的參與者變差時,是線路或路由問題。若是專用伺服器架構,就不是這個原因 - 確認方式:需要遊戲伺服器/用戶端的 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 · session 擁有者離開時,自動從剩下的用戶端中選出新的擁有者 - [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 遊戲也可以看成由主機兼任伺服器的用戶端-伺服器架構 #### sy-optimistic-reject · 先行演出後遭伺服器拒絕 · Client-side feedback rejected by server 自己畫面上先呈現的打擊、技能,事後沒有被伺服器認可時,明明看到的結果就變成沒發生過。 - 為什麼 → 於是 → 畫面上:在伺服器確認前先播放打擊特效與技能動作(先行演出) → 伺服器重新檢查射程、目標位置、冷卻與資源後拒絕 → 噴血了卻沒有傷害,技能只有動作沒有效果,只有冷卻在跑 - 症狀:吃指令/回檔, 拉回/因素:延遲 - 誰會遇到:只有我, 只有特定功能/何時:做特定動作時 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:只有傷害數字、死亡、獎勵這類需要確定的部分才依伺服器結果顯示,常見的拒絕原因先在本地檢查,被拒絕時還原冷卻與資源並顯示原因。伺服器:射程與目標位置檢查保留 ping 的餘裕,在拒絕回應中附上原因,收集各技能的拒絕比例作為指標。 - 數值參考:拒絕回應會在按下後晚 ping + tick 等待的時間才到。ping 150ms 時,約有 0.2 秒會以為「打中了」。 - 圖表上:只有部分偏高(各技能的伺服器拒絕比例(依 ping 區間)) - 查看位置:在伺服器端收集各技能的拒絕比例與拒絕原因(射程、目標位置、冷卻、資源),並依玩家 RTT 區間分開。在用戶端記錄先行演出的動作被拒絕的次數 - 符合的跡象:拒絕集中在特定技能以及射程、目標位置類原因,ping 越高拒絕比例越高 - 不符合的跡象:拒絕原因是冷卻、資源且與 ping 無關時,要查用戶端與伺服器的資料值(冷卻、消耗)是否不同。沒有拒絕、演出卻在伺服器回應後才開始時,是伺服器回應後才演出(請求-回應方式) - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:先行演出是遮蓋 ping 最好的方法。只是用戶端與伺服器用來判斷的資訊(對手位置、剩餘資源)差異越大,拒絕就越頻繁。把各技能的拒絕比例收集成指標,就容易找出判定不一致的地方。 - 出處: - [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 能力在用戶端立即執行,但由伺服器做最終決定,並可能推翻結果 - [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 鎖定目標的遊戲對 ping 不敏感,原因之一就是這種方式。代價是無法保證兩邊結果相同,一定要有不時對齊位置的機制。浮點數運算的結果可能依 CPU 種類、編譯器及其最佳化設定(包括 debug 與 release 建置的差異)而有些微不同。在 lockstep、rollback 這類只交換輸入、假設兩邊計算結果完全相同的架構中,這些小差異會累積起來,可能讓兩邊畫面的遊戲狀態分歧,造成不一致(desync)。 - 出處: - [Deterministic Lockstep](https://gafferongames.com/post/deterministic_lockstep/) · Gaffer On Games · 即使在同一台機器上是決定性的,編譯器、OS、CPU 不同時浮點數結果仍可能不同 - [State Synchronization](https://gafferongames.com/post/state_synchronization/) · Gaffer On Games · 連同輸入一起送出狀態,就算沒有完美的決定性也能讓兩邊一致 - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · 封包遺失,或兩個角色要走到同一個位置時,伺服器與用戶端的模擬會出現差異,需要校正 - [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 · 極小的差異隨時間擴大,讓村民的路徑逐漸偏移。以檢查碼比較世界、物件、尋路結果來找出不同步(out-of-sync) - [Floating Point Determinism](https://gafferongames.com/post/floating_point_determinism/) · Gaffer On Games · 同樣的浮點數程式碼,結果也可能因編譯器、CPU 架構、debug/release 建置而不同。AMD 與 Intel CPU 在超越函數上算出略有不同值的案例 - [/fp (Specify floating-point behavior)](https://learn.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior) · Microsoft · /fp:fast 會重新排列或合併浮點運算,結果可能與其他 /fp 設定不同;以 FMA 合併的運算也可能與分開相乘再相加的結果不同 #### sy-low-send-rate · 快照傳送頻率太低 · Low snapshot / update rate 伺服器每秒只送幾次位置更新(快照)時,內插緩衝就得相應拉長,看到的其他角色會是更久以前的狀態。 - 為什麼 → 於是 → 畫面上:為了節省傳輸量,每秒只送 5~10 次位置更新 → 要畫得流暢,緩衝就得設成封包間隔的 2 倍(200~400ms);設得短的話,只漏掉一個封包就會停住 → 對手轉向較晚才看到,與判定不一致。緩衝短時會卡頓,封包遺失時出現瞬移 - 症狀:卡頓, 瞬移, 吃指令/回檔/因素:延遲, 遺失 - 誰會遇到:整個伺服器/何時:一直都有 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:近距離或戰鬥中的目標送得頻繁、遠處目標送得少;只送有變化的部分(差異壓縮),縮小單次大小並提高頻率。用戶端:依封包間隔自動調整內插緩衝長度。 - 數值參考:每秒 10 次的話封包間隔 100ms、緩衝 200ms。再加上 ping 150ms 的單向延遲 75ms,看到的對手約是 0.3 秒前的狀態。 - 圖表上:一開始就一直偏高(各用戶端的封包抵達間隔、內插緩衝長度) - 查看位置:在伺服器端封包擷取中只篩選送往某位玩家的流量,用 Wireshark 的 I/O Graphs 查看每秒封包數與間隔。有遊戲端 log 的話,同時查看各物件的更新間隔與用戶端內插緩衝的餘裕(距離下一個快照抵達的剩餘時間) - 符合的跡象:位置更新一直很稀疏,每秒 5~10 次(間隔 100~200ms),內插緩衝設得超過 200ms,或緩衝餘裕經常歸 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 次更新時,內插 200ms 可以承受一次遺漏。Half-Life 預設為每秒 20 次、內插 100ms - [Snapshot Interpolation](https://gafferongames.com/post/snapshot_interpolation/) · Gaffer On Games · 每秒 10 個的話,要承受連續兩個遺失需要 350ms 延遲;每秒 30 個時可降到 150ms - [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 · 把符合顯示篩選器的封包數、位元組數依時間區間畫成圖表 ### 只有部分人遇到的問題(24 個原因) #### pt-slow-burst · 慢的人在別人畫面上快轉移動 · Laggy player seen by others (bursty inputs) 線路差的人,輸入會忽快忽慢、成批抵達伺服器。伺服器每個 tick 收到多少就套用多少時,在其他人眼中,那個角色會頓一下後一次走好幾步。 - 為什麼 → 於是 → 畫面上:慢的人的移動指令,有的 tick 抵達 0 個,有的 tick 一次抵達 2~3 個 → 伺服器在收到的 tick 一次全部套用,那個角色的位置呈階梯式變化 → 在其他人畫面上,只有那個角色頓一下後一次快轉移動。其他都正常 - 症狀:快轉, 瞬移/因素:抖動, 遺失 - 誰會遇到:只有特定角色看起來怪怪的, 特定地區/電信業者/何時:一直都有, 移動中/切換地圖時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:外部(外部) - 遊戲開發團隊要做的事:用每位玩家各自的輸入緩衝把指令平均分配套用,依輸入序號按原本的間隔套用;只加長其他人畫面上的內插緩衝並不夠(因為伺服器記錄的位置本身就是階梯狀)。 - 外部要做的事:引導慢的玩家改用有線連線,並檢查 Wi-Fi 與分享器。 - 數值參考:抖動 80ms 時,在 20 tick(50ms)伺服器上,每個 tick 的指令數會在 0~3 個之間波動。 - 圖表上:只有部分偏高(各玩家每個 tick 套用的指令數、各玩家的抖動) - 查看位置:在伺服器端封包擷取中只篩選被回報玩家送出的封包,計算每個 tick 間隔(例:50ms)抵達幾個,並與其他玩家比較。有伺服器 log 的話,查看各玩家每個 tick 套用的移動指令數與輸入序號 - 符合的跡象:只有被回報玩家的封包在每個 tick 間 0 個與 2~3 個之間跳動、成批抵達,且該玩家的抖動、封包遺失偏高,其他玩家的封包則均勻抵達。該玩家改用有線後會減少 - 不符合的跡象:多個角色同時快轉時,是伺服器 tick 延遲或觀看者那端的線路。抵達與套用都均勻、卻只有那個角色看起來亂跳時,是觀看端的內插或顯示問題 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:在伺服器權威架構下,這是正常行為。一個慢的人造成的 lag,在別人眼中只會呈現為「那個人動作怪怪的」,不會影響其他人的操作或怪物的動作。只是與那個人直接往來的事(交易、隊伍機制、PvP 判定)也會跟著延遲。 - 出處: - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · 伺服器把抵達的輸入依 tick 順序放入每位玩家的移動佇列,空了就用預測補上。校正只有該玩家看得到,其他人看到的是流暢的畫面 - [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 · 把成批湧入的指令分散到各伺服器 tick 處理(meter out) #### pt-event-server · 一到就處理的伺服器造成快轉 · Event-driven processing of bursty inputs 封包一抵達就立即處理並通知的伺服器,會把慢的人成批湧入的動作接連立即執行。 - 為什麼 → 於是 → 畫面上:慢的人的技能、移動請求成批抵達 → 伺服器一收到就依序執行,並立即通知所有人 → 在其他人眼中,那個人在一瞬間放出好幾個技能,或像快轉一樣移動 - 症狀:快轉/因素:抖動 - 誰會遇到:只有特定角色看起來怪怪的/何時:做特定動作時, 一直都有 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:依動作附帶的輸入時間間隔執行(時間只在容許範圍內才採信),或把成批湧入的動作按最小間隔(公共冷卻)錯開依序執行,不要直接拒絕;不要只看抵達時間做冷卻檢查(正常輸入會被吃掉)。用戶端:在動作上附上輸入時間再送出。 - 圖表上:只有部分偏高(各玩家的動作執行間隔) - 查看位置:在伺服器 log 記錄各玩家動作的抵達時間、執行時間、用戶端附上的輸入時間(若有),比較執行間隔與輸入間隔。同時在伺服器端封包擷取中查看該玩家封包的抵達間隔 - 符合的跡象:輸入間隔正常,伺服器的抵達與執行間隔卻擠在幾 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 就計算移動,並以與前一次移動的時間戳記差決定時間間隔。與伺服器時間差太多時就丟棄該次移動 - [Deterministic Lockstep](https://gafferongames.com/post/deterministic_lockstep/) · Gaffer On Games · 輸入一到就套用的話,即使以 60Hz 送出,間隔也不平均,結果忽快忽慢 #### pt-input-buffer · 各玩家的輸入緩衝大小 · Per-player server input buffer (jitter buffer) 伺服器替每個人暫存一點輸入、每個 tick 取出一個使用時,在其他人眼中很流暢,但本人的動作在伺服器上確定的時間點也會晚上這麼多。 - 為什麼 → 於是 → 畫面上:伺服器把慢的人的輸入存進緩衝,每個 tick 套用一個 → 緩衝小時經常被取空,那個角色會停在原地,或由伺服器依最後一個輸入推測移動;緩衝大時,本人的輸入會較晚確定 → 緩衝小時在別人眼中會頓一下,緩衝大時本人的技能結果較晚出現(輸入延遲) - 症狀:卡頓, 輸入延遲/因素:抖動 - 誰會遇到:只有特定角色看起來怪怪的, 只有我/何時:一直都有 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:依每個人的線路狀態自動調整緩衝大小,積壓時一次取出兩個追上進度,並指示緩衝經常取空的人的用戶端提早送出輸入。用戶端:依伺服器指示,把輸入再提早一點送出(調整用戶端時間)。 - 數值參考:依遊戲而異,通常是 1~3 tick 的量。VALORANT 的 128 tick 伺服器把伺服器緩衝壓得更短,平均只有半個畫格(約 4ms)。常見做法是自適應,只替抖動大的人加大緩衝。 - 圖表上:只有部分偏高(各玩家的輸入緩衝長度、取空次數) - 查看位置:在伺服器上依玩家記錄每個 tick 輸入緩衝中剩餘的輸入數、緩衝取空而依最後輸入推測補上的次數、從輸入抵達到套用所花的時間 - 符合的跡象:緩衝小的人取空次數多,且在那些時刻於別人畫面上短暫停住;緩衝大的人從輸入到套用的時間則多了一個緩衝長度 - 不符合的跡象:緩衝幾乎不會取空、別人畫面上卻出現卡頓時,是觀看端的內插問題。緩衝很短、輸入延遲卻很大時,是 RTT 本身或雙重 tick 等待 - 確認方式:需要遊戲伺服器/用戶端的 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 · 集中在特定電信業者玩家的驗證誤判 · Anti-cheat / movement validation false positives on bad ISPs 線路抖動大的人,輸入會成批抵達,因此常被伺服器的速度、冷卻檢查攔下。 - 為什麼 → 於是 → 畫面上:特定電信業者、地區線路的抖動在晚間變大 → 伺服器把成批抵達的正常輸入判定為加速或違反冷卻 → 只有該電信業者的玩家出現拉回、技能被拒,嚴重時被伺服器踢出而斷線 - 症狀:拉回, 吃指令/回檔, 斷線/因素:抖動 - 誰會遇到:特定地區/電信業者, 只有我/何時:晚間尖峰時段, 移動中/切換地圖時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(網路基礎設施) - 遊戲開發團隊要做的事:檢查改用數秒內的累積容許量,參考線路狀態(ping、抖動)放寬標準,強制斷線前先有警告階段,並用每位玩家各自的輸入緩衝把成批湧入的輸入平均分配到各 tick,從根本減少誤判。 - 基礎設施團隊要做的事:依時段確認各電信業者的遺失率與抖動分布並分享給遊戲開發團隊,檢查該電信業者區段的路由(雙向 mtr),必要時變更路由或向電信業者升級處理(escalation)。 - 圖表上:只在特定時段偏高(各電信業者(ASN)的驗證拒絕、強制斷線次數,各電信業者的抖動) - 查看位置:在伺服器的驗證拒絕、校正、強制斷線 log 加上連線 IP 所屬電信業者(ASN)與時間,依電信業者與時段統計。基礎設施團隊在同一時間往該電信業者方向執行雙向 mtr,查看抖動與封包遺失 - 符合的跡象:拒絕與強制斷線集中在特定電信業者、晚間增加,同一時間該電信業者的抖動也偏高,而以數秒為單位加總的移動量仍在規則內 - 不符合的跡象:與電信業者無關、只有特定帳號反覆出現時,可能真的是作弊。所有電信業者一起增加時,是伺服器 tick 落後、指令被集中套用的伺服器端原因(超出 tick 預算) - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Source SDK 2013: player.cpp](https://raw.githubusercontent.com/ValveSoftware/source-sdk-2013/master/src/game/server/player.cpp) · Valve · 以每個 tick 累積的指令預算防止加速;開發註解提到,更嚴格的限制會讓正常玩家也出現卡頓 - [RFC 2697: A Single Rate Three Color Marker](https://www.rfc-editor.org/rfc/rfc2697) · IETF · token bucket:以平均速率與容許的突發量判定 - [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 · 用戶端與伺服器的時間戳記差距大時,會丟棄該次移動或走時間差異修正流程;以伺服器時間計算,防止加速外掛 #### pt-raid-member · 一個慢的隊友與王的機制 · One laggy member in a synchronized mechanic 在所有人必須在指定瞬間一起反應的團隊副本機制中,一個慢的人反應太晚,就會讓整個隊伍失敗。 - 為什麼 → 於是 → 畫面上:「全員同時散開」、「由一人按下按鈕」這類共同機制 → 慢的人較晚看到預兆,輸入也較晚抵達 → 因為那一個人而團滅,其他隊友覺得「都是 lag 的人害的」 - 症狀:吃指令/回檔, 輸入延遲/因素:延遲 - 誰會遇到:特定地點/頻道, 只有特定角色看起來怪怪的/何時:人潮湧入時, 做特定動作時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:機制判定區間保留 ping 的餘裕,預兆以伺服器時間提前送出,設計成一個人失敗不會導致團滅。用戶端:把收到的預兆配合伺服器時間播放。 - 圖表上:只有部分偏高(造成機制失敗的各玩家 RTT) - 查看位置:在伺服器機制 log 記錄造成失敗的玩家、該玩家的輸入抵達時間、判定區間、該玩家的 RTT 與封包遺失 - 符合的跡象:導致團滅的輸入大多來自同一個人,該玩家的 RTT 明顯高於隊伍平均,且輸入在判定區間剛結束時才抵達 - 不符合的跡象:失敗平均分散在隊友身上時,是判定區間本身太短的問題(被 ping 吃掉的短判定區間)。慢的人的輸入在判定區間內抵達卻仍失敗時,是伺服器判定程式碼 - 確認方式:需要遊戲伺服器/用戶端的 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 · 簡單反應時間平均約 231ms #### pt-mob-control · 怪物控制權在慢速用戶端上 · Monster movement delegated to a player client 有些遊戲為了減輕伺服器負載,把怪物移動的計算交給附近某位玩家的用戶端。那個人線路差時,該怪物在所有人的畫面上都會動得很怪。 - 為什麼 → 於是 → 畫面上:伺服器把怪物移動計算交給最近(或最先到)的玩家用戶端 → 負責者的結果回報延遲,或成批抵達伺服器 → 只有那隻怪物在周圍所有人畫面上頓一下後瞬移。負責者本人的畫面上卻正常 - 症狀:瞬移, 卡頓, 快轉/因素:抖動, 遺失 - 誰會遇到:只有特定角色看起來怪怪的, 特定地點/頻道/何時:一直都有, 偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:把控制權交給線路好的人(以 ping、封包遺失為標準),回報中斷時由伺服器立即收回,王這類重要怪物由伺服器自行計算。 - 圖表上:只有部分偏高(各怪物的位置回報間隔(依擁有控制權的用戶端)) - 查看位置:在伺服器上為每隻怪物記錄擁有控制權的用戶端,以及該用戶端的回報間隔、RTT、封包遺失。也可以從伺服器端封包擷取查看該用戶端送出封包的抵達間隔 - 符合的跡象:動作異常的怪物,控制權全都在同一個人手上,該玩家的回報間隔忽長忽短或中斷,把控制權交給別人後立即恢復正常 - 不符合的跡象:伺服器自行計算的怪物也一樣亂跳時,是伺服器 tick 延遲或觀看者那端的線路。轉移控制權後仍持續亂跳時,是指令同步的路徑計算不一致 - 確認方式:需要遊戲伺服器/用戶端的 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 topologies (Netcode for GameObjects 2.5)](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/manual/terms-concepts/distributed-authority.html) · Unity · 權限分散到用戶端時就沒有單一模擬,容易被作弊 #### pt-heavy-char · 特定角色的資料過於龐大 · One character with oversized data (inventory, mail, buffs) 累積了數千個道具或信件,或好友、封鎖名單、buff 特別多的角色,在登入、儲存與通知周圍玩家時要處理的量是別人的好幾倍。與線路無關,只有用那個角色時才慢。 - 為什麼 → 於是 → 畫面上:練了很久的角色,背包、信箱裡累積了數千個道具或活動獎勵 → 每次登入、切換地圖、儲存都要讀寫那麼多 DB 資料,要送給周圍的裝備與 buff 資訊也很大 → 只有那個角色進場載入很久,開背包或信箱時會頓一下。若伺服器在遊戲執行緒上等待儲存完成,連周圍的人也會短暫定格 - 症狀:連不上/無限讀取, 輸入延遲, 定格/因素:停滯 - 誰會遇到:只有我, 只有特定功能/何時:剛登入/維護剛結束, 做特定動作時, 移動中/切換地圖時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(DB 基礎設施) - 遊戲開發團隊要做的事:設定背包與信件的保存上限並自動清理舊信件,只分批載入需要的部分,儲存只寫有變更的部分,並在遊戲執行緒以外進行。 - 基礎設施團隊要做的事:從慢查詢 log 找出同一角色反覆出現的慢查詢並轉給遊戲開發團隊,提供道具、信件資料列數最多的角色排行清單。 - 數值參考:若一個道具是 DB 的一列,擁有 5,000 個道具的角色每次登入就要讀 5,000 列,是一般角色的數十倍。 - 圖表上:只有部分偏高(各角色的登入、儲存時間,各角色的 DB 查詢資料列數) - 查看位置:在 DB 的慢查詢 log(MySQL slow query log、PostgreSQL log_min_duration_statement)中找出同一角色 ID 反覆出現的慢查詢與儲存,並列出道具、信件資料表中各角色資料列數的排行 - 符合的跡象:慢查詢集中在少數幾個角色 ID,這些角色的道具、信件資料列數是平均的數十倍,從其他電腦、線路登入也一樣慢 - 不符合的跡象:同帳號的其他角色或其他玩家也一起變慢時,是 DB 設備或鎖定的問題。那個角色在其他電腦上正常時,是玩家端環境 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:用同一個角色從其他電腦、線路登入也一樣慢,而同帳號的其他角色正常時,就要懷疑角色資料。這也是回報時一定要附上角色名稱的原因。 - 出處: - [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 · 記錄超過 long_query_time 之查詢的慢查詢 log #### pt-phase · 頻道、實例、相位不同 · Different channel / instance / phase 兩個角色在不同的頻道或實例(instance),或處在依任務進度決定能看到哪些 NPC 的不同「相位」時,看到的是不同的世界。 - 為什麼 → 於是 → 畫面上:第二個角色被分配到其他頻道,或任務階段不同 → 伺服器不會把該 NPC 送給那個角色(正常) → 只有一邊沒有 NPC。看起來像 bug,但符合設計 - 症狀:看不見/幽靈物件/因素:遺失 - 誰會遇到:同一台電腦只有其中一個用戶端, 只有我/何時:一直都有, 剛登入/維護剛結束 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:把頻道、相位資訊送給用戶端,在 QA 檢查清單加入「確認兩個角色的頻道與任務階段」。用戶端:在畫面上顯示頻道與相位。 - 圖表上:只有部分偏高(各用戶端周圍的物件數、頻道與相位) - 查看位置:在遊戲畫面比較兩個角色的頻道編號與該任務的進行階段,調成相同的頻道與階段後再看一次。有伺服器物件傳送 log 的話,確認沒有把那個 NPC 送給該角色的原因(頻道、相位) - 符合的跡象:兩個角色的頻道或任務階段不同,調成相同後就看得到 NPC - 不符合的跡象:頻道與階段都相同、仍只有一邊沒有時,要查載入中抵達的出現通知被丟棄、剛進場時湧入的出現資訊遺失、視野登錄順序錯亂 - 確認方式:在玩家端環境確認 - 深入了解:也要確認任務進度是以帳號為單位還是以角色為單位儲存。同一帳號的兩個角色,一邊的進度可能會改變另一邊的相位。 - 出處: - [Actor Relevancy in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-relevancy-in-unreal-engine) · Epic Games · 伺服器對每條連線只複製相關(relevant)的 actor,不相關的 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 一進入 zone,伺服器就送出周圍 NPC 的出現通知,但用戶端還在載入地圖,於是把通知丟掉。 - 為什麼 → 於是 → 畫面上:伺服器在進場處理後立即送出周圍物件的出現通知 → 用戶端正在載入,訊息處理常式(handler)還沒準備好,於是丟棄通知 → 伺服器當作已經送過,不會再送。在 NPC 離開視野再回來之前都看不見 - 症狀:看不見/幽靈物件/因素:遺失 - 誰會遇到:同一台電腦只有其中一個用戶端, 只有我/何時:剛登入/維護剛結束, 移動中/切換地圖時 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:載入結束後送出「準備完成」,或把載入中收到的封包先保存起來再處理。伺服器:收到「準備完成」後才傳送周圍資訊。 - 數值參考:同一台電腦上兩個用戶端同時載入,或正在載入的那一邊是背景視窗時,CPU 與磁碟要分著用,處理也受到限制,那一邊的載入可能拉長好幾倍。伺服器的進場處理變快時,也會讓同一個 bug 浮現。 - 圖表上:只有部分偏高(各用戶端的載入時間、載入中丟棄的訊息數) - 查看位置:把用戶端在載入中收到並丟棄的訊息數量與種類、載入完成時間,與伺服器送出出現通知的時間比較。在同一台電腦上讓兩個用戶端同時載入,或把正在載入的那一邊放到背景視窗,就容易重現 - 符合的跡象:看不見的 NPC 的出現通知伺服器有送,抵達時間在載入完成之前,且那段時間丟棄的訊息數增加。只發生在載入較久的那個用戶端 - 不符合的跡象:出現通知在載入完成後才抵達卻仍看不見時,是基準快照遺失或物件 ID 重複使用造成誤認。伺服器根本沒送該 NPC 的通知時,是視野登錄順序錯亂或頻道、實例、相位不同 - 確認方式:需要遊戲伺服器/用戶端的 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 會從用戶端刪除,再次相關時重新複製 #### pt-aoi-race · 視野登錄順序錯亂 · Interest-management race on enter/leave 角色登錄到視野格狀(grid)的瞬間,與 NPC 換到其他格子的瞬間重疊時,該 NPC 的出現通知可能會漏掉。 - 為什麼 → 於是 → 畫面上:進場、換頻道、傳送的處理與 NPC 移動在同一瞬間重疊 → 在「新進入視野的物件」計算中漏掉了那個 NPC → 只有特定幾隻 NPC 看不見,或早已離開的 NPC 還留著 - 症狀:看不見/幽靈物件/因素:遺失 - 誰會遇到:同一台電腦只有其中一個用戶端, 只有我/何時:移動中/切換地圖時, 偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:視野更新以單一執行緒、單一順序處理,並定期重新對齊整份「可見清單」。 - 圖表上:偶爾隨機飆高(伺服器可見清單與用戶端物件清單的差異數) - 查看位置:在伺服器上連同 tick 編號記錄視野格狀登錄、物件換格子、出現與消失通知的發送,並定期比較伺服器的「可見清單」與用戶端持有的清單 - 符合的跡象:漏掉的 NPC 在該角色進場或傳送處理的同一個 tick 換了格子,且沒有對該 NPC 發送出現通知的紀錄 - 不符合的跡象:出現通知有送、但用戶端沒收到或丟掉時,是傳遞環節的問題(剛進場時湧入的出現資訊遺失、載入中抵達的出現通知被丟棄)。總是同一隻 NPC 不見時,是相位或顯示選項不同 - 確認方式:需要遊戲伺服器/用戶端的 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 · 相關性依每條連線判斷,不再相關的 actor 會從用戶端刪除 #### pt-baseline · 基準快照遺失 · Lost baseline for delta compression 在伺服器只送「與上次不同的部分」的方式中,最初送一次的完整資訊(基準)遺失時,之後的變化量就無法套用。 - 為什麼 → 於是 → 畫面上:物件的完整資訊(基準)封包遺失,或在處理前被丟棄 → 用戶端沒有可以套用後續變化量的對象,只好忽略 → 那個物件看不見,或過了很久才突然出現 - 症狀:看不見/幽靈物件, 瞬移/因素:遺失 - 誰會遇到:同一台電腦只有其中一個用戶端, 只有我/何時:偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:基準在收到接收確認(ACK)前一定要重傳,變化量只針對用戶端已確認收到的基準產生。用戶端:基準實際套用後才送出接收確認(ACK),收到未知物件的變化量時向伺服器重新請求。 - 圖表上:偶爾隨機飆高(收到未知物件變化量的次數) - 查看位置:把用戶端在沒有基準時丟棄變化量的次數與物件 ID,與伺服器送出該物件基準、收到 ACK 的時間對照。在開發環境加入封包遺失(tc netem 的 loss、Unreal 網路模擬的封包遺失比例)來重現 - 符合的跡象:對於看不見的物件,伺服器送了基準卻沒收到 ACK,仍持續只送變化量,而用戶端丟棄了這些變化量 - 不符合的跡象:基準已收到 ACK 並套用到用戶端、仍看不見時,是消失通知遺失或物件 ID 重複使用造成誤認 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Snapshot Compression](https://gafferongames.com/post/snapshot_compression/) · Gaffer On Games · 變化量只能針對對方已確認收到(ack)的基準(baseline)產生,初始狀態另外傳送 - [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 · 以用戶端確認過的快照為基準做差異壓縮,基準太舊時就送完整快照 - [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、玩家會只留在自己的畫面上。 - 為什麼 → 於是 → 畫面上:死亡、離場、離開視野的通知遺失或順序錯亂 → 用戶端認為那個物件還在 → 打了也沒反應的怪物、早已離開的玩家還站在原地 - 症狀:看不見/幽靈物件/因素:遺失 - 誰會遇到:同一台電腦只有其中一個用戶端, 只有我/何時:偶爾隨機發生, 人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:定期送出「目前可見清單」。用戶端:刪除清單上沒有的物件,應該在移動的物件若長時間沒有更新就隱藏。 - 圖表上:偶爾隨機飆高(只留在用戶端的物件數) - 查看位置:比較伺服器送來的「目前可見清單」與用戶端持有的物件清單,計算只存在於用戶端的物件,並以物件 ID 對照消失通知的發送與接收 log - 符合的跡象:幽靈物件的消失通知伺服器有送,用戶端卻沒有接收紀錄,或消失通知比出現通知先到、順序顛倒 - 不符合的跡象:伺服器的可見清單裡也還留著那個物件時,是伺服器端漏了物件清理。剛有新物件以同一個 ID 出現時,是物件 ID 重複使用造成誤認 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Actor Relevancy in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-relevancy-in-unreal-engine) · Epic Games · 不再相關的動態 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) 踏進 zone 的瞬間,伺服器會一次送出周圍數十~數百個物件的出現資訊。若用不可靠(unreliable)通道送出,或在載入中無法讀取 socket 的期間接收緩衝區溢位,就會有一部分消失且不再重送。 - 為什麼 → 於是 → 畫面上:剛進場時,出現資訊在短時間內大量湧入 → 載入中的用戶端太晚讀取 socket,使 OS 接收緩衝區溢位;或大型 UDP 封包被分段,只要遺失一個分段就整個消失。若是不可靠通道,也不會重送 → 只有載入較慢的那個用戶端少了幾隻 NPC。離開視野再回來就看得到 - 症狀:看不見/幽靈物件/因素:遺失 - 誰會遇到:同一台電腦只有其中一個用戶端, 只有我/何時:剛登入/維護剛結束, 移動中/切換地圖時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:出現與消失通知一律用保證重傳的可靠通道送出,初始資訊分批傳送。用戶端:在與載入分開的執行緒接收,並加大接收緩衝區。 - 數值參考:電腦的 UDP 接收緩衝區預設值依 OS 而異,大多為數十~數百 KB。人多的城鎮進場資訊若比這更大,只要因為載入而短暫沒讀 socket 就會溢位。 - 圖表上:剛開服或維護結束後暴增(剛進場時的接收量、出現通知遺漏數) - 查看位置:比較伺服器剛進場時送出的出現通知數與用戶端收到的數量,並查看是用哪種通道(可靠/不可靠)送出。在伺服器端封包擷取中查看剛進場時送往該玩家的量與被分段的封包(Wireshark 篩選器 ip.flags.mf == 1 || ip.frag_offset > 0) - 符合的跡象:收到的數量少於送出數量,遺漏集中在剛進場湧入的區段,且是以不可靠通道送出或大封包被分段。在載入較慢的用戶端上更常發生 - 不符合的跡象:送出與收到數量相同卻看不見時,是收到後被丟棄(載入中抵達的出現通知被丟棄)或視野計算的問題。與剛進場無關、隨時都會遺漏時,是線路的封包遺失 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [RFC 8085: UDP Usage Guidelines](https://www.rfc-editor.org/rfc/rfc8085) · IETF · 被分段的封包只要遺失一個分段就整個消失 - [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 · socket 接收緩衝區的預設大小依 OS 而異 - [Display Filter Reference: Internet Protocol Version 4](https://www.wireshark.org/docs/dfref/i/ip.html) · Wireshark · 以 ip.flags.mf(More fragments)、ip.frag_offset(Fragment Offset)篩選被分段的 IP 封包 #### pt-id-reuse · 物件 ID 重複使用造成誤認 · Entity ID reused without a generation counter 死亡的 NPC 重新出現時,伺服器若再次使用同一個物件 ID,在這段期間漏掉消失通知的用戶端,就會把新的 NPC 誤認成舊的 NPC。 - 為什麼 → 於是 → 畫面上:NPC 死亡後以同一個物件 ID 重新出現 → 漏掉消失通知的用戶端認為是「已知的物件」,忽略出現通知或維持死亡狀態 → 只有一邊的畫面上沒有 NPC 或看到它倒在地上,有時還會以其他 NPC 的外觀出現 - 症狀:看不見/幽靈物件/因素:遺失 - 誰會遇到:同一台電腦只有其中一個用戶端, 只有我/何時:偶爾隨機發生, 人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:在物件 ID 上加上世代編號(generation),區分重複使用。用戶端:收到已知 ID 的出現通知時,刪除既有物件並重新建立。 - 圖表上:偶爾隨機飆高(收到已知 ID 出現通知的次數) - 查看位置:在伺服器記錄各物件 ID 的建立與刪除時間(有世代編號時一併記錄),統計用戶端以已知 ID 收到出現通知的次數,以及視野更新時被當成「沒有變化」處理的刪除與重新建立 - 符合的跡象:看不見或倒在地上的 NPC 的 ID 與剛死亡的 NPC 相同,且這段期間該用戶端沒收到消失通知,或伺服器消失與出現通知都沒送 - 不符合的跡象:ID 有世代編號、比較時也有用到的話,就不是這個原因。ID 沒有被重複使用卻仍看不見時,是出現通知遺失 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 深入了解:伺服器端也會發生。只以 ID 比較視野內的物件清單時,在兩次視野更新之間死亡、又以同一個 ID 重新生成的 NPC 會被當成「沒有變化」,消失與出現通知都不會送出。每個人的視野更新時間點錯開時,只有剛好碰上那一刻的用戶端會遇到。 - 出處: - [Entity struct (Entities 1.3)](https://docs.unity3d.com/Packages/com.unity.entities@1.3/api/Unity.Entities.Entity.html) · Unity · Entity 由 Index 與世代編號(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:網路 ID 閒置一段時間後才重複使用 #### pt-port-collision · 固定 UDP port 衝突 · Two clients bound to the same local UDP port 用戶端若設計成使用固定的本機 port,同一台電腦的第二個用戶端就無法使用該 port,或與第一個用戶端分食封包。 - 為什麼 → 於是 → 畫面上:兩個用戶端都要開同一個本機 UDP port(用重複使用選項硬是共用) → OS 只把進來的封包交給其中一個 socket,或不保證由哪一個接收。分享器與伺服器也把兩個用戶端看成同一個位址 → 其中一邊收不到世界封包,看不見 NPC 與其他玩家,或斷線 - 症狀:看不見/幽靈物件, 斷線, 連不上/無限讀取/因素:遺失 - 誰會遇到:同一台電腦只有其中一個用戶端/何時:剛登入/維護剛結束, 一直都有 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:本機 port 交由 OS 自動選擇(bind 到 0 號 port)。伺服器:以每條連線核發的 session token 區分連線。 - 圖表上:只有部分偏高(各用戶端的接收封包數) - 查看位置:在玩家電腦上開著兩個用戶端,於命令提示字元執行 netstat -ano -p udp,查看每個遊戲處理程序(PID)開啟的本機 UDP port。伺服器端則確認兩個 session 是否以同一個公用 IP、同一個 port 進來 - 符合的跡象:兩個遊戲處理程序綁在同一個本機 port,或伺服器看到兩個 session 是同一個 IP 與 port。只開一個就正常 - 不符合的跡象:兩個用戶端使用不同的本機 port 卻仍有一邊異常時,是以 IP/裝置區分 session 的 bug 或多開限制 - 確認方式:在玩家端環境確認 - 出處: - [Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE](https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse) · Microsoft · 用 SO_REUSEADDR 對同一個 port 進行第二次 bind 時會搶走該 port,無法得知由哪個 socket 接收封包 - [bind function (winsock.h)](https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind) · Microsoft · 以 port 0 進行 bind 時,會從動態 port 範圍(49152~65535)配置一個唯一的 port - [netstat](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/netstat) · Microsoft · -a 顯示 TCP、UDP port,-n 顯示數字位址,-o 顯示處理程序 ID(PID),-p udp 只顯示 UDP #### pt-session-key · 以 IP/裝置區分 session 的 bug · Session keyed by IP or machine ID 伺服器或中介伺服器以 IP 或裝置 ID 區分連線時,會把同一台電腦(同一個公用 IP)的兩個用戶端視為同一個人。 - 為什麼 → 於是 → 畫面上:session 表以 IP 或 IP + 裝置 ID 建立 → 第二個用戶端的資訊覆蓋或混入第一個 session → 一邊看不見 NPC,另一邊斷線或收到別人的資訊 - 症狀:看不見/幽靈物件, 斷線/因素:遺失 - 誰會遇到:同一台電腦只有其中一個用戶端, 同一個家, 特定地區/電信業者/何時:剛登入/維護剛結束 - 主要負責:遊戲開發團隊(伺服器開發)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:伺服器與中介伺服器都以每條連線唯一的 session token 區分;同一個家(分享器 NAT 後方)的多人,以及電信業者把一個 IP 分給多個用戶的行動網路(CGNAT)玩家也會遇到同樣的問題,務必修正。用戶端:每個執行中的用戶端使用各自取得的 session token。 - 圖表上:只有部分偏高(同一公用 IP 的同時 session 數、session 覆蓋次數) - 查看位置:在伺服器與中介伺服器 log 記錄尋找 session 時使用的 key、session token、用戶端 IP 與 port,查看同一 IP 進來第二個連線的瞬間,既有 session 是否被改變。在同一台電腦上依序開啟兩個用戶端即可重現 - 符合的跡象:第二個用戶端連線的瞬間,第一個 session 的位址或角色資訊改變,且同一台分享器或行動網路(CGNAT)後方的其他玩家也出現同樣的斷線 - 不符合的跡象:同一 IP 的兩個 session 以不同 token 各自維持時,就不是這個原因。兩個處理程序使用同一個本機 port 時,是固定 UDP port 衝突 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [RFC 6269: Issues with IP Address Sharing](https://www.rfc-editor.org/rfc/rfc6269) · IETF · 多個用戶透過 NAT、CGN 共用一個 IPv4 位址時,光靠 IP 無法區分使用者 #### pt-multiclient · 多開限制 · Multi-client restriction policy 安全模組或伺服器政策限制同一台電腦開多個用戶端時,第二個用戶端會無法執行或連線,或者先開的那一個會斷線。有些遊戲只封鎖額外用戶端的功能。 - 為什麼 → 於是 → 畫面上:安全模組偵測到重複執行,或伺服器限制同一裝置的額外連線 → 拒絕第二次執行或連線,或切斷其中一邊。少數情況只封鎖額外用戶端的部分功能 → 連不上,或其中一邊斷線。在只封鎖功能的遊戲中,只有一邊看不見 NPC 或商店 - 症狀:連不上/無限讀取, 看不見/幽靈物件, 斷線/因素:遺失 - 誰會遇到:同一台電腦只有其中一個用戶端/何時:剛登入/維護剛結束 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:若要限制,就顯示明確的提示訊息,並在安全模組設定 QA 用的例外。伺服器:同一裝置連線限制也要設定 QA 用的例外。 - 圖表上:只有部分偏高(依原因分類的連線拒絕、斷線次數(重複連線)) - 查看位置:查看開啟第二個用戶端時出現的訊息,以及先開那一邊的斷線訊息。確認伺服器的連線拒絕、強制斷線 log 是否留下重複連線、同一裝置之類的原因代碼 - 符合的跡象:第二次執行或連線的瞬間跳出拒絕訊息,或先開的那一邊以重複連線為由斷線;只開一個用戶端就沒有問題 - 不符合的跡象:沒有拒絕或斷線原因、兩邊都連上了,卻只有一邊看不見 NPC 時,是固定 UDP port 衝突、以 IP/裝置區分 session 的 bug,或載入、顯示方面的原因 - 確認方式:在玩家端環境確認 - 出處: - [CreateMutexW function (synchapi.h)](https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexw) · Microsoft · 具名 mutex 已存在時會傳回 ERROR_ALREADY_EXISTS,用來偵測重複執行、限制只能單一執行 #### pt-background · 背景視窗的處理限制 · Background window throttling 背景視窗中的用戶端,會被遊戲、引擎、OS 降低畫格數與處理量。收到的封包來不及處理,就會積壓或溢位。 - 為什麼 → 於是 → 畫面上:遊戲選項或顯示卡驅動程式的背景畫格限制(例:NVIDIA 驅動程式可在每秒 20~200 之間指定)、省電、引擎的背景暫停設定。OS 也會優先把 CPU、GPU 分配給前景視窗 → 每個畫格處理的封包數減少,佇列堆積,接收緩衝區溢位時就被丟棄 → 把視窗切到前景時一口氣全部冒出來,或部分 NPC 始終看不見 - 症狀:看不見/幽靈物件, 快轉, 斷線/因素:停滯, 遺失 - 誰會遇到:同一台電腦只有其中一個用戶端/何時:閒置一段時間後, 一直都有 - 主要負責:遊戲開發團隊(用戶端開發)/協同:外部(外部) - 遊戲開發團隊要做的事:網路接收在與遊戲迴圈分開的執行緒持續進行,在背景也保證最低處理量,並開啟引擎的背景執行設定(Unity 為 runInBackground)。 - 外部要做的事:引導玩家關閉顯示卡驅動程式的背景畫格限制與電腦的省電模式。 - 數值參考:Unity 在 runInBackground 設定關閉時,視窗一失去焦點遊戲迴圈就會停止。若只在這個迴圈中接收,這段期間的封包完全不會被處理。 - 圖表上:中斷後一次湧入(用戶端畫格間隔、每個畫格處理的封包數) - 查看位置:在同一台電腦上把一個視窗放前景、另一個放背景,交換角色比較。用 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 會把前景視窗的處理程序優先順序提高到背景處理程序以上 - [PresentMon README](https://raw.githubusercontent.com/GameTechDev/PresentMon/main/README.md) · Intel · 依應用程式收集 Windows 圖形應用程式 CPU、GPU、顯示畫格時間的工具 #### pt-asset-lock · 快取/資源檔案同時存取衝突 · Shared cache / asset file lock conflicts 兩個用戶端同時寫入同一個快取資料夾或鎖定檔案時,其中一邊會載入不了 NPC 模型或貼圖。 - 為什麼 → 於是 → 畫面上:兩個用戶端同時寫入同一個安裝資料夾裡的快取、更新檔案 → 檔案鎖定失敗,或讀到寫到一半的檔案而載入失敗 → 名字還在卻沒有角色模型,或 NPC 是透明的 - 症狀:看不見/幽靈物件/因素:停滯 - 誰會遇到:同一台電腦只有其中一個用戶端/何時:剛登入/維護剛結束, 移動中/切換地圖時 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:每個用戶端使用各自的快取資料夾,檔案鎖定失敗時重試,載入失敗時至少顯示預設模型。 - 圖表上:只有部分偏高(各用戶端的資源載入失敗次數) - 查看位置:在玩家電腦上用 Process Monitor 只篩選遊戲安裝與快取資料夾路徑,查看兩個遊戲處理程序開啟、寫入檔案的結果。有用戶端 log 的話,找出資源載入失敗與檔案開啟錯誤代碼(ERROR_SHARING_VIOLATION) - 符合的跡象:看不見的模型在開啟檔案時以共用違規或鎖定失敗結束,同一時間另一個用戶端正在寫入該檔案。只開一個或把安裝、快取資料夾分開後就消失 - 不符合的跡象:只開一個用戶端也看不見同一個模型時,是檔案損毀或用戶端版本、資料不一致。檔案順利開啟卻畫不出來時,是記憶體/VRAM 不足 - 確認方式:在玩家端環境確認 - 出處: - [Creating and Opening Files](https://learn.microsoft.com/en-us/windows/win32/fileio/creating-and-opening-files) · Microsoft · 未指定共用模式開啟的檔案,其他處理程序無法開啟,會出現 ERROR_SHARING_VIOLATION - [Process Monitor](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon) · Microsoft · 即時記錄檔案系統、登錄檔、處理程序的活動,可以用路徑等任何欄位篩選 #### pt-vram · 記憶體/VRAM 不足導致串流失敗 · Memory / VRAM exhaustion 兩個用戶端分著用顯示記憶體時,沒有空間載入新需要的模型與貼圖,部分物件就畫不出來。 - 為什麼 → 於是 → 畫面上:兩個用戶端分用 VRAM 與 RAM。OS 有時也會先縮減背景視窗的顯示記憶體配額 → 引擎無法載入新的模型與貼圖,或不斷卸載又重新載入 → NPC 很晚才出現、模糊或看不見,畫面卡頓 - 症狀:看不見/幽靈物件, 卡頓/因素:停滯 - 誰會遇到:同一台電腦只有其中一個用戶端/何時:移動中/切換地圖時, 人潮湧入時 - 主要負責:遊戲開發團隊(用戶端開發)/協同:外部(外部) - 遊戲開發團隊要做的事:依記憶體預算自動調整畫質,載入失敗時顯示替代模型。 - 外部要做的事:引導同時開兩個用戶端的玩家降低畫質或使用低規格模式,並說明建議的 VRAM 與 RAM 規格。 - 圖表上:碰到上限後持平(各處理程序的專用 GPU 記憶體使用量) - 查看位置:在玩家電腦的工作管理員「詳細資料」分頁加上專用 GPU 記憶體欄位,把兩個用戶端的使用量總和與顯示卡的 VRAM 容量比較。遊戲端記錄 DXGI 的 QueryVideoMemoryInfo 回報的預算(Budget)與目前使用量(CurrentUsage) - 符合的跡象:兩個用戶端使用量總和在 VRAM 容量附近持平,目前使用量超過預算的時間點,模型與貼圖載入失敗集中出現。降低畫質或只開一個就消失 - 不符合的跡象:VRAM 還有餘裕卻看不見時,是快取/資源檔案同時存取衝突或顯示選項不同 - 確認方式:在玩家端環境確認 - 出處: - [Residency (Direct3D 12)](https://learn.microsoft.com/en-us/windows/win32/direct3d12/residency) · Microsoft · 切換到其他應用程式時,視訊記憶體預算可能大幅縮減,超過預算時會停頓或建立失敗。不在前景時,連保留的部分也不保證 - [GPUs in the task manager](https://devblogs.microsoft.com/directx/gpus-in-the-task-manager/) · Microsoft · 在工作管理員詳細資料分頁加上欄位,就能看到各處理程序的專用與共用 GPU 記憶體使用量。專用 GPU 記憶體就是顯示卡的 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(OS 決定的視訊記憶體預算)與 CurrentUsage(應用程式目前的使用量)。使用量超過預算時可能出現卡頓 #### pt-display-option · 顯示選項不同 · Different display settings 顯示人數上限、隱藏 NPC 名字與模型、低規格模式這類選項,在兩個用戶端設定不同時,看到的東西就不同。 - 為什麼 → 於是 → 畫面上:只有一個用戶端開了「周圍角色顯示數量上限」或低規格模式 → 不繪製遠處或優先順序低的 NPC(正常) → 只有一邊沒有 NPC - 症狀:看不見/幽靈物件/因素:停滯 - 誰會遇到:同一台電腦只有其中一個用戶端, 只有我/何時:人潮湧入時, 一直都有 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:讓玩家看得出是被選項隱藏的物件,並把設定檔依用戶端分開,避免互相混用。 - 圖表上:只有部分偏高(各用戶端畫面上繪製的物件數) - 查看位置:把兩個用戶端的顯示人數上限、名字與模型隱藏、低規格模式設定並排比較,再把一邊調成和另一邊完全相同。也要確認兩個用戶端是否共用同一個設定檔、互相覆蓋 - 符合的跡象:把設定調成相同後兩個畫面就一樣,之前看不見的 NPC 是顯示上限人數以外的遠處物件或優先順序低的物件 - 不符合的跡象:設定調成完全相同仍只有一邊沒有時,是頻道、實例、相位不同或出現通知遺失 - 確認方式:在玩家端環境確認 - 出處: - [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、模型 ID 就略過 → 只有新加入的 NPC 在一邊看不見 - 症狀:看不見/幽靈物件/因素:遺失 - 誰會遇到:同一台電腦只有其中一個用戶端/何時:剛登入/維護剛結束 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:用戶端:連線時送出資料版本,收到未知 ID 時留下 log 並顯示替代物。伺服器:連線時確認資料版本,不同就拒絕連線並引導更新。 - 圖表上:只有部分偏高(依用戶端版本統計收到未知 ID 的次數) - 查看位置:比較兩個用戶端的執行檔路徑,以及畫面與 log 上顯示的用戶端、資料版本。遊戲端記錄連線時送出的資料版本,以及收到未知 NPC、模型 ID 而略過的次數 - 符合的跡象:兩個用戶端的版本或安裝資料夾不同,看不見的 NPC 是最近更新新增的,且在已完成更新的安裝版本上看得到 - 不符合的跡象:版本與安裝資料夾都相同、仍只有一邊沒有時,是頻道、實例、相位不同,或載入、傳遞方面的原因 - 確認方式:在玩家端環境確認 - 出處: - [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),並保證最低更新週期。用戶端:在背景也及時送出接收確認,避免頻寬推估降低。 - 圖表上:隨人數/負載上升(各連線延後的物件數、各連線的傳送量) - 查看位置:在伺服器上依連線記錄每個 tick 的傳送位元組、傳送上限(推估頻寬)、沒送出而延後的物件數、各物件距離上次傳送經過的時間。Unreal 可在 Networking Insights 查看各連線的封包大小與其中包含的複製物件 - 符合的跡象:看不見的 NPC 是在那條連線上被延後很久的物件,該連線的上限低於其他連線,且越擁擠延後的物件越多 - 不符合的跡象:沒有延後的物件、那個 NPC 也及時送出時,問題在傳送之後的環節(接收緩衝區、載入、顯示選項)。所有連線都頂到上限時,是整個伺服器的傳送量或視野設計問題 - 確認方式:需要遊戲伺服器/用戶端的 log 與指標 - 出處: - [Actor Priority in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-priority-in-unreal-engine) · Epic Games · 頻寬飽和時,依優先順序(距離、視線、上次複製後經過的時間)挑選要複製的 actor。所有 actor 不一定每次都會被複製 - [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 · 顯示各連線收發封包的大小,以及其中包含的複製物件與屬性 #### pt-clock-hold · 時鐘推估誤差導致物件暫緩顯示 · Clock estimate error holds or discards entities 用戶端推估的伺服器時間有誤時,會把剛抵達的物件資訊當成「還在未來」而暫緩,或當成「太舊」而丟棄。 - 為什麼 → 於是 → 畫面上:某一個用戶端的伺服器時間推估大幅偏差(在載入中測量、從省電喚醒) → 內插的基準時間與物件資訊的時間對不上 → 物件很晚才出現,或看起來停住不動 - 症狀:看不見/幽靈物件, 卡頓/因素:延遲 - 誰會遇到:同一台電腦只有其中一個用戶端/何時:閒置一段時間後, 剛登入/維護剛結束 - 主要負責:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:定期重新做時間同步,差距大時立即重設,不採用在載入中或剛從省電喚醒時測到的值。 - 圖表上:只有部分偏高(各用戶端的伺服器時間推估誤差) - 查看位置:在用戶端記錄推估的伺服器時間、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 等待重傳的時間,後面的封包在接收緩衝區等候,之後快轉 - 症狀:定格, 快轉, 瞬移/因素:遺失, 抖動 - 誰會遇到:只有我, 同一個家/何時:偶爾隨機發生, 移動中/切換地圖時 - 主要負責:外部(外部)/協同:基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(伺服器開發), 遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:開啟 TCP_NODELAY(Nagle 開著時,RACK 沒有後續封包可用來判斷遺失);重傳卡住期間,待送的狀態更新只保留最新的一筆(用 TCP_NOTSENT_LOWAT 限制累積在 kernel 的量)。用戶端:開啟 TCP_NODELAY(玩家輸入方向的遺失由用戶端 OS 負責復原);遺失集中出現或 ping 急遽升高時,在畫面上顯示網路狀態。 - 基礎設施團隊要做的事:用 RACK-TLP 加快遺失復原(伺服器無法阻止無線遺失,能做的只有加快復原)、確認新版 Linux 的預設值 net.ipv4.tcp_recovery=1(RACK)、net.ipv4.tcp_early_retrans=3(TLP)沒有被改掉。 - 外部要做的事:建議玩家改用有線連線、使用 5GHz 或 6GHz 頻段、調整分享器位置或更換頻道。 - 數值參考:無線遺失率 1% 時,每 100 個遊戲封包就有 1 個消失。每秒收 10 個封包的話,大約每 10 秒就會頓一下。沒有 RACK-TLP 時,每次遺失都要停住一個 RTO(ping + 200ms 以上)的時間。 - 圖表上:只有部分偏高(各連線的重傳率、各連線的 RTT(ping)) - 查看位置:在玩家電腦上分別對分享器(閘道)位址與遊戲伺服器各 ping 數百次,比較遺失與延遲的變動幅度,再改用有線或行動數據重新測量。伺服器端用 ss -ti 查看該玩家連線的 retrans 與 rtt(平均值/偏差) - 符合的跡象:ping 分享器時就已出現遺失或忽高忽低的延遲,改用有線後就消失。從伺服器看,只有該玩家連線的 retrans 與 RTT 偏差偏大 - 不符合的跡象:到分享器為止都正常、過了分享器才開始遺失時,問題在電信業者或路徑端(「瓶頸佇列溢位」、「路由變更、ECMP 不良路徑」)。同一電信業者的多位玩家同時變差時,先查電信業者區段 - 確認方式:在玩家端環境確認 - 深入了解:無線設備的重試會造成抖動(每次重試數 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 · 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 · 行動網路靠鏈路層重傳讓 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 = 200ms - [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 分享器、電信業者之間的互連區段、資料中心線路這類最窄的地方,佇列一滿就會丟棄新進來的封包。 - 為什麼 → 於是 → 畫面上:影片、下載與其他使用者的流量把瓶頸區段塞滿 → 佇列滿的期間,新到的封包接連被丟棄(tail drop)。沒被丟棄的封包也要在塞滿的佇列尾端等待 → 多個封包同時消失,長時間定格後快轉,晚間時段常見 - 症狀:定格, 快轉, 拉回/因素:遺失, 延遲 - 誰會遇到:同一個家, 特定地區/電信業者, 整個伺服器/何時:晚間尖峰時段, 人潮湧入時 - 主要負責:基礎設施團隊(網路基礎設施)/協同:外部(外部), 遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:遺失集中出現或 ping 急遽升高時,在畫面上顯示網路狀態(提示可能是同一條線路上有大量傳輸)。 - 基礎設施團隊要做的事:確保資料中心線路有餘裕、檢查我方線路與交換器 port 的佇列丟棄(output drops)計數器、電信業者區段壅塞時改走其他線路或 peering 繞開。 - 外部要做的事:建議玩家在分享器使用 SQM(fq_codel、CAKE)與 ECN(讓傳送端在佇列溢位前降速)、要求電信業者為瓶頸區段擴充容量。 - 數值參考:佇列溢位的瞬間,數十 ms 內進來的封包會有相當多一起消失。容易連續遺失,連重送的封包也可能遺失,因此常常要等到 RTO 才復原。 - 圖表上:只在特定時段偏高(重傳率、RTT(ping)) - 查看位置:把伺服器重傳率(每 1 分鐘執行一次 nstat 取得的 TcpRetransSegs ÷ TcpOutSegs 增加量)與各連線 RTT 依地區、電信業者、時段分開看,同時查看我方線路與交換器 port 的輸出丟棄(ifOutDiscards)。在尖峰與離峰時段各對問題地區跑 mtr 比較 - 符合的跡象:只有晚間尖峰時重傳率上升,而且遺失前 RTT 先升高(佇列逐漸塞滿的樣子)。mtr 只在尖峰時段從某個躍點起到終點遺失與延遲一起增加 - 不符合的跡象:遺失前 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 標頭中的標記通知壅塞,取代丟棄封包 - [Smart Queue Management](https://www.bufferbloat.net/projects/cerowrt/wiki/Smart_Queue_Management/) · Bufferbloat.net · SQM:同時使用依流排程、佇列長度管理(AQM)與流量整形(shaping)的做法 - [Cake](https://www.bufferbloat.net/projects/codel/wiki/Cake/) · Bufferbloat.net · CAKE:結合 shaper 與 fq_codel 類佇列管理的分享器用 SQM - [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 顯示的 TcpRetransSegs、TcpOutSegs(Tcp 項目下的 RetransSegs、OutSegs) - [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:沒有偵測到錯誤,卻因要騰出緩衝區空間等原因而沒有送出、直接丟棄的封包數 - [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 · 突發傳送造成淺緩衝區溢位 · Sender bursts overflow shallow buffers 伺服器每個 tick 把數千人份的更新在一瞬間集中送出時,交換器的小緩衝區或雲端的瞬間上限不到 1ms 就會溢位,部分封包因此被丟棄。 - 為什麼 → 於是 → 畫面上:每個 tick 開始的瞬間,把要送給所有人的封包一次送出 → 匯集多台伺服器流量的交換器 port 緩衝區(每個 port 數百 KB~數 MB)或雲端執行個體的上限瞬間溢位(平均使用率很低) → 多人同時瞬移、頓一下,看平均指標找不出原因 - 症狀:瞬移, 定格, 快轉/因素:遺失 - 誰會遇到:特定地點/頻道, 整個伺服器/何時:人潮湧入時 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施), 基礎設施團隊(網路基礎設施) - 遊戲開發團隊要做的事:把一個 tick 的傳送分散到整個 tick 內送出(數千條連線在 tick 開始時集中送出,靠各連線的 pacing 很難解決)、錯開各伺服器的 tick 開始時間、傳送大量資料的連線用 SO_MAX_PACING_RATE 設定速度上限。 - 基礎設施團隊要做的事:伺服器設備/OS:設定整台伺服器的傳送速度上限(伺服器 OS 的 shaper,Linux tc),單一連線的集中送出用 pacing(Linux fq 佇列、BBR)打散。網路:採用大緩衝區交換器、以短間隔檢查交換器 port 的輸出丟棄計數器(看平均使用率看不出來)。 - 數值參考:10Gbps port 在 1ms 內能送出的量約 1.25MB。多台伺服器的 tick 剛好重疊、集中到同一個 port 時,緩衝區一下子就滿了。 - 圖表上:隨人數/負載上升(交換器 port 輸出丟棄數、重傳率) - 查看位置:以幾秒的間隔收集伺服器所接交換器 port 及其上層 port 的輸出丟棄(ifOutDiscards),雲端環境則看 ethtool -S 的 bw_out_allowance_exceeded、pps_allowance_exceeded。用 bcc tcpretrans 收集同一時間點的重傳來對照 - 符合的跡象:以分鐘為單位的平均使用率很低,輸出丟棄或 allowance 超出次數卻增加,而且隨同時上線人數與集中在同一處的人數變多。重傳在同一瞬間出現在該伺服器的多條連線上,沒有集中在特定玩家的 IP 網段(電信業者、地區) - 不符合的跡象:同一個 port 的 CRC、輸入錯誤一起增加時是「實體層錯誤」。接收端伺服器的 NIC 丟棄計數器或 softnet dropped 增加時是「接收端伺服器主機丟棄封包」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:Pacing 是每條連線各自運作。數千條連線在 tick 開始時各送一兩個封包所形成的集中,靠各連線的 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 · 資料中心機櫃交換器的突發流量有 70% 以上在數十 µs 內結束,以分鐘為單位的平均使用率與丟棄的關聯很弱(IMC 2017) - [tc-fq(8) — Linux manual page](https://man7.org/linux/man-pages/man8/tc-fq.8.html) · iproute2 · fq 佇列對每個 socket(連線)做 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 訊框大小(最大 64KB,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:沒有偵測到錯誤,卻因要騰出緩衝區空間等原因而沒有送出、直接丟棄的封包數 - [Demonstrations of tcpretrans, the Linux eBPF/bcc version](https://raw.githubusercontent.com/iovisor/bcc/master/tools/tcpretrans_example.txt) · IO Visor · 每次重傳顯示一行,包含對端位址、port 與連線狀態 #### rt-policer · Policer 丟棄超額流量 · Traffic policing 電信業者的資費方案、雲端執行個體的上限、DDoS 防護設備,有時會把超過規定速度的封包直接丟棄,不放進佇列。 - 為什麼 → 於是 → 畫面上:瞬間傳送量超過允許速率或允許突發量 → 超出的封包不經佇列直接丟棄(policing) → 每逢突發量大的瞬間就有多個封包消失,定格後快轉;平均速度看起來低於上限 - 症狀:定格, 快轉, 瞬移/因素:遺失 - 誰會遇到:整個伺服器, 特定地區/電信業者, 只有我/何時:人潮湧入時, 晚間尖峰時段 - 主要負責:基礎設施團隊(網路基礎設施)/協同:基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:把每個 tick 集中送出的量分散到 tick 內,讓瞬間傳送量低於允許突發量;碰到每秒封包數上限時,把一個 tick 的訊息合併成一個封包。 - 基礎設施團隊要做的事:網路:檢查設備的 policer 超出計數器、改用 shaper 取代 policer、調高允許突發量。伺服器設備/OS:檢查雲端上限超出指標(AWS 為 ethtool -S 的 bw_out_allowance_exceeded、pps_allowance_exceeded)、升級執行個體規格、在伺服器上做 pacing(Linux fq 佇列)。 - 數值參考:Shaper(放進佇列延後送出)會增加延遲,policer(立即丟棄)會增加遺失。TCP 遊戲連線遺失一次就可能停住數百 ms,所以只是短暫超過上限的程度時,通常 policer 的影響比較大。 - 圖表上:碰到上限後持平(短間隔的傳送量、policer/allowance 超出計數器) - 查看位置:查看套用 policer 的設備上的超出(exceed)與丟棄計數器,雲端環境則看 ethtool -S 的 bw_out_allowance_exceeded、pps_allowance_exceeded。發生遺失的連線,用 ss -ti 的 rtt 或封包擷取查看遺失前的 RTT - 符合的跡象:超出計數器增加,短間隔的傳送量像被截斷在某個值一樣持平。遺失前 RTT 沒有升高,只有在突發量大的瞬間才有多個封包一起消失 - 不符合的跡象:遺失前 RTT 先升高時是佇列溢位(「瓶頸佇列溢位」、「突發傳送造成淺緩衝區溢位」)。超出計數器沒有變化就是其他原因 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [RFC 2475: An Architecture for Differentiated Services](https://www.rfc-editor.org/rfc/rfc2475) · IETF · 定義:shaping 把封包延後以符合流量規格(traffic profile),policing 則丟棄超出規格的封包 - [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 · ethtool -S 的 bw_out_allowance_exceeded、pps_allowance_exceeded:超過執行個體上限而被放進佇列或丟棄的封包數 - [tc-fq(8) — Linux manual page](https://man7.org/linux/man-pages/man8/tc-fq.8.html) · iproute2 · Linux fq 佇列的每連線 pacing - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · ss -i 的 rtt(平均往返時間) #### rt-physical · 實體層錯誤(線材、光模組、接頭不良) · Bit errors: bad cable, optics, dirty fiber 纜線損壞、沾了灰塵的光纖接頭、壽命將盡的光模組會造成位元錯誤,損壞的封包會被設備默默丟棄。 - 為什麼 → 於是 → 畫面上:線材、光模組、接頭不良導致位元翻轉 → 設備丟棄檢查碼(CRC)不符的封包 → 只有經過該路徑的人持續出現短暫頓一下後快轉,與時段無關 - 症狀:定格, 快轉, 瞬移/因素:遺失 - 誰會遇到:特定地點/頻道, 同一個家/何時:一直都有 - 主要負責:基礎設施團隊(網路基礎設施)/協同:基礎設施團隊(伺服器基礎設施), 外部(外部) - 基礎設施團隊要做的事:CRC 錯誤會累積在出錯方向的接收端,所以兩端都要檢查。網路:檢查設備 port 的 CRC 與輸入錯誤計數器、檢查光訊號強度(交換器的光模組資訊)、清潔光纖接頭、更換線材與光模組。伺服器設備/OS:檢查伺服器 ethtool -S 的 rx_crc_errors(名稱依驅動程式略有不同)、檢查光訊號強度(ethtool -m)、更換伺服器端的線材或 NIC。 - 外部要做的事:問題在玩家家中時,建議更換網路線或分享器;問題在電信業者線路區段時,請電信業者檢修線路。 - 數值參考:即使只有 0.1% 的遺失,也等於每 1,000 個遊戲封包遺失一次。經過該路徑的人有數十人時,每隔幾秒就有人頓一下。封包越大越容易遇上位元錯誤。 - 圖表上:只有部分偏高(各 port 的 CRC 錯誤數、各伺服器/port 的重傳率) - 查看位置:查看鏈路兩端的 CRC 計數器。伺服器看 ethtool -S 的 rx_crc_errors 或 ip -s -s link 的 crc,交換器看 port 的 FCS 錯誤(dot3StatsFCSErrors)與輸入錯誤(ifInErrors)。光纖鏈路則用 ethtool -m 與交換器的光模組資訊查看接收光功率 - 符合的跡象:某個 port 的 CRC 錯誤不分時段持續增加,只有經過該 port 的伺服器與連線重傳率偏高。接收光功率比同類型的其他鏈路低 - 不符合的跡象:CRC 沒有變化、只有輸出丟棄增加時是佇列溢位(「突發傳送造成淺緩衝區溢位」、「瓶頸佇列溢位」)。一端的延遲碰撞(late collision)與另一端的 CRC 一起增加時是「雙工模式不一致」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Interface statistics](https://docs.kernel.org/networking/statistics.html) · Linux kernel · rx_crc_errors 是接收端介面判定為 CRC 錯誤的封包數,可用 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 查看光模組(SFP+、QSFP)的 EEPROM 與光學診斷資訊 - [RFC 3635: Definitions of Managed Objects for the Ethernet-like Interface Types](https://www.rfc-editor.org/rfc/rfc3635) · IETF · 交換器 port 的 FCS 錯誤(dot3StatsFCSErrors),此錯誤會一併計入輸入錯誤(ifInErrors) #### rt-duplex · 雙工模式不一致 · Duplex mismatch 一端設為自動協商、另一端把速度與雙工固定時,其中一端會以半雙工運作,每當負載升高就因碰撞而遺失封包。 - 為什麼 → 於是 → 畫面上:只有其中一台設備把速度與雙工設成固定值 → 一端以全雙工運作、另一端以半雙工運作,發生碰撞與延遲碰撞 → 平常一切正常,流量一增加,經過該設備的所有人都會定格後快轉 - 症狀:定格, 快轉/因素:遺失 - 誰會遇到:整個伺服器, 特定地點/頻道/何時:人潮湧入時, 晚間尖峰時段 - 主要負責:基礎設施團隊(網路基礎設施)/協同:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:兩端都設為自動協商,或兩端都固定為相同的值。網路:在交換器 port 狀態中確認速度與雙工,並在 port 計數器中確認半雙工那一端的延遲碰撞、全雙工那一端的 CRC 錯誤與過短訊框(runt)是否增加。伺服器設備/OS:用 ethtool 確認速度與雙工。 - 數值參考:1Gbps 銅線必須使用自動協商,10Gbps 以上則根本沒有半雙工。所以現在主要發生在 100Mbps 以下的老舊設備、管理用 port,以及部分線路介接區段。 - 圖表上:隨人數/負載上升(port 延遲碰撞與 CRC 錯誤數、重傳率) - 查看位置:查看鏈路兩端實際的速度與雙工。伺服器用只加介面名稱執行的 ethtool,交換器看 port 狀態或 SNMP 的 dot3StatsDuplexStatus。同時查看延遲碰撞(伺服器 tx_window_errors、交換器 dot3StatsLateCollisions)與 CRC 錯誤 - 符合的跡象:一端顯示半雙工,另一端顯示全雙工。每當流量增加,半雙工那一端的延遲碰撞與全雙工那一端的 CRC 錯誤就一起增加 - 不符合的跡象:兩端速度與雙工相同、只有 CRC 增加時是「實體層錯誤」。10Gbps 以上的鏈路沒有半雙工,可排除這個原因 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [Linux Base Driver for Intel(R) Ethernet Network Connection](https://docs.kernel.org/networking/device_drivers/ethernet/intel/e1000.html) · Linux kernel · 1000BASE-T 規格要求自動協商 - [IEEE 802.3ae 10 Gigabit Ethernet: HSSG Objectives](https://www.ieee802.org/3/ae/objectives.pdf) · IEEE · 10 Gigabit 乙太網路只支援全雙工 - [IEEE P802.3ba Objectives](https://www.ieee802.org/3/ba/PAR/P802.3ba_Objectives_0709.pdf) · IEEE · 40 與 100 Gigabit 乙太網路也只支援全雙工 - [Interface statistics](https://docs.kernel.org/networking/statistics.html) · Linux kernel · tx_window_errors 是因延遲碰撞(late collision)而失敗的傳送次數,rx_crc_errors 是收到的 CRC 錯誤封包數 - [ethtool(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ethtool.8.html) · ethtool · 用 ethtool -s 的 speed、duplex、autoneg 設定速度、雙工與自動協商;只給介面名稱時會顯示目前設定 - [RFC 3635: Definitions of Managed Objects for the Ethernet-like Interface Types](https://www.rfc-editor.org/rfc/rfc3635) · IETF · dot3StatsDuplexStatus(以 halfDuplex、fullDuplex 顯示目前的雙工模式)、dot3StatsLateCollisions(延遲碰撞次數) #### rt-host-drop · 接收端伺服器主機丟棄封包 · Receiver host drops (ring, softirq, CPU) 封包已經到了伺服器,卻因 NIC 的 ring buffer(暫存剛抵達封包的緩衝區)溢位,或 kernel 負責接收處理的 CPU 核心滿載而被丟棄。 - 為什麼 → 於是 → 畫面上:連線人數暴增、中斷集中在單一核心、虛擬機器 CPU steal、虛擬交換器過載 → 在 ring buffer(rx_missed_errors 等,名稱依驅動程式而異)或 kernel 接收佇列(softnet dropped)被丟棄 → 人潮湧入時,整個伺服器同時出現輸入慢半拍才生效、頓一下 - 症狀:輸入延遲, 定格, 快轉, 瞬移/因素:遺失, 停滯 - 誰會遇到:整個伺服器/何時:人潮湧入時 - 主要負責:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:把 ethtool -S 的 rx_missed_errors 這類丟棄計數器與 /proc/net/softnet_stat 的 dropped 加入監控、加大 ring buffer(ethtool -G)、把 RSS 與中斷分散到多個核心、把遊戲執行緒與接收處理核心分開、確保 CPU 有餘裕、虛擬機器則檢查 CPU steal 與虛擬交換器負載。 - 數值參考:伺服器在接收時丟棄的封包,會由用戶端重送。因此伺服器的重傳指標不太會反映出來,會先出現在 ethtool -S 的 rx_missed_errors 這類丟棄計數器(名稱依驅動程式而異)與 /proc/net/softnet_stat 的 dropped。 - 圖表上:碰到上限後持平(各核心的 softirq 使用率、NIC 丟棄計數器) - 查看位置:查看 ethtool -S 的丟棄計數器(rx_missed_errors 等,mlx5 為 rx_out_of_buffer、rx_discards_phy)、ip -s -s link 的 missed、/proc/net/softnet_stat 的第 2 欄(dropped)與第 3 欄(time_squeeze),並用 mpstat -P ALL 查看各核心的 %soft(軟體中斷處理)。虛擬機器也要看 %steal - 符合的跡象:人潮湧入時丟棄計數器或 softnet dropped 增加,負責接收處理的核心 %soft 接近 100% 而無法再往上。該伺服器的所有連線同時出現輸入變慢 - 不符合的跡象:伺服器的丟棄計數器沒有變化,重傳集中在特定地區或電信業者的連線時,是路徑上的遺失。伺服器送出的封包在路徑上遺失時,伺服器 nstat 的 TcpRetransSegs 會增加,這些計數器則維持不變 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [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 的計數器名稱由驅動程式決定(例:igb 的 rx_missed_errors、rx_no_buffer_count) - [Ethtool counters](https://docs.kernel.org/networking/device_drivers/ethernet/mellanox/mlx5/counters.html) · Linux kernel · mlx5 驅動程式的 rx_out_of_buffer(接收佇列沒有緩衝區)、rx_discards_phy(因 port 緩衝區不足而丟棄) - [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:封包進來的速度比 kernel 處理得快時,用來暫存的接收佇列上限 - [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 · 用 -G(--set-ring)變更 ring buffer 大小 - [proc_stat(5) — Linux manual page](https://man7.org/linux/man-pages/man5/proc_stat.5.html) · Linux man-pages · /proc/stat 的 steal:在虛擬化環境中,其他 OS 使用 CPU 的時間 - [mpstat(1) — Linux manual page](https://man7.org/linux/man-pages/man1/mpstat.1.html) · sysstat · 用 mpstat -P ALL 查看各核心使用率,%soft 是處理軟體中斷的時間,%steal 是 hypervisor 忙著處理其他虛擬 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 · nstat 顯示的 TcpRetransSegs(Tcp 項目下的 RetransSegs) #### rt-stateful-fw · 防火牆與連線追蹤丟棄封包 · Stateful firewall / conntrack drops 防火牆或 Linux 連線追蹤(conntrack,把經過的連線記錄在表中的功能),在表已滿或判斷連線狀態不符時,會丟棄封包。 - 為什麼 → 於是 → 畫面上:連線追蹤表已滿(table full),或去回程路徑不同,只有單一方向經過防火牆(非對稱路由) → 防火牆把封包視為「未知連線」或「序號超出視窗範圍」而丟棄 → 表滿了之後新連線會被擋下;路徑不一致時,只有走該路徑的人在反覆重傳後斷線 - 症狀:定格, 斷線, 連不上/無限讀取/因素:遺失 - 誰會遇到:整個伺服器, 特定地區/電信業者/何時:人潮湧入時, 剛登入/維護剛結束, 偶爾隨機發生 - 主要負責:基礎設施團隊(網路基礎設施)/協同:基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(伺服器開發), 遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:為了因應表滿的情況,用登入排隊系統控制湧入的連線、重複使用連線以免反覆建立短連線(包括伺服器之間的呼叫)、心跳封包已中斷的連線要主動先清掉。用戶端:連線失敗或中斷時,逐步拉長重試間隔並隨機分散(避免表滿時所有人又一起湧入)。 - 基礎設施團隊要做的事:網路:加大防火牆的連線追蹤表、讓遊戲 port 不做連線追蹤、調整路由讓往返路徑經過同一台防火牆、確認防火牆的 TCP 視窗檢查設定。伺服器設備/OS:加大 Linux 的表(nf_conntrack_max)、讓遊戲 port 不做連線追蹤(NOTRACK)、確認 TCP 視窗檢查設定(nf_conntrack_tcp_be_liberal)、AWS 另外確認 conntrack_allowance_exceeded。 - 數值參考:Linux conntrack 的預設上限(nf_conntrack_max)依記憶體大小為數萬~數十萬筆。目前筆數(nf_conntrack_count)達到上限時,log 會留下「nf_conntrack: table full, dropping packet」。 - 圖表上:碰到上限後持平(conntrack 項目數(nf_conntrack_count)、新連線失敗數) - 查看位置:Linux 伺服器看 nf_conntrack_count 與 nf_conntrack_max、dmesg 的「nf_conntrack: table full, dropping packet」、/proc/net/stat/nf_conntrack 的 drop 與 invalid(每個核心一行,16 進位)。防火牆看 session 表使用量與丟棄 log,AWS 看 ethtool -S 的 conntrack_allowance_exceeded - 符合的跡象:項目數在上限值持平,同一時間 table full log 與 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 只把視窗外的 RST 視為 INVALID - [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 · 表滿時會留下「nf_conntrack: table full, dropping packet」log 並丟棄(drop 統計增加),不符合連線狀態的封包則讓 invalid 統計增加 - [iptables-extensions(8) — Linux manual page](https://man7.org/linux/man-pages/man8/iptables-extensions.8.html) · netfilter · 用 raw 表的 CT --notrack 排除在連線追蹤之外 - [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 或每秒封包數達到上限,封包在設備上被丟棄。誤判時連正常封包也會被擋 → 該設備後方的所有伺服器同時出現定格、瞬移,只在人潮湧入時加劇 - 症狀:定格, 快轉, 瞬移, 斷線/因素:遺失, 延遲 - 誰會遇到:整個伺服器, 特定地區/電信業者/何時:晚間尖峰時段, 人潮湧入時 - 主要負責:基礎設施團隊(網路基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:把遊戲的流量模式(port、封包大小、每秒封包數)提供給基礎設施團隊、把一個 tick 內要送的小訊息合併後一次送出,減少封包數。 - 基礎設施團隊要做的事:把設備的 CPU、每秒封包數、丟棄計數器與遊戲指標放在一起看、以小封包為基準規劃設備容量、讓遊戲 port 不經過重度檢查、讓 DDoS 防護規則配合遊戲流量模式。 - 數值參考:設備規格上的「10Gbps」,很多是以 1,500 位元組的大封包為基準標示的。100 位元組左右的遊戲封包在相同頻寬下,封包數多出 10 倍以上,所以即使線路看起來很空,每秒封包數上限也會先被用完。 - 圖表上:碰到上限後持平(設備每秒封包數與 CPU 使用率、設備丟棄數) - 查看位置:查看設備的 CPU、每秒封包數與丟棄計數器,並以相同間隔比較設備前後交換器 port 的封包數。與同時上線人數、伺服器重傳率疊在同一個畫面上看 - 符合的跡象:尖峰或活動時,設備的每秒封包數或 CPU 停在某個值無法再往上,從設備出來的封包比進去的少,同一時間該設備後方所有伺服器的重傳率一起上升 - 不符合的跡象:設備前後的封包數相同、設備也沒有丟棄時是其他原因。伺服器的 NIC 丟棄計數器或 softnet dropped 增加時是「接收端伺服器主機丟棄封包」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [RFC 2544: Benchmarking Methodology for Network Interconnect Devices](https://www.rfc-editor.org/rfc/rfc2544) · IETF · 設備效能必須以包含最小與最大尺寸在內的多種訊框大小測試(處理效能會隨封包大小而不同) #### rt-mtu · MTU 黑洞(只有大封包反覆遺失) · PMTU black hole 中途區段能接收的大小變小,而「封包太大」的通知(ICMP)又被擋掉時,大封包不管重送幾次都會一直消失。 - 為什麼 → 於是 → 畫面上:VPN 或通道區段的最大大小變小,大小超過的通知被防火牆擋掉 → 傳送端不知道原因,持續重傳同一個大封包,RTO 每次加倍 → 平常一切正常,但在背包、人多的地方、進場載入這類有大量資料往來的瞬間,連後面的小封包也全部卡住而定格,最後斷線或無限讀取 - 症狀:定格, 斷線, 連不上/無限讀取/因素:遺失 - 誰會遇到:特定地區/電信業者, 只有我/何時:做特定動作時, 剛登入/維護剛結束 - 主要負責:基礎設施團隊(網路基礎設施)/協同:基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:要從伺服器端直接調低,就設定 socket 的最大區段大小(TCP_MAXSEG);只在遊戲程式碼中把訊息切小並不能避免(TCP 會把要送的資料重新依 MSS 大小打包)。 - 基礎設施團隊要做的事:網路:在邊界設備做 MSS 調整(clamping)、在防火牆與雲端網路 ACL 允許大小超過的 ICMP(類型 3 代碼 4,fragmentation needed)。伺服器設備/OS:設定路徑 MTU、確認伺服器防火牆與雲端安全群組也沒有擋掉大小超過的 ICMP、以 Linux tcp_mtu_probing=1 作為最後一道防線。 - 數值參考:通常是 1,500 位元組,經過通道後為 1,400 左右。同一個封包重傳 5~6 次,定格就會超過 10 秒。 - 圖表上:只有部分偏高(各連線的 RTO 與 backoff、各地區/電信業者的斷線次數) - 查看位置:用伺服器端的封包擷取或 bcc tcpretrans -s(顯示序號)查看問題連線的重傳,並用 ss -ti 查看該連線的 mss、pmtu、backoff。從伺服器對該玩家位址分別送出小 ping 與開啟 DF 的 1,500 位元組 ping(ping -M do -s 1472)比較 - 符合的跡象:塞滿 MSS 的封包以相同序號持續重傳,間隔每次加倍,比它小的封包則能通過。沒有收到大小超過的 ICMP(Wireshark 篩選條件 icmp.type == 3 and icmp.code == 4),小 ping 有回應,只有大的 DF ping 沒有回應就消失 - 不符合的跡象:小封包也一起消失時,是與大小無關的遺失(「瓶頸佇列溢位」、「路由變更、ECMP 不良路徑」)。收到大小超過的 ICMP 且 ss -ti 的 pmtu 變小時,代表路徑 MTU 探索正常運作 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:tcp_mtu_probing=1 要等重傳逾時持續幾秒(相當於 tcp_retries1=3)之後,才會判定為黑洞,並把 MSS 降到 1,024 位元組。這段期間連線會停住,所以只把它當作最後一道防線,優先採用事先預防的 MSS 調整。 - 出處: - [RFC 1191: Path MTU discovery](https://www.rfc-editor.org/rfc/rfc1191) · IETF · 路徑 MTU 探索:過大的封包會以 ICMP「fragmentation needed and DF set」(類型 3 代碼 4)通知 - [RFC 2923: TCP Problems with Path MTU Discovery](https://www.rfc-editor.org/rfc/rfc2923) · IETF · ICMP 被擋,導致只有大封包一直消失的 PMTU 黑洞問題 - [RFC 4821: Packetization Layer Path MTU Discovery](https://www.rfc-editor.org/rfc/rfc4821) · IETF · 不靠 ICMP、由傳輸層探索封包大小的方法(Linux tcp_mtu_probing 的基礎) - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_mtu_probing:0 為關閉,1 為只在偵測到黑洞時開啟,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 次時,視為偵測到黑洞,開啟 MTU 探索並調低 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:調整 SYN 中的 MSS,避開中途區段擋掉 ICMP 而讓大封包卡住的問題 - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · TCP_MAXSEG:送出封包的最大區段大小,在連線前設定時,告知對方的 MSS 也會跟著改變 - [Network maximum transmission unit (MTU) for your EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/network_mtu.html) · AWS · 網際網路閘道與 VPN 的 MTU 為 1,500,PMTUD 需要 ICMP 類型 3 代碼 4,被安全群組或網路 ACL 擋掉就收不到 - [MTU considerations | Cloud VPN](https://cloud.google.com/network-connectivity/docs/vpn/concepts/mtu-considerations) · Google Cloud · Cloud VPN 閘道 MTU 為 1,460 位元組,IPv4 通道的酬載 MTU 為 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 會一併顯示重傳封包的序號 - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · ss -i 的 mss、pmtu(路徑 MTU)、backoff(RTO 加倍的次數) - [ping(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ping.8.html) · iputils · -M do 會開啟 DF,不送出大於 kernel 所知路徑 MTU 的封包;-s 為資料大小(另加 ICMP 標頭 8 位元組) - [Display Filter Reference: Internet Control Message Protocol](https://www.wireshark.org/docs/dfref/i/icmp.html) · Wireshark · icmp.type、icmp.code 顯示篩選條件 #### rt-mapping · 連線途中 NAT 或負載平衡器的 mapping 過期 · NAT / load balancer mapping expired mid-connection 中途設備刪除閒置連線的 mapping(記錄這條連線要轉送到哪裡的項目)後,接下來送出的封包就無法送達。連線會不斷重傳直到斷線,或是設備回傳拒絕連線(RST)而立刻斷線。 - 為什麼 → 於是 → 畫面上:有一段時間沒有任何封包往來的連線(暫離、大廳) → 分享器 NAT、電信業者 CGNAT、防火牆、負載平衡器、雲端安全群組刪除閒置的 mapping → 再次移動的瞬間接連重傳,最後斷線,或是直接斷線 - 症狀:斷線, 定格/因素:遺失 - 誰會遇到:只有我, 特定地區/電信業者/何時:閒置一段時間後 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發), 基礎設施團隊(網路基礎設施), 基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:用戶端:以最短閒置逾時的一半以下為間隔送出心跳封包(玩家分享器與電信業者 CGNAT 的 mapping 只有從內部送出的封包才能確實更新,逾時時間我方也無法更改,所以由用戶端送出)、斷線時自動重新連線。伺服器:回應心跳封包,一段時間沒收到就主動先清掉連線(縮短 TCP keepalive 間隔(TCP_KEEPIDLE 等 socket 選項)、用 TCP_USER_TIMEOUT 快速偵測)、用 session token 接續連線。 - 基礎設施團隊要做的事:網路:彙整路徑上防火牆與負載平衡器的閒置逾時並提供給遊戲開發團隊,我方的防火牆與負載平衡器視需要調長。伺服器設備/OS:確認雲端安全群組的連線追蹤時間並提供給遊戲開發團隊。 - 數值參考:TCP mapping 的保留時間依設備而異,從幾分鐘到幾小時都有。雲端安全群組設為追蹤連線時,AWS Nitro v6 執行個體類型預設在 350 秒後刪除追蹤項目(其他類型為 5 天,請參考「雲端安全群組的連線追蹤過期」)。Linux TCP keepalive 的預設值是「閒置 2 小時才確認」,比大多數設備都晚。 - 圖表上:連線同時大量中斷(斷線次數、斷線前的閒置時間) - 查看位置:用伺服器端的封包擷取查看斷線連線的最後幾分鐘,仍存活的連線則用 ss -ti 的 lastsnd、lastrcv(距離最後一次傳送、接收經過的 ms)查看閒置時間。同時查看 nstat 的 TcpExtTCPAbortOnTimeout(計時器到期而放棄連線的次數) - 符合的跡象:每條斷線的連線在斷線前的閒置時間都超過相近的值(路徑上設備的閒置逾時,例:AWS Nitro v6 執行個體安全群組的 350 秒),閒置後從第一個封包起就只有重傳、沒有 ACK,最後放棄,或是立刻收到 RST - 不符合的跡象:與閒置時間無關、遊戲進行中也會斷線時,是其他原因(「路由變更、ECMP 不良路徑」、「防火牆與連線追蹤丟棄封包」)。心跳封包以最短閒置逾時的一半以下為間隔往來的連線,可排除這個原因 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [RFC 5382: NAT Behavioral Requirements for TCP](https://www.rfc-editor.org/rfc/rfc5382) · IETF · 建議 TCP NAT 的已建立連線閒置逾時應在 2 小時 4 分鐘以上(前提是設備可能會先刪除閒置的 session) - [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 閒置追蹤逾時的預設值,Nitro v6 執行個體類型為 350 秒,其他類型為 432,000 秒(5 天)。建議以短於 5 分鐘的間隔送 keepalive - [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(開始 keepalive 前的閒置時間)、TCP_USER_TIMEOUT(等待未確認的資料多久後關閉連線) - [RFC 5482: TCP User Timeout Option](https://www.rfc-editor.org/rfc/rfc5482) · IETF · TCP 使用者逾時:送出的資料在未獲確認的狀態下經過多久就關閉連線 - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · ss -i 的 lastsnd、lastrcv:距離最後一次傳送與接收所經過的時間(ms) - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtTCPAbortOnTimeout:TCP 計時器到期,沒有送出 RST 就放棄連線的次數 #### rt-path · 路由變更、ECMP 不良路徑 · Route change / bad ECMP member 網際網路路由變更的那幾秒內,或是多條 ECMP 路徑中被分配到不良路徑的連線,會有封包消失。 - 為什麼 → 於是 → 畫面上:BGP 重新計算路由,或多條路徑(ECMP、LAG)中某一條的設備或線路不良 → 切換路由時暫時遺失,或只有走該路徑的連線持續遺失 → 突然定格幾秒後快轉,或是「重新連線就變好」(被分配到其他路徑) - 症狀:定格, 快轉, 瞬移/因素:遺失 - 誰會遇到:特定地區/電信業者/何時:偶爾隨機發生 - 主要負責:基礎設施團隊(網路基礎設施)/協同:遊戲開發團隊(伺服器開發), 外部(外部) - 遊戲開發團隊要做的事:記錄各連線的重傳統計(TCP_INFO),以便找出受影響玩家的 IP、port 與時間點、停住幾秒的連線不要立刻切斷。 - 基礎設施團隊要做的事:監控各地區、各電信業者的重傳率、確認重新連線後路徑是否改變、準備多家電信業者的線路、檢查我方設備 ECMP 與 LAG 路徑中的不良鏈路、路徑量測也使用與遊戲相同的 TCP port(mtr --tcp --port。路徑依位址與 port 決定,一般 ping 可能走不同的路徑而顯示正常)。 - 外部要做的事:向電信業者回報不良路徑,並附上以相同 TCP port 量測的路徑結果與重新連線前後的比較。 - 圖表上:從某個時間點起階梯式上升(RTT(ping)、各地區/電信業者的重傳率) - 查看位置:用 bcc tcpretrans -c 依連線彙整重傳,找出受影響玩家的位址與 port,再以與遊戲相同的 TCP port,分別從伺服器往玩家、從玩家往伺服器跑 mtr(mtr -T -P PORT)比較。重新連線前後的結果也要比較 - 符合的跡象:以某個時間點為分界,某一地區或電信業者的 RTT 像階梯一樣改變,並集中出現幾秒的遺失;或是在同一電信業者內,也只有部分連線(位址與 port 的組合)持續重傳,重新連線就變好。有時一般 ping 正常,只有 TCP mtr 看得到遺失 - 不符合的跡象:該電信業者的所有連線在晚間尖峰一起變差時是「瓶頸佇列溢位」。只有一名玩家變差、從 ping 分享器就開始遺失時是「無線區段的封包遺失」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 實際案例:cloudflare-2020 - 出處: - [RFC 2991: Multipath Issues in Unicast and Multicast Next-Hop Selection](https://www.rfc-editor.org/rfc/rfc2991) · IETF · 說明在多路徑環境下,ping、traceroute 這類診斷工具的結果難以採信,以及將流(flow)雜湊後固定路徑的做法 - [RFC 2992: Analysis of an Equal-Cost Multi-Path Algorithm](https://www.rfc-editor.org/rfc/rfc2992) · IETF · ECMP 以區分流的標頭欄位雜湊值選擇下一段路徑(同一個流走同一條路徑) - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · TCP_INFO:查詢各 socket 的狀態(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)指定目標 port - [Demonstrations of tcpretrans, the Linux eBPF/bcc version](https://raw.githubusercontent.com/iovisor/bcc/master/tools/tcpretrans_example.txt) · IO Visor · 每次重傳顯示一行,包含對端位址與 port;-c 會依流彙總重傳次數 #### rt-spurious-delay · 延遲飆升造成的不必要重傳 · Spurious RTO from delay spikes 封包沒有消失,只是短暫地很晚才到;但這段延遲比 RTO 長時,傳送端就會判斷為遺失而重傳。 - 為什麼 → 於是 → 畫面上:bufferbloat、Wi-Fi 省電、行動網路無線狀態切換、虛擬機器暫停,造成瞬間延遲達數百 ms → RTO 先到期而重傳,原本的封包也隨即抵達(接收端收到重複的封包) → 定格與快轉是延遲飆升本身造成的。不必要的重傳幾乎不會拉長定格,只會推高重傳指標,因而被誤認為遺失 - 症狀:定格, 快轉, 輸入延遲/因素:延遲, 抖動 - 誰會遇到:只有我, 整個伺服器/何時:偶爾隨機發生, 閒置一段時間後 - 主要負責:外部(外部)/協同:基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:Android 10 以上的用戶端在遊戲中要求低延遲 Wi-Fi 模式(WIFI_MODE_FULL_LOW_LATENCY Wi-Fi lock,只在螢幕開啟且遊戲位於前景時生效),減少省電造成的延遲飆升。 - 基礎設施團隊要做的事:避免使用突發型(burstable)執行個體、不要把 RTO 最小值調得太低、保持 F-RTO 與時間戳記開啟(tcp_frto、tcp_timestamps)、重傳指標要與 nstat 的 TCPSpuriousRTOs、TCPDSACKRecv 一起看,以免誤認為遺失。 - 外部要做的事:為了減少延遲飆升本身,建議玩家在分享器使用 SQM、關閉 Wi-Fi 省電。 - 數值參考:Linux 會用 F-RTO 偵測不必要的 RTO,有時也會撤回已降低的傳送量。可用 nstat 的 TCPSpuriousRTOs(判定為不必要 RTO 的次數)與 TCPDSACKRecv(接收端回報「已經收過」的次數)確認。 - 圖表上:偶爾隨機飆高(RTT(ping)、不必要的 RTO 次數) - 查看位置:每 1 分鐘執行一次 nstat,一起查看 TcpExtTCPTimeouts(RTO 到期)、TcpExtTCPSpuriousRTOs、TcpExtTCPDSACKRecv、TcpExtTCPLostRetransmit 的增加量。有封包擷取時用 Wireshark 篩選條件 tcp.analysis.spurious_retransmission - 符合的跡象:RTO 增加時,TcpExtTCPSpuriousRTOs 或 TcpExtTCPDSACKRecv 也一起增加,同一時間 RTT 飆到數百 ms。接收端的封包擷取中,原本的封包與重傳的封包都有抵達 - 不符合的跡象:TcpExtTCPSpuriousRTOs 與 DSACK 沒有變化,TcpExtTCPLostRetransmit(連重送的封包也再次遺失)卻增加時,是實際遺失。RTT 沒有飆高、只有 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 之後收到的 ACK,判斷該次 RTO 是否不必要 - [RFC 3481: TCP over Second (2.5G) and Third (3G) Generation Wireless Networks](https://www.rfc-editor.org/rfc/rfc3481) · IETF · 行動網路的延遲飆升(換手、鏈路復原等)會引發不必要的 TCP 逾時與重傳,並使壅塞視窗縮小 - [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(F-RTO 偵測到的不必要 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):只在連上 AP、螢幕開啟且 App 位於前景時才生效的低延遲 Wi-Fi lock - [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 · tcp.analysis.spurious_retransmission 顯示篩選條件 #### rt-reorder · 封包亂序造成的不必要快速重傳 · Reordering triggers spurious fast retransmit 封包經過多條路徑或聚合鏈路時順序被打亂,接收端會以重複 ACK 告知「有封包缺漏」,傳送端就把其實已正常送達的封包再送一次。 - 為什麼 → 於是 → 畫面上:依封包分配路徑的設備、依封包分散傳送的 LAG(鏈路聚合)、路由切換的瞬間,都會打亂封包順序 → 後面的封包先到,累積 3 個重複 ACK → 快速重傳 → 零星往來的遊戲封包幾乎不受影響。人多處的大型狀態更新與更新檔下載會變慢,偶爾出現卡頓 - 症狀:卡頓, 輸入延遲/因素:抖動 - 誰會遇到:特定地區/電信業者, 整個伺服器/何時:一直都有, 人潮湧入時 - 主要負責:基礎設施團隊(網路基礎設施)/協同:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:網路:改以連線為單位分散,不再以封包為單位分散(ECMP、LAG 依位址與 port 雜湊)。伺服器設備/OS:使用 RACK(以時間判斷遺失,對亂序的容忍度高;透過 DSACK 偵測到不必要的重傳時,會自動放寬亂序容許範圍)、確認 Linux 對每條連線自動估計的亂序程度(ss -ti 的 reordering 值,起始值為 tcp_reordering=3)。 - 圖表上:一開始就一直偏高(亂序偵測次數、DSACK 接收數) - 查看位置:查看 nstat 的 TcpExtTCPSACKReorder、TcpExtTCPTSReorder(偵測到亂序的次數)與 TcpExtTCPDSACKRecv,各連線則看 ss -ti 的 reordering(值不為 3 時才顯示)與 reord_seen。封包擷取則用 Wireshark 篩選條件 tcp.analysis.out_of_order - 符合的跡象:亂序計數器與 DSACK 不分時段持續上升,經過特定路徑或設備的連線 reordering 值大於 3。接收端的封包擷取中,後面的封包先到,前面的封包也隨即抵達 - 不符合的跡象:亂序計數器沒有變化、TcpExtTCPLostRetransmit 增加時,是實際遺失。只在 RTT 飆高的瞬間 DSACK 增加時,是「延遲飆升造成的不必要重傳」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [RFC 2991: Multipath Issues in Unicast and Multicast Next-Hop Selection](https://www.rfc-editor.org/rfc/rfc2991) · IETF · 依封包分配路徑會使順序改變,後面的封包有 3 個以上先到時,TCP 就會進行不必要的快速重傳 - [RFC 2992: Analysis of an Equal-Cost Multi-Path Algorithm](https://www.rfc-editor.org/rfc/rfc2992) · IETF · 以區分流的標頭欄位雜湊值選擇路徑的 ECMP(以流為單位分散) - [RFC 5681: TCP Congestion Control](https://www.rfc-editor.org/rfc/rfc5681) · IETF · 在第三個重複 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)、tcp_recovery 的 RACK 設定 - [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 值與預設值 3 不同時顯示 reordering:值,曾發生過亂序時顯示 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 · tcp_info 的 tcpi_reord_seen:連線遇到亂序的次數 - [Display Filter Reference: Transmission Control Protocol](https://www.wireshark.org/docs/dfref/t/tcp.html) · Wireshark · tcp.analysis.out_of_order 顯示篩選條件 #### rt-ack-path · ACK 延遲或消失(上傳飽和) · ACK path congestion on asymmetric links 資料已順利送達,但「收到了」的 ACK 在塞滿的上傳佇列裡延遲或消失時,傳送端會判斷為遺失而重傳。 - 為什麼 → 於是 → 畫面上:家中有人上傳影片或做雲端備份,把上傳頻寬塞滿 → ACK 在分享器佇列裡延遲數百 ms,或因溢位而被丟棄 → 伺服器送來的遊戲封包大致準時到達。自己的輸入堆在同一個上傳佇列裡而晚送出,造成輸入延遲、拉回,偶爾出現不必要的重傳 - 症狀:輸入延遲, 拉回/因素:延遲, 遺失 - 誰會遇到:同一個家/何時:偶爾隨機發生, 晚間尖峰時段 - 主要負責:外部(外部)/協同:遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:ping 急遽升高時在畫面上顯示網路狀態、跳出「請檢查正在上傳的程式」提示。 - 外部要做的事:建議玩家用分享器的 SQM 縮短上傳佇列、優先處理小封包(ACK)、限制上傳速度(影片上傳、雲端備份)。 - 數值參考:後面的 ACK 會代替前面的 ACK 完成確認,所以少了幾個通常沒關係。真正的問題是 ACK 在佇列裡被延遲。 - 圖表上:只有部分偏高(各連線的 RTT(ping)) - 查看位置:在玩家電腦上,分別在開啟與關閉上傳(影片上傳、雲端備份)的狀態下 ping 遊戲伺服器比較。伺服器端用 ss -ti 查看該玩家連線的 rtt - 符合的跡象:只有上傳期間 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 · 在分享器以佇列管理與流量整形讓佇列保持短小 - [tc-cake(8) — Linux manual page](https://man7.org/linux/man-pages/man8/tc-cake.8.html) · iproute2 · CAKE 會把流分開,讓零星傳送的流(sparse flow)延遲降到最低 - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · ss -i 的 rtt(平均往返時間)與 rttvar(偏差) #### rt-rto-setting · RTO 設定不符合環境 · RTO min too low or too high RTO 最小值調得太低時,只要稍微延遲就會產生不必要的重傳;預設值(200ms)對遊戲來說又太長,每遺失一次就要停住很久。 - 為什麼 → 於是 → 畫面上:為了資料中心環境把 RTO 最小值大幅調低,或在網際網路區段直接沿用預設值 → 太低時,瞬間延遲也會引發大量重傳;太高時,每次遺失都要等很久 → 預設值時,遺失一次就定格數百 ms 後快轉;調得太低時定格變短,但不必要的重傳暴增,浪費線路頻寬 - 症狀:定格, 快轉, 輸入延遲/因素:延遲 - 誰會遇到:整個伺服器/何時:一直都有 - 主要負責:基礎設施團隊(伺服器基礎設施)/協同:遊戲開發團隊(伺服器開發) - 遊戲開發團隊要做的事:Linux 6.15 以上時,評估在遊戲連線用 TCP_RTO_MAX_MS 調低 RTO 上限(放棄連線前的時間也會跟著縮短,所以要同時用 TCP_USER_TIMEOUT 設定斷線判定時間)、只對伺服器之間的內部連線用 socket 選項 TCP_RTO_MIN_US(6.15 以上)調低 RTO 最小值、評估用 socket 選項 TCP_THIN_LINEAR_TIMEOUTS 讓遊戲連線的連續 RTO 不再加倍。 - 基礎設施團隊要做的事:只對伺服器之間的內部連線依路由調低 rto_min,網際網路區段維持預設值,改用 RACK-TLP 與 thin stream 設定(tcp_thin_linear_timeouts)補強。 - 數值參考:Linux RTO = 往返時間 + max(200ms, RTT 偏差×4)。每失敗一次就加倍,最大 120 秒。Linux 6.15 以上可用 TCP_RTO_MAX_MS 把這個上限最低降到 1 秒。 - 圖表上:一開始就一直偏高(各連線的 RTO、不必要的 RTO 次數) - 查看位置:查看伺服器的 RTO 最小值設定(ip route show 的 rto_min,Linux 6.11 以上為 sysctl net.ipv4.tcp_rto_min_us)與 ss -ti 的 rto、rtt,並查看 nstat 的 TcpExtTCPSpuriousRTOs 增加量 - 符合的跡象:調低最小值的伺服器上,網際網路連線的 rto 緊貼著 rtt,TcpExtTCPSpuriousRTOs 大量增加。維持預設值時,遊戲連線的 rto 比 rtt 大 200ms 以上,每遺失一次就停住那麼久 - 不符合的跡象:rto 符合預設計算(rtt + 200ms 左右)、不必要的 RTO 也少,停頓卻特別長時,問題在連續遺失或復原方式(「thin stream 復原緩慢」、「中間設備移除 TCP 選項」) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 出處: - [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 · Linux TCP_RTO_MIN 200ms、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 · Linux 的 RTO 為 SRTT + rttvar,rttvar 不會低於 RTO 最小值(預設 200ms) - [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 · 新增 TCP_RTO_MAX_MS socket 選項(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 · 新增整台伺服器的預設 RTO 最小值 tcp_rto_min_us,自 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 設定 RTO 最小值的 TCP_RTO_MIN_US socket 選項,自 Linux 6.15 起 - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_rto_min_us 預設 200000(路由選項 rto_min、socket 選項 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 可以只對 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 · ss -i 的 rto(ms)與 rtt - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtTCPSpuriousRTOs:F-RTO 偵測到的不必要 RTO #### rt-thin · thin stream 復原緩慢 · Thin streams fall back to RTO 像遊戲這樣零星送出小封包時,在湊齊「後續 3 個封包」之前 RTO 就先到了。同樣的遺失,停住的時間比大量傳輸長得多。 - 為什麼 → 於是 → 畫面上:封包間隔約 100ms,尚未收到 ACK 的封包(in-flight)沒有幾個 → 要湊齊 3 個重複 ACK 得花 300ms 以上,所以 RTO(ping + 200ms)先觸發,連續遺失時每次加倍 → 遺失一次就定格 0.3 秒左右,連重送的封包也遺失時,定格將近 1 秒後快轉 - 症狀:定格, 快轉/因素:遺失, 停滯 - 誰會遇到:只有我, 整個伺服器/何時:偶爾隨機發生 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:開啟 TCP_NODELAY(Nagle 開著時,RACK 沒有後續封包可用來判斷)、即時封包改用 UDP 加上自行實作的重傳。用戶端:開啟 TCP_NODELAY、即時封包用與伺服器相同的方式(UDP)傳送。 - 基礎設施團隊要做的事:使用 RACK-TLP(新版 Linux 預設)、用 tcp_thin_linear_timeouts 讓連續 RTO 不再加倍。 - 數值參考:封包間隔 100ms、ping 60ms 時,到快速重傳約需 360ms(直到後面 3 個封包抵達、確認回來為止),RTO 則約 260ms。使用 RACK 時,在下一個封包的確認回來的約 160ms 時就會立刻重送。封包間隔超過 200ms 時,RACK 也不會比 RTO 快。 - 圖表上:中斷後一次湧入(各連線的接收量、RTO 到期次數) - 查看位置:比較 nstat 的 TcpExtTCPTimeouts(RTO 到期)、TcpExtTCPFastRetrans(快速重傳)、TcpExtTCPLossProbes、TcpExtTCPLossProbeRecovery(TLP)增加量,並用 ss -ti 查看遊戲連線的 rto 與 backoff。也確認伺服器的 net.ipv4.tcp_recovery、tcp_early_retrans、tcp_sack 值 - 符合的跡象:重傳中 RTO 到期比快速重傳多,遊戲連線經常出現 backoff 大於 0(正在經歷 RTO)的情況。停住期間接收量為 0,復原後一次湧入 - 不符合的跡象:同一台伺服器的大量傳輸也同樣停住很久時,是與連線型態無關的遺失問題。集中在缺少 SACK 或時間戳記的連線時,是「中間設備移除 TCP 選項」 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:Linux 以前也有 thin stream 專用、收到 1 個重複 ACK 就重傳的選項(tcp_thin_dupack),但在 2017 年移除,現在由 RACK 取代這個角色。Nagle 開著時(TCP_NODELAY 關閉),等待遺失封包的確認期間也不會送出新封包,RACK 沒有後續封包可用來判斷,只能等到 RTO。 - 出處: - [Thin-streams and TCP](https://docs.kernel.org/networking/tcp-thin.html) · Linux kernel · 像遊戲這樣零星傳送的 thin stream,快速重傳不太能發揮作用,只能依賴很長的逾時;判定基準為尚未收到 ACK 的封包(in-flight)未滿 4 個 - [RFC 5681: TCP Congestion Control](https://www.rfc-editor.org/rfc/rfc5681) · IETF · 在第三個重複 ACK 時進行快速重傳 - [RFC 8985: The RACK-TLP Loss Detection Algorithm for TCP](https://www.rfc-editor.org/rfc/rfc8985) · IETF · RACK 以較晚送出的封包已送達來判斷遺失,TLP 等待時間為 2·SRTT(未確認的封包只有一個時,再加上延遲 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 200ms、thin stream 判定(in-flight 封包未滿 4 個)與線性重試 6 次 - [tcp: remove thin_dupack feature](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4a7f6009441144783e5925551c72e3f2e1b0839b) · Linux kernel · 2017 年 1 月移除 thin_dupack(Linux 4.11),並說明由 RACK 取代其角色 - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_thin_linear_timeouts:thin stream 最多前 6 次重試不把 RTO 加倍(預設關閉) - [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 · ss -i 的 rto(ms)、backoff(RTO 加倍的次數) #### rt-sack-stripped · 中間設備移除 TCP 選項 · Middlebox strips TCP options 部分防火牆或加速設備刪除或改寫 TCP 選項時,遺失多個封包後一個往返只能復原一個,或是視窗(一次可送出的量)變小,傳輸因此變慢。 - 為什麼 → 於是 → 畫面上:防火牆的「TCP 正規化」、老舊的加速設備移除 SACK、時間戳記、視窗縮放選項 → 遺失多個封包時每個往返只能復原一個,視窗被限制在 64KB → 每次遺失時定格的時間都長得多(沒有 SACK 就無法使用 RACK-TLP),恢復後快轉。更新檔這類大量傳輸也會變慢 - 症狀:定格, 快轉/因素:停滯, 延遲 - 誰會遇到:特定地區/電信業者, 整個伺服器/何時:一直都有 - 主要負責:基礎設施團隊(網路基礎設施)/協同:基礎設施團隊(伺服器基礎設施) - 基礎設施團隊要做的事:網路:關閉該設備的 TCP 正規化設定、也確認防火牆的序號隨機化、以兩端的封包擷取比較 SYN 中的選項。伺服器設備/OS:在 ss -ti 確認缺少 sack、wscale 標示的連線是否集中在特定路徑(Windows 電腦依設定可能不使用 ts,所以只少了 ts 可能是正常的)、確認伺服器的 net.ipv4.tcp_sack 為 1。 - 圖表上:一開始就一直偏高(沒有 SACK 就開始的復原次數(TcpExtTCPRenoRecovery)) - 查看位置:在 ss -ti 查看每條連線是否有 sack、wscale 標示,並查看 nstat 的 TcpExtTCPRenoRecovery(沒有 SACK 就開始的復原)與 TcpExtTCPSackRecovery 的比例、TcpExtTCPSACKDiscard(因前後對不上而丟棄的 SACK 區塊數)。可疑路徑則在兩端擷取 SYN,比較其中的選項(Wireshark 的 tcp.options.sack_perm 等) - 符合的跡象:只有經過特定路徑或設備的連線缺少 sack、wscale,TcpExtTCPRenoRecovery 比重偏高。傳送端 SYN 中原有的 SACK 允許選項,在接收端收到的 SYN 中消失。原因是序號隨機化時,選項還在,但 TcpExtTCPSACKDiscard 增加 - 不符合的跡象:所有連線都缺少 sack 時,先確認伺服器的 net.ipv4.tcp_sack 值。選項完整、TcpExtTCPSACKDiscard 也沒有變化時,復原慢的原因在別處(「thin stream 復原緩慢」) - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 深入了解:即使選項還在,SACK 也可能失效。防火牆的序號隨機化(sequence randomization)只改標頭中的序號、沒有改 SACK 裡的序號時,傳送端會丟棄前後對不上的 SACK。2019 年 SACK 安全問題發生時,在伺服器設定 tcp_sack=0 關掉之後就忘了改回來,結果也一樣。 - 出處: - [RFC 2018: TCP Selective Acknowledgment Options](https://www.rfc-editor.org/rfc/rfc2018) · IETF · 沒有 SACK、只靠累積 ACK 時,每個往返只能得知一個遺失的封包 - [RFC 7323: TCP Extensions for High Performance](https://www.rfc-editor.org/rfc/rfc7323) · IETF · 沒有視窗縮放選項時,視窗最大為 2^16 = 64KiB - [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 · Linux 的 TLP 只會在使用 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:傳送,接收 - [tcp: limit payload size of sacked skbs](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3b4929f65b0d8249f19a50245cd88ed1a2f78cff) · Linux kernel · 修正 2019 年 SACK 處理漏洞(CVE-2019-11477)的 commit - [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 區塊數) - [Display Filter Reference: Transmission Control Protocol](https://www.wireshark.org/docs/dfref/t/tcp.html) · Wireshark · tcp.options.sack_perm(SYN 中的 SACK 允許選項)顯示篩選條件 #### rt-zero-window · Zero window(看起來像重傳的停頓) · Zero window, often mistaken for retransmission 接收端程式沒有及時讀取 socket、緩衝區塞滿時,傳送端會停止傳送,只送出 zero window probe。線路本身沒有問題。 - 為什麼 → 於是 → 畫面上:用戶端的畫格更新停住、伺服器執行緒卡住,導致無法讀取 socket → 接收視窗變成 0,傳送端停止傳送、只送 probe(間隔越來越長) → 定格後快轉。封包擷取中看得到「ZeroWindow」,沒有遺失 - 症狀:定格, 快轉/因素:停滯 - 誰會遇到:只有我, 整個伺服器/何時:人潮湧入時, 偶爾隨機發生 - 主要負責:遊戲開發團隊(用戶端開發)/協同:遊戲開發團隊(伺服器開發), 基礎設施團隊(伺服器基礎設施) - 遊戲開發團隊要做的事:先從封包擷取中送出 ZeroWindow 的一方(無法讀取 socket 的一方)開始確認、在獨立的執行緒持續讀取網路接收資料、把接收緩衝區設為適當大小。用戶端:解決載入、GC 這類讓畫格更新停住的原因。伺服器:解決讀取 socket 的執行緒卡住的原因。 - 基礎設施團隊要做的事:把伺服器 nstat 的 TcpExtTCPToZeroWindowAdv(伺服器通告接收視窗為 0 的次數)加入監控(增加時問題在伺服器端,轉交伺服器開發)、提供伺服器端的封包擷取。 - 圖表上:中斷後一次湧入(各連線的接收量、zero window 次數) - 查看位置:在封包擷取中用 Wireshark 篩選條件 tcp.analysis.zero_window 找出通告視窗為 0 的一方。伺服器的 nstat 則分開查看 TcpExtTCPToZeroWindowAdv(伺服器通告視窗為 0)與 TcpExtTCPWinProbe(針對對方的視窗 0 送出 probe),並查看伺服器 socket 的 Recv-Q(ss 中程式尚未讀取的位元組) - 符合的跡象:停住期間沒有重傳,只有 zero window 與 probe 往來。伺服器的 TcpExtTCPToZeroWindowAdv 或伺服器 socket 的 Recv-Q 增加時,是伺服器沒有及時讀取;TcpExtTCPWinProbe 增加時,是用戶端沒有及時讀取 - 不符合的跡象:擷取中沒有 zero window、同樣的資料被重送時,原因在遺失或不必要的重傳 - 確認方式:用基礎設施工具確認(不需要遊戲程式碼) - 實際案例:roblox-2021 - 出處: - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · 接收視窗為 0 時,傳送端會送出 zero window probe,probe 間隔以指數方式拉長 - [7.5. TCP Analysis](https://www.wireshark.org/docs/wsug_html_chunked/ChAdvTCPAnalysis.html) · Wireshark · TCP ZeroWindow:接收端通告視窗為 0、讓傳送端停止傳送的封包 - [Display Filter Reference: Transmission Control Protocol](https://www.wireshark.org/docs/dfref/t/tcp.html) · Wireshark · tcp.analysis.zero_window、tcp.analysis.zero_window_probe 顯示篩選條件 - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · TcpExtTCPToZeroWindowAdv:接收視窗從非 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:對方接收視窗為 0 時,每送出一個 probe(tcp_send_probe0)就增加 - [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 · 已連線的 socket,ss 的 Recv-Q 是已收到但程式尚未讀取的位元組數 #### rt-syn · 連線請求(SYN)重傳 · SYN retransmission on connect 連線請求因連線等待佇列(backlog)溢位或被防火牆阻擋而消失時,用戶端 OS 會從 1 秒後開始,逐步拉長間隔重送。 - 為什麼 → 於是 → 畫面上:維護剛結束時連線暴增,伺服器的連線等待佇列溢位,或是防火牆、DDoS 防護丟棄 SYN → 用戶端 OS 從 1 秒後開始以固定間隔重傳 SYN(舊版 Linux 為 1 秒 → 2 秒 → 4 秒) → 按下連線按鈕後,延遲剛好是 1 秒、3 秒這種整秒數,持續失敗就連不上/無限讀取 - 症狀:連不上/無限讀取/因素:遺失 - 誰會遇到:整個伺服器, 特定地區/電信業者/何時:剛登入/維護剛結束 - 主要負責:遊戲開發團隊(伺服器開發)/協同:基礎設施團隊(伺服器基礎設施), 基礎設施團隊(網路基礎設施), 遊戲開發團隊(用戶端開發) - 遊戲開發團隊要做的事:伺服器:加大 listen 的 backlog 參數(與 somaxconn 一起調)、讓遊戲伺服器及時呼叫 accept、登入排隊系統。用戶端:拉長連線重試間隔(隨機分散)。 - 基礎設施團隊要做的事:伺服器設備/OS:伺服器連線等待佇列溢位可用 nstat 的 TcpExtListenOverflows、TcpExtListenDrops 與 log 中的「Possible SYN flooding」警告確認、加大 somaxconn(與 listen 參數一起調)、使用 SYN cookie。網路:放寬防火牆與 DDoS 防護的 SYN 限制。 - 數值參考:Linux(包括 Android)第一次 SYN 重傳在 1 秒後。舊版 kernel 之後間隔每次加倍,在第 1、3、7、15 秒……時重送,6.5 以上則在 1、2、3、4、5 秒重送五次後才開始加倍(7、11、19 秒……)(tcp_syn_linear_timeouts=4)。Android 手機即使更新 OS,很多仍沿用出廠時的 kernel,所以即使是同一個 Android 版本,不同裝置也可能不一樣。不論哪一種,全部失敗後約 2 分鐘就會放棄。Windows 依版本與設定從 1 秒或 3 秒開始拉長,重送次數為 2~4 次,因此 20~30 秒就會放棄(該電腦的值可用 netsh int tcp show global 的 Max SYN Retransmissions 確認)。 - 圖表上:剛開服或維護結束後暴增(連線嘗試次數、連線等待佇列溢位次數) - 查看位置:在伺服器的 nstat 查看 TcpExtListenOverflows、TcpExtListenDrops,以及 dmesg 的「Possible SYN flooding on port」警告,並用 ss -lnt 查看監聽 socket 的 Recv-Q(等待 accept 的連線數)是否碰到 Send-Q(backlog 上限)。用伺服器端擷取確認 SYN 是否抵達、是否回傳 SYN-ACK - 符合的跡象:維護剛結束連線暴增時,TcpExtListenOverflows 增加,Recv-Q 貼著 Send-Q。擷取中同一個用戶端的 SYN 以整秒間隔重複送來,伺服器卻沒有回應 - 不符合的跡象:SYN 沒有到達伺服器、伺服器計數器也沒有變化時,是前端的防火牆或 DDoS 防護丟棄了 SYN,要查該設備的 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 · 把 SYN 重傳的前幾次改為固定間隔的 commit,自 Linux 6.5 起(預設值 4 沿用 macOS、iOS 的做法) - [Android common kernels](https://source.android.com/docs/core/architecture/kernel/android-common) · Android (Google) · 5.10~6.18 的通用 kernel(common kernel)同時受到支援,舊平台用的 kernel(例: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 而異,可用 netsh int tcp show global 的 Max SYN Retransmissions 確認 - [listen(2) — Linux manual page](https://man7.org/linux/man-pages/man2/listen.2.html) · Linux man-pages · listen 的 backlog 參數會被截斷為 somaxconn(自 Linux 5.4 起預設 4096,之前為 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 · 「Possible SYN flooding on port …」log 訊息 - [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 · 監聽 socket 在 ss 中的 Recv-Q 為等待 accept 的連線數,Send-Q 為 backlog 上限 ## 各章出處 ### #l-client-game - [Slow rendering](https://developer.android.com/topic/performance/vitals/render) · Android (Google) · 要達到 60FPS,每個畫格必須在 16ms 內繪製完成,來不及就會跳過畫格,看起來就是卡頓 - [Interpolation and extrapolation (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/interpolation.html) · Unity · 在零星抵達的快照之間補出中間畫面的內插,以及資料晚到時沿相同方向與速度繼續推算的外插與其上限 - [Introduction to prediction (Netcode for Entities 6.5)](https://docs.unity3d.com/Packages/com.unity.netcode@6.5/manual/intro-to-prediction.html) · Unity · 用戶端不等伺服器結果、先依自己的輸入移動的預測,以及與伺服器不同時的校正 - [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 尖峰:關閉漸進式 GC 時,檢查整個 heap 的期間主執行緒會停住,超過 16ms 的畫格上限 - [Scripting.GarbageCollector.CollectIncremental](https://docs.unity3d.com/ScriptReference/Scripting.GarbageCollector.CollectIncremental.html) · Unity · 實驗的漸進式 GC 時間片段 3ms:incrementalTimeSliceNanoseconds 預設值 3ms - [Shader loading](https://docs.unity3d.com/Manual/shader-loading.html) · Unity · 實驗的新地圖載入:第一次使用某個著色器變體時,驅動程式要為 GPU 產生它,可能因此停住 - [Frame Pacing library](https://developer.android.com/games/sdk/frame-pacing) · Android (Google) · 實驗的 V-Sync 邊界:60Hz 螢幕在沒有新畫格時,會再顯示一次前一個畫格 - [Set fixed timestep to optimize physics simulation frequency](https://docs.unity3d.com/Manual/physics-optimization-cpu-frequency.html) · Unity · 實驗的固定步長追趕:畫格比步長間隔長時,一個畫格內要執行好幾次步長,負擔變大 - [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 · 給每個執行緒一個時間片段(約 20ms,依 OS 與 CPU 而異),用完就換下一個執行緒的先佔式多工 - [Scheduling Priorities](https://learn.microsoft.com/en-us/windows/win32/procthread/scheduling-priorities) · Microsoft · 可執行的執行緒中,優先順序最高的那些執行緒輪流(round robin)取得時間片段 - [Priority Boosts](https://learn.microsoft.com/en-us/windows/win32/procthread/priority-boosts) · Microsoft · 把前景視窗所屬處理程序的優先順序提高到背景處理程序以上 - [socket(7) — Linux manual page](https://man7.org/linux/man-pages/man7/socket.7.html) · Linux man-pages · 每個 socket 都有接收緩衝區(SO_RCVBUF),預設與最大大小由系統設定決定 - [Wi-Fi low-latency mode](https://source.android.com/docs/core/connect/wifi-low-latency) · Android (Google) · Android 10 以上的 Wi-Fi 低延遲模式中,框架會明確關閉 Wi-Fi 省電(doze)(App 位於前景且螢幕開啟時) - [Cached apps freezer](https://source.android.com/docs/core/perf/cached-apps-freezer) · Android (Google) · Android 14 以上會在 10 秒後凍結快取狀態的 App,使其無法使用 CPU - [Extending your app’s background execution time](https://developer.apple.com/documentation/uikit/extending-your-app-s-background-execution-time) · Apple · iOS 會在 App 切到背景幾秒後將其暫停 - [_WDF_TIMER_CONFIG (wdftimer.h)](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdftimer/ns-wdftimer-_wdf_timer_config) · Microsoft · 實驗的計時器 15.6ms:Windows 系統時鐘 tick 的預設間隔為 15.6ms - [timeBeginPeriod function (timeapi.h)](https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod) · Microsoft · 實驗的計時器 1ms:程式可以用 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) · 實驗的手機過熱:裝置只能在有限時間內維持高效能,之後就會因過熱而降頻 ### #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.5ns、L2 7ns、主記憶體 100ns、同一資料中心內往返 0.5ms、磁碟尋軌 10ms(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 · 伺服器用 NVMe SSD 的 99.99% 延遲(four-nines latency)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:Linux TCP 重傳的最小等待時間 200ms(表中的 TCP 重傳列) - [What is NUMA?](https://docs.kernel.org/mm/numa.html) · Linux kernel · 其他 CPU 那一側的(遠端)記憶體,存取比本地記憶體慢、頻寬也較低(表中的 NUMA 列) - [JEP 439: Generational ZGC](https://openjdk.org/jeps/439) · OpenJDK · G1 暫停為數 ms~數秒,ZGC 暫停為 1ms 以下(本文中整個 heap 的 GC 數百 ms~數秒、表中大型 heap 的 GC 1 秒) - [Available Collectors](https://docs.oracle.com/en/java/javase/25/gctuning/available-collectors.html) · Oracle · ZGC 犧牲少許處理量,把最長暫停壓在 1ms 以下,暫停時間與 heap 大小無關 - [Garbage Collector Implementation](https://docs.oracle.com/en/java/javase/25/gctuning/garbage-collector-implementation.html) · Oracle · 分代回收:只處理 Young 區的 minor 回收很短,處理整個 heap 的 major 回收則久得多(GC 實驗的分代模式) - [The Z Garbage Collector](https://docs.oracle.com/en/java/javase/25/gctuning/z-garbage-collector1.html) · Oracle · ZGC 把昂貴的工作並行執行,暫停不會超過 1ms,但回收跟不上時,應用程式可能停下來等待 GC(GC 實驗的並行執行模式) - [A Guide to the Go Garbage Collector](https://go.dev/doc/gc-guide) · Go · GC 標記階段會用掉 25% 的 CPU,期間程式變慢;配置量大時,goroutine 還要協助 GC(assist)而延遲(GC 實驗的並行執行模式) - [Go 1.8 Release Notes](https://go.dev/doc/go1.8) · Go · Go GC 暫停通常未滿 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 · 記憶體不足時會回收頁面快取與可 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 · 7,200rpm HDD 的 4K 隨機讀取 170 IOPS、平均旋轉延遲 4.16ms(本文中 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 額度最高突發到 3,000 IOPS,額度用完就回到基準效能 - [Amazon EBS-optimized instance types](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html) · AWS · 小型執行個體每 24 小時只有一次能以 EBS 最高效能運作 30 分鐘,之後回到基準效能(例:t4g.2xlarge 基準 4,000、最高 15,700 IOPS,磁碟實驗中雲端突發型的假設) - [Managed disk bursting](https://learn.microsoft.com/en-us/azure/virtual-machines/disk-bursting) · Microsoft Azure · 小型磁碟與 VM 可靠額度最多突發 30 分鐘 - [Concepts overview](https://docs.kernel.org/admin-guide/mm/concepts.html) · Linux kernel · 寫入檔案的資料會先放進頁面快取並標記為 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 · 沒有索引時,會從第一列開始讀取整個資料表(全表掃描) - [InnoDB Locking](https://dev.mysql.com/doc/refman/8.4/en/innodb-locking.html) · MySQL · 想修改同一資料列的請求,要等持有該資料列鎖定的 transaction 結束(熱點資料列) - [Number Of Database Connections](https://wiki.postgresql.org/wiki/Number_Of_Database_Connections) · PostgreSQL · DB 資源用盡之後,增加連線反而會降低處理量(DB 實驗中 CPU 核心不足時,加大連線池會讓全部變慢的依據) - [WAL Configuration (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/wal-configuration.html) · PostgreSQL · 檢查點是預設每 5 分鐘或每 1GB WAL 集中寫出 dirty page 的昂貴作業,分散寫入可避免 I/O 暴增 - [Semisynchronous Replication](https://dev.mysql.com/doc/refman/8.4/en/replication-semisync.html) · MySQL · 非同步複寫時主 DB 當機,已 commit 的 transaction 可能不在備援 DB 上(容錯移轉後被回滾) - [Log-Shipping Standby Servers (PostgreSQL Documentation)](https://www.postgresql.org/docs/current/warm-standby.html) · PostgreSQL · 串流複寫預設為非同步,commit 與反映到複本之間有延遲(在複本上看不到剛寫入的內容) - [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 · 偶發的長延遲(尾端延遲)會隨規模變大,越來越左右整體服務的體感 - [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 · 路由器必須能限制 Time Exceeded 等 ICMP 錯誤訊息的發送頻率,也可以限制 Echo Reply(解讀 mtr、ping 結果時要注意) - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · icmp_ratelimit、icmp_ratemask:Linux 預設會限制 Time Exceeded、Destination Unreachable 等 ICMP 回應 - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · ss -ti 的 rtt(平均往返時間)/rttvar 欄位 - [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)延遲(執行緒等待 CPU 的時間)的分布 - [RIPE Atlas documentation](https://atlas.ripe.net/docs/) · RIPE NCC · 從全球各地量測點執行 ping、traceroute 的公開合成監控工具 - [GeoLite2 Free Geolocation Data](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data) · MaxMind · 為 IP 位址標上國家與 ASN 的公開資料庫 - [View CloudWatch metrics for your instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html) · AWS · 基本監控為 5 分鐘間隔,詳細監控為 1 分鐘間隔(彙總間隔會掩蓋短暫的飆高) ### #l-nic - [NAPI](https://docs.kernel.org/networking/napi.html) · Linux kernel · 裝置以中斷通知有新封包時,kernel 透過 NAPI 取出處理;中斷合併通常由裝置負責 - [Scaling in the Linux Networking Stack](https://docs.kernel.org/networking/scaling.html) · Linux kernel · 透過 RSS 把多個接收佇列分配到多個核心的架構,每個佇列各有中斷,以雜湊選擇佇列 - [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)、中斷合併(-C)、接收雜湊(-N)、統計(-S) - [How to receive a million packets per second](https://blog.cloudflare.com/how-to-receive-a-million-packets/) · Cloudflare · 量測結果:一個佇列、一個核心在每秒約 35 萬~43 萬個封包就到頂,要增加佇列與核心才能接收 100 萬 pps(模擬假設每個核心的處理量為比這更寬裕的 70 萬 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、連線追蹤上限時,會在執行個體外部排入佇列後丟棄;上限超出計數器 ### #l-server-os - [listen(2) — Linux manual page](https://man7.org/linux/man-pages/man2/listen.2.html) · Linux man-pages · 連線等待佇列(backlog)與 somaxconn 上限(自 5.4 起預設 4,096,之前為 128) - [SNMP counter](https://docs.kernel.org/networking/snmp_counter.html) · Linux kernel · Linux 在 accept 佇列滿時會丟棄連線請求(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 上限預設值 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 且無法再回收時,會在該 cgroup 內執行 OOM killer;以 cpu.max 設定 CPU 上限 - [proc_stat(5) — Linux manual page](https://man7.org/linux/man-pages/man5/proc_stat.5.html) · Linux man-pages · steal:在虛擬化環境中被其他作業系統占用的 CPU 時間 - [Exponential Backoff And Jitter](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/) · AWS · 只靠指數退避,重試仍會集中;加入隨機延遲(jitter)才能減少競爭(模擬的重試方式) ### #l-socket - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · TCP 是保證順序的可靠位元組串流;Nagle 與延遲 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_NODELAY、TCP_USER_TIMEOUT、keepalive 等 TCP socket 選項 - [socket(7) — Linux manual page](https://man7.org/linux/man-pages/man7/socket.7.html) · Linux man-pages · SO_SNDBUF、SO_RCVBUF、SO_KEEPALIVE、SO_LINGER socket 選項 - [RFC 5681: TCP Congestion Control](https://www.rfc-editor.org/rfc/rfc5681) · IETF · 以 3 個重複 ACK 觸發快速重傳,計時器重傳後壅塞視窗為 1 個區段(模擬的教科書規則) - [RFC 8985: The RACK-TLP Loss Detection Algorithm for TCP](https://www.rfc-editor.org/rfc/rfc8985) · IETF · 以傳送時間取代重複 ACK 計數來判斷遺失的 RACK - [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 · Linux tcp_rto_min_us 預設 200ms,遺失偵測使用 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 · 模擬的 Linux 延遲 ACK 40ms:TCP_DELACK_MIN(HZ/25 = 40ms),最大 TCP_DELACK_MAX(HZ/5 = 200ms) - [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 · 模擬的 Windows 延遲 ACK 200ms:收到資料就啟動 200ms 的延遲 ACK 計時器,與 Nagle 疊加時小封包要等 ACK - [RFC 896: Congestion Control in IP/TCP Internetworks](https://www.rfc-editor.org/rfc/rfc896) · IETF · Nagle 規則原本的目的:遠端終端機每按一個鍵(1 位元組)就送出 41 位元組封包的問題 - [send(2) — Linux manual page](https://man7.org/linux/man-pages/man2/send.2.html) · Linux man-pages · 傳送緩衝區滿時,阻塞式 send() 不會返回 ### #journey - [ITU-T G.114: One-way transmission time](https://www.itu.int/rec/T-REC-G.114-200305-I/en) · ITU · 光纖傳輸延遲 5µs/km(1,000km 單程 5ms);單程 150ms 以下對大多數應用幾乎無感,但互動性強的作業在 100ms 以下也會受影響 - [Azure network round-trip latency statistics](https://learn.microsoft.com/en-us/azure/networking/azure-network-latency) · Microsoft Azure · 實驗中各伺服器位置的網際網路區段規模:從首爾出發,釜山地區 8ms、東京 30ms、新加坡 68ms、美國西部 124~136ms、歐洲 234~244ms(往返中位數) - [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 無線區段單向延遲要求未滿 10ms(以無負載、小封包為準;實驗中的 LTE 值是在此之上再加上負載與排程等待的假設) - [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)無線區段單向延遲要求 4ms(eMBB,以無負載為準) - [An Experimental Study of Home Gateway Characteristics (IMC 2010)](https://conferences.sigcomm.org/imc/2010/papers/p260.pdf) · ACM · 實驗的分享器佇列:家用分享器在上傳與下載同時進行時,佇列等待延遲有時會增加到數百 ms(最高約 400ms) - [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 的目標等待時間 5ms、觀察間隔 100ms(實驗中 SQM 佇列 5ms 左右) - [RFC 8290: The Flow Queue CoDel Packet Scheduler and Active Queue Management Algorithm](https://www.rfc-editor.org/rfc/rfc8290) · IETF · FQ-CoDel:依位址與 port 為每個流分開佇列,優先送出不會形成佇列的小流 - [What Can I Do About Bufferbloat?](https://www.bufferbloat.net/projects/bloat/wiki/What_can_I_do_about_Bufferbloat/) · Bufferbloat.net · 使用支援 cake、fq_codel 這類 SQM 的分享器,一邊量測負載下的延遲一邊調整 SQM 速度 - [An Experimental Study of Home Gateway Characteristics (IMC 2010)](https://conferences.sigcomm.org/imc/2010/papers/p260.pdf) · ACM · 量測 34 款家用分享器:上傳與下載同時進行時佇列等待延遲最高約 400ms,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 · NAT 的 UDP mapping 計時器要求(2 分鐘以上,建議預設 5 分鐘以上),以及由內部送出的封包更新 mapping - [RFC 8325: Mapping Diffserv to IEEE 802.11](https://www.rfc-editor.org/rfc/rfc8325) · IETF · Wi-Fi 採用 CSMA/CA:頻道忙碌時先延後,經隨機退避後才傳送 - [Resolve Wi-Fi and Bluetooth issues caused by wireless interference](https://support.apple.com/en-us/102319) · Apple · 實驗的微波爐干擾:微波爐、藍牙等會干擾 2.4GHz Wi-Fi,改用 5GHz 就會改善 - [RFC 9438: CUBIC for Fast and Long-Distance Networks](https://www.rfc-editor.org/rfc/rfc9438) · IETF · 實驗的上傳速度調節:CUBIC 遇到遺失時把傳送視窗縮為 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/km:光在光纖中每秒約前進 20 萬 km,1,000km 往返最少 10ms - [The Internet at the Speed of Light (HotNets 2014)](https://conferences.sigcomm.org/hotnets/2014/papers/hotnets-XIII-final111.pdf) · ACM · 實際路由器路徑的長度中位數約為光纖直線距離的 1.5 倍,最小 ping 為光速理論值的 3.2 倍(約為光纖直線距離的 2 倍) - [Azure network round-trip latency statistics](https://learn.microsoft.com/en-us/azure/networking/azure-network-latency) · Microsoft Azure · 從首爾實測的往返中位數:東京 30ms、美國西部 124~136ms、歐洲 234~244ms(韓國–歐洲約為光纖直線距離的 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 政策與跨網域路由會讓路徑大幅變長 - [Inferring Persistent Interdomain Congestion (SIGCOMM 2018)](https://www.caida.org/catalog/papers/2018_inferring_persistent_interdomain_congestion/inferring_persistent_interdomain_congestion.pdf) · ACM · 部分電信業者之間的互連區段,每天尖峰時段都會反覆出現延遲與遺失上升的壅塞 - [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 · 路由變更後到路由重新穩定所需時間的每日平均值: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 · 路由故障後收斂最長要數分鐘,期間遺失與延遲增加(2000 年當時的量測) ### #l-dc-net - [High-Resolution Measurement of Data Center Microbursts (IMC 2017)](https://conferences.sigcomm.org/imc/2017/papers/imc17-final60.pdf) · ACM · 資料中心網路的端到端延遲未滿 1ms,突發流量有 70% 以上在數十 µs 內結束,看平均使用率看不出來 - [Data Center TCP (DCTCP) (SIGCOMM 2010)](https://conferences.sigcomm.org/sigcomm/2010/papers/sigcomm/p63.pdf) · ACM · 通用型交換器由多個 port 共用淺緩衝區,短時間內多個流集中到同一個 port 時,緩衝區會溢位而遺失封包 - [Netfilter Conntrack Sysfs variables](https://docs.kernel.org/networking/nf_conntrack-sysctl.html) · Linux kernel · 連線追蹤表的最大項目數與保留時間預設值(UDP 30 秒、串流 120 秒、已建立的 TCP 5 天) - [Edit attributes for your Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/edit-load-balancer-attributes.html) · AWS · ALB 閒置逾時預設 60 秒,時間到就由負載平衡器關閉連線 - [Network Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/network-load-balancers.html) · AWS · 實驗的負載平衡器預設值:NLB TCP 350 秒、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 · Azure Load Balancer 閒置逾時預設 4 分鐘 - [Amazon EC2 security group connection tracking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-connection-tracking.html) · AWS · 安全群組連線追蹤的預設值,以及負載平衡器、防火牆的 TCP 閒置逾時通常為 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 · 實驗的公司防火牆:SRX 防火牆的預設 session 逾時為 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 分鐘:CGN 的 UDP mapping 中位數在有線網路為 35 秒、行動網路為 65 秒,量測到的 NAT 有 74% 在 1 分鐘以下,家用分享器(CPE)NAT 大多為 65 秒 - [tcp(7) — Linux manual page](https://man7.org/linux/man-pages/man7/tcp.7.html) · Linux man-pages · 實驗的 TCP keepalive:預設閒置 2 小時(7,200 秒)後,以 75 秒間隔確認 9 次,沒有回應就中斷連線 - [Cached apps freezer](https://source.android.com/docs/core/perf/cached-apps-freezer) · Android (Google) · 實驗的手機背景:Android 14 以上會在 App 處理程序進入快取狀態 10 秒後將其凍結 - [RFC 5880: Bidirectional Forwarding Detection (BFD)](https://www.rfc-editor.org/rfc/rfc5880) · IETF · 比路由協定以秒為單位的 Hello 更快偵測路徑故障的 BFD - [RFC 2923: TCP Problems with Path MTU Discovery](https://www.rfc-editor.org/rfc/rfc2923) · IETF · ICMP 被擋,只有大封包消失的路徑 MTU 黑洞 ### #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(伺服器)越多、抵達越平均,等待越短 - [CloudWatch metrics that are available for your instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html) · AWS · EC2 CPUUtilization 是整個執行個體的值,預設每 5 分鐘、詳細監控每 1 分鐘彙總一次 - [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 · 主記憶體參照 100ns,同一資料中心內往返 500,000ns(0.5ms) - [ITU-T G.114: One-way transmission time](https://www.itu.int/rec/T-REC-G.114-200305-I/en) · ITU · 光纖傳播延遲 5µs/km(光速極限計算的依據) - [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 次更新、內插 100ms。每秒 10 次時,內插 200ms 可撐過一次遺漏 - [IP Sysctl](https://docs.kernel.org/networking/ip-sysctl.html) · Linux kernel · tcp_rto_min_us 預設值 200000(200ms) - [Factors influencing the latency of simple reaction time (Frontiers in Human Neuroscience, 2015)](https://doi.org/10.3389/fnhum.2015.00131) · Frontiers · 簡單反應時間平均約 231ms(校正設備延遲後為 213ms) - [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 · 未滿 100ms 的延遲也會影響遊戲任務的表現,拖曳這類操作連約 10ms 都察覺得到 - [Peeking into VALORANT's Netcode](https://www.riotgames.com/en/news/peeking-valorants-netcode) · Riot Games · 熟練的玩家在盲測中連約 10ms 的差異都察覺得到 - [Latency and Player Actions in Online Games (Communications of the ACM, 2006)](https://web.cs.wpi.edu/~claypool/papers/precision-deadline/final.pdf) · ACM · 各類型遊戲的延遲容許值:第一人稱約 100ms、第三人稱(RPG、MMO)約 500ms、RTS 約 1,000ms - [JEP 333: ZGC: A Scalable Low-Latency Garbage Collector](https://openjdk.org/jeps/333) · OpenJDK · G1 在 128GB heap 上暫停平均 157ms、最長 544ms,ZGC 則不論 heap 與存活資料大小,都維持在約 1~2ms - [CommonNetworkParametersExtensions (Unity Transport 2.5)](https://docs.unity3d.com/Packages/com.unity.transport@2.5/api/Unity.Networking.Transport.CommonNetworkParametersExtensions.html) · Unity · disconnectTimeoutMS:在這段時間內完全沒收到任何資料就中斷連線(預設 30,000ms) ### #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 · 內插顯示的是過去的狀態,換來的是流暢;外插無法得知方向改變,猜錯時會跳動。預測誤差以伺服器結果校正 - [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 · 一收到就繪製會因抖動而卡頓;內插緩衝雖然稍微增加延遲,畫面卻很流暢 - [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 · 用每個 tick 累積的指令預算限制一次湧入的指令。太嚴格時連正常玩家也會卡頓 - [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 · 更新遺漏時,物件會停在最後的位置(卡頓),或先外插再跳動(瞬移)。外插時間必須設上限 - [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 · 緩衝時間依伺服器 tick rate 與用戶端的渲染畫格率而不同 - [Using Gameplay Abilities in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/using-gameplay-abilities-in-unreal-engine) · Epic Games · 伺服器可以推翻以預測方式先執行的技能(吃指令/回檔) - [Actor Relevancy in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-relevancy-in-unreal-engine) · Epic Games · 伺服器判定不相關的 actor 不會被複製,或會從用戶端刪除(看不見/幽靈物件) - [CommonNetworkParametersExtensions (Unity Transport 2.5)](https://docs.unity3d.com/Packages/com.unity.transport@2.5/api/Unity.Networking.Transport.CommonNetworkParametersExtensions.html) · Unity · 超過閒置逾時就中斷連線(斷線) ### #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 · 權威伺服器、用戶端預測與校正、內插、延遲補償的原理,以及「躲到轉角後還被打中」這類取捨 - [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 · 從 P2P lockstep 到用戶端/伺服器架構,再到用戶端預測的 netcode 演進 - [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 · 伺服器權威、預測、緩衝、回溯判定與回溯上限 - [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:把指令排程到兩個回合之後,回合長度配合最慢的電腦;穩定的 500ms 延遲沒問題,忽高忽低的延遲則讓人不舒服 - [Deterministic Lockstep](https://gafferongames.com/post/deterministic_lockstep/) · Gaffer On Games · Lockstep 要等所有輸入到齊才前進,以播放延遲緩衝吸收抖動 - [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 消除了 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 · 同樣的延遲,影響會因動作的精確度與時限、以及視角(第一人稱、第三人稱、上帝視角)而不同 - [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 · 把集中湧入的指令分散到各 tick 處理的指令預算,以及嚴格限制的副作用 - [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 秒 - [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 · 延遲補償的「躲到轉角後還被打中」,以及讓所有人輸入對齊的輸入延遲 - [send(2) — Linux manual page](https://man7.org/linux/man-pages/man2/send.2.html) · Linux man-pages · 傳送緩衝區滿時,send() 在阻塞模式下會等待,非阻塞模式則立刻回傳 EAGAIN - [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 · 分散式權限:每個用戶端各自負責計算部分物件 - [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 封包只要遺失一個分段,就會整個消失 - [Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE](https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse) · Microsoft · 兩個 socket 共用同一個 port 時,無法得知由哪一個接收封包 - [RFC 6269: Issues with IP Address Sharing](https://www.rfc-editor.org/rfc/rfc6269) · IETF · 多個用戶共用一個 IP 位址時,只靠 IP 無法區分使用者 - [Application.runInBackground](https://docs.unity3d.com/ScriptReference/Application-runInBackground.html) · Unity · App 在背景暫停的預設行為 - [Actor Priority in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/actor-priority-in-unreal-engine) · Epic Games · 頻寬飽和時,只依優先順序複製部分 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 · 在第三個重複 ACK 時進行快速重傳;RTO 之後壅塞視窗從 1 個區段(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 資訊判斷缺漏封包的遺失復原(快速重傳的重複 ACK、SACK 訊號) - [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(未確認的封包只有一個時,加上延遲 ACK 的餘裕)、送出 TLP 後重設 RTO、必須使用 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 · 用時間戳記事後判斷復原是否不必要(模擬中將壅塞視窗改回原值) - [RFC 7323: TCP Extensions for High Performance](https://www.rfc-editor.org/rfc/rfc7323) · IETF · 時間戳記與視窗縮放選項;沒有縮放時,視窗最大 64KiB - [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 在遺失時把壅塞視窗縮為 0.7 倍(模擬中的縮減 30%) - [RFC 9293: Transmission Control Protocol (TCP)](https://www.rfc-editor.org/rfc/rfc9293) · IETF · Zero window probe:視窗為 0 時也會送出 probe,間隔以指數方式拉長 - [RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport](https://www.rfc-editor.org/rfc/rfc9000) · IETF · QUIC 遺失封包時只會卡住該封包承載的串流,其他串流繼續進行(串流分離) - [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:以依流排程、AQM、流量整形減少佇列溢位與 bufferbloat - [RFC 1191: Path MTU discovery](https://www.rfc-editor.org/rfc/rfc1191) · IETF · 以大小超過的 ICMP(類型 3 代碼 4)找出路徑 MTU - [RFC 1812: Requirements for IP Version 4 Routers](https://www.rfc-editor.org/rfc/rfc1812) · IETF · 路由器可以限制 ICMP 錯誤訊息的產生速度(只有中間某一個躍點看起來有遺失的 mtr 結果) - [RFC 2991: Multipath Issues in Unicast and Multicast Next-Hop Selection](https://www.rfc-editor.org/rfc/rfc2991) · IETF · 在多路徑環境下,ping、traceroute 這類診斷結果難以採信,以及用流雜湊固定路徑的做法 - [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 個,最多 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 不容易觸發快速重傳,只能依賴很長的逾時;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 200ms、TCP_RTO_MAX 120 秒、TCP_TIMEOUT_INIT 1 秒、延遲 ACK 40~200ms(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 縮小壅塞視窗 - [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 次時視為偵測到黑洞並進行 MTU 探索;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 · BBR 的 pacing_rate = 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 · SYN RTO 的前 4 次退避不再加倍(第一次 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 · TCP_RTO_MAX_MS socket 選項,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 · 為每條連線設定 RTO 最小值的 TCP_RTO_MIN_US socket 選項(Linux 6.15) - [Android common kernels](https://source.android.com/docs/core/architecture/kernel/android-common) · Android (Google) · 仍受支援的 Android 通用 kernel 為 5.10 以上(比 RACK 成為預設的 4.18 更新) - [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 · fq 佇列的每連線 pacing 與 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:以調整 SYN 中的 MSS 避開擋掉 ICMP 的區段 - [ss(8) — Linux manual page](https://man7.org/linux/man-pages/man8/ss.8.html) · iproute2 · ss -i 的 rto(ms)、backoff(指數退避次數)、rtt/rttvar、cwnd - [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:目前/累計、lost、reordering、bytes_sent、bytes_retrans - [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 · 每次重傳顯示一行,包含位址、port 與狀態;-c 依流彙總,-l 包含 TLP 嘗試 - [Interface statistics](https://docs.kernel.org/networking/statistics.html) · Linux kernel · 用 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 · mlx5 的 rx_out_of_buffer、rx_discards_phy - [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 · 大部分突發流量在數十 µ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)指定目標 port - [pathping](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/pathping) · Microsoft · 多次送出探測、計算各躍點遺失與延遲的 Windows 路徑診斷命令 - [Display Filter Reference: Transmission Control Protocol](https://www.wireshark.org/docs/dfref/t/tcp.html) · Wireshark · 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 · 重傳、快速重傳、不必要的重傳、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 · 用 netsh int tcp show global 確認 TCP 全域設定(Max SYN Retransmissions),以兩端同時擷取確認中途的遺失 - [Packet Monitor (Pktmon)](https://learn.microsoft.com/en-us/windows-server/networking/technologies/pktmon/pktmon) · Microsoft · 顯示 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 · Windows 10 年度更新(1607)與 Server 2016 預設開啟 TLP 與 RACK(RTT 超過 10ms 的連線);只剩一個封包時,TLP 會把 200ms 的延遲 ACK 納入考量 - [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 · 舊版 Windows 的 SYN 重傳:從 3 秒開始每次加倍,共 2 次 ### #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 為準)。所以心跳封包由用戶端送出 - [RFC 5382: NAT Behavioral Requirements for TCP](https://www.rfc-editor.org/rfc/rfc5382) · IETF · NAT 可能會刪除閒置的 TCP session,建議的閒置逾時為 2 小時 4 分鐘以上(各設備的設定可能不同) - [Amazon EC2 security group connection tracking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-connection-tracking.html) · AWS · 安全群組連線追蹤的 TCP 閒置逾時(Nitro v6 執行個體類型 350 秒、其他類型 5 天,可在 60 秒~5 天之間調整),建議以短於 5 分鐘的間隔送 keepalive,經過 NLB 的 TCP 為 350 秒 - [Control subnet traffic with network access control lists](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html) · AWS · 網路 ACL 不保存狀態(沒有連線追蹤),回應流量也必須另外以規則允許 - [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、pps_allowance_exceeded) - [listen(2) — Linux manual page](https://man7.org/linux/man-pages/man2/listen.2.html) · Linux man-pages · listen 的 backlog 參數就是實際的佇列大小,大於 somaxconn 時會被截斷為該值(自 5.4 起預設 4096) - [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 · 用 nf_conntrack_count 與 nf_conntrack_max 計算 conntrack 使用率 - [Control Group v2](https://docs.kernel.org/admin-guide/cgroup-v2.html) · Linux kernel · cpu.stat 的 nr_throttled:容器因 CPU 上限而被節流的次數 - [proc_stat(5) — Linux manual page](https://man7.org/linux/man-pages/man5/proc_stat.5.html) · Linux man-pages · /proc/stat 的 steal:在虛擬化環境中,其他 OS 使用 CPU 的時間 - [RFC 2992: Analysis of an Equal-Cost Multi-Path Algorithm](https://www.rfc-editor.org/rfc/rfc2992) · IETF · ECMP 以區分流的標頭欄位雜湊值選擇路徑(同一個流走同一條路徑,不同的流可能走不同路徑) - [mtr(8) manual page source](https://raw.githubusercontent.com/traviscross/mtr/master/man/mtr.8.in) · mtr · 以與遊戲相同的協定與 port 量測路徑(-T、-u、-P) ### #l-server-proc - [VALORANT's 128-Tick Servers](https://www.riotgames.com/en/news/valorants-128-tick-servers) · Riot Games · tick 預算:128 tick 時,每個畫格必須在 7.8125ms 內完成;依子系統量測畫格時間,分配並管理預算 - [Introducing Time Dilation (TiDi)](https://www.eveonline.com/news/view/introducing-time-dilation-tidi) · CCP Games · EVE Online 的物理模擬每秒更新一次,過載時放慢遊戲時鐘,按比例減少與時間掛鉤的負載 - [HED-GP Technical Retrospective: What a HED-ache](https://www.eveonline.com/news/view/what-a-hed-ache) · CCP Games · Time Dilation 下限 10%;n 人的每個動作都要通知 n 人的 O(n²) 傳送,是大規模戰鬥的限制因素 - [Handling variation in time](https://docs.unity3d.com/Manual/time-handling-variations.html) · Unity · tick 實驗的模型:固定間隔的推進落後時,集中執行追趕的步驟(快轉),超過上限的時間被捨棄,遊戲時間變慢(慢動作) - [Comparing Interest Management Algorithms for Massively Multiplayer Games](https://www.sable.mcgill.ca/~clump/papers/boulanger-06-comparing.pdf) · ACM · NetGames 2006 論文(作者公開版本)。比較所有配對的距離在人數增加時無法負荷,切成格狀後只需確認周邊格子 - [Replication Graph in Unreal Engine](https://dev.epicgames.com/documentation/en-us/unreal-engine/replication-graph-in-unreal-engine) · Epic Games · 把世界切成格狀,依各格子的清單挑選傳送對象,即使玩家與 actor 很多也能節省伺服器 CPU - [Amdahl's Law in the Multicore Era](https://research.cs.wisc.edu/multifacet/papers/ieeecomputer08_amdahl_multicore.pdf) · IEEE · 鎖實驗的處理量上限:只能一次執行一個的部分比例為 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 · 鎖實驗的看門狗:以存活檢查(liveness check)抓到死結狀態並重新啟動,預設每 10 秒檢查一次,連續失敗 3 次就重新啟動(約 30 秒) - [ASP.NET Core Best Practices](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/best-practices) · Microsoft · 同步呼叫:資料存取與 I/O 要以非同步方式呼叫,阻塞式呼叫會導致執行緒池耗盡與回應延遲 ### #l-infra - [Site Reliability Engineering, Chapter 22: Addressing Cascading Failures](https://sre.google/sre-book/addressing-cascading-failures/) · Google · 連鎖故障:慢速後端占住前端的執行緒與資源,故障因重試、健康檢查失敗、快取清空的重新啟動而擴散的過程與因應方式 - [Circuit Breaker Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker) · Microsoft Azure · 斷路器的關閉、開啟、半開狀態與失敗次數門檻;逾時太長時,在切斷之前執行緒會一直被占住 - [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)、session 用的共用伺服器池、儲存狀態的 DB - [Working with DB instance read replicas](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ReadRepl.html) · AWS · 架構圖實驗的複寫延遲:讀取複本以非同步方式更新,可能讀到舊資料 - [Site Reliability Engineering, Chapter 20: Load Balancing in the Datacenter](https://sre.google/sre-book/load-balancing-datacenter/) · Google · 部署與重新啟動:先進入 lame duck 狀態把新請求轉走再關閉,重新啟動後先預熱 - [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 · 架構圖實驗的看門狗:終止存活訊號中斷的服務並自動重新啟動 ## 圖表形狀 - **固定週期飆高** (`periodic`): 平時很低,每隔幾秒、幾分鐘或每逢整點,以相同間隔往上衝。 - **偶爾隨機飆高** (`random`): 沒有固定間隔,不規則地往上衝,隨即回落。 - **從某個時間點起階梯式上升** (`step`): 以更新、設定變更、路由變更這類特定時間點為界,升高一階後就維持在那裡。 - **緩慢爬升** (`ramp`): 在幾小時到幾天內一點一點往上升,隨運行時間拉長而變大。 - **緩慢爬升後驟降** (`sawtooth`): 慢慢爬升,到重啟或清理的瞬間驟降,如此反覆。 - **只在特定時段偏高** (`peak`): 像晚間尖峰一樣,每天只在同一時段隆起成一座小山。 - **隨人數/負載上升** (`load`): 同時上線人數或聚集在同一處的人數增加時,會以比人數更陡的斜率跟著上升。 - **碰到上限後持平** (`ceiling`): 處理量或連線數到達某個值後就上不去,從那時起等待與錯誤開始增加。 - **一開始就一直偏高** (`high`): 持續停在高檔,不會突然飆高。原因來自距離、路由、設計這類結構性因素。 - **只有部分偏高** (`outlier`): 大部分正常,只有特定玩家、地區、電信業者或裝置特別高。 - **中斷後一次湧入** (`gap`): 有一段時間收到的量掉到 0,接著一次全部湧進來。 - **連線同時大量中斷** (`drop`): 連線數驟降,或斷線次數瞬間暴增。 - **剛開服或維護結束後暴增** (`surge`): 伺服器剛開放或活動剛開始時大幅衝高,然後慢慢回落。 ## 各情境處理流程 ### 更新後 lag 特定更新或部署之後,lag 回報增加時。適用於「這次更新後就怪怪的」這類回報集中出現,或圖表從某個時間點起階梯式上升並維持不降的情況。 1. **確定開始時間,收集前後所有變更**: 找出回報最初集中的時間與圖表階梯式上升的時間,把前後發布的變更全部列出來。用戶端更新、伺服器部署、設定變更、DB schema 變更(DDL)與重新啟動、網路與防火牆作業、基礎設施更換(執行個體類型、kernel、驅動程式)都要一起看。每次部署時用監控工具的註記(annotation)功能在所有圖表上留下垂直線,這一步很快就能完成。遊戲更新與基礎設施作業若在同一個維護時段發布,兩者都要留作候選。優先聯絡:發布變更的遊戲開發團隊與基礎設施團隊雙方。(原因:in-deploy, so-os-update, db-ddl-lock, db-plan-flip, db-cold-cache) 2. **切分範圍:版本、裝置、伺服器、地區**: 先看異常集中在哪個面向。只有新版本的使用者有問題時懷疑用戶端;只有特定 OS、顯示卡、裝置有問題時懷疑用戶端效能或驅動程式;只有特定伺服器、頻道、zone 有問題時懷疑伺服器;只有特定國家、電信業者有問題時懷疑網路路徑;所有人同時出問題時,先懷疑共用資源(DB、負載平衡器、閘道)或剛發布的伺服器部署。用戶端遙測資料若有版本號,就把舊版本與新版本的 ping、FPS、畫格尖峰、斷線次數並排比較。ping 不變、只有 FPS 變差時,比起網路,更可能是用戶端效能的問題。優先聯絡:集中在版本或裝置時找遊戲開發團隊(用戶端);集中在伺服器或頻道時,主機指標正常找遊戲開發團隊(伺服器),異常則找基礎設施團隊(伺服器設備/OS);集中在國家或電信業者時找基礎設施團隊(網路)。(原因:cg-hitch, cg-sync-load, co-vram, cg-crash) 3. **在同一時段比較新版本與舊版本**: 只比較部署前後,會混入星期、時段、活動造成的變化,讓判斷變得模糊。可能的話,先把新版本部署到部分伺服器(金絲雀),與同一時段的舊版本伺服器(對照組)並排比較 tick 時間 p50 與 p99、超出 tick 預算的次數、CPU、記憶體與錯誤率。如果已經全面部署,就與上週同一天、同一時段比較。只看整個伺服器的平均值,部分伺服器或 zone 的問題會被掩蓋,所以要按伺服器與 zone 分開看。優先聯絡:遊戲開發團隊(伺服器)。(原因:sp-tick-overrun, mem-alloc, mem-leak, sp-broadcast) 4. **比較前後的流量特徵**: 即使不懂伺服器程式碼,也能用網路端看得到的數值,確認更新是否改變了流量的樣貌。比較前後的每位玩家每秒封包數(pps)與位元組數、平均與最大封包大小、連線數,以及每個 tick 一次送出的突發傳送量。UDP 封包開始超過路徑 MTU(通常是 1,500 位元組)時,就會發生 IP 分段。只要遺失一個分段,整個封包就遺失;也有 NAT 或防火牆會直接丟棄分段。途中經過 MTU 較小區段(通道、VPN)的玩家,只有大封包會消失。pps 增加時,要查是否碰到雲端執行個體的 PPS 上限,或防火牆、DDoS 防護設備的處理上限。優先聯絡:特徵有變時附上證據找遊戲開發團隊(伺服器);特徵不變、只有遺失與重傳增加時找基礎設施團隊(網路)。(原因:sp-patch-traffic, sk-fragment, rt-mtu, nic-cloud-pps, rt-appliance-pps, rt-burst) 5. **比較前後 DB 查詢的種類與次數**: DB 延遲升高時,先看查詢數(QPS)是否也一起升高。PostgreSQL 的 pg_stat_statements 與 MySQL Performance Schema 的 digest 彙總,會把只有值不同的查詢歸為同一類,統計執行次數與總時間;比較更新前後的前幾名查詢清單,就能找出新出現的查詢、次數增加好幾倍的查詢(N+1),以及沒用索引而讀取整張資料表的查詢(MySQL 看 SUM_NO_INDEX_USED 欄位)。優先聯絡:QPS 或查詢樣貌有變時找遊戲開發團隊(伺服器);查詢相同、只有延遲增加時找基礎設施團隊(DB:執行計畫、IOPS、鎖定)。(原因:db-no-index, db-login-storm, db-plan-flip, db-cache-stampede) 6. **用主機與伺服器處理程序的指標切分層級**: 不需要程式碼,用 OS 上看得到的數值區分問題在伺服器內部還是主機。伺服器 socket 的接收佇列(Recv-Q)堆積,表示伺服器處理程序沒有及時讀取(tick 停住、GC、鎖);只有一個執行緒 100% 是單執行緒瓶頸;GC log 的暫停時間變長,表示記憶體使用模式改變了。也要確認是否以調高的 log 等級部署,導致 log 寫入增加。反過來,CPU steal、CPU 節流、NIC 丟棄(drop)增加時,要查同一時間變更的基礎設施(執行個體類型、kernel、容器上限)。優先聯絡:處理程序內部的訊號找遊戲開發團隊(伺服器),主機的訊號找基礎設施團隊(伺服器設備/OS)。(原因:mem-gc, sp-hotzone, dk-sync-log, so-cpu-quota, so-steal, so-os-update) 7. **還原以確認原因,並留下紀錄**: 把最可能的變更只在部分伺服器或部分玩家身上還原(回復部署、關閉功能開關),或把設定改回之前的值,看症狀是否一起消失。只有還原的那一邊變好,原因就能確定。還原作業本身也可能因重新啟動與冷快取而暫時變慢,不急的話就在離峰時段進行。結果要連同原因 ID 記錄在事故紀錄中,並把封包大小、查詢數、tick 時間上限列入下次更新的部署前檢查項目。優先聯絡:發布變更的團隊。(原因:in-deploy, db-cold-cache) ### 新增海外國家/地區 開放新的服務國家,或新增區域、資料中心時。開服前的檢查,以及分辨「國內沒問題,只有新國家的玩家 lag」這類回報時,都可以使用。 1. **開服前測量當地各電信業者的路徑品質**: 針對目標國家的主要電信業者(ASN),逐一測量到遊戲伺服器候選位置的往返時間(RTT)分布、抖動(jitter,封包抵達間隔忽長忽短)與遺失。單一平均值會掩蓋電信業者之間的差異,所以要看各電信業者的中位數與第 95 百分位數,並分成晚間尖峰與凌晨來看。公開量測網路 RIPE Atlas 可以指定國家與 ASN,從全球的探針(probe)發送 ping 與 traceroute;也可以在候選區域開臨時 VM 來測量。中間設備有時會限制 ICMP 回應,所以可能的話,也要用與遊戲相同的協定與 port 測量。只有特定電信業者特別繞經遠方城市時,就是 peering 或路由問題。電信業者比起延遲更重視成本,會選擇成本較低的路徑,所以近處也可能繞遠路。優先聯絡:基礎設施團隊(網路);路徑問題出在電信業者端時找外部(電信業者、IX)。(原因:isp-distance, isp-routing, isp-peak, isp-cable) 2. **把測量值與遊戲設計能承受的上限比較**: 把測得的 RTT 與抖動,拿來與遊戲的判定區間(閃避、格擋這類反應時間)、延遲補償上限、內插緩衝長度、輸入緩衝大小比較。例如格擋判定是 0.2 秒時,往返延遲加上內插緩衝超過這個值的電信業者用戶,即使及時反應也會太晚。若放寬延遲補償來配合,這次換成被打的一方回報「躲到牆後還被打到」的情況會增加。超過上限的電信業者很多時,基礎設施團隊要研究把區域或邊緣 PoP 設在更近的位置,遊戲開發團隊則要檢討判定、內插與延遲補償的數值。本白皮書的「同步方式」一章就是對照基準。優先聯絡:遊戲開發團隊(伺服器、用戶端:設計上限)、基礎設施團隊(網路:區域與 PoP 位置)。(原因:sy-short-window, sy-no-lagcomp, sy-lagcomp-overreach, cg-no-buffer) 3. **確認 MTU 與 UDP 能否通過**: 確認遊戲最大的封包能否在當地網路完整通過。以不同大小發送設定了禁止分段(DF)標記的 ping 來測量路徑 MTU,查看是否有 PPPoE、通道、行動網路這類小於 1,500 位元組的區段。UDP 這類資料包(datagram)傳輸的標準(RFC 8899)建議 IPv4 以 1,200 位元組作為大多數路徑都能通過的基本大小,遊戲的最大封包若大於這個值,要與遊戲開發團隊決定縮小或分開傳送的做法。也要確認公共 Wi-Fi、公司網路與部分電信業者是否封鎖 UDP 或遊戲 port,或限制速度,並查看被封鎖時有沒有替代路徑(TCP、443 port)。優先聯絡:基礎設施團隊(網路)與遊戲開發團隊(伺服器:封包大小)。(原因:dc-mtu, rt-mtu, sk-fragment, isp-udp-block, hn-captive, isp-shaping) 4. **測量 NAT、CGNAT 的閒置逾時,調整心跳封包間隔**: 測量當地家用分享器與行動網路(CGNAT)過多久會刪除閒置 UDP 連線的 mapping(位址與 port 的對應紀錄)。每次測試時,先從測試裝置送一個封包到伺服器建立 mapping,之後裝置不再送任何東西,讓伺服器在設定的時間(30 秒、60 秒、120 秒……)過後送封包到裝置。裝置開始收不到這個封包的時間,就是這個網路的閒置逾時。標準(RFC 4787)規定 UDP mapping 的過期時間不得短於 2 分鐘,並建議預設 5 分鐘以上,但各設備的數值差異很大,也有刪得更快的設備。mapping 只有靠從裝置送出的封包才能確實更新,所以心跳封包要由用戶端送出,並確認間隔不超過測得值、負載平衡器與雲端安全群組閒置逾時之中最短值的一半。優先聯絡:遊戲開發團隊(用戶端:心跳封包間隔;伺服器:逾時值)、基礎設施團隊(負載平衡器與安全群組設定)。(原因:hn-nat, isp-cgnat, rt-mapping, dc-lb-idle, dc-cloud-conntrack) 5. **確認在當地會經過的外部服務與安全設備**: 確認當地的平台登入、付款、身分驗證是否以正常速度回應,當地 DNS 能否正確解析登入與更新伺服器的位址,CDN 是否從靠近該國的據點提供更新檔。查看 DDoS 防護與防火牆的國家封鎖規則與速率限制是否擋到新國家的 IP 網段,特別是多位用戶共用一個 IP 的 CGNAT 網段是否被整批封鎖。優先聯絡:基礎設施團隊(安全設備、DNS、CDN)、外部(平台、金流業者、電信業者)。(原因:in-external, isp-dns, dc-ddos, isp-cgnat) 6. **開服後按國家與 ASN 分開看**: 在連線 log 與負載平衡器 log 的用戶端 IP 加上國家與 ASN,按國家與電信業者查看 RTT、重傳、斷線次數與原因(心跳封包逾時、RST、伺服器踢出)。可以用 MaxMind GeoLite ASN 這類免費資料庫把 IP 轉換為 ASN 與組織名稱,並依當地個資法規,把 IP 縮減為 /24 或 ASN 單位再保存。只集中在一個 ASN 時,先查該電信業者的路徑(基礎設施團隊、外部);整個新國家都差時,先查距離與設計上限(基礎設施團隊、遊戲開發團隊);只有晚間變差時,先查 peering 壅塞。如果只有部分玩家 ping 一直偏高,要與遊戲開發團隊(伺服器)一起確認是否因 GeoIP 錯誤、VPN、以隊長為準的分配而被分到遠方區域。合成監控正常、只有玩家端不好時,問題在玩家環境或用戶端。(原因: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 大規模艦隊戰的伺服器過載 - 發生什麼事:2014 年 1 月的一篇回顧文章談到 HED-GP 星系的大規模艦隊戰,當時伺服器嚴重過載。Time Dilation(過載時放慢遊戲時間的功能)降到下限 10%、整個戰場進入慢動作之後,負載仍持續堆積;處理模組停止與重複運作的工作積壓程度(Dogma Lateness)最高達到遊戲時間 193 秒,換算成實際時間約 32 分鐘。規模幾乎相同的 2013 年 7 月 6VDT 戰役,最高為 42 秒(實際約 7 分鐘)。 - 原因:CCP 先聲明,效能分析工具本身會增加負載,這種情況下不會執行,所以無法確定原因,接著舉出兩個可能的主因。第一是戰鬥拖長,來不及處理的負載不斷累積。第二是無人機使用量增加:戰鬥期間部署的無人機數量(不重複計算)在 6VDT 為 21,123 架,在 HED-GP 為 38,852 架,多了 84%。一個人的行動必須通知所有看得到的人,這類傳送量會隨人數的平方(O(n²))增加,而無人機每次攻擊產生的訊息更多。無人機挑選攻擊目標的程式碼也常把同一戰場上所有可攻擊的目標全部掃過一遍,成本接近 n² 成長。 - 學到的教訓:人潮集中的單一區域處理量超過上限時,整個區域都會變成慢動作;戰鬥越久,積壓的處理越多,輸入延遲也越大。確認訊號是負責該區域的伺服器(節點)的 tick 時間、積壓工作量,以及人數與物件數;其他區域都正常是這類問題的特徵。主要負責單位是遊戲開發團隊(伺服器),要修改的是一個行動需要通知的對象範圍,以及 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 玩家回報的實際流量本來應該從舊金山直接送到波特蘭,卻經過洛杉磯、丹佛、西雅圖,直接走只要 14ms 的路程花了 70ms。Riot 說明,路由器滿載而丟棄封包時,其他英雄會在畫面上亂跳,投射物看起來像瞬移一樣。 - 原因:Riot 指出原因在路徑與路由器。比起延遲最短的路徑,骨幹業者與電信業者更傾向把流量送往成本最低的路徑;BGP 決定的路徑若繞遠路,經過的路由器也會變多。路由器的處理負擔取決於封包數量,與封包大小無關。遊戲封包約 55 位元組左右,同樣的資料量下,封包數是 1,500 位元組封包的 27 倍,填滿路由器輸入緩衝區的速度也快上同樣倍數。依 Riot 的說明,許多路由器在過載時會先丟棄 UDP 封包。Riot 的解決方案是建立自有網路 Riot Direct:在美國 10 個大型網際網路據點架設路由器,並與盡可能多的電信業者直接互連(peering)。根據系列第 2 篇,以 ping 不到 80ms 遊玩的玩家比例在 9 個多月內從 31% 升到 50%,遊戲伺服器搬到芝加哥後,一夜之間達到 80%。 - 學到的教訓:即使在同一個國家,如果只有特定電信業者的用戶 ping 特別高,就要懷疑路徑。確認訊號是各電信業者(ASN)的 RTT 分布,以及 traceroute 顯示的途經城市。主要負責單位是基礎設施團隊(網路),修正方式是與電信業者直接 peering、連接 IX(網際網路交換中心),以及選擇伺服器位置。電信業者端的路由政策需要與外部(電信業者)協商。這個案例也顯示,光是把伺服器移到靠近玩家分布中心的位置,效果就很大。 - 相關原因: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: League of Legends 歐洲、巴西伺服器的邊緣主機過載 - 發生什麼事:2020 年 2 月底,League of Legends 的 EUW、EUNE、BR 伺服器多次發生事故,新開始的對局數大幅減少。配對、遊戲伺服器等後端服務狀態全都正常,但幾乎沒有流量進來。Riot 為了避免在可能不穩定的叢集上開放錦標賽模式(Clash),把時程延後一週。事後檢討報告沒有寫出每次事故持續多久。 - 原因:三件事疊在一起。送往某個服務的請求格式有誤,在特定情況下不斷失敗、不斷重試,請求量因此暴增。容器系統與 OS 版本之間有已知的相容性問題,造成 OS 內部記憶體洩漏,而升級只在 Riot 全部容器環境中約 60% 完成,歐洲與拉丁美洲叢集還在進行中。接收網際網路流量、過濾後轉送到後端的邊緣(edge)容器,在同一個 shard(伺服器群)內會分散部署,但不同 shard 之間沒有這種限制,所以每次事故時,至少有三個 shard 的邊緣容器集中在同一台主機上。暴增的重試壓在這台主機上,記憶體洩漏則讓這台主機停擺。 - 學到的教訓:後端服務都回報「正常,但流量進不來」時,就要查它們前面那一層(邊緣、閘道、負載平衡器)。確認訊號是各主機傳入連線數是否偏向少數主機,以及特定請求的失敗與重試比率。主要負責單位是遊戲開發團隊(伺服器:錯誤的請求與重試方式),容器部署規則、OS 升級與連線集中警示則由基礎設施團隊(伺服器設備/OS)負責。Riot 修正了請求程式碼,讓重試不再暴增,並在實作 shard 之間的分散部署之前,先加上連線集中警示。 - 相關原因: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 讓整個伺服器停擺 - 發生什麼事:2021 年 1 月 22 日,League of Legends EUW 伺服器有 5 個多小時無法正常運作。登入玩家數與遊戲中玩家數兩項指標同時中斷,兩次重新啟動之間,登入人數雖然增加,對局卻幾乎沒有開始。 - 原因:負責非關鍵功能的 DB 主伺服器發生硬體故障,而這個 DB 沒有設定自動容錯移轉到備用伺服器。各 DB 的連線池雖然分開,所有連線池卻共用同一個執行緒池;送往故障 DB 的工作一直沒有結束、持續佔用執行緒,整個系統可用的執行緒因此耗盡。大量警示湧入時,團隊先懷疑最近遭遇的惡意網路攻擊與其他地區的硬體作業,故障 DB 的警示約 1 小時後才被注意到。所有系統都在同一個 JVM 中執行,重新啟動後在重新連線的負載下,GC 每次讓處理程序暫停幾秒,指標收集也出現很大的空白。登入排隊也沒有遵守設定的上限,湧入的玩家忽多忽少。 - 學到的教訓:一個被認為不重要的附屬 DB,也可能透過執行緒池這類共用資源讓整體停擺。確認訊號是各 DB 等待中的請求數、執行緒池使用率,以及對局開始數相對於登入數過低的比例。負責單位是遊戲開發團隊(伺服器:執行緒池隔離、逾時)與基礎設施團隊(DB:自動容錯移轉)。大量警示湧入時,很容易先懷疑最近遇過的問題(例如攻擊),所以要依判定順序(範圍 → 時間點 → 層級)逐一排除。重新啟動後,也要一併確認登入排隊是否依設定限制湧入量。 - 相關原因: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 小時事故:服務探索(Consul)叢集的資源競爭 - 發生什麼事:事故始於 2021 年 10 月 28 日下午(太平洋時間)一台 Consul 伺服器的 CPU 高負載;16:35 在線玩家數掉到平時的一半,接著整個服務停擺。直到 10 月 31 日 16:45,所有玩家才能重新進入,距事故開始已過了 73 小時。Roblox 表示每天有 5 千萬人使用。 - 原因:Roblox 用 HashiCorp Consul 處理服務探索(服務之間互相找到對方位址的功能)、健康檢查與 KV 儲存,而一個 Consul 叢集同時承擔多種工作負載。根本原因有兩個。第一,Consul 新的 streaming 功能在幾個月間逐步擴大使用範圍,事故前一天也在流量路由服務上啟用,並把該服務的節點數增加 50%;在讀寫都非常大量的負載下,這項功能在單一共用資源(Go channel)上造成競爭。事故期間換上了核心數更多的雙插槽(NUMA)伺服器,在這些伺服器上競爭更嚴重。第二,Consul 用來儲存 Raft log 的 BoltDB,其空閒頁面清單(freelist)管理變得異常緩慢,每追加 16kB 以下的資料就要寫入 7.8MB 到磁碟。平時不到 300ms 的 KV 寫入延遲中位數升到 2 秒,在緩慢的 leader 伺服器上還觀察到 TCP 緩衝區塞滿的 zero window。遙測(telemetry)依賴 Consul,找原因所需的指標也跟著消失。 - 學到的教訓:許多服務共同依賴的基礎系統(服務探索、設定儲存、驗證)一旦變慢,所有功能會同時停擺。確認訊號是該系統的寫入延遲、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 資料片上市的壅塞與登入排隊錯誤 - 發生什麼事:2021 年 12 月,從資料片 Endwalker 的搶先體驗開始,各 World 都極度壅塞。登入排隊變長,在角色選擇畫面登入時或排隊等候中,經常出現 Error 2002。部分 World 與 zone 也曾當機(Error 3001),也出現過排隊逾時(Error 4004)。到 12 月 11 日公告時,搶先體驗進入第 8 天,壅塞仍在持續。 - 原因:Error 2002 會在兩種情況下發生。第一種是每個邏輯資料中心的排隊人數超過 17,000 人時;這是為了避免排隊過長導致登入伺服器當機而設的上限,此時用戶端會完全關閉。12 月 7 日把開發用的備用設備投入大廳伺服器、提高上限後,這個錯誤減少了,排隊卻反而變得更長。第二種是排隊中玩家的線路不穩定時。等待時間拉長後,網際網路路徑上的封包遺失或 Wi-Fi 不穩造成短暫斷線的情況變多。大廳伺服器會等待重新連線數十秒到 1 分鐘左右,在這段時間內連回就能從排隊中原本的位置接續,超過時間就得排到最後面。Square Enix 表示大部分回報都屬於這種情況。由於半導體短缺,也無法立即增加 World。 - 學到的教訓:排隊越長,排隊中玩家短暫的線路中斷就越容易變成連線錯誤。即使是同樣的壅塞,錯誤也會集中在使用 Wi-Fi 或不穩定線路的人身上,成為「只有部分人」遇到的問題。確認訊號是排隊長度、等待時間,以及斷線原因中排隊時斷線的比例。主要負責單位是遊戲開發團隊(伺服器:排隊上限與重新連線寬限時間),大廳與 World 伺服器擴充則由基礎設施團隊協同處理。重新連線寬限時間留得充裕,就能減少玩家線路短暫中斷演變成失去排隊順位的情況。 - 相關原因: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 業者,因此這是遊戲也會一起受影響的基礎設施事故類型。2020 年 7 月 17 日 21:12 到 21:39(UTC)的 27 分鐘內,Cloudflare 整個網路的流量減少約 50%。影響僅限於連接骨幹的美國、歐洲、俄羅斯、巴西部分城市據點,其他據點正常。 - 原因:紐華克–芝加哥骨幹區段故障,使亞特蘭大–華盛頓區段壅塞,工程師為了減輕亞特蘭大的骨幹流量而修改路由器設定。原本應該停用整個政策項目(term),卻只停用了其中的條件(prefix-list),結果亞特蘭大路由器以更高的優先順序(local-preference 200)把所有 BGP 路由散布到整個骨幹。各據點為通往自家伺服器的路由所設定的優先順序是 100,因此連接骨幹的據點流量全都湧向亞特蘭大。亞特蘭大過載,受影響的據點則幾乎沒有流量可處理。把亞特蘭大路由器從骨幹移除後即恢復。Cloudflare 表示這與攻擊或入侵無關。 - 學到的教訓:如果只有特定城市或地區的玩家同時遇到斷線或連不上/無限讀取,其他人都正常,就先懷疑剛做過的路由設定變更。圖表上只有一個據點的 CPU 與流量往上衝,受影響的據點反而掉到接近 0。主要負責單位是基礎設施團隊(網路),若是業者端的故障則屬於外部。Cloudflare 決定為骨幹 BGP session 設定可接收的路由數上限(maximum-prefix),並調整優先順序,讓單一據點無法把其他據點的流量拉走。 - 相關原因:isp-bgp, rt-path - 原文:[Cloudflare](https://blog.cloudflare.com/cloudflare-outage-on-july-17-2020/) ### fastly-2021 · Fastly 2021: Fastly CDN 全球性錯誤 - 發生什麼事:許多遊戲透過 CDN 發送更新檔、啟動器與網頁,因此這是遊戲也會一起受影響的基礎設施事故類型。2021 年 6 月 8 日 09:47(UTC)起,Fastly 網路有 85% 回傳錯誤。49 分鐘內網路的 95% 恢復正常,12:35 事故平息。 - 原因:5 月 12 日開始的軟體部署中,含有一個特定客戶設定遇到特定條件時就會觸發的 bug。6 月 8 日,一位客戶推送了正常的設定變更,剛好符合這個條件。Fastly 在 1 分鐘內偵測到異常,找出並停用造成問題的客戶設定後開始恢復。修正 bug 的部署在同一天 17:25 開始。 - 學到的教訓:已部署好幾週的程式碼,遇到罕見條件時也可能瞬間變成全球事故。遊戲端的確認訊號是更新、啟動器、網頁請求的 HTTP 錯誤率在所有地區同時升高,以及 CDN 業者的狀態頁面。已建立的遊戲連線若不經過 CDN 就不受影響,只有新連線、更新下載與網頁登入受阻,這是此類事故的特徵。主要負責單位是外部(CDN 業者),遊戲開發團隊與基礎設施團隊要預先準備替代路徑,例如同時使用兩家以上的 CDN,或直接從來源伺服器(origin)下載。 - 相關原因:in-external - 原文:[Fastly](https://www.fastly.com/blog/summary-of-june-8-outage) ### meta-2021 · Meta 2021: 一道骨幹指令讓 Facebook 連 DNS 都消失的事故 - 發生什麼事:這是一起同樣適用於遊戲公司自有網路與 DNS 的基礎設施事故。2021 年 10 月 4 日,Facebook(現為 Meta)的服務在全球都無法連線。連接各資料中心的骨幹全部中斷,網際網路端也找不到 Facebook 的 DNS 伺服器。事後檢討報告沒有寫出事故持續了多久。 - 原因:例行維護作業中,為了確認全球骨幹容量而下的指令,意外切斷了骨幹的所有連線,而原本應該擋下這類指令的稽核工具因為 bug 沒有擋住。小型據點的 DNS 伺服器設計成無法與資料中心通訊時,會判定自己異常並撤回 BGP 宣告,所以 DNS 伺服器明明還在運作,網際網路上卻連不到。平常的連線路徑與頻外(out-of-band)管理連線都中斷,內部工具也失去 DNS,只好派工程師親自到資料中心,又因為安全程序多花了時間。復原時,各資料中心的用電量都少了數十 MW,研判一次全部恢復可能讓電力設備到快取都陷入風險,因此分階段提高負載。 - 學到的教訓:所有地區、所有電信業者同時出現連不上/無限讀取時,要先查 DNS 與 BGP 路由,再看遊戲伺服器。透過外部 DNS 查詢與公開的 BGP 路由資訊,在公司外部也能確認。主要負責單位是基礎設施團隊(網路)。要事先檢查事故時使用的頻外連線路徑與內部工具是否依賴同一套 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 內部網路壅塞 - 發生什麼事:許多遊戲把伺服器、登入與資料放在公有雲上,因此這是遊戲也會一起受影響的基礎設施事故類型。2021 年 12 月 7 日上午 7:30(太平洋標準時間),北維吉尼亞區域(us-east-1)的內部網路發生壅塞。上午 7:33 起 EC2 API 錯誤與延遲增加,難以啟動新的執行個體(執行個體啟動於下午 2:40 恢復),接著出現主控台登入失敗、無法變更 Route 53 設定、CloudWatch 指標延遲與部分遺失。網路設備在下午 2:22 完全恢復。已在執行的 EC2 執行個體與既有的 DNS 回應不受影響。 - 原因:一項為主網路中某個服務擴充容量的自動作業,在內部網路的大量用戶端上引發非預期的行為,連線嘗試因此暴增。連接內部網路與主網路的設備超出負荷,通訊出現延遲,延遲又讓連線嘗試與重試增加,壅塞因此持續。用戶端本來具有在這種壅塞時拉長請求間隔的退避(backoff)機制,卻因為潛在缺陷而沒有正常運作。內部監控也依賴同一個網路,維運團隊只能在沒有即時指標的情況下靠 log 應變。 - 學到的教訓:重試無法拉長間隔時,短暫的壅塞就會變成長達幾小時的事故。從遊戲端來看,已在執行的遊戲伺服器可能正常,但新伺服器擴充(自動擴展)、使用雲端 API 的登入、配對、付款,以及監控,可能會同時受阻。確認訊號是雲端供應商的狀態頁面、雲端 API 錯誤率與執行個體啟動失敗。主要負責單位是外部(雲端供應商);遊戲開發團隊要為所有重試加上隨機間隔的指數退避與次數限制,基礎設施團隊則要準備好即使無法擴充也撐得住的餘裕容量,以及其他區域的備案。 - 相關原因:in-cascade, in-autoscale, in-external - 原文:[AWS](https://aws.amazon.com/message/12721/) ### cloudflare-dns-2025 · Cloudflare 2025: Cloudflare 公用 DNS 1.1.1.1 事故 - 發生什麼事:這是玩家自己在裝置或分享器上設定使用的公用 DNS 解析器(resolver)發生的事故。這類事故的特點是只有使用這項設定的玩家會遇到,而且這些玩家的所有遊戲與服務會同時受阻。2025 年 7 月 14 日 21:52 到 22:54(UTC)的 62 分鐘內,1.1.1.1 解析器在全球都沒有回應。Cloudflare 表示,對許多使用者來說,這等於幾乎所有網際網路服務都無法使用。UDP、TCP、DNS over TLS 查詢受到影響,以網域名稱連線的 DNS over HTTPS 則相對穩定。 - 原因:6 月 6 日為日後要用的另一個服務準備服務拓撲(決定在哪些據點宣告哪些 IP 網段的組態)時,1.1.1.1 解析器的 IP 網段被誤綁進這份組態。7 月 14 日變更這個服務的組態後,宣告解析器網段的據點從全部據點縮減為一個離線據點,全球的 BGP 路由因此撤回。這項變更沒有經過金絲雀(canary)部署,直接擴散到所有資料中心。22:20 還原設定後,流量恢復到約 77%,但這段期間約 23% 的邊緣伺服器上必要的 IP 設定已被刪除,重新設定又花了時間,直到 22:54 才恢復正常。Cloudflare 表示這是與攻擊或 BGP 劫持無關的內部設定錯誤。 - 學到的教訓:遊戲伺服器與其他玩家都正常,只有部分玩家在登入、更新伺服器上出現連不上/無限讀取時,要懷疑這些玩家使用的 DNS。已連線的 session 維持不變、只有新連線失敗,是這類問題的特徵。請玩家更換 DNS 設定,或直接查詢伺服器位址試試,馬上就能分辨。主要負責單位是外部(DNS 營運者、電信業者);遊戲開發團隊(用戶端)若把名稱解析失敗與其他錯誤分開提示,客服就能立刻判定。 - 相關原因:isp-dns, isp-bgp - 原文:[Cloudflare](https://blog.cloudflare.com/cloudflare-1-1-1-1-incident-on-july-14-2025/) ### aws-2025 · AWS 2025: AWS us-east-1 DynamoDB DNS 事故與漫長的復原 - 發生什麼事:許多遊戲把伺服器、登入與資料放在公有雲上,因此這是遊戲也會一起受影響的基礎設施事故類型。從 2025 年 10 月 19 日晚上 11:48 到 20 日下午 2:20(太平洋夏令時間),北維吉尼亞區域分三個階段持續受到影響。到 20 日凌晨 2:40 為止 DynamoDB API 錯誤增加;凌晨 2:25 到上午 10:36 新的 EC2 執行個體啟動失敗(部分新執行個體的連線問題於下午 1:50 解除);上午 5:30 到下午 2:09 部分 Network Load Balancer(NLB)的連線錯誤增加。 - 原因:管理 DynamoDB DNS 的自動化系統中存在潛在的競爭條件(race condition)。在不同可用區域套用 DNS 計畫的執行器(DNS Enactor)中,有一個延遲得特別嚴重,它用舊計畫覆蓋了新計畫;緊接著,另一個執行器的清理作業刪除了這份舊計畫,區域端點(dynamodb.us-east-1.amazonaws.com)的 DNS 記錄因此變成空值。自動化無法修復這個狀況,只能由人工復原。EC2 的實體伺服器管理系統依賴 DynamoDB,這段期間每台實體伺服器維持的租約(lease)都過期了。DynamoDB 恢復後,實體伺服器數量太多,重新建立租約的作業還沒完成就逾時,重試的工作又再次堆積,陷入「壅塞崩潰(congestive collapse)」狀態。新啟動執行個體的網路設定傳播得慢,NLB 健康檢查在成功與失敗之間反覆,連正常節點也一再從 DNS 移除又加回。 - 學到的教訓:一處的 DNS 記錄錯誤會擴散到依賴該服務的其他服務,原因排除後,積壓的工作與反覆波動的健康檢查仍會讓復原多拖好幾個小時。從遊戲端來看,已在執行的伺服器或許撐得住,但無法啟動新伺服器,自動擴展會停擺;健康檢查不穩時,負載平衡器還可能把正常的伺服器移出。確認訊號是雲端狀態頁面、受管服務的 API 錯誤率、執行個體啟動失敗,以及負載平衡器的健康目標數。主要負責單位是外部(雲端供應商),基礎設施團隊要限制因健康檢查失敗而一次被移出的伺服器數量,並準備其他區域的備案。 - 相關原因:in-external, in-cascade, in-autoscale, dc-lb-imbalance, isp-dns - 原文:[AWS](https://aws.amazon.com/message/101925/) ## 術語 - **Ping** (Ping, RTT): 自己送出的訊號到伺服器再回來所花的時間(往返)。遊戲裡顯示的 ping 有時也包含伺服器處理的等待時間。 - **延遲** (Latency): 封包從出發到抵達所花的時間。通常只指單一方向,所以大約是 ping 的一半。 - **抖動** (Jitter): 封包抵達間隔忽長忽短的程度。即使平均 ping 相同,抖動大時畫面就會卡頓。 - **封包** (Packet): 在網路上一次送出的一包資料。通常最大 1,500 位元組,遊戲的狀態更新則是數十到數百位元組。 - **封包遺失** (Packet loss): 送出的封包沒有抵達,在途中消失。使用 TCP 的遊戲只要 1% 就能感覺到每隔幾秒到十幾秒頓一下;具備內插與輸入重複傳送的 UDP 遊戲,有時到幾 % 都還遮得住。 - **頻寬** (Bandwidth): 線路每秒能傳送的最大資料量(Mbps)。和多快送達(延遲)是不同的概念。 - **Tick** (Tick): 伺服器計算一次遊戲狀態的單位。20 tick 的伺服器每秒計算 20 次,也就是每 50ms 一次。 - **Tick rate** (Tick rate): 每秒跑幾次 tick。越高反應越快,但伺服器成本和傳輸量也會增加。為了節省傳輸量,送封包的頻率有時會設得比 tick rate 低。 - **Tick 預算** (Tick budget): 跑完一次 tick 的時間上限。超過的話下一個 tick 會延後,tick 間隔因此拉長。 - **FPS** (Frames per second): 每秒繪製畫面的次數。60FPS 代表一個畫格 16.7ms。 - **畫格時間** (Frame time): 繪製一個畫格所花的時間。對體感來說,偶爾飆高的畫格比平均 FPS 更重要。 - **快照** (Snapshot): 伺服器每個 tick 送出的「目前遊戲狀態」摘要,內含位置、血量、狀態等。通常只挑出和接收端現有狀態不同的部分來送(差異壓縮)。 - **內插** (Interpolation): 把收到的兩個快照之間連起來繪製,讓畫面看起來流暢的技術。代價是呈現的畫面會稍微落後。 - **內插緩衝** (Interpolation buffer): 為了內插而刻意延後繪製的時間,是吸收抖動和一兩個遺失封包的緩衝時間。通常是封包間隔的 2 倍(每秒收 20 次就是 100ms),也有遊戲會在抖動變大時自動加長。 - **外插** (Extrapolation, Dead reckoning): 沒有新封包時,用最後的速度推測接下來會在哪裡並繪製出來的技術。猜錯時看起來就像瞬移,所以很多遊戲只推測 0.25 秒左右就停下(Source 引擎預設值 0.25 秒)。 - **用戶端預測** (Client-side prediction): 不等伺服器確認,先讓自己的角色動起來的技術。 - **伺服器校正** (Reconciliation): 伺服器結果回來後,與預測比對並修正自己角色的位置。做法是從伺服器確認過的位置開始,重新套用尚未確認的輸入來計算。差距大時看起來就是拉回。 - **延遲補償** (Lag compensation): 伺服器做攻擊判定時,回溯到攻擊者當時看到的過去時間點,確認是否命中的技術。為了不讓被打的一方太冤,回溯幅度會設上限。競技射擊遊戲常見 0.2~0.25 秒左右,也有像 Source 引擎預設值那樣回溯到 1 秒的。 - **權威伺服器** (Authoritative server): 最終判定只由伺服器做的設計。可以防止作弊,但所有結果都要經過一次伺服器往返,所以要用預測和先行演出掩蓋等待時間。 - **Lockstep** (Deterministic lockstep): 所有人只交換輸入,在同一回合做完全相同計算的方式。輸入會加上固定延遲,只要有一個人的輸入晚到,所有人都要等。 - **伺服器輸入緩衝** (Server-side input buffer): 伺服器替每個人先存一點輸入,每個 tick 取出一個來用的緩衝。抖動大的人在別人眼中也會很流暢,但這個人的動作在伺服器上確定的時間點也會相應延後。 - **Listen server** (Listen server): 由一名玩家的電腦一邊玩遊戲、一邊兼任伺服器的方式。房主的 ping 是 0,但房主的線路或電腦一慢,所有人都會 lag。 - **相位** (Phasing): 即使在同一個地點,也會依任務進度顯示不同 NPC 與地形的功能。兩個角色進度不同時,只有一邊看不到 NPC 是正常的。 - **Rollback netcode** (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 阻塞** (Head-of-line blocking): 前面一個卡住,後面全部都得等的現象,是 TCP 造成快轉的原因。 - **RTO** (Retransmission timeout): 重傳計時器。TCP 判斷封包遺失後,到重新送出之前等待的時間。Linux 是 ping + 200ms 以上,每失敗一次就加倍。 - **Nagle 演算法** (Nagle’s algorithm): 在先前送出的資料收到確認(ACK)之前,先把小筆資料湊在一起再一次送出,藉此減少封包數的 TCP 功能。遊戲通常要關掉。 - **TCP_NODELAY** (TCP_NODELAY): 關閉 Nagle 演算法的 socket 選項,小訊息會立刻送出。 - **延遲 ACK** (Delayed ACK): 把收到的確認稍微延後、和其他資料一起送出的功能。Linux 通常是 40ms(最多 200ms),Windows 舊版是 200ms,新版是 40ms。 - **Socket 緩衝區** (SO_SNDBUF / SO_RCVBUF): OS 為每個 socket 配置的傳送、接收等待空間大小。太小會滿出來,太大則會讓舊資料堆積等待。 - **keepalive** (SO_KEEPALIVE): 確認閒置連線是否還活著的 TCP 功能。預設為關閉,就算開啟,預設值也要 2 小時後才確認。 - **RST** (TCP reset): 當場強制切斷連線的 TCP 訊號,還沒送出的資料會被丟棄。 - **心跳封包** (Heartbeat): 遊戲自己定期送出的「我還活著」訊號,用來偵測斷掉的連線,並讓中間設備維持連線。 - **逾時** (Timeout): 在這段時間內沒有回應就視為失敗的標準。太短會誤判,太長則太晚發現。 - **NAT** (Network Address Translation): 分享器讓家中多台裝置共用一個公用 IP 對外連線,並把連線記錄在 NAT 表的功能。 - **CGNAT** (Carrier-grade NAT): 電信業者讓多個用戶共用一個 IP 的大規模 NAT。 - **MTU** (Maximum Transmission Unit): 一次能送出的封包大小上限。通常是 1,500 位元組,經過 VPN、PPPoE 的路段會更小。 - **Bufferbloat** (Bufferbloat): 設備讓佇列堆得太大,使延遲增加到數百 ms 的現象。 - **SQM** (Smart Queue Management (fq_codel, CAKE)): 讓佇列保持短小、並依各個資料流公平送出的分享器功能,是 bufferbloat 的解法。 - **QoS** (Quality of Service): 給予優先順序、讓重要流量先送出的功能。 - **Peering** (Peering): 電信業者之間互相連接網路的地方,晚上容易壅塞。 - **BGP** (Border Gateway Protocol): 電信業者之間互相告知網際網路上該走哪條路由的規則。一旦改變,路由和 ping 都會跟著變。 - **DDoS** (Distributed Denial of Service): 從許多來源送出大量流量、讓服務癱瘓的攻擊。 - **清洗中心** (DDoS scrubbing center): DDoS 攻擊時,先接下送往伺服器的流量、濾掉攻擊,只把正常流量轉過去的防護業者據點。據點越遠,路徑就越長。 - **防火牆** (Firewall): 只讓允許的連線通過的設備或程式,用 session 表追蹤連線。 - **負載平衡器** (Load balancer): 把進來的連線分配到多台伺服器的設備。 - **Session 表** (Session table, conntrack): 設備或 OS 用來追蹤目前連線的表,大小有上限。 - **Microburst** (Microburst): 平均流量不高,但在 1ms 以下的極短瞬間流量集中湧入的現象。 - **NIC** (Network Interface Card): 伺服器的網路卡。 - **Ring buffer** (Ring buffer): 把 NIC 收到的封包暫存到 CPU 取走為止的緩衝區。循環使用固定數量的 slot,slot 全滿時新封包就會被丟棄。 - **中斷** (Interrupt): 裝置通知 CPU「有事要處理」的訊號。 - **RSS** (Receive Side Scaling): 把收到的封包分到多個接收佇列,讓多個 CPU 核心處理的 NIC 功能。 - **PPS** (Packets per second): 每秒封包數。遊戲伺服器常常在頻寬還有餘裕時,就先碰到這個數字的上限。 - **Kernel** (Kernel): 作業系統的核心部分,負責網路、記憶體與 CPU 分配。 - **backlog** (Listen backlog): 伺服器尚未接受的新連線請求所排的佇列。佇列滿了之後,Linux 會默默丟棄新請求,Windows 則回傳拒絕回應。 - **TIME_WAIT** (TIME_WAIT): 先關閉連線的一方為了防範晚到的封包,暫時(Linux 為 60 秒)保留該 port 組合的狀態。 - **CPU steal** (Steal time): 虛擬機器想用 CPU,但實體伺服器正把 CPU 分給其他虛擬機器,因而等待的時間。用 top 的 st 值查看。 - **CPU 節流** (CFS throttling): 容器在固定週期(CFS period,通常 100ms)內用完 CPU 配額(quota)時,被強制暫停到下一個週期。 - **檔案描述子** (File descriptor): 處理程序每開啟一個檔案或連線就會配給的編號(fd),數量有上限。 - **執行緒** (Thread): 程式內獨立執行的工作單位,多個執行緒可以同時執行。 - **Context switch** (Context switch): CPU 把正在執行的執行緒換成另一個執行緒。這個動作本身有成本。 - **鎖** (Lock, Mutex): 讓共用資料一次只能由一個執行緒使用的鎖定機制。 - **死結** (Deadlock): 執行緒彼此等待對方持有的鎖,永遠停住的狀態。 - **執行緒池** (Thread pool): 預先建立好的一組工作執行緒。全部都在忙時,新工作就得等。 - **非同步 I/O** (epoll, IOCP, io_uring): 不等待 I/O 完成,先做其他事,完成時再收到通知的方式。 - **AOI** (Area of Interest): 每位玩家「看得到的範圍」。只傳送範圍內的變化,以減少傳輸量。為了降低判斷誰在範圍內的成本,通常會把地圖切成格狀(grid),只看鄰近的格子。 - **廣播** (Broadcast, fan-out): 把一個變化送給所有看得到它的人。聚集的人彼此都看得到時,要送的量會隨人數的平方成長。 - **GC** (Garbage collection): 自動回收用完不要的記憶體的功能。GC 進行中,程式有時會停住。 - **Heap** (Heap): 程式執行中隨需要配置來用的記憶體區域。 - **記憶體洩漏** (Memory leak): 用完的記憶體沒有歸還,使用量持續增加的 bug。即使有 GC,只要某處還一直參照已經用完的物件,就會發生。 - **Swap** (Swap, paging): RAM 不夠時,把部分記憶體搬到磁碟上。要再用搬出去的記憶體時,速度比 RAM 慢 1,000 倍以上。 - **OOM killer** (Out-of-memory killer): 記憶體耗盡時,Linux 挑出用最多記憶體的處理程序強制結束的機制。容器只要碰到記憶體上限就會觸發。 - **快取未命中** (Cache miss): CPU 旁邊的快取裡沒有需要的資料,必須到較慢的記憶體去拿。 - **IOPS** (I/O operations per second): 磁碟每秒能處理的讀寫次數。雲端磁碟的上限取決於付了多少錢。 - **fsync** (fsync): 等到資料確實寫進磁碟為止的指令。一般寫入會先放在 OS 記憶體裡,之後才寫進磁碟,中間若伺服器斷電,資料可能會消失。fsync 安全但慢。 - **Burst credit** (Burst credits): 雲端磁碟或伺服器累積的額度,讓它們能短時間發揮超過基準的效能。額度用完就會掉回基準效能。 - **索引** (Index): DB 的索引。沒有索引就必須讀完整張資料表。 - **全表掃描** (Full table scan): 不用索引,逐一檢查資料表所有資料列的查詢。 - **執行計畫** (Query plan): DB 決定以什麼順序、用哪個索引來執行查詢的方法。即使程式碼沒變,只要 DB 換了執行計畫,同一個查詢也可能突然變慢。 - **Transaction** (Transaction): 綁成一組、「全部完成,或全部不做」的 DB 操作。遊戲內交易一定要用 transaction 處理。結束前會鎖住修改過的資料列,所以越短越好。 - **連線池** (Connection pool): 預先建立好的一組 DB 連線。全部被占用時,新請求就要等待。 - **熱點資料列** (Hot row): 許多請求同時想修改的同一筆資料列,是鎖競爭的成因。 - **複寫延遲** (Replication lag): 複本 DB 跟不上主 DB 而落後的時間。 - **回滾** (Rollback): 存檔被取消、回到先前的狀態。玩家會覺得「道具不見了」。 - **快取** (Cache (Redis etc.)): 把常用資料複製一份放在較快的地方,以減輕 DB 負載。 - **檢查點** (Checkpoint): DB 定期把累積在記憶體中的變更一次寫入磁碟。在那個當下,存檔與查詢可能會短暫變慢。 - **容錯移轉** (Failover): 主伺服器或主 DB 掛掉時切換到備援端。切換期間短暫無法存檔,若複寫落後,最後一部分資料可能會遺失。 - **MVCC** (Multi-version concurrency control): DB 為了讓讀取者和修改者互不阻擋,暫時保留舊版本資料的方式。只要有長時間未結束的 transaction,舊版本就會堆積而變慢。 - **Cache stampede** (Cache stampede): 快取同時失效,請求一口氣湧向原始資料來源(DB)的現象。 - **閘道** (Gateway): 接收用戶端連線、再轉送給後端遊戲伺服器的中介伺服器。 - **斷路器** (Circuit breaker): 對持續失敗的服務呼叫暫時切斷、直接當作失敗處理,以防止連鎖故障的機制。過一陣子會試著呼叫一兩次,確認恢復後再重新允許呼叫。 - **連鎖故障** (Cascading failure): 一處的故障沿著呼叫鏈擴散到其他服務。 - **自動擴展** (Autoscaling): 依負載自動增減伺服器數量的功能。擴展需要時間。 - **看門狗** (Watchdog): 監視伺服器是否停住的計時器。遊戲迴圈停住超過設定時間(數秒到數十秒)時,會留下狀態紀錄(dump),並強制結束伺服器讓它重新啟動。 - **使用率** (Utilization): worker(CPU 核心、執行緒、DB 連線這類處理請求的單位)忙碌時間所占的比例。超過 80~90% 後,等待時間會急遽增加。 - **p99** (99th percentile): 100 次中有 99 次比它快、約 1 次比它慢的值。比平均值更能反映體感上的 lag。 - **V-Sync** (Vertical sync): 配合螢幕更新週期輸出畫格的功能。能消除畫面撕裂,但會產生輸入延遲;FPS 掉到更新率以下時,還可能在 60 和 30 之間來回切換而卡頓。 - **可變更新率** (VRR, G-Sync, FreeSync): 讓螢幕配合畫格完成的時機更新畫面的功能,可減少 V-Sync 在 60 與 30 之間切換造成的卡頓與輸入延遲。 - **反作弊** (Anti-cheat): 防止遊戲外掛的安全模組。定期檢查或與伺服器之間的心跳封包失敗時,也可能造成卡頓或斷線。 - **Overlay** (Overlay): 通訊軟體、錄影、FPS 顯示程式在遊戲畫面上疊加繪製的功能。因為會插進遊戲的繪製流程,可能造成卡頓。 - **著色器編譯** (Shader compilation): 把圖形特效程式轉換成 GPU 用格式的作業。沒有事先做好的話,第一次看到時畫面會頓一下;更新顯示卡驅動程式後,已存下的結果會失效,必須重做。 - **主執行緒** (Main thread, Game thread): 依序處理遊戲規則計算與畫面準備的核心執行緒。這裡只要有一件事耗時太久,畫面就會在那段時間停住。 - **計時器解析度** (Timer resolution): 作業系統喚醒休眠中程式的最短間隔。Windows 預設值是 15.6ms,程式若沒有另外調整,即使要求「1ms 後叫醒我」也會晚醒。 - **過熱降頻** (Thermal throttling): 裝置過熱時自動降低 CPU、GPU 速度的保護機制。手機玩遊戲幾分鐘到數十分鐘後很常發生。 - **VRAM** (Video memory): 顯示卡上的專用記憶體,貼圖與模型都放在這裡再繪製。不夠時得經由較慢的路徑和電腦記憶體來回搬資料,因而卡頓。 - **Net graph** (Net graph): 在遊戲畫面上以即時圖表顯示 ping、封包遺失、FPS、tick 的開發/除錯用顯示。回報 lag 的影片裡若一起錄到,找原因會容易得多。 - **重傳率** (Retransmission rate): 送出的 TCP 封包中重送的比例。沒有公認標準,但整台伺服器平均低於 0.1% 算健康,超過 1% 就容易有很多玩家感到 lag。也要一併看比平常值多了幾倍。 - **SACK** (Selective ACK): 接收端詳細告知「這段收到了,只缺這部分」的 TCP 功能。即使遺失好幾個,也能一次補回。 - **RACK-TLP** (Recent ACK, Tail Loss Probe): 以時間為基準判斷遺失,一段時間沒收到 ACK 就把最後一個封包再送一次,讓復原提早的 TCP 功能。是新版 Linux、Android 的預設行為。Windows 從 10(1607)與 Server 2016 起預設啟用 TLP 與 RACK,連遺失的重傳也能補救的新版 RACK 則從 Server 2022 開始。只在啟用 SACK 的連線上運作。 - **不必要的重傳** (Spurious retransmission): 封包其實沒遺失,只是晚到或順序顛倒,就被當成遺失而重送。既浪費線路,又白白降低傳輸量。 - **Zero window** (Zero window): 接收端緩衝區滿了,通知對方「先暫停傳送」的狀態。看起來像重傳,但線路正常,是接收端程式沒有及時讀取。 - **thin stream** (Thin stream): 像遊戲這樣零星送出小封包的連線。因為不容易湊齊觸發快速重傳的訊號,遺失時會停住很久。 - **Policer** (Policer): 超過設定速率的封包不進佇列、直接丟棄的限速方式。先放進佇列再慢慢送出的方式則稱為 shaper。 - **Pacing** (Pacing): 把要送的封包在時間上平均分散送出,避免一次全部倒出去。可以防止小緩衝區滿溢。 - **ECN** (Explicit Congestion Notification): 壅塞時不丟棄封包,改為加上「壅塞」標記,讓傳送端降低速度的功能。不必遺失封包就能通知壅塞。兩端與壅塞路段的設備都必須支援才有效果。 - **MSS** (Maximum Segment Size): TCP 單一封包承載資料的最大大小。通常是 1,460 位元組,配合通道路段調小可以避免 MTU 黑洞。 - **換手** (Handover): 移動中的手機切換所連線的基地台。 - **百分位數** (Percentile (p50, p95, p99)): 把數值由小到大排列時,位於第幾 % 的值。p50 是中位數,p99 則接近 100 次中最慢那 1 次的值。能呈現被平均值掩蓋的飆高。 - **尾端延遲** (Tail latency): 大部分都很快,偶爾才出現的長延遲。在平均值裡幾乎看不出來,卻是玩家記得的 lag。 - **合成監控** (Synthetic monitoring): 由量測用的裝置或伺服器代替真實玩家,在固定地點定期發送 ping、traceroute 等,量測路徑品質。RIPE Atlas 是代表性的公開工具。 - **彙總間隔** (Aggregation interval): 圖表上的一個點彙總了幾秒或幾分鐘的值。間隔越長,短暫的飆高就越會被平均稀釋掉。 - **事後檢討** (Postmortem): 事故結束後,整理發生了什麼、為什麼發生、要改什麼的文件。比起究責,更重視防止再次發生。 - **C-state** (CPU idle state): CPU 閒置時進入的省電狀態。越深層越省電,但喚醒所需時間也越長。 - **即時遷移** (Live migration): 雲端業者為了主機維護等原因,把執行中的虛擬機器搬到另一台主機。搬移的瞬間可能短暫停住。 - **SNAT** (Source NAT): 把送出封包的來源位址換成公用位址的 NAT。一個公用位址可用的 port 數有上限,用完時新連線就會失敗。 - **NAT 閘道** (NAT gateway): 讓私有網路中的伺服器連到網際網路時共用一個公用位址的雲端元件。每個目的地的同時連線數有上限。 - **低軌衛星網路** (LEO satellite internet): 透過高度數百到數千 km 的衛星群連線的網路。延遲比同步軌道衛星短得多,但切換連線衛星時延遲可能飆高。 - **GeoIP** (IP geolocation): 以 IP 位址推估國家、城市、電信業者的資料庫。其中有錯誤或過時的項目,有時會導致玩家被分配到遠方地區的伺服器。 - **TLS 憑證** (TLS certificate): 證明伺服器確實是本尊的電子文件。有效期限一到就會讓加密連線失敗,無法連線。 - **畫格生成** (Frame generation): 顯示卡在實際繪製的畫格之間插入預測畫格來提高 FPS 的技術。畫面會更流暢,但從輸入到畫面的延遲可能增加。