суббота, 5 февраля 2011 г.

Особенности конкурентной записи/чтения файлов в perl + NFS

Рассмотрю буквально парочку малоизвестных нюансов/подводных камней.

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

my $content = do {local (@ARGV, $/) = $filename; <ARGV> };
За объяснениями в perldoc perlvar  и поиск по ARGV.

Такой подход использовался и в File::Slurp. В данной ситуации не просходит flock, это важно учитывать если Вы этот файл еще и пишите другим процессом . Проблему конкурентной записи можно решить и без flock, просто нужно операцию записи сделать атомарной(File::Slurp поддерживает для этих целей флаг "atomic").

Идея заключается в следующем:
  1. Создается уникальный временный файл и в него пишутся данные
  2. Временный файл переименовывается в целевой файл. Операция rename в большинстве ОС - атомарная. 
И казалось бы все отлично, но лишь до того момента когда вы начнете использовать NFS :)

Операция rename в NFS - атомарная, но есть следующий нюанс. Если вы делаете rename и файл назначения уже существует, то сначала удаляется файл назначения, а затем лишь происходит rename. И это ДВЕ  ОТДЕЛЬНЫЕ операции.

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

4 коммент.:

antonfin комментирует...

> Проблему конкурентной записи можно решить и без flock, просто нужно операцию записи сделать атомарной(File::Slurp поддерживает для этих целей флаг "atomic").

Я, конечно, могу ошибаться, но по-моему проблему конкурентной записи File::Slurp не решает.

Приведу пример.
время 1. процесс 1 решает делает запись в файл 1.
время 2. процесс 1 создает файл 1.temp.
время 3. процесс 2 открывает файл 1 вычитывает данные из файла.
время 4. процесс 1 делает соответствующую запись в 1.temp.
время 5. процесс 2 закрывает файл 1.

1 вариант коллизии
время 6. процесс 2 удаляет файл 1.
время 7. процесс 1 переименовывает файл 1.temp в 1. (который был до этого удален! и по идеи не должен был существовать!)

2 вариант коллизии
время 6. процесс 1 переименовывает файл 1.temp в 1.
время 7. процесс 2 сохраняет устаревшие! данные в БД.

Реальным примером может служить почти одновременные запросы к серверу на сохранение новых настроек в файл сессии и прекращение сеанса пользователя, когда настройки из сессии должны транспортироваться в БД.

Действительно логика данного модуля (а точнее операция записи с флагом atomic), позволяет решить ряд проблем, которые могут возникнуть при конкурентном доступе процессов к одному файлу, но это далеко не замена flock.

koorchik комментирует...

2antonfin
Тоха, ты что-то напутал. Если не использовать atomic, то конечно можно повредить файл, который сейчас читает другой процесс. Но с опцией "atomic" - все гуд.

> 1 вариант коллизии
время 6. процесс 2 удаляет файл 1.


С чего это вдруг процесс 2 должен удалять файл? Он же просто хотел данные вычитать.
И даже если не использовать rename, то в другом процессе вызов open в режиме записи создаст файл.

Кроме того, если у тебя есть файл в который хотят писать 2 процесса, то ты тоже не можешь предсказать, кто первый сделает flock. Например, когда тебе нужно сбросить сессию на диск ты не знаешь, какая версия сессии сохранится даже если ты используешь flock.

flock полезен если ты хочешь атомарно вычитать, а потом записать(например, увеличение счетчика). Можно открыть файл в режиме "+<", либо использовать дополнительный файл, как семафор.

antonfin комментирует...

2koorchik,

Извиняюсь. Тут очень тонкая игра слов. Я воспринял "конкурентная запись", как "конкурентный доступ" (или "состязательная ситуация процессов").

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

Но, если у тебя процессы только пишут в файл и не важен порядок, то решение с модулем File::Slurp и флагом "atomic" подходит.

koorchik комментирует...

2antonfin:
> Но, если у тебя процессы только пишут в файл и не важен порядок, то решение с модулем File::Slurp и флагом "atomic" подходит.

Одни процессы могут писать файл, а другие в это время читать. Тут проблемы нет никакой.

Единственное, что когда ты хочешь объеденить 2 операции(чтение+запись одного файла, или запись в несколько разных файлов, или чтение с нескольких разных файлов) в одну транзакцию, тогда тебе нужен flock(последние абзац моего предыдущего комментария).

Отправить комментарий

Не забудьте добавить себя в постоянные читатели и включить уведомления о новых комментариях, либо воспользуйтесь RSS каналом ;)