dpkg
Debian パッケージ マネージャーは、sync_file_range() + fsync() の代わりに AIO fsync() 操作の 1 つを使用することで、顕著なパフォーマンスの向上を実現できますか?
[提案された] fsync2() API は、同期的であることを除いて、既存の AIO_FSYNC/AIO_FDSYNC API と本質的に同一であり、アプリケーションは同期を避けたいと考えています。
AIO_FSYNC の使用に反対する唯一の議論は、「実装は単なるワークキューである」というものです。これは、ファイルシステムの実装に依存しないにもかかわらず、発行されたすべての fsync 操作のカーネル側での自動並列化を可能にするため、ほとんど意味がありません。これにより、ファイルシステムは、同時 fsync 操作を完了するときに、不要なジャーナル書き込みを自動的に最適化できます。XFS、ext4 などでは、ユーザー アプリケーションが多数のプロセス/スレッドから同時に fsync() を実行するときに、すでにこれが行われています。
このシンプルな実装により、XFS 上の単純な「aio fsync による untar」ワークロード (つまり、「多数の 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
。最初にパッケージのすべてのファイルを書き込みます。次に、ファイルに対する 2 回目のパスがあり、 を使用してデータの書き戻しを待機しSYNC_FILE_RANGE_WAIT_BEFORE
、 を呼び出しfsync()
、最後にファイルの名前を適切な場所に変更します。
コミットを参照:
- 同期sync(2)の使用をデフォルトで無効にする
- アンパック時に安全な I/O 操作を無効にする新しい --force-unsafe-io を追加します。
- Linuxでは、解凍されたファイルの書き戻しをできるだけ早く開始する
- Linuxではfsyncの前に書き戻しを完了する
私の仮説は、fsync()操作を並列化することで、より効率的なバッチ処理が可能になり、パフォーマンスが向上する可能性があるというものです。メタデータ書き込み、特に、ディスク上のメタデータが常に一貫していることを保証するために必要な、関連するバリア/ディスク キャッシュ フラッシュのバッチ処理。
編集: 少なくとも ext4 ファイルシステムを使用する場合、私の仮説は単純すぎたようです。
操作を伴う sync_file_range() 呼び出しの 2 番目のシリーズは、
SYNC_FILE_RANGE_WAIT_BEFORE
以前に開始された書き戻しが完了するまでブロックされます。これにより、基本的に遅延割り当てが解決されたことが保証されます。つまり、データ ブロックが割り当てられて書き込まれ、inode が更新されます (メモリ内) が、必ずしもディスクにプッシュされるわけではありません。[fsync()] 呼び出しは、実際に inode をディスクに強制的に書き込みます。 ext4ファイルシステムの場合、最初の[fsync()]は実際にすべてのinodeをディスクにプッシュします。、そして後続の [fsync()] 呼び出しはすべて実際には何も行いません (ファイル 'a'、'b'、および 'c' がすべて同じファイル システムにあると仮定)。ただし、これは (重い) jbd2 コミットの数を最小限に抑えることを意味します。
これは Linux 固有のシステム コール --- sync_file_range() --- を使用しますが、その結果、すべてのファイル システムで全般的にパフォーマンスが高速化されるはずです。したがって、これは ext4 固有のハックとは考えていませんが、他のどのファイル システムよりも ext4 の方が高速化される可能性は高いです。
--テッド・ツォ
代わりに AIO fsync() 操作を使用すると、他のファイルシステムでもメリットが得られる可能性があります。
bcachefs
(開発中) は、異なるファイル間の IO を ext4 よりもはるかに適切に分離すると主張しています。そのため、テストすると特に興味深いかもしれません。
ext4 は純粋な AIO fsync() パターンに対してそれほど最適化されていないようです (他のファイルシステムにも同じ制約があると思います)。もしそうなら、最初に同じ sync_file_range() 呼び出しをすべて実行し、次にすべての AIO fsync() 操作を 2 回目のラウンドとして開始し、fsync() 操作が完了したらすべてのファイルの名前を変更して終了することが可能であると思います。
古い:
このような調査の最初のステップは測定です:-)。
を使用して fsync() 部分を無効にすることができますecho "force-unsafe-io" > /etc/dpkg/dpkg.cfg.d/force-unsafe-io
。
これまでのところ、Debian 9 コンテナで のapt-get install
下で実行してみました。たとえば、「unsafe io」を使用してパッケージをインストールすると、同期 fsync() 呼び出しは 495 回のみです。一方、通常インストールでは、fsync() 呼び出しが 1011 回あります。また、「unsafe io」は呼び出しを無効にし、sync_file_range() 呼び出しの数を 1036 から 518 に減らしました。strace -f -wc
aptitude
aptitude
SYNC_FILE_RANGE_WAIT_BEFORE
しかし、これによって平均所要時間が短縮されたかどうかは、あまり明確ではありませんでした。短縮されたとしても、実行間のランダムな変動によるもの以上のものではないようです。これまでのところ、私はこれを機械式 HDD 上の ext4 と XFS でテストしました。
apt-get
解凍された 518 個のファイルの合計サイズは 21.7 MB でした (以下の出力を参照)。
「安全でない io」を要求した場合でも存在し続ける 495 の fsync() 呼び出しに関して:
ext4 では、strace 出力は、残りの fsync() 呼び出しに費やされた時間は約 11 秒と示しました。XFS では、対応する数字は約 7 秒でした。いずれの場合も、これがインストールにかかった時間の大部分を占めましたaptitude
。
したがって、「unsafe io」がインストールにわずかな改善をもたらすとしてもaptitude
、違いが実際に顕著になるには、システムの他の部分よりも大幅に高速な(低レイテンシの)デバイスにマウントする必要があるようです/var
。しかし、私はそのニッチなケースを最適化することには興味がありません。
で実行するとstrace -f -y -e trace=fsync,rename
、残りの fsync() 呼び出しのうち 2 つは 上で行われ/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
) を 1 つインストールしてテストしました。 に関係なく、やはり同じ時間がかかるようです--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。 では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.
以前と同様に、fsync() ステージを sync() に置き換えると、驚くほど優れたパフォーマンスが得られるようです。一致しています--force-unsafe-io
:-)。sync() または syncfs() は、使用しても問題ない場合は非常に便利です。
btrfs
btrfs で aio_fsync() のテストを開始したとき、最近のデータ整合性の修正により、fsync() 操作によってファイルの rename() がブロックされる可能性があることを発見しました。私は btrfs には興味がないと判断しました。