遊戲 Lag 白皮書
繁體中文

遊戲 Lag
白皮書

本白皮書從自己的遊戲畫面到伺服器的資料庫,分成 13 層說明畫面卡頓、角色瞬移、斷線的原因。每個原因都列出確認方法與負責團隊,也能在實驗中調整條件親自確認。內容以 MMO 案例為主,但大部分與遊戲類型無關,適用於各種線上遊戲。

00入門

造成 lag 的四個因素

原因超過一百種,但造成 lag 的因素大致可以歸成四類:封包晚到、封包忽快忽慢、封包根本沒到,或者某個環節停止了計算。遊戲會用各種技術掩蓋這些因素,掩蓋失敗時留下的痕跡,就是我們看到的 lag「樣貌」。

在 MMO 裡,自己畫面上的世界是依照伺服器送來的封包重新繪製的畫面。做法依遊戲而異,但伺服器通常 1 秒計算 10~30 次遊戲狀態(每一次稱為一個 tick),再從結果中只挑出每位玩家周圍的變化,用封包送出。自己的電腦讀取抵達的封包後繪製畫面。因此 lag 大多是遊戲如何呈現「封包沒有準時抵達」的問題。

也有四個因素以外的情況。伺服器和自己的電腦對同一件事算出不同結果時(移動規則不一致、bug),即使線路正常,也會出現拉回或看不見。這類 lag 的線索是:與 ping 無關,總在同一個地點、同一個動作上反覆發生。

從因素到症狀

比喻

用宅配來想像。配送總是要 3 天,是延遲;有的一天就到、有的要五天,是抖動(jitter);箱子不見了,是遺失;物流中心關門,是停滯。遊戲靠「箱子晚到或沒到,就用前後的箱子推測補上(內插、外插)」、「沒到就請對方重寄(重傳)」這類方法撐住。

先建立時間感

Lag 的討論大多以毫秒(ms,1/1000 秒)為單位。只要記住下表中的幾個數字,就更容易聽懂遊戲開發團隊和基礎設施團隊在說什麼。

基準時間意義

所有層共通的佇列特性

CPU、磁碟、資料庫、分享器、電信業者線路。層雖然不同,結構卻一樣:有處理請求的 worker(CPU 核心、執行緒、DB 連線等),前面會形成佇列。當 worker 閒置時,佇列是空的,但忙碌程度(使用率)一超過 80~90%,佇列就會急遽變長。單一 worker、請求隨機抵達時,平均等待時間在使用率 50% 時等於處理時間,80% 時是 4 倍,90% 時是 9 倍。「CPU 明明還剩 10%,為什麼會 lag?」的答案就在這裡。而且監控畫面上的 CPU 數值通常是多個核心、1~5 分鐘的平均值,會掩蓋只有一個核心跑到 100% 的情況,或是只集中在短短幾秒內的負載。

本白皮書涵蓋與不涵蓋的範圍

本白皮書探討線上遊戲遊玩過程中造成 lag 的原因,範圍從繪製遊戲畫面的電腦、手機開始,到家用網路、電信業者、資料中心、伺服器與資料庫。以影像串流接收遊戲畫面的雲端遊戲、以獨立服務運作的語音聊天,以及更新與下載速度,因為結構不同,不在討論範圍內。不過其中的網路原因(Wi-Fi、bufferbloat、線路壅塞等)與這裡的原因相同。

01整體地圖

封包的傳輸路徑:從輸入到伺服器 DB

按下技能鍵後,這個訊號會經過自己的電腦、家用網路、電信業者、資料中心,抵達伺服器。在伺服器內部經過多個層處理的結果,再反向經過同樣的層,繪製到畫面上。總共 13 層,任何一層卡住就會 lag。點選下方地圖中的層,即可跳到對應章節。

02親手試試

Lag 實驗室

這是由一台伺服器、一條線路、一台電腦組成的小型模擬。逐一破壞條件,看看卡頓、瞬移、拉回、快轉、慢動作、輸入延遲、定格、斷線各自是怎麼產生的。封包時間軸用線條顯示每個封包何時送出、何時抵達。線越斜代表花的時間越久,× 是遺失的封包。

03從看到的樣子找原因

症狀辭典

玩家只會說「很 lag」,但 lag 的樣貌其實透露了不少原因的線索。每個症狀的小圖,是畫面中角色經過的軌跡。點重疊代表停住,點拉開代表變快或跳過。

卡頓

動作不流暢,不斷短暫停住又繼續動。 ping 值正常時,多半是自己電腦的畫格問題(用戶端、OS);ping 忽高忽低時,多半是 Wi-Fi 或線路的抖動。不過遊戲內顯示的 ping 通常是在每個畫格跑一次的遊戲迴圈裡量的,所以畫格時間飆高時,ping 數字也可能跟著跳。

瞬移

角色沒有移動過程,一下子就出現在很遠的位置。 通常代表封包斷了一段時間。要查封包遺失、線路短暫中斷、伺服器停住、外插失敗。其他人都正常、只有一個人在跳的話,先懷疑那個人的線路。

拉回

自己的角色往前走到一半,被拉回剛剛經過的位置。 自己畫面上的預測和伺服器判定對不上。可能是自己的輸入沒送到伺服器(封包遺失)、伺服器的移動驗證把它擋掉,或是兩邊的移動計算不一致。

快轉

停住的畫面重新動起來時,積壓的移動、打擊和傷害一口氣快速跑完。 封包在某處堆積,然後一次放行。典型的例子是 TCP 等待重傳、伺服器追進度、用戶端處理積壓。

慢動作

所有東西都變慢,技能施放和怪物移動看起來像被拉長。依伺服器設計不同,也可能速度不變,改以卡頓或瞬移的形式出現。 伺服器無法在時限內跑完 tick。線路本身正常,所以在遊戲外量的 ping 不變;遊戲內的 ping 若含有伺服器處理的等待時間,可能會稍微升高。要查人數暴增、視野計算、廣播、記憶體不足。

輸入延遲

按下去之後要等一段時間才看到結果。畫面本身可能很流暢。 往返時間(ping)很長,或是某處的佇列堆積起來。要查距離、分享器佇列、Nagle(把小封包湊在一起再送的 TCP 功能)、伺服器佇列。ping 很低卻總是遲鈍的話,就查垂直同步(V-Sync)、FPS 偏低這類自己電腦端的因素,或是每個動作都要等伺服器確認的設計(見「同步方式」一章)。

定格

畫面裡所有東西停住一下(0.5 秒到數秒),然後又開始動。 可能是整台伺服器停住(GC、死結、同步呼叫)、線路短暫中斷,或是自己的電腦停住。

吃指令/回檔

明明做了的動作變成沒發生過,或是結果過了好一陣子才被推翻。 可能是請求遺失(封包遺失、佇列滿溢)、伺服器的判定和自己的畫面不同(判定時間點不同、先行演出後被拒絕),或是存檔途中失敗(DB 鎖定或故障、伺服器當機)。

斷線

遊戲中連線中斷,被送回登入畫面或重新連線視窗。 在逾時時間內一個封包都沒收到。要查線路長時間中斷、閒置逾時、伺服器當機或重啟,以及停住時間超過逾時的伺服器或自己的電腦(載入太久)。若沒有任何提示、遊戲直接關掉,先查用戶端被強制結束(閃退、記憶體不足),再查連線。

連不上/無限讀取

進不了遊戲,或是停在讀取、進場畫面。 負責接受新連線的地方(伺服器的連線等待佇列、防火牆、登入伺服器、DB)滿了。維護剛結束時特別常見。

看不見/幽靈物件

應該存在的 NPC、怪物、玩家只在自己的畫面上消失,或是早已消失的物件只留在自己的畫面上。 多半是少了某個封包,或是繪製失敗,和速度快慢關係不大。要查頻道或相位不同、出現/消失通知遺失、載入期間被丟棄、資源載入失敗。離開視野再回來後會不會出現,是最關鍵的線索。

04同步設計

同步方式與體感

有些遊戲 ping 150ms 也毫無感覺,有些遊戲 60ms 就覺得遲鈍。即使不是動作遊戲也一樣。線路相同時,這個差異通常來自用戶端與伺服器事先約定「由誰、在何時、決定什麼」的方式,也就是同步設計。其中有些是刻意的選擇,有些則真的是做錯了。

所有連線遊戲都在解同一個問題。伺服器與自己的電腦之間必然有時間差,雙方總要有一方決定如何處理「尚未確定的事」。選項大致有四種。

  • 等待:伺服器確定之前什麼都不顯示。結果準確,但 ping 就直接成了反應速度。
  • 先顯示,之後再修正:自己的動作立即演出,伺服器結果不同時再修正。速度快,但偶爾會看到拉回或動作被取消。
  • 事先排程:像「1.5 秒後重擊地面」這樣,連同未來的時間點一起通知。演出時間比 ping 長的話,完全感覺不到 ping。
  • 大家做同樣的計算:只交換輸入,各自做一模一樣的計算(lockstep、rollback)。傳輸量很小,但一個人的延遲會擴散到所有人。

因此對 ping 的敏感度,比起遊戲類型,更取決於兩個問題:一個核心動作要等待幾次伺服器往返,以及遊戲規則容許的時間是否比「ping + 人的反應時間」寬裕。

常見的同步方式

方式運作方式常見於ping 150ms 時的樣子弱點
請求-回應
伺服器確認後才顯示
按下後先詢問伺服器,等回應到了才演出。回合制、卡牌、放置型遊戲,商店、交易、製作 UI,早期 MMO 的技能與道具使用所有動作都晚約 0.2 秒開始。回合制幾乎感覺不到連續動作、一個畫面內有多次往返的 UI
狀態同步 + 內插
伺服器權威
伺服器每個 tick 送出遊戲狀態,用戶端在兩個狀態之間補畫中間的動作。多數 MMO 中其他玩家、怪物的顯示其他人是約 0.2 秒前的樣子。平時幾乎看不出來抖動(jitter,抵達間隔忽長忽短)、遺失 → 瞬移,tick rate 過低
用戶端預測 + 伺服器校正自己的輸入立即反映,伺服器結果到了再比對修正。FPS、動作 MMO、多數 MMO 的移動自己的操作立即反應。偶爾短暫拉回與伺服器計算不一致時頻繁校正
延遲補償
伺服器回溯判定
伺服器回溯到攻擊者當時看到的過去時間點,判定是否命中。FPS、非鎖定動作遊戲開槍的人覺得公平,被打的人卻會覺得「明明躲到掩體後面還是中彈」被打的一方覺得冤枉。攻擊者 ping 越高,回溯幅度越大,情況越嚴重
指令/目的地同步只送出「走到這裡」、「攻擊這個目標」之類的意圖,兩端各自計算。點擊移動的 MMO、Tab 鎖定戰鬥、部分 MOBA只有起步稍晚,移動與攻擊都很流暢路徑或結果不一致時需要校正
事件排程
以伺服器時間為準
像「伺服器時間 T 開始」這樣連同未來的時間點一起通知,各自在該時間點播放。團隊副本王的招式、過場動畫、整點活動預告比 ping 長的話,實際上沒有影響比排定時間晚抵達時,會跳過開頭部分
確定性 lockstep收集所有人的輸入,在同一回合做完全相同的計算。輸入會加上固定延遲。RTS(星海爭霸類)、部分合作、解謎遊戲所有輸入都固定延後(按下時立即用音效、提示掩蓋)。抖動大時所有人一起停住抖動、遺失、最慢的那一個人
Rollback
預測後回溯
預測對手的輸入先往下進行,猜錯就回溯到過去重新計算。格鬥遊戲(GGPO 類)、部分動作、運動遊戲操作手感幾乎即時(通常有 1~3 個畫格的輸入延遲)。對手動作偶爾跳掉幾個畫格ping 高時回溯幅度變大,看起來像瞬移
用戶端權威各自決定自己的結果,伺服器只負責轉送與紀錄。部分手機、休閒遊戲,P2P、中繼架構自己的畫面很順。與其他人畫面上的結果不一致外掛,「我明明打中了卻沒算」

實際的遊戲會混用這些方式。常見做法是依動作分別選擇:移動用預測,技能先行演出再確定,王的招式用事件排程,交易用請求-回應。

ping 150ms 也很順的遊戲有什麼共通點

1. 一個動作中完全不夾帶往返。按下按鈕就立即開始播放動畫、音效、特效(先行演出),伺服器結果只用在像傷害數字這種晚一點也看不出來的部分。

2. 遊戲規則容許的時間比 ping 寬裕得多。王的預告有 1~2 秒的話,即使封包晚約 0.2 秒到、人的反應花掉 0.25 秒,還是來得及閃開。自己的技能如果有施放時間,伺服器確認會在施法條跑滿的期間一起完成,等待就藏在施放時間裡。這是 Tab 鎖定 MMO 對 ping 不敏感的最大原因。反過來說,0.5 秒左右的短預告,只要 ping 到 150ms 就很難看到再閃開(見下方判定區間實驗)。

3. 先收下連續動作。有預輸入(技能佇列)的話,即使在冷卻結束前就按下一個技能也會先收下,連段之間就不會夾著往返時間。

4. 吸收抖動。內插緩衝與以伺服器時間為準的演出,會把「大多 150ms、偶爾 250ms」才到的封包,轉換成固定晚約 250ms 的穩定資料流。代價是看到的畫面再舊一點,但動作很流暢。人很快就能習慣固定的延遲,卻很難習慣忽快忽慢。做得好的遊戲會配合抖動變大變小,自動拉長或縮短緩衝。

5. 判定與「自己看到的」一致。以玩家看到的時間點來判定閃避與命中(延遲補償),或者一開始就採用與位置無關的規則(指定目標)。

6. 一個人的延遲不會讓其他人等待。權威伺服器架構下,即使自己的 ping 很差,其他人也不受影響。採用 lockstep 或主機架構時,最慢的那一個人決定所有人的體感。

分辨刻意的設計與做錯的設計

對 ping 敏感,有時是刻意的設計,有時是做錯造成的。

可能是刻意的選擇
  • 短判定區間:像 0.2 秒格擋(parry)、完美閃避這樣,短判定區間本身就是樂趣的遊戲。反應時間會被 ping 吃掉、抖動會打亂時機,這兩點無法避免,所以會用延遲補償或區域伺服器來減輕。
  • 為防止作弊而由伺服器確定:貨幣、道具、排名這類絕不能被偽造的結果,等待伺服器確認才是對的。
  • Lockstep:要只靠輸入同步數百個單位,這是最實際的架構。代價是必須依 ping 調整輸入延遲。
  • 公平性:也有遊戲刻意把延遲補償調弱,避免被打的一方因為別人 ping 高而吃虧。
很可能是做錯的跡象
  • 用鍵盤、手把直接操控的遊戲,卻連移動和普通攻擊都要等伺服器確認。沒有預測的話,ping 會直接變成操作手感。點擊移動這類下指令的操作,即使等待伺服器確認也比較不明顯,所以 MOBA 等遊戲有時會刻意採用。
  • 一次 UI 操作有多次往返:開視窗 → 取得清單 → 確認 → 購買,如果每一步都是一次往返,ping 150ms 時要花 0.7~0.8 秒。這些可以合併成一次。
  • 線路 ping 很低,卻固定地遲鈍:要懷疑 Nagle(把小封包累積起來再送的 TCP 預設行為,用 TCP_NODELAY 關閉)、請求累積到下一個 tick、結果也再等下一個 tick 才送出的雙重等待,以及每個動作都要等 DB 儲存完成才回應的架構。自己電腦的垂直同步(V-Sync)、FPS 過低也會帶來同樣的感覺。
  • 沒有技能佇列,「確認後才接受下一個輸入」:每次連段都夾著往返時間,DPS 會依 ping 下降。
  • 一抵達就播放:沒有內插緩衝或伺服器時間,依收到的順序演出的話,抖動會直接變成動畫的卡頓。

同步設計中造成 lag 的原因

伺服器回應後才演出(請求-回應方式) Request-response (no client-side feedback)

按下按鈕後,在伺服器回應之前既沒有動畫也沒有音效。ping 直接就是反應速度。

為什麼: 技能、移動、撿取都等伺服器確認後才播放 → 於是: 從按下的瞬間起,在往返時間 + tick 等待這段期間毫無反應 → 畫面上: ping 150ms 時,每個動作都慢 0.2 秒

症狀: 輸入延遲 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

依序往返過多的協定(chatty) Chatty protocol / sequential round trips

一次操作需要依序與伺服器往返好幾次時,ping 會乘上這個次數。

為什麼: 開啟商店 → 請求清單 → 確認價格 → 購買 → 更新背包,每一步各自請求 → 於是: 收到前一個請求的回應後才送出下一個請求 → 畫面上: ping 150ms 時買一次東西要將近 1 秒。載入特別久

症狀: 輸入延遲, 連不上/無限讀取 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

沒有技能預輸入 No input/spell queue

必須收到伺服器確認前一個技能已結束才能按下一個技能時,每次連段之間都會夾著一段往返時間。

為什麼: 只在「前一個技能確定後」才接受下一個技能的輸入 → 於是: 每個技能之間都空出一段 ping 長度的時間 → 畫面上: 連段之間出現空檔,ping 越高 DPS 越低

症狀: 輸入延遲, 吃指令/回檔 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

被 ping 吃掉的短判定區間 Timing window too short for latency + reaction

閃避、格擋、防禦這類需要反應的時間很短時,ping 會把這段時間吃掉,出現躲不掉的攻擊。

為什麼: 像王的攻擊預兆 0.5 秒、格擋判定 0.2 秒這樣短的判定區間 → 於是: 預兆較晚看到(下行延遲 + 內插),自己的輸入也較晚抵達(上行延遲 + tick 等待) → 畫面上: 明明躲開了卻被打中,格擋被吃掉

症狀: 吃指令/回檔, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發), 基礎設施團隊(伺服器基礎設施)

沒有延遲補償的判定 Server-now hit validation

伺服器只用「伺服器上現在的位置」判定命中時,判定會與自己看到的畫面不一致。

為什麼: 自己畫面上的對手是約 0.2 秒前的位置(ping 150ms、內插 100ms 時) → 於是: 伺服器以目前位置判定,對手早已不在自己瞄準的地方 → 畫面上: 明明打中了卻判定落空。射擊移動中的目標要預留提前量

症狀: 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

延遲補償過度 Excessive lag compensation

以攻擊者為準回溯得太遠時,被打的一方明明已經躲好了還是會中彈。

為什麼: 為了 ping 高的攻擊者,伺服器大幅回溯後判定 → 於是: 在被打的人的畫面上,早已躲進掩體 → 畫面上: 「躲在牆後還被打中」,ping 高的人占優勢

症狀: 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發)

用戶端權威 Client-authoritative results

各自決定自己的結果時,自己的畫面很順暢,但結果會與其他人的畫面不一致,也容易被外掛利用。

為什麼: 位置、命中由用戶端決定,伺服器只負責轉送 → 於是: 兩個人都聲稱自己先打中,伺服器無法驗證 → 畫面上: 對手瞬移、穿牆,「我明明打中卻沒中」

症狀: 瞬移, 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

Lockstep 中等待最慢的玩家 Lockstep waits for the slowest peer

在所有人一起計算同一回合的架構中,只要一個人的輸入晚到,所有人都要等。

為什麼: 每回合要收齊所有玩家的輸入才能計算 → 於是: 某個人的輸入因抖動或封包遺失而晚到 → 畫面上: 所有人同時頓一下,嚴重時跳出「等待玩家中」視窗

症狀: 定格, 卡頓, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

Rollback netcode 預測失敗 Rollback misprediction

先預測對手的輸入並呈現出來,猜錯時就回溯重新計算。ping 越大,回溯的幅度越大。

為什麼: 對手改變輸入(與預測不同) → 於是: 實際輸入晚了 ping 的一半才抵達,於是回溯這段時間重新計算 → 畫面上: 對手的動作跳過幾個畫格或突然改變

症狀: 瞬移 · 主要負責 遊戲開發團隊(用戶端開發)

沒有時間戳記、一到就播放 Events played on arrival (no timestamps)

伺服器事件沒有附上發生時間、一收到就播放時,網路抖動會讓演出時機跟著忽快忽慢。

為什麼: 「開始攻擊」、「播放特效」事件一抵達就執行 → 於是: 每個封包的抵達時間不同,間隔忽長忽短 → 畫面上: 連續攻擊動作忽快忽慢,王的招式時機每次都不一樣

症狀: 卡頓, 快轉 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

雙重 tick 等待 Double tick quantization

請求先累積到下一個 tick 才處理,結果又等到再下一個 tick 才送出時,tick 間隔會被加上兩次。

為什麼: 收到的請求在下一個 tick 處理 → 於是: 處理結果也累積到下一個傳送 tick 才送出 → 畫面上: 線路 ping 很低,反應卻固定慢了約 tick 間隔的 1.5 倍。10 tick 伺服器平均 0.15 秒,最差 0.2 秒

症狀: 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發)

過於嚴格的伺服器驗證 Over-strict server validation

伺服器對移動速度、冷卻時間、射程檢查得太嚴格時,連因抖動而擠在一起抵達的正常輸入也會被拒絕。

為什麼: 「一個 tick 內可移動的距離」、「冷卻時間容許誤差 0ms」這類嚴格標準 → 於是: 因抖動使兩個指令擠在同一個 tick 抵達時,就被判定為違規 → 畫面上: 拉回,冷卻好了技能卻被拒絕

症狀: 拉回, 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發)

主機(房主)架構 Listen server / host advantage

由某個玩家的電腦擔任伺服器時,那個人的線路與電腦效能決定了所有人的體感。

為什麼: 房主的電腦擔任伺服器(P2P、listen server) → 於是: 房主的線路或電腦慢時會影響所有人,房主本人 ping 為 0 → 畫面上: 只有房主占優勢,房主離開時所有人定格、斷線

症狀: 卡頓, 定格, 斷線 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發), 基礎設施團隊(伺服器基礎設施)

先行演出後遭伺服器拒絕 Client-side feedback rejected by server

自己畫面上先呈現的打擊、技能,事後沒有被伺服器認可時,明明看到的結果就變成沒發生過。

為什麼: 在伺服器確認前先播放打擊特效與技能動作(先行演出) → 於是: 伺服器重新檢查射程、目標位置、冷卻與資源後拒絕 → 畫面上: 噴血了卻沒有傷害,技能只有動作沒有效果,只有冷卻在跑

症狀: 吃指令/回檔, 拉回 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

指令同步的路徑計算不一致 Command sync with divergent pathing

只互傳「走到這裡」、路徑由兩邊各自計算時,計算只要有一點不同,角色或怪物就會走上不同的路徑,再被拉回原位。

為什麼: 點擊移動、怪物追擊時只送目的地,路徑由用戶端另外計算 → 於是: 因地形資料差異、與其他角色碰撞、計算順序不同,走上與伺服器不同的路徑 → 畫面上: 怪物穿牆走到一半一下子被移走,點擊移動的角色像滑行般轉向

症狀: 瞬移, 拉回 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

快照傳送頻率太低 Low snapshot / update rate

伺服器每秒只送幾次位置更新(快照)時,內插緩衝就得相應拉長,看到的其他角色會是更久以前的狀態。

為什麼: 為了節省傳輸量,每秒只送 5~10 次位置更新 → 於是: 要畫得流暢,緩衝就得設成封包間隔的 2 倍(200~400ms);設得短的話,只漏掉一個封包就會停住 → 畫面上: 對手轉向較晚才看到,與判定不一致。緩衝短時會卡頓,封包遺失時出現瞬移

症狀: 卡頓, 瞬移, 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

05影響範圍

只有一個人慢、只有一邊怪的時候

Lag 回報中最容易混淆的,是只有少數人或只有一邊遇到的情況。慢的那個人在別人眼中是什麼樣子、會不會連帶拖慢其他人,完全取決於伺服器處理輸入的方式和同步方式。同一台電腦開兩個用戶端、只有其中一個看不見 NPC 的現象,也在本章說明。

只有特定玩家、特定線路慢的時候

現在大部分 MMO 都是伺服器權威架構。伺服器決定所有結果,用戶端繪製收到的結果。在這種架構下,lag 大多只出現在慢的人身上。

  • 慢的本人會遇到輸入延遲:技能、撿道具這類需要伺服器確認的動作,會晚上一個 ping 的時間。移動靠預測可以立即顯示,但抖動(jitter,抵達間隔忽長忽短)大時,還會遇到拉回,並看到其他人瞬移。
  • 其他人只會看到慢的人的角色頓一下又快轉,或是瞬移。自己的操作和怪物的動作都正常。如果只是 ping 高、沒有抖動和遺失,看起來只是位置稍微落後,動作依然流暢。讓別人看起來很 lag 的主因是抖動與遺失,ping 的影響小得多。
  • 如果只有特定電信業者、地區的線路不好,這些玩家會同時出現上述症狀。從伺服器的角度看,只有這些人的輸入忽快忽慢地抵達,所以移動驗證或外掛偵測的誤判也會集中在他們身上。
  • 如果只有特定角色慢,比起線路,更該懷疑那個角色的資料。累積了數千個道具、信件的角色,每次登入和儲存都要比別人多讀寫好幾倍。用同一個角色從其他電腦、線路登入,看看是否一樣慢,就能分辨。

不過也有一個慢的人會拖慢所有人的架構。共通點是「有人在等那個人」。

  • 所有人等待同一回合的架構:lockstep(RTS)、按回合推進的合作內容。一個人的輸入晚到,全員都會停住。即使沒有抖動、只是單純延遲,所有人的輸入也會延後到最慢那個人的 ping 才反映。
  • 伺服器為了送資料給慢的人而等待的架構:阻塞式傳送(停下來一直等到傳送緩衝區有空間才送出)、同步處理。該伺服器執行緒負責的所有人都會變慢。一般做法是每個人各有一個傳送佇列,不等待。這時 lag 只會出現在慢的人身上,佇列太長時也只有那個人斷線。
  • 慢的人擔任中心角色的架構:由那個人的電腦當主機(房主)的 P2P(不經伺服器、玩家之間直接連線)或 listen server(玩家的電腦兼當伺服器),以及只能靠隊長權限推進的活動。如果遊戲為了減輕伺服器負載,把怪物移動的計算交給附近玩家的用戶端,那個人負責的怪物在所有人畫面上都會卡頓。
  • 判定以慢的人為準回溯的架構:延遲補償。慢的人可以公平地命中,但被打的一方會覺得冤枉:「明明已經躲起來了還是被打中」。因此會替回溯幅度設上限。上限依遊戲而異,大約在 0.2~1 秒之間(Source 引擎的預設值是 1 秒)。

伺服器處理輸入的方式不同,呈現的樣子也不同

伺服器處理輸入的方式慢的本人遇到的狀況慢的人在別人眼中的樣子其他人自己的遊戲
每個 tick 集中處理
固定 tick,收到的輸入一次處理
技能結果會晚 ping 加上等待 tick 的時間(輸入延遲)。移動驗證嚴格時會拉回頓一下後一次走好幾步(快轉、瞬移)。沒有抖動、只是 ping 高的話很流暢沒有影響
一抵達就處理
事件驅動,收到立即套用並傳送
輸入延遲等於 ping。只省下等待 tick 的那段時間移動忽快忽慢(輕微快轉)。一起湧入的多個技能在一瞬間全部執行沒有影響
每位玩家各自的輸入緩衝
每人各自暫存,一個 tick 處理一個
確定時間會晚一個緩衝的長度相對流暢。緩衝空了會暫時停在原地沒有影響
延遲補償判定
回溯到攻擊者看到的時間點
瞄準哪裡就打中哪裡(在回溯上限內)躲起來之後還是會被那個人的攻擊打中冤枉被打中(擴散)
Lockstep、回合等待輸入延遲。輸入晚到時定格全員定格定格。光是晚到也會輸入延遲(擴散到全員)
阻塞式傳送、同步處理
伺服器等待那個人
定格後快轉該伺服器執行緒負責的所有人都變慢慢動作、定格(擴散到該執行緒負責的人)
慢的人是主機
P2P、listen server
本人的 ping 是 0所有人的畫面都卡頓全員 lag
由慢的人控制怪物
把怪物移動的計算交給用戶端
本人畫面上的怪物正常那個人負責的怪物頓一下後瞬移所有和那些怪物戰鬥的人(擴散)

同一台電腦的兩個用戶端,只有一邊看不見 NPC

同一個人在同一台電腦上開了兩個用戶端,卻只有一邊看不見 NPC 時,原因幾乎不會是線路。兩個用戶端用的是同一台分享器、同一條線路。差異出在以下三個地方。

  1. 伺服器沒有送給那個用戶端:頻道、實例(instance)、任務相位(phasing,依任務進度區分看得到哪些 NPC 的功能)不同;視野登錄順序錯亂;每條連線的傳輸量上限;把同一台電腦、同一個 IP 當成同一個人的 session bug;多開限制。
  2. 送了,但用戶端丟掉了:載入中抵達的出現通知被丟棄;剛進場時湧入的出現資訊因接收緩衝區溢位,或因不可靠(unreliable)通道(遺失也不重送的通道)而消失;基準快照(只送變化量時作為起點的完整資訊)遺失;ID 被重複使用的新 NPC 被誤認成舊 NPC;固定 UDP port 衝突,封包被另一個用戶端攔走;背景視窗的處理被延後,接收緩衝區溢位;伺服器時間的推估有偏差,延後了顯示。
  3. 收到了,但畫不出來:兩個用戶端同時寫入同一個快取檔,導致模型載入失敗;顯示記憶體(VRAM)不足;顯示人數上限之類的選項設定不同;版本、資料不一致。

最有力的線索有三個:名字還在,只有角色模型不見嗎(伺服器有送,繪製失敗)、離開視野再回來就看得到嗎(漏掉了一則出現通知),以及把看不見的那個視窗切到前景會改善嗎(背景視窗的處理限制)。反過來,早已死掉的怪物只在自己畫面上還站著的「幽靈物件」,是漏掉了消失通知。

只有部分人遇到的問題

慢的人在別人畫面上快轉移動 Laggy player seen by others (bursty inputs)

線路差的人,輸入會忽快忽慢、成批抵達伺服器。伺服器每個 tick 收到多少就套用多少時,在其他人眼中,那個角色會頓一下後一次走好幾步。

為什麼: 慢的人的移動指令,有的 tick 抵達 0 個,有的 tick 一次抵達 2~3 個 → 於是: 伺服器在收到的 tick 一次全部套用,那個角色的位置呈階梯式變化 → 畫面上: 在其他人畫面上,只有那個角色頓一下後一次快轉移動。其他都正常

症狀: 快轉, 瞬移 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 外部(外部)

一到就處理的伺服器造成快轉 Event-driven processing of bursty inputs

封包一抵達就立即處理並通知的伺服器,會把慢的人成批湧入的動作接連立即執行。

為什麼: 慢的人的技能、移動請求成批抵達 → 於是: 伺服器一收到就依序執行,並立即通知所有人 → 畫面上: 在其他人眼中,那個人在一瞬間放出好幾個技能,或像快轉一樣移動

症狀: 快轉 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

各玩家的輸入緩衝大小 Per-player server input buffer (jitter buffer)

伺服器替每個人暫存一點輸入、每個 tick 取出一個使用時,在其他人眼中很流暢,但本人的動作在伺服器上確定的時間點也會晚上這麼多。

為什麼: 伺服器把慢的人的輸入存進緩衝,每個 tick 套用一個 → 於是: 緩衝小時經常被取空,那個角色會停在原地,或由伺服器依最後一個輸入推測移動;緩衝大時,本人的輸入會較晚確定 → 畫面上: 緩衝小時在別人眼中會頓一下,緩衝大時本人的技能結果較晚出現(輸入延遲)

症狀: 卡頓, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

集中在特定電信業者玩家的驗證誤判 Anti-cheat / movement validation false positives on bad ISPs

線路抖動大的人,輸入會成批抵達,因此常被伺服器的速度、冷卻檢查攔下。

為什麼: 特定電信業者、地區線路的抖動在晚間變大 → 於是: 伺服器把成批抵達的正常輸入判定為加速或違反冷卻 → 畫面上: 只有該電信業者的玩家出現拉回、技能被拒,嚴重時被伺服器踢出而斷線

症狀: 拉回, 吃指令/回檔, 斷線 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(網路基礎設施)

一個慢的隊友與王的機制 One laggy member in a synchronized mechanic

在所有人必須在指定瞬間一起反應的團隊副本機制中,一個慢的人反應太晚,就會讓整個隊伍失敗。

為什麼: 「全員同時散開」、「由一人按下按鈕」這類共同機制 → 於是: 慢的人較晚看到預兆,輸入也較晚抵達 → 畫面上: 因為那一個人而團滅,其他隊友覺得「都是 lag 的人害的」

症狀: 吃指令/回檔, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

怪物控制權在慢速用戶端上 Monster movement delegated to a player client

有些遊戲為了減輕伺服器負載,把怪物移動的計算交給附近某位玩家的用戶端。那個人線路差時,該怪物在所有人的畫面上都會動得很怪。

為什麼: 伺服器把怪物移動計算交給最近(或最先到)的玩家用戶端 → 於是: 負責者的結果回報延遲,或成批抵達伺服器 → 畫面上: 只有那隻怪物在周圍所有人畫面上頓一下後瞬移。負責者本人的畫面上卻正常

症狀: 瞬移, 卡頓, 快轉 · 主要負責 遊戲開發團隊(伺服器開發)

特定角色的資料過於龐大 One character with oversized data (inventory, mail, buffs)

累積了數千個道具或信件,或好友、封鎖名單、buff 特別多的角色,在登入、儲存與通知周圍玩家時要處理的量是別人的好幾倍。與線路無關,只有用那個角色時才慢。

為什麼: 練了很久的角色,背包、信箱裡累積了數千個道具或活動獎勵 → 於是: 每次登入、切換地圖、儲存都要讀寫那麼多 DB 資料,要送給周圍的裝備與 buff 資訊也很大 → 畫面上: 只有那個角色進場載入很久,開背包或信箱時會頓一下。若伺服器在遊戲執行緒上等待儲存完成,連周圍的人也會短暫定格

症狀: 連不上/無限讀取, 輸入延遲, 定格 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)

頻道、實例、相位不同 Different channel / instance / phase

兩個角色在不同的頻道或實例(instance),或處在依任務進度決定能看到哪些 NPC 的不同「相位」時,看到的是不同的世界。

為什麼: 第二個角色被分配到其他頻道,或任務階段不同 → 於是: 伺服器不會把該 NPC 送給那個角色(正常) → 畫面上: 只有一邊沒有 NPC。看起來像 bug,但符合設計

症狀: 看不見/幽靈物件 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

載入中抵達的出現通知被丟棄 Spawn messages dropped before the client is ready

一進入 zone,伺服器就送出周圍 NPC 的出現通知,但用戶端還在載入地圖,於是把通知丟掉。

為什麼: 伺服器在進場處理後立即送出周圍物件的出現通知 → 於是: 用戶端正在載入,訊息處理常式(handler)還沒準備好,於是丟棄通知 → 畫面上: 伺服器當作已經送過,不會再送。在 NPC 離開視野再回來之前都看不見

症狀: 看不見/幽靈物件 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

視野登錄順序錯亂 Interest-management race on enter/leave

角色登錄到視野格狀(grid)的瞬間,與 NPC 換到其他格子的瞬間重疊時,該 NPC 的出現通知可能會漏掉。

為什麼: 進場、換頻道、傳送的處理與 NPC 移動在同一瞬間重疊 → 於是: 在「新進入視野的物件」計算中漏掉了那個 NPC → 畫面上: 只有特定幾隻 NPC 看不見,或早已離開的 NPC 還留著

症狀: 看不見/幽靈物件 · 主要負責 遊戲開發團隊(伺服器開發)

基準快照遺失 Lost baseline for delta compression

在伺服器只送「與上次不同的部分」的方式中,最初送一次的完整資訊(基準)遺失時,之後的變化量就無法套用。

為什麼: 物件的完整資訊(基準)封包遺失,或在處理前被丟棄 → 於是: 用戶端沒有可以套用後續變化量的對象,只好忽略 → 畫面上: 那個物件看不見,或過了很久才突然出現

症狀: 看不見/幽靈物件, 瞬移 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

消失通知遺失(幽靈物件) Missed despawn (ghost entity)

反過來,漏掉「已消失」的通知時,早已死亡或離開的 NPC、玩家會只留在自己的畫面上。

為什麼: 死亡、離場、離開視野的通知遺失或順序錯亂 → 於是: 用戶端認為那個物件還在 → 畫面上: 打了也沒反應的怪物、早已離開的玩家還站在原地

症狀: 看不見/幽靈物件 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

剛進場時湧入的出現資訊遺失 Initial spawn burst lost (unreliable channel, receive buffer, fragmentation)

踏進 zone 的瞬間,伺服器會一次送出周圍數十~數百個物件的出現資訊。若用不可靠(unreliable)通道送出,或在載入中無法讀取 socket 的期間接收緩衝區溢位,就會有一部分消失且不再重送。

為什麼: 剛進場時,出現資訊在短時間內大量湧入 → 於是: 載入中的用戶端太晚讀取 socket,使 OS 接收緩衝區溢位;或大型 UDP 封包被分段,只要遺失一個分段就整個消失。若是不可靠通道,也不會重送 → 畫面上: 只有載入較慢的那個用戶端少了幾隻 NPC。離開視野再回來就看得到

症狀: 看不見/幽靈物件 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

物件 ID 重複使用造成誤認 Entity ID reused without a generation counter

死亡的 NPC 重新出現時,伺服器若再次使用同一個物件 ID,在這段期間漏掉消失通知的用戶端,就會把新的 NPC 誤認成舊的 NPC。

為什麼: NPC 死亡後以同一個物件 ID 重新出現 → 於是: 漏掉消失通知的用戶端認為是「已知的物件」,忽略出現通知或維持死亡狀態 → 畫面上: 只有一邊的畫面上沒有 NPC 或看到它倒在地上,有時還會以其他 NPC 的外觀出現

症狀: 看不見/幽靈物件 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

固定 UDP port 衝突 Two clients bound to the same local UDP port

用戶端若設計成使用固定的本機 port,同一台電腦的第二個用戶端就無法使用該 port,或與第一個用戶端分食封包。

為什麼: 兩個用戶端都要開同一個本機 UDP port(用重複使用選項硬是共用) → 於是: OS 只把進來的封包交給其中一個 socket,或不保證由哪一個接收。分享器與伺服器也把兩個用戶端看成同一個位址 → 畫面上: 其中一邊收不到世界封包,看不見 NPC 與其他玩家,或斷線

症狀: 看不見/幽靈物件, 斷線, 連不上/無限讀取 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

以 IP/裝置區分 session 的 bug Session keyed by IP or machine ID

伺服器或中介伺服器以 IP 或裝置 ID 區分連線時,會把同一台電腦(同一個公用 IP)的兩個用戶端視為同一個人。

為什麼: session 表以 IP 或 IP + 裝置 ID 建立 → 於是: 第二個用戶端的資訊覆蓋或混入第一個 session → 畫面上: 一邊看不見 NPC,另一邊斷線或收到別人的資訊

症狀: 看不見/幽靈物件, 斷線 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

多開限制 Multi-client restriction policy

安全模組或伺服器政策限制同一台電腦開多個用戶端時,第二個用戶端會無法執行或連線,或者先開的那一個會斷線。有些遊戲只封鎖額外用戶端的功能。

為什麼: 安全模組偵測到重複執行,或伺服器限制同一裝置的額外連線 → 於是: 拒絕第二次執行或連線,或切斷其中一邊。少數情況只封鎖額外用戶端的部分功能 → 畫面上: 連不上,或其中一邊斷線。在只封鎖功能的遊戲中,只有一邊看不見 NPC 或商店

症狀: 連不上/無限讀取, 看不見/幽靈物件, 斷線 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

背景視窗的處理限制 Background window throttling

背景視窗中的用戶端,會被遊戲、引擎、OS 降低畫格數與處理量。收到的封包來不及處理,就會積壓或溢位。

為什麼: 遊戲選項或顯示卡驅動程式的背景畫格限制(例:NVIDIA 驅動程式可在每秒 20~200 之間指定)、省電、引擎的背景暫停設定。OS 也會優先把 CPU、GPU 分配給前景視窗 → 於是: 每個畫格處理的封包數減少,佇列堆積,接收緩衝區溢位時就被丟棄 → 畫面上: 把視窗切到前景時一口氣全部冒出來,或部分 NPC 始終看不見

症狀: 看不見/幽靈物件, 快轉, 斷線 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 外部(外部)

快取/資源檔案同時存取衝突 Shared cache / asset file lock conflicts

兩個用戶端同時寫入同一個快取資料夾或鎖定檔案時,其中一邊會載入不了 NPC 模型或貼圖。

為什麼: 兩個用戶端同時寫入同一個安裝資料夾裡的快取、更新檔案 → 於是: 檔案鎖定失敗,或讀到寫到一半的檔案而載入失敗 → 畫面上: 名字還在卻沒有角色模型,或 NPC 是透明的

症狀: 看不見/幽靈物件 · 主要負責 遊戲開發團隊(用戶端開發)

記憶體/VRAM 不足導致串流失敗 Memory / VRAM exhaustion

兩個用戶端分著用顯示記憶體時,沒有空間載入新需要的模型與貼圖,部分物件就畫不出來。

為什麼: 兩個用戶端分用 VRAM 與 RAM。OS 有時也會先縮減背景視窗的顯示記憶體配額 → 於是: 引擎無法載入新的模型與貼圖,或不斷卸載又重新載入 → 畫面上: NPC 很晚才出現、模糊或看不見,畫面卡頓

症狀: 看不見/幽靈物件, 卡頓 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 外部(外部)

顯示選項不同 Different display settings

顯示人數上限、隱藏 NPC 名字與模型、低規格模式這類選項,在兩個用戶端設定不同時,看到的東西就不同。

為什麼: 只有一個用戶端開了「周圍角色顯示數量上限」或低規格模式 → 於是: 不繪製遠處或優先順序低的 NPC(正常) → 畫面上: 只有一邊沒有 NPC

症狀: 看不見/幽靈物件 · 主要負責 遊戲開發團隊(用戶端開發)

用戶端版本、資料不一致 Client version / data table mismatch

第二個用戶端是不同的安裝版本或尚未更新完成時,會不認得伺服器送來的新 NPC ID,直接默默忽略。

為什麼: 其他資料夾的安裝版本,或在更新途中執行的用戶端 → 於是: 收到不認得的 NPC ID、模型 ID 就略過 → 畫面上: 只有新加入的 NPC 在一邊看不見

症狀: 看不見/幽靈物件 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

各連線的傳送預算與優先順序 Per-connection bandwidth budget and priority

伺服器對每條連線的傳送量設上限、從近的開始送時,上限被設得較低的那一邊,會較晚收到或收不到遠處的 NPC。

為什麼: 在人多的地方,伺服器在每條連線的傳送量上限內依重要度依序傳送 → 於是: 頻寬推估偏低的連線(例:因為是背景視窗而接收確認較慢的那一邊),會一直延後後段的物件 → 畫面上: 遠處的 NPC 只在一邊較晚出現或看不見

症狀: 看不見/幽靈物件, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

時鐘推估誤差導致物件暫緩顯示 Clock estimate error holds or discards entities

用戶端推估的伺服器時間有誤時,會把剛抵達的物件資訊當成「還在未來」而暫緩,或當成「太舊」而丟棄。

為什麼: 某一個用戶端的伺服器時間推估大幅偏差(在載入中測量、從省電喚醒) → 於是: 內插的基準時間與物件資訊的時間對不上 → 畫面上: 物件很晚才出現,或看起來停住不動

症狀: 看不見/幽靈物件, 卡頓 · 主要負責 遊戲開發團隊(用戶端開發)

06常見原因

TCP 重傳:成因與延遲變大的原因

伺服器指標上的「TCP 重傳(Retransmission)」增加時,lag 回報往往也會跟著增加。重傳代表「封包遺失了」或「誤判為遺失」。原因可能出在從 Wi-Fi 到伺服器網路卡的路徑上任何一處,而在遊戲這種稀疏地送出小封包的連線上,只要遺失一個封包,就可能擴大成數百 ms 的定格。本章整理重傳的根本原因、找出原因的方法與解決方向。

伺服器送出遊戲收到123×遺失456124563等待重送(依復原方式而異)3遊戲什麼都沒收到:定格3、4、5、6 一次湧入:快轉
這是每 50ms 送一次的連線中,只有 3 號遺失的情況。TCP 只依順序交付,所以即使 4、5、6 號抵達,在重新收到 3 號之前也不會交給遊戲。因此一次遺失就變成定格與隨後的快轉。重送的時間點依復原方式而不同,從一次往返左右到「往返時間 + 至少 200ms」(重傳計時器)都有可能(見下方「重傳的種類」)。

重傳拖慢遊戲的四個原因

  1. 順序等待(HOL 阻塞,head-of-line blocking):TCP 在重新收到遺失的那一個之前,不會把後面抵達的封包交給遊戲。遺失一個,後面全部一起停住,再一次放行(定格後快轉)。
  2. 重傳等待時間:送出端要等重傳計時器(RTO)到期才會重送。以 Linux 來說是「往返時間 + 至少 200ms」。重送的封包也遺失的話,等待時間會每次加倍(0.3 秒 → 0.6 秒 → 1.2 秒 ……)。
  3. thin stream(稀疏地送出小封包的連線):快速重傳是靠接收端通知「後面已經有 3 個封包抵達」的訊號(3 個重複 ACK。ACK 是「收到了」的確認)觸發。遊戲封包是 50~200ms 才一個,常常訊號還沒湊齊,RTO 就先到了。這就是大檔案下載撐得住、唯獨遊戲會停住的原因。新版 Linux 的 RACK 只要後面有一個封包抵達就能判斷,大幅縮小了這個差距,但封包間隔長達 200ms 左右時,RACK 也不會比 RTO 快。
  4. 傳送量縮減:TCP 把遺失視為壅塞訊號,會縮小一次能送出的量(壅塞視窗)。一旦走到 RTO,就得從一次只送一個的狀態重新增加。這段期間新產生的封包會堆在伺服器上等待,人多地方的大量更新也會一個接一個被延後。

重傳的種類

種類何時發生復原所需時間遊戲中的樣子
快速重傳
Fast retransmit
後面的封包先抵達,接收端通知「中間缺了」(重複 ACK、SACK)時往返時間 + 後面 3 個封包抵達所需的時間短暫頓一下。封包越密集越快
RACK、TLP
以時間判斷遺失,重送尾端封包
較晚送出的封包已抵達,但前面的封包過了一定時間仍未到時;或是一段時間沒有 ACK 時,把最後一個封包再送一次後面封包的確認一到就很快重送(RACK。可能只是順序對調,所以會多等約往返時間的 1/4)。沒有後續封包時約為往返時間的 2 倍(TLP),尚未確認的封包只有一個時,考量延遲 ACK 會再多等 200msthin stream 上也只是相對短暫地頓一下。新版 Linux 預設
RTO 重傳
Retransmission timeout
完全沒有訊號、等待時間到期時往返時間 + 至少 200ms,每次失敗加倍數百 ms~數秒的定格後快轉,拖久了會斷線
SYN 重傳連線請求本身遺失時(連線等待佇列(backlog)溢位、防火牆阻擋)1 秒、2 秒、4 秒、8 秒 ……(Linux 6.5 以上會先以 1 秒間隔重送最多五次再開始加倍,舊版 Windows 從 3 秒開始)按下連線後,延遲剛好落在 1 秒、3 秒這種整秒數,持續失敗就會連不上/無限讀取
不必要的重傳
Spurious retransmission
沒有遺失,只是晚到或順序對調,被誤認為遺失而重送沒有需要復原的東西,卻只有傳送量被縮減(Linux 若透過 DSACK 或時間戳記偵測到,有時會撤回縮減;DSACK 是接收端的「已經收到」通知)浪費頻寬,大量傳輸速度下降。指標上只有重傳率偏高
Zero window probe
容易與重傳混淆
接收端緩衝區滿了、處於「先別送」狀態時,送出端用來確認狀態的封包直到接收端開始讀取定格。線路正常,問題出在接收端程式沒有及時讀取

如何找出在哪裡遺失

重傳率是「送出的封包中重送的比例」。開機以來的累計值會淹沒近期的變化,所以要用 1 分鐘這類固定間隔內的增加量來計算。沒有公認的標準,但整個伺服器平均值的大致感覺是:低於 0.1% 為健康,0.1~1% 代表部分玩家偶爾頓一下,超過 1% 會有很多玩家感覺到,超過 3% 屬於嚴重。手機、海外玩家多的遊戲,平時的數值會更高。所以比起只看一個數字,更要同時看比平時增加了幾倍。平均值會被少數不良線路拉動,所以按地區、電信業者、伺服器、時段拆開來看,是找出原因的捷徑。如果中間有先接下連線、再重新連到伺服器的設備(proxy、部分負載平衡器與閘道),遊戲伺服器的指標只會反映該設備與伺服器之間的區段。玩家端的重傳要在那台設備上看。

在哪裡看看什麼可以知道什麼
整個伺服器(Linux)以 1 分鐘間隔執行兩次 nstat 的增加量:TcpRetransSegs ÷ TcpOutSegs,以及 TcpExt 系列的 TCPTimeouts、TCPLossProbes、TCPLossProbeRecovery、TCPLostRetransmit、TCPSpuriousRTOs、TCPDSACKRecv、TCPSynRetrans重傳率、走到 RTO 的次數、送出 TLP 的次數與其中實際補上遺失的次數、連重送的封包都再次遺失的次數、連線請求重傳。DSACK、Spurious 多代表「沒有遺失卻重送」。Linux 的 OutSegs 不含重傳的部分,嚴格的比例是 RetransSegs ÷ (OutSegs + RetransSegs),但在 1% 左右差異很小
每條連線(Linux)ss -ti 的 retrans(目前復原中/累計)、rto、backoff、rtt、cwnd、lost、reordering、bytes_retrans是否只有特定玩家、地區重傳多,RTO 拉長了多少(backoff 是 RTO 連續加倍的次數)。bytes_retrans ÷ bytes_sent 就是該連線的重傳率
逐筆重傳(Linux)eBPF 工具 tcpretrans(bcc)。-c 為依連線彙總,-l 包含 TLP每發生一次重傳就顯示一行對端 IP、port、連線狀態。不用封包擷取就能輕量確認重傳集中在哪個玩家 IP 網段、哪台伺服器
伺服器網路卡ip -s -s link 的 dropped、missed、crc,ethtool -S 的 rx_missed_errors、rx_no_buffer_count、rx_crc_errors 等(名稱依驅動程式而異,mlx5 是 rx_out_of_buffer、rx_discards_phy),/proc/net/softnet_stat 的第 2 欄(dropped)、第 3 欄(time_squeeze)伺服器網路卡是否一收到就丟棄(ring buffer、CPU),或是線材、光模組不良(CRC)。softnet_stat 每顆 CPU 一行,以 16 進位表示。time_squeeze 持續增加,代表負責接收處理的核心無法及時完成工作
雲端網路AWS ENA 看 ethtool -S 的 bw_in_allowance_exceeded、bw_out_allowance_exceeded、pps_allowance_exceeded、conntrack_allowance_exceeded、linklocal_allowance_exceeded。預設的 CloudWatch 畫面沒有這些值,要用 CloudWatch agent 另外收集是否在執行個體上限處被默默丟棄。數值持續增加就是超過上限。其他雲端也有依 VM 規格而定的頻寬、連線數上限
交換器、路由器、防火牆port 的 CRC 與輸入錯誤、輸出丟棄、policer 超量、session 表使用量、丟棄 log是否在資料中心設備區段被丟棄。5 分鐘平均使用率很低、輸出丟棄卻增加,就是 microburst(極短時間內的流量湧入)
路徑用 mtr、pathping 看一路延續到終點的遺失。要送數百次以上才看得出 1% 左右的遺失,用與遊戲相同的 TCP port 送(mtr -T -P PORT)會更準確從第幾個躍點開始出現遺失。只有中間某一個躍點顯示遺失、後面都正常時,只是那台設備限制了測量用的回應(ICMP)。去程與回程的路徑可能不同,所以也要從伺服器端往玩家端測量
封包擷取(兩端)Wireshark 篩選器 tcp.analysis.retransmission,以及同屬 tcp.analysis. 系列的 fast_retransmission、spurious_retransmission、duplicate_ack、lost_segment、zero_window原始封包在送出端的擷取中有、接收端沒有,就是在兩端之間遺失。接收端也有的話,是不必要的重傳,或 ACK 在回程中延遲或遺失。在接收端伺服器 ring buffer 被丟棄的封包,在擷取中也會顯示成「在兩端之間遺失」,所以要搭配網路卡計數器一起看
Windows Server效能監視器的 TCPv4\Segments Retransmitted/sec ÷ Segments Sent/sec、Network Interface\Packets Received Discarded、netsh int tcp show global、pktmon(Windows 10 1809、Windows Server 2019 以上內建)重傳率趨勢、網路卡是否一收到就丟棄、TCP 設定、在 Windows 內部的哪個環節被丟棄

確認順序:和基礎設施團隊一起查時,照以下順序最快。

  1. 何時、對誰:查重傳率從什麼時候開始增加,是否集中在特定地區、電信業者、伺服器、時段。
  2. 是否真的遺失:TCPSpuriousRTOs、DSACK 一起增加時,先懷疑把晚到的封包誤認為遺失的不必要重傳。
  3. 伺服器接收階段:同一時間網路卡、softnet、雲端上限計數器有增加的話,就是在伺服器端被丟棄。
  4. 資料中心設備:查看交換器、防火牆的丟棄與 CRC 計數器,以及 session 表。
  5. 外部路徑:從問題玩家端與伺服器端雙向執行 mtr,找出遺失開始出現的躍點。
  6. 還是查不出來時:在兩端同一時間擷取封包並比對。

回報中如果有時間(精確到秒)、玩家的電信業者與地區、連線的伺服器、症狀名稱,基礎設施團隊就能立刻照這個順序查。

解決方向

1. 不要遺失(根本解決)

  • 用有線取代 Wi-Fi,改用 5GHz、6GHz,在分享器啟用 SQM、ECN 以減少佇列溢位
  • 讓伺服器不要一次射出整個 tick 的更新,在 tick 內分散送出。單一連線集中送出的流量,用 pacing(fq、BBR、傳送速率上限)攤平
  • 加大 ring buffer,把中斷分散到多個核心,確認雲端上限
  • 更換有 CRC 錯誤的線材、光模組,統一雙工(duplex)設定
  • 用 shaper(先放進佇列再慢慢送出)取代 policer(超出的部分立即丟棄),提高允許的突發量(burst)
  • 為防火牆與連線追蹤(conntrack)表、中間設備的每秒封包數保留餘裕,讓去程與回程經過同一台防火牆
  • 調整 MSS(一個封包可承載的最大資料量)並允許超過大小的通知(ICMP),防止 MTU 黑洞(MTU 探索是最後的安全網);以用戶端送出的心跳封包維持 NAT、LB 的 mapping

2. 快速復原

  • 確認 SACK、時間戳記沒有在伺服器設定中被關閉,或被中間設備移除(沒有 SACK,RACK-TLP 也無法運作)
  • 使用 RACK-TLP(新版 Linux、Android 預設)。像自己的輸入這種由用戶端送出的方向,由用戶端 OS 負責復原,伺服器設定改變不了(Windows 從 Windows 10 的 1607 版、Windows Server 2016 起預設啟用 TLP 與 RACK,連遺失的重傳也能復原的新版 RACK 則從 Windows Server 2022 起)
  • thin stream 用 tcp_thin_linear_timeouts,Linux 6.15 以上可用 TCP_RTO_MAX_MS 降低 RTO 上限
  • 遊戲連線要開啟 TCP_NODELAY(Nagle 把新封包累積起來不送的話,RACK 可用的後續封包就沒了)
  • 內部網路的伺服器之間連線,降低各路由的 RTO 最小值(ip route … rto_min)
  • 用 TCP_USER_TIMEOUT 和遊戲心跳封包快速切斷失效的連線並重新連線

3. 降低對重傳的敏感度(架構)

  • 即時位置、戰鬥資料走 UDP,只重送需要的部分(舊位置沒有重送的價值)。輸入如果把最近幾個重疊送出,遺失一個時下一個封包就能補上
  • 把聊天、交易這類需要順序的資料與即時封包分成不同的流(QUIC stream、拆分 TCP 連線等)。一邊的遺失不會擋住另一邊
  • 如果繼續用 TCP,不要在傳送緩衝區堆積舊位置,改用最新狀態覆蓋(TCP_NOTSENT_LOWAT 等)。長時間定格後的快轉會變短
  • 用內插緩衝與預測,在畫面上掩蓋短暫的停頓。數百 ms 的 RTO 定格則難以掩蓋

設定名稱整理:該開啟的設定與容易混淆的設定

與重傳復原相關的設定大多是作業系統(kernel)設定,能只對遊戲連線個別開啟的 socket 選項只有幾個。常因名稱而被混淆的 TCP_NODELAY 並不會加快復原。不過如果不開啟(使用 Nagle),復原期間新封包會更晚送出。下表以 Linux 為準,Windows 的名稱與支援範圍不同。

設定設定位置改變什麼注意
TCP_NODELAYsocket 選項關閉 Nagle。小訊息不累積,立即送出消除即使沒有遺失也會出現的 40~200ms 等待。重傳計時器(RTO)本身不變。不過 Nagle 開著的話,等待復原期間新封包也會被綁住,復原後還要再等一次往返;快速重傳與 RACK 所依賴的後續封包也送不出去,容易走到 RTO。遊戲通常會開啟
net.ipv4.tcp_recovery (RACK)kernel 設定以時間判斷遺失。不怕順序對調,thin stream 也能快速復原預設值 1(開啟)。Linux 4.4 加入,約在 4.18 時成為現在的樣子。從 6.17 起 RACK 是唯一的遺失判斷方式,改成 0 也沒有效果。在沒有 SACK 的連線上無法運作
net.ipv4.tcp_early_retrans (TLP)kernel 設定一段時間(約往返時間的 2 倍)沒有 ACK 時,把最後一個封包再送一次,及早發現尾端遺失(tail loss)預設值 3(開啟),0 為關閉。需要 SACK 才能運作。尚未收到 ACK 的封包(in-flight)只有一個時,會再多等 200ms,變得和 RTO 差不多
net.ipv4.tcp_sack, tcp_dsack, tcp_timestampskernel 設定選擇性 ACK(SACK,通知中間缺少的部分)、重複接收通知(DSACK)、往返時間測量(時間戳記)預設全部開啟。有些伺服器在 2019 年 SACK 資安問題時關閉後一直沒恢復。SACK 關閉時,RACK、TLP 也會跟著無法運作
net.ipv4.tcp_thin_linear_timeouts / TCP_THIN_LINEAR_TIMEOUTSkernel 設定/socket 選項尚未收到 ACK 的封包(in-flight)少於 4 個的連線,前 6 次 RTO 不加倍預設關閉。可用 socket 選項只對遊戲連線開啟。不會縮短第一次的 RTO
TCP_RTO_MAX_MS / net.ipv4.tcp_rto_max_mssocket 選項/kernel 設定(Linux 6.15 以上)降低持續加倍的 RTO 上限(預設 120 秒)。最小 1 秒避免連續遺失後 RTO 膨脹到數十秒。判定失效連線也會跟著變快
net.ipv4.tcp_mtu_probingkernel 設定大封包持續消失時縮小封包尺寸,穿過 MTU 黑洞預設值 0(關閉)。1 = 重傳持續約 3 秒、懷疑有黑洞時才縮小(在那之前會停住)。2 = 一開始就從 1,024 位元組開始,逐步嘗試加大
ip route … rto_min路由設定降低該路由的 RTO 最小值(預設 200ms)只用在伺服器之間的內部網路。在網際網路區段降低會增加不必要的重傳。Linux 6.11 以上的 net.ipv4.tcp_rto_min_us 是整台伺服器的值,網際網路連線也會一起改變。6.15 以上可以用 socket 選項 TCP_RTO_MIN_US 只對內部連線降低
TCP_USER_TIMEOUTsocket 選項重傳持續時,放棄連線之前的時間不會加快復原。讓失效的連線及早切斷並重新連線。沒有設定時,Linux 會重傳 15 次左右、持續約 15 分鐘後才切斷(tcp_retries2)
SO_KEEPALIVE + TCP_KEEPIDLE 等socket 選項確認閒置連線是否還活著與重傳無關。用於維持 NAT、LB 的 mapping,以及偵測失效的連線
fq 佇列 + SO_MAX_PACING_RATE、BBR佇列設定/socket 選項/kernel 設定把封包平均分散送出,減少突發流量(burst,一次集中送出)造成的遺失屬於「預防」遺失。與復原速度無關

TCP 重傳的根本原因

無線區段的封包遺失 Wi-Fi / cellular link loss

Wi-Fi 與行動網路在無線區段會先重傳幾次,仍然失敗就丟棄封包。被丟棄的封包,TCP 要過好一段時間才會重送。

為什麼: 訊號弱或干擾嚴重,無線區段的傳輸接連失敗 → 於是: 超過無線設備的重試上限(通常是數次~十幾次)就丟棄封包 → 畫面上: 定格的時間等於 TCP 等待重傳的時間,後面的封包在接收緩衝區等候,之後快轉

症狀: 定格, 快轉, 瞬移 · 主要負責 外部(外部) · 協同 基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(伺服器開發), 遊戲開發團隊(用戶端開發)

瓶頸佇列溢位(壅塞遺失) Tail drop at a congested bottleneck

分享器、電信業者之間的互連區段、資料中心線路這類最窄的地方,佇列一滿就會丟棄新進來的封包。

為什麼: 影片、下載與其他使用者的流量把瓶頸區段塞滿 → 於是: 佇列滿的期間,新到的封包接連被丟棄(tail drop)。沒被丟棄的封包也要在塞滿的佇列尾端等待 → 畫面上: 多個封包同時消失,長時間定格後快轉,晚間時段常見

症狀: 定格, 快轉, 拉回 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 外部(外部), 遊戲開發團隊(用戶端開發)

突發傳送造成淺緩衝區溢位 Sender bursts overflow shallow buffers

伺服器每個 tick 把數千人份的更新在一瞬間集中送出時,交換器的小緩衝區或雲端的瞬間上限不到 1ms 就會溢位,部分封包因此被丟棄。

為什麼: 每個 tick 開始的瞬間,把要送給所有人的封包一次送出 → 於是: 匯集多台伺服器流量的交換器 port 緩衝區(每個 port 數百 KB~數 MB)或雲端執行個體的上限瞬間溢位(平均使用率很低) → 畫面上: 多人同時瞬移、頓一下,看平均指標找不出原因

症狀: 瞬移, 定格, 快轉 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施), 基礎設施團隊(網路基礎設施)

Policer 丟棄超額流量 Traffic policing

電信業者的資費方案、雲端執行個體的上限、DDoS 防護設備,有時會把超過規定速度的封包直接丟棄,不放進佇列。

為什麼: 瞬間傳送量超過允許速率或允許突發量 → 於是: 超出的封包不經佇列直接丟棄(policing) → 畫面上: 每逢突發量大的瞬間就有多個封包消失,定格後快轉;平均速度看起來低於上限

症狀: 定格, 快轉, 瞬移 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(伺服器開發)

實體層錯誤(線材、光模組、接頭不良) Bit errors: bad cable, optics, dirty fiber

纜線損壞、沾了灰塵的光纖接頭、壽命將盡的光模組會造成位元錯誤,損壞的封包會被設備默默丟棄。

為什麼: 線材、光模組、接頭不良導致位元翻轉 → 於是: 設備丟棄檢查碼(CRC)不符的封包 → 畫面上: 只有經過該路徑的人持續出現短暫頓一下後快轉,與時段無關

症狀: 定格, 快轉, 瞬移 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 基礎設施團隊(伺服器基礎設施), 外部(外部)

雙工模式不一致 Duplex mismatch

一端設為自動協商、另一端把速度與雙工固定時,其中一端會以半雙工運作,每當負載升高就因碰撞而遺失封包。

為什麼: 只有其中一台設備把速度與雙工設成固定值 → 於是: 一端以全雙工運作、另一端以半雙工運作,發生碰撞與延遲碰撞 → 畫面上: 平常一切正常,流量一增加,經過該設備的所有人都會定格後快轉

症狀: 定格, 快轉 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 基礎設施團隊(伺服器基礎設施)

接收端伺服器主機丟棄封包 Receiver host drops (ring, softirq, CPU)

封包已經到了伺服器,卻因 NIC 的 ring buffer(暫存剛抵達封包的緩衝區)溢位,或 kernel 負責接收處理的 CPU 核心滿載而被丟棄。

為什麼: 連線人數暴增、中斷集中在單一核心、虛擬機器 CPU steal、虛擬交換器過載 → 於是: 在 ring buffer(rx_missed_errors 等,名稱依驅動程式而異)或 kernel 接收佇列(softnet dropped)被丟棄 → 畫面上: 人潮湧入時,整個伺服器同時出現輸入慢半拍才生效、頓一下

症狀: 輸入延遲, 定格, 快轉, 瞬移 · 主要負責 基礎設施團隊(伺服器基礎設施)

防火牆與連線追蹤丟棄封包 Stateful firewall / conntrack drops

防火牆或 Linux 連線追蹤(conntrack,把經過的連線記錄在表中的功能),在表已滿或判斷連線狀態不符時,會丟棄封包。

為什麼: 連線追蹤表已滿(table full),或去回程路徑不同,只有單一方向經過防火牆(非對稱路由) → 於是: 防火牆把封包視為「未知連線」或「序號超出視窗範圍」而丟棄 → 畫面上: 表滿了之後新連線會被擋下;路徑不一致時,只有走該路徑的人在反覆重傳後斷線

症狀: 定格, 斷線, 連不上/無限讀取 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(伺服器開發), 遊戲開發團隊(用戶端開發)

中間設備超過處理上限(防火牆、IPS、DDoS 防護) Inline appliance PPS / CPU overload

防火牆、入侵防禦設備(IPS)、DDoS 防護設備會逐一檢查經過的封包。從超過檢查能力的那一刻起,處理不了的封包就會被丟棄。

為什麼: 尖峰時段或活動期間,每秒湧入數十萬個以上的小型遊戲封包,或是檢查規則太重 → 於是: 設備的 CPU 或每秒封包數達到上限,封包在設備上被丟棄。誤判時連正常封包也會被擋 → 畫面上: 該設備後方的所有伺服器同時出現定格、瞬移,只在人潮湧入時加劇

症狀: 定格, 快轉, 瞬移, 斷線 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 遊戲開發團隊(伺服器開發)

MTU 黑洞(只有大封包反覆遺失) PMTU black hole

中途區段能接收的大小變小,而「封包太大」的通知(ICMP)又被擋掉時,大封包不管重送幾次都會一直消失。

為什麼: VPN 或通道區段的最大大小變小,大小超過的通知被防火牆擋掉 → 於是: 傳送端不知道原因,持續重傳同一個大封包,RTO 每次加倍 → 畫面上: 平常一切正常,但在背包、人多的地方、進場載入這類有大量資料往來的瞬間,連後面的小封包也全部卡住而定格,最後斷線或無限讀取

症狀: 定格, 斷線, 連不上/無限讀取 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(伺服器開發)

連線途中 NAT 或負載平衡器的 mapping 過期 NAT / load balancer mapping expired mid-connection

中途設備刪除閒置連線的 mapping(記錄這條連線要轉送到哪裡的項目)後,接下來送出的封包就無法送達。連線會不斷重傳直到斷線,或是設備回傳拒絕連線(RST)而立刻斷線。

為什麼: 有一段時間沒有任何封包往來的連線(暫離、大廳) → 於是: 分享器 NAT、電信業者 CGNAT、防火牆、負載平衡器、雲端安全群組刪除閒置的 mapping → 畫面上: 再次移動的瞬間接連重傳,最後斷線,或是直接斷線

症狀: 斷線, 定格 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發), 基礎設施團隊(網路基礎設施), 基礎設施團隊(伺服器基礎設施)

路由變更、ECMP 不良路徑 Route change / bad ECMP member

網際網路路由變更的那幾秒內,或是多條 ECMP 路徑中被分配到不良路徑的連線,會有封包消失。

為什麼: BGP 重新計算路由,或多條路徑(ECMP、LAG)中某一條的設備或線路不良 → 於是: 切換路由時暫時遺失,或只有走該路徑的連線持續遺失 → 畫面上: 突然定格幾秒後快轉,或是「重新連線就變好」(被分配到其他路徑)

症狀: 定格, 快轉, 瞬移 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 遊戲開發團隊(伺服器開發), 外部(外部)

延遲飆升造成的不必要重傳 Spurious RTO from delay spikes

封包沒有消失,只是短暫地很晚才到;但這段延遲比 RTO 長時,傳送端就會判斷為遺失而重傳。

為什麼: bufferbloat、Wi-Fi 省電、行動網路無線狀態切換、虛擬機器暫停,造成瞬間延遲達數百 ms → 於是: RTO 先到期而重傳,原本的封包也隨即抵達(接收端收到重複的封包) → 畫面上: 定格與快轉是延遲飆升本身造成的。不必要的重傳幾乎不會拉長定格,只會推高重傳指標,因而被誤認為遺失

症狀: 定格, 快轉, 輸入延遲 · 主要負責 外部(外部) · 協同 基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(用戶端開發)

封包亂序造成的不必要快速重傳 Reordering triggers spurious fast retransmit

封包經過多條路徑或聚合鏈路時順序被打亂,接收端會以重複 ACK 告知「有封包缺漏」,傳送端就把其實已正常送達的封包再送一次。

為什麼: 依封包分配路徑的設備、依封包分散傳送的 LAG(鏈路聚合)、路由切換的瞬間,都會打亂封包順序 → 於是: 後面的封包先到,累積 3 個重複 ACK → 快速重傳 → 畫面上: 零星往來的遊戲封包幾乎不受影響。人多處的大型狀態更新與更新檔下載會變慢,偶爾出現卡頓

症狀: 卡頓, 輸入延遲 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 基礎設施團隊(伺服器基礎設施)

ACK 延遲或消失(上傳飽和) ACK path congestion on asymmetric links

資料已順利送達,但「收到了」的 ACK 在塞滿的上傳佇列裡延遲或消失時,傳送端會判斷為遺失而重傳。

為什麼: 家中有人上傳影片或做雲端備份,把上傳頻寬塞滿 → 於是: ACK 在分享器佇列裡延遲數百 ms,或因溢位而被丟棄 → 畫面上: 伺服器送來的遊戲封包大致準時到達。自己的輸入堆在同一個上傳佇列裡而晚送出,造成輸入延遲、拉回,偶爾出現不必要的重傳

症狀: 輸入延遲, 拉回 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

RTO 設定不符合環境 RTO min too low or too high

RTO 最小值調得太低時,只要稍微延遲就會產生不必要的重傳;預設值(200ms)對遊戲來說又太長,每遺失一次就要停住很久。

為什麼: 為了資料中心環境把 RTO 最小值大幅調低,或在網際網路區段直接沿用預設值 → 於是: 太低時,瞬間延遲也會引發大量重傳;太高時,每次遺失都要等很久 → 畫面上: 預設值時,遺失一次就定格數百 ms 後快轉;調得太低時定格變短,但不必要的重傳暴增,浪費線路頻寬

症狀: 定格, 快轉, 輸入延遲 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發)

thin stream 復原緩慢 Thin streams fall back to RTO

像遊戲這樣零星送出小封包時,在湊齊「後續 3 個封包」之前 RTO 就先到了。同樣的遺失,停住的時間比大量傳輸長得多。

為什麼: 封包間隔約 100ms,尚未收到 ACK 的封包(in-flight)沒有幾個 → 於是: 要湊齊 3 個重複 ACK 得花 300ms 以上,所以 RTO(ping + 200ms)先觸發,連續遺失時每次加倍 → 畫面上: 遺失一次就定格 0.3 秒左右,連重送的封包也遺失時,定格將近 1 秒後快轉

症狀: 定格, 快轉 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(用戶端開發)

中間設備移除 TCP 選項 Middlebox strips TCP options

部分防火牆或加速設備刪除或改寫 TCP 選項時,遺失多個封包後一個往返只能復原一個,或是視窗(一次可送出的量)變小,傳輸因此變慢。

為什麼: 防火牆的「TCP 正規化」、老舊的加速設備移除 SACK、時間戳記、視窗縮放選項 → 於是: 遺失多個封包時每個往返只能復原一個,視窗被限制在 64KB → 畫面上: 每次遺失時定格的時間都長得多(沒有 SACK 就無法使用 RACK-TLP),恢復後快轉。更新檔這類大量傳輸也會變慢

症狀: 定格, 快轉 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 基礎設施團隊(伺服器基礎設施)

Zero window(看起來像重傳的停頓) Zero window, often mistaken for retransmission

接收端程式沒有及時讀取 socket、緩衝區塞滿時,傳送端會停止傳送,只送出 zero window probe。線路本身沒有問題。

為什麼: 用戶端的畫格更新停住、伺服器執行緒卡住,導致無法讀取 socket → 於是: 接收視窗變成 0,傳送端停止傳送、只送 probe(間隔越來越長) → 畫面上: 定格後快轉。封包擷取中看得到「ZeroWindow」,沒有遺失

症狀: 定格, 快轉 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發), 基礎設施團隊(伺服器基礎設施)

連線請求(SYN)重傳 SYN retransmission on connect

連線請求因連線等待佇列(backlog)溢位或被防火牆阻擋而消失時,用戶端 OS 會從 1 秒後開始,逐步拉長間隔重送。

為什麼: 維護剛結束時連線暴增,伺服器的連線等待佇列溢位,或是防火牆、DDoS 防護丟棄 SYN → 於是: 用戶端 OS 從 1 秒後開始以固定間隔重傳 SYN(舊版 Linux 為 1 秒 → 2 秒 → 4 秒) → 畫面上: 按下連線按鈕後,延遲剛好是 1 秒、3 秒這種整秒數,持續失敗就連不上/無限讀取

症狀: 連不上/無限讀取 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施), 基礎設施團隊(網路基礎設施), 遊戲開發團隊(用戶端開發)

07權責劃分

遊戲開發團隊與基礎設施團隊的負責範圍

同樣是 lag,修的地方也不同。用戶端、伺服器程式碼與同步設計由遊戲開發團隊負責,線路、網路設備、伺服器設備、DB 設備由基礎設施團隊負責。玩家的電腦與家用網路、電信業者區段、雲端供應商端的問題,兩個團隊都無法直接修正,只能引導、提出請求或繞過。每張原因卡片都標示了主要負責與協同處理的單位,展開卡片的「數值參考、確認方法、各團隊因應」,就能看到各團隊分別要做的事。

  1. 回報、警示症狀、精確到秒的時間、伺服器/頻道
  2. 誰會遇到一個人、一個家/特定電信業者、地區/特定伺服器、頻道/全部
  3. 優先聯絡候選原因卡片的主要負責。診斷小幫手的「優先確認」
  4. 要交接的資訊IP 與電信業者、斷線原因、相關圖表、最近的變更
  5. 一起做的事依卡片上各團隊要做的事分工
這是工單進來後,到分配成各團隊待辦事項的流程。「誰會遇到」最能決定負責單位,詳細標準見下表與用觀測資料判定。
負責單位負責範圍常用的解決手段
遊戲開發團隊用戶端遊戲用戶端程式碼:畫格、GC、載入,內插、外插、預測,用戶端的網路處理(包含送出心跳封包、自動重新連線)修改程式碼、調整內插緩衝與預測、改變載入方式、心跳間隔與重新連線流程、用戶端更新
遊戲開發團隊伺服器遊戲伺服器程式碼:tick、執行緒、鎖,同步設計,連線處理(accept 迴圈、listen 參數),心跳回應與斷線連線的清理,socket 選項,查詢與 transaction 設計邏輯最佳化、非同步呼叫、分散 tick 與地圖負載、登入排隊、用 session token 接續、socket 選項(TCP_NODELAY 等)、查詢與索引設計、伺服器更新
基礎設施團隊網路線路與 IDC 網路設備(交換器、路由器、防火牆、負載平衡器、DDoS 防護),雲端網路 ACL、VPC 路由、負載平衡器,電信業者與 peering設備設定與更換、擴充線路與 peering、變更路由、向電信業者升級處理(escalation)、調整負載平衡器與防火牆的閒置逾時與 session 上限
基礎設施團隊伺服器設備/OS伺服器設備與雲端執行個體(包含安全群組、連線追蹤),OS 與 kernel 設定,NIC,部署與監控環境擴充、變更執行個體、kernel 設定(sysctl:somaxconn、conntrack 等)、安全群組配置與連線追蹤時間、NIC ring buffer 與中斷分散、調整 cron 與備份時間
基礎設施團隊DB 設備DB 伺服器與儲存裝置,DB 設定、複寫、備份,快取伺服器擴充 DB、確保儲存裝置 IOPS、DB 參數與複寫設定、調整備份與檢查點
外部玩家/電信業者/雲端玩家的電腦與家用網路、電信業者區段(我方合約之外)、雲端供應商引導玩家(改用有線連線等)、向電信業者與雲端供應商提出請求、遊戲端繞過與緩解

各層/主題負責單位一覽

粗體數字是該單位擔任主要負責的原因數,+數字是協同處理的原因數。點選格子,下方會顯示這些原因與該團隊要做的事。

界線模糊時:原因所在的團隊為主要負責,其他團隊負責緩解與確認

主要負責是根本原因所在之處,或是能消除根本原因的單位。即使原因是線路或設備,遊戲開發團隊在這段期間也要用降低影響的設計(內插緩衝、輸入重複傳送、重新連線)撐住;原因若是伺服器程式碼,基礎設施團隊增加設備也只是暫時延後問題。常被混淆的界線規定如下。

  • 閒置後斷線:玩家分享器與電信業者設備的閒置逾時我方無法改變,而這些 mapping(位址與 port 的對應紀錄)只有靠從內部往外送的封包才能確實維持。因此由用戶端送出心跳封包,斷線時自動重新連線;伺服器回應心跳封包,收不到時先清理連線,再用 session token 接續。基礎設施團隊告知我方設備的逾時值,必要時調高。
  • 連線等待佇列(backlog)溢位:實際的上限是伺服器程式碼的 listen 參數與 accept 迴圈,所以伺服器開發是主要負責,伺服器設備/OS 負責 kernel 上限(somaxconn)與 SYN cookie。
  • 雲端:安全群組與執行個體的連線追蹤由伺服器設備/OS 負責,網路 ACL、VPC 路由、雲端負載平衡器由網路負責。

表中的「先找」是第一個要找的單位,依符合該現象的原因卡片中各單位擔任主要負責的次數決定。列出兩個時,前者是擔任主要負責的卡片最多的單位,後者是一開始就要一起找來的單位。

現象遊戲開發團隊要做的事基礎設施團隊要做的事優先查看的指標
特定電信業者、地區的遺失與抖動大
先找基礎設施團隊網路
自適應內插緩衝、輸入重疊傳送、耐遺失的 UDP 傳輸、用每條連線的遺失與重傳統計找出受影響玩家的 IP、port、時間,依線路狀態放寬移動驗證標準用與遊戲相同的協定、port 雙向測量路徑(mtr),避開不良路徑,向電信業者升級處理,增加 peering 與線路各電信業者的遺失率與抖動分布、重傳率
TCP 重傳增加
先找基礎設施團隊網路遊戲開發團隊伺服器
TCP_NODELAY、把一個 tick 的傳送分散在 tick 內、及時讀取 socket(防止 zero window)、用心跳封包維持 mapping、即時封包改走 UDP 或其他連線、不在傳送緩衝區堆積舊位置(TCP_NOTSENT_LOWAT)消除遺失點(線材、光模組、雙工、policer、防火牆連線追蹤、MTU)、調整 MSS、伺服器 ring buffer 與中斷分散、kernel 復原設定(RACK、tcp_mtu_probing)重傳增加量(nstat)、zero window 次數、NIC 與交換器 port 的丟棄、CRC 計數器
伺服器 CPU 飽和導致 tick 超出預算
先找遊戲開發團隊伺服器
最佳化視野計算與廣播、把 tick 拆到多個執行緒、拆分擁擠的地圖與頻道、讓工作執行緒數符合 CPU 上限、把 tick 處理時間記錄成指標單核心效能(時脈)高的 CPU 與執行個體、各核心 CPU 使用率警示、確認 CPU steal 與容器 CPU 節流、把處理中斷的核心與 tick 執行緒的核心分開tick 處理時間、各核心 CPU 使用率、steal、節流次數(nr_throttled)
DB 回應延遲
先找遊戲開發團隊伺服器基礎設施團隊DB 設備
查詢、索引、transaction 設計(縮短 transaction、統一鎖定順序)、在遊戲執行緒之外非同步呼叫、合併查詢與快取、調整連線池大小與等待逾時找出慢查詢、執行計畫、鎖等待並分享給遊戲開發團隊,檢查點、複寫、統計資訊更新設定,儲存裝置 IOPS,確認伺服器數 × 連線池大小在最大連線數以內,擴充 DB 設備慢查詢 log、鎖等待、連線等待、複寫延遲、IOPS
維護剛結束時連不上
先找遊戲開發團隊伺服器
讓接收連線的執行緒(accept 迴圈)不會因其他工作而停住、加大 listen 的 backlog 參數、登入排隊系統、合併登入查詢(消除 N+1)、用戶端重試間隔逐漸拉長並隨機分散kernel somaxconn 與 SYN cookie、防火牆與負載平衡器的 session 上限、伺服器 conntrack 與檔案描述子上限、DB 快取預熱、活動前預先擴展伺服器ListenOverflows、session 表與 conntrack 使用率、登入查詢數與連線等待
閒置一段時間就斷線
先找遊戲開發團隊用戶端
用戶端:以最短閒置逾時的一半以下為間隔送出心跳封包(即使有一個晚到或遺失,下一個也能在逾時前抵達),斷線時自動重新連線。伺服器:回應心跳封包,一段時間收不到就先清理連線,再用 session token 接續。彙整路徑上負載平衡器、防火牆的閒置逾時(網路)與雲端安全群組的連線追蹤時間(伺服器設備/OS),分享給遊戲開發團隊,我方設備必要時調高。玩家分享器與電信業者 CGNAT 的逾時無法改變斷線連線的閒置時間分布(集中在某個值附近,就是具有該逾時的設備)、網路類型(行動、有線)
每到固定時間伺服器就停住
先找遊戲開發團隊伺服器基礎設施團隊伺服器設備/OS
把整點活動、存檔、計時器、快取到期的時間隨機分散,批次查詢拆小、分批少量執行,明確指定暫停時間短的 GC分散 cron、備份、log 壓縮的時間並降低 I/O 優先順序,DB 備份在複本上進行、檢查點平均分散,確認磁碟 burst credit,限制備份傳輸速度停住的時間點與工作排程(cron、備份、批次、檢查點)的對照,GC log
DDoS、流量暴增
先找基礎設施團隊網路
把遊戲流量模式(port、封包大小、每秒封包數)分享給基礎設施團隊,限制各帳號與角色的請求頻率,及早阻擋異常封包DDoS 防護(清洗)與配合遊戲流量的防護規則、隱藏伺服器位址、設備的每秒封包上限,以 IP 為準的限制要考慮電信業者共用 IP 與網咖每秒封包數、設備 CPU 與丟棄、各地區與電信業者的連線失敗率(確認誤判)
玩家的 Wi-Fi、電腦問題
先找外部玩家/電信業者/雲端遊戲開發團隊用戶端
遊戲內網路狀態顯示(ping、遺失)、依抖動自動調整內插緩衝長度、在出現延遲時的 log 中記錄網路類型與電腦 CPU 使用率、建議改用有線連線等提示文字無法直接修正。同一電信業者、地區的回報集中時,重新歸類為線路問題回報中的線路與裝置資訊、同一電信業者與地區的比例

交接時要準備的資訊

遊戲開發團隊 → 基礎設施團隊

  • 精確時間(到秒,標明時區)與持續時間,目前是否仍在發生
  • 伺服器與頻道 ID、影響範圍(只有我、特定電信業者、整個伺服器)與受影響人數(相對於同時上線人數)
  • 症狀名稱與樣子:斷線的話,斷線前的閒置時間;定格的話,長度與重複週期
  • 受影響者的 IP、port、電信業者、地區(多條路徑中有一條不良時,要有 port 才能分辨),遊戲使用的協定(TCP、UDP)與伺服器 port
  • 遊戲端指標:tick 處理時間、ping 與遺失分布、重傳增加的連線數、斷線原因(心跳逾時、連線重設 RST 等)
  • 目前的心跳間隔、伺服器的無回應判定時間、重試方式
  • 最近有無部署或設定變更,已確認並排除的原因

基礎設施團隊 → 遊戲開發團隊

  • 同一時間的設備與線路指標(使用率、丟棄與錯誤計數器、session 數)與伺服器 OS 指標(ListenOverflows、conntrack 使用率、CPU steal)
  • 路徑上設備的逾時與上限值:負載平衡器與防火牆的閒置逾時、安全群組的連線追蹤時間、session 數與每秒封包數上限
  • 設備與線路的變更紀錄與預定作業(更換、設定變更、備份與 cron、電信業者作業公告)
  • 向電信業者、雲端供應商提出的案件編號與預計回覆時間
  • 臨時措施(繞過、放寬上限)與復原的時間點
  • 原因所在區段與結論、防止再發計畫
  • 遊戲端需要的措施(心跳間隔、重試方式、連線數限制等)

兩個團隊共通:指定一位事故處理負責人,在同一個頻道留下依時間排序的紀錄,並事先告知下一次更新進度的時間。主要負責改由其他團隊擔任時,連同這份紀錄一起交接,避免重複做同樣的確認。結束後,用同一份紀錄修正對應原因卡片的負責單位與要做的事。

用戶端遊戲程式

在玩家電腦或手機上執行的遊戲程式本身。即使網路完美,這裡的畫格一延遲,畫面就會卡頓。網路不好時能掩蓋得多好,也是在這裡決定。

遊戲 1 秒大約重複 60 次同樣的工作:讀取輸入、處理收到的封包、把遊戲狀態推進一步、繪製畫面。這個迴圈跑一次就是一個畫格,60FPS 的話每個畫格有 16.7ms(以 30FPS 執行的手機遊戲是 33.3ms)。一個畫格延遲多久,畫面就停住多久,接著在下一個畫格一口氣補上落後的部分。

在網路方面,用戶端的工作是「補足缺少的資訊」。其他玩家的位置是從伺服器斷斷續續送來的,所以要把中間連起來畫(內插);封包中斷時要靠推測繼續移動(外插);自己的角色則不等伺服器確認,先行移動顯示(預測)。這些技術失敗的樣子,就是瞬移、拉回、卡頓。

比喻

遊戲用戶端就像製作現場直播畫面的電視台副控室。現場(伺服器)的照片斷斷續續送來時,副控室把中間自然地接起來,看起來就像影片。照片晚到時沒有照片可接,畫面就會停住;副控室本身太忙,播出也會中斷。

這一層造成 lag 的原因

畫格時間尖峰 Frame hitch

某一個畫格的計算時間比平常多出好幾倍,畫面短暫停住。

為什麼: 技能特效暴增、大量生成(spawn)、整個 UI 重新整理,全擠在同一個畫格 → 於是: 無法在 16.7ms 內完成,花了 50~300ms → 畫面上: 畫面頓一下,下一個畫格所有東西一次移動到位

症狀: 卡頓, 定格 · 主要負責 遊戲開發團隊(用戶端開發)

用戶端垃圾回收 Client GC (Unity C#, Unreal, Lua)

回收用過即丟的記憶體(垃圾)期間,整個遊戲會暫停。特徵是以規律的間隔出現卡頓。

為什麼: 每個畫格都建立再丟棄暫時性的字串、陣列、List → 於是: 垃圾累積起來後,GC 暫停主執行緒進行回收 → 畫面上: 每隔幾秒~幾十秒規律地出現卡頓

症狀: 卡頓, 定格 · 主要負責 遊戲開發團隊(用戶端開發)

主執行緒同步載入、著色器編譯 Synchronous asset load, shader compile

要繪製第一次出現的地圖、怪物、特效之前,得先讀取檔案並建立著色器,畫面因此停住。

為什麼: 進入新地圖,或第一次出現的技能、裝備、怪物登場 → 於是: 主執行緒等待讀取檔案與著色器編譯 → 畫面上: 只有第一次停住 0.1~1 秒,第二次起就正常

症狀: 定格, 卡頓 · 主要負責 遊戲開發團隊(用戶端開發)

儲存裝置太慢,資源串流跟不上 Slow storage stalls asset streaming

在 HDD 這類較慢的儲存裝置上,讀取開放世界貼圖與模型的速度跟不上移動,物件會晚出現,或遊戲為了等待讀取而卡頓。

為什麼: 騎坐騎、傳送等快速移動,或進入人多的地方,一次需要大量新的貼圖與模型 → 於是: HDD 等較慢的儲存裝置無法以需要的速度讀取,讀取請求越積越多,部分載入還要主執行緒等到完成為止 → 畫面上: 貼圖有一段時間是模糊的,建築與角色晚出現;等待讀取的瞬間出現卡頓、定格

症狀: 看不見/幽靈物件, 卡頓, 定格 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 外部(外部)

大量角色同畫面的渲染負載 Render/animation cost of crowds

攻城戰、世界王這類數百人擠進同一個畫面的場合,光是繪製的成本就負荷不了。

為什麼: 同一個畫面裡數百人與特效重疊 → 於是: 動畫、陰影、名字、特效的成本隨人數成比例增加 → 畫面上: FPS 由 60 → 15 大幅下降,所有動作都卡頓,輸入也跟著延遲

症狀: 卡頓, 輸入延遲 · 主要負責 遊戲開發團隊(用戶端開發)

主執行緒封包處理瓶頸 Network processing on the main thread

每個畫格只處理固定數量的已接收封包時,一次湧入的封包會不斷延到下一個畫格。

為什麼: 人多的地方每秒湧入數千筆更新 → 於是: 主執行緒碰到每畫格的處理量上限,讀不完 → 畫面上: 其他人的動作越來越晚反映,而且一次湧入

症狀: 快轉, 輸入延遲 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

沒有內插緩衝或緩衝太短 Missing/short interpolation buffer

一收到伺服器封包就立刻繪製時,抖動(封包抵達間隔忽長忽短)會原封不動地出現在畫面上。

為什麼: 收到位置就直接繪製,或緩衝比抖動還短 → 於是: 封包晚到多久就停多久,一次湧入多少就跳多少 → 畫面上: 其他角色走走停停,一頓一頓地移動

症狀: 卡頓 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

過度外插(dead reckoning) Over-extrapolation / dead reckoning

封包沒來的期間,照最後的速度繼續顯示移動,等發現猜錯再拉回來。

為什麼: 收不到封包,就照最後的方向與速度繼續移動 → 於是: 實際上對方已經停下或改變方向 → 畫面上: 對方角色走了好一段後一下子移到真正的位置,或穿牆而過。封包抵達間隔忽長忽短時,會反覆衝過頭又被拉回,看起來像在顫動

症狀: 瞬移, 卡頓 · 主要負責 遊戲開發團隊(用戶端開發)

用戶端預測不一致 Prediction mismatch / reconciliation

自己的用戶端已經先顯示移動,伺服器卻算出不同的結果時,自己的角色就會被拉回去。

為什麼: 用戶端在伺服器確認前先移動(預測) → 於是: 伺服器對碰撞、移動速度、buff 的計算結果不同,或沒收到指令 → 畫面上: 確認到達時,自己的角色被往回拉

症狀: 拉回 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

固定時間步長追趕失控 Fixed-timestep catch-up / spiral of death

停過一次之後集中補算落後的部分,又因為這些計算而再度落後。

為什麼: 以固定間隔執行的遊戲模擬停了一次 → 於是: 把積欠的每一步集中在一個畫格內算完 → 畫面上: 長畫格接連出現而飆高,或碰到上限使整個世界變慢

症狀: 卡頓, 快轉, 慢動作 · 主要負責 遊戲開發團隊(用戶端開發)

時鐘同步誤差 Clock sync error

用戶端推估的伺服器時間不準時,內插時間點與冷卻判定就會出現偏差。

為什麼: 只在連線時對時一次,ping 改變了也不重新校正 → 於是: 內插時間點與冷卻結束時間跟伺服器不一致 → 畫面上: 對手偶爾頓一下;冷卻明明結束了,技能卻被拒絕

症狀: 卡頓, 吃指令/回檔 · 主要負責 遊戲開發團隊(用戶端開發)

float 時間精度損失 Float time precision loss on long sessions

以精度較低的浮點數格式(float)保存遊戲時間時,開著的時間越久,時間解析度(能分辨的最小時間差)就越差,動作與特效會顫動。

為什麼: 把遊戲啟動後經過的時間累加在 float 裡,或直接傳給著色器 → 於是: 開著的時間越久,float 能表示的最小差距就越大 → 畫面上: 只有連續開了好幾天的用戶端,角色、動畫、流動特效會不停顫動,重新開啟就恢復正常

症狀: 卡頓 · 主要負責 遊戲開發團隊(用戶端開發)

垂直同步(V-Sync)與渲染佇列 V-Sync, render queue

GPU 畫好的畫格會先在佇列裡堆上幾張,再配合螢幕的更新週期送出,這段期間輸入就被延遲。

為什麼: 顯示卡驅動程式預先在佇列中堆積 1~3 張畫格 → 於是: 輸入反映到畫面上也要多花這麼多時間 → 畫面上: ping 很低,操作卻沉重遲鈍

症狀: 輸入延遲, 卡頓 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 外部(外部)

用戶端記憶體洩漏 Client memory leak

開得越久,記憶體用量越大,遊戲越來越慢,最後被強制關閉。

為什麼: 切換地圖時,貼圖、UI、特效沒有釋放 → 於是: GC 越來越頻繁,OS 記憶體不足而發生 swap → 畫面上: 玩了幾個小時後越來越卡頓,最後強制結束(在玩家看來像斷線)

症狀: 卡頓, 斷線 · 主要負責 遊戲開發團隊(用戶端開發)

用戶端閃退 Client crash

遊戲因未處理的錯誤而關閉。在玩家看來像斷線,但伺服器是正常的。

為什麼: null 參考、記憶體不足、顯示卡驅動程式錯誤 → 於是: 遊戲處理程序被強制結束 → 畫面上: 回報「閃退了」。同一時間其他人都正常

症狀: 斷線 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 外部(外部)

遊戲安全模組(反作弊)檢查 Anti-cheat scan and heartbeat

為了防範外掛而與遊戲一起執行的安全模組會定期檢查。檢查太重,或與安全伺服器往來的心跳封包(定期確認連線存活的訊號)延遲時,就會卡頓或斷線。

為什麼: 安全模組定期檢查遊戲記憶體、執行中的程式與驅動程式 → 於是: 檢查期間遊戲執行緒停住,或心跳封包沒能準時送出 → 畫面上: 以固定間隔頓一下,嚴重時跳出安全錯誤訊息並斷線

症狀: 卡頓, 定格, 斷線 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

用戶端 OS 與裝置

遊戲在 Windows、Android、iOS 上與其他程式共用 CPU、記憶體、網路。作業系統太晚分配 CPU 給遊戲、為了省電而降速,或是把背景 App 暫停時,就會 lag。

作業系統(OS)的排程器(決定輪到誰使用 CPU 的功能)會把 CPU 時間分配給多個程式。遊戲、防毒軟體、瀏覽器、更新程式都在等「輪到自己」,OS 以數 ms 到數十 ms 為單位(時間片段)輪流分配核心。OS 雖然會給前景的遊戲(foreground)稍高的優先順序,但工作比核心多時,遊戲也得等待,這段等待就會拖慢畫格。

網路也要經過 OS。網路卡、Wi-Fi 晶片收到的封包,會先放進驅動程式與 OS 的接收緩衝區,等遊戲來取。遊戲太忙、取得太晚,緩衝區就會溢位;一次全部取出,就會變成快轉。在行動裝置上特別重要的是,OS 為了省電會讓無線連線進入省電狀態,也會頻繁暫停 App 本身。

比喻

OS 就像讓多位廚師共用唯一一間廚房的主廚。即使遊戲正在做急件料理,只要名叫「防毒掃描」的廚師占住爐口,遊戲就得等。廚房太熱時(發熱),主廚還會把火力調小。

這一層造成 lag 的原因

背景處理程序占用 CPU Background CPU contention

防毒掃描、Windows Update、直播軟體、瀏覽器影片占住 CPU 核心時,遊戲執行緒分配不到 CPU,只能等待。

為什麼: 其他程式長時間占用 CPU 核心 → 於是: 遊戲執行緒等待排程 → 畫面上: 畫格延遲,已接收封包的處理也跟著變慢

症狀: 卡頓, 快轉 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

省電模式、過熱降頻 Power saving, thermal throttling

筆電的電池模式、手機的省電模式、裝置過熱,都會讓 CPU、GPU 速度下降。過熱的特徵是一開始正常,過了一段時間才變慢。

為什麼: 處於電池或省電模式,或裝置發燙 → 於是: 依裝置不同,CPU、GPU 時脈降低 30~50% → 畫面上: 省電模式一開就發生,過熱則在玩了幾分鐘~20 分鐘左右後,FPS 下降並出現卡頓

症狀: 卡頓, 輸入延遲 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

計時器解析度 Timer resolution (Windows 15.6ms)

Windows 的預設計時器以 15.6ms 為單位,所以「只休息 1ms」實際上會等到下一個計時器週期,最長拉長到 15.6ms。

為什麼: 用 Sleep(短暫等待)的方式實作畫格限制與封包傳送 → 於是: OS 只以 15.6ms 為單位喚醒 → 畫面上: 畫格間隔與輸入傳送間隔忽長忽短

症狀: 卡頓 · 主要負責 遊戲開發團隊(用戶端開發)

手機 App 切到背景 App suspended in background

為了看通知而把 App 暫時切到背景時,OS 會在幾秒後暫停(suspend)App,這段期間伺服器就會切斷玩家的連線。

為什麼: 為了看訊息、接電話把遊戲切到背景 → 於是: 遊戲引擎暫停遊戲進行,OS 很快也會停止 App 與網路 → 畫面上: 回到遊戲時早已斷線,只能重新連線

症狀: 斷線 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

Wi-Fi ↔ LTE/5G 切換 Network switch changes IP

走出家門時 Wi-Fi 斷掉、改用 LTE/5G,自己的 IP 位址會改變,原本的連線就此失效。

為什麼: Wi-Fi 訊號變弱,切換到行動網路 → 於是: 自己的 IP 位址改變,用舊位址建立的連線無法再收送資料 → 畫面上: 短暫停住後斷線,或重新連線

症狀: 定格, 斷線 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發), 基礎設施團隊(網路基礎設施)

安全軟體的封包檢查 Antivirus / firewall inspection

防毒軟體、防火牆檢查每一個封包時延遲會增加,太過頭還會把遊戲誤判為攻擊而封鎖。

為什麼: 安全軟體逐一檢查收送的封包 → 於是: 每個封包都多出延遲,檢查來不及時封包會被丟棄 → 畫面上: ping 不規則地飆高,或連線被封鎖

症狀: 卡頓, 連不上/無限讀取 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

接收緩衝區溢位 Socket receive buffer overflow

遊戲太忙,太晚從 socket(OS 提供的網路收送介面)取出封包時,OS 緩衝區就會溢位。

為什麼: 畫格延誤,遊戲太晚讀取 socket → 於是: OS 接收緩衝區滿了,UDP 直接丟棄,TCP 則縮小接收視窗讓對方停止傳送 → 畫面上: 瞬移(UDP)或快轉(TCP)

症狀: 瞬移, 快轉 · 主要負責 遊戲開發團隊(用戶端開發)

用戶端記憶體不足、swap Paging / swap on client

同時開著數十個瀏覽器分頁和遊戲時,OS 會把遊戲的一部分記憶體移到磁碟上。

為什麼: 整體 RAM 不足 → 於是: OS 把遊戲暫時沒用到的記憶體移到磁碟 → 畫面上: 再次用到那部分的瞬間,依儲存裝置不同會停住數十~數百 ms

症狀: 定格, 卡頓 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

顯示記憶體(VRAM)不足 VRAM over-commit

畫質選項需要的記憶體比顯示卡記憶體還大時,OS 會把貼圖移到電腦的主記憶體再搬回來,因此出現卡頓。

為什麼: 高貼圖選項,加上人多的地方各式各樣的裝備與特效,讓顯示卡記憶體全滿 → 於是: OS 把暫時沒用到的貼圖移到電腦的主記憶體,需要時再透過較慢的 PCIe 匯流排搬回來 → 畫面上: 每次出現新場景或新角色都會頓一下,貼圖有一段時間是模糊的

症狀: 卡頓, 定格 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 外部(外部)

Wi-Fi 背景掃描 Periodic Wi-Fi background scan

OS 為了尋找周圍的 Wi-Fi,會定期切換到其他頻道,這段期間通訊會短暫停止。

為什麼: OS 或驅動程式以固定週期搜尋周圍的 Wi-Fi → 於是: 搜尋期間收送短暫停止 → 畫面上: ping 以非常固定的間隔(例如每 60 秒)飆高

症狀: 卡頓, 瞬移 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

網路卡省電、驅動程式問題 NIC power saving, driver bugs

有線網路卡或 Wi-Fi 晶片在封包與封包之間進入省電狀態時,要重新喚醒需要時間。

為什麼: 網路裝置的省電功能開著,或驅動程式太舊 → 於是: 喚醒(wake-up)延遲,偶爾裝置會重新啟動 → 畫面上: 不規則的延遲,少數情況會停住數秒

症狀: 卡頓, 定格 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

同一台裝置上的其他 App 占用頻寬 Other apps saturating the link

雲端同步、大型下載、遊戲更新在同一台電腦上執行時,遊戲封包得在佇列中等待。

為什麼: 其他 App 把上傳、下載用到滿 → 於是: 遊戲封包堆積在電腦與分享器的佇列中 → 畫面上: ping 暴增、輸入延遲、快轉

症狀: 輸入延遲, 快轉 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

視窗最小化或非作用中時的處理限制 Minimized / unfocused window throttling

切到其他視窗或把遊戲最小化時,遊戲與 Windows 會為了省電讓遊戲跑得比較慢。回到遊戲時,累積的封包會一口氣湧入,或者早已斷線。

為什麼: 用 Alt+Tab 切到其他視窗,或把遊戲最小化 → 於是: 遊戲看不見的期間,FPS 會大幅降低或停止,Windows 也會調低看不見的程式的優先順序 → 畫面上: 切回來的瞬間出現快轉,切出去太久則會斷線

症狀: 快轉, 卡頓, 斷線 · 主要負責 遊戲開發團隊(用戶端開發)

overlay 程式干擾 Overlays and screen hooks

通訊軟體、啟動器、錄影、FPS 顯示程式為了在遊戲畫面上疊加自己的 UI,會介入遊戲的渲染流程(hook)。每個畫格的工作量因此增加,偶爾還會與遊戲衝突,造成頓一下或遊戲被強制關閉。

為什麼: 通訊軟體、遊戲啟動器、顯示卡工具、錄影程式的 overlay 開著 → 於是: 每次把畫格輸出到畫面時,overlay 都會介入疊加自己的 UI → 畫面上: 畫格一點一點變慢,通知跳出的瞬間頓一下,或出現畫面錯誤、強制關閉(玩家看起來像斷線)

症狀: 卡頓, 定格, 斷線 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

顯示器、輸入裝置與畫格生成的延遲 Display, input device and frame generation latency

ping 正常、操作卻很沉重時,可能是電視的影像處理、無線控制器或畫格生成功能在輸入與畫面之間增加了延遲。

為什麼: 電視的遊戲模式沒開、使用藍牙或無線控制器,或開啟了畫格生成(DLSS、FSR 畫格生成) → 於是: 電視在進行畫質處理時會延後輸出畫格,無線輸入會因傳輸週期與干擾而晚到,畫格生成則要等下一個畫格才能做出中間的畫格 → 畫面上: ping 與 FPS 數字都很好,按下按鍵後卻要過一會兒才反映在畫面上,形成輸入延遲

症狀: 輸入延遲 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

家用網路:Wi-Fi、分享器、行動網路

封包離開家之前的最後幾公尺。距離雖短,但相當多的 lag 回報都源自這裡。因為 Wi-Fi 是多台裝置共用同一個無線頻道,分享器則把全家人的流量排進同一個佇列送出。

Wi-Fi 與鄰居的分享器共用同一個無線頻道(頻段),2.4GHz 頻段還會與藍牙、微波爐重疊。傳送時發生碰撞就稍等一下再重送,這些重傳累積起來,封包就會忽快忽慢地抵達。ping 的平均值看起來沒問題,卻時不時飆高,是 Wi-Fi 的典型樣子。

分享器是家中所有裝置連上網際網路時必經的設備。送出的量超過網際網路線路能承接的速度時,分享器或數據機裡就會形成佇列,而沒有佇列管理功能(SQM)的設備,會讓這個佇列堆積到數百 ms 的長度。昂貴的分享器若關閉了這個功能也一樣。弟弟妹妹上傳影片的那一刻,遊戲封包也得在那個佇列的最尾端等待。這種現象稱為 bufferbloat。

分享器還會把「內部裝置 ↔ 外部伺服器」的連線記錄在 NAT 表中,一段時間沒有封包往來就會從表中刪除。這是閒置後斷線的常見原因。行動網路則還要再加上基地台換手、無線省電狀態、訊號微弱等因素。

比喻

分享器就像社區唯一的出入口。搬家卡車(影片上傳)排成一列時,急件機車快遞(遊戲封包)也得在卡車後面等。聰明的分享器(SQM)會另外開一條快遞專用道。

這一層造成 lag 的原因

Wi-Fi 干擾與訊號減弱 Wi-Fi interference, weak signal

訊號弱或有干擾時,無線區段要反覆重送好幾次,封包抵達的時間就會忽快忽慢。

為什麼: 牆壁、距離、微波爐、藍牙、鄰居的分享器讓無線訊號品質變差 → 於是: 無線區段傳送失敗 → 重傳好幾次 → 畫面上: 封包抵達忽快忽慢(抖動),角色走走停停,嚴重時封包遺失而出現瞬移

症狀: 卡頓, 瞬移, 拉回 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

Wi-Fi 頻道壅塞 Crowded Wi-Fi channel

在公寓大樓這類有數十台分享器的地方,大家共用同一個頻道,只能等待傳送機會。

為什麼: 數十台分享器使用同一個 2.4GHz 頻道 → 於是: 要傳送就得等其他裝置傳完、頻道空出來 → 畫面上: 大家回到家的晚間時段抖動(封包抵達間隔忽長忽短)增加,出現卡頓

症狀: 卡頓, 輸入延遲 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

Bufferbloat(分享器佇列) Bufferbloat

家人上傳影片或下載大型檔案時,分享器佇列會堆積相當於數百 ms 的封包,遊戲封包也得排在後面等待。

為什麼: 家人上傳影片、雲端備份、自己的直播推流、大量下載把線路塞滿 → 於是: 分享器或數據機把滿出來的封包堆進很大的佇列 → 畫面上: 遊戲封包也排在佇列後面等待,ping 暴增到數百 ms

症狀: 輸入延遲, 快轉, 瞬移 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

NAT mapping 過期 NAT mapping timeout

分享器會把一段時間沒有封包往來的閒置連線從 NAT 表中刪除。這是閒置一段時間後一有動作就斷線的常見原因。

為什麼: 分享器把「內部裝置 ↔ 外部伺服器」的連線記錄在 NAT 表(位址轉換表)中 → 於是: 一段時間沒有封包就從表中刪除(UDP 通常為 30~120 秒) → 畫面上: 伺服器的封包進不了家中網路,造成斷線

症狀: 斷線 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發)

分享器效能不足、過熱 Router CPU / session table exhaustion

便宜的分享器同時接了數十台裝置、數千條連線時,分享器本身就處理不過來。

為什麼: 數十台裝置,加上 P2P、BT(torrent)開啟數千條連線 → 於是: 分享器的 CPU 與 session 表飽和 → 畫面上: 封包處理延遲、封包遺失,新連線失敗

症狀: 卡頓, 連不上/無限讀取, 斷線 · 主要負責 外部(外部)

基地台換手(移動中) Cellular handover

搭公車、捷運移動時,切換基地台的期間通訊會中斷。

為什麼: 移動中連線的基地台改變 → 於是: 通常只空白數十 ms,但訊號差導致切換失敗時,有時會中斷數百 ms~數秒 → 畫面上: 停住後瞬移,時間長的話會斷線

症狀: 定格, 瞬移, 斷線 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發), 遊戲開發團隊(伺服器開發)

RRC 狀態切換延遲(行動網路無線省電) Radio state promotion (RRC)

手機一段時間沒有通訊時,會把無線連線降到低耗電狀態,下一個封包要送出時得重新拉高,因此變慢。

為什麼: 短暫沒有通訊時,手機把無線連線切換到省電狀態 → 於是: 要送出下一個封包,必須重新拉高連線 → 畫面上: 閒置一段時間後的第一個動作特別慢

症狀: 輸入延遲 · 主要負責 遊戲開發團隊(用戶端開發)

行動網路訊號弱、收訊死角 Weak cellular signal

在電梯、地下室、建築物深處,重傳會增加、速度下降,最後斷線。

為什麼: 移動到訊號弱的地方 → 於是: 無線重傳增加、速度下降、瞬間斷訊 → 畫面上: 抖動與封包遺失造成卡頓、瞬移,最後斷線

症狀: 卡頓, 瞬移, 斷線 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

5G↔LTE 頻繁切換(5G 涵蓋邊緣) 5G NSA / LTE switching

在 5G 訊號弱的建築物內或 5G 涵蓋邊緣,手機會頻繁在 5G 與 LTE 之間切換,每次切換 ping 都會飆高或通訊短暫中斷。

為什麼: 身處 5G 訊號時好時壞的地方(建築物內、5G 涵蓋邊緣) → 於是: 手機隨時在 5G 與 LTE 之間切換,每次都會出現短暫空白 → 畫面上: 即使靜止不動,ping 也會不規則地飆高,偶爾定格、瞬移

症狀: 卡頓, 瞬移, 定格 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

公共 Wi-Fi/公司網路限制 Captive portal, restrictive network

咖啡廳 Wi-Fi 的登入頁面或公司防火牆會擋掉遊戲連線。

為什麼: 尚未通過登入頁面認證,或防火牆封鎖遊戲 port 與 UDP → 於是: 連線嘗試本身被擋,或只有部分通過 → 畫面上: 無法連線,或能登入卻進不了遊戲

症狀: 連不上/無限讀取 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發), 遊戲開發團隊(伺服器開發)

網際網路線路:電信業者網路與長距離區段

離開家的封包,會經過電信業者網路、多家電信業者之間的互連區段,有時還要經過海底電纜,才抵達伺服器所在的資料中心。這段區段的延遲大多由距離與路徑選擇(路由)決定,很多時候遊戲公司無法直接修正。

光在光纖中 1 秒約可前進 20 萬 km。距離 1,000km 的伺服器,往返至少需要 10ms,只要還是走光纖,這個數字再怎麼升級伺服器或設備都無法縮短。實際的封包不走直線,會沿著電信業者之間的互連點(peering)繞行,所以通常要花理論值的 1.5~2 倍。像韓國–歐洲這種直線上幾乎沒有大型電纜的區段,會繞經東南亞與蘇伊士或美國,達到 2.5~3 倍(往返約 230~270ms)。

問題在於這條路徑會隨時間與狀況改變。晚上 9~11 點左右大家都在看影片,電信業者之間的互連區段容易壅塞;路由資訊(BGP)改變時,封包會有幾秒到幾十秒(少數情況下幾分鐘)無法抵達目的地;海底電纜斷了,則會有好幾週繞遠路。如果 lag 是「只有特定電信業者的玩家」、「只在晚上」、「只在海外」,就先懷疑這一層。

比喻

電信業者網路就像高速公路網。從首爾到釜山的路即使不塞,距離本身還是需要時間;下班時間的收費站(peering 區段)會塞車;發生事故時,導航會指引你繞一大段遠路。

這一層造成 lag 的原因

傳播延遲(物理距離) Propagation delay

光在光纖中 1 秒也只能前進約 20 萬 km。伺服器在遠方,效能再好也會慢。

為什麼: 伺服器位在遠方(海外伺服器、其他大陸) → 於是: 距離越遠,往返時間越長(每 1,000km 至少 10ms) → 畫面上: 所有動作都有固定的輸入延遲,判定上也吃虧

症狀: 輸入延遲 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 基礎設施團隊(網路基礎設施), 遊戲開發團隊(伺服器開發)

衛星網路(低軌/同步軌道) Satellite internet (LEO, GEO)

衛星網路的電波必須往返太空。同步軌道衛星光是往返就超過 0.5 秒;Starlink 這類低軌衛星平時很快,但在重新分配路徑的瞬間延遲會忽高忽低,有時還會短暫中斷。

為什麼: 在家中、船上或飛機上,透過同步軌道衛星、低軌衛星網路,或使用衛星的機上 Wi-Fi 連線 → 於是: 同步軌道衛星高度約 36,000km,往返距離本身就很長;低軌衛星則以很短的週期重新分配終端設備、衛星、地面站之間的路徑,在那一瞬間會短暫出現延遲與封包遺失 → 畫面上: 同步軌道衛星讓所有動作都有很大的輸入延遲;低軌衛星平時正常,但會每隔固定時間出現卡頓、瞬移

症狀: 輸入延遲, 卡頓, 瞬移 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發), 遊戲開發團隊(伺服器開發)

繞遠路的路由 Suboptimal routing

受電信業者之間互連合約的影響,連到附近的伺服器也可能繞到遠處再回來。

為什麼: 自己的電信業者與伺服器端的電信業者沒有直接互連 → 於是: 經過其他國家或其他城市,距離與經過的設備都增加 → 畫面上: 只有特定電信業者的使用者 ping 特別高

症狀: 輸入延遲 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 外部(外部)

尖峰時段 peering 區段壅塞 Peak-hour congestion at peering

晚上 9~11 點左右影音流量暴增,電信業者之間的互連區段(peering)容易壅塞。

為什麼: 晚上串流與下載的流量集中湧入 → 於是: peering 區段出現排隊與封包遺失 → 畫面上: 只在晚上,特定電信業者的使用者出現卡頓、瞬移

症狀: 卡頓, 瞬移, 拉回 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 外部(外部)

海底電纜/國際線路故障 Submarine cable fault

海底電纜一旦斷裂,修復前的幾週(長則幾個月)流量都得繞遠路,剩下的線路也會壅塞。

為什麼: 電纜被切斷或設備故障 → 於是: 流量集中到繞遠路的路徑與剩下的線路上 → 畫面上: 海外玩家的 ping 暴增並出現封包遺失,持續幾天到幾週

症狀: 輸入延遲, 瞬移 · 主要負責 外部(外部) · 協同 基礎設施團隊(網路基礎設施)

BGP 路由變更與收斂 Route change / BGP convergence

網際網路的路由資訊改變後重新收斂的幾秒到幾十秒(偶爾幾分鐘)之間,封包會遺失。

為什麼: 某個電信業者區段的路由資訊改變 → 於是: 幾秒到幾十秒之間封包消失,或切換到新路徑 → 畫面上: 突然停住幾秒,之後 ping 值改變(例:40 → 70ms)

症狀: 定格, 瞬移 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 遊戲開發團隊(伺服器開發), 外部(外部)

ECMP 其中一條路徑異常 ECMP / link bundle member fault

電信業者與資料中心會為同一個目的地準備多條路徑,並替每條連線指定其中一條傳送。只要其中一條路徑故障,被分配到那條路徑的人就會持續遇到 lag。

為什麼: 在多條線路綁在一起的區段中,其中一條線路或一台設備異常或壅塞 → 於是: 路徑由位址與 port 的組合(雜湊)決定,只有被分配到那條路徑的連線出現封包遺失與延遲 → 畫面上: 同一地區、同一電信業者,卻只有部分人持續瞬移。重新連線後有時就恢復正常

症狀: 瞬移, 拉回, 卡頓 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 遊戲開發團隊(伺服器開發), 外部(外部)

電信業者限速與流量管理 Traffic shaping, data caps

在用量超過上限或會管理特定流量的資費方案中,封包會被延後或丟棄。

為什麼: 資費方案的行動數據用完後被限速,或特定流量受到限制 → 於是: 封包排隊等待或被丟棄 → 畫面上: 用到一定用量之後開始 lag,行動網路特別常見

症狀: 輸入延遲, 瞬移 · 主要負責 外部(外部) · 協同 遊戲開發團隊(伺服器開發), 基礎設施團隊(網路基礎設施)

國家/電信業者層級的 UDP 限制與封包檢測 UDP blocking, throttling and inspection by networks

部分網路會封鎖特定 UDP 位址與 port,或限制 UDP 速度;封包檢測設備也會過濾掉無法辨識的協定。用 UDP 通訊的遊戲在這類網路上會連不上或經常斷線。

為什麼: 從限制 UDP 速度的部分電信業者網路,或設有國家/電信業者層級流量檢測(審查)設備的網路連線 → 於是: 封鎖特定 UDP 位址與 port、在壅塞時段限制 UDP 速度、過濾不在允許清單上的 port 與協定,或只放行前幾個封包後就封鎖 → 畫面上: 只有特定國家或電信業者的玩家連不上/無限讀取,或連上後很快斷線,壅塞時段因封包遺失而瞬移

症狀: 連不上/無限讀取, 斷線, 瞬移 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發), 遊戲開發團隊(伺服器開發), 基礎設施團隊(網路基礎設施)

線路品質不良 Faulty last-mile line / modem

接頭接觸不良、老舊線路或數據機異常,會造成持續的封包遺失與週期性的線路中斷。

為什麼: 纜線受損、接觸不良,或數據機、光纖終端設備異常 → 於是: 位元錯誤導致封包被丟棄,有時線路會重新連線,因而中斷數秒到 1 分鐘左右 → 畫面上: 持續少量的封包遺失,偶爾定格數秒或斷線

症狀: 瞬移, 定格, 斷線 · 主要負責 外部(外部)

DNS 故障與延遲 DNS failure / slowness

負責把伺服器名稱轉成位址的 DNS 一旦變慢或失敗,就找不到登入伺服器與更新伺服器。

為什麼: 電信業者的 DNS 故障或設定錯誤 → 於是: 找不到登入伺服器、更新伺服器的位址 → 畫面上: 按下連線按鈕後要等很久,或連不上。已經連上的人不受影響

症狀: 連不上/無限讀取 · 主要負責 外部(外部) · 協同 遊戲開發團隊(用戶端開發)

DDoS 造成共用線路飽和 DDoS saturating shared links

針對遊戲公司,或同一網路上其他對象的大量攻擊,會塞滿共用的線路。

為什麼: 出現大量攻擊流量 → 於是: 連走同一條線路的正常流量也被擠壓、丟棄 → 畫面上: 許多人同時瞬移、斷線、連不上

症狀: 瞬移, 斷線, 連不上/無限讀取 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 外部(外部)

電信業者共用 IP(CGNAT) Carrier-grade NAT

行動網路與部分電信業者讓多位用戶共用一個 IP,並在短時間內清除閒置連線的 NAT mapping。

為什麼: 電信業者的設備要管理大量用戶的 session 表 → 於是: session 表有上限,閒置逾時很短 → 畫面上: 閒置一陣子後斷線;共用同一個 IP 的人被誤判而一起封鎖

症狀: 斷線, 連不上/無限讀取 · 主要負責 遊戲開發團隊(用戶端開發) · 協同 遊戲開發團隊(伺服器開發), 基礎設施團隊(網路基礎設施)

經由 VPN/遊戲加速器 VPN / game accelerator detour

開啟 VPN 或遊戲加速器後,封包會經過該公司的中繼伺服器。中繼伺服器很遠或壅塞時,反而會變慢。

為什麼: VPN 或加速器把遊戲封包全部轉送到中繼伺服器 → 於是: 加上到中繼伺服器的距離與壅塞,通道標頭也讓 MTU(一次能傳送的封包大小)變小 → 畫面上: ping 升高並出現封包遺失;與使用同一中繼位址的人一起被封鎖而連不上

症狀: 輸入延遲, 瞬移, 連不上/無限讀取 · 主要負責 外部(外部) · 協同 基礎設施團隊(網路基礎設施), 遊戲開發團隊(伺服器開發)

資料中心網路設備

抵達伺服器之前,封包會依序通過路由器、DDoS 防護設備、防火牆、負載平衡器、交換器。這段區段平時連 1ms 都不到,但只要一台設備容量滿載或故障,整個伺服器的數千名玩家就會同時受影響。

每台設備的角色不同。路由器決定路徑,DDoS 防護設備過濾攻擊流量,防火牆只讓允許的連線通過,並用 session 表追蹤所有連線。負載平衡器把進來的連線分配給多台伺服器,交換器則把伺服器彼此連接起來。

這些設備的共同弱點是表格大小與緩衝區大小。防火牆的 session 表滿了就無法接受新連線;負載平衡器會在一段時間後刪除閒置連線;當多台伺服器在同一瞬間對數千人一起送出封包時(世界王出現),交換器的小緩衝區不到 1ms 就會溢位。還有,一台設備故障、切換到備援設備(容錯移轉)的那幾秒內,所有人都會停住。

比喻

資料中心的入口就像機場的安檢與登機門。安檢(防火牆)只讓名單上的人通過,名單欄位填滿就再也收不了人。登機門人員(負載平衡器)會把安靜坐了很久的乘客當成「已經離開的人」,從名單上劃掉。

這一層造成 lag 的原因

防火牆 session 表飽和 Firewall session table exhaustion

防火牆會把放行的每條連線記錄在 session 表中追蹤。表一旦滿了,就無法接受新連線。

為什麼: 連線暴增或遭受攻擊,使 session 數達到上限 → 於是: 沒有空的項目可以記錄新連線,因此拒絕連線 → 畫面上: 想新連進來的人連不上/無限讀取,部分既有連線也會斷線

症狀: 連不上/無限讀取, 斷線 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 遊戲開發團隊(伺服器開發), 遊戲開發團隊(用戶端開發)

DDoS 防護導流與誤判 DDoS scrubbing latency, false positives

為了擋下攻擊而把流量導向清洗中心時,路徑會變長,有時還會把正常使用者誤判為攻擊而封鎖。

為什麼: 偵測到攻擊後(或常態性地)把進來的流量導向清洗中心 → 於是: 路徑變長,部分正常封包被判定為攻擊 → 畫面上: 整體 ping 上升,只有特定地區/電信業者連不上

症狀: 輸入延遲, 連不上/無限讀取, 瞬移 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 遊戲開發團隊(伺服器開發)

負載平衡器閒置逾時 Load balancer idle timeout

負載平衡器會在一段時間後清除閒置連線。遊戲還以為連線仍然維持著,結果就斷線了。

為什麼: 玩家有一段時間完全沒送出任何封包(開著對話視窗、暫離) → 於是: 負載平衡器清理閒置連線(常見的預設值為 60~350 秒) → 畫面上: 再次移動的瞬間斷線

症狀: 斷線 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 遊戲開發團隊(用戶端開發), 遊戲開發團隊(伺服器開發)

雲端安全群組的連線追蹤過期 Cloud security group connection tracking timeout

雲端伺服器上的防火牆(安全群組)也會追蹤連線,閒置連線的追蹤項目會在一定時間後過期。即使是沒有經過負載平衡器、直接連線的伺服器,靜止不動的玩家也可能斷線。

為什麼: 安全群組處於會追蹤遊戲連線的設定(只允許特定位址、限制輸出規則、經由 NLB 等) → 於是: 連線閒置一段時間後追蹤項目過期,之後進來的封包被安全群組默默丟棄 → 畫面上: 暫離後再移動時沒有反應,接著斷線。伺服器程式很長一段時間都沒察覺

症狀: 斷線 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(用戶端開發), 遊戲開發團隊(伺服器開發)

雲端 NAT 閘道的連線與 port 上限 Cloud NAT gateway connection / port limits

私有子網路中的伺服器對外(平台驗證、付款、外部 API)的連線,由 NAT 閘道轉換位址與 port 後送出。送往同一目的地的同時連線超過閘道的 port 上限時,新連線就會失敗。

為什麼: 伺服器對平台驗證、付款這類同一個外部位址大量開啟短連線,或長時間開著連線 → 於是: NAT 閘道無法再分配該目的地可用的來源 port,新連線失敗 → 畫面上: 遊戲內一切正常,只有登入、付款、發放獎勵這類呼叫外部的功能失敗或變慢(連不上/無限讀取、吃指令/回檔)

症狀: 連不上/無限讀取, 吃指令/回檔 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 遊戲開發團隊(伺服器開發)

負載平衡器分配不均與健康檢查誤判 LB imbalance, bad health checks

連線全集中到一台伺服器,或持續把人送往已經掛掉的伺服器。

為什麼: 分配規則不合適,或健康檢查(health check)看不到實際狀態 → 於是: 只有一台伺服器過載,或連線被送往掛掉的伺服器 → 畫面上: 只有部分頻道、部分人出現慢動作,或連不上/無限讀取

症狀: 慢動作, 連不上/無限讀取 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 遊戲開發團隊(伺服器開發)

交換器 microburst Switch microburst drops

多台伺服器在同一瞬間同時對數千人送出封包時,這些流量匯集的交換器 port 上的小緩衝區,不到 1ms 就會滿溢。

為什麼: 世界王出現、大範圍技能,或多台伺服器的 tick 在同一瞬間對齊,同時送出封包 → 於是: 多個 port 匯入一個 port,或從快速 port 轉到慢速 port 的地方,緩衝區(每個 port 數百 KB~數 MB)瞬間塞滿 → 畫面上: 部分封包被丟棄,許多人同時瞬移、技能被吃

症狀: 瞬移, 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(網路基礎設施), 基礎設施團隊(伺服器基礎設施)

資料中心線路飽和 Uplink saturation

更新檔發布、log 傳送、備份與遊戲共用同一條線路時,線路會被塞滿。

為什麼: 大量傳輸佔用同一條線路 → 於是: 線路上的排隊與封包遺失增加 → 畫面上: 整個伺服器的 ping 上升並出現瞬移

症狀: 輸入延遲, 瞬移 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 基礎設施團隊(伺服器基礎設施)

網路設備容錯移轉(failover) Network device failover

路由器或防火牆有一台故障、切換到備援設備(failover)的幾秒之間,所有人的畫面都會停住。

為什麼: 設備故障或維護,切換到備援設備 → 於是: 切換需要數秒;session 資訊沒有同步時,連線會被重置 → 畫面上: 伺服器上的所有玩家同時定格,大量斷線

症狀: 定格, 斷線 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 遊戲開發團隊(伺服器開發), 遊戲開發團隊(用戶端開發)

纜線不良與 port 錯誤 Bad cable / optics (CRC errors)

光模組或纜線不良時,經過該路徑的封包會有一定比例損毀。

為什麼: 光模組或纜線不良造成位元錯誤 → 於是: 損毀的封包被設備默默丟棄 → 畫面上: 只有使用該路徑的部分伺服器與使用者因持續的封包遺失而瞬移、拉回

症狀: 瞬移, 拉回 · 主要負責 基礎設施團隊(網路基礎設施)

MTU 不一致(只有大封包消失) MTU black hole

中途區段的 MTU(一次能傳送的大小)變小,而大小超過的通知又被擋掉時,只有大封包會一直消失。

為什麼: 在通道或 VPN 區段中 MTU 變小 → 於是: 大小超過的通知(ICMP)被防火牆擋下,傳送端不知道 → 畫面上: 只有打開背包、角色清單這類大畫面時,畫面會停住,接著斷線

症狀: 定格, 斷線, 連不上/無限讀取 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(伺服器開發)

伺服器網路卡(NIC)

插在伺服器上的網路卡每秒要接收數十萬到數百萬個封包並交給 CPU。這裡的處理一旦落後,伺服器程式連封包來過都不知道,封包就遺失了。

NIC 把抵達的封包依序放進 ring buffer(輪流重複使用固定數量 slot 的接收緩衝區),並通知 CPU「封包來了」(中斷,interrupt)。CPU 從 ring buffer 取出封包交給 OS。封包進來的速度比 CPU 取出的速度快時,slot 會全部填滿,之後來的封包就被丟棄。只有網路卡統計(ethtool -S)的數字默默上升,遊戲伺服器 log 不會留下任何錯誤,因此是很難找的 lag。

現在的 NIC 有多個接收佇列(ring buffer),並具備把通知分散到多個 CPU 核心的功能(RSS),但若沒有設定,或流量集中到同一個佇列,就只有一個核心跑到 100%,成為瓶頸。雲端伺服器在 NIC 前面另有每秒封包數、頻寬、連線數上限,超出的部分在抵達伺服器之前就被丟棄。這在 CPU、ring buffer 這類平常的指標上看不出來,AWS 的話只會留在 ENA 驅動程式的統計(ethtool -S 的 pps_allowance_exceeded 等)裡。

比喻

NIC 是公寓的信箱,ring buffer 是信箱的格數,中斷是郵差按的門鈴。郵件大量湧入、取信的人卻只有一個時,信箱滿出來,信就掉到地上。RSS 就是安排多個人取信。

這一層造成 lag 的原因

NIC 中斷集中在單一核心 Single-queue NIC / no RSS

NIC 只把封包到達的中斷(interrupt)送給一個 CPU 核心時,那個核心就會成為瓶頸。

為什麼: 只有一個接收佇列,或是負責分散到多個核心的 RSS 被關閉 → 於是: 單一核心達到 100%,無法及時取出封包 → 畫面上: 人潮湧入時,整個伺服器出現封包遺失與延遲(瞬移、輸入延遲)

症狀: 瞬移, 拉回, 輸入延遲 · 主要負責 基礎設施團隊(伺服器基礎設施)

Ring buffer 不足 RX ring buffer overflow

NIC 暫存封包的 ring buffer 太小時,封包瞬間湧入就會讓緩衝區滿溢而被丟棄。

為什麼: ring buffer 仍是預設值(依驅動程式為 256~2,048 個 slot),容量偏小 → 於是: 突發流量來時,CPU 還沒取走封包,緩衝區就滿了 → 畫面上: 只在突發流量的瞬間出現封包遺失(瞬移、技能被吃)。遊戲伺服器 log 中看不到任何痕跡

症狀: 瞬移, 吃指令/回檔 · 主要負責 基礎設施團隊(伺服器基礎設施)

中斷合併過度 Interrupt coalescing

為了減輕 CPU 負擔,NIC 把封包累積起來再一次通知 CPU(中斷合併,interrupt coalescing)時,累積多久就會晚多久。

為什麼: NIC 累積一定時間或一定數量的封包後才通知 → 於是: 累積期間,封包只能等待 → 畫面上: 延遲些微增加。通常很小,設定過度時會達到 ms 等級

症狀: 輸入延遲 · 主要負責 基礎設施團隊(伺服器基礎設施)

超過雲端 PPS 上限 Cloud PPS / bandwidth allowance

雲端伺服器依類型各有每秒封包數與頻寬的上限,超過時會默默丟棄。

為什麼: 同時上線人數增加,每秒封包數超過執行個體上限 → 於是: 雲端網路丟棄超出的部分 → 畫面上: 找不出原因的封包遺失造成瞬移、技能被吃。伺服器 CPU 還有餘裕

症狀: 瞬移, 吃指令/回檔 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發), 外部(外部)

NIC 頻寬飽和 NIC bandwidth saturation

把 1Gbps、10Gbps 網路卡用到極限時,傳送佇列會變長,最後封包被丟棄。

為什麼: 廣播增加,使傳輸量達到網路卡的極限 → 於是: 傳送佇列變長,滿了就丟棄 → 畫面上: 整個伺服器出現延遲與封包遺失(輸入延遲、瞬移)

症狀: 輸入延遲, 瞬移 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

虛擬化額外開銷與 noisy neighbor Noisy neighbors in virtualization

同一台實體伺服器上的其他虛擬機器大量使用網路或 CPU 時,自己伺服器的處理會不規則地被延後。

為什麼: 同一台實體伺服器上的其他虛擬機器大量使用資源 → 於是: 自己虛擬機器的封包處理不規則地延遲 → 畫面上: 沒有明顯原因,偶爾出現抖動(封包抵達間隔忽長忽短)而卡頓

症狀: 卡頓 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 外部(外部)

雲端主機維護與即時遷移 Cloud host maintenance / live migration

雲端供應商維護實體伺服器(主機)時,會把虛擬機器搬到其他主機(即時遷移),或暫時停住。這段期間整台伺服器都會停住,停住的時間一長,連線就會中斷。

為什麼: 供應商因主機維護或故障預測,把虛擬機器搬到其他主機,或短暫暫停 → 於是: 搬移期間 CPU、記憶體、網路變慢,最後虛擬機器會短暫完全停住(依供應商與方式,從不到 1 秒到 30 秒左右) → 畫面上: 伺服器上所有人的畫面同時停住,接著快轉、瞬移;停住的時間比逾時長時,會大量斷線

症狀: 定格, 快轉, 瞬移, 斷線 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發), 外部(外部)

NIC 驅動程式/韌體問題 NIC hang / reset

驅動程式 bug 或功能異常讓網路卡停住並重新啟動,這段期間所有收發都會中斷。

為什麼: 驅動程式 bug、offload 功能異常 → 於是: NIC 停住後重新啟動(數秒) → 畫面上: 那台伺服器上所有人的畫面一起停住,接著瞬移或斷線

症狀: 定格, 斷線 · 主要負責 基礎設施團隊(伺服器基礎設施)

GRO/LRO 合併等待延遲 GRO/LRO batching

這是把多個封包合併成一個來減輕 CPU 負擔的功能。依設定不同,小型遊戲封包有時會短暫等待下一個可以一起合併的封包。

為什麼: NIC 或 kernel 把到達的封包合併後處理 → 於是: 開啟硬體合併(LRO)或合併等待時間設定時,會短暫等待下一個封包 → 畫面上: 延遲些微增加(大多在數十 µs 以下)

症狀: 輸入延遲 · 主要負責 基礎設施團隊(伺服器基礎設施)

伺服器 OS(kernel)

伺服器的 Linux、Windows kernel 負責接受連線、管理 socket 緩衝區,並把 CPU 與記憶體分配給遊戲伺服器程式。大部分預設值都是為了兼顧多種用途而設定的保守值,對於數萬人長時間連線的遊戲伺服器,往往不能直接沿用。

新連線進來時,kernel 會把連線請求放進連線等待佇列(backlog),由遊戲伺服器逐一取走。佇列滿了時,Linux 會默默丟棄新請求,Windows 則回傳拒絕回應。在 Linux 上,每條連線都需要一個檔案描述子(fd),也就是附在每個開啟的檔案或連線上的編號,而一個處理程序能持有的 fd 數也有上限。維護剛結束、數萬人同時按下連線按鈕時,連線等待佇列與 fd 會最先耗盡。

記憶體不足時,kernel 還會把資料移到磁碟(swap,有開啟的話);真的耗盡時,Linux 會挑出使用最多記憶體的處理程序強制終止(OOM killer)。遊戲伺服器通常是那台伺服器上使用最多記憶體的處理程序,所以會最先被終止。如果對容器設了記憶體上限,即使整台伺服器還有餘裕,一碰到上限也會發生同樣的事。時間同步(NTP)、排程工作、容器 CPU 上限、虛擬機器的 CPU steal(其他虛擬機器使用實體 CPU 時的等待時間)這類看似與遊戲無關的事,也會讓伺服器短暫停住或讓計時器出現偏差。

比喻

伺服器 OS 就像遊樂園的入口閘門與管理處。開園時間(維護結束)大家一擁而上,閘門前的隊伍(backlog)就會滿出來;可發的手環(檔案描述子)發完了,就再也進不去。

這一層造成 lag 的原因

連線等待佇列(backlog)溢位 Listen backlog / SYN queue overflow

維護剛結束時數萬人同時連線,kernel 的連線等待佇列(backlog)會溢位,連線請求因此被丟棄。

為什麼: 維護一結束,連線湧入的速度就超過遊戲伺服器用 accept 處理連線的速度 → 於是: kernel 的連線等待佇列(backlog,取伺服器程式碼傳給 listen 的值與 kernel 上限中較小者)已滿 → 畫面上: 連線請求被丟棄後不斷重試,結果連不上/無限讀取

症狀: 連不上/無限讀取 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(用戶端開發)

檔案描述子上限 File descriptor limit (ulimit)

每個連線都需要一個檔案描述子(fd,OS 為開啟的檔案或 socket 指定的編號),而一個處理程序能開啟的 fd 數量有上限。

為什麼: 同時上線人數達到處理程序的檔案描述子上限 → 於是: 伺服器無法接受新連線(Too many open files),開啟 log 檔與 DB 連線也跟著失敗 → 畫面上: 從某個固定人數起誰都進不來,出現連不上/無限讀取

症狀: 連不上/無限讀取 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發)

Kernel socket 緩衝區不足 Small socket buffers

傳送與接收緩衝區太小時,一遇到突發流量,UDP 收到的封包就會被丟棄,TCP 則因緩衝區沒有空間而無法傳送。

為什麼: SO_SNDBUF、SO_RCVBUF 維持預設值或設得太小 → 於是: 突發流量或接收執行緒短暫停住時,UDP 接收緩衝區溢位而丟棄封包;TCP 則因傳送緩衝區沒有空間而等待 → 畫面上: 瞬移(UDP 遺失)或快轉(TCP 等待)

症狀: 瞬移, 快轉 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發)

執行緒過多與 context switch Thread oversubscription, context switching

執行緒數量遠多於 CPU 核心時,OS 會把 CPU 花在輪流切換執行緒上。

為什麼: 例如每個連線各開一個執行緒,讓執行緒多達數百~數千個 → 於是: context switch(切換執行中的執行緒)的成本與快取未命中增加 → 畫面上: CPU 很忙,處理量卻很低,tick 忽長忽短,出現卡頓、慢動作

症狀: 卡頓, 慢動作 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

CPU steal(虛擬機器) CPU steal time

實體伺服器(hypervisor)把虛擬機器的 CPU 時間暫時讓給其他虛擬機器(CPU steal)時,遊戲伺服器會停住。

為什麼: 同一台主機上的其他虛擬機器大量使用 CPU → 於是: 自己的虛擬機器每次失去數 ms~數十 ms 的執行機會 → 畫面上: tick 時間莫名暴增,出現卡頓、定格

症狀: 卡頓, 定格 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 外部(外部)

容器 CPU 節流(CFS 配額) Container CPU throttling (CFS quota)

容器設了 CPU 上限時,只要在固定週期(通常是 100ms)內用完配額,剩下的時間就會被強制暫停(節流)。

為什麼: 在 Kubernetes 等平台上為遊戲伺服器容器設定 CPU 上限(limit) → 於是: tick 計算集中的瞬間用完配額,暫停數十 ms 直到下一個週期 → 畫面上: 平均 CPU 很低,tick 卻週期性飆高,出現卡頓、慢動作

症狀: 卡頓, 慢動作 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發)

伺服器電源管理(C-state、頻率調整)造成延遲飆高 CPU power management latency (C-states, frequency scaling)

閒置的 CPU 核心為了省電,會進入深層省電狀態(C-state)並降低頻率。封包或計時器到來時,喚醒核心並拉高頻率需要時間,處理小封包時就會多出延遲。

為什麼: OS 的頻率調整策略(governor)或 BIOS 電源設定允許深層 C-state 與低頻率 → 於是: 閒置核心每次從深層省電狀態喚醒都會慢上最多數百 µs;頻率被鎖在低檔時,tick 計算本身也會變慢 → 畫面上: 通常很難察覺,但伺服器之間的呼叫一多就會累積起來,變成人少時回應反而變慢的輸入延遲。頻率被鎖在低檔時,人潮湧入就會讓 tick 延後,出現慢動作

症狀: 輸入延遲, 慢動作 · 主要負責 基礎設施團隊(伺服器基礎設施)

OOM killer Out-of-memory killer

Linux 在記憶體耗盡時,會挑出使用最多記憶體的處理程序強制終止,通常就是遊戲伺服器。

為什麼: 洩漏或用量暴增導致記憶體耗盡,或容器達到記憶體上限 → 於是: kernel 強制終止遊戲伺服器處理程序 → 畫面上: 該伺服器上的所有人同時斷線,最近的進度可能回檔

症狀: 斷線, 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

記憶體回收與壓縮(compaction)造成的暫停 Memory compaction / reclaim stalls (THP)

OS 為了組出大分頁(huge page)而壓縮記憶體,或回收可用記憶體時,處理程序會停住。

為什麼: 可用記憶體減少,或大分頁功能(THP)執行記憶體壓縮 → 於是: 要求記憶體的執行緒一直等到回收或壓縮結束 → 畫面上: 不規則的伺服器暫停(數 ms~數百 ms)

症狀: 定格, 卡頓 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發)

系統時鐘跳動(NTP step) Wall-clock jump (NTP step)

伺服器時鐘一次被往前或往後調整幾秒時,依賴系統時鐘的計時器會同時觸發或停住。

為什麼: 時間同步一次大幅調整時鐘 → 於是: 計時器集中觸發或停住,逾時判定出錯 → 畫面上: buff 與冷卻時間異常、大家同時斷線、快轉

症狀: 快轉, 斷線, 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

排程工作 Cron jobs (log rotation, backup, scans)

每天固定時間執行的 log 壓縮、備份、安全掃描會占用 CPU 與磁碟。

為什麼: OS 工作在排定的時間執行 → 於是: 與遊戲伺服器共用 CPU 與磁碟 → 畫面上: 像每天凌晨 4 點這樣的固定時間出現卡頓、慢動作

症狀: 卡頓, 慢動作 · 主要負責 基礎設施團隊(伺服器基礎設施)

OS、kernel、驅動程式、韌體更新後的效能變化 Performance regression after OS / kernel / driver / firmware update

遊戲程式碼沒變,但伺服器 OS、kernel、驅動程式、韌體更新後就變慢。更新有時會改變預設值、排程器、CPU 漏洞緩解措施(mitigations)與驅動程式的行為。

為什麼: 定期安全性更新或新的伺服器映像檔讓 kernel、驅動程式、韌體有所改變 → 於是: 預設值或排程器改變,或啟用了新的漏洞緩解措施,同樣的工作要花更多 CPU 時間,執行緒分配到 CPU 的順序也跟著改變 → 畫面上: 原本正常的伺服器從更新那天起一直都慢一點,出現輸入延遲;人潮湧入時出現卡頓、慢動作

症狀: 輸入延遲, 卡頓, 慢動作 · 主要負責 基礎設施團隊(伺服器基礎設施)

伺服器 conntrack 表飽和 conntrack table full

Linux 防火牆會把每條連線記錄在連線追蹤(conntrack)表中,這個表碰到上限時就會丟棄新封包。

為什麼: 連線暴增或反覆建立短連線,讓連線紀錄增加 → 於是: 表已滿,新連線與部分封包被丟棄 → 畫面上: 連不上,或因原因不明的遺失出現瞬移

症狀: 連不上/無限讀取, 瞬移 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發), 遊戲開發團隊(用戶端開發)

伺服器間連線的臨時 port 耗盡 Ephemeral port exhaustion (TIME_WAIT)

遊戲伺服器頻繁地對 DB 或其他伺服器建立又關閉短連線時,已關閉的連線會占用 port 一段時間,導致無法開啟新連線。

為什麼: 每個請求都開啟再關閉一條新連線 → 於是: 先關閉的一方會以 TIME_WAIT 狀態占用 port 約 60 秒(Linux),可用的 port 因此耗盡 → 畫面上: 內部請求失敗,導致存檔失敗、功能異常

症狀: 吃指令/回檔, 連不上/無限讀取 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

Socket 與協定:TCP、UDP、socket 選項

這部分決定遊戲如何透過網路收送資料。即使是同一條線路,依使用哪種協定、socket 選項怎麼設,遺失一個封包時,可能只是「稍微跳一下」,也可能變成「停住 1 秒再快轉」。

TCP 保證依送出的順序完整交付。代價是遺失一個時,在重新收到之前,連後面抵達的資料也全部不交給遊戲,一直等待。UDP 什麼都不保證。抵達的資料會立即交出、不用等待,但遺失的部分要由遊戲自行處理。因此動作性強的遊戲會在 UDP 上自行實作所需程度的可靠性(可靠 UDP),而許多 MMO 則採用容易實作的 TCP,並承受它的弱點。

Socket 選項是這些行為的細部設定:小封包要不要累積後再送(TCP_NODELAY)、收送緩衝區要多大(SO_SNDBUF、SO_RCVBUF)、何時察覺失效的連線(SO_KEEPALIVE、TCP_USER_TIMEOUT)、關閉時剩下的資料怎麼處理(SO_LINGER)。預設值大多是為了用較少的封包有效率地傳送大量資料,對頻繁收送小封包的遊戲往往不利。

重點

TCP 只會依送出的順序把收到的資料交給遊戲。17 號封包遺失時,即使 18~30 號已經抵達,也全部要等到 17 號重新送達(HOL 阻塞)。UDP 是一抵達就交出,所以即使 17 號始終沒來,其餘的也能及時處理。

重傳為什麼會發生(Wi-Fi、壅塞、MTU 黑洞、不必要的重傳等)以及找出原因的方法,在 06 TCP 重傳中依原因分別說明。

這一層造成 lag 的原因

TCP HOL 阻塞 Head-of-line blocking

TCP 為了維持順序,在重新收到遺失的那一個封包之前,不會把之後到達的封包交給遊戲。

為什麼: 一個封包遺失 → 於是: 後面的封包都已抵達,卻只能在接收緩衝區等待 → 畫面上: 停住之後一口氣全部放行,出現快轉

症狀: 定格, 快轉 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

TCP RTO 與指數退避 RTO and exponential backoff

每次重傳又失敗,等待時間就加倍,於是線路短暫中斷會變成長時間停住。

為什麼: 線路短暫中斷,重傳也接連失敗 → 於是: 到下一次嘗試的等待時間以 0.3 → 0.6 → 1.2 → 2.4 秒逐次加倍(以 ping 100ms 計) → 畫面上: 線路只斷了 1 秒,遊戲卻停住 2 秒以上。斷得更久最後就會斷線

症狀: 定格, 斷線 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

Nagle 演算法 + 延遲 ACK Nagle + delayed ACK (TCP_NODELAY off)

把小封包集中起來送的 Nagle 演算法,與延後送出 ACK 的延遲 ACK 互相牽制,每次把訊息分段寫入就會延遲 40~200ms。

為什麼: 沒有開啟 TCP_NODELAY 就分段寫入小訊息 → 於是: 傳送端在等 ACK,接收端卻延後送出 ACK → 畫面上: 線路 ping 很低,每個動作卻都固定慢半拍,出現輸入延遲

症狀: 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

慢速用戶端造成的阻塞式傳送 Blocking send on a full socket

線路很慢的某位玩家傳送緩衝區已滿時,若用阻塞方式(緩衝區有空間之前呼叫不會返回的傳送)送出,伺服器執行緒就會一直等那一個人。

為什麼: 慢速用戶端的傳送緩衝區已滿 → 於是: 由於是阻塞式傳送,伺服器執行緒會等到緩衝區有空間為止 → 畫面上: 該執行緒負責的所有人都出現定格、慢動作

症狀: 定格, 慢動作 · 主要負責 遊戲開發團隊(伺服器開發)

慢速用戶端(slow consumer)的處理策略 Slow-consumer policy

對於待傳送資料不斷累積的用戶端,伺服器會丟棄過時的狀態更新,或直接切斷連線。

為什麼: 用戶端的線路跟不上伺服器送出的資料量 → 於是: 伺服器丟棄過時的狀態更新,或在超過上限時切斷連線 → 畫面上: 只有那個人出現瞬移或斷線

症狀: 瞬移, 斷線 · 主要負責 遊戲開發團隊(伺服器開發)

keepalive 預設值 2 小時 TCP keepalive defaults

對方沒有送出關閉訊號就消失時,TCP 要過很久才會察覺。keepalive(確認閒置連線是否仍存活的 TCP 功能)預設是關閉的,即使開啟,也要閒置 2 小時才開始確認。

為什麼: 用戶端因斷電或線路中斷,沒有送出關閉訊號就消失 → 於是: 伺服器認為連線仍存活(keepalive 預設 7,200 秒;若有正在傳送的資料,約 15 分鐘後才放棄重傳) → 畫面上: 留下幽靈角色,重新連線時出現「帳號已登入」錯誤

症狀: 連不上/無限讀取, 看不見/幽靈物件 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發), 基礎設施團隊(伺服器基礎設施)

UDP 封包的 IP 分段 IP fragmentation of large UDP

超過 MTU(一次能傳送的最大大小)的 UDP 封包會在 IP 層被分段(fragmentation),只要遺失其中一個分段,整個封包就會被丟棄。

為什麼: 人多之處的快照超過 1,500 位元組 → 於是: 拆成多個分段傳送,只要遺失一個就整個丟棄 → 畫面上: 封包越大,遺失率高出好幾倍。只在人多的地方出現瞬移

症狀: 瞬移 · 主要負責 遊戲開發團隊(伺服器開發)

可靠 UDP 的重傳設定 Reliable-UDP tuning (KCP, ENet…)

在 UDP 上自行實作的重傳規則太保守時復原會變慢,太積極時反而讓線路更壅塞。

為什麼: 重傳間隔、次數、視窗大小的設定與線路不相符 → 於是: 復原太慢,或重複傳送讓壅塞惡化 → 畫面上: 技能被吃、快轉、壅塞時 lag 更嚴重

症狀: 吃指令/回檔, 快轉 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

閒置後的慢啟動(slow start) Slow start after idle

連線閒置一段時間後,TCP 會再次縮小壅塞視窗(一次能送出的量),所以突然要送大量資料時,得分成好幾次送出。

為什麼: 透過原本閒置的連線送出進入城鎮等大量資料 → 於是: 壅塞視窗已縮小,只能分成多個往返傳送 → 畫面上: 剛進入時,周圍的角色與 NPC 晚了好幾個往返才出現(伺服器越遠越明顯)

症狀: 輸入延遲, 看不見/幽靈物件 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發)

壅塞控制造成傳送量驟降 Congestion control backoff

TCP 把封包遺失視為壅塞的訊號,會把傳送速率降低 30~50%。對 Wi-Fi 造成的遺失也會做出同樣的反應。

為什麼: 要送的資料量大時,Wi-Fi 或線路上發生少量封包遺失 → 於是: TCP 大幅降低傳送速率後慢慢回升(Linux、Windows 預設的 CUBIC 會降低 30%) → 畫面上: 人多的地方狀態更新積壓,出現快轉、輸入延遲

症狀: 快轉, 輸入延遲 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發)

RST 強制關閉造成最後的資料遺失 SO_LINGER, abrupt RST

伺服器倉促切斷連線時,最後送出的通知或存檔完成訊號會消失。

為什麼: 伺服器以強制關閉(RST)結束連線。把 SO_LINGER 設為 0 秒,或沒讀完收到的資料就關閉時會發生 → 於是: 仍在傳送中的踢出原因與最後的資料被丟棄 → 畫面上: 沒來由地出現「因不明錯誤中斷連線」

症狀: 斷線 · 主要負責 遊戲開發團隊(伺服器開發)

阻塞式 I/O 架構 Blocking I/O model

執行緒在等待某個 socket 時無法做其他事的架構,玩家越多整體就越慢。

為什麼: 每條連線各自等待讀寫的方式 → 於是: 一條連線的延遲擴散到同一執行緒的其他連線 → 畫面上: 同時上線人數越多,所有人都出現慢動作、輸入延遲

症狀: 慢動作, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發)

SO_REUSEPORT 分配不均 SO_REUSEPORT imbalance, stuck worker

多個處理程序共用同一個 port 接收時,kernel 會依位址雜湊為每個連線指定負責的處理程序,之後不再更換。其中一個處理程序停住時,只有分配給它的人要等待。

為什麼: 閘道或登入伺服器用 SO_REUSEPORT 啟動多個處理程序 → 於是: 即使某個處理程序因 GC 或過載停住,分配給它的新連線與 UDP 封包也不會轉給其他處理程序 → 畫面上: 只有部分人連不上或定格。在處理程序數量改變的重新啟動時,部分 UDP session 會中斷

症狀: 連不上/無限讀取, 定格, 斷線 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

Windows UDP socket 的 WSAECONNRESET 錯誤 WSAECONNRESET on a Windows UDP socket

Windows 伺服器向已經離開的用戶端送出 UDP 時,會收到「port 無法到達」(ICMP)的通知。這個通知會讓下一次接收呼叫以錯誤結束;如果伺服器程式碼把這個錯誤當成 socket 本身故障來處理,所有使用該 socket 的人都會受到影響。

為什麼: 持續向剛離開的用戶端位址送出 UDP,收到「port 無法到達」(ICMP)通知 → 於是: Windows 讓下一次接收呼叫以 WSAECONNRESET(10054)錯誤結束,伺服器程式碼因此停止接收或關閉 socket → 畫面上: 使用該 socket 的所有人同時定格、斷線

症狀: 斷線, 定格 · 主要負責 遊戲開發團隊(伺服器開發)

伺服器遊戲程式:tick 與執行緒

實際計算遊戲邏輯的程式。移動、戰鬥、怪物 AI、視野計算、廣播,全都必須在一次「tick」內完成。人越集中在同一個地方,視野計算量與要送出的封包就會以人數的平方增加。

伺服器以固定的 tick 間隔計算遊戲狀態。20 tick 伺服器每 50ms 計算一次,要在這段時間內套用所有玩家的輸入、移動怪物、計算誰看得到誰(視野、AOI),再把變化的內容送給所有看得到的人。這 50ms 就是 tick 預算。超出預算,下一個 tick 就會延後。每個 tick 只推進固定遊戲時間的伺服器,整個遊戲世界的時間會變慢(慢動作);依實際經過的時間一次推進的伺服器,雖然能維持速度,但封包變得稀疏,會卡頓、瞬移。無論哪種,反應都會變慢。一個遊戲執行緒負責整個伺服器(頻道)時,該伺服器的所有人都會受影響;如果每個地圖分配一個執行緒,就是那個地圖的人一起受影響。

問題在於人數。所有人兩兩比較的話,100 人每個 tick 要比較約 1 萬次,1,000 人約 100 萬次。因此伺服器會把地圖切成格狀(grid),只比較鄰近的格子,但像世界王、攻城戰、城鎮廣場活動這樣所有人擠到同一個格子附近時,格狀的效果就會降低,計算量與要送出的資料會暴增。再加上多個執行緒等待同一份資料的鎖,以及在 tick 中途等待 DB 回應的同步呼叫,等待期間該執行緒負責的所有人都會一起停住。

比喻

伺服器的 tick 就像指揮的拍子。管弦樂團的團員(玩家)越多,一拍之內要顧及的樂譜就越多,拍子一亂,整首曲子都會變慢。如果有人跑去倉庫(DB)找一張樂譜,所有人都得等他。

這一層造成 lag 的原因

超出 tick 預算 Tick overrun

一個 tick 內要做的事超出預算時,伺服器的 tick 週期會拉長,整個區域的遊戲進行變慢,或出現卡頓。

為什麼: 一個 tick(例如 50ms)要處理的事超出預算 → 於是: 每秒應計算 20 次的遊戲狀態只算了 8 次 → 畫面上: 整個區域出現慢動作(依伺服器設計也可能是卡頓),技能反應變慢

症狀: 慢動作, 輸入延遲, 卡頓 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

視野(AOI)計算量暴增(N²) Area-of-interest explosion

若要判斷誰能看到誰而讓所有人兩兩比較,人數變成 10 倍時,運算量會變成 100 倍。

為什麼: 所有角色兩兩比較距離,或即使切成格狀(grid),一個格子附近仍擠了數百人 → 於是: 100 人約比較 1 萬次,1,000 人約 100 萬次 → 畫面上: 在世界王、攻城戰等人潮聚集的地方 tick 時間暴增,出現慢動作、卡頓

症狀: 慢動作, 卡頓 · 主要負責 遊戲開發團隊(伺服器開發)

廣播量暴增 Broadcast fan-out (N×N)

把一個人的移動送給所有看得到他的人時,要送出的狀態更新數量會是聚集人數的平方。

為什麼: 把一個人的變化傳送給所有看得到的人 → 於是: 1,000 人互相看得到時,每個 tick 有 100 萬筆狀態更新 → 畫面上: 傳送佇列與頻寬飽和,造成延遲與遺失(輸入延遲、快轉、瞬移)

症狀: 輸入延遲, 瞬移, 快轉 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

單執行緒區域過載(熱點) Single-threaded hot zone

在每個區域由一個執行緒負責的架構中,人潮擠到同一處時,只有那一個核心會到 100%。

為什麼: 一個區域(頻道)由一個執行緒負責 → 於是: 人潮擠到同一處時只有那個核心飽和,其餘核心還有餘裕 → 畫面上: 只有那個區域 lag,其他區域正常

症狀: 慢動作, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

鎖競爭 Lock contention

多個執行緒為了寫入同一份資料而等待同一個鎖時,執行緒再多也只能一次執行一個。

為什麼: 拍賣場、公會倉庫這類共用資料由多個執行緒同時使用 → 於是: 拿到鎖的執行緒完成之前,其餘執行緒都在等待 → 畫面上: 只有特定功能變慢,嚴重時整個 tick 延遲

症狀: 輸入延遲, 定格 · 主要負責 遊戲開發團隊(伺服器開發)

死結 Deadlock

兩個執行緒各自等待對方持有的鎖時,就會永遠停住。

為什麼: 執行緒 A 持有鎖 1 並等待鎖 2,B 持有鎖 2 並等待鎖 1 → 於是: 兩者都永遠停住,相關的執行緒也接連停住 → 畫面上: 整台伺服器停止運作,看門狗(watchdog)重新啟動時所有人斷線

症狀: 定格, 斷線 · 主要負責 遊戲開發團隊(伺服器開發)

遊戲執行緒上的同步呼叫 Synchronous DB / file I/O on the game loop

在 tick 進行中等待 DB 回應或檔案寫入時,伺服器上整個遊戲進行就會停住同樣長的時間。

為什麼: 在 tick 內等待 DB 查詢與儲存、log 寫入、外部 API 呼叫 → 於是: DB 花 100ms,tick 也停住 100ms → 畫面上: 每當 DB 或磁碟變慢,整個野外就頓一下

症狀: 定格, 卡頓 · 主要負責 遊戲開發團隊(伺服器開發)

訊息佇列積壓 Mailbox / job queue backlog

請求進來的速度比處理速度快而在佇列中累積時,排在後面的請求要幾秒後才會處理,或被丟棄。

為什麼: 請求抵達的速度比處理速度快 → 於是: 佇列變長,超過上限就丟棄 → 畫面上: 技能、交易反應變慢或被吃掉

症狀: 輸入延遲, 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發)

計時器集中同時觸發 Synchronized timers

所有怪物重生、所有 buff 到期、整點獎勵都擠在同一個 tick 時,那一個 tick 的負擔會變成平常的數十倍。

為什麼: 重生、到期、獎勵、自動存檔的計時器都設在同一時刻 → 於是: 那一個 tick 的工作量是平常的數十倍 → 畫面上: 每到固定時刻就頓一下

症狀: 定格, 卡頓 · 主要負責 遊戲開發團隊(伺服器開發)

尋路運算暴增 Pathfinding storms

數百隻怪物同時追擊玩家並計算路徑時,會耗用大量 CPU。

為什麼: 拉一大群怪或大量生成時,許多怪物同時追擊玩家 → 於是: 每隻怪物各自計算尋路 → 畫面上: 只有那個練功區出現慢動作

症狀: 慢動作 · 主要負責 遊戲開發團隊(伺服器開發)

序列化與壓縮的成本 Serialization / compression cost

把要送出的資料轉成位元組並壓縮也要耗用 CPU,人多時這項成本會暴增。

為什麼: 每筆狀態更新都要把結構轉成位元組並壓縮 → 於是: 成本與人數的平方成正比增加 → 畫面上: 傳送變慢,出現輸入延遲

症狀: 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發)

伺服器當機 Server process crash

伺服器處理程序因未處理的錯誤而終止時,該伺服器上所有人會同時斷線。

為什麼: 指向不存在對象的錯誤(null 參照)、錯誤的資料、記憶體不足等致命錯誤 → 於是: 伺服器(或 zone)處理程序結束 → 畫面上: 所有人同時斷線,上次存檔後的進度可能回檔

症狀: 斷線, 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

執行緒池耗盡 Thread pool starvation

負責處理工作的工作執行緒全被慢的工作綁住時,新請求只能無止盡地等待。

為什麼: 工作執行緒都被綁在等待外部 API 或 DB 回應上 → 於是: 沒有執行緒可以接新請求 → 畫面上: 登入、商城等特定功能出現無限讀取

症狀: 連不上/無限讀取, 輸入延遲, 定格 · 主要負責 遊戲開發團隊(伺服器開發)

無窮迴圈、邏輯失控 Infinite loop / runaway logic

因 bug 導致一個 tick 結束不了時,伺服器就會停住,看門狗會強制重新啟動。

為什麼: 條件寫錯導致迴圈結束不了,或遞迴失控 → 於是: tick 結束不了,伺服器停止 → 畫面上: 定格後所有人斷線

症狀: 定格, 斷線 · 主要負責 遊戲開發團隊(伺服器開發)

集中在單一目標的戰鬥(世界王) Hot entity / combat event fan-out

數百人同時攻擊同一隻王時,這隻王的運算全集中在一處,打擊資訊也要傳送給所有看得到的人。

為什麼: 數百人不停地對同一隻王施放技能、buff、debuff → 於是: 王的血量、仇恨列表、debuff 計算集中在一處,每次打擊都要把傷害數字與特效封包傳送給所有看得到的人 → 畫面上: 技能延遲命中,傷害數字一口氣跳出來,只有王的周圍出現慢動作

症狀: 輸入延遲, 快轉, 慢動作 · 主要負責 遊戲開發團隊(伺服器開發)

進入密集區域時的生成暴增 Spawn burst when entering a crowd

用傳送移動到擠滿人的城鎮時,伺服器必須一次送出數百位新進入視野者的外觀、裝備與狀態。

為什麼: 因傳送移動、登入或切換頻道,突然出現在人多的地方 → 於是: 伺服器一次產生並送出數百人份的完整資訊,自己的電腦也要一次載入 → 畫面上: 剛抵達時畫面短暫停住,角色一個一個慢慢出現,輸入也有延遲

症狀: 定格, 輸入延遲, 快轉 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發)

物件累積(未清理的道具、召喚物) Entity / timer buildup over uptime

該消失的地上道具、召喚物、已結束的計時器沒有清理而不斷累積時,伺服器開得越久,每個 tick 要做的事就越多。

為什麼: 地上道具、召喚物、已到期的計時器、空隊伍資訊沒有及時刪除 → 於是: 每個 tick 要走訪的清單一天比一天長 → 畫面上: 維護剛結束時正常,過了幾天後只有那台伺服器或那個區域越來越遲鈍

症狀: 慢動作, 卡頓, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發)

更新改變了流量模式 Patch changes traffic pattern

新內容、特效、同步項目讓封包變大、變頻繁時,原本運作正常的伺服器在更新後就會碰到 MTU、頻寬、封包數上限。

為什麼: 更新增加了新的技能特效、同步項目、道具資訊,封包變大或變頻繁 → 於是: 大封包超過 MTU 而被分段,增加的流量碰到頻寬、雲端 PPS 上限與傳送緩衝區的限制 → 畫面上: 更新後人多的地方開始出現瞬移、技能被吃、輸入延遲。基礎設施沒有任何變更,封包遺失卻增加

症狀: 瞬移, 吃指令/回檔, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施), 基礎設施團隊(網路基礎設施)

記憶體

伺服器記住的一切,也就是角色、怪物、道具、地圖,都放在記憶體裡。記憶體本身很快,但只要因 GC(回收不再使用的記憶體)而暫停、一點一點地流失(洩漏),或因為不足而發生 swap(把部分記憶體移到磁碟),就會 lag。

Java、C#、Go 這類自動管理記憶體的語言,會由垃圾回收器(GC)收集並回收用完丟棄的記憶體。依 GC 方式不同,有時會讓所有執行緒短暫停下。一次對整個 heap(程式執行中配置使用的記憶體區域)做 GC 時,存活的資料越多就越久,可達數百 ms 到數秒。ZGC 這類新型 GC 能把暫停縮短到 1ms 以下,代價是使用更多 CPU 與記憶體。C++ 伺服器沒有 GC,卻要面對忘記釋放的記憶體不斷累積的洩漏,以及可用空間被切成零碎小塊、無法使用大區塊的碎片化。即使有 GC,用完的物件如果某處仍持續參照,一樣會發生洩漏。

記憶體變慢的另一個原因是記憶體階層(儲存裝置離 CPU 多遠)。CPU 旁邊的快取是 1ns,RAM 是 100ns,把移到磁碟的記憶體(swap)讀回來,則要花 RAM 的 1,000 倍以上。請在下方「數值參考」表中,把這些差異放大成人的時間尺度來看看。

比喻

記憶體就像廚師的工作台。食材在伸手可及處(快取)就很快,走到冰箱(RAM)稍慢一點,工作台滿了、食材放到倉庫(磁碟、swap),每拿一次都要花很久。洗碗(GC)的時候,就得停下烹飪。

這一層造成 lag 的原因

伺服器 GC 全面暫停 Stop-the-world GC pause

Java、C# 伺服器為了回收垃圾而暫停所有執行緒(stop-the-world)的期間,整個伺服器都會停住。

為什麼: heap 滿了,開始 GC → 於是: 暫停所有遊戲執行緒進行回收(存活資料越多,暫停越久) → 畫面上: 伺服器上的所有人同時定格,接著出現快轉

症狀: 定格, 快轉 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

腳本引擎的 GC 暫停 Scripting VM GC (Lua, etc.)

即使是 C++ 伺服器,只要任務、AI、技能是用 Lua 等腳本執行,腳本引擎進行 GC 的期間,該 zone 就會停住。

為什麼: 每個 zone 的腳本引擎執行任務、AI、事件時,大量產生暫時物件 → 於是: 腳本引擎的 GC 一次回收大量物件時,該 zone 的 tick 會停住 → 畫面上: 只在特定 zone 或特定活動期間週期性地頓一下

症狀: 卡頓, 定格 · 主要負責 遊戲開發團隊(伺服器開發)

記憶體配置暴增 Allocation storms

活動期間大量產生暫時物件時,GC 執行的頻率會比平常高出許多。

為什麼: 道具掉落、戰鬥 log、活動獎勵讓暫時物件暴增 → 於是: GC 頻率增加數倍,還沒來得及變成垃圾的物件被移到 Old 區,Full GC 也跟著提早發生 → 畫面上: 只在活動期間週期性地頓一下

症狀: 卡頓, 定格 · 主要負責 遊戲開發團隊(伺服器開發)

記憶體洩漏 Memory leak

沒有釋放的記憶體一點一點累積,幾天後演變成 GC 暴增、swap 或處理程序被強制終止。

為什麼: 已登出角色的資料、事件處理常式(event handler)沒有被釋放 → 於是: 可用記憶體在幾天內逐漸減少 → 畫面上: 維護剛結束時一切正常,之後一天比一天 lag,最後伺服器當機

症狀: 慢動作, 定格, 斷線 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

GC thrashing(heap 可用空間不足) GC thrashing (heap nearly full)

存活資料逼近 heap 上限時,每次 GC 幾乎回收不到東西,GC 就會不停反覆執行。

為什麼: 活動湧入的人潮或記憶體洩漏,讓存活資料逼近 heap 上限 → 於是: GC 只能回收一點點,馬上又進行 Full GC,CPU 大部分都被 GC 占用 → 畫面上: 整個伺服器在幾分鐘內反覆出現慢動作與定格,最後因記憶體不足而終止

症狀: 慢動作, 定格, 斷線 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

Swap Swapping

記憶體不足、OS 把部分內容移到磁碟後,每次用到那塊記憶體,都得等待慢上 1,000 倍以上的磁碟。

為什麼: 使用中的記憶體超過實體 RAM → 於是: OS 把一部分移到磁碟,需要時再讀回來 → 畫面上: tick 暴增到數百 ms,伺服器上所有玩家都遇到慢動作與定格

症狀: 慢動作, 定格 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發)

快取未命中 CPU cache misses

資料散落在記憶體各處時,CPU 每次都必須到較慢的 RAM 讀取並等待資料。

為什麼: 物件透過指標散落各處,存取順序雜亂 → 於是: CPU 快取裡沒有,每次都要從 RAM 讀取(慢 100 倍左右) → 畫面上: 做同樣的事,tick 成本卻高出數倍,嚴重時出現慢動作

症狀: 慢動作 · 主要負責 遊戲開發團隊(伺服器開發)

記憶體碎片化 Heap fragmentation

反覆配置與釋放,讓閒置空間被切得零碎時,占用的記憶體會遠多於實際使用量。

為什麼: 多個執行緒長時間配置、釋放大小不一的記憶體 → 於是: 閒置空間零散分布,無法歸還給 OS,用量像洩漏一樣持續增加 → 畫面上: 開越久越會因 swap 與記憶體不足而變慢,最後被強制終止

症狀: 慢動作, 斷線 · 主要負責 遊戲開發團隊(伺服器開發)

NUMA 遠端記憶體 Remote NUMA access

在有兩顆 CPU 的伺服器上,使用接在另一顆 CPU 上的記憶體時,存取會變慢。

為什麼: 執行緒與記憶體位於不同的 CPU 插槽(socket) → 於是: 記憶體存取變慢(依設備不同,約慢 1.5~2 倍) → 畫面上: 規格相同,各處理程序的效能卻有落差

症狀: 慢動作 · 主要負責 基礎設施團隊(伺服器基礎設施)

磁碟

Log、角色存檔、地圖資料、DB 檔案都在磁碟上。磁碟比記憶體慢數百倍(SSD)到 10 萬倍(HDD),所以如果遊戲伺服器的架構會等待磁碟,磁碟一忙,遊戲也會跟著停住。

磁碟的效能以「1 秒能讀寫幾次」(IOPS)來衡量。老舊 HDD 約 150 次出頭,SSD 則是數萬到數十萬次。雲端磁碟的上限依付費多寡而定(AWS 預設的 gp3 是 3,000 次)。部分雲端磁碟與小規格伺服器,除了平時速度之外,還會提供可短暫發揮更高效能的 burst credit(突發額度),但忙碌時段一拉長,額度耗盡,速度就會突然下降。「每天晚上過了幾個小時就會 lag」這類回報就是這個樣子。

關鍵在於誰在等。一般的檔案寫入會先由 OS 收進記憶體,之後再寫到磁碟,所以通常立刻就完成。問題出在要求等到「實際寫入磁碟為止」(fsync),或是 OS 能暫存在記憶體中的上限滿了的時候。這時若由遊戲執行緒直接等待(同步),磁碟延遲 100ms,tick 也會停住 100ms。把寫入交給另一個執行緒(非同步),遊戲就不會停住,但伺服器突然當機時,還沒寫入的內容可能會消失(吃指令/回檔)。

比喻

磁碟是倉庫,IOPS 是倉庫門的數量。門少的話,搬進搬出的人就得排隊。Burst credit 就像能短暫衝刺的體力,用完就回到走路的速度。

這一層造成 lag 的原因

同步寫入 log Synchronous logging

遊戲執行緒每寫一行 log 都要等磁碟寫完的話,磁碟一忙,遊戲進行也會跟著停住。

為什麼: 在遊戲執行緒上直接把戰鬥、交易 log 寫入檔案 → 於是: 要求確實寫入(fsync),或 OS 的寫入緩衝區(頁面快取)達到上限時,磁碟一忙,一次寫入就要數十 ms → 畫面上: log 量大的戰鬥中會頓一下

症狀: 卡頓, 定格 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

fsync 暴增 fsync storms

要求把資料「確實」寫入磁碟時,依磁碟不同,每次需要 0.1ms~數十 ms;請求一多,佇列就會變長。

為什麼: 定期存檔、登出潮使確實寫入的請求大量湧入 → 於是: 磁碟佇列變長 → 畫面上: 每到存檔時間就 lag,登出、切換頻道變慢

症狀: 卡頓, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施), 基礎設施團隊(DB 基礎設施)

雲端磁碟 burst credit 耗盡 Burst credit depletion

部分雲端磁碟與小規格伺服器有 burst credit(突發額度),可以短時間跑得比基準效能快;忙碌時段一拉長、額度用完,速度就會突然下降。

為什麼: 長時間以高於基準效能的速度使用 → 於是: burst credit 用完,效能驟降回基準值 → 畫面上: 每天晚上過了幾個小時後開始 lag

症狀: 卡頓, 慢動作, 輸入延遲 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 基礎設施團隊(DB 基礎設施)

IOPS 上限與佇列飽和 IOPS limit / queue saturation

請求數超過磁碟每秒能處理的數量時,佇列會變長,延遲急遽增加。

為什麼: 讀寫請求接近磁碟的處理能力 → 於是: 佇列變長(通常在使用率 90% 以上時急遽增加) → 畫面上: 存檔、載入變慢;若是同步呼叫則會定格

症狀: 輸入延遲, 定格 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發), 基礎設施團隊(DB 基礎設施)

磁碟已滿 Disk full

log 與 dump 越積越多、把磁碟塞滿時,寫入會失敗;沒有防範措施的話,伺服器會當機。

為什麼: log、dump、暫存檔累積到 100% → 於是: 寫入失敗。沒有錯誤處理就當機,有的話則存檔失敗 → 畫面上: 斷線、進度回檔

症狀: 斷線, 吃指令/回檔 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 基礎設施團隊(DB 基礎設施), 遊戲開發團隊(伺服器開發)

備份、壓縮、掃描作業 Backup / compression / scans

凌晨的備份、log 壓縮、安全掃描獨占磁碟時,遊戲伺服器的讀寫就會被擠到後面。

為什麼: 排定的備份、壓縮作業開始 → 於是: 占用大部分的磁碟頻寬與 IOPS → 畫面上: 每天同一時間 lag

症狀: 卡頓, 輸入延遲 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 基礎設施團隊(DB 基礎設施)

伺服器端的延遲載入 Lazy loading on the server

伺服器在副本、地圖資料第一次被請求時才從磁碟讀取的話,那個 tick 期間所有人都會停住。

為什麼: 有人第一次進入副本或地圖 → 於是: 伺服器在遊戲執行緒上從磁碟讀取資料 → 畫面上: 那台伺服器上的所有人短暫定格

症狀: 定格 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

寫入 core dump Core dump writing

伺服器當機時要把數 GB 的記憶體寫入磁碟,有時會讓重新啟動延後好幾分鐘。

為什麼: 伺服器當機,把整個記憶體寫成檔案 → 於是: 寫入數 GB 的期間無法重新啟動 → 畫面上: 伺服器當機造成斷線後,很長一段時間連不上

症狀: 連不上/無限讀取 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發)

HDD 尋軌延遲 HDD seek latency

HDD 的讀寫頭必須在碟片上移動(尋軌,seek),所以讀寫分散各處的資料時,每次要花將近 10ms。

為什麼: 老舊伺服器或低價儲存裝置使用 HDD → 於是: 每次分散的讀寫約 10ms → 畫面上: 存檔、載入全面變慢

症狀: 輸入延遲 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 基礎設施團隊(DB 基礎設施), 遊戲開發團隊(伺服器開發)

資料庫

這裡存放角色、道具、貨幣、交易紀錄這類絕對不能遺失的東西。DB 變慢時,戰鬥正常,但道具很晚才入帳、交易失敗、登入一直跑不完。如果遊戲伺服器的架構會等待 DB,整個野外都會停住。

遊戲伺服器會預先與 DB 建立幾條連線(連線池)輪流使用。一個查詢(送給 DB 的請求)花很久時,那條連線就會一直被占用;連線池的連線全被占用時,其餘請求就在佇列中等待。查詢變慢的常見原因有兩個:沒有索引(就像書的索引)而讀取整張資料表(全表掃描),或是多個請求同時想修改同一筆資料列而等待鎖定。

DB 為了可靠,也為了承接大量請求,會使用多種機制:分擔讀取的複本、故障時接手的備援 DB、定期把變更集中寫入磁碟的檢查點。檢查點集中時會暫時變慢。複本落後時會出現「剛買的道具看不到」;複寫落後的狀態下切換到備援 DB,則會出現「登入後回到了稍早的狀態」這類吃指令/回檔症狀。如果遊戲伺服器幾分鐘才儲存一次角色,伺服器當機時就會變成「回到 10 分鐘前」。

比喻

DB 就像銀行櫃台。櫃台數量(連線池)是固定的,一個請求要翻遍整本帳簿(全表掃描)時,後面的請求都得等。所有人都想打開同一個金庫(熱點資料列)時,一次只能進去一個人。

這一層造成 lag 的原因

沒有索引的查詢 Missing index / full table scan

沒有索引時,要找出符合條件的資料列,就必須讀完整張資料表(全表掃描)。

為什麼: 部署新功能時,加入了沒有索引的條件查詢 → 於是: 掃描數百萬筆資料列,一個查詢就要數百 ms~數秒 → 畫面上: 信箱、交易紀錄載入變慢,連線被占住,連其他請求也要等待

症狀: 輸入延遲, 連不上/無限讀取 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)

熱點資料列鎖定競爭 Hot row lock contention

所有人都要修改同一筆資料列(公會倉庫、拍賣場熱門道具、全伺服器共用的計數器)時,一次只有一個請求能取得鎖定。

為什麼: 活動、熱門道具讓修改集中在同一筆資料列 → 於是: 請求一直等到取得鎖定為止 → 畫面上: 交易失敗、「請稍後再試」、逾時

症狀: 吃指令/回檔, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)

DB 死結 Database deadlock

兩個 transaction(當成一個整體處理的一組 DB 操作)互相等待對方鎖住的資料列時,DB 會強制取消其中一方。

為什麼: 交易 A 依道具 → 貨幣的順序鎖定,交易 B 依貨幣 → 道具的順序鎖定 → 於是: DB 偵測到死結,回滾其中一方 → 畫面上: 交易、製作偶爾失敗,道具被退回

症狀: 吃指令/回檔, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)

連線池耗盡 Connection pool exhaustion

與 DB 建立的連線數是固定的,慢查詢占住連線時,其餘請求只能等待。

為什麼: 慢查詢或請求暴增,使所有連線都在使用中 → 於是: 新請求要等到有連線空出來 → 畫面上: 登入時無限讀取、存檔變慢、逾時

症狀: 連不上/無限讀取, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)

複寫延遲 Replication lag

寫入走主 DB、讀取走複本的架構下,複本跟得慢時,剛寫入的內容就會看不到。

為什麼: 寫入集中湧向主 DB,複本落後數秒 → 於是: 從複本讀取剛存檔的內容時,資料還不存在 → 畫面上: 剛買的道具看不到、交易所價格還是舊的、重複發放的 bug

症狀: 吃指令/回檔 · 主要負責 基礎設施團隊(DB 基礎設施) · 協同 遊戲開發團隊(伺服器開發)

檢查點與 log flush Checkpoint / log flush stalls

DB 定期把記憶體中的變更一次大量寫入磁碟的瞬間,查詢會變慢。

為什麼: 變更累積後定期寫入磁碟 → 於是: 那一刻磁碟變忙,查詢變慢 → 畫面上: 存檔、載入週期性變慢

症狀: 輸入延遲, 卡頓 · 主要負責 基礎設施團隊(DB 基礎設施)

冷快取(剛重新啟動時) Cold buffer pool after restart

DB 重新啟動後記憶體快取是空的,有一段時間所有查詢都要從磁碟讀取。

為什麼: 因維護而重新啟動 DB → 於是: 常用資料不在記憶體中,只能從磁碟讀取 → 畫面上: 維護剛結束的一段時間內,登入、載入很慢

症狀: 連不上/無限讀取, 輸入延遲 · 主要負責 基礎設施團隊(DB 基礎設施) · 協同 遊戲開發團隊(伺服器開發)

登入暴增與 N+1 查詢 Login storm, N+1 queries

載入一個角色要分開查詢數十次的話,數萬人同時登入就會變成數百萬個查詢。

為什麼: 載入角色時,道具、技能、任務各自分開查詢 → 於是: 維護剛結束時大量同時登入,查詢暴增 → 畫面上: 登入時無限讀取,連正在遊戲中的玩家存檔都被拖慢

症狀: 連不上/無限讀取, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)

大量批次作業 Batch jobs during service

在營運時段執行排行榜彙總、信件批次發送、舊資料清理時,會占用鎖定與磁碟。

為什麼: 在營運時段執行大量作業 → 於是: 大範圍鎖定,占用磁碟與 CPU → 畫面上: 特定時段交易、存檔失敗,載入變慢

症狀: 輸入延遲, 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)

DB 容錯移轉 Database failover

主 DB 故障、切換到備援 DB 的期間無法寫入,最後一批還沒複寫過去的資料可能會遺失。

為什麼: 主 DB 故障,備援 DB 升格為主 DB → 於是: 切換期間有數秒~幾分鐘無法寫入;若是非同步複寫,尚未複寫的資料可能遺失 → 畫面上: 短時間內所有存檔失敗,道具、經驗值回檔

症狀: 吃指令/回檔, 定格, 斷線, 連不上/無限讀取 · 主要負責 基礎設施團隊(DB 基礎設施) · 協同 遊戲開發團隊(伺服器開發)

存檔週期過長造成的進度遺失 Periodic save window

為了減輕負載而每隔幾分鐘才存檔一次的話,伺服器在兩次存檔之間當機時,進度就會消失。

為什麼: 每隔幾分鐘才儲存一次角色狀態 → 於是: 在兩次存檔之間發生伺服器當機或故障 → 畫面上: 重新連線後回到幾分鐘前的狀態(回檔)

症狀: 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)

Cache stampede Cache stampede / thundering herd

熱門資料的快取同時過期時,數千個請求會一口氣湧向 DB。

為什麼: 存放在 Redis 等快取中的熱門資料同時過期 → 於是: 要重新產生同一筆資料的請求一口氣湧向 DB → 畫面上: DB 過載,多項功能接連變慢甚至停住

症狀: 輸入延遲, 定格, 連不上/無限讀取 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)

長時間未結束的 transaction Long-running transaction / MVCC purge lag

一個 transaction 長時間不結束時,會一直持有鎖定,DB 也無法清理(purge)舊版本的資料,整體會越來越慢。

為什麼: 開著 transaction 等待其他伺服器的回應,或在營運中於主 DB 上執行長時間的彙總查詢 → 於是: 持有的鎖定一直不釋放,待清理的舊版本資料持續累積 → 畫面上: 使用那些資料列的功能逾時,存檔與查詢在幾個小時內全面變慢

症狀: 輸入延遲, 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)

Redis 慢指令 Redis blocking commands (single-threaded)

Redis 一次只處理一個指令,因此一個慢指令就會擋住後面所有的請求。

為什麼: 營運中用 KEYS 搜尋全部 key,或一次讀取、刪除含數百萬個元素的排行榜或清單 → 於是: 在該指令結束前,其他所有請求都要等待(數十 ms~數秒) → 畫面上: 使用 session、排行榜、快取的功能同時頓一下,登入變慢

症狀: 定格, 輸入延遲, 連不上/無限讀取 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)

執行計畫改變造成的查詢延遲 Query plan regression (stats, parameter sniffing)

程式碼沒變,DB 卻改變了處理同一個查詢的方式(執行計畫)時,昨天只要 2ms 的查詢,今天會變成數百 ms。

為什麼: 統計資訊自動更新、DB 重新啟動或資料分布改變,使 DB 重新建立執行計畫 → 於是: 選中了不走索引的計畫,同一個查詢慢了數十~數百倍,連線被占住 → 畫面上: 明明沒有部署,特定功能的載入卻突然變慢,連其他請求也要等待

症狀: 輸入延遲, 連不上/無限讀取 · 主要負責 基礎設施團隊(DB 基礎設施) · 協同 遊戲開發團隊(伺服器開發)

營運中 schema 變更(DDL)的鎖定 Schema change lock (DDL / metadata lock)

在服務中替資料表新增欄位或索引時,可能因為一個只需要短暫持有的鎖定,讓所有使用該資料表的請求都在等待。

為什麼: 以 hotfix 替營運中的資料表新增欄位或索引 → 於是: schema 變更在等待先前開啟的長 transaction,後面進來的所有請求又在等待這個 schema 變更 → 畫面上: 使用該資料表的功能(背包、信件等)整個停住並逾時

症狀: 輸入延遲, 吃指令/回檔, 連不上/無限讀取 · 主要負責 基礎設施團隊(DB 基礎設施) · 協同 遊戲開發團隊(伺服器開發)

伺服器架構與維運

現在的 MMO 大多是登入、閘道、野外、副本、聊天、隊伍、拍賣場、快取、DB 等伺服器互相呼叫運作的架構。一處故障就會擴散到相連的地方,部署、擴展、維護這類維運作業也會造成 lag。

把伺服器拆開,可以防止一處故障擴散到全體,但相對地會產生呼叫鏈(伺服器依序呼叫其他伺服器的連結)。例如遊戲伺服器呼叫拍賣場伺服器,拍賣場伺服器再呼叫快取與 DB。鏈尾的伺服器一變慢,前面的伺服器就會一邊等待回應、一邊持續占用執行緒與連線,最後連看似無關的功能也停擺。這稱為連鎖故障,要用逾時與斷路器(暫時阻擋持續失敗之呼叫的機制)防止擴散。

維運作業也是 lag 的原因。部署更新時的重新啟動、人潮湧入時自動增加伺服器所需的幾分鐘、切換 zone 時把角色搬到其他伺服器的過程、bot 與巨集造成的隱形負載,在玩家看來都是「lag」。

比喻

伺服器架構就像多個部門互相傳遞簽核的公司。簽核流程末端的部門(DB)一變慢,前面的部門就拿著文件排隊,最後整間公司的業務都停擺。逾時是「超過 10 分鐘沒回覆就先退件」的規則,斷路器則是「持續退件時,一段時間內不再把文件送到那個部門,直接退回」的規則。

這一層造成 lag 的原因

經由閘道/proxy Gateway / proxy hop

在用戶端與遊戲伺服器之間放一台中介伺服器時,每經過一次就多一段處理時間,而且這台伺服器會成為單點故障。

為什麼: 用戶端 ↔ 閘道 ↔ 遊戲伺服器的架構 → 於是: 多了中介伺服器的處理與等待時間,過載時影響所有人 → 畫面上: 整體 ping 上升;閘道故障時,經過它的玩家全部斷線

症狀: 輸入延遲, 斷線 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(用戶端開發)

切換 zone(跨伺服器轉移) Zone / server handoff

進入其他地圖或副本時,把角色資料交給另一台伺服器的過程中會產生延遲與失敗。

為什麼: 進入副本、移動到其他大陸,負責的伺服器因此改變 → 於是: 儲存 → 傳遞 → 載入;目標伺服器擁擠或沒有空的副本實例時要等待 → 畫面上: 載入很久、進場失敗、移動途中斷線

症狀: 連不上/無限讀取, 定格, 斷線, 拉回 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

連鎖故障 Cascading failure

一個服務變慢時,呼叫它的伺服器會卡在等待回應,連不相關的功能也跟著停擺。

為什麼: DB、認證等某一個服務變慢 → 於是: 呼叫端伺服器的執行緒與連線卡在等待回應,失敗請求的重試又加重負載 → 畫面上: 看似無關的功能也全部變慢或停擺

症狀: 定格, 輸入延遲, 連不上/無限讀取 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(網路基礎設施)

附屬伺服器故障 Auxiliary service outage

聊天、隊伍、拍賣場這類與遊戲伺服器分開運作的伺服器故障時,只有那個功能無法運作。

為什麼: 功能專用伺服器變慢或掛掉 → 於是: 只有該功能的請求沒有回應 → 畫面上: 無法聊天、組隊邀請沒反應、交易所無限讀取(戰鬥正常)

症狀: 吃指令/回檔, 連不上/無限讀取 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

部署與重新啟動 Deploy / rolling restart

為了更新而重新啟動伺服器時,如果不先把連線移走,原本在那台伺服器上的人就會斷線,關閉前的存檔與重新連線也會同時湧入。

為什麼: 部署 hotfix,依序重新啟動伺服器 → 於是: 沒有把連線移到其他伺服器就關閉,那台伺服器上所有玩家的存檔同時湧向 DB → 畫面上: 沒有公告就斷線、重新連線暴增

症狀: 斷線, 連不上/無限讀取, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

自動擴展延遲 Autoscaling lag

人潮湧入時會自動增加伺服器,但準備要花幾分鐘,這段期間既有的伺服器處於過載。

為什麼: 活動開始,連線急增 → 於是: 新伺服器從啟動到準備好需要數分鐘 → 畫面上: 活動剛開始的幾分鐘內出現慢動作、連不上

症狀: 慢動作, 連不上/無限讀取 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發)

log 與監控過載 Logging / monitoring overhead

發生故障時 log 會暴增,以同步方式送出 log 的伺服器會因為 log 變得更慢。

為什麼: 發生錯誤,log 與指標的傳送量暴增 → 於是: log 收集器消化不及,同步傳送的伺服器只能等待 → 畫面上: 故障時的卡頓、定格因為 log 變得更嚴重

症狀: 卡頓, 定格 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)

伺服器之間的時鐘差異 Clock skew between servers

各伺服器的時鐘各差一點時,冷卻時間、buff、活動開始的判定就會在伺服器之間對不上。

為什麼: 時間同步停止的伺服器,時鐘與其他伺服器相差數百 ms~數秒 → 於是: 在伺服器之間傳遞 buff 結束時間這類絕對時間,判定就會對不上 → 畫面上: 一移動 buff 就消失,或冷卻重新開始計算

症狀: 吃指令/回檔 · 主要負責 基礎設施團隊(伺服器基礎設施) · 協同 遊戲開發團隊(伺服器開發)

巨集/機器人過多 Bots and macros

機器人送出請求的頻率遠高於真人,會吃掉伺服器的處理量。

為什麼: 大量連線的機器人不停重複打怪、移動、交易 → 於是: 伺服器處理量與 DB 負載增加 → 畫面上: 特定練功區或整個伺服器變慢(慢動作、輸入延遲)

症狀: 慢動作, 輸入延遲 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(網路基礎設施)

依賴外部服務 External dependencies (auth, billing, platform)

平台登入、付款、身分驗證這類外部服務變慢或停擺時,會卡在那個步驟。

為什麼: 外部認證、付款服務故障或延遲 → 於是: 在該步驟等待回應 → 畫面上: 無法登入、付款失敗。已在遊戲中的人正常

症狀: 連不上/無限讀取, 吃指令/回檔 · 主要負責 外部(外部) · 協同 遊戲開發團隊(伺服器開發)

配對與區域分配錯誤 Wrong region assignment (matchmaking / GeoDNS)

沒被分到近的區域、卻被分到遠方區域的伺服器時,即使線路正常,也只有那位玩家的 ping 一直偏高。

為什麼: GeoIP 資料錯誤、VPN、以隊友平均 ping 分配整個隊伍、人數不足時擴大到遠方區域的規則、依 DNS 解析器(resolver)位置分配 → 於是: 明明有近的區域,卻連到海外區域的伺服器 → 畫面上: 在多個區域設有伺服器的遊戲中,只有我(或只有我們隊伍)ping 一直偏高,出現輸入延遲、拉回、技能被吃

症狀: 輸入延遲, 拉回, 吃指令/回檔 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發), 基礎設施團隊(網路基礎設施), 外部(外部)

TLS 憑證過期/設定錯誤 TLS certificate expiry / misconfiguration

登入、API、更新伺服器的憑證過期或缺少中繼憑證時,從那一刻起新連線的用戶端 TLS 連線都會失敗。

為什麼: 憑證超過有效期限、伺服器送出時少了中繼憑證,或玩家裝置的日期時間錯誤 → 於是: 用戶端憑證驗證失敗,中斷 TLS 連線 → 畫面上: 在登入、更新階段連不上/無限讀取,只有商城等 HTTPS 功能失敗。已經連線的人大多正常

症狀: 連不上/無限讀取, 吃指令/回檔 · 主要負責 基礎設施團隊(網路基礎設施) · 協同 基礎設施團隊(伺服器基礎設施), 遊戲開發團隊(用戶端開發)

登入排隊上限與重新連線寬限不足 Login queue cap / no reconnect grace

上市或維護剛結束時連線湧入,登入排隊碰到上限就拒絕新的排隊,正在排隊的玩家只要短暫斷線就會失去位置,回到隊伍最後面。

為什麼: 想連線的人比登入伺服器一次能接受的人數多,所以設排隊;排隊太長時,為了保護伺服器會拒絕新的排隊 → 於是: 排隊越長等待時間越久,這段期間 Wi-Fi、行動網路只要短暫斷線就會失去排隊位置 → 畫面上: 連不上/無限讀取、排隊中出現錯誤並關閉遊戲、又要從最後面重新排隊

症狀: 連不上/無限讀取, 斷線 · 主要負責 遊戲開發團隊(伺服器開發) · 協同 遊戲開發團隊(用戶端開發), 基礎設施團隊(伺服器基礎設施)

T1工具

診斷小幫手

收到 lag 回報時,只要選出「誰、何時、什麼樣子」這三項。小幫手會依分數高低,列出本白皮書中最符合的候選原因。這不是確定診斷,但足以決定要先找哪個團隊。

T2工具

用觀測資料判定

收到回報或警示時,依範圍 → 時間點 → 層級的順序縮小範圍。異常集中在哪裡最能決定負責單位,與什麼事件重疊能縮小原因範圍,再以哪一層的指標異常來確認。搭配每張原因卡片上的「圖表上」與「確認方法」,就能從圖表形狀挑出候選原因,並立刻找到要查看的位置。

判定流程

1 範圍

異常集中在哪裡

  • 特定國家、電信業者(ASN) → 基礎設施團隊網路 外部電信業者
  • 特定伺服器、頻道、zone → 主機指標正常時是遊戲開發團隊伺服器,異常時是基礎設施團隊伺服器設備/OS
  • 特定 OS、裝置、版本 → 遊戲開發團隊用戶端
  • 一個人、一個家 → 外部玩家環境(多人出現相同樣子時是遊戲開發團隊用戶端)
  • 全體同時 → 共用資源(DB、負載平衡器、閘道)或剛上線的部署
2 時間點

與什麼重疊

3 層級

哪一層的指標異常

  1. 網路:RTT、遺失、重傳率,介面錯誤與丟棄
  2. 主機:各核心 CPU、CPU steal、softirq、NIC 丟棄、記憶體壓力
  3. 遊戲伺服器:tick 時間、socket 接收佇列(Recv-Q)、各執行緒 CPU、GC log
  4. DB:查詢延遲、鎖等待、複寫延遲
  5. 用戶端:畫格時間、net graph、閃退報告

判定訊號表

要確認的項目看起來是這樣時優先聯絡
伺服器 socket 接收佇列(Recv-Q)伺服器處理程序來不及讀取而堆積遊戲開發團隊伺服器(tick 停住、GC、鎖)
每條連線的重傳與 RTT只有部分連線,集中在特定 ASN基礎設施團隊網路 外部電信業者/玩家線路
一台主機上的所有連線基礎設施團隊伺服器設備/OS(NIC、kernel)
更新剛結束後整個伺服器的重傳與頻寬增加封包大小或頻率改變遊戲開發團隊伺服器 基礎設施團隊網路(MTU、上限)
CPU steal、節流、softirq、NIC 丟棄增加基礎設施團隊伺服器設備/OS
只有一個執行緒 100%、run queue 延遲、GC 暫停增加遊戲開發團隊伺服器
DB 延遲增加,查詢數不變IOPS、鎖定、其他作業基礎設施團隊DB 設備
DB 查詢數或形態在更新後改變N+1、新查詢遊戲開發團隊伺服器
海外據點的合成監控(RTT、遺失)不佳基礎設施團隊網路 外部電信業者
合成監控正常,只有玩家端不佳玩家環境或用戶端外部玩家環境 遊戲開發團隊用戶端
斷線原因分布心跳逾時↑/RST↑/伺服器主動斷線↑NAT、路徑/設備/伺服器
精確的週期(整點、N 分鐘)排程工作、備份、GC、活動建立該排程的單位

從圖表形狀找原因

只要知道監控圖表是什麼形狀,候選原因就會大幅減少。下面 13 種形狀,各自彙整了會造成該形狀的原因。原因卡片上的小圖也是同樣的形狀。實線是主要觀察的指標,虛線是一起觀察的指標(人數、等待、錯誤等),淺色虛線是平時的水準。

偶爾隨機飆高

沒有固定間隔,不規則地往上衝,隨即回落。

畫格時間尖峰, 過度外插(dead reckoning), 用戶端預測不一致, 固定時間步長追趕失控, 背景處理程序占用 CPU, 用戶端記憶體不足、swap, 網路卡省電、驅動程式問題, Wi-Fi 干擾與訊號減弱, 5G↔LTE 頻繁切換(5G 涵蓋邊緣), 線路品質不良, Ring buffer 不足, 虛擬化額外開銷與 noisy neighbor, Kernel socket 緩衝區不足, CPU steal(虛擬機器), 記憶體回收與壓縮(compaction)造成的暫停, 系統時鐘跳動(NTP step), 慢速用戶端造成的阻塞式傳送, 可靠 UDP 的重傳設定, RST 強制關閉造成最後的資料遺失, 遊戲執行緒上的同步呼叫, 同步寫入 log, 伺服器端的延遲載入, DB 死結, Redis 慢指令, log 與監控過載, Lockstep 中等待最慢的玩家, Rollback netcode 預測失敗, 沒有時間戳記、一到就播放, 過於嚴格的伺服器驗證, 指令同步的路徑計算不一致, 視野登錄順序錯亂, 基準快照遺失, 消失通知遺失(幽靈物件), 物件 ID 重複使用造成誤認, 延遲飆升造成的不必要重傳

隨人數/負載上升

同時上線人數或聚集在同一處的人數增加時,會以比人數更陡的斜率跟著上升。

大量角色同畫面的渲染負載, 主執行緒封包處理瓶頸, 接收緩衝區溢位, 同一台裝置上的其他 App 占用頻寬, Bufferbloat(分享器佇列), 交換器 microburst, 執行緒過多與 context switch, 容器 CPU 節流(CFS 配額), UDP 封包的 IP 分段, 阻塞式 I/O 架構, 超出 tick 預算, 視野(AOI)計算量暴增(N²), 廣播量暴增, 單執行緒區域過載(熱點), 鎖競爭, 尋路運算暴增, 序列化與壓縮的成本, 集中在單一目標的戰鬥(世界王), 記憶體配置暴增, 熱點資料列鎖定競爭, 複寫延遲, 經由閘道/proxy, 切換 zone(跨伺服器轉移), 各連線的傳送預算與優先順序, 突發傳送造成淺緩衝區溢位, 雙工模式不一致

碰到上限後持平

處理量或連線數到達某個值後就上不去,從那時起等待與錯誤開始增加。

顯示記憶體(VRAM)不足, 分享器效能不足、過熱, 電信業者限速與流量管理, DDoS 造成共用線路飽和, 防火牆 session 表飽和, 雲端 NAT 閘道的連線與 port 上限, 資料中心線路飽和, NIC 中斷集中在單一核心, 超過雲端 PPS 上限, NIC 頻寬飽和, 檔案描述子上限, 伺服器 conntrack 表飽和, 伺服器間連線的臨時 port 耗盡, 訊息佇列積壓, 執行緒池耗盡, GC thrashing(heap 可用空間不足), 雲端磁碟 burst credit 耗盡, IOPS 上限與佇列飽和, 連線池耗盡, 連鎖故障, 登入排隊上限與重新連線寬限不足, 記憶體/VRAM 不足導致串流失敗, Policer 丟棄超額流量, 接收端伺服器主機丟棄封包, 防火牆與連線追蹤丟棄封包, 中間設備超過處理上限(防火牆、IPS、DDoS 防護)

一開始就一直偏高

持續停在高檔,不會突然飆高。原因來自距離、路由、設計這類結構性因素。

沒有內插緩衝或緩衝太短, 垂直同步(V-Sync)與渲染佇列, 計時器解析度, 顯示器、輸入裝置與畫格生成的延遲, 傳播延遲(物理距離), 繞遠路的路由, 中斷合併過度, GRO/LRO 合併等待延遲, 伺服器電源管理(C-state、頻率調整)造成延遲飆高, Nagle 演算法 + 延遲 ACK, 快取未命中, HDD 尋軌延遲, 伺服器回應後才演出(請求-回應方式), 依序往返過多的協定(chatty), 沒有技能預輸入, 用戶端權威, 雙重 tick 等待, 快照傳送頻率太低, 封包亂序造成的不必要快速重傳, RTO 設定不符合環境, 中間設備移除 TCP 選項

只有部分偏高

大部分正常,只有特定玩家、地區、電信業者或裝置特別高。

儲存裝置太慢,資源串流跟不上, 用戶端閃退, 安全軟體的封包檢查, overlay 程式干擾, RRC 狀態切換延遲(行動網路無線省電), 行動網路訊號弱、收訊死角, 公共 Wi-Fi/公司網路限制, 衛星網路(低軌/同步軌道), ECMP 其中一條路徑異常, 國家/電信業者層級的 UDP 限制與封包檢測, DNS 故障與延遲, 經由 VPN/遊戲加速器, 負載平衡器分配不均與健康檢查誤判, 纜線不良與 port 錯誤, MTU 不一致(只有大封包消失), 慢速用戶端(slow consumer)的處理策略, keepalive 預設值 2 小時, 閒置後的慢啟動(slow start), SO_REUSEPORT 分配不均, NUMA 遠端記憶體, 巨集/機器人過多, 配對與區域分配錯誤, 被 ping 吃掉的短判定區間, 沒有延遲補償的判定, 延遲補償過度, 主機(房主)架構, 先行演出後遭伺服器拒絕, 慢的人在別人畫面上快轉移動, 一到就處理的伺服器造成快轉, 各玩家的輸入緩衝大小, 一個慢的隊友與王的機制, 怪物控制權在慢速用戶端上, 特定角色的資料過於龐大, 頻道、實例、相位不同, 載入中抵達的出現通知被丟棄, 固定 UDP port 衝突, 以 IP/裝置區分 session 的 bug, 多開限制, 快取/資源檔案同時存取衝突, 顯示選項不同, 用戶端版本、資料不一致, 時鐘推估誤差導致物件暫緩顯示, 無線區段的封包遺失, 實體層錯誤(線材、光模組、接頭不良), MTU 黑洞(只有大封包反覆遺失), ACK 延遲或消失(上傳飽和)

連線同時大量中斷

連線數驟降,或斷線次數瞬間暴增。

手機 App 切到背景, Wi-Fi ↔ LTE/5G 切換, NAT mapping 過期, 電信業者共用 IP(CGNAT), 負載平衡器閒置逾時, 雲端安全群組的連線追蹤過期, 網路設備容錯移轉(failover), OOM killer, Windows UDP socket 的 WSAECONNRESET 錯誤, 死結, 伺服器當機, 無窮迴圈、邏輯失控, 寫入 core dump, DB 容錯移轉, 存檔週期過長造成的進度遺失, 附屬伺服器故障, 部署與重新啟動, TLS 憑證過期/設定錯誤, 連線途中 NAT 或負載平衡器的 mapping 過期

不靠遊戲程式碼能確認多少

我們統計了每個原因最容易的確認方式。基礎設施工具指可以用 OS、網路、雲端、DB 工具與執行環境的啟動選項(GC log 等)確認,不必修改遊戲程式碼。遊戲 log 與指標指 tick 時間、斷線原因這類必須由遊戲記錄下來才看得到的東西。這一欄越多的層,越有理由請開發團隊加上量測。

如何解讀數字

平均值會掩蓋飆高。每秒 20 tick 的伺服器,即使只有 1% 的 tick 變慢,大約每 5 秒就會讓所有人頓一下,但平均 tick 時間幾乎不變。所以要一起看百分位數。p50(中位數)是一半樣本比它快的值,p99 是 100 次中最慢那 1 次附近的值。玩家記得的「lag」,通常是 p99 那一端。

彙總間隔也會掩蓋飆高。在 1 分鐘平均的圖表上,1 秒的停頓會被稀釋成 1/60。找停頓時,要一起看同一張圖表的最大值或 p99,或是更短的間隔。

抖動(jitter)是封包抵達間隔的變動程度。即使平均 ping 很低,抖動大時內插緩衝也會被耗空,出現卡頓、瞬移。

測量方法測量什麼注意事項
ping (ICMP)到設備的往返時間路由器、伺服器可能延後處理 ICMP 回應或限制數量,結果可能與遊戲封包不同。被阻擋時完全沒有回應
mtr·traceroute各躍點的延遲與遺失只有中間某台設備遺失率高、之後的躍點正常時,很可能只是那台設備減少了 ICMP 回應。只有一路延續到終點的遺失才是真正的遺失
TCP RTT(ss -ti 的 rtt)kernel 對每條連線測得的往返時間是實際遊戲連線的數值,所以最可信。可在伺服器端逐一查看每位玩家
遊戲內 ping遊戲用自己的訊息測得的往返時間在遊戲迴圈內測量時,會混入畫格、tick 的等待時間。即使線路正常,伺服器或電腦忙碌時也會升高

現在就能做的事、要加進遊戲程式碼的事

不需遊戲程式碼
  • 加上維度:在連線 log 與負載平衡器 log 的用戶端 IP 上標註國家、電信業者(ASN),讓「只有海外」、「只有特定電信業者」浮現出來。
  • 連線品質:在伺服器上用 ss -ti 或 eBPF 工具收集每條連線的 RTT 與重傳,依 ASN 分組查看。
  • 從伺服器外看伺服器內部:socket 佇列、各執行緒 CPU(pidstat -t)、run queue 延遲、只需啟動選項就能開啟的 GC log。
  • 路徑測量:從目標國家、電信業者端進行合成監控(RIPE Atlas、雲端區域中的測量用伺服器)與 mtr。
  • 變更紀錄:把部署、更新、設定、網路作業以垂直線標在所有圖表上。這是判定「更新後」的起點。
只需少量遊戲程式碼
  • 用戶端摘要回報:每 30~60 秒回報 RTT p50 與 p95、抖動、遺失、FPS、畫格尖峰次數、版本、伺服器與頻道。
  • 伺服器 tick 指標:tick 時間 p50 與 p99、tick 超出預算次數、各 zone 人數、每條連線的傳送佇列。
  • 斷線原因代碼:心跳逾時、RST、伺服器主動斷線、驗證失敗、維護,在兩端使用相同代碼。
  • Session ID 與時間:所有 log 都記錄 session、角色、伺服器 ID 與同步過的 UTC 時間。
  • 回報 lag 按鈕:連同 session ID 上傳最近 60 秒的 RTT、FPS、tick 空白。
T3工具

案例與處理流程

彙整了兩種常見情境的確認順序,以及原開發商、營運商親自公開的實際事故案例。每個步驟與案例都連到相關的原因卡片。

各情境處理流程

更新後 lag

特定更新或部署之後,lag 回報增加時。適用於「這次更新後就怪怪的」這類回報集中出現,或圖表從某個時間點起階梯式上升並維持不降的情況。

  1. 確定開始時間,收集前後所有變更: 找出回報最初集中的時間與圖表階梯式上升的時間,把前後發布的變更全部列出來。用戶端更新、伺服器部署、設定變更、DB schema 變更(DDL)與重新啟動、網路與防火牆作業、基礎設施更換(執行個體類型、kernel、驅動程式)都要一起看。每次部署時用監控工具的註記(annotation)功能在所有圖表上留下垂直線,這一步很快就能完成。遊戲更新與基礎設施作業若在同一個維護時段發布,兩者都要留作候選。優先聯絡:發布變更的遊戲開發團隊與基礎設施團隊雙方。
  2. 切分範圍:版本、裝置、伺服器、地區: 先看異常集中在哪個面向。只有新版本的使用者有問題時懷疑用戶端;只有特定 OS、顯示卡、裝置有問題時懷疑用戶端效能或驅動程式;只有特定伺服器、頻道、zone 有問題時懷疑伺服器;只有特定國家、電信業者有問題時懷疑網路路徑;所有人同時出問題時,先懷疑共用資源(DB、負載平衡器、閘道)或剛發布的伺服器部署。用戶端遙測資料若有版本號,就把舊版本與新版本的 ping、FPS、畫格尖峰、斷線次數並排比較。ping 不變、只有 FPS 變差時,比起網路,更可能是用戶端效能的問題。優先聯絡:集中在版本或裝置時找遊戲開發團隊(用戶端);集中在伺服器或頻道時,主機指標正常找遊戲開發團隊(伺服器),異常則找基礎設施團隊(伺服器設備/OS);集中在國家或電信業者時找基礎設施團隊(網路)。
  3. 在同一時段比較新版本與舊版本: 只比較部署前後,會混入星期、時段、活動造成的變化,讓判斷變得模糊。可能的話,先把新版本部署到部分伺服器(金絲雀),與同一時段的舊版本伺服器(對照組)並排比較 tick 時間 p50 與 p99、超出 tick 預算的次數、CPU、記憶體與錯誤率。如果已經全面部署,就與上週同一天、同一時段比較。只看整個伺服器的平均值,部分伺服器或 zone 的問題會被掩蓋,所以要按伺服器與 zone 分開看。優先聯絡:遊戲開發團隊(伺服器)。
  4. 比較前後的流量特徵: 即使不懂伺服器程式碼,也能用網路端看得到的數值,確認更新是否改變了流量的樣貌。比較前後的每位玩家每秒封包數(pps)與位元組數、平均與最大封包大小、連線數,以及每個 tick 一次送出的突發傳送量。UDP 封包開始超過路徑 MTU(通常是 1,500 位元組)時,就會發生 IP 分段。只要遺失一個分段,整個封包就遺失;也有 NAT 或防火牆會直接丟棄分段。途中經過 MTU 較小區段(通道、VPN)的玩家,只有大封包會消失。pps 增加時,要查是否碰到雲端執行個體的 PPS 上限,或防火牆、DDoS 防護設備的處理上限。優先聯絡:特徵有變時附上證據找遊戲開發團隊(伺服器);特徵不變、只有遺失與重傳增加時找基礎設施團隊(網路)。
  5. 比較前後 DB 查詢的種類與次數: DB 延遲升高時,先看查詢數(QPS)是否也一起升高。PostgreSQL 的 pg_stat_statements 與 MySQL Performance Schema 的 digest 彙總,會把只有值不同的查詢歸為同一類,統計執行次數與總時間;比較更新前後的前幾名查詢清單,就能找出新出現的查詢、次數增加好幾倍的查詢(N+1),以及沒用索引而讀取整張資料表的查詢(MySQL 看 SUM_NO_INDEX_USED 欄位)。優先聯絡:QPS 或查詢樣貌有變時找遊戲開發團隊(伺服器);查詢相同、只有延遲增加時找基礎設施團隊(DB:執行計畫、IOPS、鎖定)。
  6. 用主機與伺服器處理程序的指標切分層級: 不需要程式碼,用 OS 上看得到的數值區分問題在伺服器內部還是主機。伺服器 socket 的接收佇列(Recv-Q)堆積,表示伺服器處理程序沒有及時讀取(tick 停住、GC、鎖);只有一個執行緒 100% 是單執行緒瓶頸;GC log 的暫停時間變長,表示記憶體使用模式改變了。也要確認是否以調高的 log 等級部署,導致 log 寫入增加。反過來,CPU steal、CPU 節流、NIC 丟棄(drop)增加時,要查同一時間變更的基礎設施(執行個體類型、kernel、容器上限)。優先聯絡:處理程序內部的訊號找遊戲開發團隊(伺服器),主機的訊號找基礎設施團隊(伺服器設備/OS)。
  7. 還原以確認原因,並留下紀錄: 把最可能的變更只在部分伺服器或部分玩家身上還原(回復部署、關閉功能開關),或把設定改回之前的值,看症狀是否一起消失。只有還原的那一邊變好,原因就能確定。還原作業本身也可能因重新啟動與冷快取而暫時變慢,不急的話就在離峰時段進行。結果要連同原因 ID 記錄在事故紀錄中,並把封包大小、查詢數、tick 時間上限列入下次更新的部署前檢查項目。優先聯絡:發布變更的團隊。

新增海外國家/地區

開放新的服務國家,或新增區域、資料中心時。開服前的檢查,以及分辨「國內沒問題,只有新國家的玩家 lag」這類回報時,都可以使用。

  1. 開服前測量當地各電信業者的路徑品質: 針對目標國家的主要電信業者(ASN),逐一測量到遊戲伺服器候選位置的往返時間(RTT)分布、抖動(jitter,封包抵達間隔忽長忽短)與遺失。單一平均值會掩蓋電信業者之間的差異,所以要看各電信業者的中位數與第 95 百分位數,並分成晚間尖峰與凌晨來看。公開量測網路 RIPE Atlas 可以指定國家與 ASN,從全球的探針(probe)發送 ping 與 traceroute;也可以在候選區域開臨時 VM 來測量。中間設備有時會限制 ICMP 回應,所以可能的話,也要用與遊戲相同的協定與 port 測量。只有特定電信業者特別繞經遠方城市時,就是 peering 或路由問題。電信業者比起延遲更重視成本,會選擇成本較低的路徑,所以近處也可能繞遠路。優先聯絡:基礎設施團隊(網路);路徑問題出在電信業者端時找外部(電信業者、IX)。
  2. 把測量值與遊戲設計能承受的上限比較: 把測得的 RTT 與抖動,拿來與遊戲的判定區間(閃避、格擋這類反應時間)、延遲補償上限、內插緩衝長度、輸入緩衝大小比較。例如格擋判定是 0.2 秒時,往返延遲加上內插緩衝超過這個值的電信業者用戶,即使及時反應也會太晚。若放寬延遲補償來配合,這次換成被打的一方回報「躲到牆後還被打到」的情況會增加。超過上限的電信業者很多時,基礎設施團隊要研究把區域或邊緣 PoP 設在更近的位置,遊戲開發團隊則要檢討判定、內插與延遲補償的數值。本白皮書的「同步方式」一章就是對照基準。優先聯絡:遊戲開發團隊(伺服器、用戶端:設計上限)、基礎設施團隊(網路:區域與 PoP 位置)。
  3. 確認 MTU 與 UDP 能否通過: 確認遊戲最大的封包能否在當地網路完整通過。以不同大小發送設定了禁止分段(DF)標記的 ping 來測量路徑 MTU,查看是否有 PPPoE、通道、行動網路這類小於 1,500 位元組的區段。UDP 這類資料包(datagram)傳輸的標準(RFC 8899)建議 IPv4 以 1,200 位元組作為大多數路徑都能通過的基本大小,遊戲的最大封包若大於這個值,要與遊戲開發團隊決定縮小或分開傳送的做法。也要確認公共 Wi-Fi、公司網路與部分電信業者是否封鎖 UDP 或遊戲 port,或限制速度,並查看被封鎖時有沒有替代路徑(TCP、443 port)。優先聯絡:基礎設施團隊(網路)與遊戲開發團隊(伺服器:封包大小)。
  4. 測量 NAT、CGNAT 的閒置逾時,調整心跳封包間隔: 測量當地家用分享器與行動網路(CGNAT)過多久會刪除閒置 UDP 連線的 mapping(位址與 port 的對應紀錄)。每次測試時,先從測試裝置送一個封包到伺服器建立 mapping,之後裝置不再送任何東西,讓伺服器在設定的時間(30 秒、60 秒、120 秒……)過後送封包到裝置。裝置開始收不到這個封包的時間,就是這個網路的閒置逾時。標準(RFC 4787)規定 UDP mapping 的過期時間不得短於 2 分鐘,並建議預設 5 分鐘以上,但各設備的數值差異很大,也有刪得更快的設備。mapping 只有靠從裝置送出的封包才能確實更新,所以心跳封包要由用戶端送出,並確認間隔不超過測得值、負載平衡器與雲端安全群組閒置逾時之中最短值的一半。優先聯絡:遊戲開發團隊(用戶端:心跳封包間隔;伺服器:逾時值)、基礎設施團隊(負載平衡器與安全群組設定)。
  5. 確認在當地會經過的外部服務與安全設備: 確認當地的平台登入、付款、身分驗證是否以正常速度回應,當地 DNS 能否正確解析登入與更新伺服器的位址,CDN 是否從靠近該國的據點提供更新檔。查看 DDoS 防護與防火牆的國家封鎖規則與速率限制是否擋到新國家的 IP 網段,特別是多位用戶共用一個 IP 的 CGNAT 網段是否被整批封鎖。優先聯絡:基礎設施團隊(安全設備、DNS、CDN)、外部(平台、金流業者、電信業者)。
  6. 開服後按國家與 ASN 分開看: 在連線 log 與負載平衡器 log 的用戶端 IP 加上國家與 ASN,按國家與電信業者查看 RTT、重傳、斷線次數與原因(心跳封包逾時、RST、伺服器踢出)。可以用 MaxMind GeoLite ASN 這類免費資料庫把 IP 轉換為 ASN 與組織名稱,並依當地個資法規,把 IP 縮減為 /24 或 ASN 單位再保存。只集中在一個 ASN 時,先查該電信業者的路徑(基礎設施團隊、外部);整個新國家都差時,先查距離與設計上限(基礎設施團隊、遊戲開發團隊);只有晚間變差時,先查 peering 壅塞。如果只有部分玩家 ping 一直偏高,要與遊戲開發團隊(伺服器)一起確認是否因 GeoIP 錯誤、VPN、以隊長為準的分配而被分到遠方區域。合成監控正常、只有玩家端不好時,問題在玩家環境或用戶端。
  7. 確認遠地玩家對其他玩家造成的影響: 遠地連線的玩家增加時,影響不只是那個人的畫面變差而已。延遲高的玩家,輸入會集中抵達,在其他人的畫面上只有那個角色以快轉的方式移動,還會被伺服器的速度與冷卻檢查擋下,出現拉回或技能被拒絕。在隊伍機制中,一個延遲高的玩家反應太晚就會讓整個隊伍失敗;在 lockstep 方式中,所有人都要等最慢的那個人。新國家開服後,要看既有玩家「只有特定角色看起來怪怪的」這類回報是否增加,並與遊戲開發團隊決定輸入緩衝、驗證容許值與配對地區分離。優先聯絡:遊戲開發團隊(伺服器)。

實際事故案例

只挑選遊戲公司與基礎設施公司自行公開的事後檢討(postmortem)。摘要只寫原文揭露的內容,詳細經過請看原文。

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 搜尋目標的成本。放慢遊戲時間的設計無法消除過載,但能讓所有人以相同速度變慢,避免只有部分行動無止盡地積壓。 原文

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(網際網路交換中心),以及選擇伺服器位置。電信業者端的路由政策需要與外部(電信業者)協商。這個案例也顯示,光是把伺服器移到靠近玩家分布中心的位置,效果就很大。 原文

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 之間的分散部署之前,先加上連線集中警示。 原文

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:自動容錯移轉)。大量警示湧入時,很容易先懷疑最近遇過的問題(例如攻擊),所以要依判定順序(範圍 → 時間點 → 層級)逐一排除。重新啟動後,也要一併確認登入排隊是否依設定限制湧入量。 原文

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%。 原文

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 伺服器擴充則由基礎設施團隊協同處理。重新連線寬限時間留得充裕,就能減少玩家線路短暫中斷演變成失去排隊順位的情況。 原文

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),並調整優先順序,讓單一據點無法把其他據點的流量拉走。 原文

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)下載。 原文

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 與網路;復原時分階段提高負載,避免重新連線一次湧入。 原文

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 錯誤率與執行個體啟動失敗。主要負責單位是外部(雲端供應商);遊戲開發團隊要為所有重試加上隨機間隔的指數退避與次數限制,基礎設施團隊則要準備好即使無法擴充也撐得住的餘裕容量,以及其他區域的備案。 原文

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 營運者、電信業者);遊戲開發團隊(用戶端)若把名稱解析失敗與其他錯誤分開提示,客服就能立刻判定。 原文

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 錯誤率、執行個體啟動失敗,以及負載平衡器的健康目標數。主要負責單位是外部(雲端供應商),基礎設施團隊要限制因健康檢查失敗而一次被移出的伺服器數量,並準備其他區域的備案。 原文

T4工具

Lag 回報指南

遊戲開發團隊與基礎設施團隊在找原因時,最花時間的就是查出「何時、何地、誰」。填好下面的項目,就能直接在 log 與圖表中找到那個時刻。

T5工具

名詞解釋

這些是和遊戲開發團隊、基礎設施團隊溝通時常出現的詞。可以在搜尋框輸入中文或英文試試。

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 的技術。畫面會更流暢,但從輸入到畫面的延遲可能增加。
T6工具

參考文獻

這是本白皮書中數值、預設值、行為說明的依據。只收錄標準文件(RFC),kernel 與 OS 文件,雲端、引擎、DB 官方文件,演講與論文等可信的資料。原因卡片與各章末尾的「出處」也連到同樣的資料。版本改變時預設值也可能改變,實際套用前請確認所用版本的文件。

資料 616 筆,發行者 83 家。清單請見純文字版的參考文獻。