Что такое wal postgresql
Sysadminium
База знаний системного администратора
Буферный кэш и журнал WAL в PostgreSQL
Рассмотрим технологии которые обеспечивают высокую производительность и безопасность работы сервера PostgreSQL, а именно “Буферный кэш” и “Журнал предзаписи WAL“. В документации об этом можете почитать тут.
Устройство буферного кэша
Кэш нужен чтобы читать востребованные данные ни с диска а с более быстрой оперативной памяти. Предварительно данные приходится загружать с диска в буферных кэш оперативной памяти.
В общей памяти отводится определённый кусок памяти под массив буферов. В каждом буфере хранится одна страница памяти. Страница памяти это 8 КБ. Когда мы собираем PostgreSQL из исходных кодов мы можем изменить размер этой страницы. А после сборки это изменить уже не получится.
Есть 3 варианта размера страниц:
Буферный кэш занимает большую часть общей памяти.
Если процессу понадобятся какие-то данные он их вначале ищет в буферном кэше. Если данных в кэше не оказалось, то мы просим операционную систему прочитать эту страничку и поместить в буферный кэш. Операционная система имеет свой дисковые кэш, и ищет эту страничку там, если не находит, то читает с диска и помещает в дисковый кэш. Затем из дискового кэша страничка помещается в буферный кэш для PostgreSQL.
Так как буферный кэш находится в общей памяти, то чтобы разные процессы не мешали друг другу, к нему применяют различные блокировки.
Алгоритм вытеснения
Чтобы буферный кэш не переполнился нужно редко используемые страницы из него вытеснять. Другими словами удалить из буфера. Если мы страничку поменяли, то она считается грязной, и перед вытеснением из буфера её нужно записать на диск. Если мы страничку не меняли то и записывать на диск её ещё раз не имеет смыла.
В PostgreSQL реализован алгоритм вытеснения страниц из буфера при котором в кэше остаются самые часто используемые страницы.
Журнал предзаписи (WAL)
То что у нас данные находятся в оперативной памяти – это хорошо. Но при сбое эти данные теряются, если не успели записаться на диск.
После сбоя наша база становится рассогласованной. Какие-то страницы менялись и были записаны, другие не успели записаться.
Журнал предварительной записи (WAL) – механизм, которым позволит нам восстановить согласованность данных после сбоя.
Когда мы хотим поменять какую-то страницу памяти, мы предварительно пишем, что хотим сделать в журнал предзаписи. И запись в этом журнале должна попасть на диск раньше, чем сама страница будет записана на диск.
В случае сбоя мы смотрим в журнал предзаписи и видим какие действия выполнялись, проделываем эти действия заново и восстанавливаем согласованность данных.
Почему запись в WAL эффективнее чем запись самой страницы? При записи страницы памяти на диск она пишется в произвольное место, это место еще нужно выбрать, подготовить для записи и начать запись. А запись в журнал ведется одним потоком и с этим потоком нормально справляются и обычные жёсткие диски и ssd.
WAL защищает всё что попадает в буферный кэш:
Производительность WAL
По умолчанию, каждый раз, когда транзакция фиксирует изменения, результат должен быть сброшен на диск. Для этого вначале страница сбрасывается из буферной памяти на дисковый кэш. А затем выполняется операция fsync для записи страницы на диск. То есть частые COMMIT приводят к частым fsync.
В PostgreSQL есть другой режим работы – асинхронный. При этом каждый COMMIT не приводит к fsync. Вместо этого страницы сбрасываются на диск по расписанию специальным процессом – WALWRITER. Этот процесс периодически просыпается и записывает на диск всё что изменилось за время пока он спал и опять засыпает.
В асинхронном режиме postgresql работает быстрее, но вы можете потерять некоторые данные при сбое.
Режим работы настраивается с помощью конфигурационного файла и настройки не требуют перезагрузки сервера. Это позволяет приложению устанавливать параметры на лету. Некоторые транзакции сразу запишут изменения на диск, то есть поработают в синхронном режиме. Другие транзакции будут работать в асинхронном режиме. Условно можно поделить операции на критичные и не критичные.
Следующие параметры отвечают за режим работы WAL:
Контрольная точка
При выполнении контрольной точки (CHECKPOINT) – принудительно сбрасываются на диск все грязные страницы, которые есть в буферном кэше. Это гарантирует что на момент контрольной точки все данные сбросились на диск и при восстановлении данных нужно читать не весь журнал WAL, а только ту часть которая была сделана после последней контрольной точки.
Сброс данных при контрольной точке не проходит моментально, это бы сильно нагрузило наш сервер. Чтобы не было пиковых нагрузок сброс идет примерно 50% времени от времени между контрольными точками. Например, контрольные точки делаются каждую минуту, тогда сброс осуществляют плавно в течении 30 секунд. Это регулируется и вы можете установить например 90% времени.
Контрольная точка также уменьшает размер необходимого дискового пространства. Так как весь журнал WAL не нужен, его можно периодически подрезать.
Отдельный серверный процесс контрольных точек автоматически выполняет контрольные точки с заданной частотой. Эту частоту можно настроить следующими параметрами:
Значения по умолчанию: 5 минут и 1 Гбайт, соответственно. Если после предыдущей контрольной точки новые записи WAL не добавились, следующие контрольные точки будут пропущены, даже если проходит время checkpoint_timeout. Также можно выполнить контрольную точку принудительно, воспользовавшись SQL-командой CHECKPOINT.
Уменьшение значений checkpoint_timeout и max_wal_size приводит к учащению контрольных точек. Но появляется дополнительная нагрузка на сервер.
Процессы, связанные с буферным кэшем и журналом
Уровни журналов
Практика
В журнале WAL каждая запись имеет номер LSN (Log Sequence Number). С помощью функции pg_current_wal_lsn() можно посмотреть номер текущей записи:
Запомним номер текущей записи WAL в переменной “pos1”, затем создадим табличку с 1000 строк и опять сохраним текущий LSN в переменной “pos2”. Затем посмотрим разницу между этими числами, но в формате LSN, и получим число байт записанных в WAL при создании таблички с 1000 строками:
У нас вышло 138 KB! Так много получилось из за создания таблички, создание 1000 строк почти не нагрузит WAL.
Физически журнал хранится в файлах по 16 МБ в отдельном каталоге (PGDATA/pg_wal). На журнал можно взглянуть не только из файловой системы, но и с помощью функцию pg_ls_waldir():
Посмотреть на выполняемые процессы сервера postgres можно так:
К процессам, обслуживающим буферный кэш и журнал, можно отнести:
Теперь давайте остановим сервер корректно и посмотрим в лог файл:
Как видим сервер просто открыл соединения на сокете и tcp порту 5432 и начал работу.
Теперь завершим работу сервера некорректно, используя опцию immediate:
Как видим журнал изменился! Перед тем, как начать принимать соединения, СУБД выполнила восстановление (automatic recovery in progress).
WAL в PostgreSQL: 3. Контрольная точка
Мы уже познакомились с устройством буферного кеша — одного из основных объектов в разделяемой памяти, — и поняли, что для восстановления после сбоя, когда содержимое оперативной памяти пропадает, нужно вести журнал предзаписи.
Нерешенная проблема, на которой мы остановились в прошлый раз, состоит в том, что неизвестно, с какого момента можно начинать проигрывание журнальных записей при восстановлении. Начать с начала, как советовал Король из Алисы, не получится: невозможно хранить все журнальные записи от старта сервера — это потенциально и огромный объем, и такое же огромное время восстановления. Нам нужна такая постепенно продвигающаяся вперед точка, с которой мы можем начинать восстановление (и, соответственно, можем безопасно удалять все предшествующие журнальные записи). Это и есть контрольная точка, о которой сегодня пойдет речь.
Контрольная точка
Каким свойством должна обладать контрольная точка? Мы должны быть уверены, что все журнальные записи, начиная с контрольной точки, будут применяться к страницам, записанным на диск. Если бы это было не так, при восстановлении мы могли бы прочитать с диска слишком старую версию страницы и применить к ней журнальную запись, и тем самым безвозвратно повредили бы данные.
Как получить контрольную точку? Самый простой вариант — периодически приостанавливать работу системы и сбрасывать все грязные страницы буферного и других кешей на диск. (Заметим, что страницы только записываются, но не вытесняются из кеша.) Такие точки будет удовлетворять условию, но, конечно, никто не захочет работать с системой, постоянно замирающей на неопределенное, но весьма существенное время.
Поэтому на практике все несколько сложнее: контрольная точка из точки превращается в отрезок. Сперва мы начинаем контрольную точку. После этого, не прерывая работу и по возможности не создавая пиковых нагрузок, потихоньку сбрасываем грязные буферы на диск.
Когда все буферы, которые были грязными на момент начала контрольной точки, окажутся записанными, контрольная точка считается завершенной. Теперь (но не раньше) мы можем использовать момент начала в качестве той точки, с которой можно начинать восстановление. И журнальные записи вплоть до этого момента нам больше не нужны.
Выполнением контрольной точки занимается специальный фоновый процесс checkpointer.
Продолжительность записи грязных буферов определяется значением параметра checkpoint_completion_target. Он показывает, какую часть времени между двумя соседними контрольными точками будет происходить запись. Значение по умолчанию равно 0.5 (как на рисунках выше), то есть запись занимает половину времени между контрольными точками. Обычно значение увеличивают вплоть до 1.0 для большей равномерности.
Рассмотрим подробнее, что происходит при выполнении контрольной точки.
Сначала процесс контрольной точки сбрасывает на диск буферы статуса транзакций (XACT). Поскольку их немного (всего 128), они записываются сразу же.
Затем начинается основная работа — запись грязных страниц из буферного кеша. Как мы уже говорили, сбросить все страницы одномоментно невозможно, поскольку размер буферного кеша может быть значительным. Поэтому сперва все грязные на текущий момент страницы помечаются в буферном кеше в заголовках специальным флагом.
А затем процесс контрольной точки постепенно проходит по всем буферам и сбрасывает помеченные на диск. Напомним, что страницы не вытесняются из кеша, а только записываются на диск, поэтому не нужно обращать внимания ни на число обращений к буферу, ни на его закрепление.
Помеченные буферы могут также быть записаны и серверными процессами — смотря кто доберется до буфера первым. В любом случае при записи снимается установленный ранее флаг, так что (для целей контрольной точки) буфер будет записан только один раз.
Естественно, в ходе выполнения контрольной точки страницы продолжают изменяться в буферном кеше. Но новые грязные буферы не помечены флагом и процесс контрольной точки не должен их записывать.
В конце своей работы процесс создает журнальную запись об окончании контрольной точки. В этой записи содержится LSN момента начала работы контрольной точки. Поскольку контрольная точка ничего не записывает в журнал в начале своей работы, по этому LSN может находиться любая журнальная запись.
Чтобы посмотреть на работу контрольной точки, создадим какую-нибудь таблицу — ее страницы попадут в буферный кеш и будут грязными:
Запомним текущую позицию в журнале:
Теперь выполним контрольную точку вручную и убедимся, что в кеше не осталось грязных страниц (как мы говорили, новые грязные страницы могут появляться, но в нашем случае никаких изменений в процессе выполнения контрольной точки не происходило):
Посмотрим, как контрольная точка отразилась в журнале:
Здесь мы видим две записи. Последняя из них — запись о прохождении контрольной точки (CHECKPOINT_ONLINE). LSN начала контрольной точки указан после слова redo, и эта позиция соответствует журнальной записи, которая в момент начала контрольной точки была последней.
Ту же информацию мы найдем и в управляющем файле:
Восстановление
Теперь мы готовы уточнить алгоритм восстановления, намеченный в прошлой статье.
Если в работе сервера произошел сбой, то при последующем запуске процесс startup обнаруживает это, посмотрев в файл pg_control и увидев статус, отличный от «shut down». В этом случае выполняется автоматическое восстановление.
Сначала процесс восстановления прочитает из того же pg_control позицию начала контрольной точки. (Для полноты картины заметим, что, если присутствует файл backup_label, то запись о контрольной точке читается из него — это нужно для восстановления из резервных копий, но это тема для отдельного цикла.)
Далее он будет читать журнал, начиная с найденной позиции, последовательно применяя журнальные записи к страницам (если в этом есть необходимость, как мы уже обсуждали в прошлый раз).
В заключение все нежурналируемые таблицы перезаписываются с помощью образов в init-файлах.
На этом процесс startup завершает работу, а процесс checkpointer тут же выполняет контрольную точку, чтобы зафиксировать на диске восстановленное состояние.
Можно сымитировать сбой, принудительно остановив сервер в режиме immediate.
Проверим состояние кластера:
При запуске PostgreSQL понимает, что произошел сбой и требуется восстановление.
Необходимость восстановления отмечается в журнале сообщений: database system was not properly shut down; automatic recovery in progress. Затем начинается проигрывание журнальных записей с позиции, отмеченной в «redo starts at» и продолжается до тех пор, пока удается получать следующие журнальные записи. На этом восстановление завершается в позиции «redo done at» и СУБД начинает работать с клиентами (database system is ready to accept connections).
А что происходит при нормальной остановке сервера? Чтобы сбросить грязные страницы на диск, PostgreSQL отключает всех клиентов и затем выполняет финальную контрольную точку.
Запомним текущую позицию в журнале:
Теперь аккуратно останавливаем сервер:
Проверим состояние кластера:
А в журнале обнаружим единственную запись о финальной контрольной точке (CHECKPOINT_SHUTDOWN):
(Страшным фатальным сообщением pg_waldump всего-навсего хочет сказать о том, что дочитал до конца журнала.)
Снова запустим экземпляр.
Фоновая запись
Как мы выяснили, контрольная точка — один из процессов, который записывает грязные страницы из буферного кеша на диск. Но не единственный.
Если обслуживающему процессу (backend) потребуется вытеснить страницу из буфера, а страница окажется грязной, ему придется самостоятельно записать ее на диск. Это нехорошая ситуация, приводящая к ожиданиям — гораздо лучше, когда запись происходит асинхронно, в фоновом режиме.
Поэтому в дополнение к процессу контрольной точки (checkpointer) существует также процесс фоновой записи (background writer, bgwriter или просто writer). Этот процесс использует тот же самый алгоритм поиска буферов, что и механизм вытеснения. Отличий по большому счету два.
Настройка
26.05.2020 — в раздел внесены уточнения. Спасибо stegmatic, указавшему на ошибку.
Процесс контрольной точки обычно настраивается из следующих соображений.
Сначала надо определиться, какой объем журнальных файлов мы можем себе позволить сохранять между двумя последовательными контрольными точками (и какое время восстановления нас устраивает). Чем больше, тем лучше, но по понятным причинам это значение будет ограничено.
Далее мы можем посчитать, за какое время при обычной нагрузке будет генерироваться этот объем. Как это делать, мы уже рассматривали (надо запомнить позиции в журнале и вычесть одну из другой).
Это время и будет нашим обычным интервалом между контрольными точками. Записываем его в параметр checkpoint_timeout. Значение по умолчанию — 5 минут — явно слишком мало, обычно время увеличивают, скажем, до получаса. Повторюсь: чем реже можно позволить себе контрольные точки, тем лучше — это сокращает накладные расходы.
Однако возможно (и даже вероятно), что иногда нагрузка будет выше, чем обычная, и за указанное в параметре время будет сгенерирован слишком большой объем журнальных записей. В этом случае хотелось бы выполнять контрольную точку чаще. Для этого в параметре max_wal_size мы указываем общий допустимый объем журнальных файлов. Если фактический объем будет получаться больше, сервер инициирует внеплановую контрольную точку.
Сервер хранит журнальные файлы не только за прошедшую завершенную контрольную точку, но и за текущую еще не завершенную. Поэтому для получения общего объема надо умножать
объем между контрольными точками на (1 + checkpoint_completion_target). А до версии 11 — на (2 + checkpoint_completion_target), поскольку PostgreSQL хранил файлы и за позапрошлую контрольную точку тоже.
Таким образом, большая часть контрольных точек происходит по расписанию: раз в checkpoint_timeout единиц времени. Но при повышенной нагрузке контрольная точка вызывается чаще, при достижении объема max_wal_size.
Важно понимать, что значение max_wal_size может быть превышено:
Процесс фоновой записи имеет смысл настраивать после того, как настроена контрольная точка. Совместно эти процессы должны успевать записывать грязные буферы до того, как они потребуются обслуживающим процессам.
Процесс фоновой записи работает циклами максимум по bgwriter_lru_maxpages страниц, засыпая между циклами на bgwriter_delay.
Количество страниц, которые будут записаны за один цикл работы, определяется по среднему количеству буферов, которые запрашивались обслуживающими процессами с прошлого запуска (при этом используется скользящее среднее, чтобы сгладить неравномерность между запусками, но при этом не зависеть от давней истории). Вычисленное количество буферов умножается на коэффициент bgwriter_lru_multiplier (но при любом раскладе не будет превышать bgwriter_lru_maxpages).
Значения по умолчанию: bgwriter_delay = 200ms (скорее всего слишком много, за 1/5 секунды много воды утечет), bgwriter_lru_maxpages = 100, bgwriter_lru_multiplier = 2.0 (пытаемся реагировать на спрос с опережением).
Если процесс совсем не обнаружил грязных буферов (то есть в системе ничего не происходит), он «впадает в спячку», из которой его выводит обращение серверного процесса за буфером. После этого процесс просыпается и опять работает обычным образом.
Мониторинг
Настройку контрольной точки и фоновой записи можно и нужно корректировать, получая обратную связь от мониторинга.
Параметр checkpoint_warning выводит предупреждение, если контрольные точки, вызванные переполнением размера журнальных файлов, выполняются слишком часто. Его значение по умолчанию — 30 секунд, и его надо привести в соответствие со значением checkpoint_timeout.
Параметр log_checkpoints (выключеный по умолчанию) позволяет получать в журнале сообщений сервера информацию о выполняемых контрольных точках. Включим его.
Теперь поменяем что-нибудь в данных и выполним контрольную точку.
В журнале сообщений мы увидим примерно такую информацию:
Тут видно, сколько буферов было записано, как изменился состав журнальных файлов после контрольной точки, сколько времени заняла контрольная точка и расстояние (в байтах) между соседними контрольными точками.
Но, наверное, самая полезная информация — это статистика работы процессов контрольной точки и фоновой записи в представлении pg_stat_bgwriter. Представление одно на двоих, потому что когда-то обе задачи выполнялись одним процессом; затем их функции разделили, а представление так и осталось.
Здесь, в числе прочего, мы видим количество выполненных контрольных точек:
Важная информация о количестве записанных страниц:
Еще для настройки фоновой записи пригодится maxwritten_clean — это число показывает, сколько раз процесс фоновой записи прекращал работу из-за превышения bgwriter_lru_maxpages.
Сбросить накопленную статистику можно с помощью следующего вызова:
PostgreSQL: как и почему пухнет WAL
Чтобы сделать мониторинг полезным, нам приходится прорабатывать разные сценарии вероятных проблем и проектировать дашборды и триггеры таким образом, чтобы по ним сразу была понятна причина инцидента.
В некоторых случаях мы хорошо понимаем, как работает тот или иной компонент инфраструктуры, и тогда заранее известно какие метрики будут полезны. А иногда мы снимаем практически все возможные метрики с максимальной детализацией и потом смотрим, как на них видны те или иные проблемы.
Сегодня будем смотреть как и почему может распухать Write-Ahead Log (WAL) постгреса. Как обычно — примеры из реальной жизни в картинках.
Немного теории WAL в postgresql
Любое изменение в базе данных в первую очередь фиксируется в WAL, и только после этого изменяются данные в странице в buffer cache и она помечается как dirtied — нуждающаяся в сохранении на диск. Кроме того периодически запускается процесс CHECKPOINT, который сохраняет на диск все dirtied страницы и сохраняет номер сегмента WAL, вплоть до которого все измененные страницы уже записаны на диск.
Если вдруг postgresql по какой-то причине аварийно завершится и запустится снова, в процессе восстановления будут проигрываться все сегменты WAL с момента последнего checkpoint.
Сегменты WAL предшествующие чекпоинту нам уже не пригодятся для поставарийного восстановления БД, но в постгресе WAL также участвует в процессе репликации, а еще может быть настроено резервное копирование всех сегментов для целей Point In Time Recovery — PITR.
Опытный инженер уже наверное всё понял, как оно ломается в реальной жизни:)
Давайте смотреть графики!
WAL распух #1
Наш мониторинговый агент для каждого найденного инстанса постгреса вычисляет путь на диске до директории с wal и снимает как суммарный размер, так и количество файлов (сегментов):
Первым делом смотрим, как давно у нас запускался CHECKPOINT.
Метрики берем из pg_stat_bgwriter:
Видим, что checkpoint давненько не запускался. В данном случае напрямую понять причину НЕзапуска это процесса невозможно (а было бы круто конечно), но мы то знаем, что в постгресе очень много проблем возникает из-за длинных транзакций!
Дальше понятно, что делать:
Еще один важный момент: на репликах, подключенных к данному серверу, wal так же распух!
WAL archiver
По случаю напоминаю: репликация не бэкап!
Хороший бэкап должен позволять восстановиться на любой момент времени. Например, если кто-то «случайно» выполнил
То у нас должна быть возможность восстановить базу на состояние ровно перед этой транзакцией. Это называется PITR (point-in-time recovery) и реализуется в postgresql периодическими полными бэкапами базы + сохранением всех сегментов WAL после дампа.
За резервное копирование wal отвечает настройка archive_command, постгрес просто запускает заданную вами команду, и если она завершается без ошибки, сегмент считается успешно скопированным. Если получилась ошибка — будет пробовать до победного, сегмент при этом будет лежать на диске.
Ну и в качестве иллюстрации — графики сломанной архивации wal:
Здесь помимо самого размера всех сегментов wal, есть unarchived size — это размер сегментов, которые еще не считаются успешно сохраненными.
Статусы считаем по счетчикам из pg_stat_archiver. На количество фэйлов мы сделали автотриггер для всех клиентов, так как оно достаточно часто ломается, особенно когда в качестве места назначения используется какое-то облачное хранилище (S3 к примеру).
Replication lag
Streaming replication в посгтресе работает через передачу и проигрывания wal на репликах. Если по каким-то причинам реплика отстала и не проиграла какое-то количество сегментов, мастер будет хранить для нее pg_settings.wal_keep_segments сегментов. Если реплика отстанет на бОльшее количество сегментов, она больше не сможет подключиться к мастеру (придется наливать заново).
Для того, чтобы гарантировать сохранение любого нужного количества сегментов, в 9.4 появился функционал слотов репликации, о котором речь пойдет дальше.
Replication slots
Если репликация настроена с использованием replication slot и к слоту было хотя бы одно успешное подключение реплики, то в случае, если реплика пропадает, постгрес будет хранить все новые сегменты wal до тех пор, пока не кончится место.
То есть забытый слот репликации может быть причиной распухания wal. Но к счастью мы можем мониторить состояние слотов через pg_replication_slots.
Вот как это выглядит на живом примере:
На верхнем графике мы рядом с размером wal всегда отображаем либо слот с максимальным количеством накопленных сегментов, но так же есть детальный график, который покажет какой именно слот распух.
После того, как мы поняли, что за слот копит данные, мы либо можем починить связанные с ним реплики, либо просто его удалить.
Я привел самые распространенные случаи распухания wal, но уверен есть и другие кейсы (баги в постгресе тоже иногда встречаются). Поэтому важно мониторить размер wal и реагировать на проблемы раньше, чем кончится место на диске и база перестанет обслуживать запросы.
Наш сервис мониторинга уже всё это умеет собирать, правильно визуализировать и алертить. А еще у нас есть on-premises вариант поставки для тех, кому облако не подходит.