Безопасный и эффективный способ изменения нескольких файлов в системах POSIX? - PullRequest
7 голосов
/ 20 марта 2009

Я следил за обсуждением «ошибки» в 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 () для закрытия в другом порядке, добавлен акцент на записи много-много-файлов.

Ответы [ 4 ]

3 голосов
/ 20 марта 2009

Краткий ответ: решение проблемы на уровне приложения - неправильное место. EXT4 должен убедиться, что после того, как я закрою файл, данные будут записаны своевременно. В настоящее время EXT4 «оптимизирует» эту запись, чтобы иметь возможность собирать больше запросов на запись и обрабатывать их за один раз.

Проблема очевидна: независимо от того, что вы делаете, вы не можете быть уверены, что ваши данные заканчиваются на диске. Вызов fdisk () вручную только ухудшает ситуацию: вы в основном мешаете оптимизации EXT4, замедляя всю систему.

OTOH, EXT4 обладает всей информацией, необходимой для обоснованного предположения, когда необходимо записать данные на диск. В этом случае я переименовываю временный файл в имя существующего файла. Для EXT4 это означает, что он должен либо отложить переименование (чтобы данные исходного файла оставались нетронутыми после сбоя), либо он должен сбрасываться сразу. Поскольку переименование не может быть отложено (следующий процесс может захотеть увидеть новые данные), переименование неявно означает сброс, и это сброс должен происходить на уровне FS, а не на уровне приложения.

EXT4 может создать виртуальную копию файловой системы, которая содержит изменения, пока диск не изменен (пока). Но это не влияет на конечную цель: приложение не может знать, какие оптимизации ФС собирается выполнить, и поэтому ФС должна убедиться, что оно выполняет свою работу.

Это тот случай, когда безжалостная оптимизация зашла слишком далеко и испортила результаты. Золотое правило: оптимизация никогда не должна менять конечный результат. Если вы не можете поддерживать это, вы не должны оптимизировать.

Пока Tso считает, что более важно иметь быструю FS, а не ту, которая ведет себя правильно, я советую не обновляться до EXT4 и закрывать все сообщения об ошибках по этому поводу - «работает, как задумано Tso».

[ПРАВИТЬ] Еще несколько мыслей по этому поводу. Вы можете использовать базу данных вместо файла. Давайте на мгновение проигнорируем трату ресурсов. Может ли кто-нибудь гарантировать, что файлы, используемые базой данных, не будут повреждены в результате сбоя? Наверное. База данных может записывать данные и вызывать fsync () каждую минуту или около того. Но тогда вы можете сделать то же самое:

while True; do sync ; sleep 60 ; done

Опять же, ошибка в FS препятствует тому, чтобы это работало в каждом случае. В противном случае люди не будут так обеспокоены этой ошибкой.

Вы можете использовать демон настройки фона, например, реестр Windows. Демон записывает все конфиги в один большой файл. Он может вызвать fsync () после записи всего. Проблема решена ... для ваших конфигов. Теперь вам нужно сделать то же самое для всего остального, что пишут ваши приложения: текстовые документы, изображения, что угодно. Я имею в виду, что практически любой процесс Unix создает файл. Это чертовски основа всей идеи Unix!

Ясно, что это не жизнеспособный путь. Таким образом, ответ остается: нет решения на вашей стороне. Продолжайте беспокоить Tso и других разработчиков FS, пока они не исправят свои ошибки.

1 голос
/ 20 марта 2009

Мой собственный ответ - сохранить изменения временных файлов, и после завершения их записи выполните одну функцию fsync (), а затем переименуйте их все.

0 голосов
/ 19 сентября 2015

Проблема, на которую вы ссылаетесь, хорошо изучена, вы должны обязательно прочитать это: https://www.academia.edu/9846821/Towards_Efficient_Portable_Application-Level_Consistency

Fsync может быть пропущен при безопасное переименование поведение и каталог fsync может быть пропущен при безопасный новый файл поведение. И то, и другое зависит от реализации и не гарантируется POSIX.

0 голосов
/ 20 марта 2009

Вам необходимо поменять местами 3 и 4 в вашем последнем списке - fsync(fd) использует дескриптор файла. и я не понимаю, почему это было бы особенно дорого - вы все равно хотите, чтобы данные записывались на диск функцией close (). Таким образом, стоимость будет одинаковой между тем, что вы хотите, и тем, что произойдет с fsync().

Если стоимость слишком велика, (и она у вас есть) fdatasync(2) избегайте синхронизации метаданных, поэтому она должна быть дешевле.

EDIT: Поэтому я написал несколько очень хакерских тестовых кодов:

#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 (), поэтому преимущества в производительности слияния записей за минуту будут устранены.

...