Журналирование
Журнал предзаписи
16
Авторские права
© Postgres Professional, 2016–2025
Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Игорь Гнатюк
Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации,
демонстрации) разрешается без ограничений. Коммерческое
использование возможно только с письменного разрешения компании
Postgres Professional. Запрещается внесение изменений в материалы
курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу:
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за
любые повреждения и убытки, включая потерю дохода, нанесенные
прямым или непрямым, специальным или случайным использованием
материалов курса. Компания Postgres Professional не предоставляет
каких-либо гарантий на материалы курса. Материалы курса
предоставляются на основе принципа «как есть» и компания Postgres
Professional не обязана предоставлять сопровождение, поддержку,
обновления, расширения и изменения.
2
Темы
Журнал предзаписи (WAL)
Логическое и физическое устройство журнала
Процесс упреждающей записи и восстановление
3
Журнал предзаписи
Основная задача
возможность восстановления согласованности данных после сбоя
Механизм
при изменении данных действие также записывается в журнал
журнальная запись попадает на диск раньше измененных данных
восстановление после сбоя — повторное выполнение потерянных
операций с помощью журнальных записей
Основная причина существования журнала — необходимость
восстановления согласованности данных в случае сбоя, при котором
теряется содержимое оперативной памяти, в частности, буферного
кеша. Тем самым обеспечивается выполнение свойства долговечности
уква «D» из набора свойств транзакций ACID).
Одновременно с изменением данных создается запись в журнале,
содержащая достаточную информацию для повторения этой операции.
Журнальная запись в обязательном порядке попадает на диск (или
другое энергонезависимое устройство) до того, как туда попадет
измененная страница — отсюда и название: «журнал предзаписи»,
«write-ahead log».
В случае сбоя можно прочитать журнал и при необходимости повторить
те операции, которые уже были выполнены, но результат которых не
успел попасть на диск.
4
Что защищено журналом
Изменение любых страниц в буферном кеше
в том числе страницы таблиц и индексов
кроме нежурналируемых и временных таблиц
Фиксация и отмена транзакций — буферы CLOG
Файловые операции
создание и удаление файлов
добавление и удаление страниц файлов
создание и удаление каталогов
Журналировать нужно все операции, при выполнении которых
возможна ситуация, что при сбое изменения (или часть изменений)
не дойдут до диска.
В частности, в журнал записываются следующие действия:
изменение страниц в буферном кеше (как правило, это страницы
таблиц и индексов) — так как измененная страница попадает на диск
не сразу;
фиксация и отмена транзакций — точно так же, изменение статуса
происходит в буфере CLOG и попадает на диск не сразу;
файловые операции (создание и удаление файлов и каталогов,
например, создание файлов при создании таблицы) — так как эти
операции должны происходить синхронно с изменением данных.
При этом в журнал не записываются:
операции с нежурналируемыми таблицами — их название говорит
само за себя;
операции с временными таблицами — они существуют не дольше,
чем создавший их сеанс, и поэтому не нуждаются в восстановлении.
5
Логическое устройство
LSN
«номер»
записи
0
заголовок:
номер транзакции,
менеджер ресурсов,
контрольная
сумма
данные
последовательность записей
«номер» записи — 64-битный LSN (log sequence number)
специальный тип pg_lsn
Логически журнал можно представить себе как последовательность
записей различной длины. Каждая запись содержит данные
о некоторой операции, предваренные заголовком. В заголовке,
в числе прочего, указаны:
номер транзакции, к которой относится запись;
менеджер ресурсов — компонент системы, ответственный за данную
запись, который понимает, как интерпретировать данные. Есть
отдельные менеджеры для таблиц, для каждого типа индекса, для
статуса транзакций и т. п.;
контрольная сумма (CRC).
Начиная с версии 16 расширения могут создавать собственные
менеджеры ресурсов, использующие специфический формат для своих
записей WAL. Это поможет разработчикам создавать расширения,
реализующие новые табличные и индексные методы доступа.
Для того чтобы сослаться на определенную запись, используется тип
данных pg_lsn (LSN = log sequence number) — 64-битное число,
представляющее собой байтовое смещение до записи относительно
начала журнала.
Начало каждой записи выравнивается по границе машинного слова,
это одна из причин двоичной несовместимости WAL на разных
платформах.
7
Физическое устройство
wal_buffers
$PGDATA/pg_wal/
000000010000000100000001
000000010000000100000002
000000010000000100000003
000000010000000100000004
000000010000000100000005
000000010000000100000006
В памяти
кольцевой буферный кеш
wal_buffers = –1
На диске
файлы (сегменты) по 16 МБ
1/32 shared_buffers
На диске журнал хранится в виде файлов (сегментов) в каталоге
PGDATA/pg_wal. Каждый файл по умолчанию занимает 16 Мбайт.
Размер сегмента может быть задан при инициализации кластера.
Журнальные записи попадают в текущий файл; когда он заполняется —
начинает использоваться следующий.
В оперативной памяти для журнала выделены специальные буферы.
Размер кеша задается параметром wal_buffersначение по умолчанию
подразумевает автоматическую настройку: выделяется 1/32 часть
буферного кеша, но не меньше чем 64 Кбайт и не больше чем размер
одного сегмента WAL).
Журнальный кеш устроен наподобие буферного кеша, но работает
преимущественно в режиме кольцевого буфера: записи добавляются
в «голову» буфера, а записываются на диск с «хвоста».
9
Упреждающая запись
CLOG WAL
pg_current_wal_insert_lsn()
LSN
Проиллюстрируем сказанное выше про упреждающую запись.
На слайде показаны три важные области общей памяти экземпляра:
буферный кеш (размером shared_buffers),
только что рассмотренный журнальный кеш WAL (размером
wal_buffers),
кеш состояния транзакций, называемый также CLOG (размером
128 страниц).
При изменении страницы данных в буферном кеше формируется
журнальная запись. Она помещается в страницу журнала, а ссылка
на запись (а точнее ее LSN + длина, то есть LSN следующей записи)
записывается в специальное поле LSN в заголовке страницы данных.
Позицию для записи можно узнать с помощью функции
pg_current_wal_insert_lsn().
10
Упреждающая запись
CLOG WAL
pg_current_wal_insert_lsn()
LSN
LSN
Допустим, далее происходит фиксация транзакции. Для этого
формируется еще одна журнальная запись, меняется бит состояния
на странице CLOG и ссылка на эту запись проставляется в поле LSN
измененной страницы.
При вставке указатель pg_current_wal_insert_lsn сдвигается вперед.
Заметим, что между записями, относящимися к одной транзакции,
могут попасть записи других транзакций, относящихся к любой БД.
Журнал — общий для всего кластера.
11
Упреждающая запись
CLOG WAL
pg_current_wal_insert_lsn()
LSN
LSN
pg_current_wal_lsn()
Далее в какой-то момент (в какой именно — будет рассмотрено
в теме «Настройка журнала») журнальные записи, которые еще не
попали на диск, должны на него попасть.
Функция pg_current_wal_lsn() показывает последнюю запись, уже
дошедшую до диска.
12
Упреждающая запись
CLOG WAL
LSN
LSN
Только после того, как на диск попали журнальные записи, могут быть
записаны и сами измененные страницы. Порядок контролируется
с учетом LSN последнего изменения страницы и текущего состояния
pg_current_wal_lsn. При этом работа продолжается, в журнал будут
попадать новые и новые записи. Главное, чтобы запись с LSN
последнего изменения страницы уже была на диске.
Если окажется, что страница данных должна быть записана (например,
она вытесняется из буферного кеша), а журнальная запись еще не
попала на диск, журнальные буферы сбрасываются принудительно.
14
Восстановление
Алгоритм (упрощенный)
при старте сервера после сбоя
(состояние кластера в pg_control отличается от «shut down»):
1. для каждой журнальной записи:
1.1. определить страницу, к которой относится эта запись
1.2. применить запись, если ее LSN больше, чем LSN страницы
2. перезаписать нежурналируемые таблицы init-файлами
Если в работе сервера произошел сбой, то при последующем запуске
процесс startup (запускаемый postmaster-ом в самом начале работы)
обнаружит это, посмотрев в файл pg_control и увидев статус, отличный
от «shut down». Тогда автоматически будет выполнено восстановление.
Процесс startup будет последовательно читать журнал и применять
записи к страницам, если в этом есть необходимость (что можно
проверить, сравнив LSN страницы на диске с LSN журнальной записи).
Изменение страниц происходит в буферном кеше, как при обычной
работе — для этого postmaster запускает необходимые фоновые
процессы.
Аналогично записи применяются и к файлам: например, если запись
говорит о том, что файл должен существовать, а его нет — файл
создается.
В конце процесса все нежурналируемые таблицы перезаписываются
с помощью образов в init-файлах.
Процесс восстановления можно ускорить, включив реализованную для
этого в PostgreSQL 15 предвыборку данных журнала. Эта предвыборка
включается параметром recovery_prefetch. В этом случае значение
параметра wal_dec ode _buf fer_size определяет, как далеко сервер будет
заглядывать в журнал, чтобы найти там номера нужных страниц.
Приведенный алгоритм является упрощенным. В частности, мы ничего
не сказали о том, с какого места надо начинать чтение журнальных
записей (это будет рассмотрено в теме «Контрольная точка»).
15
Итоги
Использование буферов в оперативной памяти
приводит к необходимости журналирования
Журнал содержит информацию,
позволяющую повторно выполнить операции после сбоя
и восстановить согласованность
Журнал всегда записывается на диск до того,
как записываются измененные страницы данных
16
Практика
1. Создайте таблицу с первичным ключом и добавьте в нее
несколько строк. Сколько байт занимают сгенерированные
журнальные записи?
2. Чем можно объяснить довольно большое их число?
Просмотрите заголовки этих журнальных записей
и проверьте свои предположения.
3. Измените добавленные в таблицу строки. Снова измените
строки, но не фиксируйте транзакцию. Сымитируйте сбой,
прервав процесс postmaster.
Запустите сервер и убедитесь, что зафиксированные
изменения не пропали, а незафиксированная транзакция
оборвана. Найдите информацию о восстановлении после
сбоя в журнале сообщений сервера.
2. Можно воспользоваться утилитой pg_waldump или расширением
pg_walinspect.
3. Воспользуйтесь командой
$ sudo kill -QUIT номер-процесса
Номер процесса находится в первой строке файла postmaster.pid
в каталоге PGDATA сервера.