Stories

Detail Return Return

PostgreSQL 18 異步 I/O(AIO)調優指南 - Stories Detail

導語:PostgreSQL 18 中最大的變化是引入了異步 I/O (AIO) 子系統,這引出了一個問題:如何根據工作負載調整它?Tomas Vondra 這篇博客提供瞭如何設置 AIO 配置,並根據你的工作負載進行測試的實用指南。

PostgreSQL 18 已正式發佈,該版本包含大量改進。其中一項重大架構變更是異步 I/O(Asynchronous I/O,簡稱 AIO) ——它支持對 I/O 操作進行異步調度,使數據庫能更好地控制存儲資源,同時提升存儲利用率。

本文不會詳細解釋 AIO 的工作原理,也不會展示詳盡的基準測試結果。本文的核心目標是分享 PostgreSQL 18 中 AIO 的調優建議,並解釋其中一些固有的但卻不顯而易見的權衡關係與限制。

理想情況下,這些調優建議應被納入官方文檔,但這需要基於實踐經驗形成明確共識——而 AIO 作為全新特性,目前尚缺乏足夠的生產環境驗證數據。儘管在開發階段我們已開展了大量基準測試,並據此設定了默認參數,但這無法替代實際生產系統的運行經驗。因此,本文將基於個人經驗,探討如何(可能)調整默認參數,以及在此過程中需權衡的因素。

io_method / io_workers

有一系列與 AIO(或廣義上的 I/O)相關的參數。但您可能只需要關注 Postgres 18 中引入的這兩個:

  • io_method = worker (options: sync, io_uring)
  • io_workers = 3

其他參數(如 io_combine_limit)都有合理的默認值。對於如何調整它們,我沒有太好的建議,所以暫時保持默認即可。在本文中,我將重點討論這兩個重要的參數。

io_method

io_method 決定了 AIO 實際處理請求的方式——由哪個進程執行 I/O,以及 I/O 是如何被調度的。它有三個可能的值:

  • sync - 這是一個"向後兼容"選項,在支持的情況下使用 posix_fadvice 進行同步 I/O。這會將數據預取到頁面緩存中,而不是共享緩衝區裏。
  • worker - 創建一個"I/O 工作進程"池來執行實際的 I/O。當一個後端進程需要從數據文件中讀取一個塊時,它會將一個請求插入到共享內存中的隊列裏。一個 I/O 工作進程被喚醒,執行 pread 操作,將數據放入共享緩衝區,並通知後端進程。
  • io_uring - 每個後端進程都有一個 io_uring 實例(一對隊列)並使用它來執行 I/O。與 worker 的不同之處在於,它不是直接執行 pread,而是通過 io_uring 提交請求。

默認值是 io_method = worker。我們確實考慮過將 syncio_uring 都設為默認值,但我認為 worker 是正確的選擇。它是真正"異步"的,並且隨處可用(因為這是我們自己的實現)。

sync 曾被視為一種"回退"選擇,以防我們在 beta/RC 階段遇到問題。但我們並沒有遇到問題,而且也不確定使用 sync 是否真的會有幫助,因為它仍然要經過 AIO 基礎設施。如果您希望模擬舊版本的行為,仍然可以使用 sync

io_uring 是一種流行的異步 I/O 方式(不僅僅是磁盤 I/O)。它非常出色,高效且輕量級。但它是 Linux 特有的,而我們需要支持眾多平台。我們本可以使用特定於平台的默認值(類似於 wal_sync_method),但這似乎帶來了不必要的複雜性。

注意: 即使在 Linux 上,也很難驗證 io_uring。一些容器運行時(例如 containerd)之前因為安全風險而禁用了 io_uring 支持。

沒有任何一個 io_method 選項是"普遍最優的"。總會存在某些工作負載下 A 優於 B,反之亦然。最終,我們希望大多數系統都能使用 AIO 並從中受益,同時我們希望保持簡單,所以選擇了 worker

💡建議: 我的建議是堅持使用 io_method = worker,並調整 io_workers 的值(如下一節所述)。

io_workers

Postgres 的默認配置非常保守。它甚至可以在像樹莓派這樣的小型機器上啓動。但另一方面,對於通常擁有更多 RAM/CPU 的典型數據庫服務器來説,這種保守配置的表現就很糟糕了。要在這樣的大型機器上獲得良好的性能,您需要調整一些參數(shared_buffers, max_wal_size 等)。

我希望我們能有一種自動化方法來為這些基本參數選擇"合適"的初始值,但這比看起來要困難得多。這在很大程度上取決於上下文(例如,同一系統上可能還在運行其他東西)。不過,至少現在有像 PGTune 這樣的工具,能為這些參數提供合理的推薦值。

這當然也適用於 io_workers = 3 這個默認值,它只創建 3 個 I/O 工作進程。對於擁有 8 個核心的小型機器來説,這可能沒問題,但對於 128 個核心的機器來説,這絕對是不夠的。

實際上,我可以通過一個基準測試的結果來證明這一點,這個測試是我為選擇 io_method 默認值而進行的。該基準測試生成了一個合成數據集,然後運行匹配部分數據的查詢(同時強制使用特定的掃描類型)。

注意: 該基準測試(連同腳本、大量結果和更詳細的解釋)最初在關於 io_method 默認值的 pgsql-hackers 郵件列表線程中分享。請查看該線程以獲取更多細節和其他人的反饋。展示的結果來自一台搭載 Ryzen 9900X(12 核/24 線程)和 4 塊 NVMe SSD(配置為 RAID0)的小型工作站。

以下是對比不同 io_method 選項查詢耗時的圖表 PDF 文件
1.jpg

每種顏色代表不同的 io_method 值(17 代表 "Postgres 17")。對於 "worker" 有兩種數據序列,對應不同數量的工作進程(3 和 12)。這是針對兩個數據集的:

  • uniform - 均勻分佈(因此 I/O 完全是隨機的)
  • linear_10 - 順序分佈帶有一點隨機性(不完美的相關性)

圖表顯示了一些非常有趣的現象:

  • 索引掃描io_method 沒有影響,這很好理解,因為索引掃描尚未使用 AIO(所有 I/O 都是同步的)。
  • 位圖掃描 : 行為更加混亂。worker 方法表現最好,但僅限於有 12 個工作進程時。使用默認的 3 個工作進程時,對於低選擇性的查詢,它的性能實際上很差。
  • 順序掃描 : 不同方法之間存在明顯差異。worker 是最快的,比 sync(和 PG17)快大約一倍。io_uring 介於兩者之間。

在縱軸(y 軸)採用對數刻度的圖表中 PDF 文件,使用 3 個 I/O 工作進程(io_workers=3)的 worker 模式在 bitmap 掃描(位圖掃描)場景下的性能劣勢更為明顯:
2.jpg

io_workers=3 的配置始終是最慢的(在線性圖表中幾乎難以察覺這一點)。

好的一面是,雖然 I/O 工作進程不是免費的,但它們的開銷也不算高。因此,即便工作進程數量偏多,通常也比數量不足要好。

未來,我們可能會根據需求啓動/停止工作進程,使其變得"自適應"。這樣我們就能始終保持最優的進程數量。目前甚至已經有一個進行中的補丁,但它未能納入 Postgres 18。

建議: 考慮增加 io_workers。目前尚無理想的推薦值或計算公式,或許設置為核心數的 1/4 是個可行的選擇?

權衡

放之四海而皆準的最優配置是不存在的。我曾見過"使用 io_uring 以獲得最高效率"的建議,但前面的基準測試清楚地表明,對於順序掃描,io_uring 明顯比 worker 慢。

別誤會,我本身認可 io_uring,它確實是一個出色的接口,而且上述建議也並非"錯誤"。任何性能調優建議本質上都是一種簡化表達,總會存在與之相悖的情況。現實世界從不像建議描述的那樣簡單:這類建議的核心意義,就在於用一條簡潔的規則,掩蓋背後繁雜的複雜細節。

那麼,這些異步 I/O(AIO)方式之間,究竟存在哪些權衡與差異呢?

帶寬

io_uringworker 之間的一個重大區別在於任務的執行位置。對於 io_uring,所有任務都在後端進程內部執行;而對於 worker,這些任務會在獨立的進程中進行。

這可能會對帶寬產生一些值得關注的影響,具體取決於處理 I/O 的開銷大小。而這個開銷可能相當高,因為它涉及:

  • 實際的 I/O 操作
  • 校驗和驗證(在 Postgres 18 中默認啓用)
  • 將數據複製到共享緩衝區

對於 io_uring,所有這些都發生在後端進程本身。I/O 部分可能更高效,但校驗和驗證與內存複製(memcpy)這兩個步驟卻可能成為性能瓶頸。對於 worker,這項工作實際上在工作進程之間分配。如果您有 1 個後端進程和 3 個工作進程,限制就提高了 3 倍。

當然,反之亦然。如果有 16 個連接,那麼對於 io_uring,就是 16 個進程可以驗證校驗和等等。對於 worker,限制就是 io_workers 設置的值。

這就是我建議將 io_workers 設置為核心數約 25% 的原因。我認為還可以設得更高,可能達到每個核心一個 I/O 工作進程。無論如何,3 看起來明顯太低了。

注意: 我相信這種將開銷分散到多個進程的能力,是 worker 在順序掃描上優於 io_uring 的原因。在本次基準測試中,約 20% 的差異對於校驗和內存複製來説似乎是合理的。

信號

另一個重要的細節是後端進程與 I/O 工作進程之間進程間通信的開銷,這是基於 UNIX 信號的。一次 I/O 操作的執行流程如下:

  1. 後端進程將一個讀取請求添加到共享內存的隊列中
  2. 後端進程向一個 I/O 工作進程發送信號,將其喚醒
  3. I/O 工作進程執行後端請求的 I/O,並將數據複製到共享緩衝區
  4. I/O 工作進程向後端進程發送信號,通知其 I/O 已完成

在最壞情況下,這意味着每處理一個 8KB 大小的數據塊,就需要完成一次 “雙向信號傳輸”(共 2 次信號交互)。問題在於,信號傳輸並非 “零成本”—— 一個進程每秒能處理的信號數量是有限的。

我寫了一個簡單的基準測試,用於測試兩個進程之間的信號傳遞性能。在我的機器上,測試結果顯示能達到每秒 25 萬至 50 萬次往返通信。如果每個 8KB 的數據塊都需要一次往返通信,這意味着傳輸速度僅為 2-4GB/s。這並不算快,尤其是考慮到數據可能已經在頁面緩存中,而不僅僅是從存儲中讀取的冷數據。根據一項從頁緩存複製數據的測試,一個進程可以達到 10-20GB/s 的速度,大約是信號傳遞方式的 4 倍。顯然,信號可能會成為一個性能瓶頸。

注意: 具體的限制因硬件而異,在老舊的機器上可能會低得多。但在我能訪問的所有機器上,這個普遍觀察結果都成立。

不過好消息是,這隻會影響"最壞情況"的工作負載,即需要逐個讀取 8KB 頁面。大多數常規工作負載並非如此。後端進程通常會在共享內存中找到很多緩衝區(因此不需要 I/O)。或者,由於預讀,I/O 以更大的塊發生,這將信號開銷分攤到了多個數據塊上。因此,我不認為這會成為一個嚴重的問題。

在關於索引預取的郵件列表線程中,有關於 AIO 開銷(不僅僅是由於信號)的更長時間討論。

關於異步 I/O(AIO)的開銷(不僅限於信號帶來的開銷),在 “索引預取”的郵件列表線程中還有更深入的探討。

文件限制

io_uring 無需任何進程間通信(IPC),因此它不受信號開銷或類似問題的影響。但 io_uring 同樣存在限制,只是限制點有所不同。

例如,每個進程都會受到 “進程級帶寬限制”(比如單個進程最多能執行多少內存複製操作)。但根據頁面緩存測試判斷,這些限制相當高——大約 10-20GB/s。

另一個需要考慮的問題是,io_uring 可能需要相當多的文件描述符。正如這個 pgsql-hackers 線程中所解釋的:

問題在於,使用 io_uring 時,我們需要為每個可能的子進程創建一個文件描述符(FD),以便一個後端進程可以等待由另一個後端進程發起的 I/O 完成。這些 io_uring 實例需要在主進程中創建,以便所有後端進程都能訪問。顯然,如果 max_connections 設置得較高,這有助於更快地達到未調整的軟性 RLIMIT_NOFILE 限制。

因此,如果您決定使用 io_uring,您可能也需要調整 ulimit -n

注意: 這並非 Postgres 代碼中您可能遇到文件描述符限制的唯一地方。大約一年前,我提了一個與文件描述符緩存相關的補丁構想。每個後端進程最多保持 max_files_per_process 個打開的文件描述符,默認情況下該 GUC 設置為 1000。這在過去是足夠的,但在使用分區(或按租户的模式)的情況下,很容易觸發大量頻繁且開銷較高的打開/關閉調用。那是一個獨立但類似的問題。

總結

AIO 是 PostgreSQL 18 的一項重大架構變更,但目前仍存在侷限性:僅支持讀操作,部分操作仍依賴舊的同步 I/O 機制。這些限制並非永久性的,預計將在未來版本中逐步解決。

基於本文的分析,最終的 AIO 調優建議如下:

  1. 保留 io_method = worker 的默認值:除非通過基準測試證明 io_uring 對您的工作負載更優,否則不建議切換。僅在需要模擬 PostgreSQL 17 行為時使用 sync(即使這可能導致部分場景性能下降)。
  2. 根據 CPU 核心數調整 io_workers:建議從核心數的 25% 開始配置,在 I/O 密集場景下可嘗試提高至 100%。

若您在調優過程中發現有趣的結論,歡迎將其反饋給作者,更推薦發佈到 pgsql-hackers 郵件列表。這些經驗將幫助官方完善未來文檔中的調優建議。

原文鏈接:https://vondra.me/posts/tuning-aio-in-postgresql-18/

user avatar u_17470194 Avatar u_13482808 Avatar u_15214399 Avatar feibendemaojin Avatar weixiaodehai_cywv9b Avatar jueqiangdeqianbi Avatar
Favorites 6 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.