PostgreSQL

WAL: Write-Ahead Log и durability

Каждый раз когда PostgreSQL говорит 'COMMIT' - данные уже на диске. Не в памяти, не 'скоро запишутся' - именно на диске. Это гарантия durability, буква D в ACID. Механизм который это обеспечивает - WAL. Без понимания WAL невозможно правильно настроить производительность, репликацию или понять почему база восстановилась после краша быстро.

  • **Stripe (2022):** при переходе с HDD на NVMe SSD главным выигрышем стало снижение WAL fsync latency с 3-5ms до 0.15ms. Throughput вырос на 40% без изменения кода приложения - просто WAL стал быстрее.
  • **Heroku PostgreSQL:** обещает crash recovery < 10 минут на инстансах до 500GB за счёт оптимизированного WAL replay на NVMe. Это SLA-гарантия, которую делает возможной архитектура WAL.
  • **Citus (распределённый PostgreSQL):** при добавлении шардов использует WAL-репликацию для синхронизации. wal_level = logical необходим для logical replication между нодами - настройка WAL напрямую влияет на возможности горизонтального масштабирования.

Что такое WAL и зачем он нужен

Write-Ahead Log (WAL) - фундаментальный механизм durability в PostgreSQL. Правило одно: перед изменением любых данных на диске - сначала запись в WAL. Если сервер упадёт в любой момент, WAL содержит достаточно информации для полного восстановления.

WAL позволяет PostgreSQL откладывать запись грязных страниц из shared_buffers на диск - достаточно что WAL уже на диске. Это кардинально ускоряет COMMIT: последовательная запись в WAL-файл быстрее случайной записи в heap-файлы таблиц.

Stripe перешёл на NVMe SSD именно ради WAL: на SATA SSD fsync WAL занимал 2-5ms, на NVMe - 0.1-0.3ms. При 5000 TPS это разница между 99th percentile в 8ms и в 50ms для транзакционных запросов.

Почему COMMIT возвращает успех клиенту только после записи WAL на диск, но не ждёт записи страниц таблицы?

WAL segments и LSN

WAL хранится как последовательность сегментов по 16MB (по умолчанию) в каталоге $PGDATA/pg_wal/. Каждая запись в WAL имеет адрес - LSN (Log Sequence Number), 64-битное монотонно возрастающее значение.

PostgreSQL pre-allocates несколько WAL-сегментов вперёд (wal_keep_size или min_wal_size). Количество WAL-файлов регулируется параметром max_wal_size (default 1GB). При превышении начинается checkpoint или старые файлы переиспользуются.

Что такое LSN (Log Sequence Number)?

Checkpoints: синхронизация WAL и heap

Checkpoint - момент, когда PostgreSQL гарантирует что все грязные страницы из shared_buffers записаны на диск. После checkpoint при crash recovery достаточно воспроизвести только WAL после этой точки - не весь WAL с начала времён.

checkpoint_completion_target = 0.9 означает: растянуть запись грязных страниц на 90% времени между checkpoint'ами. Это сглаживает I/O нагрузку. Если checkpoints_req >> checkpoints_timed в pg_stat_bgwriter - max_wal_size слишком мал и checkpoint'ы происходят слишком часто из-за переполнения WAL.

Что означает высокое значение checkpoints_req в pg_stat_bgwriter?

Full Page Writes: защита от torn pages

Операционная система и диск используют 512B или 4KB блоки, а PostgreSQL - 8KB страницы. При сбое питания запись 8KB страницы может прерваться на середине - половина страницы будет старой, половина новой. Это называется torn page.

CloudNativePG (оператор Kubernetes для PostgreSQL) всегда оставляет full_page_writes = on даже на enterprise SSD с battery backup. Причина: защита нужна не только от power failure, но и от багов в драйверах и firmware диска.

Зачем PostgreSQL записывает full page image в WAL только при первом изменении страницы после checkpoint, а не при каждом?

Crash Recovery: от LSN к живой базе

При старте после краша PostgreSQL автоматически запускает recovery. Находит последний checkpoint, читает WAL с этой точки и replay каждую запись. Транзакции без COMMIT откатываются. Процесс полностью автоматический.

Heroku PostgreSQL гарантирует RTO (Recovery Time Objective) < 10 минут на инстансах до 500GB. Это возможно именно потому что crash recovery - replay WAL - работает с максимальной скоростью последовательного чтения диска, что на NVMe даёт 3-5 GB/s.

synchronous_commit = off опасен для данных и его нельзя использовать

synchronous_commit = off жертвует durability последних ~200ms транзакций (wal_writer_delay) в обмен на снижение latency. Это приемлемо для некритичных данных: счётчики просмотров, аналитические события, логи. Транзакция не будет потеряна частично - потеряется целиком или не потеряется.

При synchronous_commit = off COMMIT возвращает успех до fsync WAL. Если сервер упадёт в этот момент, последние транзакции (за несколько сотен миллисекунд) будут потеряны. Но они потеряются полностью - WAL не содержит их совсем. Это отличается от data corruption - данные согласованны, просто часть операций откатится при recovery.

Откуда PostgreSQL узнаёт, с какого LSN начинать replay WAL при crash recovery?

Итоги

  • **WAL = durability:** запись в WAL предшествует любому изменению данных на диске. COMMIT возвращает успех только после fsync WAL - это гарантия D в ACID
  • **Checkpoint - граница recovery:** при crash восстанавливается только WAL после последнего checkpoint. Частые checkpoint'ы ускоряют recovery но увеличивают I/O
  • **full_page_writes защищает от torn pages:** первое изменение страницы после checkpoint записывает полную 8KB копию в WAL, чтобы дельты можно было применить даже после частичной записи страницы

Связанные темы

WAL - основа многих механизмов PostgreSQL:

  • Репликация (streaming replication) — Standby серверы получают те же WAL-записи и replay их - репликация работает поверх WAL-механизма
  • Shared Buffers и Buffer Cache — WAL позволяет откладывать запись грязных страниц из buffer cache - понимание их взаимодействия критично для тюнинга
  • Background Workers и процессы — walwriter процесс отвечает за периодический flush WAL буфера; checkpointer запускает checkpoint и взаимодействует с bgwriter

Вопросы для размышления

  • Если увеличить max_wal_size с 1GB до 8GB - как это повлияет на частоту checkpoint'ов, время crash recovery и I/O нагрузку?
  • Почему logical replication требует wal_level = logical, а streaming replication работает с wal_level = replica? Что дополнительно пишется в WAL при logical?
  • Как рассчитать оптимальный checkpoint_timeout для системы где RTO (Recovery Time Objective) = 5 минут, а WAL генерируется со скоростью 100MB/s?

Связанные уроки

  • db-03-acid
WAL: Write-Ahead Log и durability

0

1

Войти