Linux

AIO fsync 可以提高 dpkg 性能嗎?

  • June 6, 2019

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 installstrace -f -wcDebian 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安裝在比系統其餘部分快得多(延遲更低)的設備上,然後才能真正注意到差異。但我對優化那個小眾案例不感興趣。

執行 understrace -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

ext4:這次讓 fsync 在非日誌中同步父目錄

$$ … $$這顯然也包括 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 不感興趣。

為什麼第一次呼叫 fsync() 時 rename() 需要更長的時間?

引用自:https://unix.stackexchange.com/questions/522163