Если экземпляр PostgreSQL был некорректно остановлен, то перед восстановлением файлов выполняется синхронизаций всех файлов кластера баз данных. Способ синхронизации определяется параметром конфигурации recovery_init_sync_method. В статье рассматривается, как ускорить запуск экземпляра и резервирование, если в директории PGDATA много файлов.
При запуске экземпляра по управляющему файлу (pg_control) проверяется последнее известное состояние экземпляра. Посмотреть состояние можно утилитой командной строки:
pg_controldata | grep state
Database cluster state: in production
Скрытый текст
В поле "Database cluster state" может быть указано одно из 7 состояний:
1) "shut down" - при запуске экземпляра на это состояние укажет запись в логе кластера баз данных: "database system was shut down at <дата и время>". Такое состояние будет записано в управляющий файл после корректной (с контрольной точкой) остановки экземпляра (режимы fast и smart);
2) "shut down in recovery" - запись в логе: "database system was shut down in recovery at..". Такое состояние будет, если экземпляр реплики сбойнул, когда находился в режиме восстановления
3) "in production" - либо экземпляр работает и обслуживает клиентов, либо экземпляр был остановлен в режиме immediate или сбойнул (например, выключилось питание). При запуске экземпляра в логе будет запись: "database system was interrupted; last known up at.."
4) "shutting down" - запись в логе: "database system shutdown was interrupted; last known up at.." экземпляр сбойнул в процессе остановки
5) "in crash recovery" - запись в логе: "database system was interrupted while in recovery at ..". HINT: This probably means that some data is corrupted and you will have to use the last backup for recovery. экземпляр мастера сбойнул при восстановлении ("crash recovery")
6) "in archive recovery" запись в логе: "database system was interrupted while in recovery at log time время. HINT: If this has occurred more than once some data might be corrupted and you might need to choose an earlier recovery target" - экземпляр сбойнул в процессе управляемого восстановления
7) также существует состояние "unrecognized status code", запись в логе: control file contains invalid database cluster state - состояние неизвестно.
Первые два состояния означают, что экземпляр был остановлен корректно, то есть с выполнением финальной контрольной точки, файлы кластера согласованы и более того, гарантированно записаны "на диск", так как по каждому файлу, который менялся любым процессом экземпляра на протяжении контрольной точки, процесс checkpointer отправляет системный вызов fsync в конце контрольной точки.
Если экземпляр не был корректно остановлен, то процесс startup выполняет синхронизацию файлов кластера и только затем приступает к восстановлению файлов по журналу предзаписи.
Способ синхронизации определяется значением параметра конфигурации recovery_init_sync_method. По умолчанию значение fsync, что означает, что процесс startup будет открывать файлы и затем посылать fsync
Скрытый текст
1) отдельно по каждому файлу в директории PGDATA
2) по всем файлам директорий, на которые указывает символическая ссылка PGDATA/pg_wal (если директория является символической ссылкой). Директорию с журналами предзаписи обычно монтируют на отдельную файловую систему.
3) символические ссылки в директории PGDATA/pg_tblspc, то есть по файлам в директориях табличных пространств.
По другим символическим ссылкам синхронизация не выполняется. Это важно знать, чтобы не использовать символические ссылки в директории PGDATA и её поддиректориях, с риском получить несинхронизированные блоки в файлах.
Если в кластере десятки тысяч файлов, то в linux будут посылаться десятки тысяч системных вызовов fsync, а это может занять долгое время. И это время будет добавляться к времени простоя кластера.
Зачем нужна синхронизация? Процесс startup перед накатом WAL должен убедиться, что все файлы кластера надёжно сохранены в системе хранения ("на диске"). Системный вызов fsync примечателен тем, что не только записывает блоки файла из страничного кэша, но и даёт указание драйверу устройства хранения (контроллеру шины ввода-вывода, контроллеру HDD/SDD) опустошить кэши. Другими словами, даже если в страничном кэше linux нет страниц файла, нет гарантий, что страницы не находятся в кэше оборудования и открытие файла с последующим вызовом fsync это один из способов убедиться, что содержимое кэшей оборудования записано на устройства постоянного хранения.
Посмотрим пример синхронизации. Создадим 4000 таблиц:
create schema test;
select format('create table test.t%s (c1 int, с2 text)
with (autovacuum_enabled=off);', g.id)
from generate_series(1, 4000) as g(id)
\gexec
Большое число таблиц создаётся для простоты. Длительность синхронизации зависит только от числа файлов, а не от числа таблиц. Даже одна таблица или секция таблицы может иметь 4000 файлов, так как максимальный размер файла данных по умолчанию 1Гб.
Остановим экземпляр без контрольной точки и запустим его:
time pg_ctl start
waiting for server to start....2025-03-12 19:15:51.589 MSK [126137] LOG: redirecting log output to logging collector process
2025-03-12 19:15:51.589 MSK [126137] HINT: Future log output will appear in directory "log".
........................................................ stopped waiting
pg_ctl: server did not start in time
real 0m60.185s
user 0m0.101s
sys 0m0.022s
По умолчанию утилита вернула промпт через минуту, однако при этом экземпляр ещё не запустился и продолжил синхронизировать файлы. Сообщения в логе кластера:
cat postgresql-2025-03-12.log
2025-03-12 19:15:51.589 MSK [126137] LOG: starting Tantor Special Edition 16.6.1 a74db619 on x86_64-pc-linux-gnu, compiled by gcc (Astra 12.2.0-14.astra3+b1) 12.2.0, 64-bit
2025-03-12 19:15:51.589 MSK [126137] LOG: listening on IPv4 address "0.0.0.0", port 5432
2025-03-12 19:15:51.589 MSK [126137] LOG: listening on IPv6 address "::", port 5432
2025-03-12 19:15:51.598 MSK [126137] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2025-03-12 19:15:51.623 MSK [126141] LOG: database system was interrupted; last known up at 2025-03-12 19:11:13 MSK
2025-03-12 19:16:01.804 MSK [126141] LOG: syncing data directory (fsync), elapsed time: 10.00 s, current path: ./base/5/23781
2025-03-12 19:16:11.805 MSK [126141] LOG: syncing data directory (fsync), elapsed time: 20.00 s, current path: ./base/5/23555
2025-03-12 19:16:21.805 MSK [126141] LOG: syncing data directory (fsync), elapsed time: 30.00 s, current path: ./base/5/35586
2025-03-12 19:16:23.279 MSK [126141] LOG: database system was not properly shut down; automatic recovery in progress
2025-03-12 19:16:23.292 MSK [126141] LOG: redo starts at 6/6BD55058
2025-03-12 19:16:24.202 MSK [126141] LOG: invalid record length at 6/6F6320C8: expected at least 26, got 0
2025-03-12 19:16:24.202 MSK [126141] LOG: redo done at 6/6F632080 system usage: CPU: user: 0.90 s, system: 0.00 s, elapsed: 0.91 s
2025-03-12 19:16:24.235 MSK [126139] LOG: checkpoint starting: end-of-recovery immediate wait
2025-03-12 19:16:48.618 MSK [126139] LOG: checkpoint complete: wrote 7347 buffers (44.8%); 0 WAL file(s) added,
0 removed, 4 recycled; write=0.551 s, sync=23.756 s, total=24.392 s;
sync files=12049, longest=0.029 s, average=0.002 s; distance=58228 kB,
estimate=58228 kB; lsn=6/6F6320C8, redo lsn=6/6F6320C8
2025-03-12 19:16:48.638 MSK [126137] LOG: database system is ready to accept connections
Если процесс startup работает дольше 10 секунд (значение по умолчанию параметра конфигурации log_startup_progress_interval
), то с частотой, заданной этим же параметром, процесс startup будет записывать в лог кластера информацию о том, что он делает в этот момент. В приведённом примере в такие моменты выполнялся fsync по какому-то файлу. В приведённом примере процесс startup занимался 90 секунд (c 19:15:51 до 19:16:23) синхронизацией файлов в директории PGDATA, о чем сообщал каждые 10 секунд строками в диагностическом журнале кластера:
2025-03-12 19:16:01.804 MSK [126141] LOG: syncing data directory (fsync),
elapsed time: 10.00 s, current path: ./base/5/2378
Накат WAL при этом занял всего лишь секунду: с 19:16:23 по 19:16:24.
После восстановления выполнялась контрольная точка, которая длилась 24 секунды (total=24.392 s
). На что контрольная точка тратила время? На посылку системных вызовов fsync по каждому из 12049 файлов: sync files=12049
. Почему 12049 файлов, если было создано 4000 таблиц? Потому, что на каждую таблицу было создано по 3 файла: файл основного слоя, основной слой TOAST-таблицы, файл TOAST-индекса. Таблицы пусты, вакуумирования не было, поэтому файлы fsm и vm не созданы, иначе число файлов было бы ещё больше.
Можно ли ускорить запуск экземпляра после сбоя? Да, если установить значение парамера конфигурации recovery_init_sync_method = syncfs
. Использование syncfs не ухудшает отказоустойчивость.
Почему это значение не было сделано значением по умолчанию? PostgreSQL может работать на хосте не как основной сервис, а быть вспомогательным, например, для файлового сервера, который может кэшировать огромное число файлов, а PostgreSQL использовать небольшое число файлов. При значении syncfs выполняется сброс грязных страниц из страничного кэша linux по всем смонтированным файловым системам, на которых располагаются файлы кластера PostgreSQL, что может снизить эффективность страничного кэша файлового сервера. То же самое делает команда sync -f, но только по всем смонтированным файловым системам.
Вторая причина: были опасения наличия ошибок в реализации системного вызова sync в linux, о чем написано в документации PostgreSQL к параметру конфигурации recovery_init_sync_method: "Linux версий до 5.8 может не всегда сообщать экземпляру об ошибках ввода-вывода при записи данных на диск, сообщения можно будет увидеть разве что в журнале ядра" (журнал ядра можно посмотреть командой операционной системы dmesg). Можно удивиться почему это важно, но можно поверить разработчикам PostgreSQL, что экземпляру очень важно получить сообщение об ошибке ввода-вывода, если она возникнет.
Если установить значение recovery_init_sync_method = syncfs, то синхронизация будет выполняться за доли секунды и время запуска экземпляра уменьшится. В приведёном примере на 90 секунд. Можно ли уменьшить время на контрольную точку (в примере 24 секунды)? В текущей версии PostgreSQL нельзя. Процесс checkpointer отправляет fsync по каждому файлу, блоки которых менялись за время контрольной точки любыми процессами. Процесс checkpointer не использует системный вызов sync. Возможно, в будущих версиях такую возможность добавят, так как в 17 версии поддержку метода синхронизации syncfs добавили в утилиты initdb, pg_dump, pg_rewind, pg_upgrade, pg_combinebackup, pg_basebackup.
Синхронизация файлов при резервировании утилитой pg_basebackup
У pg_basebackup в 17 версии появился параметр --sync-method=syncfs, то есть совсем недавно. Это значит, что разработчики PostgreSQL обратили внимание на то, что число файлов в кластерах растёт и fsync по каждому файлу всё менее оптимален. По умолчанию, pg_basebackup, так же как и процесс startup, использует fsync по каждому файлу, а это число файлов кластера и длительность синхронизации та же, что у startup. Можно ли было ускорить резервирование на версиях до 17? Да, можно. Для этого можно было использовать параметр --no-sync и после завершения резервирования выполнить команду sync -f. Чем отличается этот метод от --sync-method=syncfs ? На сервере может быть множество смонтированных файловых систем, параметр синхронизирует только файловые системы, в которые выполнялось резервирование. Нюанс: pg_basebackup не синхронизирует то откуда копирует (PGDATA), он синхронизирует то, куда копирует по окончании резервирования. Это нужно, чтобы после завершения бэкапа была гарантия, что при пропадании питания (или других сбоях) бэкап бы целым и утилите pg_basebackup можно было бы доверять.
В статье был рассмотрен методом синхронизации syncfs, который ускоряет синхронизацию на кластерах с большим числом файлов.