遊戲伺服器處於一個尷尬的交匯點。它們需要遊戲引擎的原始吞吐量——每幀更新數萬個實體。但它們也需要資料庫提供的功能:不會破壞狀態的交易、不會掃描一切的查詢,以及能從崩潰中恢復的持久性。

如今,遊戲伺服器團隊通常會選擇其中一邊,並為另一邊進行補救。為了速度採用實體-元件-系統(ECS)框架,並手動序列化到資料庫以實現持久性。或者為了安全採用資料庫,但在每次觸及遊戲狀態時都會遇到阻抗不匹配。

Typhon 融合了這兩種傳統。它是一個資料庫引擎,以遊戲引擎的方式儲存資料——並提供遊戲伺服器所需的保證。以下解釋為何這兩個世界並不像看起來那麼遙遠。

ECS 架構在遊戲引擎中演進。關聯式資料庫在企業軟體中演進。它們從未互相交流。但看看它們的成果:

ECS 的「原型」就是一個表。一個「元件」就是一個欄位。一個「系統」就是一個查詢。詞彙不同,底層結構相同。兩個領域,跨越數十年和產業界限,卻因解決相同的基本問題——在效能限制下管理結構化資料——而匯聚到結構上相同的解決方案。

這種匯聚是為何合成成為可能的原因。這並非偶然——而是由相同的物理定律驅動。資料必須為 CPU 快取佈局。存取模式必須是可預測的。延遲預算真實存在。

ECS 教會了資料庫界關於資料應如何儲存的重要一課。Typhon 直接從遊戲引擎架構中汲取了三點教訓:

預設的快取局部性。在傳統的列儲存中,讀取所有玩家位置意味著載入整個列——姓名、物品欄、生命值,所有的一切。其中大部分位元組都是浪費的。在 ECS 中,元件是按類型儲存的:所有位置連續儲存,所有生命值連續儲存。讀取 10,000 個位置是一個線性記憶體掃描,其中每個位元組都有用。

這比大多數開發者意識到的更重要。L1 快取命中約需 1 奈秒。DRAM 錯失約需 60-70 奈秒——懲罰高達 65 倍。當你的資料庫佈局迫使快取錯失時,再巧妙的演算法也無法挽救你。

零拷貝是預設,而非優化。在傳統資料庫中,讀取一個記錄意味著從儲存頁面反序列化到語言層級的物件。在 ECS 中,一個元件已經在記憶體中以其最終佈局呈現——你只需交還一個指標。Typhon 保留了這一點:元件是可直接繪製的非託管結構體,直接從固定記憶體頁面讀取。無需序列化,無需託管堆分配,無需 GC 介入。

實體作為純粹的身份。在 ECS 中,實體只是一個 ID——一個 64 位元數字,沒有內在結構。所有資料都儲存在外部的元件表中。這與 ORM 的思維方式相反,後者認為物件就是實體。Typhon 繼承了這一點:EntityId 是一個輕量級的值類型,所有狀態都儲存在類型的元件儲存中。這種分離使得其餘架構成為可能——每個元件的版本控制、每個元件的儲存模式、每個元件類型的獨立索引。

傳統資料庫解決了 ECS 從未面臨過的問題。Typhon 從資料庫架構中汲取了四項能力:

帶有每個元件 MVCC 的 ACID 交易。遊戲引擎通常沒有隔離性。兩個系統在同一幀中修改同一個實體是一個競爭條件——在單進程遊戲中,你可以控制執行順序,因此可以管理它。但在具有並發玩家會話的遊戲伺服器上,你無法做到。

資料庫數十年前就透過 MVCC 解決了這個問題:快照隔離,讀者永遠不會封鎖寫者,並在提交時進行衝突檢測。Typhon 引入了這一點——但有所不同。傳統資料庫對整個列進行版本控制。Typhon 單獨對每個元件進行版本控制。一個實體的 PositionComponent 和 InventoryComponent 各自維護其自己的修訂鏈:一個包含 12 位元組修訂條目的循環緩衝區,每個條目都標有 48 位元交易序號。

這意味著讀取玩家位置的交易可以同時看到所有元件類型的統一凍結時間點——而無需鎖定其中任何一個。寫者永遠不會封鎖讀者。而且由於修訂是按元件而非按實體進行的,更新玩家的位置不會為其物品欄創建新版本。複製的資料更少,需要收集的垃圾也更少。

索引選擇性存取。這是關鍵。ECS 系統每幀都會迭代所有符合元件簽名的實體。這對於粒子模擬非常有效,因為每個粒子都需要更新。但遊戲伺服器通常不需要所有這些:

當你處理 1-4% 的實體時,掃描所有實體的工作量是必要的 25-100 倍。ECS 框架意識到了這一點——Unity DOTS 添加了可啟用元件,Flecs 添加了 group_by,Unreal MassEntity 添加了 LOD 層級。這些都是針對同一底層問題的巧妙變通方法:ECS 是為批量迭代設計的,而不是選擇性存取。

資料庫透過索引解決了這個問題。B+樹用於基於值的查找,空間樹用於區域查詢,選擇性估計用於決定何時掃描與何時尋找。Typhon 將這些引入元件儲存模型——不是作為附加的變通方法,而是作為一等公民。

空間分割。特別是對於空間存取模式——遊戲伺服器中選擇性存取的第一大需求——Typhon 將一個兩層空間索引直接整合到元件儲存中:

這兩層都在與其他所有內容相同的交易模型內運行。沒有額外的空間雜湊與你的 ECS 並存。也沒有因為追蹤指標到單獨的資料結構而破壞快取局部性。

持久性。遊戲客戶端可以承受崩潰時的狀態丟失——重新載入關卡。但遊戲伺服器不能。玩家物品欄、經濟狀態、進度資料——所有這些都必須在進程重啟和崩潰後得以保留。基於 WAL 的崩潰恢復、檢查點、可配置的 fsync——這些是遊戲伺服器需要的資料庫基礎,但 ECS 框架從未提供。

查詢規劃。當你同時擁有索引和順序儲存時,需要有人決定使用哪種存取路徑。資料庫在成本基礎查詢優化方面有數十年的工作——選擇性估計、直方圖統計、索引選擇。Typhon 將查詢規劃器引入 ECS 世界:給定元件欄位的謂詞,它會根據估計的選擇性自動選擇全掃描或 B+樹尋找。

Typhon 並非用膠帶將 ECS 和資料庫概念黏合在一起。它將它們綜合成一個專為遊戲伺服器工作負載設計的單一模型。

Typhon 中的元件同時是 ECS 元件和資料庫綱要:

可直接繪製、非託管、固定大小、按類型連續儲存——這是 ECS 的一面。帶有自動 B+樹索引的類型化欄位(在標記的欄位上)——這是資料庫的一面。一個聲明,兩個世界。

查詢 API 使這種合成具體化:

ECS 風格的類型化元件存取。資料庫風格的謂詞過濾,帶有自動索引選擇。在具有快照隔離的交易中。查詢規劃器根據選擇性選擇掃描與 B+樹——開發者無需操心。

由於遊戲伺服器對不同操作有不同的持久性需求,Typhon 允許你按工作單元進行選擇:

延遲模式為位置更新提供遊戲引擎級別的提交延遲,這些更新可以在崩潰後重新模擬。即時模式為授予價值真實貨幣的稀有物品的交易提供資料庫級別的保證。遊戲伺服器按操作進行選擇——而不是全局統一。

遊戲伺服器不會以相同的方式處理所有資料。玩家位置每秒變化 60 次,並可在崩潰後重新模擬。物品欄變更很少見,但絕不能丟失。AI 運行時狀態——當前目標、威脅分數、尋路航點——每幀都會重新計算,並在重啟後毫無價值。

傳統資料庫以相同的方式處理所有資料。傳統 ECS 將所有內容保留在記憶體中,沒有持久性區別。Typhon 允許你按元件類型進行選擇:

SingleVersion 元件完全跳過修訂鏈開銷——沒有循環緩衝區,沒有每次寫入的分配。它們透過 DirtyBitmap 來追蹤變更:每個實體一個位元,寫入時翻轉,在幀邊界掃描。這就是遊戲引擎追蹤變更的方式,也是適合每幀更新資料的模型。

Versioned 元件獲得完整的 MVCC 和快照隔離——讀者看到一致的歷史狀態,寫者不封鎖讀者,提交時檢測到衝突。這就是資料庫保護關鍵資料的方式,也是適合絕不能損壞的資料的模型。

Transient 元件根本不觸及磁碟——沒有 WAL,沒有檢查點,沒有恢復。純記憶體儲存,具有與其他所有元件相同的查詢和索引 API。每幀重新計算的 AI 黑板資料沒有必要支付持久性開銷。

相同的引擎,相同的交易 API,但儲存層執行每個元件類型所需的確切操作。這就是「專為遊戲伺服器打造」的實際意義。

在 ECS 中,「系統」每幀運行,處理所有匹配的實體。在資料庫中,「物化視圖」維護一個快取的結果集並逐步更新它。Typhon 的視圖兩者兼具:

初始的 ToView() 運行一個完整查詢。之後,Refresh() 會排空一個無鎖環形緩衝區中的變更,這些變更由提交路徑推送——只有索引欄位實際發生變更的實體才會被重新評估。如果 100,000 個實體符合你的視圖,但自上次刷新以來只有 12 個發生了變更,那麼你將執行 12 次評估,而不是 100,000 次。

這就是從資料庫角度解決的迭代一切問題:不要重新掃描,追蹤增量。

專為遊戲伺服器優化意味著需要放棄一些東西。

僅限可直接繪製的元件。元件內不允許使用 string、object 參考或變長陣列。文字使用 String64 等固定大小的類型。這是零拷貝讀取和快取友好儲存的代價——這是遊戲開發者從 ECS 框架中已經熟悉的限制。

以實體為中心的關係,而非 SQL JOIN。Typhon 支援導航連結、1:N 和 N:M 關係——但它們遵循實體參考,更接近圖資料庫而非傳統 SQL 資料庫。這符合遊戲伺服器對資料的自然思考方式(一個實體擁有元件,一個公會包含成員),但如果你的思維模式是 SELECT ... FROM a JOIN b ON a.x = b.y,那麼這是一個不同的範式。

程式碼中的綱要,而非 SQL。元件是帶有屬性的 C# 結構體,而非 DDL 語句。對遊戲開發者來說很自然,對資料庫管理員來說是陌生的領域。如果你的團隊習慣於 SQL,這是一個範式轉變。

在下一篇文章中,我將深入探討使這一切真正快速的效能哲學——資料導向設計、快取線感知和零分配熱路徑。這些原則讓託管語言能夠實現微秒級延遲的交易。