Журналы (WAL) в PostgreSQL

Когда мы изменяем страницы данных в памяти, они не сразу попадают на диск. А ведь у нас просто может упасть кластер, причин множество. А наша задача – обеспечить возможность восстановления согласованности данных после сбоя – Durability в ACID.

Для этого применяется механизм упреждающей записи – используется журнал (WAL – write ahead log):

  • при изменении данных действие записывается в журнал
  • журнальная запись попадает на диск раньше изменённых данных
  • восстановление после сбоя – повторное выполнение потерянных операций с помощью журнальных записей

Что туда попадает:

  • изменение любых страниц в буферном кэше
  • фиксация и отмена транзакций (про транзакции здесь и здесь) – буферы XACT

НЕ ПОПАДАЮТ – временные и нежурналируемые таблицы

Схематично можно изобразить:

То есть изменённые блоки попадают сначала в журнал, а потом уже когда-нибудь (точнее посмотрим дальше) – на жёсткий диск. Опять же, журнал пишется на диск не в ту же долю секунды – есть определённая задержка:

Данные должны дойти до энергонезависимого хранилища через многочисленные кэши – СУБД сообщает операционной системе способом, указанным в wal_sync_method. При этом не забываем про аппаратное кэширование. При этом я не учитываю сетевые массивы.

Посмотрим на параметры:

SHOW fsync;

SHOW wal_sync_method;

Изменить их можем в postgresql.conf.

Утилита pg_test_fsync помогает выбрать оптимальный способ.

Пишем изменённые данные в буфер и в WAL-буфер. На самом деле запись идёт не сразу в файл логов, а в буфер файла логов.

При этом данные записываются в буфер с текущим номером:

SELECT pg_current_wal_insert_lsn();

XACT – мультитранзакции тоже пишут свои данные в буфер.

Потом при наступлении определённого события происходит вызов команды записи данных на жёсткий диск.

После записи WAL-буфера на диск уже пишутся сами данные:

Соответственно, при при старте сервера после сбоя (состояние кластера в pg_control отличается от «shut down») происходит следующий сценарий:

  • для каждой журнальной записи определяем страницу, к которой относится эта запись
  • применяем запись, если её LSN больше, чем LSN страницы
  • перезаписываем нежурналируемые таблицы init-файлами

Посмотрим на практике.

Подключимся к БД buffer_temp:

\c buffer_temp

Посмотрим на текущие WAL-файлы:

SELECT * FROM pg_ls_waldir() LIMIT 10;

Создадим расширение для просмотра содержимого страниц:

CREATE EXTENSION pageinspect;

Начнём транзакцию:

BEGIN;

Посмотрим текущую позиция LSN:

SELECT pg_current_wal_insert_lsn();

Посмотрим, какой у нас WAL file:

SELECT pg_walfile_name(‘0/182BCA8’);

Посмотрим последний номер LSN в 0 страницы таблицы test_text используя сырые данные функцией get_raw_page:

SELECT lsn FROM page_header(get_raw_page(‘test_text’,0));

Закоммитим транзакцию:

COMMIT;

Обновим строку:

UPDATE test_text set t = ‘1’ WHERE t = ‘строка 1′;

Увидим, что когда текущий LSN изменился – у нас уже другая транзакция (подробнее о разных транзакциях) :

SELECT pg_current_wal_insert_lsn();

Так и изменился LSN в странице 0, где изменили данные:

SELECT lsn FROM page_header(get_raw_page(‘test_text’,0));

Посмотрим, сколько всего байт журнальных записей прошло с тех пор:

SELECT ‘0/182E0D8’::pg_lsn – ‘0/17E0E80’::pg_lsn;

Посмотрим на физический WAL-файл, что там произошло между этими значениями LSN:

/usr/lib/postgresql/15/bin/pg_waldump -p /var/lib/postgresql/15/main/pg_wal -s 0/17E0E80 -e 0/182E0D8 000000010000000000000001

Здесь лишь хвост – так как больше 300 000 байт за это время успело попасть в файл. Таким образом, здесь увидим все SQL-команды и транзакции, которые проходили за это время.

Возникают интересные вопросы. В какой момент нужно сбрасывать на диск WAL-файлы? Что делать с грязными буферами? И нужно ли это вообще? Можно же с самого начала накатить все wal?

Есть несколько причин для периодического сброса изменений на диск:

  • зачем хранить большой объём измененной информации в буфере?
  • сколько займёт время восстановления, если произойдет сбой?
  • сколько может изменённая страница лежать в буферном кэше?

Ответы на эти вопросы мы рассмотрим в следующих статьях.

Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

десять − пять =