AIO fsync 可以提高 dpkg 性能嗎?
Debian 軟體包管理器能否
dpkg
通過使用 AIO fsync() 操作之一而不是 sync_file_range() + fsync() 來獲得顯著的性能改進?這
$$ proposed $$fsync2() API 本質上與現有的 AIO_FSYNC/AIO_FDSYNC API 相同,只是它是同步的,這是應用程序想要避免的。 我被提出反對的唯一論點
$$ using $$AIO_FSYNC 是“實現只是一個工作隊列”,這在很大程度上是沒有意義的,因為它獨立於文件系統實現,但允許對發出的所有 fsync 操作進行自動核心端並行化。這允許文件系統在完成並發 fsync 操作時自動優化不必要的日誌寫入 - 當使用者應用程序從許多程序/執行緒同時執行 fsync() 時,XFS、ext4 等已經這樣做了….. 這個簡單的實現允許在 XFS 上執行簡單的“使用 aio fsync 解壓”工作負載(即“在我們進行時分批寫入許多 4kB 文件和 aio_fsync(),在我們分派新批次之前退出已完成的 fsync()”)工作負載大約 2000 個文件/秒(同步寫入 IO 延遲限制)到超過 40,000 個文件/秒(寫入 iops 限制在後端儲存上)。
apt-get install
範例工作負載與or有相似之處dpkg -i
(部分取決於已安裝包中文件的大小 :-)。dpkg
必須有效地 fsync() 所有解壓縮的文件,然後才能將它們重命名到位。
dpkg
已根據 Ted T’so 的建議進行了優化。優化是在某些點添加對 sync_file_range() 的呼叫。此系統呼叫不提供與 fsync() 相同的保證。請閱讀sync_file_range()的文件並註意突出的警告:-)。這些操作都不會寫出文件的元數據。因此,除非應用程序嚴格執行對已經實例化的磁碟塊的覆蓋,否則無法保證數據在崩潰後仍然可用。
dpkg
在寫入每個文件後立即觸發數據回寫,使用SYNC_FILE_RANGE_WRITE
. 它首先寫入包的所有文件。然後第二次遍歷文件,等待數據寫回使用SYNC_FILE_RANGE_WAIT_BEFORE
,呼叫fsync()
,最後將文件重命名到位。查看送出:
我的假設是並行化 fsync() 操作可以提高性能,因為它允許更有效地批處理元數據寫入,特別是批處理關聯的屏障/磁碟記憶體刷新,這是確保磁碟上元數據始終保持一致所必需的。
編輯:似乎我的假設太簡單了,至少在使用 ext4 文件系統時:
帶有操作的第二系列 sync_file_range() 呼叫
SYNC_FILE_RANGE_WAIT_BEFORE
將阻塞,直到先前啟動的寫回完成。這基本保證了延遲分配已經解決;也就是說,數據塊已經分配和寫入,inode 更新(在記憶體中),但不一定推送到磁碟。這
$$ fsync() $$呼叫實際上會強制 inode 到磁碟。 對於 ext4 文件系統,第一個$$ fsync() $$實際上會將所有 inode 推送到磁碟,以及所有後續$$ fsync() $$呼叫實際上是無操作的(假設文件“a”、“b”和“c”都在同一個文件系統上)。但這意味著它將(重量級)jbd2 送出的數量降至最低。 它使用特定於 linux 的系統呼叫 — sync_file_range() — 但結果應該是所有文件系統的整體性能更快。所以我不認為這是特定於 ext4 的 hack,儘管它可能確實使 ext4 比任何其他文件系統更快。
——特德·索
其他一些文件系統可能會受益於使用 AIO fsync() 操作。
bcachefs
(正在開發中)聲稱比 ext4 更好地隔離不同文件之間的 IO。所以測試起來可能特別有趣。聽起來 ext4 可能沒有針對純 AIO fsync() 模式進行如此優化(我猜其他文件系統也可能具有相同的約束)。如果是這樣,我想可以先執行所有相同的 sync_file_range() 呼叫,然後將所有 AIO fsync() 操作作為第二輪開始,最後將所有文件重命名為 fsync()操作完成。
老的:
這種調查的第一步應該是測量:-)。
可以禁用 fsync() 部分,使用
echo "force-unsafe-io" > /etc/dpkg/dpkg.cfg.d/force-unsafe-io
.到目前為止,我嘗試
apt-get install
在strace -f -wc
Debian 9 容器中執行。例如aptitude
使用“unsafe io”安裝包,只有 495 個同步 fsync() 呼叫。正常安裝aptitude
時,有 1011 個 fsync() 呼叫。“unsafe io”也禁用了SYNC_FILE_RANGE_WAIT_BEFORE
呼叫,將 sync_file_range() 呼叫的數量從 1036 減少到 518。然而,這是否減少了平均花費的時間還不太清楚。如果確實如此,它似乎並沒有超過執行之間的隨機變化。到目前為止,我在機械硬碟上的 ext4 和 XFS 上對此進行了測試。
apt-get
表示 518 個解壓文件的總大小為 21.7 MB(見下面的輸出)。關於 495 fsync() 呼叫,即使在請求“不安全 io”時仍然存在:
在 ext4 上,strace 輸出顯示剩餘 fsync() 呼叫所花費的時間約為 11 秒。在 XFS 上,相應的數字約為 7 秒。在所有情況下,這是安裝
aptitude
.因此,即使“不安全的 io”對安裝進行了小幅改進
aptitude
,您似乎也需要/var
安裝在比系統其餘部分快得多(延遲更低)的設備上,然後才能真正注意到差異。但我對優化那個小眾案例不感興趣。執行 under
strace -f -y -e trace=fsync,rename
顯示,對於剩餘的 fsync() 呼叫,其中 2 個是 on/etc/ld.so.cache~
,其中 493 個是對內部文件,/var/lib/dpkg/
即包數據庫。318 個 fsync() 呼叫在
/var/lib/dpkg/updates/
. 這些是 dpkg 數據庫的增量/var/lib/dpkg/status
。增量在 dpkg 執行結束時匯總到主數據庫(“檢查點”)中。The following NEW packages will be installed: aptitude aptitude-common libboost-filesystem1.62.0 libboost-iostreams1.62.0 libboost-system1.62.0 libcgi-fast-perl libcgi-pm-perl libclass-accessor-perl libcwidget3v5 libencode-locale-perl libfcgi-perl libhtml-parser-perl libhtml-tagset-perl libhttp-date-perl libhttp-message-perl libio-html-perl libio-string-perl liblwp-mediatypes-perl libparse-debianchangelog-perl libsigc++-2.0-0v5 libsqlite3-0 libsub-name-perl libtimedate-perl liburi-perl libxapian30 0 upgraded, 25 newly installed, 0 to remove and 0 not upgraded. Need to get 0 B/6000 kB of archives. After this operation, 21.7 MB of additional disk space will be used.
問題表明這對 ext4 或 XFS 沒有幫助。
我還測試了安裝一個更大的包(
linux-image-4.9.0-9-amd64
)。不管--force-unsafe-io
.分機2
在 ext2 上,
--force-unsafe-io
將安裝時間linux-image
從 50 秒減少到 13 秒。
5.0.17-200.fc29.x86_64
我執行測試的核心是CONFIG_EXT4_USE_FOR_EXT2
.我使用使用者空間 aio_fsync() 實現測試了 ext2。然而,最好的改進並不依賴於使用 AIO fsync()。
我的進步實際上是由於副作用。我已將 dpkg 更改為首先執行所有 fsync() 操作,然後是所有 rename() 操作。而未修補的 dpkg 在每個 fsync() 之後呼叫 rename()。我使用了高達 256 的 AIO 隊列深度。隊列深度為 1 的 AIO fsync() 明顯慢於同步 fsync() - 似乎有一些成本。最好的改進還需
SYNC_FILE_RANGE_WRITE
要先完成所有原始操作。改進版linux-image
在大約 18 秒內安裝完畢。這個操作順序實際上是 Ted T’so 最初建議的 :-D。發生的情況是 on
CONFIG_EXT4_USE_FOR_EXT2
, fsync() 也有助於同步父目錄。您想首先進行所有文件名操作,這樣您就可以避免每個目錄的多次磁碟更新。 我認為舊實現或普通文件系統不會發生這種情況。CONFIG_EXT2``ext4
$$ … $$這顯然也包括 ext2 預設模式。$$ … $$
https://elixir.bootlin.com/linux/v5.0.17/source/fs/ext4/fsync.c#L38
* If we're not journaling and this is a just-created file, we have to * sync our parent directory (if it was freshly created) since * otherwise it will only be written by writeback, leaving a huge * window during which a crash may lose the file. This may apply for * the parent directory's parent as well, and so on recursively, if * they are also freshly created.
和以前一樣,用 sync() 替換 fsync() 階段似乎提供了令人不安的良好性能,匹配
--force-unsafe-io
:-)。如果您可以不使用它們,sync() 或 syncfs() 似乎非常好。btrfs
當我開始在 btrfs 上測試 aio_fsync() 時,我發現由於最近的數據完整性修復,fsync() 操作可能導致文件的 rename() 阻塞。我決定我對 btrfs 不感興趣。