LINUX.ORG.RU

Позволяет ли такой алгоритм откатить последнюю запись в файле в случае фейла питания?

 ,


2

5

(Ахтунг! Рассматриваем только исправное оборудование, файловую систему и ядро без глюков.)

Требуется добавлять записи переменной длины (от байта до сотни МБ) в конец файла так, чтобы при любом фейле питания можно было бы надёжно отличить последнюю ошибочную запись (если такая была) от предыдущих нормальных.

Решение такое:

запись
- пример: хотим добавить 76 кб данных
- добавляем в конец файла header фиксированного размера, в который пишем hash данных + размер данных (76*1024). Можно записать hash этого header прямо перед ним самим
- вызываем fsync() — это как барьер памяти, не позволяющий переупорядочить операции записи перед ним и за ним
- дописываем наши данные (76 kb payload)
- вызываем fsync()

чтение после сбоя
1. Открываем файл сначала и последовательно проверяем: если очередной header в неадеквате (файл кончился раньше размера header (он фиксир.), hash header-a не совпало) — считаем всё пространство файла начиная с header-а далее пустым. Потеряем только 1 запись, ведь в середине ничего не переписывали.
2. если header вменяем, но в файле осталось меньше данных чем сказано в header или данных хватило, но их hash не совпал с указанным в header - пункт (1) - считаем файл пустым, начиная с начала header

Данная фигня как-бэ гарантирует, что мы можем потерять только последнюю запись и надёжно её выкинуть без утечек «дисковой памяти» и можем продолжить дописывать в файл с правильного места, не создав далее ошибок для его разбора. Обсудите, так ли это?

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

Lavos ★★★★★
()
Ответ на: комментарий от Lavos

fsync() я дёргаю 50 раз как дебил затем, что ядру/ФС никто не мешает переупорядочить запись: оно может успешно записать header последующей записи и последующую запись, а потом начать записывать предыдущую запись и обломиться. Так я потеряю 2 записи. Хотя это маловероятно наверное в случае больших payload.

Я имею ввиду:

write header1
write data1
write header2
write data2

может реализоваться такой цепочкой событий, если нет fsync():

write header2
write data2
write data1
FAIL

hlamotron
() автор топика
Ответ на: комментарий от tailgunner

То есть ты понял, что порядок записи неважен?

Да, неважен порядок между write(header1) и write(data1), лишь бы оба записались. А потом вот можно сделать fsync() перед новой записью.

hlamotron
() автор топика
Ответ на: комментарий от Lavos

если у него append-only лог который надо редко читать то зачем? В противном случае индекс можно держать в памяти.

Deleted
()

Первое, о чём я подумал - не изобретать велосипед и заводить для каждой записи отдельный файл, переименовывая его после close(). Очень просто и очень надёжно, авторы ФС умные люди и уже давно всё продумали и на все грабли понаступали.

legolegs ★★★★★
()
Ответ на: комментарий от legolegs

авторы ФС умные люди и уже давно всё продумали и на все грабли понаступали.

Ты имеешь ввиду авторов btrfs, у которой метаданные сжирали полдиска за раз, или автора reiserfs, который продумал далеко не всё?

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

Автор reiserfs не продумал как избавиться от жены не оставив следов? Было ещё что-то? Она основана на принципе B-дерева, этого достаточно. А вот XFS в контексте обсуждения упомянуть более уместно, она как раз и устроена на схожем принципе.

anonymous
()
Ответ на: комментарий от WizardOfOz

Кэш HDD будет записан за счёт энергии раскрученых блинов.

legolegs ★★★★★
()
Ответ на: комментарий от WizardOfOz

Алгоритм складный.

А как насчет неконтролируемого ОС кэша HDD?

Вот насчет этого надо читать. Если HDD не гарантирует сброс кэша в случае сбоя питания и размер кэша больше размера записи, то нельзя гарантировать <= одной битой записи.
Тогда нужно уходить на отдельный накопитель для данных (флеш без кэша), возможно стоит и без ФС на нем тогда обойтись (и забыть плясках и спорах вокруг fsync).

Tayler ★★
()
Ответ на: комментарий от hateyoufeel

Ты имеешь ввиду авторов btrfs

Этих я вообще ввиду не имею, странно, что они не использовали nodejs или php вместо си, это бы было гармонично.

legolegs ★★★★★
()

Открываем файл сначала и последовательно проверяем

А если файл разрастется?

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

Данные в файле, кстати, могут испортиться и в середине: https://en.wikipedia.org/wiki/Data_corruption#Silent . То есть для увеличения надежности нужно держать еще какую-то избыточность.

В этом вопросе хорошо бы вовремя остановиться. Для многих приложений держать хэши данных уже лишнее

Deleted
()

В звисимости от выбранной ФС у тебя может случиться всякое, начиная от битого файла, заканчивая разрушением ФС.

Я бы сделал файл фиксированной длины, который пишется по кольцу блоками одинаковой длины(одна запись может занимать несколько блоков но не меньше одного) + 4(четыре) файла с картами (фиксированной длины, пишем по кольцу).

Алгоритм добавления записи такой:

1.Запись пишем поблочно:
  а) Обновляем карту в первом индексном файле, делаем fsync/close
  б) Обновляем карту во втором индексном файле, делаем fsync/close
  в) Пишем блок в архивный файл, делаем fsync/close.
  г) Обновляем карту в третьем индексном файле, делаем fsync/close
  д) Обновляем карту в четвертом индексном файле, делаем fsync/close

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

shkolnick-kun ★★★★★
()
Последнее исправление: shkolnick-kun (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.