8

「一時ファイルの作成、一時ファイルの書き込み、一時ファイルの名前をターゲットファイルに変更する」プロセスを使用すると、クラッシュ時にファイルがゼロになる原因となる EXT4 の「バグ」に関する議論に従っています。POSIX は、fsync() が呼び出されない限り、データがハードディスクにフラッシュされたことを確認できないと述べています。

明らかにやっている:

0) get the file contents (read it or make it somehow)
1) open original file and truncate it
2) write new contents
3) close file

2) または fsync() 中にコンピューターがクラッシュし、部分的に書き込まれたファイルになってしまうため、fsync() を使用しても適切ではありません。

通常、これはかなり安全であると考えられてきました。

0) get the file contents (read it or make it somehow)
1) open temp file
2) write contents to temp file
3) close temp file
4) rename temp file to original file

残念ながらそうではありません。EXT4 で安全にするには、次のことを行う必要があります。

0) get the file contents (read it or make it somehow)
1) open temp file
2) write contents to temp file
3) fsync()
4) close temp file
5) rename temp file to original file

これは安全であり、クラッシュ時には、新しいファイルの内容か、古いゼロ化されていない内容または部分的な内容のいずれかが必要です。しかし、アプリケーションが大量のファイルを使用する場合、書き込みのたびに fsync() が遅くなります。

私の質問は、変更がディスクに保存されたことを確認するために fsync() が必要なシステムで複数のファイルを効率的に変更する方法ですか? つまり、何千ものファイルのように、多くのファイルを変更するということです。2 つのファイルを変更し、それぞれの後に fsync() を実行することはそれほど悪くはありませんが、複数のファイルを変更する場合、fsync() は速度を低下させます。

編集: fsync() クローズ一時ファイルを正しい順序に変更し、多数の多数のファイルを書き込むことに重点を置きました。

4

4 に答える 4

3

簡単な答えは次のとおりです。これをアプリ層で解決するのは間違った場所です。EXT4 は、ファイルを閉じた後、データが適切なタイミングで書き込まれるようにする必要があります。現在のように、EXT4 はこの書き込みを「最適化」して、より多くの書き込み要求を収集し、一度にバーストできるようにします。

問題は明らかです。何をしても、データがディスク上で終了していることを確認できません。手動で fdisk() を呼び出すと、状況が悪化するだけです。基本的に、EXT4 の最適化の邪魔になり、システム全体の速度が低下します。

OTOH、EXT4には、データをディスクに書き出す必要があるときに、知識に基づいた推測を行うために必要なすべての情報があります。この場合、一時ファイルの名前を既存のファイルの名前に変更します。EXT4 の場合、これは、名前の変更を延期する (クラッシュ後も元のファイルのデータが損なわれないようにする) か、すぐにフラッシュする必要があることを意味します。名前の変更を延期することはできないため (次のプロセスが新しいデータを確認する必要がある場合があります)、名前の変更は暗黙的にフラッシュすることを意味し、そのフラッシュはアプリ レイヤーではなく FS レイヤーで発生する必要があります。

EXT4 は、ディスクが (まだ) 変更されていない間に、変更を含むファイルシステムの仮想コピーを作成する場合があります。しかし、これは最終的な目標には影響しません。つまり、アプリは FS がどのような最適化を行うかを知ることができないため、FS はその仕事を確実に行う必要があります。

これは、冷酷な最適化が行き過ぎて結果を台無しにしたケースです。黄金律: 最適化によって最終結果が変わることがあってはなりません。これを維持できない場合は、最適化してはいけません。

Tso が、正しく動作するものよりも高速な FS を使用する方が重要であると考えている限り、EXT4 にアップグレードせず、これに関するすべてのバグ レポートを閉じることをお勧めします。

[編集] これに関するいくつかの考え。ファイルの代わりにデータベースを使用できます。リソースの浪費はしばらく無視しましょう。データベースが使用するファイルがクラッシュによって破損しないことを保証できる人はいますか? おそらく。データベースはデータを書き込み、約 1 分ごとに fsync() を呼び出すことができます。しかし、その後、同じことができます:

while True; do sync ; sleep 60 ; done

繰り返しますが、FS のバグにより、これがすべての場合に機能しなくなります。そうでなければ、人々はこのバグにそれほど悩まされないでしょう.

Windows レジストリのようなバックグラウンド構成デーモンを使用できます。デーモンは、すべての構成を 1 つの大きなファイルに書き込みます。すべてを書き出した後に fsync() を呼び出すことができます。問題は解決しました...あなたの設定のために。次に、アプリが作成する他のすべてのもの (テキスト ドキュメント、画像など) についても同じことを行う必要があります。つまり、ほぼすべての Unix プロセスがファイルを作成します。これは、Unix のアイデア全体の驚くべき基礎です!

明らかに、これは実行可能なパスではありません。したがって、答えは残ります。あなたの側に解決策はありません。バグが修正されるまで、Tso や他の FS 開発者を悩ませ続けてください。

于 2009-03-20T13:29:05.710 に答える
1

私自身の答えは、一時ファイルの変更を維持し、それらをすべて書き終えた後、1 つの fsync() を実行してから、それらすべての名前を変更することです。

于 2009-03-20T12:39:59.153 に答える
0

あなたが参照している問題は十分に研究されています。これを必ずお読みください: https://www.academia.edu/9846821/Towards_Efficient_Portable_Application-Level_Consistency

安全な名前変更動作では fsync をスキップでき、安全な新しいファイル動作ではディレクトリ fsync をスキップできます。どちらも実装固有であり、POSIX では保証されていません。

于 2015-09-19T13:30:12.043 に答える
0

最後のリストで 3 と 4 を交換する必要があります -fsync(fd)ファイル記述子を使用します。なぜそれが特にコストがかかるのかわかりません-とにかく、データを close() によってディスクに書き込む必要があります。したがって、コストは、発生したいことと で発生することの間で同じになりますfsync()

コストが高すぎる場合 (そしてそれを持っている場合)fdatasync(2)は、メタデータの同期を避けるため、コストが軽くなるはずです。

編集:だから私はいくつかの非常にハッキーなテストコードを書きました:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <string.h>

static void testBasic()
{
    int fd;
    const char* text = "This is some text";

    fd = open("temp.tmp", O_WRONLY | O_CREAT);
    write(fd,text,strlen(text));
    close(fd);
    rename("temp.tmp","temp");
}

static void testFsync()
{
    int fd;
    const char* text = "This is some text";

    fd = open("temp1", O_WRONLY | O_CREAT);
    write(fd,text,strlen(text));
    fsync(fd);
    close(fd);
    rename("temp.tmp","temp");
}

static void testFdatasync()
{
    int fd;
    const char* text = "This is some text";

    fd = open("temp1", O_WRONLY | O_CREAT);
    write(fd,text,strlen(text));
    fdatasync(fd);
    close(fd);
    rename("temp.tmp","temp");
}

#define ITERATIONS 10000

static void testLoop(int type)
{
    struct timeval before;
    struct timeval after;
    long seconds;
    long usec;
    int i;

    gettimeofday(&before,NULL);
    if (type == 1)
    {
        for (i = 0; i < ITERATIONS; i++)
        {
            testBasic();
        }
    }
    if (type == 2)
    {
        for (i = 0; i < ITERATIONS; i++)
        {
            testFsync();
        }
    }
    if (type == 3)
    {
        for (i = 0; i < ITERATIONS; i++)
        {
            testFdatasync();
        }
    }
    gettimeofday(&after,NULL);

    seconds = (long)(after.tv_sec - before.tv_sec);
    usec = (long)(after.tv_usec - before.tv_usec);
    if (usec < 0)
    {
        seconds--;
        usec += 1000000;
    }

    printf("%ld.%06ld\n",seconds,usec);
}

int main()
{
    testLoop(1);
    testLoop(2);
    testLoop(3);
    return 0;
}

生成する私のラップトップで:

0.595782
6.338329
6.116894

fsync()これは、実行することのコストが ~10 倍高いことを示唆しています。そしてfdatasync()少し安いです。

私が目にする問題は、すべてのアプリケーションがそのデータが fsync() にとって十分に重要であると考えるようになることだと思います。そのため、1 分以上の書き込みをマージすることのパフォーマンス上の利点はなくなります。

于 2009-03-20T12:26:39.607 に答える