Pull to refresh

Comments 8

Наверняка такая проблема может возникнуть и для других БД в Linux?

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


Программа 1:


open()
write()
close() // Данные переданы в подсистему виртуальной памяти и находятся в очереди на запись


Программа 2:


fsync() // ошибка при записи на диск, программа 2 получает об этом уведомление, хотя потерянные данные были записаны программой 1


Программа 1:


fsync() // нет ошибок, хотя они на самом деле были


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




Минимум, который нужен — это чтобы программа 1 (СУБД) получила уведомление, что данные не сброшены успешно и откатила транзакцию с ошибкой. А сейчас она в такой ситуации репортит клиенту, что транзакция успешно выполнена.

Мда… нехилый камушек в огород Линукса. Были случаи когда из-за аппаратного сбоя слетала ФС полностью у людей и это отворачивало от Линукса надолго, хотя у меня не встречалось: даже выключал многократно на горячую ПК — всё загружалось потом без проблем. Надеюсь, что как-то поправят.

Транзакция давно завершена к тому времени, откатить уже ничего нельзя. Речь идёт о процессе checkpointer, который периодически сбрасывает модифицированные блоки на диск из общего пула в оперативной памяти. Для подтверждения транзакции необходимо и достаточно чтобы fsync() при записи в WAL (write ahead log) выполнился успешно. И то, если синхронизация не отключена в конфиге.


Так вот, после того как checkpointer успешно записал все изменённые блоки на диск, считается что старые файлы WAL, содержащие эти изменения, уже можно начать переиспользовать. И если окажется что датаблоки в реальности были записаны криво, или вообще не записаны (а мы то про это даже и не знаем!), и WAL файлы уже переписаны новыми транзакциями, то мы получаем потерянные обновления и неконсистентную БД.

fsync() не может вернуть «нет ошибок» когда они были, проблема в другом — если один fsync() дал ошибку и это не проверили, то следующий успешный скажет «всё ок», таким образом «теряя» ошибку.

Так что проблема на самом деле в программах — если проверять на ошибки каждый fsync() (как и нужно, собственно, делать) — то проблемы собственно нет, жалобы идут на то что fsync() не level-triggered в случае ошбок и это не документировано, а авторы предположили что fsync() сбросит всё что не было сброшено раньше, в то время как сброс идёт только того что было после последнего fsync().

По идее она может возникнуть, если не использовать DIRECT_IO, о чем в статье написано (см DIO).

Помнится мне, в минорном релизе 10.7, и 11.2 (дата выпуска: 2019-02-14) было одно исправление по этой проблеме:
www.postgresql.org/docs/10/release-10-7.html
Во избежание повреждения данных вместо повторения вызова fsync() при ошибке теперь выполняется аварийный останов (Крейг Рингер, Томас Мунро)

В ряде популярных операционных систем ядро сбрасывает буферы данных, не имея возможности сохранить их на диске, и выдаёт ошибку в fsync(). При этом повторный вызов fsync() будет успешным, но на самом деле данные уже потеряны, так что продолжение работы сервера чревато повреждением базы. В случае же аварийного останова мы можем воспроизвести данные из WAL, где в такой ситуации может остаться единственная копия данных. Хотя это, определённо, неэффективное и не очень красивое поведение, других вариантов нет, а подобные ситуации, к счастью, крайне редки.

Для управления этим поведением был добавлен новый параметр сервера data_sync_retry; если вы уверены, что ядро вашей ОС не теряет незаписанные буферы данных при описанном сценарии, вы можете задать для data_sync_retry значение on и восстановить прежнее поведение.
www.postgresql.org/docs/10/runtime-config-error-handling.html#GUC-DATA-SYNC-RETRY
Интересно, а под Windows — постгресс ведет себя также?
Sign up to leave a comment.

Articles

Change theme settings