WAL — журнал упреждающей записи

Предпосылки: страницы и кортежи.

Физическая структура хранения | Буферный кеш

Страницы сделали обновления управляемыми, но не дали гарантию «COMMIT переживёт падение». Durability в PostgreSQL строится на журнале, который можно проиграть при восстановлении.

Проблема: записывать на диск при каждом COMMIT слишком дорого

Страницы таблиц разбросаны по диску. Если пытаться «сохранить всё прямо на COMMIT», придётся подтверждать запись набора страниц в разные места файлов данных. Это множество независимых операций ввода‑вывода: случайный I/O (random I/O) (HDD vs SSD).

На HDD одна случайная запись страницы может занимать около 10 мс (см. оценку в страницах и кортежах). Транзакция, изменившая 10 разных страниц, превращает COMMIT в порядок 100 мс только на записи этих страниц.

Если транзакция изменила данные на нескольких страницах, наивный подход заставляет COMMIT ждать диск: пока все эти страницы не будут надёжно записаны. При параллельной нагрузке это быстро превращается в узкое место и делает время COMMIT непредсказуемым.

Альтернатива: держать изменения в памяти, писать на диск позже пачкой. Но при сбое всё, что в памяти, теряется. Нарушение Durability.

Решение: Write-Ahead Log (WAL)

WAL (журнал упреждающей записи) — последовательный лог всех изменений.

Почему «упреждающий» (write-ahead)? Запись в лог происходит до того, как изменения попадают в основные файлы данных. Сначала лог, потом данные.

Механизм:

  1. Транзакция меняет страницы в памяти
  2. Каждое изменение записывается в WAL-буфер
  3. При COMMIT: WAL-буфер сбрасывается на диск (fsync — принудительно довести запись до диска)
  4. Клиент получает «OK»
  5. Изменённые страницы остаются в памяти, записываются на диск позже

Dirty page (грязная страница) — страница в памяти, которая изменена, но ещё не записана на диск. Название буквальное: страница «испачкана» изменениями, которые ещё не сохранены.

Почему WAL быстрее:

WAL — один поток: запись в него почти всегда последовательная (append‑only). Последовательная запись устройствам хранения даётся намного дешевле, чем «скакать» по разным страницам в файлах таблиц.

Обычно WAL хранит описание изменения (дельту), а не полный образ страницы. Полный образ страницы тоже бывает нужен — например, как защита от torn page после контрольной точки (см. Full Page Writes в буферном кеше).

Checkpoint: когда записывать dirty pages на диск

Dirty pages копятся в памяти. Когда их записывать в основные файлы?

Checkpoint (контрольная точка) — момент, когда PostgreSQL записывает все dirty pages на диск и делает отметку: «все изменения до этой точки сохранены».

WAL timeline:
[records][records][CHECKPOINT][records][records][сейчас]

            все dirty pages записаны

Зачем нужен checkpoint:

WAL растёт бесконечно. После checkpoint’а старые WAL-записи можно удалить — изменения уже в основных файлах, WAL для них не нужен.

Проблема checkpoint’а: Нужно записать все dirty pages. Если их много — всплеск I/O, база «подвисает».

Чтобы не создавать резких всплесков I/O, PostgreSQL старается распределять запись контрольной точки во времени: часть dirty pages записывается заранее, а не «одним залпом».

Trade-off checkpoint’ов:

Частые checkpoint’ы → меньше WAL нужно проигрывать при восстановлении, но больше записи в WAL из-за Full Page Writes. После каждого checkpoint’а первое изменение страницы записывает её полный образ (8 КБ), а не только дельту. Чем чаще checkpoint’ы — тем чаще страницы «сбрасывают» флаг FPW и записывают полные образы снова. Это write amplification: объём записи в WAL превышает объём фактических изменений данных.

Редкие checkpoint’ы → меньше write amplification, но дольше восстановление при сбое: нужно проигрывать больший хвост WAL.

Recovery: как WAL спасает данные при сбое

Сервер упал. В памяти были dirty pages, которые не успели записаться на диск. Данные потеряны?

Нет. При старте PostgreSQL:

  1. Находит последний checkpoint — точку, где все изменения были сохранены
  2. Читает WAL-записи от checkpoint’а до конца
  3. «Проигрывает» изменения заново (redo)
  4. Данные восстановлены

Это работает, потому что WAL — упорядоченный поток записей, и у каждой записи есть позиция LSN (Log Sequence Number). Recovery применяет WAL‑записи по LSN и не «переигрывает» то, что уже находится на диске. Каждая страница хранит pd_lsn — LSN последнего изменения страницы. При redo PostgreSQL сравнивает LSN записи с pd_lsn страницы: если страница уже содержит это изменение (или более новое) — запись пропускается. Поэтому redo можно безопасно повторять: запись либо применится один раз, либо будет проигнорирована.

WAL гарантирует, что COMMIT переживёт сбой. Но данные при этом остаются в памяти как dirty pages — буферный кеш управляет этой памятью и координирует запись на диск.

Sources


Физическая структура хранения | Буферный кеш