Синхронизация файлов при запуске экземпляра PostgreSQL
Если экземпляр PostgreSQL был некорректно остановлен, то перед восстановлением файлов выполняется синхронизаций всех файлов кластера. Способ синхронизации определяется параметром конфигурации recovery_init_sync_method. В статье рассматривается, как ускорить запуск экземпляра и резервирование, если в директории PGDATA имеется много файлов.
При запуске экземпляра по управляющему файлу (pg_control) проверяется последнее известное состояние экземпляра. Посмотреть состояние можно утилитой командной строки:
pg_controldata | grep state
Database cluster state: in production
В поле «Database cluster state» может быть указано одно из семи состояний:
1) «shut down» запись в логе: database system was shut down at после корректной (с контрольной точкой) остановки экземпляра
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 — состояние неизвестно.
При запуске экземпляра для всех состояний, кроме первых двух, процесс startup сначала выполняет синхронизацию файлов кластера и только затем приступает к восстановлению файлов по журналу предзаписи. Способ синхронизации определяется значением параметра конфигурации recovery_init_sync_method. По умолчанию используется метод fsync, что означает, что процесс startup будет открывать файлы и затем посылать fsync:
1) по всем файлам в директории PGDATA
2) файлам директорий, на которые указывает символическая ссылка PGDATA/pg_wal (если она есть)
3) символические ссылки в директории PGDATA/pg_tblspc, то есть по директориям табличных пространств.
По другим символическим ссылкам синхронизации не будет.
Если в кластере десятки тысяч файлов, то системных вызовов будет много и синхронизация может занять долгое время.
Зачем нужна синхронизация? Процесс 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 будет записывать в, а лог кластера информацию о том, что он делает.
В нашем случае процесс 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. Процесс startup однопоточный и восстановление не распараллеливается. Но это не является проблемой, так как процесс startup выполняет предварительное чтение блоков журнала. Статистику предварительного чтения можно посмотреть в представлении:
select * from pg_stat_recovery_prefetch\gx
-[ RECORD 1 ]--+----------------------------
stats_reset | 2025-03-12 19:15:51.5779+03
prefetch | 2061 число prefetched блоков
hit | 180186 не загружались, уже находились в кэше буферов
skip_init | 3687 не загружались, так как инициализированлись нулями
skip_new | 4698 не загружались, так как блоки не существовали
skip_fpw | 4936 не загружались, так как в WAL был полный образ блоков
skip_rep | 189965 команда на предварительное чтение уже была послана
wal_distance | 0 число блоков WAL, прочитанных заранее для prefetch
block_distance | 0 число блоков в процессе предвыборки
io_depth | 0 число необработанных вызовов prefetch
Представление заполняется процессом startup. Если восстановления при запуске экземпляра не было, значения нулевые. В представлении отражаются только блоки WAL, а не файлов данных. Но после восстановления выполняется контрольная точка, которая длилась долго: 24 секунды: total=24.392 s. На что контрольная точка тратила время? На посылку системных вызовов fsync по каждому из 12049 файлов: sync files=12049. Почему 12049 файлов, если было создано 4000 таблиц? Потому что на каждую таблицу было создано по 3 файла: файл основного слоя, основной слой TOAST-таблицы, файл TOAST-индекса. Таблицы пусты, вакуумирования не было, поэтому файлы fsm и vm не созданы, иначе число файлов было бы ещё больше.
Можно ли ускорить запуск экземпляра после сбоя? Да, если установить значение: recovery_init_sync_method = syncfs;
Использование recovery_init_sync_method = syncfs не ухудшает отказоустойчивость. По умолчанию, длительность удержания страниц в страничном кэше linux определяется параметром linux: vm.dirty_expire_centisecs — сколько буфер может быть грязным, прежде чем будет помечен для записи, по умолчанию 3000 (30 секунд). Под большой нагрузкой на диск от пометки блока до записи может пройти больше, чем 30 секунд. даже в случае если кроме экземпляра диск нагружается другими приложениями, полная синхронизация файловой системы выполнится быстрее, чем пофайлово. Значение syncfsне используется по умолчанию, вероятно, из-за опасений наличия ошибок в linux, о чем написано в документации PostgreSQL к параметру конфигурации recovery_init_sync_method: «Linux версий до 5.8 может не всегда сообщать экземпляру об ошибках ввода-вывода при записи данных на диск, сообщения можно будет увидеть разве что в журнале ядра». Журнал ядра можно посмотреть командой операционной системы dmesg. Оценить время синхронизации файловых систем можно командой операционной системы: time sync -f
Если установить значение recovery_init_sync_method = syncfs, то синхронизация будет выполняться моментально и время на запуск экземпляра уменьшится в нашем примере на 90 секунд. Можно ли уменьшить время на контрольную точку (в примере 24 секунды)? В текущей версии нельзя — процесс checkpointer отправляет fsync по каждому файлу, блоки которых менялись за контрольную точку, процесс не использует sync, хотя мог бы.
Синхронизация файлов при резервировании утилитой pg_basebackup
У утилиты резервирования pg_basebackup в 17 версии также появился параметр --sync-method=syncfs. В ядро PostgreSQL добавляются возможности ускорения синхронизации. По умолчанию, утилита резервирования, так же как и процесс startup, использует fsync по каждому файлу, а это число файлов кластера и длительность синхронизации та же, что у startup: в нашем примере 90 секунд. Можно ли ускорить резервирование на версиях до 17? Да, можно. Для этого нужно использовать параметр --no-sync и после выполнения резервирования выполнить команду sync -f, которая выполнит синхронизацию всех файловых систем. Зачем тогда добавили параметр --sync-method=syncfs? На сервере может быть множество смонтированных файловых систем, параметр синхронизирует только файловые системы, в которые выполнялось резервирование.
Было рассмотрено использование синхронизации методом syncfs, что может ускорить синхронизацию на кластерах с десятками и сотнями тысяч файлов при запуске экземпляра после сбоя, восстановления, резервирования.