WAL и долговечность
Журнал упреждающей записи фиксирует каждое изменение до того, как тронута страница данных. fsync и synchronous_commit — рычаг долговечность-против-пропускной способности; checkpoint'ы ограничивают время восстановления; тот же WAL питает репликацию и PITR.
Платёжный сервис коммитит заказ, возвращает клиенту 200 OK, и через 80 миллисекунд машина теряет питание. После перезагрузки заказ на месте, total цел — хотя сама страница таблицы не успела на диск до краха. Это выживание не везение; это журнал упреждающей записи, делающий ровно одну работу: записывает то, что ты собираешься изменить, до изменения. Переключи один флаг конфига — synchronous_commit = off — и тот же заказ может исчезнуть при следующем крахе, в обмен на 3× пропускной способности записи. Этот урок — про этот флаг и машинерию за ним.
Упреждающая запись: сначала лог, потом страница
Прежде чем решать, стоит ли переключить synchronous_commit, нужно понять, что он контролирует — потому что два флага долговечности делают принципиально разные вещи. Главное правило защиты от краха — журнал упреждающей записи (write-ahead logging, WAL): прежде чем Postgres изменит страницу данных, он сначала дописывает компактную запись об изменении в WAL — последовательный, только-на-добавление лог. Только запись WAL должна быть сброшена на долговечное хранилище в момент коммита; сами страницы таблицы и индекса можно записать позже, лениво, фоновым писателем и checkpointer.
Почему это быстрее и безопаснее, чем запись самой страницы данных при коммите? Потому что записи WAL последовательны (дописывание в конец текущего файла лога), тогда как записи страниц данных случайны (разбросаны по heap). Форсировать один маленький последовательный fsync на коммит куда дешевле, чем форсировать много случайных записей страниц — и этого достаточно, потому что если машина падает до сброса грязных страниц, восстановление переигрывает WAL и реконструирует их. Страницы данных — оптимизация; WAL — источник истины.
-- Где WAL сейчас и как быстро он генерируется:
SELECT pg_current_wal_lsn(); -- текущая позиция записи (LSN)
SHOW synchronous_commit; -- on | off | remote_apply ...
SHOW wal_level; -- replica (по умолч.) | logical | minimal
SHOW max_wal_size; -- мягкий потолок, запускающий checkpoint'ыРычаг долговечность/пропускная способность
synchronous_commit управляет, когда коммит подтверждается относительно сброса WAL:
on(по умолчанию) —COMMITждёт, пока запись WAL не будетfsync-нута на долговечное хранилище. Закоммиченная транзакция переживёт крах. Полная долговечность, один fsync латентности на коммит.off—COMMITвозвращается немедленно после записи WAL в буфер ОС, до fsync. Сброс происходит асинхронно в пределахwal_writer_delay(≈200 мс). При крахе можно потерять последнюю долю секунды закоммиченных транзакций — но база остаётся консистентной (без повреждений, без рваного состояния), ты теряешь лишь хвост. В обмен пропускная способность на мелких транзакциях записи может подскочить в 2–3×, потому что коммиты больше не блокируются на диске.
Это самое значимое решение по тюнингу долговечности. Сеньорская формулировка: synchronous_commit = off — это не «небезопасно» так, как отключение fsync; он никогда не повреждает базу, он лишь расширяет окно потерянных-но-подтверждённых коммитов. Для платёжного реестра: никогда. Для аналитического конвейера загрузки, способного переиграть свой источник: часто это бесплатный 3× выигрыш.
Два связанных флага, которые путают: fsync = off — реально опасный; он позволяет самому WAL пропустить сброс на диск, так что крах может повредить кластер; никогда не используй вне одноразовой тестовой машины. А synchronous_commit = remote_apply расширяет долговечность до применения изменения репликой (см. репликацию ниже).
Checkpoint’ы ограничивают восстановление
Если бы WAL копился вечно, восстановление после краха переигрывало бы с начала времён. Checkpoint — периодический акт сброса всех грязных страниц данных в heap и записи: «всё до этой позиции WAL теперь безопасно на файлах данных». Восстановлению нужно переиграть WAL только после последнего checkpoint — поэтому частота checkpoint’ов ограничивает время восстановления.
Компромисс: частые checkpoint’ы — короткое восстановление, но больше постоянного I/O записи (частый сброс грязных страниц); редкие checkpoint’ы — меньше ровного I/O, но долгое, скачкообразное восстановление и большой WAL. Postgres запускает checkpoint, когда истекает checkpoint_timeout (по умолчанию 5 мин) или WAL достигает max_wal_size (по умолчанию 1 ГБ). Прод-тюнинг обычно повышает оба, чтобы checkpoint’ы были разреженнее, а пики записи сглажены, принимая более долгое восстановление — checkpoint_completion_target (по умолчанию 0.9) растягивает сброс по интервалу, избегая стены I/O.
▸Почему это работает
Почему тот же WAL — основа и репликации, и восстановления на момент времени? Потому что WAL — полное, упорядоченное описание каждого изменения кластера. Потоковая репликация отправляет этот поток WAL на standby, который непрерывно его переигрывает, оставаясь почти живой копией — так работают read-реплики и HA-failover. Восстановление на момент времени (PITR) берёт базовый бэкап плюс архивированные сегменты WAL и переигрывает WAL до любой выбранной отметки времени или LSN, так что можно восстановиться до «5 секунд до запуска плохой миграции». Оба — это просто «переиграй WAL где-то ещё», ровно то, что делает восстановление после краха локально. Задай wal_level = replica (по умолчанию) или logical, чтобы хранить достаточно деталей WAL для этого; minimal логирует меньше и лишает их.
Почему логирование изменения в WAL до записи страницы данных делает коммиты и быстрыми, и долговечными?
Что именно происходит при synchronous_commit = off, если сервер падает сразу после возврата COMMIT?
Заполни пропуск: журнал упреждающей записи фиксирует изменение _______ того, как страница данных модифицирована, поэтому крах можно починить, переиграв лог и реконструировав любую ещё не сброшенную страницу.
- 01Что такое упреждающая запись и почему она быстрее и безопаснее записи страницы данных при коммите?
- 02Что меняет synchronous_commit = off и чем он отличается от fsync = off?
- 03Что такое checkpoint и как он связан с восстановлением после краха и репликацией?
Упреждающая запись — механизм долговечности Postgres: каждое изменение дописывается в последовательный WAL до модификации страницы данных, и при коммите fsync-ится лишь эта маленькая запись WAL — страницы heap и индекса сбрасываются лениво, потому что крах чинится переигрыванием WAL с последнего checkpoint. Поэтому коммиты быстры (один последовательный fsync, а не много случайных записей страниц) и долговечны (WAL — источник истины). Ключевой рычаг — synchronous_commit: on ждёт fsync WAL (полная долговечность); off подтверждает до него, рискуя последней долей секунды закоммиченных транзакций при крахе, но сохраняя базу консистентной и неповреждённой, ради 2–3× пропускной способности — категорически безопаснее, чем никогда-не-на-проде fsync = off. Checkpoint’ы сбрасывают грязные страницы и ограничивают, сколько WAL переиграть восстановлению; тюнь checkpoint_timeout, max_wal_size и checkpoint_completion_target, балансируя I/O записи против времени восстановления. Наконец, тот же самый WAL питает потоковую репликацию (отправь и переиграй на standby) и PITR (переиграй до выбранной отметки) — восстановление, репликация и восстановление во времени это всё просто «переиграй лог». Теперь, когда видишь базу, пережившую крах без потерь, реплику, отстающую на секунду, или восстановление к конкретному моменту — знаешь, какой единственный механизм сделал все три возможными.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.