AIO fsync 可以提高 dpkg 效能嗎?

AIO fsync 可以提高 dpkg 效能嗎?

Debian 套件管理器可以dpkg透過使用 AIO fsync() 操作之一而不是sync_file_range() + fsync() 來獲得顯著的效能改進嗎?

[提議的] fsync2() API 本質上與現有的 AIO_FSYNC/AIO_FDSYNC API 相同,只是它是同步的,而這是應用程式想要避免的。

我反對[使用] AIO_FSYNC 的唯一論點是“該實現只是一個工作隊列”,這在很大程度上是無意義的,因為它獨立於文件系統實現,但允許自動內核端並行化所有發出的fsync 操作。這允許檔案系統在完成並發 fsync 操作時自動優化掉不必要的日誌寫入 - 當用戶應用程式從大量進程/線程同時運行 fsync() 時,XFS、ext4 等已經這樣做了...

這個簡單的實作允許在 XFS 上執行簡單的「使用 aio fsync 解壓縮」工作負載(即「批量寫入許多 4kB 檔案和 aio_fsync(),在分派新批次之前退出已完成的 fsync()」)工作負載大約2000 個檔案/秒(同步寫入IO 延遲範圍)到超過40,000 個檔案/秒(後端儲存上的寫入IOPS)。

--戴夫欽納

範例工作負載與apt-get install或相似dpkg -i(部分取決於已安裝軟體包中檔案的大小:-)。 dpkg在將它們重新命名到位之前,必須有效地 fsync() 所有解壓縮的檔案。

dpkg已根據 Ted T'so 的建議進行了優化。最佳化是在某些點添加對sync_file_range() 的呼叫。這個系統呼叫做了不是提供與 fsync() 相同的保證。請閱讀文檔同步文件範圍()並注意突出的警告:-)。

這些操作都不會寫出檔案的元資料。因此,除非應用程式嚴格執行已實例化磁碟區塊的覆蓋,否則無法保證資料在崩潰後可用。

dpkg寫入每個檔案後立即觸發資料寫入,使用SYNC_FILE_RANGE_WRITE.它首先寫入包的所有檔案。然後第二次遍歷文件,等待使用SYNC_FILE_RANGE_WAIT_BEFORE、呼叫 的資料寫回fsync(),最後將文件重新命名到位。

查看提交:

我的假設是,並行化 fsync() 操作可以透過允許更有效的批次來提高效能元數據寫入,特別是批次相關的屏障/磁碟快取刷新,這是確保磁碟上的元資料始終一致所必需的。

編輯:看來我的假設太簡單了,至少在使用 ext4 檔案系統時:

第二系列的sync_file_range()呼叫以及操作 SYNC_FILE_RANGE_WAIT_BEFORE將阻塞,直到先前啟動的寫回完成。這樣基本就保證了延遲分配已經解決;也就是說,資料塊已被分配和寫入,並且索引節點已更新(在記憶體中),但不一定被推送到磁碟。

[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

到目前為止,我嘗試在 Debian 9 容器中apt-get install運行。strace -f -wc例如,使用「unsafe io」安裝aptitude包,只有 495 個同步 fsync() 呼叫。正常安裝時aptitude,有 1011 個 fsync() 呼叫。 「unsafe io」也禁用了該SYNC_FILE_RANGE_WAIT_BEFORE調用,將sync_file_range()調用的數量從1036減少到518。

然而,尚不清楚這是否會減少平均時間。如果確實如此,那麼它似乎也只不過是運行之間的隨機變化而已。到目前為止,我在機械 HDD 上的 ext4 和 XFS 上進行了測試。


apt-get表示 518 個解壓縮檔案的總大小為 21.7 MB(請參閱下方的輸出)。

關於 495 fsync() 調用,即使在請求“不安全 io”時,該調用仍然存在:

在 ext4 上,strace 輸出顯示剩餘的 fsync() 呼叫所花費的時間約為 11 秒。在XFS上,相應的數字約為7秒。在所有情況下,這是安裝所花費的大部分時間aptitude

因此,即使「不安全的 io」對安裝帶來了小小的改進aptitude,似乎您也需要/var安裝在比系統其他部分更快(延遲更低)的設備上,然後差異才會真正明顯。但我對優化這個利基案例不感興趣。

運行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.

答案1

這個問題顯示這對 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()。我使用的 AIO 佇列深度高達 256。最好的改進還需SYNC_FILE_RANGE_WRITE要先完成所有原始操作。改進版本安裝時間linux-image約 18 秒。

這個操作順序其實是 Ted T'so 最初建議的:-D。發生的情況是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() 似乎非常好。

BTFS

當我開始在 btrfs 上測試 aio_fsync() 時,我發現由於最近的資料完整性修復,fsync() 操作可能會導致檔案的 rename() 阻塞。我決定我對 btrfs 不感興趣。

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

相關內容