Надежность и журнал упреждающей записи
В этой главе объясняется, как с помощью журнала упреждающей записи (Write Ahead Log, WAL) обеспечивается эффективная и надежная работа.
Надежность
Надежность является важным свойством любой серьезной СУБД, и QHB делает все возможное, чтобы гарантировать надежную работу. Одним из аспектов надежной работы является то, что все данные, записанные зафиксированной транзакцией, должны храниться в энергонезависимой области, защищенной от потери питания, сбоя операционной системы и аппаратного сбоя (разумеется, за исключением отказа самой энергонезависимой области). Успешная запись данных в постоянное хранилище компьютера (диск или равнозначный носитель) обычно соответствует этому требованию. На самом деле, даже в случае серьезного повреждения компьютера, если диски уцелели, их можно перенести на другой компьютер с похожими компонентами, и все зафиксированные транзакции останутся нетронутыми.
Хотя принудительное периодическое сохранение данных на пластинах диска может показаться простой операцией, это не так. Поскольку диск работает значительно медленнее, чем основная память и процессор, между основной памятью компьютера и пластинами диска существует несколько уровней кэширования. Во-первых, это буферный кэш операционной системы, который кэширует часто запрашиваемые дисковые блоки и объединяет записи на диск. К счастью, все операционные системы предоставляют приложениям возможность принудительно записывать из буферного кэша на диск, и QHB использует эти особенности. (Настройку этого процесса см. в описании параметра wal_sync_method.)
Далее, кэш может быть в дисковом контроллере; особенно это распространено на RAID-контроллерах. В некоторых случаях такое кэширование проводится в формате сквозной записи, то есть данные отправляются на диск сразу после поступления. Другие проводятся в формате обратной записи, то есть данные отправляются на диск через некоторое время. Такие кэширования могут представлять угрозу надежности, поскольку память в кэше дискового контроллера энергозависима и потеряет свое содержимое при сбое питания. Качественные контроллеры снабжены блоками резервных батарей (Battery Backup Unit, BBU), то есть на плате контроллера имеется батарея, которая поддерживает питание кэша в случае потери питания системы. После восстановления питания данные будут корректно записаны на диски.
И, наконец, большинство дисков имеют внутренний кэш. Некоторые из них работают в формате сквозной записи, другие — обратной, и у дисковых кэшей с обратной записью те же проблемы, касающиеся потери данных, что и у кэшей дисковых контроллеров. Особенно часто кэши обратной записи, не выдерживающие сбоя питания, встречаются на дисках IDE и SATA потребительского уровня. Многие твердотельные накопители (SSD) также имеют энергозависимые кэши обратной записи.
Эти кэши обычно можно выключить, однако способ сделать это зависит от операционной системы и типа диска:
-
В Linux диски IDE и SATA можно запрашивать с помощью команды
hdparm -I
; кэширование записи включено, если рядом со строкой Write cache есть знак *. для выключения кэширования записи можно воспользоваться командойhdparm -W 0
. Диски SCSI можно запросить с помощью утилиты sdparm. Используйтеsdparm --get=WCE
, чтобы проверить, включен ли кэш записи, иsdparm --clear=WCE
, чтобы выключить его. -
В FreeBSD диски IDE можно запрашивать с помощью команды
atacontrol
, а кэширование записи выключается параметром hw.ata.wc=0 в файле /boot/loader.conf; диски SCSI можно запрашивать с помощью командыcamcontrol identify
, а кэширование записи запрашивается и изменяется с помощью утилиты sdparm, если она доступна. -
В Solaris кэширование записи на диск управляется командой
format -e
. (Файловая система Solaris ZFS безопасна при включенном кэшировании записи на диск, поскольку она применяет собственные команды сброса кэша на диск.) -
В macOS кэширования записей можно избежать, установив в параметре wal_sync_method значение fsync_writethrough.
Диски SATA последних моделей (те, которые следуют стандарту ATAPI-6 или более
новому) предлагают команду сброса кэша на диска (FLUSH CACHE EXT
), тогда как
диски SCSI уже давно поддерживают похожую команду SYNCHRONIZE CACHE
. Эти
команды недоступны для QHB напрямую, но некоторые файловые
системы (например ZFS, ext4 ) могут использовать их для сброса данных на пластины
на дисках с включенной обратной записью. К сожалению, такие файловые системы ведут
себя неоптимально при комбинировании с дисковыми контроллерами с BBU. В таких
системах команда синхронизации направляет все данные из кэша контроллера на диски,
что исключает большую часть преимуществ BBU. Для проверки, не ваш ли это случай,
можно запустить программу qhb_test_fsync. Если это так, преимущества BBU для
производительности можно восстановить, выключив барьеры записи в файловой системе
или переконфигурировав контроллер диска, если это возможно. Если барьеры записи
выключены, убедитесь, что батарея остается работоспособной; неисправная батарея
может привести к потере данных.
Когда операционная система отправляет запрос на запись оборудованию для хранения данных, у нее мало возможностей убедиться, что данные поступили в действительно энергонезависимую область хранилища. Скорее, это администратор обязан убедиться, что все компоненты хранилища обеспечивают целостность как данных и метаданных файловой системы. Избегайте дисковых контроллеров с кэшами записи без батарей резервного питания. На уровне диска выключите кэширование с обратной записью, если диск не может гарантировать, что данные будут записаны перед выключением. Если вы используете SSD, помните, что многие из них по умолчанию не выполняют команды сброса кэша на диск. Протестировать надежность поведения подсистемы ввода/вывода можно с помощью diskchecker.pl.
Еще один риск потери данных связан с самими операциями записи на пластины диска. Пластины диска, как правило, разделены на сектора, обычно по 512 байт каждый. Каждая физическая операция чтения или записи обрабатывает сектор целиком. Когда на диск поступает запрос на запись, он может быть кратен 512 байтам (QHB обычно записывает 8192 байт или 16 секторов за раз), и в процессе записи в любой момент может произойти сбой из-за потери питания, то есть некоторые из 512-байтовых секторов будут записаны, а другие нет. Чтобы защититься от таких сбоев, перед изменением фактической страницы на диске QHB периодически записывает полные образы страниц в постоянное хранилище WAL. Благодаря этому во время восстановления после сбоя QHB может восстановить частично записанные страницы из WAL. Если ваша файловая система предотвращает частичную запись страниц (например, ZFS), можно выключить эту запись образов страниц, выключив параметр full_page_writes. Дисковые контроллеры с батареями резервного питания (BBU) не предотвращают частичную запись страниц, если не гарантируют, что данные записываются в BBU как полные (8 КБ) страницы.
QHB также защищает от некоторых видов повреждения данных на устройствах хранения, которые могут возникнуть из-за аппаратных ошибок или поломки носителя со временем, например при чтении/записи мусорных данных.
-
Каждая отдельная запись в файле WAL защищена проверкой CRC-32 (32-разрядная версия), которая позволяет определить правильность содержимого записи. Значение CRC устанавливается при записи каждой записи WAL и проверяется во время восстановления после сбоя, восстановления из архива и репликации.
-
В настоящее время страницы данных по умолчанию не имеют контрольных сумм, хотя образы страниц, сохраненные в записях WAL, будут защищены; подробную информацию о включении контрольных сумм для страниц данных см. на справочной странице утилиты qhb_bootstrap.
-
Внутренние структуры данных, например pg_xact, pg_subtrans, pg_multixact, pg_serial, pg_notify, pg_stat, pg_snapshots, напрямую не проверяются при помощи контрольных сумм, а страницы не защищены записями полных страниц. Однако там, где такие структуры данных являются постоянными, пишутся записи WAL, которые позволяют точно воссоздать последние изменения при восстановлении после сбоя, и эти записи WAL защищены так же, как описывалось выше.
-
Отдельные файлы состояний в pg_twophase защищены CRC-32.
-
Временные файлы данных, используемые в больших запросах SQL для сортировки, материализации и промежуточных результатов, в настоящее время не проверяются, и изменения в этих файлах не будут записываться в WAL.
QHB не защищает от исправляемых ошибок памяти, и предполагается, что вы будете работать с ОЗУ, которое использует отраслевой стандарт кодов исправления ошибок (Error Correcting Codes, ECC) или более эффективную защиту.
Контрольные суммы данных
По умолчанию страницы данных не защищены контрольными суммами, но при необходимости их можно включить для кластера. В таком случае каждая страница данных будет содержать контрольную сумму, рассчитываемую при записи и проверяемую при каждом чтении страницы. Контрольными суммами защищены только страницы данных, но не внутренние структуры данных и временные файлы.
Контрольные суммы обычно включают при инициализации кластера с помощью утилиты qhb_bootstrap. Также контрольные суммы можно включить или выключить позднее, в остановленном кластере. Включить или выключить контрольные суммы данных можно на уровне всего кластера, но не для отдельной базы данных или таблицы.
Текущее состояние контрольных сумм в кластере можно узнать, просмотрев значение
неизменяемой конфигурационной переменной data_checksums с помощью команды
SHOW data_checksums
.
При попытке восстановить страницы после повреждения иногда может потребоваться обойти защиту, обеспечиваемую контрольными суммами. Для этого можно временно установить параметр конфигурации ignore_checksum_failure.
Включение контрольных сумм в остановленном кластере
Чтобы проверить, включить или выключить контрольные суммы данных в остановленном кластере, можно воспользоваться приложением qhb_checksums.
Журнал упреждающей записи (WAL)
Запись в журнал упреждающей записи (Write Ahead Log, WAL) — это стандартный метод обеспечения целостности данных. Подробное описание можно найти в большинстве (если не во всех) справочных и учебных пособиях по обработке транзакций. Если коротко, центральная концепция WAL заключается в том, что изменения в файлах данных (в которых находятся таблицы и индексы) должны записываться только после того, как эти изменения были запротоколированы в журнал, то есть после того как записи журнала, описывающие эти изменения, были сброшены в постоянное хранилище. Если следовать этой процедуре, нет необходимости сбрасывать страницы данных на диск при каждой фиксации транзакции, потому что мы знаем, что в случае сбоя сможем восстановить базу данных с помощью этого журнала: любые изменения, которые не были применены к страницам данных можно восстановить из записей журнала. (Это называется восстановлением с повтором транзакций, также известном как REDO).
Совет
Поскольку WAL восстанавливает содержимое файла базы данных после сбоя, журналируемые файловые системы не нужны для надежного хранения файлов данных или файлов WAL. Фактически издержки на журналирование могут снизить производительность, особенно если журналирование приводит к сбросу на диск данных файловой системы. К счастью, сброс данных на диск во время журналирования можно выключить с помощью параметра монтирования файловой системы, например data=writeback в файловой системе Linux ext3. Тем не менее журналируемые файловые системы увеличивают скорость загрузки после сбоя.
Использование WAL приводит к значительному сокращению количества операций записи на диск, поскольку для фиксации транзакции нужно сбросить на диск только файл журнала, а не каждый файл данных, измененный этой транзакцией. Файл журнала записывается последовательно, поэтому затраты на синхронизацию журнала намного ниже, чем затраты на сброс страниц файлов данных на диск. Это особенно верно для серверов, обрабатывающих множество небольших транзакций, затрагивающих разные части хранилища данных. Более того, когда сервер обрабатывает много небольших параллельных транзакций, одного вызова fsync на файл журнала может быть достаточно для фиксации многих транзакций.
WAL также позволяет поддерживать резервное копирование и восстановление на определенный момент времени в режиме онлайн, как описано в разделе Непрерывное архивирование и восстановление на момент времени (PITR). Архивируя данные WAL, можно поддерживать возврат к любому моменту времени, охваченному доступными данными WAL: мы просто устанавливаем предыдущую физическую резервную копию базы данных и воспроизводим изменения из журнала до нужного момента времени. Более того, физическому резервному копированию необязательно быть мгновенным снимком состояния базы данных — если оно выполнено некоторое время назад, то воспроизведение журнала WAL за этот период устранит любые внутренние несоответствия.
Асинхронная фиксация
Асинхронная фиксация — это дополнительная функция, позволяющая транзакциям завершаться гораздо быстрее за счет того, что в случае сбоя базы данных самые последние транзакции могут быть потеряны. Во многих приложениях это приемлемый компромисс.
Как описано в предыдущем разделе, фиксация транзакции обычно выполняется синхронно: сервер ожидает сброса записей WAL транзакции в постоянное хранилище, а затем возвращает клиенту оповещение об успешном выполнении. Таким образом, клиенту гарантируется, что транзакция, о фиксации которой было сообщено, будет сохранена даже в том случае, если после этого тут же произойдет сбой сервера. Однако для коротких транзакций эта задержка занимает основную часть общего времени транзакции. Выбор режима асинхронной фиксации означает, что сервер возвращает сообщение об успешной фиксации, как только транзакция логически завершается, т. е. до того, как сгенерированные ею записи WAL действительно попадают на диск. Это может значительно увеличить скорость обработки небольших транзакций.
Асинхронная фиксация транзакций приводит к риску потери данных. Существует короткое временное окно между сообщением клиенту о завершении транзакции и моментом, когда транзакция действительно фиксируется (то есть гарантированно не будет потеряна в случае сбоя сервера). Таким образом, асинхронную фиксацию транзакций нельзя использовать, если клиент будет выполнять внешние действия, исходя из предположения, что транзакция будет сохранена. Например, банку, разумеется, не стоит использовать асинхронную фиксацию для транзакции, записывающей выдачу наличных в банкомате. Но во многих сценариях, например, при протоколировании событий в журнал, нет необходимости в серьезных гарантиях такого рода.
Риск, связанный с использованием асинхронной фиксации транзакций, — это потеря данных, а не их повреждение. При сбое базы данных она восстановится путем воспроизведения WAL вплоть до последней сброшенной на диск записи. Поэтому база данных будет восстановлена в самосогласованном состоянии, но любые транзакции, которые еще не были записаны на диск, в этом состоянии отражены не будут. Таким образом, суммарный эффект заключается в потере последних нескольких транзакций. Поскольку транзакции воспроизводятся в порядке фиксации, согласованность не нарушается — например, если транзакция B внесла изменения, полагаясь результаты предыдущей транзакции A, не может быть такого, что результаты A были утеряны, а результаты B сохранены.
Пользователь может выбрать режим фиксации каждой транзакции, что дает возможность параллельно запускать транзакции с синхронной и асинхронной фиксацией. Это обеспечивает гибкий компромисс между производительностью и уверенностью в прочности транзакций. Режим фиксации управляется устанавливаемым пользователем параметром synchronous_commit, который можно менять любым из способов настройки параметра конфигурации. Режим, используемый для любой отдельной транзакции, зависит от значения synchronous_commit на момент начала фиксации транзакции.
Некоторые служебные команды, например DROP TABLE
, принудительно фиксируются
синхронно, независимо от значения параметра synchronous_commit. Это
необходимо для обеспечения согласованности между файловой системой сервера и
логическим состоянием базы данных. Команды, поддерживающие двухфазную фиксацию,
например PREPARE TRANSACTION
, также всегда являются синхронными.
Если во время окна риска между асинхронной фиксацией и записью изменений в WAL транзакции происходит сбой базы данных, изменения, сделанные во время этой транзакции, будут потеряны. Длительность окна риска ограничена, поскольку фоновый процесс (процесс записи WAL) сбрасывает новые записи WAL на диск каждые wal_writer_delay миллисекунды. Фактическая максимальная длительность окна риска в три раза больше wal_writer_delay, так как процесс записи WAL предназначен для записи целых страниц за раз во время периодов активности.
ВНИМАНИЕ!
Выключение в немедленном режиме равнозначно сбою сервера и, следовательно, приведет к потере любых несохраненных асинхронных фиксаций.
Асинхронная фиксация обеспечивает поведение, отличное от установки fsync = off. Параметр fsync применяется ко всему серверу и будет изменять поведение всех транзакций. Он выключает всю логику в QHB, которая пытается синхронизировать записи в различные части базы данных, и, следовательно, сбой системы (то есть сбой оборудования или операционной системы, а не самой QHB) может привести к сколь достаточно значительному повреждению состояния базы данных. Во многих сценариях асинхронная фиксация обеспечивает улучшение производительности, почти не уступающее тому, которое можно получить, выключив fsync, но без риска повреждения данных.
Действие параметра commit_delay тоже очень похоже на асинхронную фиксацию, но на самом деле это метод синхронной фиксации (фактически commit_delay игнорируется во время асинхронной фиксации). commit_delay вызывает задержку непосредственно перед тем, как транзакция сбрасывает WAL на диск, в надежде, что одиночный сброс, выполненный одной такой транзакцией, может также обслуживать другие транзакции, фиксируемые примерно в то же время. Этот параметр можно рассматривать как способ увеличения временного окна, в котором транзакции могут присоединиться к группе, собирающейся участвовать в одиночном сбросе, чтобы распределить стоимость сброса между несколькими транзакциями.
Конфигурация WAL
Существует несколько параметров конфигурации, связанных с WAL, которые влияют на производительность базы данных. В этом раздел рассказывается об их использовании. Общую информацию об установке параметров конфигурации сервера см. в главе Конфигурация сервера.
Контрольные точки — это точки в последовательности транзакций, в которых гарантируется, что в файлы кучи и индексов попала вся информация об изменениях, записанная до этой контрольной точки. Во время контрольной точки все грязные страницы данных сбрасываются на диск, и в файл журнала записывается специальная запись контрольной точки. (Записи изменений были до этого сброшены в файлы WAL). В случае сбоя процедура восстановления после сбоя находит последнюю запись контрольной точки, чтобы определить точку в журнале (называемую записью REDO), с которой она должна начать операцию REDO. Любые изменения, внесенные в файлы данных до этого момента, гарантированно уже находятся на диске. Следовательно, после контрольной точки сегменты журнала, предшествующие тому, в котором содержится запись REDO, больше не нужны и могут быть использованы повторно или удалены. (Когда архивация WAL завершается, сегменты журнала должны быть заархивированы перед повторным использованием или удалением).
Требование контрольной точки — сброс всех грязных страниц данных на диск — может стать причиной значительной нагрузки на ввод/вывод. По этой причине активность контрольной точки регулируется таким образом, чтобы операции ввода/вывода начинались при запуске контрольной точки и завершались до начала следующей контрольной точки; это минимизирует снижение производительности во время контрольных точек.
Процесс контрольной точки сервера время от времени автоматически выполняет
контрольную точку. Контрольная точка производится каждые checkpoint_timeout
секунд или если вот-вот будет превышено значение max_wal_size, в зависимости
от того, что произойдет раньше. Значения по умолчанию — 5 минут и 1 ГБ
соответственно. Если с предыдущей контрольной точки не было записи в WAL, новые
контрольные точки будут пропущены, даже если прошло время checkpoint_timeout.
(Если используется архивация WAL, и вы хотите установить более низкий предел
частоты архивирования файлов, чтобы ограничить возможную потерю данных, следует
настроить параметр archive_timeout, а не параметры контрольной точки). Также
можно принудительно установить контрольную точку с помощью команды SQL CHECKPOINT
.
Уменьшение checkpoint_timeout и/или max_wal_size приводит к тому, что контрольные точки случаются чаще. Это позволяет быстрее восстанавливаться после сбоя, так как потребуется меньше работы. Тем не менее следует сопоставить это с возросшими затратами на более частый сброс грязных страниц данных на диск. Если установлен параметр full_page_writes (по умолчанию это так), необходимо учитывать еще один фактор. Чтобы обеспечить согласованность страницы данных, первая модификация страницы данных после каждой контрольной точки приводит к протоколированию в журнал всего содержимого этой страницы. В этом случае чем меньше интервал между контрольными точками, тем больше объем вывода в журнал WAL, что частично сводит на нет смысл уменьшенного интервала и в любом случае приводит к увеличению дискового ввода/вывода.
Контрольные точки довольно затратны, во-первых, потому что они требуют записи всех
текущих грязных буферов на диск, а во-вторых, потому что из-за них, как описано
выше, создается дополнительный трафик WAL. Поэтому целесообразно устанавливать
параметры контрольных точек достаточно высокими, чтобы контрольные точки не
происходили слишком часто. Для простой проверки работоспособности параметров
контрольной точки можно установить параметр checkpoint_warning. Если контрольные
точки следуют друг за другом чаще, чем через каждые checkpoint_warning секунд,
в журнал сервера будет выведено сообщение с рекомендацией увеличить значение
max_wal_size. Единичные появления такого сообщения не являются поводом для
тревоги, но если оно возникает часто, следует увеличить значения управляющих
параметров контрольной точки. Массовые операции, например, перенос большого объема
данных с помощью COPY
, могут вызвать появление нескольких таких предупреждений,
если не установить в max_wal_size достаточно большое значение.
Чтобы избежать переполнения системы ввода/вывода всплеском интенсивности записи страниц, запись грязных буферов во время контрольной точки растягивается на некоторый промежуток времени. Этот период контролируется параметром checkpoint_completion_target, который задается как часть интервала между контрольными точками (конфигурируемого параметром checkpoint_timeout). Скорость ввода/вывода регулируется таким образом, чтобы контрольная точка заканчивалась по истечении заданной доли от checkpoint_timeout секунд или до превышения порога max_wal_size, в зависимости от того, что наступит раньше. При значении по умолчанию 0.9 можно ожидать, что QHB завершит каждую контрольную точку несколько раньше, чем запустится следующая запланированная контрольная точка (примерно на 90% выполнения предыдущей контрольной точки). Это максимально растягивает ввод/вывод, равномерно распределяя нагрузку ввода/вывода при контрольных точках в интервалах между ними. Недостатком этого является то, что растягивание контрольных точек влияет на время восстановления, поскольку необходимо будет хранить больше сегментов WAL, которые могут понадобиться для восстановления. Пользователю, обеспокоенному чрезмерной продолжительностью восстановления, может понадобиться уменьшить значение checkpoint_timeout так, чтобы контрольные точки происходили чаще, но при этом ввод/вывод по-прежнему растягивался на интервал между ними. Либо можно уменьшить значение checkpoint_completion_target, но в результате ввод/вывод временами будет более интенсивным (во время контрольной точки) а временами — менее (после завершения контрольной точки, но до следующей запланированной контрольной точки), так что этот вариант не рекомендуется. Хотя в checkpoint_completion_target можно задать значение до 1.0, лучше не делать его выше 0.9 (значение по умолчанию), поскольку контрольные точки включают в себя некоторые другие действия помимо записи грязных буферов. При значении 1.0 контрольные точки, скорее всего, не будут выполнены вовремя, что приведет к снижению производительности из-за неожиданного изменения количества необходимых сегментов WAL.
На платформах Linux и POSIX checkpoint_flush_after позволяет принудительно заставить ОС сбрасывать страницы, записанные контрольной точкой, на диск после определенного количества байтов. В противном случае эти страницы могут храниться в кэше страниц ОС, вызывая затормаживание при вызове fsync в конце контрольной точки. Этот параметр часто помогает уменьшить задержки транзакций, но также может негативно повлиять на производительность, особенно когда объем нагрузок больше, чем shared_buffers, но меньше, чем кэш страниц ОС.
Количество файлов сегментов WAL в каталоге pg_wal зависит от параметров min_wal_size и max_wal_size, а также от объема WAL, сгенерированного в предыдущих циклах контрольных точек. Когда старые файлы сегментов журнала становятся не нужны, они удаляются или перерабатываются (то есть переименовываются, чтобы стать будущими сегментами в пронумерованной последовательности). Если из-за кратковременного пика скорости вывода журнала превышается предел max_wal_size, ненужные файлы сегментов будут удаляться, пока система не опустится ниже этого предела. Находясь ниже этого предела, система повторно использует такое количество файлов WAL, которого достаточно для покрытия расчетной потребности до следующей контрольной точки, и удаляет остальные. Расчет основан на скользящем среднем количества файлов WAL, использованных в предыдущих циклах контрольных точек. Если фактическое использование превышает расчетное, то скользящее среднее немедленно увеличивается, поэтому оно в некоторой степени накапливает пиковое использование, а не среднее. Параметр min_wal_size задает минимальное количество файлов WAL для будущего использования; ровно столько WAL всегда будет использовано повторно, даже если система простаивает и оценка использования WAL предполагает, что сегментов требуется мало.
Независимо от max_wal_size, последние wal_keep_size мегабайт файлов WAL
плюс один дополнительный файл WAL хранятся всегда. Кроме того, если используется
архивация WAL, старые сегменты нельзя удалить или переработать, пока они
не заархивированы. Если архивация WAL не успевает за генерацией WAL или если
archive_command
или archive_library
неоднократно завершается с ошибкой, старые
файлы WAL будут накапливаться в pg_wal до тех пор, пока ситуация не
разрешится. Медленный или давший сбой резервный сервер, использующий слот
репликации, будет давать такой же эффект (см. подраздел Слоты репликации).
В режиме восстановления архива или в режиме резерва сервер периодически выполняет точки перезапуска, которые схожи с контрольными точками в обычной работе: сервер переносит все свое состояние на диск, обновляет файл pg_control, чтобы показать, что уже обработанные данные WAL не нужно снова сканировать, а затем перерабатывает все старые файлы сегментов журнала в каталоге pg_wal. Точки перезапуска не могут выполняться чаще, чем контрольные точки на основном сервере, потому что точки перезапуска могут выполняться только в записях контрольных точек. Точка перезапуска срабатывает при достижении записи контрольной точки, если прошло не менее checkpoint_timeout секунд с момента последней точки перезапуска или если размер WAL вот-вот превысит max_wal_size. Однако из-за ограничений на время выполнения точки перезапуска max_wal_size часто превышается во время восстановления вплоть до объема WAL, записываемого за один цикл контрольной точки. (В любом случае max_wal_size не является жестким лимитом, поэтому следует всегда оставлять значительный запас, чтобы избежать нехватки дискового пространства для файлов журнала).
Существуют две широко используемые внутренние функции WAL: XLogInsertRecord и XLogFlush. XLogInsertRecord используется для помещения новой записи в буферы WAL в разделяемой памяти. Если для новой записи нет места, XLogInsertRecord придется записать (переместить в кэш ядра) несколько заполненных буферов WAL. Это нежелательно, поскольку XLogInsertRecord используется при каждом низкоуровневом изменении базы данных (например при добавлении строки) в тот момент, когда на задействованных страницах данных удерживается эксклюзивная блокировка, поэтому операция должна быть максимально быстрой. Что еще хуже, запись в буферы WAL тоже может привести к созданию нового сегмента журнала, что занимает еще больше времени. Обычно буферы WAL должны записываться и сбрасываться на диск вызовом XLogFlush, который, по большей части, выполняется во время фиксации транзакции, чтобы обеспечить сброс записей транзакции в постоянное хранилище. В системах с высокой скоростью заполнения журнала вызовы XLogFlush могут происходить недостаточно часто, чтобы не давать XLogInsertRecord производить записи. В таких системах следует увеличить количество буферов WAL, изменив параметр wal_buffers. Если установлен параметр full_page_writes и система сильно нагружена, увеличение значения wal_buffers поможет сгладить скачки времени отклика в период, идущий сразу после каждой контрольной точки.
Параметр commit_delay определяет, на сколько микросекунд главный процесс групповой фиксации будет находиться в спящем режиме после получения блокировки в XLogFlush, пока ведомые процессы стоят в очереди после главного. Эта задержка позволяет другим серверным процессам добавлять свои записи фиксации в буферы WAL, чтобы все они были сброшены итоговой синхронизирующей операцией главного процесса. Спящего режима не будет, если не включен параметр fsync или если в данный момент активны транзакции менее чем в commit_siblings сеансах; таким образом, главный процесс не засыпает, когда маловероятно, что какой-либо другой сеанс в ближайшее время зафиксирует транзакцию. Обратите внимание, что на некоторых платформах разрешение запроса на переход в спящий режим составляет десять миллисекунд, поэтому любое ненулевое значение параметра commit_delay от 1 до 10000 микросекунд будет иметь одинаковый эффект. Также обратите внимание, что на некоторых платформах состояние сна может продолжаться немного дольше, чем указано в параметре.
Поскольку цель commit_delay состоит в том, чтобы распределить стоимость каждой операции сброса на диск между параллельно фиксирующимися транзакциями (возможно, за счет задержки транзакции), прежде чем выбрать разумное значение для этого параметра, необходимо количественно оценить эту стоимость. Чем выше эта стоимость, тем потенциально более эффективно commit_delay будет способствовать увеличению производительности транзакций (до некоторой степени). Для измерения среднего времени (в микросекундах), затрачиваемого на одну операцию по сбросу WAL на диск, можно воспользоваться программой qhb_test_fsync. Значение, составляющее половину выводимого этой утилитой среднего времени сброса на диск после однократной операции записи 8 КБ, зачастую является наиболее эффективным значением для параметра commit_delay, поэтому оно рекомендуется как отправная точка при оптимизации для конкретной рабочей нагрузки. Хотя настройка commit_delay особенно полезна, когда журнал WAL хранится на вращающихся дисках с высокой задержкой, преимущества могут быть значительными даже для носителей с очень быстрым временем синхронизации, таких как твердотельные накопители или массивы RAID с резервными батареями для кэш-записи, но это, безусловно, необходимо проверить при репрезентативной нагрузке. В таких случаях следует использовать более высокие значения commit_siblings, тогда как меньшие значения commit_siblings часто полезны на носителях с более высокой задержкой. Обратите внимание, что слишком большое значение commit_delay вполне может увеличить задержку транзакции настолько, что пострадает общая производительность транзакций.
Когда commit_delay равен нулю (по умолчанию), групповая фиксация все еще может произойти, но каждая группа будет состоять только из сеансов, достигших точки, где они должны сбрасывать свои записи фиксации на диск в тот промежуток времени, когда выполняется предыдущая операция сброса (если таковая выполняется). Если клиентов больше, то, как правило, между сеансами возникает «давка», и эффект от групповой фиксации становится значительным, даже когда commit_delay равен нулю. Таким образом, явная установка commit_delay может и не оказать особого влияния на производительность. Установка commit_delay может помочь, только когда есть несколько параллельно фиксирующихся транзакции либо производительность до некоторой степени ограничена скоростью фиксации транзакции. Тем не менее при высокой вращательной задержке диска этот параметр может быть полезен для увеличения производительности транзакций даже всего с двумя клиентами.
Параметр wal_sync_method определяет, как QHB будет запрашивать у ядра принудительную запись изменений WAL на диск. Все параметры должны быть одинаковыми с точки зрения надежности, за исключением fsync_writethrough, который иногда может вызвать сброс дискового кэша, даже если другие параметры этого не делают. Однако что из этого будет быстрее, зависит от платформы, на которой работает QHB. Скорость при различных методах можно протестировать с помощью утилиты qhb_test_fsync. Обратите внимание, что этот параметр не имеет значения, если был выключен fsync.
Включение параметра конфигурации wal_debug (при условии, что QHB была скомпилирована с его поддержкой) приведет к тому, что каждый связанный с WAL вызов XLogInsertRecord и XLogFlush будет протоколироваться в журнал сервера. В будущем этот параметр может быть заменен на более общий механизм.
Имеются две внутренние функции для записи данных WAL на диск: XLogWrite и issue_xlog_fsync. Когда включен параметр track_wal_io_timing, общее время, затраченное XLogWrite на запись данных WAL на диск, а issue_xlog_fsync на их синхронизацию с диском, рассчитывается в представлении pg_stat_wal как wal_write_time и wal_sync_time соответственно. Функцию XLogWrite обычно вызывают функции XLogInsertRecord (когда в буферах WAL нет места для новой записи) и XLogFlush и процесс записи WAL, чтобы записать буферы WAL на диск и вызвать issue_xlog_fsync. Функцию issue_xlog_fsync обычно вызывает XLogWrite, чтобы синхронизировать файлы WAL с диском. Если параметр wal_sync_method имеет значение open_datasync или open_sync, операция записи в XLogWrite обеспечивает синхронизацию записанных данных WAL с диском, а issue_xlog_fsync ничего не делает. Если же wal_sync_method имеет значение fdatasync, fsync или fsync_writethrough, то операция записи перемещает буферы WAL в кэш ядра, а issue_xlog_fsync синхронизирует их с диском. Независимо от значения параметра track_wal_io_timing, в полях wal_write и wal_sync представления pg_stat_wal также отображается, сколько раз XLogWrite записывает данные WAL на диск и сколько раз issue_xlog_fsync синхронизирует их с диском соответственно.
Для уменьшения времени ожидания ввода/вывода во время восстановления можно воспользоваться параметром recovery_prefetch, указав ядру инициировать чтение дисковых блоков, которые скоро понадобятся, но в данный момент отсутствуют в пуле буферов QHB. Значения параметров maintenance_io_concurrency и wal_decode_buffer_size ограничивают количество параллельных операций и объем предварительной выборки соответственно. По умолчанию в параметре recovery_prefetch установлено значение try, которое включает эту функциональность в системах, где доступна функция posix_fadvise.
Внутреннее устройство WAL
WAL включается автоматически; со стороны администратора не требуется никаких действий, кроме как обеспечивать соблюдение требований к дисковому пространству для журналов WAL и выполнять необходимую настройку (см. раздел Конфигурация WAL).
Записи WAL добавляются в журналы WAL по мере возникновения каждой новой записи. Позиция добавления описывается порядковым номером в журнале (Log Sequence Number, LSN), который представляет собой байтовое смещение в журналах, монотонно увеличивающееся с каждой новой записью. Значения LSN возвращаются с типом данных pg_lsn. Значения можно сравнивать для расчета объема данных WAL, которые их разделяют, поэтому они используются для оценки прогресса репликации и восстановления.
Журналы WAL хранятся в каталоге pg_wal, находящемся в каталоге данных, в виде набора файлов сегментов, обычно размером по 16 МБ каждый (но этот размер можно изменить, указав в параметре --wal-segsize утилиты qhb_bootstrap другое значение). Каждый сегмент делится на страницы, обычно по 8 кБ каждая (этот размер можно изменить с помощью параметра конфигурации --with-wal-blocksize). Заголовки записей журнала описываются в файле access/xlogrecord.h; содержимое записи зависит от типа запротоколированного события. В качестве имен файлам сегмента присваиваются постоянно растущие номера, начиная с 000000010000000000000000. При переполнении эти номера не зацикливаются, но чтобы исчерпать доступный запас номеров потребуется очень и очень много времени.
Бывает выгодно размещать журнал на диске, отличном от того, где находятся основные файлы базы данных. Это можно сделать, переместив каталог pg_wal в другое место (разумеется, при выключенном сервере) и создав символическую ссылку из исходного места в главном каталоге данных на новое.
Задача WAL — обеспечить выполнение записи в журнал до изменений записей базы данных, но этот порядок может быть нарушен дисковыми накопителями, которые ложно сообщают ядру об успешной записи, тогда как на самом деле они только кэшировали данные и еще не сохранили их на диске. Сбой питания в такой ситуации может привести к неисправимому повреждению данных. Администраторы должны позаботиться о том, чтобы диски, содержащие файлы журнала WAL QHB, не создавали подобные ложные оповещения. (См. раздел Надежность.)
После создания контрольной точки и сброса журнала на диск, позиция этой контрольной точки сохраняется в файле pg_control. Поэтому в начале восстановления сервер читает сперва pg_control а потом запись контрольной точки; затем он выполняет операцию REDO путем сканирования вперед от местоположения журнала, указанного в записи контрольной точки. Поскольку все содержимое страниц данных сохраняется в журнале при первом изменении страницы после контрольной точки (при условии, что включен параметр full_page_writes), все страницы, измененные после контрольной точки, будут восстановлены в согласованном состоянии.
Чтобы справиться с ситуацией, когда файл pg_control поврежден, следует поддерживать возможность сканирования существующих сегментов журнала в обратном порядке — от самого нового к самому старому, — чтобы найти последнюю контрольную точку. Это еще не реализовано. Файл pg_control достаточно мал (менее одной страницы диска), чтобы не страдать от проблемы частичной записи, и на момент написания этого раздела не было сообщений о сбоях баз данных исключительно из-за невозможности чтения самого pg_control. Таким образом, хотя теоретически это слабое место, по-видимому, на практике проблем с pg_control не выявлено.