атомарность rename() и NFS?

Со ссылкой на: Является ли rename() атомарным?

Я спрашиваю что-то похожее, но не совсем то же самое, потому что я хочу знать, безопасно ли полагаться на атомарность rename() при использовании NFS?

Вот сценарий, с которым я имею дело: у меня есть «индексный» файл, который всегда должен присутствовать.

So:

  • Клиент создает новый файл
  • Клиент переименовывает новый файл поверх «старого» индексного файла.

Отдельный клиент:

  • Читает индексный файл
  • относится к структуре диска на основе индекса.

Это предполагает, что rename() является атомарным, что означает, что всегда будет «индексный» файл (хотя это может быть устаревшая версия из-за кэширования и синхронизации)

Однако проблема, с которой я столкнулся, заключается в том, что это происходит в NFS и работает, но некоторые из моих клиентов NFS иногда сообщают «ENOENT» — такого файла или каталога нет. (например, при сотнях операций, происходящих с интервалом в 5 м, мы получаем эту ошибку каждые пару дней).

Итак, я надеюсь, что кто-нибудь может просветить меня - действительно ли невозможно получить «ENOENT» в этом сценарии?

Причина, по которой я спрашиваю, заключается в этой записи в RFC 3530:

Операция RENAME должна быть атомарной для клиента.

Мне интересно, означает ли это, что просто клиент выдает переименование, а не клиент, просматривающий каталог? (Я согласен с кешированной/устаревшей структурой каталогов, но смысл этой операции в том, что этот файл всегда будет «присутствовать» в той или иной форме)

Последовательность операций (от клиента, выполняющего операцию записи):

21401 14:58:11 open("fleeg.ext", O_RDWR|O_CREAT|O_EXCL, 0666) = -1 EEXIST (File exists) <0.000443>
21401 14:58:11 open("fleeg.ext", O_RDWR) = 3 <0.000547>
21401 14:58:11 fstat(3, {st_mode=S_IFREG|0600, st_size=572, ...}) = 0 <0.000012>
21401 14:58:11 fadvise64(3, 0, 572, POSIX_FADV_RANDOM) = 0 <0.000008>
21401 14:58:11 fcntl(3, F_SETLKW, {type=F_WRLCK, whence=SEEK_SET, start=1, len=1}) = 0 <0.001994>
21401 14:58:11 open("fleeg.ext.i", O_RDWR|O_CREAT, 0666) = 4 <0.000538>
21401 14:58:11 fstat(4, {st_mode=S_IFREG|0600, st_size=42, ...}) = 0 <0.000008>
21401 14:58:11 fadvise64(4, 0, 42, POSIX_FADV_RANDOM) = 0 <0.000006>
21401 14:58:11 close(4)                 = 0 <0.000011>
21401 14:58:11 fstat(3, {st_mode=S_IFREG|0600, st_size=572, ...}) = 0 <0.000007>
21401 14:58:11 open("fleeg.ext.i", O_RDONLY) = 4 <0.000577>
21401 14:58:11 fstat(4, {st_mode=S_IFREG|0600, st_size=42, ...}) = 0 <0.000007>
21401 14:58:11 fadvise64(4, 0, 42, POSIX_FADV_RANDOM) = 0 <0.000006>
21401 14:58:11 fstat(4, {st_mode=S_IFREG|0600, st_size=42, ...}) = 0 <0.000007>
21401 14:58:11 fstat(4, {st_mode=S_IFREG|0600, st_size=42, ...}) = 0 <0.000007>
21401 14:58:11 read(4, "\3PAX\1\0\0O}\270\370\206\20\225\24\22\t\2\0\203RD\0\0\0\0\17\r\0\2\0\n"..., 42) = 42 <0.000552>
21401 14:58:11 close(4)                 = 0 <0.000013>
21401 14:58:11 fcntl(3, F_SETLKW, {type=F_RDLCK, whence=SEEK_SET, start=466, len=68}) = 0 <0.001418>
21401 14:58:11 pread(3, "\21@\203\244I\240\333\272\252d\316\261\3770\361#\222\200\313\224&J\253\5\354\217-\256LA\345\253"..., 38, 534) = 38 <0.000010>
21401 14:58:11 pread(3, "\21@\203\244I\240\333\272\252d\316\261\3770\361#\222\200\313\224&J\253\5\354\217-\256LA\345\253"..., 38, 534) = 38 <0.000010>
21401 14:58:11 pread(3, "\21\"\30\361\241\223\271\256\317\302\363\262F\276]\260\241-x\227b\377\205\356\252\236\211\37\17.\216\364"..., 68, 466) = 68 <0.000010>
21401 14:58:11 pread(3, "\21\302d\344\327O\207C]M\10xxM\377\2340\0319\206k\201N\372\332\265R\242\313S\24H"..., 62, 300) = 62 <0.000011>
21401 14:58:11 pread(3, "\21\362cv'\37\204]\377q\362N\302/\212\255\255\370\200\236\350\2237>7i`\346\271Cy\370"..., 104, 362) = 104 <0.000010>
21401 14:58:11 pwrite(3, "\21\302\3174\252\273.\17\v\247\313\324\267C\222P\303\n~\341F\24oh/\300a\315\n\321\31\256"..., 127, 572) = 127 <0.000012>
21401 14:58:11 pwrite(3, "\21\212Q\325\371\223\235\256\245\247\\WT$\4\227\375[\\\3263\222\0305\0\34\2049A;2U"..., 68, 699) = 68 <0.000009>
21401 14:58:11 pwrite(3, "\21\262\20Kc(!.\350\367i\253hkl~\254\335H\250.d\0036\r\342\v\242\7\255\214\31"..., 38, 767) = 38 <0.000009>
21401 14:58:11 fsync(3)                 = 0 <0.001007>
21401 14:58:11 fstat(3, {st_mode=S_IFREG|0600, st_size=805, ...}) = 0 <0.000009>
21401 14:58:11 open("fleeg.ext.i.tmp", O_RDWR|O_CREAT|O_TRUNC, 0666) = 4 <0.001813>
21401 14:58:11 fstat(4, {st_mode=S_IFREG|0600, st_size=0, ...}) = 0 <0.000007>
21401 14:58:11 fadvise64(4, 0, 0, POSIX_FADV_RANDOM) = 0 <0.000007>
21401 14:58:11 write(4, "\3PAX\1\0\0qT2\225\226\20\225\24\22\t\2\0\205;D\0\0\0\0\17\r\0\2\0\n"..., 42) = 42 <0.000012>
21401 14:58:11 stat("fleeg.ext.i", {st_mode=S_IFREG|0600, st_size=42, ...}) = 0 <0.000011>
21401 14:58:11 fchmod(4, 0100600)       = 0 <0.002517>
21401 14:58:11 fstat(4, {st_mode=S_IFREG|0600, st_size=42, ...}) = 0 <0.000008>
21401 14:58:11 close(4)                 = 0 <0.000011>
21401 14:58:11 rename("fleeg.ext.i.tmp", "fleeg.pax.i") = 0 <0.001201>
21401 14:58:11 close(3)                 = 0 <0.000795>
21401 14:58:11 munmap(0x7f1475cce000, 4198400) = 0 <0.000177>
21401 14:58:11 munmap(0x7f14760cf000, 4198400) = 0 <0.000173>
21401 14:58:11 futex(0x7f147cbcb908, FUTEX_WAKE_PRIVATE, 2147483647) = 0 <0.000010>
21401 14:58:11 exit_group(0)            = ?
21401 14:58:11 +++ exited with 0 +++

NB - Пути и файлы переименованы выше для согласованности. fleeg.ext — это файл данных, а fleeg.ext.i — индекс. Во время этого процесса - файл fleeg.ext.i перезаписывается (файлом .tmp), поэтому считается, что по этому пути всегда должен быть файл (либо старый, либо новый, который просто перезаписал его).

На клиенте reading PCAP выглядит так: LOOKUP Ошибка вызова NFS:

124   1.375777  10.10.41.35 -> 10.10.41.9   NFS 226   LOOKUP    fleeg.ext.i V3 LOOKUP Call, DH: 0x6fbbff3a/fleeg.ext.i
125   1.375951   10.10.41.9 -> 10.10.41.35  NFS 186 5347  LOOKUP  0775 Directory  V3 LOOKUP Reply (Call In 124) Error: NFS3ERR_NOENT
126   1.375975  10.10.41.35 -> 10.10.41.9   NFS 226   LOOKUP    fleeg.ext.i V3 LOOKUP Call, DH: 0x6fbbff3a/fleeg.ext.i
127   1.376142   10.10.41.9 -> 10.10.41.35  NFS 186 5347  LOOKUP  0775 Directory  V3 LOOKUP Reply (Call In 126) Error: NFS3ERR_NOENT

person Sobrique    schedule 28.12.2016    source источник
comment
Вы закрываете файл перед переименованием? Важно, чтобы он был закрыт первым.   -  person Maxim Egorushkin    schedule 30.12.2016
comment
Хм, глядя на strace, кажется, что это не так - «новый» файл создается вызовом open, а затем renameed во время открытия. Я проведу еще одну трассировку для подтверждения.   -  person Sobrique    schedule 30.12.2016
comment
@MaximEgorushkin - перезапустил трассировку и добавил ее в вопрос - файл закрывается до переименования.   -  person Sobrique    schedule 30.12.2016
comment
Какой системный вызов возвращает ENOENT, это open или read?   -  person Maxim Egorushkin    schedule 30.12.2016
comment
Сообщение об ошибке, которое у меня есть, указывает, что это open - хотя я менее уверен, так как у меня нет следов сбоя (это происходит недостаточно часто, чтобы я мог его поймать), просто ведение журнала уровня приложения.   -  person Sobrique    schedule 30.12.2016


Ответы (3)


Я думаю проблема не в том, что RENAME не является атомарным, а в том, что ОТКРЫТИЕ файла через NFS не является атомарным.

NFS использует дескрипторы файлов; чтобы что-то сделать с файлом, клиент сначала получает дескриптор файла через ПРОСМОТР, затем полученный дескриптор файла используется для выполнения других запросов. Требуется как минимум две дейтаграммы, и время между ними может, в определенных обстоятельствах, быть довольно "большим".

Я полагаю, что с вами происходит то, что клиент (client1) выполняет ПРОСМОТР; сразу после этого LOOKUPed файл стирается в результате RENAME (клиентом2); дескриптор файла client1 больше недействителен, поскольку он ссылается на индексный дескриптор, а не на именованный путь.

Причина всего этого в том, что NFS стремится быть без гражданства. Дополнительная информация в этом PDF-файле: http://pages.cs.wisc.edu/~remzi/OSTEP/dist-nfs.pdf

На страницах 6 и 8 это поведение хорошо объяснено.

person linuxfan says Reinstate Monica    schedule 30.12.2016

Должно ли быть невозможно получить ENOENT в этом сценарии?

Это вполне возможно. В RFC 3530 говорится:

Операция должна быть атомарной для клиента.

Скорее всего, это означает, что она должна быть атомарной для клиента, вызывающего эту операцию, а не для всех клиентов.

И далее в нем говорится:

Если целевой каталог уже содержит запись с именем... существующая цель удаляется до того, как произойдет переименование.

По этой причине другие клиенты иногда получают ENOENT.

Другими словами, rename не является атомарным в NFS.

person Maxim Egorushkin    schedule 30.12.2016

Думаю, теперь у меня есть ответ на вопрос, что происходит. Я добавляю это здесь, потому что, хотя другие очень помогли в этом, фактический корень проблемы заключается в следующем:

Хозяин чтения:

79542  10.643148 10.0.0.52 -> 10.0.0.24 NFS 222  ACCESS allowed   testfile  V3 ACCESS Call, FH: 0x76a9a83d, [Check: RD MD XT XE]
79543  10.643286 10.0.0.24 -> 10.0.0.52 NFS 194 0 ACCESS allowed 0600 Regular File testfile NFS3_OK V3 ACCESS Reply (Call In 79542), [Allowed: RD MD XT XE]
79544  10.643335 10.0.0.52 -> 10.0.0.24 NFS 222  ACCESS allowed     V3 ACCESS Call, FH: 0xe0e7db45, [Check: RD LU MD XT DL]
79545  10.643456 10.0.0.24 -> 10.0.0.52 NFS 194 0 ACCESS allowed 0755 Directory  NFS3_OK V3 ACCESS Reply (Call In 79544), [Allowed: RD LU MD XT DL]
79546  10.643487 10.0.0.52 -> 10.0.0.24 NFS 230  LOOKUP    testfile  V3 LOOKUP Call, DH: 0xe0e7db45/testfile
79547  10.643632 10.0.0.24 -> 10.0.0.52 NFS 190 0 LOOKUP  0755 Directory  NFS3ERR_NOENT V3 LOOKUP Reply (Call In 79546) Error: NFS3ERR_NOENT
79548  10.643662 10.0.0.52 -> 10.0.0.24 NFS 230  LOOKUP    testfile  V3 LOOKUP Call, DH: 0xe0e7db45/testfile
79549  10.643814 10.0.0.24 -> 10.0.0.52 NFS 190 0 LOOKUP  0755 Directory  NFS3ERR_NOENT V3 LOOKUP Reply (Call In 79548) Error: NFS3ERR_NOENT

Пишущий хост:

203306  13.805489  10.0.0.6 -> 10.0.0.24 NFS 246  LOOKUP    .nfs00000000d59701e500001030  V3 LOOKUP Call, DH: 0xe0e7db45/.nfs00000000d59701e500001030
203307  13.805687 10.0.0.24 -> 10.0.0.6  NFS 186 0 LOOKUP  0755 Directory  NFS3ERR_NOENT V3 LOOKUP Reply (Call In 203306) Error: NFS3ERR_NOENT
203308  13.805711  10.0.0.6 -> 10.0.0.24 NFS 306  RENAME    testfile,.nfs00000000d59701e500001030  V3 RENAME Call, From DH: 0xe0e7db45/testfile To DH: 0xe0e7db45/.nfs00000000d59701e500001030
203309  13.805982 10.0.0.24 -> 10.0.0.6  NFS 330 0,0 RENAME  0755,0755 Directory,Directory  NFS3_OK V3 RENAME Reply (Call In 203308)
203310  13.806008  10.0.0.6 -> 10.0.0.24 NFS 294  RENAME    testfile_temp,testfile  V3 RENAME Call, From DH: 0xe0e7db45/testfile_temp To DH: 0xe0e7db45/testfile
203311  13.806254 10.0.0.24 -> 10.0.0.6  NFS 330 0,0 RENAME  0755,0755 Directory,Directory  NFS3_OK V3 RENAME Reply (Call In 203310)
203312  13.806297  10.0.0.6 -> 10.0.0.24 NFS 246  CREATE    testfile_temp  V3 CREATE Call, DH: 0xe0e7db45/testfile_temp Mode: EXCLUSIVE
203313  13.806538 10.0.0.24 -> 10.0.0.6  NFS 354 0,0 CREATE  0755,0755 Regular File,Directory testfile_temp NFS3_OK V3 CREATE Reply (Call In 203312)
203314  13.806560  10.0.0.6 -> 10.0.0.24 NFS 246  SETATTR  0600  testfile_temp  V3 SETATTR Call, FH: 0x4b69a46a
203315  13.806767 10.0.0.24 -> 10.0.0.6  NFS 214 0 SETATTR  0600 Regular File testfile_temp NFS3_OK V3 SETATTR Reply (Call In 203314)

Это только воспроизводится, если вы открываете тот же файл для чтения, поэтому в дополнение к тривиальному циклу записи-переименования C:

#!/usr/bin/env perl

use strict;
use warnings;

while ( 1 ) {
  open ( my $input, '<', 'testfile' ) or warn $!;
  print ".";
  sleep 1;
}

Это приводит к тому, что мой тестовый пример быстро (минуты), а не вообще, по-видимому. Это файл '.nfsXXX', который создается при открытии дескриптора файла, а затем удаляется (или перезаписывается RENAME).

Поскольку NFS не имеет состояния, он должен иметь какое-то постоянное значение для клиента, чтобы он по-прежнему мог читать/записывать этот файл так же, как если бы он выполнял открытие/разъединение в локальной файловой системе. И для этого мы получаем двойной RENAME и очень короткий (менее миллисекунды) интервал, в течение которого файл, на который мы нацеливаемся, отсутствует для поиска LOOKUP NFS RPC.

person Sobrique    schedule 03.01.2017
comment
Что-то странное, или я что-то упускаю. Файловый дескриптор NFS должен постоянно ссылаться на один и тот же объект (более или менее индексный дескриптор). Первое переименование в вашей трассировке — 0xe0e7db45/testfile To DH: 0xe0e7db45/.nfs000, где я вижу, что дескриптор файла остается прежним. После этого первого переименования дескриптор по-прежнему относится к одному и тому же объекту, поэтому я не понимаю, почему потребуется ДВА переименования вместо одного простого атомарного переименования. - person linuxfan says Reinstate Monica; 04.01.2017
comment
Это происходит только в том случае, если на хосте-писателе открыт дескриптор файла для чтения. Переименование является атомарным для этого клиента, но поскольку происходит два переименования, удаленный клиент видит каталог, который — очень кратко — пуст между двумя событиями. - person Sobrique; 04.01.2017
comment
Я все еще думаю, что двойное переименование неправильно. Даже при открытом файле должно работать одно переименование, которое действительно сохраняет дескриптор файла. Я все еще что-то упускаю. - person linuxfan says Reinstate Monica; 04.01.2017
comment
Поскольку сервер NFS не имеет состояния, он должен иметь «что-то», на что может указывать дескриптор открытого файла. Это не может быть «новое» имя файла, потому что вы только что что-то переименовали поверх него, поэтому сначала нужно переименовать предыдущее. - person Sobrique; 04.01.2017
comment
Последний комментарий, обещаю. «Что-то», о котором вы говорите, должно быть дескриптором файла NFS, который ссылается (обычно) на индексный дескриптор. Вы получаете дескриптор файла, ссылающийся на myfile.txt, вы переименовываете файл, и дескриптор файла по-прежнему указывает на тот же объект, даже если он переименован. Итак, это двойное переименование кажется мне неправильным. - person linuxfan says Reinstate Monica; 04.01.2017
comment
Я не переименовываю «myfile.txt». Я переименовываю 'myfile.tmp' вместо 'myfile.txt', чтобы заменить его (и, таким образом, удалить его). Обычно это не проблема в Unix — дескрипторы файлов остаются там до тех пор, пока счетчик ссылок не упадет до нуля, даже если файл будет удален. Но NFS приходится иметь дело с перезагрузкой сервера или клиента. Таким образом, чтобы сохранить «myfile.txt» открытым — во время его перезаписи — сначала необходимо переименовать «открытую» копию (и сохранить FH). - person Sobrique; 04.01.2017
comment
Хост записи журнала: относится не к серверу, а к клиенту. Это, пожалуй, еще более странно. - person linuxfan says Reinstate Monica; 05.01.2017
comment
@Sobrique, какой инструмент отладки NFS вы использовали для создания трассировок узла чтения: и узла записи:? Они выглядят более читаемыми, чем то, что я видел до сих пор. - person Guillaume Papin; 10.04.2019
comment
@GuillaumePapin Это был просто «tshark», который поставляется в комплекте с wireshark, но это только текст. - person Sobrique; 06.06.2019