Пошук уроків, статей та іншого контенту
Розберете призначення WAL, його сегменти, архівацію та роль журналу в надійності й відновленні даних.
WAL, або Write-Ahead Log, — це журнал попереднього запису PostgreSQL. У ньому сервер спочатку фіксує зміни, а вже потім записує змінені сторінки таблиць та індексів у файли даних.
Основне правило WAL:
Запис про зміну має бути надійно збережений у WAL до того, як відповідна змінена сторінка потрапить на диск.
Це дає змогу відновити узгоджений стан бази після:
аварійного завершення процесу;
перезавантаження сервера;
збою живлення;
пошкодження або неповного запису сторінок даних.
WAL не є журналом дій користувачів і не зберігає SQL-запити в початковому вигляді. Це внутрішній бінарний журнал операцій, необхідних PostgreSQL для відновлення сторінок даних.
Під час зміни даних PostgreSQL зазвичай не записує змінену сторінку таблиці на диск негайно. Сторінка потрапляє до буферів у пам’яті, а інформація про зміну записується у WAL.
Спрощена послідовність така:
Транзакція змінює сторінку таблиці або індексу.
PostgreSQL створює WAL-запис про цю зміну.
WAL-запис потрапляє до WAL-буфера.
Під час COMMIT WAL записується на диск відповідно до налаштування synchronous_commit.
Пізніше фонова підсистема записує змінену сторінку даних у файли таблиць.
Якщо сервер аварійно зупинився між кроками 4 і 5, сторінка даних може ще не містити зміни. Під час наступного запуску PostgreSQL прочитає WAL і повторить потрібну операцію над сторінкою.
Цей процес називається crash recovery, або аварійне відновлення.
Запис змінених сторінок на диск для кожної операції був би повільним і складним. Одна транзакція може змінити багато сторінок, а різні сторінки можуть розташовуватися далеко одна від одної на диску.
WAL дає змогу:
послідовно записувати журнал;
групувати записи кількох транзакцій;
відкладати запис сторінок даних;
відновлювати зміни після збою.
Послідовний запис журналу зазвичай ефективніший за випадковий запис багатьох сторінок даних.
Кожен WAL-запис має позицію в журналі, яка називається LSN — Log Sequence Number.
LSN записується у вигляді на кшталт:
0/16B6C50Це не номер рядка і не час. LSN позначає позицію у потоці WAL та дає змогу порівнювати порядок записів:
більший LSN означає пізніший запис;
за LSN можна оцінити обсяг згенерованого WAL;
LSN використовують під час реплікації, резервного копіювання та відновлення.
Подивитися поточні позиції можна SQL-запитами:
SELECT
pg_current_wal_lsn() AS current_lsn,
pg_current_wal_insert_lsn() AS insert_lsn,
pg_current_wal_flush_lsn() AS flush_lsn;pg_current_wal_insert_lsn() показує позицію, до якої WAL було вставлено записи.
pg_current_wal_flush_lsn() показує позицію, до якої WAL було скинуто на диск поточним сервером.
Різниця між цими значеннями може свідчити про WAL, який ще перебуває в буферах і не був скинутий на диск.
WAL зберігається не в одному нескінченному файлі, а в окремих файлах — сегментах WAL.
У стандартній конфігурації розмір сегмента становить 16 МБ. Розмір визначається під час створення кластера бази даних і не змінюється звичайною зміною параметра конфігурації.
Файли WAL зберігаються в каталозі:
$PGDATA/pg_wal/У старіших версіях PostgreSQL цей каталог називався pg_xlog.
Назва WAL-файлу має шістнадцятковий формат, наприклад:
00000001000000000000000AНазва містить інформацію про:
timeline;
старшу частину логічної позиції;
номер сегмента.
Точну назву сегмента для поточного LSN можна отримати так:
SELECT
pg_current_wal_lsn() AS lsn,
pg_walfile_name(pg_current_wal_lsn()) AS wal_file;Оцінити відстань між двома позиціями можна за допомогою pg_wal_lsn_diff:
SELECT pg_size_pretty(
pg_wal_lsn_diff(
pg_current_wal_lsn(),
'0/0'
)
) AS generated_wal;У реальному кластері позицію '0/0' зазвичай замінюють на LSN, отриманий раніше, наприклад на початку вимірювання.
Коли поточний сегмент заповнюється, PostgreSQL переходить до наступного. Старі сегменти можуть:
бути повторно використані;
бути видалені;
залишатися в pg_wal, якщо вони ще потрібні для реплікації або архівації.
Кількість файлів у pg_wal не дорівнює кількості всього WAL, який коли-небудь створювався. PostgreSQL зазвичай повторно використовує старі сегменти.
Не можна вручну видаляти файли з pg_wal. Це може зробити кластер непридатним для запуску або зруйнувати можливість відновлення.
Checkpoint — це момент, коли PostgreSQL записує на диск достатню кількість змінених сторінок даних і створює в WAL контрольну точку.
Під час запуску після збою PostgreSQL не обов’язково читає весь WAL від початку історії. Він знаходить останній відповідний checkpoint і продовжує відновлення з нього.
Основні параметри checkpoint:
SHOW checkpoint_timeout;
SHOW max_wal_size;
SHOW min_wal_size;checkpoint_timeout задає максимальний інтервал між автоматичними checkpoint;
max_wal_size визначає приблизний обсяг WAL, після якого checkpoint може бути запущений;
min_wal_size впливає на мінімальну кількість WAL, яку PostgreSQL намагається зберігати для повторного використання.
Checkpoint не означає, що WAL більше не потрібен у будь-якому сценарії. Для архівації, відновлення до моменту в часі та реплікації можуть бути потрібні значно старші WAL-сегменти.
Після аварійного завершення PostgreSQL під час запуску перевіряє стан контрольних файлів і знаходить незавершене коректне завершення роботи.
Далі сервер:
знаходить останню контрольну точку;
читає WAL після неї;
повторює необхідні зміни сторінок;
відновлює узгоджений стан;
робить базу доступною для підключень.
WAL-запис описує операцію на сторінці, а не обов’язково повний новий стан рядка. Тому для коректного відновлення PostgreSQL використовує внутрішню структуру сторінок, ідентифікатори блоків та інші службові дані.
Параметр full_page_writes захищає від проблеми часткового запису сторінки. Якщо збій станеться під час запису сторінки, на диску можуть опинитися частини старого та нового вмісту. Після checkpoint PostgreSQL може записати повну копію зміненої сторінки у WAL, щоб відновлення могло замінити пошкоджений варіант.
Перевірити параметри можна так:
SHOW wal_level;
SHOW full_page_writes;
SHOW fsync;
SHOW synchronous_commit;Для звичайного надійного режиму зазвичай очікують:
fsync = on;
full_page_writes = on;
wal_level не нижче replica.
Не слід вимикати fsync або full_page_writes у робочій базі без чіткого розуміння наслідків.
Під час виконання COMMIT клієнт отримує підтвердження не просто після зміни сторінки в пам’яті. PostgreSQL також враховує, коли WAL було скинуто на диск.
Поведінка залежить від synchronous_commit.
За значенням:
SHOW synchronous_commit;можна побачити поточний режим.
У звичайному режимі on транзакція вважається підтвердженою після того, як її WAL надійно скинуто на локальний диск. Це зменшує ризик втрати вже підтверджених транзакцій після аварії.
Якщо застосунок використовує менш строгий режим, сервер може повернути успішний результат раніше, ніж WAL буде скинуто на диск. У такому разі після збою невелика кількість уже підтверджених транзакцій може бути втрачена.
Це компроміс між продуктивністю та довговічністю підтверджених змін.
Самих WAL-файлів у pg_wal недостатньо для довготривалого відновлення. PostgreSQL може повторно використати або видалити старий сегмент, коли він більше не потрібен локальному серверу.
Щоб зберігати WAL поза основним кластером, використовують WAL-архівацію.
Архівація потрібна для:
відновлення бази після втрати основного диска;
point-in-time recovery;
зберігання безперервного ланцюжка змін між базовими резервними копіями;
перенесення WAL на інший сервер.
Для ввімкнення архівації потрібно налаштувати щонайменше:
wal_level = replica
archive_mode = on
archive_command = '...'archive_mode вмикають на рівні кластера, і для його зміни потрібен перезапуск сервера.
archive_command — це команда операційної системи. PostgreSQL виконує її для кожного завершеного WAL-сегмента.
У команді доступні спеціальні підстановки:
%p — повний шлях до локального WAL-файлу;
%f — ім’я WAL-файлу без шляху.
Наприклад, для локального тестового архіву:
archive_command = 'test ! -e /var/lib/postgresql/wal_archive/%f && cp %p /var/lib/postgresql/wal_archive/%f'Перед цим каталог архіву має існувати, а користувач PostgreSQL — мати до нього доступ.
Команда повинна завершуватися кодом 0 лише після успішного збереження WAL-файлу. Якщо команда завершується ненульовим кодом, PostgreSQL вважає архівацію невдалою і спробує виконати її знову.
Команда архівації повинна бути:
ідемпотентною;
безпечною при повторному запуску;
здатною повідомити про помилку;
достатньо швидкою, щоб архіватор не відставав від генерації WAL;
спрямованою на окреме сховище, бажано не на той самий диск.
Поширена помилка — використовувати команду, яка завжди повертає успішний код, навіть якщо копіювання не відбулося. У такому випадку PostgreSQL може вважати сегмент заархівованим, хоча файл фактично втрачено.
Ще одна помилка — зберігати архів на тому самому диску, що й PGDATA. Це не захищає від відмови диска.
Стан архіватора можна перевірити так:
SELECT
archived_count,
failed_count,
last_archived_wal,
last_archived_time,
last_failed_wal,
last_failed_time,
stats_reset
FROM pg_stat_archiver;Особливо важливі поля:
archived_count — кількість успішно заархівованих сегментів;
failed_count — кількість невдалих спроб;
last_archived_wal — останній заархівований файл;
last_failed_wal — останній файл, архівація якого завершилася помилкою.
Зростання failed_count потребує перевірки дискового простору, дозволів, доступності архівного сховища та самої команди.
За нормального навантаження новий WAL-сегмент створюється після заповнення поточного. Для тестування або керування моментом перемикання можна використати:
SELECT pg_switch_wal();Функція завершує поточний WAL-сегмент і переходить до нового. Це не означає, що новий сегмент одразу буде заархівований: архіватор ще має отримати його та успішно виконати archive_command.
Параметр archive_timeout дає змогу примусово перемикати сегмент, якщо протягом заданого часу не набралося достатньо WAL:
archive_timeout = 300Це може бути корисно для систем із низькою активністю, де потрібно регулярно доставляти WAL до архіву. Водночас занадто мале значення створює багато частково заповнених сегментів і збільшує накладні витрати.
Для повного відновлення до певного моменту потрібні дві складові:
базова резервна копія кластера;
безперервний ланцюжок WAL, створений після цієї копії.
WAL сам по собі не замінює базову резервну копію. Він описує зміни, але не містить повної початкової копії всіх файлів даних.
Типовий процес виглядає так:
створити базову резервну копію;
зберігати всі WAL-сегменти, створені після її початку;
під час відновлення розгорнути базову копію;
передати PostgreSQL архівовані WAL;
відтворити зміни до потрібного моменту.
Якщо хоча б один необхідний WAL-сегмент втрачений, безперервність ланцюжка порушується, і відновлення до пізнішого моменту може стати неможливим.
Point-in-time recovery, або PITR, дає змогу відновити базу не лише до стану останньої резервної копії, а до певного моменту в часі.
Наприклад, якщо помилкова команда DELETE була виконана о 14:30, можна відновити копію до 14:29:59 — за умови, що є базова копія та всі необхідні WAL.
Під час налаштування відновлення PostgreSQL використовує параметри на кшталт:
restore_command = 'cp /var/lib/postgresql/wal_archive/%f %p'Тут:
%f — ім’я WAL-файлу, який потрібно отримати;
%p — шлях, куди його потрібно скопіювати.
Для сучасних версій PostgreSQL сценарій відновлення також визначається параметрами цілі відновлення та спеціальним файлом сигналу, наприклад recovery.signal.
Важливо, що restore_command має повертати ненульовий код, якщо потрібного WAL-файлу ще немає або його неможливо отримати. Це дає серверу змогу коректно керувати пошуком наступних файлів.
Корисно регулярно перевіряти:
розмір каталогу pg_wal;
стан WAL-архіватора;
кількість невдалих архівацій;
затримку архівування;
генерацію WAL під навантаженням;
наявність вільного місця.
Поточний розмір WAL можна оцінити в операційній системі:
du -sh "$PGDATA/pg_wal"Поточну позицію та назву WAL-файлу — у PostgreSQL:
SELECT
pg_current_wal_lsn() AS current_lsn,
pg_walfile_name(pg_current_wal_lsn()) AS current_wal_file;Для вимірювання генерації WAL можна зняти дві позиції з інтервалом:
SELECT pg_current_wal_lsn() AS lsn_before;
-- Через деякий час виконати повторно
SELECT pg_current_wal_lsn() AS lsn_after;Різницю можна обчислити так:
SELECT pg_size_pretty(
pg_wal_lsn_diff('0/2000000', '0/1000000')
) AS wal_difference;У практичному сценарії замість наведених позицій використовують значення з попередніх вимірювань.
pg_walВидаляти WAL-файли вручну небезпечно. PostgreSQL сам визначає, які сегменти ще потрібні.
Якщо pg_wal переповнюється, потрібно шукати причину:
архівація не працює;
реплікація відстає;
активна транзакція утримує старі дані;
неправильно працює слот реплікації;
недостатньо місця в архівному сховищі.
Файл може існувати в архіві, але бути пошкодженим або неповністю скопійованим. Команда архівації повинна вважати операцію успішною лише після завершення повного копіювання.
Копія WAL у сусідній каталог не захистить від відмови диска або втрати сервера. Архів потрібно зберігати окремо та регулярно перевіряти можливість його читання.
fsyncfsync = off може прискорити тимчасові тести, але створює ризик пошкодження даних після збою. Для робочого кластера це зазвичай неприйнятний режим.
archive_timeoutНадто часте перемикання сегментів може створювати багато частково заповнених WAL-файлів. Значення потрібно вибирати з урахуванням вимог до відновлення та обсягу навантаження.
WAL не призначений для перегляду історії дій користувачів або відновлення попередніх значень рядків у зручному для людини форматі. Для аудиту потрібні інші механізми.
WAL — це журнал попереднього запису, який PostgreSQL використовує для надійності та відновлення.
Запис у WAL має бути збережений до запису відповідної сторінки даних.
WAL зберігається у послідовності сегментів, зазвичай розміром 16 МБ.
LSN визначає позицію запису в WAL і використовується для контролю прогресу.
Після аварії PostgreSQL читає WAL після checkpoint і відновлює узгоджений стан.
Архівація WAL потрібна для захисту від втрати основного кластера та point-in-time recovery.
Для PITR потрібні базова резервна копія та безперервний набір WAL після неї.
archive_command має бути надійною, ідемпотентною та правильно повідомляти про помилки.
Файли з pg_wal не можна видаляти вручну.
Стан архівації потрібно контролювати через pg_stat_archiver і системний моніторинг.