09 Занятие 09: Журналирование / Журнал предзаписи
Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета. HTML source: dba2_09_wal_log.html Archive: DBA2-handouts-16.zip Страниц PDF: 22
Page 1: Журналирование / Журнал предзаписи
Авторские права
© Postgres Professional, 2016–2025 Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Игорь Гнатюк Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.
Page 2: Темы
- Журнал предзаписи (WAL)
- Логическое и физическое устройство журнала
- Процесс упреждающей записи и восстановление
Page 3: Журнал предзаписи
Основная задача
- возможность восстановления согласованности данных после сбоя
- Механизм
- при изменении данных действие также записывается в журнал
- журнальная запись попадает на диск раньше измененных данных
- восстановление после сбоя — повторное выполнение потерянных
- операций с помощью журнальных записей
Комментарии преподавателя
Основная причина существования журнала — необходимость восстановления согласованности данных в случае сбоя, при котором теряется содержимое оперативной памяти, в частности, буферного кеша. Тем самым обеспечивается выполнение свойства долговечности (буква «D» из набора свойств транзакций ACID).
Одновременно с изменением данных создается запись в журнале, содержащая достаточную информацию для повторения этой операции. Журнальная запись в обязательном порядке попадает на диск (или другое энергонезависимое устройство) до того, как туда попадет измененная страница — отсюда и название: «журнал предзаписи», «write-ahead log».
В случае сбоя можно прочитать журнал и при необходимости повторить те операции, которые уже были выполнены, но результат которых не успел попасть на диск.
https://postgrespro.ru/docs/postgresql/16/wal-intro
Page 4: Что защищено журналом
Изменение любых страниц в буферном кеше
- в том числе страницы таблиц и индексов
- кроме нежурналируемых и временных таблиц
- Фиксация и отмена транзакций — буферы CLOG
- Файловые операции
- создание и удаление файлов
- добавление и удаление страниц файлов
- создание и удаление каталогов
Комментарии преподавателя
Журналировать нужно все операции, при выполнении которых возможна ситуация, что при сбое изменения (или часть изменений) не дойдут до диска.
В частности, в журнал записываются следующие действия:
● изменение страниц в буферном кеше (как правило, это страницы таблиц и индексов) — так как измененная страница попадает на диск не сразу;
● фиксация и отмена транзакций — точно так же, изменение статуса происходит в буфере CLOG и попадает на диск не сразу;
● файловые операции (создание и удаление файлов и каталогов, например, создание файлов при создании таблицы) — так как эти операции должны происходить синхронно с изменением данных.
При этом в журнал не записываются:
● операции с нежурналируемыми таблицами — их название говорит само за себя;
● операции с временными таблицами — они существуют не дольше, чем создавший их сеанс, и поэтому не нуждаются в восстановлении.
Page 5: Логическое устройство
{#source-figure-005 caption=“Source slide page 5: Логическое устройство”}
последовательность записей «номер» записи — 64-битный LSN (log sequence number) специальный тип pg_lsn
Комментарии преподавателя
Логически журнал можно представить себе как последовательность записей различной длины. Каждая запись содержит данные о некоторой операции, предваренные заголовком. В заголовке, в числе прочего, указаны:
● номер транзакции, к которой относится запись;
● менеджер ресурсов — компонент системы, ответственный за данную запись, который понимает, как интерпретировать данные. Есть отдельные менеджеры для таблиц, для каждого типа индекса, для статуса транзакций и т. п.;
● контрольная сумма (CRC).
Начиная с версии 16 расширения могут создавать собственные менеджеры ресурсов, использующие специфический формат для своих записей WAL. Это поможет разработчикам создавать расширения, реализующие новые табличные и индексные методы доступа.
https://postgrespro.ru/docs/postgresql/16/custom-rmgr
Для того чтобы сослаться на определенную запись, используется тип данных pg_lsn (LSN = log sequence number) — 64-битное число, представляющее собой байтовое смещение до записи относительно начала журнала.
Начало каждой записи выравнивается по границе машинного слова, это одна из причин двоичной несовместимости WAL на разных платформах.
https://postgrespro.ru/docs/postgresql/16/datatype-pg-lsn
Page 6: Логическое устройство журнала
Список менеджеров ресурсов покажет утилита pg_waldump:
LSN выводится как два 32-битных числа в шестнадцатеричной системе через косую черту. Текущая позиция в журнале:
При помощи утилиты pg_waldump и появившегося в 15-й версии PostgreSQL расширения pg_walinspect мы можем исследовать содержимое журнала предзаписи. Создадим базу данных и установим расширение (чтобы воспользоваться расширением, роль должна быть включена в pg_read_server_files или быть суперпользовательской):
Нам понадобится небольшая таблица:
Получим текущую позицию в журнале, после чего вставим в таблицу строку:
Теперь позиция журнала такая:
Page 7: Page 7
Посмотрим, какие записи появились в журнале:
Операция INSERT+INIT инициализирует страницу и добавляет в нее версию строки, операция COMMIT фиксирует транзакцию. Для записи INSERT+INIT в журнале сохраняется блок данных, содержащий значения полей вставляемой версии строки. Расширение позволяет увидеть эти данные (в столбце block_data можно различить коды символов добавленной строки):
Page 8: Физическое устройство
{#source-figure-008 caption=“Source slide page 8: Физическое устройство”}
В памяти
- На диске
Комментарии преподавателя
На диске журнал хранится в виде файлов (сегментов) в каталоге PGDATA/pg_wal. Каждый файл по умолчанию занимает 16 Мбайт. Размер сегмента может быть задан при инициализации кластера.
Журнальные записи попадают в текущий файл; когда он заполняется — начинает использоваться следующий.
В оперативной памяти для журнала выделены специальные буферы. Размер кеша задается параметром wal_buffers (значение по умолчанию подразумевает автоматическую настройку: выделяется 1/32 часть буферного кеша, но не меньше чем 64 Кбайт и не больше чем размер одного сегмента WAL).
Журнальный кеш устроен наподобие буферного кеша, но работает преимущественно в режиме кольцевого буфера: записи добавляются в «голову» буфера, а записываются на диск с «хвоста».
Page 9: Физическое устройство журнала
Размер кеша журнала в общей памяти:
Все журнальные файлы (сегменты) находятся в каталоге /var/lib/postgresql/16/main/pg_wal/, их также показывает специальная функция:
Имена файлов составлены из трех чисел. Первое — номер линии времени (используется при восстановлении из архива), а два следующих — старшие разряды LSN. Размер файлов можно задать при инициализации кластера, по умолчанию — 16 Мбайт. Запись INSERT+INIT находится в этом файле:
Текущий сегмент:
Page 10: Упреждающая запись
{#source-figure-010 caption=“Source slide page 10: Упреждающая запись”}
- • •
Комментарии преподавателя
Проиллюстрируем сказанное выше про упреждающую запись. На слайде показаны три важные области общей памяти экземпляра:
● буферный кеш (размером shared_buffers),
● только что рассмотренный журнальный кеш WAL (размером wal_buffers),
● кеш состояния транзакций, называемый также CLOG (размером 128 страниц).
При изменении страницы данных в буферном кеше формируется журнальная запись. Она помещается в страницу журнала, а ссылка на запись (а точнее ее LSN + длина, то есть LSN следующей записи) записывается в специальное поле LSN в заголовке страницы данных.
Позицию для записи можно узнать с помощью функции pg_current_wal_insert_lsn().
Page 11: Упреждающая запись
{#source-figure-011 caption=“Source slide page 11: Упреждающая запись”}
- • •
Комментарии преподавателя
Допустим, далее происходит фиксация транзакции. Для этого формируется еще одна журнальная запись, меняется бит состояния на странице CLOG и ссылка на эту запись проставляется в поле LSN измененной страницы.
При вставке указатель pg_current_wal_insert_lsn сдвигается вперед.
Заметим, что между записями, относящимися к одной транзакции, могут попасть записи других транзакций, относящихся к любой БД. Журнал — общий для всего кластера.
Page 12: Упреждающая запись
{#source-figure-012 caption=“Source slide page 12: Упреждающая запись”}
- • •
Комментарии преподавателя
Далее в какой-то момент (в какой именно — будет рассмотрено в теме «Настройка журнала») журнальные записи, которые еще не попали на диск, должны на него попасть.
Функция pg_current_wal_flush_lsn() показывает последнюю запись, уже дошедшую до диска.
Page 13: Упреждающая запись
{#source-figure-013 caption=“Source slide page 13: Упреждающая запись”}
- • •
Комментарии преподавателя
Только после того, как на диск попали журнальные записи, могут быть сброшены и сами измененные страницы. Порядок контролируется с учетом LSN последнего изменения страницы и текущего значения pg_current_wal_flush_lsn. При этом работа продолжается, в журнал будут попадать новые и новые записи. Для сброса страницы главное, чтобы запись с LSN последнего ее изменения уже была на диске.
Если окажется, что страница данных должна быть сброшена (например, она вытесняется из буферного кеша), а журнальная запись еще не попала на диск, журнальные буферы сбрасываются принудительно.
Page 14: Упреждающая запись
Мы будем заглядывать в заголовок табличной страницы. Для этого понадобится расширение:
Начнем транзакцию.
Текущая позиция и текущий сегмент журнала:
Изменим строку в таблице:
Позиция в журнале изменилась:
Этот же номер LSN (или меньший, если в журнал попали дополнительные записи) мы найдем и в заголовке измененной страницы:
Завершим транзакцию.
Позиция в журнале снова изменилась:
Размер фрагмента журнала (в байтах), соответствующий нашей транзакции:
Безусловно, в журнал попадает информация обо всех действиях во всем кластере, но в данном случае мы рассчитываем на то, что в системе ничего не происходит. Вот сами записи:
Page 15: Page 15
Заметим, что из-за выравнивания длина записи обычно меньше, чем занимаемое ей место (столбец occupied). Увидеть содержимое журнала можно и с помощью утилиты pg_waldump. Она может работать с диапазоном LSN (как в этом примере), выбрать записи для определенного отношения и отдельного слоя, а также для отдельной страницы или указанной транзакции. В отличие от расширения pg_walinspect, утилитой можно пользоваться и на остановленном сервере, при этом ей нужен доступ на чтение журнальных файлов, поэтому мы выполним команду от имени суперпользователя:
Мы видим заголовки журнальных записей: операция HOT_UPDATE, относящаяся к измененной странице (rel+blk), операция COMMIT с указанием времени.
Page 16: Восстановление
Алгоритм (упрощенный)
- при старте сервера после сбоя
- (состояние кластера в pg_control отличается от «shut down»):
- для каждой журнальной записи:
- 1.1. определить страницу, к которой относится эта запись
- 1.2. применить запись, если ее LSN больше, чем LSN страницы
- перезаписать нежурналируемые таблицы init-файлами
Комментарии преподавателя
Если в работе сервера произошел сбой, то при последующем запуске процесс startup (запускаемый postmaster-ом в самом начале работы) обнаружит это, посмотрев в файл pg_control и увидев статус, отличный от «shut down». Тогда автоматически будет выполнено восстановление.
Процесс startup будет последовательно читать журнал и применять записи к страницам, если в этом есть необходимость (что можно проверить, сравнив LSN страницы на диске с LSN журнальной записи). Изменение страниц происходит в буферном кеше, как при обычной работе — для этого postmaster запускает необходимые фоновые процессы.
Аналогично записи применяются и к файлам: например, если запись говорит о том, что файл должен существовать, а его нет — файл создается.
В конце процесса все нежурналируемые таблицы перезаписываются с помощью образов в init-файлах.
В PostgreSQL 15 реализована возможность предвыборки данных журнала при восстановлении — за это отвечает параметр recovery_prefetch. По умолчанию его значение — try (предвыборка используется, если ее поддерживает ОС). Если предвыборка включена, то значение параметра wal_decode_buffer_size определяет, как далеко сервер будет заглядывать в журнал, чтобы найти там номера нужных страниц.
Приведенный алгоритм является упрощенным. В частности, мы ничего не сказали о том, с какого места надо начинать чтение журнальных записей (это будет рассмотрено в теме «Контрольная точка»).
Page 17: Итоги
Использование буферов в оперативной памяти приводит к необходимости журналирования
- Журнал содержит информацию, позволяющую повторно выполнить операции после сбоя и восстановить согласованность
- Журнал всегда записывается на диск до того, как записываются измененные страницы данных
Page 18: Практика
- Создайте таблицу с первичным ключом и добавьте в нее несколько строк. Сколько байт занимают сгенерированные журнальные записи?
- Чем можно объяснить довольно большое их число? Просмотрите заголовки этих журнальных записей и проверьте свои предположения.
- Измените добавленные в таблицу строки. Снова измените строки, но не фиксируйте транзакцию. Сымитируйте сбой, прервав процесс postmaster.
- Запустите сервер и убедитесь, что зафиксированные
- изменения не пропали, а незафиксированная транзакция
- оборвана. Найдите информацию о восстановлении после
- сбоя в журнале сообщений сервера.
Комментарии преподавателя
Можно воспользоваться утилитой pg_waldump или расширением pg_walinspect.
Воспользуйтесь командой $ sudo kill -QUIT номер-процесса
Номер процесса находится в первой строке файла postmaster.pid в каталоге PGDATA сервера.
Page 19: 1. Размер журнальных записей
Запомним начальную позицию в журнале:
Создадим таблицу и добавим строки:
Запомним конечную позицию:
Размер журнальных записей:
2. Состав журнальных записей
Журнальный файл:
Понадобится расширение pg_walinspect:
Смотрим записи:
Page 20: Page 20
Page 21: Page 21
Вначале (до первой операции COMMIT) происходит активная работа с таблицами и индексами системного каталога. За счет этого размер записей и получился существенно больше, чем в демонстрации.
3. Восстановление после сбоя
Обновляем строки:
Прерываем основной серверный процесс.
Запускаем сервер.
Page 22: Page 22
Проверяем изменения:
Журнал сообщений: