在POSIX系统上修改多个文件的安全且有效的方法?

如果使用“创建临时文件,写入临时文件,重命名临时文件到目标文件”进程,我一直在讨论关于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

即使使用fsync()也不好,因为计算机可能在2)或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()的系统上高效地修改多个文件以确保更改已保存到磁盘? 我的意思是修改很多文件,就像成千上万的文件一样。 修改两个文件并在每个文件之后执行fsync()不会太糟糕,但在修改多个文件时,fsync()确实会减慢速度。

编辑:将fsync()关闭临时文件更改为正确的顺序,增加了编写许多许多文件的重点。


简短的答案是:在应用层解决这个问题是错误的地方。 EXT4必须确保在关闭文件后,数据会及时写入。 现在,EXT4“优化”了这篇文章,以便能够收集更多的写入请求,并一口气将它们迸发出去。

问题很明显:不管你做什么,你都不能确定你的数据在磁盘上结束。 手动调用fdisk()只会让事情变得更糟:您基本上妨碍了EXT4的优化,降低了整个系统的速度。

OTOH,EXT4具有在有必要将数据写入磁盘时进行有根据的猜测所需的所有信息。 在这种情况下,我将临时文件重命名为现有文件的名称。 对于EXT4,这意味着它必须推迟重命名(所以原始文件的数据在崩溃后保持不变),或者必须立即刷新。 由于它不能推迟重命名(下一个进程可能希望看到新数据),因此重命名隐式意味着刷新,并且刷新必须发生在FS层而不是应用层。

EXT4可能会创建一个文件系统的虚拟副本,其中包含在未修改磁盘时(尚)的更改。 但是这并不影响最终目标:应用程序无法知道FS要做什么优化,因此FS必须确保它能够完成其工作。

这种情况下,无情的优化已经变得太过分了,并且破坏了结果。 黄金法则:优化决不能改变最终结果。 如果你不能保持这一点,你不能优化。

只要Tso认为拥有一个快速的FS而不是一个正确的行为更重要,我建议不要升级到EXT4,并关闭所有关于这个问题的错误报告是“按照Tso设计的方式工作”。

[编辑]对此的更多想法。 您可以使用数据库而不是文件。 让我们暂时忽略资源浪费。 任何人都可以保证数据库使用的文件不会因崩溃而损坏吗? 大概。 数据库可以写入数据并每隔一分钟左右调用一次fsync()。 但是,你可以这样做:

while True; do sync ; sleep 60 ; done

同样,FS中的错误可以防止这种情况在每种情况下都起作用。 否则,人们不会为这个bug所困扰。

您可以使用Windows注册表等后台配置守护进程。 守护进程会将所有配置写入一个大文件。 它可以在写出所有内容后调用fsync()。 问题解决了...对于你的配置。 现在,您需要为应用程序的其他所有内容执行相同的操作:文本文档,图像等等。 我的意思是几乎任何Unix进程创建一个文件。 这是整个Unix想法的怪胎!

显然,这不是一条可行的道路。 所以答案依然存在:你身边没有解决方案。 继续困扰Tso和其他FS开发人员,直到他们修复了他们的错误。


我自己的答案是保持对临时文件的修改,并在完成全部写入之后,执行一个fsync(),然后对它们全部重命名。


您需要在上次列表中交换3和4 - fsync(fd)使用文件描述符。 我不明白为什么这会特别昂贵 - 你想要通过close()写入磁盘的数据。 所以,你想要发生什么和fsync()会发生什么,成本是一样的。

如果成本太高,(你有它) fdatasync(2)避免同步元数据,所以应该是成本更低。

编辑:所以我写了一些非常hacky的测试代码:

#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.%06ldn",seconds,usec);
}

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

在我的笔记本电脑上产生:

0.595782
6.338329
6.116894

这表明做fsync()成本要高10倍。 和fdatasync()稍微便宜一些。

我想我看到的问题是,每个应用程序都会认为它的数据对fsync()足够重要,所以在一分钟内合并写入的性能优势将被消除。

链接地址: http://www.djcxy.com/p/42367.html

上一篇: Secure and efficient way to modify multiple files on POSIX systems?

下一篇: Turning a list into variables in python