安全有效地修改POSIX系统上的多个文件?

时间:2009-03-20 12:16:00

标签: filesystems posix

我一直在关注EXT4上的“bug”,如果使用“创建临时文件,写入临时文件,将temp重命名为目标文件”进程,会导致文件在崩溃时归零。 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()关闭临时文件更改为正确的顺序,强调编写许多文件。

4 个答案:

答案 0 :(得分:3)

简短的回答是:在应用层解决这个问题是错误的。 EXT4必须确保在关闭文件后及时写入数据。就像现在一样,EXT4“优化”了这篇文章,以便能够收集更多的写请求,并一次性将其爆发。

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

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

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

这是一个无情的优化已经走得太远并破坏结果的情况。黄金法则:优化决不能改变最终结果。如果您无法保持这一点,则不得进行优化。

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

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

while True; do sync ; sleep 60 ; done

同样,FS中的错误会阻止它在每种情况下都能正常工作。否则,人们不会被这个错误所困扰。

您可以使用Windows注册表之类的后台配置守护程序。守护进程会将所有配置写入一个大文件中。在写完所有内容后,它可以调用fsync()。问题解决了......对于你的配置。现在,您需要对应用程序编写的所有内容执行相同操作:文本文档,图像等。我的意思是几乎任何Unix进程都创建一个文件。这是整个Unix理念的疯狂基础!

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

答案 1 :(得分:1)

我自己的答案是继续对临时文件进行修改,在完成全部编写后,执行一次fsync(),然后对它们进行全部重命名。

答案 2 :(得分: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()非常重要,因此合并写入一分钟的性能优势将被消除。

答案 3 :(得分:0)

您所提到的问题已得到充分研究,您应该明确地阅读: https://www.academia.edu/9846821/Towards_Efficient_Portable_Application-Level_Consistency

可以在安全重命名行为下跳过Fsync,并且可以在安全新文件行为下跳过目录fsync。两者都是特定于实现的,并且不受POSIX保证。