Linuxで書き込みが失われる原因となるI / Oエラーに対処するプログラムを作成する


138

TL; DR:LinuxカーネルがバッファリングされたI / O書き込みを失った場合、アプリケーションがそれを見つける方法はありますか?

fsync()耐久性のためにファイル(およびその親ディレクトリ)を使用する必要があることを知っています。問題は、I / Oエラーが原因で書き込み保留中のダーティバッファーがカーネルで失われた場合、アプリケーションはこれをどのように検出して回復または中止できるでしょうか。

書き込みの順序と書き込みの持続性が重要になるデータベースアプリケーションなどを考えてください。

書き込みを忘れましたか?どうやって?

Linuxカーネルのブロックレイヤーは、状況によっては、などによって正常に送信されたバッファリングされたI / O要求を失う可能性があり、次のようなエラーが発生します。write()pwrite()

Buffer I/O error on device dm-0, logical block 12345
lost page write due to I/O error on dm-0

end_buffer_write_sync(...)およびend_buffer_async_write(...)fs/buffer.c参照)。

新しいカーネルでは、エラーには代わりに「失われた非同期ページ書き込み」が含まれます

Buffer I/O error on dev dm-0, logical block 12345, lost async page write

アプリケーションwrite()は既にエラーなしで返されているので、アプリケーションにエラーを報告する方法はないようです。

それらを検出しますか?

私はカーネルソースにそれほど精通していませんが、非同期書き込みを行っている場合、書き出されなかったバッファーに設定されると思いますAS_EIO

    set_bit(AS_EIO, &page->mapping->flags);
    set_buffer_write_io_error(bh);
    clear_buffer_uptodate(bh);
    SetPageError(page);

しかし、アプリケーションが後でfsync()ディスク上にあることを確認するためにファイルを処理するときに、アプリケーションがこれを見つけることができるかどうか、またはどのようにそれを見つけることができるかは不明です。

それはのように見えるwait_on_page_writeback_range(...)mm/filemap.cでかもしれないdo_sync_mapping_range(...)fs/sync.cで呼び出される順番ですsys_sync_file_range(...)-EIO1つ以上のバッファに書き込めなかった場合に返されます。

私が推測しているように、これがfsync()の結果に反映される場合、アプリがパニックに陥り、I / Oエラーが発生しfsync()、再起動時に作業をやり直す方法を知っている場合は、それで十分です。

おそらくアプリがファイル内のどのバイトオフセットが失われたページに対応しているかを知る方法はないので、アプリがその方法を知っている場合はそれらを書き換えることができますが、アプリがfsync()ファイルの最後の成功以降に保留中の作業をすべて繰り返し、それが書き換えられる場合ファイルに対する失われた書き込みに対応するダーティカーネルバッファー。失われたページのI / Oエラーフラグをクリアし、次のページfsync()を完了できるようにする必要があります。

それから、救済とやり直し作業があまりにも劇的でfsync()ある、戻ってくるかもしれない他の無害な状況はあり-EIOますか?

どうして?

もちろん、そのようなエラーは発生しません。この場合、エラーは、dm-multipathドライバーのデフォルトと、SANがシンプロビジョニングされたストレージの割り当ての失敗を報告するために使用するセンスコードとの間の不幸な相互作用から発生しました。しかし、これが発生する可能性があるのはこれだけではありません。たとえば、libvirtやDockerなどで使用されているシンプロビジョニングされたLVMからの報告も見ました。データベースのような重要なアプリケーションは、あたかもすべてが順調であるかのように盲目的に実行するのではなく、そのようなエラーに対処しようとする必要があります。

場合は、カーネルはカーネルパニックで死なずに失うの書き込みにそれはOKだと思っ、アプリケーションが対処する方法を見つける必要があります。

実際的な影響は、DBMSが書き込みが失敗したことを知らなかったために、SANのマルチパスの問題が原因で書き込みが失われ、データベースが破損するケースを見つけたことです。楽しくない。


1
これらのエラー条件を保存して記憶するには、SystemFileTableに追加のフィールドが必要になると思います。そして、ユーザー空間プロセスが後続の呼び出しでそれらを受信または検査する可能性。(fsync()とclose()はこの種の履歴情報を返しますか?)
joop

@joopありがとうございます。「write()の明らかなバリアントを投稿した人がclose()またはfsync( )耐久性のために」質問を読むことなく?
クレイグリンガー

ところで、私はあなたが本当にカーネルソースを掘り下げるべきだと思います。ジャーナリングされたファイルシステムもおそらく同じ種類の問題に悩まされるでしょう。スワップパーティションの処理については言うまでもありません。これらはカーネル空間に存在するため、これらの条件の処理はおそらくもう少し厳格になります。ユーザー空間から見えるwritev()も見る場所のようです。[クレイグで:はい、私はあなたの名前を知っているので、私はあなたが完全な馬鹿ではないことを知っています;-]
joop

1
私は同意します、私はそれほど公平ではありませんでした。悲しいかなあなたの答えはあまり満足できません。つまり、簡単な解決策はありません(驚くべき?)。
ジャンバティストユネス2017年

1
@Jean-BaptisteYunèsTrue。私が使用しているDBMSでは、「クラッシュしてやり直し」を使用できます。オプションではないほとんどのアプリでは、同期I / Oの恐ろしいパフォーマンスを許容するか、不十分に定義された動作とI / Oエラーの破損を受け入れる必要がある場合があります。
クレイグリンガー

回答:


91

fsync()-EIOカーネルが書き込みを失った場合に戻ります

(注:初期の部分は古いカーネルを参照しています。最新のカーネルを反映するように以下を更新しています)

失敗したend_buffer_async_write(...)-EIO場合の非同期バッファーの書き出しは、ファイルの失敗したダーティバッファーページにフラグを設定するように見えます

set_bit(AS_EIO, &page->mapping->flags);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);

それまでに検出されたwait_on_page_writeback_range(...)ことで呼び出されるようdo_sync_mapping_range(...)で呼び出されるようsys_sync_file_range(...)によって呼び出されるsys_sync_file_range2(...)Cライブラリの呼び出しを実装しますfsync()

しかし、一度だけ!

このコメント sys_sync_file_range

168  * SYNC_FILE_RANGE_WAIT_BEFORE and SYNC_FILE_RANGE_WAIT_AFTER will detect any
169  * I/O errors or ENOSPC conditions and will return those to the caller, after
170  * clearing the EIO and ENOSPC flags in the address_space.

fsync()戻るとき、-EIOまたは(マンページに文書化されていない)の-ENOSPC場合は、エラー状態クリアされるのでfsync()、ページが書き込まれなかったとしても、後続は成功を報告します。

確かwait_on_page_writeback_range(...) に、テスト時にエラービットをクリアします

301         /* Check for outstanding write errors */
302         if (test_and_clear_bit(AS_ENOSPC, &mapping->flags))
303                 ret = -ENOSPC;
304         if (test_and_clear_bit(AS_EIO, &mapping->flags))
305                 ret = -EIO;

したがって、アプリケーションfsync()が成功するまで再試行でき、データがディスク上にあると信頼できるとアプリケーションが期待する場合、それはひどく間違っています。

これが、DBMSで見つかったデータ破損の原因であると確信しています。それは再試行しfsync()、成功するとすべてがうまくいくと考えます。

これは許可されますか?

POSIX / SuSドキュメントでfsync()は、どちらの方法でもこれを実際に指定していません。

fsync()関数が失敗した場合、未処理のI / O操作が完了したことは保証されません。

Linuxのマンページはfsync()、失敗したときに何が起こるかについては何も述べていません。

したがって、fsync()エラーの意味は「書き込みに何が起こったのか、うまくいったかどうかはわかりません。もう一度確認してください」のようです。

新しいカーネル

ページ上の4.9 end_buffer_async_writeセット-EIOで、を介してmapping_set_error

    buffer_io_error(bh, ", lost async page write");
    mapping_set_error(page->mapping, -EIO);
    set_buffer_write_io_error(bh);
    clear_buffer_uptodate(bh);
    SetPageError(page);

同期については、構造は非常に複雑になっていますが、似ていると思います。filemap_check_errorsmm/filemap.c、今行われます。

    if (test_bit(AS_EIO, &mapping->flags) &&
        test_and_clear_bit(AS_EIO, &mapping->flags))
            ret = -EIO;

ほぼ同じ効果があります。エラーチェックはすべてfilemap_check_errorsテストとクリアを行うようです:

    if (test_bit(AS_EIO, &mapping->flags) &&
        test_and_clear_bit(AS_EIO, &mapping->flags))
            ret = -EIO;
    return ret;

私はbtrfsラップトップで使用していますが、ext4テスト用のループバックを作成し、/mnt/tmpそれにパフォーマンスプローブを設定すると、次のようになります。

sudo dd if=/dev/zero of=/tmp/ext bs=1M count=100
sudo mke2fs -j -T ext4 /tmp/ext
sudo mount -o loop /tmp/ext /mnt/tmp

sudo perf probe filemap_check_errors

sudo perf record -g -e probe:end_buffer_async_write -e probe:filemap_check_errors dd if=/dev/zero of=/mnt/tmp/test bs=4k count=1 conv=fsync

私は次の呼び出しスタックを見つけますperf report -T

        ---__GI___libc_fsync
           entry_SYSCALL_64_fastpath
           sys_fsync
           do_fsync
           vfs_fsync_range
           ext4_sync_file
           filemap_write_and_wait_range
           filemap_check_errors

読み通すと、そうです、最新のカーネルは同じように動作します。

これがあればという意味のようですfsync()(またはおそらくwrite()またはclose()リターン)-EIO、ファイルはあなたが最後に成功したときの間でいくつかの未定義の状態にあるfsync()Dまたはclose()それとその直近dはwrite()10の状態を。

テスト

この動作を示すために、テストケースを実装しました

含意

DBMSは、クラッシュリカバリを開始することでこれに対処できます。これに対処するために通常のユーザーアプリケーションは一体どうなっているのでしょうか。fsync()manページは、それが意味することに警告を与えていない「のfsync-IF-あなたフィールライクそれは」と私は期待して多くのアプリはこの動作とよく対応しておりません。

バグレポート

参考文献

lwn.netは、記事「ブロック層のエラー処理の改善」でこれに触れました

postgresql.orgメーリングリストスレッド


3
lxr.free-electrons.com/source/fs/buffer.c?v=2.6.26#L598は、{まだスケジュールされていないI / O}ではなく、{pending&scheduled I / O}を待つため、競合の可能性があります。これは明らかに、デバイスへの余分なラウンドトリップを回避するためです。(I / Oがスケジュールされるまでユーザーwrites()が
返らないと思います。mmap

3
同じディスク上の他のファイルに対して他のプロセスがfsyncを呼び出すと、エラーが返される可能性がありますか?
Random832 2017

3
@ Random832 PostgreSQLのようなマルチプロセッシングDBに非常に関連しているので、良い質問です。おそらくそのように見えますが、私は理解するのに十分なほどカーネルコードを知りません。とにかく両方が同じファイルを開いている場合、あなたのprocは協力的である方がよいでしょう。
クレイグリンガー

1
@DavidFoerster:syscallsは負のerrnoコードを使用して失敗を返します。errno完全にユーザー空間Cライブラリの構成要素です。(クレイグリンガーは、上記したように)1つ(システムコールまたはCライブラリ関数)と呼ばれている確実に識別エラー戻り値ので、このようなシステムコールとCライブラリとの間の戻り値の違いを無視するのが一般的である。」-1errno==EIO"はCライブラリ関数を-EIO指し、" "はsyscallを指します。最後に、オンラインのLinux manページはLinux manページの最新のリファレンスです。
名目上の動物

2
@CraigRinger:最後の質問に答えるには、「トランザクションレベルが完全なファイルの場合は低レベルI / Oとfsync()/ fdatasync()を使用し、トランザクションサイズがページ単位のレコードの場合はmmap()/ msync()を使用し、低レベルIを使用します。fdatasync()それ以外の場合は、/ O 、、および複数の同時ファイル記述子(トランザクションごとに1つの記述子とスレッド)から同じファイルへ。Linux固有のオープンファイルディスクリプションロック(fcntl()F_OFD_)は、最後のロックで非常に役立ちます。
名目上の動物

22

アプリケーションのwrite()はすでにエラーなしで返されているので、アプリケーションにエラーを報告する方法はないようです。

私は同意しない。write書き込みが単にキューに入れられた場合、エラーなしで戻ることができますが、エラーは、ディスクへの実際の書き込みを必要とする次の操作で報告されfsyncます。つまり、システムがキャッシュをフラッシュすることを決定した場合、次の書き込みで少なくとも最後のファイルを閉じたとき。

そのため、アプリケーションでcloseの戻り値をテストして、起こり得る書き込みエラーを検出することが不可欠です。

賢いエラー処理を本当に実行する必要があるfsync 場合は、最後の成功以降に書き込まれたすべてが失敗した可能性があり、少なくとも何かが失敗したと想定する必要があります。


4
ええ、それはそれを釘付けにすると思います。これは、実際にアプリケーションが最後に確認され、成功したので、そのすべての作業を再度行う必要があることを示唆しているfsync()か、close()それが取得する場合、ファイルの-EIOからwrite()fsync()またはclose()。まあ、それは楽しいです。
クレイグリンガー

1

write(2)提供するものが予想より少ない。manページは、成功したwrite()呼び出しのセマンティクスについて非常に公開されています。

からの正常な戻りwrite()は、データがディスクにコミットされたことを保証するものではありません。実際、一部のバグの多い実装では、データ用に領域が正常に予約されていることさえ保証されていません。確実にする唯一の方法は、fsyncすべてのデータの書き込みが完了した後に(2)を呼び出すことです。

成功write()とは、データがカーネルのバッファリング機能に到達したことを意味するだけであると結論付けることができます。バッファーの永続化に失敗した場合、その後ファイル記述子にアクセスするとエラーコードが返されます。かもしれない最後の手段としてclose()close(2)システムコールのマニュアルページには、次の文が含まれています。

前のwrite(2)操作のエラーが最後のclose()で最初に報告される可能性があります。

アプリケーションがデータの書き込みを永続化する必要がある場合は、定期的にfsync/ を使用する必要がありますfsyncdata

fsync()変更されたすべての情報を取得できるように、ファイル記述子fdによって参照されるファイルのすべての変更されたコア内データ(つまり、変更されたバッファキャッシュページ)をディスクデバイス(または他の永続的なストレージデバイス)に転送(「フラッシュ」)システムがクラッシュまたは再起動した後でも。これには、ディスクキャッシュがある場合は、それを介した書き込みまたはフラッシュが含まれます。転送が完了したことをデバイスが報告するまで、呼び出しはブロックされます。


4
はい、fsync()必要であることは承知しています。しかし、I / Oエラーのためにカーネルがページを失う特定のケースでfsync()失敗しますか?その後、それはどのような状況で成功しますか?
クレイグリンガー

カーネルのソースもわかりません。I / Oの問題のfsync()リターン-EIOを想定しましょう(それ以外の場合は何が良いでしょうか?)。そのため、データベースは以前の書き込みの一部が失敗したことを認識し、回復モードに入る可能性があります。これはあなたが望むものではありませんか?最後の質問の動機は何ですか?どの書き込みが失敗したかを知りたいですか、それとも後で使用するためにファイル記述子を回復したいですか?
fzgregor 2017

理想的には、DBMSは、回避できる可能性がある場合は、クラッシュリカバリ(すべてのユーザーをキックオフして一時的にアクセスできなくなるか、少なくとも読み取り専用になる)に入らないようにします。しかし、カーネルが「fd Xのバイト4096から8191」と教えてくれたとしても、クラッシュリカバリをほとんど行わずにそこに何を(再)書き込むかを理解するのは難しいでしょう。したがって、主な質問は、再試行して安全な場所にfsync()戻る可能性のある無実の状況が他にあるかどうか、そして違いを伝えることが可能かどうかです。-EIO
クレイグリンガー

確かにクラッシュリカバリは最後の手段です。しかし、すでに述べたように、これらの問題は非常にまれであると予想されます。したがって、いずれの状態でも回復に問題はありません-EIO。各ファイル記述子が一度に1つのスレッドでのみ使用される場合、このスレッドは最後のスレッドに戻りfsync()write()呼び出しをやり直すことができます。しかし、それでもそれらwrite()がセクターの一部のみを書き込む場合、変更されていない部分は依然として破損している可能性があります。
fzgregor 2017

1
クラッシュリカバリに入るのはおそらく妥当なことです。部分的に破損したセクターについては、DBMS(PostgreSQL)は、その理由のために特定のチェックポイントの後で初めてページ全体に触れたときにページ全体のイメージを保存するため、問題ないはずです:)
Craig Ringer

0

ファイルを開くときにO_SYNCフラグを使用します。これにより、データがディスクに確実に書き込まれます。

これで満足できない場合は、何もありません。


17
O_SYNCパフォーマンスの悪夢です。これは、アプリケーションがI / Oスレッドを生成しない限り、ディスクI / Oが発生している間は他にできないことを意味します。バッファリングされたI / Oインターフェースは安全ではなく、誰もがAIOを使用するべきだと言うかもしれません。確かに、サイレントで失われた書き込みは、バッファ付きI / Oでは受け入れられませんか?
クレイグリンガー

3
O_DATASYNCその点では少しだけ優れています)
クレイグリンガー

@CraigRinger このニーズがあり、何らかのパフォーマンスが必要な場合、AIO 使用する必要があります。または単にDBMSを使用します。それはあなたのためにすべてを処理します。
デミ

10
@Demiここでのアプリケーションはdbms(postgresql)です。バッファリングされたI / Oの代わりにAIOを使用するようにアプリケーション全体を書き直すことは実用的ではないことを想像できると思います。それも必要ではありません。
クレイグリンガー

-5

closeの戻り値を確認してください。バッファリングされた書き込みは成功しているように見えますが、closeは失敗する可能性があります。


8
まあ、数秒ごとにファイルopen()を処理close()することはほとんどありません。だからこそfsync()
クレイグリンガー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.