27.5. Настройка WAL #

Существует несколько конфигурационных параметров, относящихся к WAL, которые влияют на производительность СУБД. Далее рассказывается об их использовании. Общую информацию об установке параметров конфигурации сервера смотрите в Главе 18.

Контрольные точки — это точки в последовательности транзакций, в которых гарантируется, что файлы с данными и индексами были обновлены всей информацией, записанной перед контрольной точкой. Во время выполнения контрольной точки все «грязные» страницы данных, находящиеся в памяти, сбрасываются на диск, а в файле WAL создаётся специальная запись контрольной точки. (Сделанные изменения были перед этим записаны в файлы WAL.) В случае сбоя процедура восстановления ищет последнюю запись контрольной точки, чтобы определить эту точку в WAL (называемую записью REDO), от которой процедура должна начать операцию воспроизведения изменений. Любые изменения файлов данных до этой точки уже гарантированно находятся на диске. Таким образом, после контрольной точки сегменты WAL, которые предшествуют записи 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, заданным по умолчанию, можно ожидать, что Postgres Pro завершит процедуру контрольной точки незадолго до следующей запланированной (примерно на 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, сгенерированного в предыдущих циклах контрольных точек. Когда старые файлы сегментов оказываются не нужны, они удаляются или перерабатываются (то есть переименовываются, чтобы стать будущими сегментами в нумерованной последовательности). Если вследствие кратковременного скачка интенсивности записи в WAL предел max_wal_size превышается, ненужные файлы сегментов будут удаляться, пока система не опустится ниже этого предела. Оставаясь ниже этого предела, система перерабатывает столько файлов WAL, сколько необходимо для покрытия ожидаемой потребности до следующей контрольной точки, и удаляет остальные. Эта оценка базируется на скользящем среднем числа файлов WAL, задействованных в предыдущих циклах контрольных точек. Скользящее среднее увеличивается немедленно, если фактическое использование превышает оценку, так что в нём в некоторой степени накапливается пиковое использование, а не среднее. Значение min_wal_size ограничивает снизу число файлов WAL, которые будут переработаны для будущего использования; такой объём WAL всегда будет перерабатываться, даже если система простаивает и оценка использования говорит, что нужен совсем небольшой WAL.

Вне зависимости от max_wal_size, последние файлы WAL в объёме wal_keep_size мегабайт и ещё один дополнительный файл WAL сохраняются в любом случае. Кроме того, если применяется архивация WAL, старые сегменты не могут быть удалены или переработаны, пока они не будут заархивированы. Если WAL архивируется медленнее, чем генерируется, либо если команда archive_library или библиотека archive_library постоянно даёт сбои, старые файлы WAL будут накапливаться в pg_wal, пока ситуация не будет разрешена. Медленно работающий или отказавший ведомый сервер, использующий слот репликации, даст тот же эффект (см. Подраздел 25.2.6).

В режиме восстановления архива или горячего резерва сервер периодически выполняет точки перезапуска, которые похожи на контрольные точки в обычном режиме работы: сервер принудительно сбрасывает своё состояние на диск, обновляет файл pg_control, чтобы показать, что уже обработанные данные WAL не нужно сканировать снова, и затем перерабатывает все старые файлы сегментов журнала в каталоге pg_wal. Точки перезапуска не могут выполняться чаще, чем контрольные точки на ведущем сервере, так как они выполняются только в записях контрольных точек. Точка перезапуска может выполняться по расписанию или по внешнему запросу. Счётчик restartpoints_timed в представлении pg_stat_checkpointer подсчитывает точки, выполняемые по расписанию, а счётчик restartpoints_req — точки, выполняемые по запросу. Точка перезапуска по расписанию выполняется, когда достигается запись контрольной точки и после предыдущей точки перезапуска прошло не меньше checkpoint_timeout секунд или при неудавшейся предыдущей попытке выполнить точку перезапуска. В последнем случае следующая точка перезапуска по расписанию выполняется через 15 секунд. Точка перезапуска по запросу начинает выполняться по тем же причинам, что и контрольная точка, но в основном, если размер WAL может превысить max_wal_size. Однако из-за того, что на время выполнения точек перезапуска накладываются ограничения, max_wal_size часто превышается при восстановлении вплоть до объёма WAL, записываемого в цикле между контрольными точками. (Значение max_wal_size не является строгим ограничением, поэтому рекомендуется иметь достаточно объёмный диск, чтобы не остаться без свободного места.) Счётчик restartpoints_done представления pg_stat_checkpointer подсчитывает количество действительно выполненных точек перезапуска.

В некоторых случаях, когда размер WAL на ведущем сервере быстро увеличивается, например при добавлении большого числа строк командой INSERT, счётчик точек перезапуска по запросу restartpoints_req на резервном сервере может показывать взрывной рост. Это происходит, потому что запросы на создание новой точки перезапуска из-за разрастания WAL не могут быть выполнены, так как безопасная запись контрольной точки с последней точки перезапуска ещё не была воспроизведена на резервном сервере. Это нормальное поведение, которое не приводит к повышенному потреблению ресурсов системы. Из всех связанных с точками перезапуска счётчиков на существенное потребление ресурсов системы указывает только restartpoints_done.

Наиболее часто используются две связанные с WAL внутренние функции: XLogInsertRecord и XLogFlush. XLogInsertRecord применяется для добавления записи в буферы WAL в разделяемой памяти. Если места для новой записи недостаточно, XLogInsertRecord придётся записать (переместить в кеш ядра) несколько заполненных буферов WAL. Это нежелательно, так как XLogInsertRecord используется при каждом изменении в базе данных на низком уровне (например, при добавлении строки) в момент, когда установлена исключительная блокировка задействованных страниц данных, поэтому данная операция должна быть максимально быстрой. Что ещё хуже, запись буферов WAL может также повлечь создание нового сегмента WAL, что займёт ещё больше времени. Обычно буферы WAL должны записываться и сохраняться на диске в функции XLogFlush, которая вызывается, по большей части, при фиксировании транзакции, чтобы результаты транзакции сохранились в надёжном хранилище. В системах с интенсивной записью в WAL вызовы XLogFlush могут иметь место не так часто, чтобы XLogInsertRecord не приходилось производить запись. В таких системах следует увеличить число буферов WAL, изменив параметр wal_buffers. Когда включён режим full_page_writes и система очень сильно загружена, увеличение wal_buffers поможет сгладить скачки во времени ответа в период сразу после каждой контрольной точки.

Параметр commit_delay определяет, на сколько микросекунд будет засыпать ведущий процесс группы, записывающий в журнал, после получения блокировки в XLogFlush, пока подчинённые формируют очередь на запись. Во время этой задержки другие серверные процессы смогут добавлять записи в WAL буферы журнала, чтобы все эти записи сохранились на диск в результате одной операции синхронизации, которую выполнит ведущий. Ведущий процесс не засыпает, если отключён режим fsync, либо число сеансов с активными транзакциями меньше commit_siblings, так как маловероятно, что какой-либо другой сеанс зафиксирует транзакцию в ближайшее время. Заметьте, что на некоторых платформах, разрешение этого таймера сна составляет 10 миллисекунд, так что любое значение параметра commit_delay от 1 до 10000 микросекунд будет действовать одинаково. Кроме того, в некоторых системах состояние сна может продлиться несколько дольше, чем требует параметр.

Так как цель commit_delay состоит в том, чтобы позволить стоимости каждой операции синхронизации амортизироваться через параллельную фиксацию транзакций (потенциально за счёт задержки транзакции), необходимо определить количество той стоимости, прежде чем урегулирование сможет быть выбрано разумно. Чем выше стоимость, тем более эффективный будет commit_delay в увеличении пропускной способности транзакций в какой-то степени. Программа pg_test_fsync может использоваться, чтобы измерить среднее время в микросекундах, которое занимает одиночная работа сброса WAL на диск. Значение половины среднего времени сообщаемого программой рекомендуется в качестве отправной точки для использования значения в параметре commit_delay при оптимизации для конкретного объёма работы, и говорит о том, сколько нужно времени для синхронизации сброса единственной операции записи 8 Кбайт. Настройка параметра commit_delay особенно полезна в случае хранения WAL в хранилище с высокоскоростными дисками, такими как твердотельные накопители (SSD) или RAID-массивы с кешем записи и аварийным питанием на батарее; но это определённо должно тестироваться на репрезентативной рабочей нагрузке. Более высокие значения commit_siblings должны использоваться в таких случаях, тогда как меньшие значения commit_siblings часто полезны на носителях с большими задержками. Обратите внимание на то, что увеличение значения параметра commit_delay может увеличить задержку транзакции настолько, что пострадает общая производительность транзакций.

Даже если commit_delay равен нулю (значение по умолчанию), групповая фиксация всё равно может произойти, но группа будет состоять только из тех сеансов, которым понадобилось сбросить записи о фиксации на диск за то время, пока происходил предыдущий сброс. Чем больше сеансов, тем чаще это происходит даже при нулевом commit_delay, поэтому увеличение этого параметра может и не оказать заметного действия. Установка commit_delay имеет смысл в двух случаях: (1) когда несколько транзакций одновременно фиксируют изменения, (2) либо когда частота фиксаций ограничена пропускной способностью дисковой подсистемы. Однако при задержке из-за низкой скорости вращения диска эта настройка может оказаться полезной даже всего при двух сеансах.

Параметр wal_sync_method определяет, как Postgres Pro будет обращаться к ядру, чтобы принудительно сохранить WAL на диск. Все методы должны быть одинаковыми в плане надёжности, за исключением fsync_writethrough, который может иногда принудительно сбрасывать кеш диска, даже если другие методы не делают этого. Однако какой из них самый быстрый, во многом определяется платформой; вы можете протестировать скорость, используя модуль pg_test_fsync. Обратите внимание, что данный параметр не имеет значения, если fsync выключен.

Включение параметра конфигурации wal_debug (предоставляется, если Postgres Pro был скомпилирован с его поддержкой) приводит к тому, что все вызовы связанных с 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 позволяет уменьшить время ожидания ввода/вывода во время восстановления, указывая ядру начинать чтение дисковых блоков, которые ещё не в пуле буферов Postgres Pro, но вскоре потребуются. Количество параллельных потоков и объём предвыборки ограничиваются параметрами maintenance_io_concurrency и wal_decode_buffer_size соответственно. По умолчанию параметр recovery_prefetch имеет значение try, при котором эта функциональность включается в системах, поддерживающих posix_fadvise.