Сформированы корректирующие обновления для всех поддерживаемых веток PostgreSQL: 11.2, 10.7, 9.6.12, 9.5.16 и 9.4.21, в которых исправлено около 70 ошибок. Наиболее значительным изменением стала переработка механизма использования вызова fsync() для обеспечения целостности записываемых на диск данных.

Оказалось, что вызов fsync() некорректно используется в PostgreSQL уже около 20 лет, что потенциально могло приводить в Linux, NetBSD и OpenBSD к потере записываемых данных в случае аппаратных сбоев. Разработчики PostgreSQL полагали, что успешно завершившийся вызов fsync() гарантирует, что поступившие данные записаны на постоянный носитель, но оказалось, что существуют ситуации когда это не так.

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

Так как буферы были очищены повторный вызов будет завершён успешно и PostgreSQL посчитает, что все данные записаны успешно. Но на деле, при чтении блоков, которые PostgreSQL полагает записанными, будет возвращено не то, что ожидается. Проблема усугубляется тем, что в разных операционных системах fsync() ведёт себя по разному и логичным кажется поведение, когда данные не отбрасываются при первой же неудачной попытке записи, а сохраняются в памяти в состоянии «dirty» для повторения попыток записи.

Начиная с выпусков PostgreSQL 11.2, 10.7, 9.6.12, 9.5.16 и 9.4.21 логика обработки ошибок fsync() изменена и PostgreSQL теперь не пытается после сбоя выполнения fsync() повторно вызвать fsync(), а завершается с выдачей фатальной ошибки. Данный шаг даёт возможность при перезапуске восстановить корректное состояние данных на основе WAL-лога, минуя скрытое повреждение содержимого базы. Подобная логика обработки ошибки может показаться неоптимальной, но разработчики сочли данное решение достаточным так как указанные проблемы возникают крайне редко.

Для систем, ядро которых не сбрасывает содержимое буфера записи после сбоя, в настройки добавлена опция data_sync_retry, позволяющая вернуть старое поведение с двойным вызовом fsync(). Например, FreeBSD не очищает состояние и при повторном вызове fsync() повторно выведет ошибку.

Поведение с очисткой буфера записи после ошибки объясняется тем, что в подавляющем большинстве случаев ошибки ввода/вывода возникают из-за удаления USB-накопителя без отмонтирования. Без очистки ситуация, когда какой-то процесс продолжает пытаться записать большой объём данных, приведёт к накапливанию страниц в состоянии «dirty», вплоть до исчерпания доступной памяти. Очистка же помогает сохранить работоспособность системы в подобных ситуациях.

Дополнительно можно отметить, что факт ошибки не всегда удаётся отследить. Например, если ошибка ввода/вывода возникла до открытия файла, то fsync() завершится успешно. Компания Google для обхода описанной проблемы использует альтернативный метод обработки ошибок ввода/вывода, основанный на сборе сведений об ошибках напрямую из ядра через netlink-сокет. Другим вариантом является использование прямого ввода/вывода (DIO), который предоставляет дополнительные механизмы для отслеживания сброса данных на диск и контроля за активностью ввода/вывода.