19.1 Зачем нужна контрольная точка
Вспомним зазор между памятью и диском. Изменённые страницы копятся в буферном кеше «грязными», на диск файлов данных попадают не сразу. Журнал помнит все изменения, но он растёт бесконечно.
Контрольная точка ставит границу. В этот момент PostgreSQL сбрасывает
на диск все грязные буферы, накопленные к данному моменту, и
записывает в служебный файл pg_control отметку: «до вот этой
позиции журнала (redo point) все изменения уже в файлах данных».
После этого весь журнал до redo point больше не нужен для восстановления - его можно переиспользовать или удалить. А восстановление, случись оно, начнётся не с начала времён, а с последней контрольной точки. Чем чаще контрольные точки, тем короче восстановление - но тем больше работы по записи в обычное время.
19.2 Redo point: откуда начнётся восстановление
Тонкость: контрольная точка не мгновенна. Пока она сбрасывает грязные буферы (это может занять минуты), база продолжает работать и писать новый журнал. Поэтому redo point - это позиция журнала на момент начала контрольной точки, а не её завершения.
Логика такая: всё, что было грязным к началу контрольной точки, она гарантированно запишет. Значит, восстановление, начатое с redo point, увидит в журнале все изменения, которые на тот момент ещё не были в файлах данных, и доиграет их. Изменения до redo point уже на диске - их проигрывать не нужно.
pg_control хранит redo point последней завершённой контрольной
точки. Именно его читает PostgreSQL при старте, чтобы понять, с какого
места проигрывать журнал.
19.3 Восстановление после сбоя
Теперь сложим процедуру. Сервер упал - kill -9, отключили питание,
паника ядра. При следующем старте PostgreSQL видит, что был остановлен
некорректно, и запускает crash recovery:
По шагам:
- читаем redo point из
pg_control; - идём по журналу от redo point вперёд, применяя каждую запись к страницам (это фаза redo);
- full-page images по пути чинят возможные рваные страницы, дельты накатывают остальные изменения;
- доходим до места, где журнал обрывается (битая или неполная CRC) - это конец доступного журнала;
- база открыта.
Восстановление идёт ровно столько журнала, сколько накопилось между последней контрольной точкой и сбоем - поэтому частота контрольных точек напрямую задаёт верхнюю границу времени восстановления.
19.4 Что происходит с незакоммиченными транзакциями
Естественный вопрос: redo проигрывает весь журнал, включая изменения транзакций, которые на момент сбоя не были закоммичены. Разве они не «оживут»?
Нет, и вот почему. Redo действительно накатывает на страницы и изменения незакоммиченных транзакций - физически они появляются в данных. Но видимость в PostgreSQL решает не наличие версии, а статус её транзакции в clog. Транзакция, не успевшая закоммититься до сбоя, в clog так и осталась незавершённой - а значит, по правилам видимости её версии не видны никому. Фактически она откачена.
Получается красиво: redo не разбирает, кто закоммичен, а кто нет - он тупо восстанавливает физическое состояние. А логику «видно/не видно» берёт на себя MVCC поверх clog. Поэтому после восстановления закоммиченные изменения на месте, а незакоммиченные невидимы - ровно как и положено.
19.5 Когда запускается контрольная точка
Контрольную точку запускают два повода, что наступит раньше:
- по времени -
checkpoint_timeout(по умолчанию 5 минут); - по объёму журнала - когда с прошлой контрольной точки
накопилось журнала больше
max_wal_size(по умолчанию 1 ГБ).
Плюс контрольную точку можно вызвать вручную командой CHECKPOINT или
косвенно - например, она случается перед остановкой сервера.
max_wal_size - это не жёсткий лимит на размер pg_wal/, а порог,
при котором инициируется контрольная точка. Если база пишет быстрее,
чем контрольные точки успевают, журнал может временно превысить этот
размер. Поэтому на нагруженных базах max_wal_size поднимают, чтобы
контрольные точки случались по таймеру, а не каждые несколько секунд
по объёму.
19.6 Размазывание записи: checkpoint_completion_target
Если контрольная точка сбросит все грязные буферы разом, получится всплеск нагрузки на диск: только что было тихо - и вдруг шквал записи. Это бьёт по задержкам пользовательских запросов.
Чтобы сгладить всплеск, PostgreSQL растягивает запись грязных буферов
во времени. Управляет этим checkpoint_completion_target (по
умолчанию 0.9): контрольная точка старается размазать запись на 90%
интервала до следующей. Вместо шквала - ровный фоновый поток записи.
Это объясняет, почему ручной CHECKPOINT ощущается как нагрузка, а
штатные контрольные точки - почти нет: ручная торопится, штатная не
спешит. На пиковых базах баланс между checkpoint_timeout,
max_wal_size и checkpoint_completion_target - один из главных
рычагов сглаживания нагрузки на диск. Разбор и метрики - в checkpoint.
19.7 Наблюдать за контрольными точками
В PostgreSQL 17 статистику по контрольным точкам вынесли в отдельный
вид pg_stat_checkpointer (раньше она жила в pg_stat_bgwriter):
SELECT num_timed, num_requested, buffers_written
FROM pg_stat_checkpointer;
num_timed- контрольные точки по таймеру (checkpoint_timeout);num_requested- по объёму журнала или вручную;buffers_written- сколько грязных буферов они записали.
Здоровый признак - когда num_timed заметно больше num_requested:
значит, контрольные точки случаются спокойно по расписанию, а не
авралом из-за переполнения журнала. Перекос в сторону num_requested
- сигнал поднять
max_wal_size. Приlog_checkpoints = onкаждая контрольная точка ещё и пишет в лог сервера подробности.
19.8 Граница главы: восстановление есть, репликации пока нет
Мы собрали полный цикл надёжности одиночного сервера: изменения идут в журнал (write-ahead), копятся грязными в кеше, периодически сбрасываются контрольной точкой, а после сбоя redo доигрывает журнал от последней контрольной точки. Закоммиченное переживает сбой, незакоммиченное откатывается через clog.
Тот же журнал умеет больше, чем восстановление на одном сервере: его
можно передать на реплику, заархивировать для восстановления на точку
в прошлом, разобрать на логические изменения. Но сколько именно
информации писать в журнал - зависит от режима wal_level. Им и
займёмся в последней главе части.
Уроки в sandbox
lab-19.1. Найди redo point и посмотри, что проиграет восстановление
Вскроем машинерию восстановления, не дожидаясь настоящего сбоя.
Прочитаем redo point из управляющих данных, увидим, как контрольная
точка подтягивает его к текущей позиции, и через pg_walinspect
посмотрим ровно те записи журнала, которые crash recovery проиграл бы
от redo point до конца. Перед шагами предскажи, как сдвинется redo
point после CHECKPOINT.
Прочитай redo point:
SELECT redo_lsn FROM pg_control_checkpoint();- это место, с которого началось бы восстановление.Нагенерируй изменения (INSERT/UPDATE) и сравни
redo_lsnсpg_current_wal_lsn()- между ними и лежит то, что пришлось бы проиграть.Вызови
CHECKPOINT;и снова снимиredo_lsn- предскажи: подтянется ли он к текущей позиции?Через
pg_get_wal_records_info(redo_lsn, pg_current_wal_lsn())посмотри записи, которые проиграло бы восстановление.Открой во второй сессии незакоммиченную транзакцию и убедись, что её строк не видно: redo накатил бы их физически, но clog держит их невидимыми.
sandbox с автопроверкой - открыть в песочнице
Резюме
- Контрольная точка сбрасывает все грязные буферы на диск и записывает в `pg_control` redo point - позицию журнала, до которой данные уже на диске.
- Восстановление начинается не с начала журнала, а с redo point последней контрольной точки; частота контрольных точек задаёт верхнюю границу времени recovery.
- Redo point - позиция на момент НАЧАЛА контрольной точки, потому что сброс буферов не мгновенен, а база тем временем пишет новый журнал.
- Crash recovery проигрывает WAL от redo point вперёд до обрыва CRC; FPI чинят рваные страницы, дельты накатывают изменения.
- Redo физически накатывает и незакоммиченные изменения, но их транзакции не отмечены в clog - MVCC делает их невидимыми, то есть откатывает.
- Контрольная точка запускается по `checkpoint_timeout` (5 мин), по `max_wal_size` (1 ГБ) или вручную; запись размазывается через `checkpoint_completion_target`.
- Статистику смотрят в `pg_stat_checkpointer` (PG17): перекос `num_requested` над `num_timed` - сигнал поднять `max_wal_size`.
Контрольные вопросы
Зачем нужна контрольная точка, если все изменения и так есть в журнале?
Показать ответ
Чтобы не проигрывать журнал с начала времён. Без контрольных точек восстановление после сбоя означало бы повтор всех изменений за всю историю базы. Контрольная точка сбрасывает грязные буферы на диск и фиксирует redo point - позицию, до которой данные уже надёжно в файлах. После этого журнал до redo point не нужен для восстановления, а recovery начинается с этой точки. Чем чаще контрольные точки, тем короче восстановление, но тем больше работы по записи в обычное время.
Почему redo point - это позиция на начало контрольной точки, а не на её конец?
Показать ответ
Потому что контрольная точка не мгновенна: сброс всех грязных буферов может занять минуты, и всё это время база работает и пишет новый журнал. Контрольная точка гарантирует, что запишет всё, что было грязным к её началу. Значит, восстановление, начатое с позиции на начало контрольной точки, увидит в журнале все изменения, которых на тот момент ещё не было на диске, и доиграет их. Брать позицию на конец было бы неверно - часть изменений между началом и концом могла не попасть в файлы данных.
Если redo проигрывает весь журнал, почему незакоммиченные транзакции не оживают?
Показать ответ
Redo действительно накатывает на страницы и изменения незакоммиченных транзакций - физически их версии появляются в данных. Но видимость в PostgreSQL определяется статусом транзакции в clog, а не наличием версии. Транзакция, не успевшая закоммититься до сбоя, осталась в clog незавершённой, поэтому по правилам видимости её версии не видны никому
- она фактически откачена. Redo восстанавливает физическое состояние, а логику «видно/не видно» берёт на себя MVCC поверх clog.
Что запускает контрольную точку и что означает max_wal_size?
Показать ответ
Контрольная точка запускается по таймеру (
checkpoint_timeout, 5 мин по умолчанию), по объёму накопленного журнала (max_wal_size, 1 ГБ), вручную командойCHECKPOINTили, например, перед остановкой сервера - что наступит раньше.max_wal_size- не жёсткий лимит на размерpg_wal/, а порог, при котором инициируется контрольная точка. Если база пишет быстрее, журнал может временно его превысить. На нагруженных базах его поднимают, чтобы контрольные точки шли по таймеру, а не авралом по объёму.На что смотреть в pg_stat_checkpointer и о чём говорит перекос num_requested?
Показать ответ
Смотрят на
num_timed(контрольные точки по таймеру),num_requested(по объёму журнала или вручную) иbuffers_written(сколько буферов они сбросили). Здоровая картина - когдаnum_timedзаметно большеnum_requested: контрольные точки случаются спокойно по расписанию. Перекос в сторонуnum_requestedозначает, что журнал переполняется быстрее, чем проходит таймер, и контрольные точки идут авралом по объёму - это сигнал поднятьmax_wal_size.