Комментарии 8
Я думаю, она может возникнуть для любой программы вообще. Если я правильно понял, то сценарий ошибки такой:
Программа 1:
open()
write()
close() // Данные переданы в подсистему виртуальной памяти и находятся в очереди на запись
Программа 2:
fsync() // ошибка при записи на диск, программа 2 получает об этом уведомление, хотя потерянные данные были записаны программой 1
Программа 1:
fsync() // нет ошибок, хотя они на самом деле были
В качестве "программы 2" может быть просто тред ядра, который в фоновом режиме сбрасывает "грязные" страницы из виртуальной памяти на диск.
Минимум, который нужен — это чтобы программа 1 (СУБД) получила уведомление, что данные не сброшены успешно и откатила транзакцию с ошибкой. А сейчас она в такой ситуации репортит клиенту, что транзакция успешно выполнена.
Транзакция давно завершена к тому времени, откатить уже ничего нельзя. Речь идёт о процессе checkpointer, который периодически сбрасывает модифицированные блоки на диск из общего пула в оперативной памяти. Для подтверждения транзакции необходимо и достаточно чтобы fsync() при записи в WAL (write ahead log) выполнился успешно. И то, если синхронизация не отключена в конфиге.
Так вот, после того как checkpointer успешно записал все изменённые блоки на диск, считается что старые файлы WAL, содержащие эти изменения, уже можно начать переиспользовать. И если окажется что датаблоки в реальности были записаны криво, или вообще не записаны (а мы то про это даже и не знаем!), и WAL файлы уже переписаны новыми транзакциями, то мы получаем потерянные обновления и неконсистентную БД.
Так что проблема на самом деле в программах — если проверять на ошибки каждый fsync() (как и нужно, собственно, делать) — то проблемы собственно нет, жалобы идут на то что fsync() не level-triggered в случае ошбок и это не документировано, а авторы предположили что fsync() сбросит всё что не было сброшено раньше, в то время как сброс идёт только того что было после последнего fsync().
По идее она может возникнуть, если не использовать DIRECT_IO, о чем в статье написано (см DIO).
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
Сюрприз fsync() PostgreSQL