Многоверсионность
Заморозка
13
Авторские права
© Postgres Professional, 2016–2022.
Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов
Использование материалов курса
Некоммерческое использование материалов курса (презентации,
демонстрации) разрешается без ограничений. Коммерческое
использование возможно только с письменного разрешения компании
Postgres Professional. Запрещается внесение изменений в материалы
курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу:
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за
любые повреждения и убытки, включая потерю дохода, нанесенные
прямым или непрямым, специальным или случайным использованием
материалов курса. Компания Postgres Professional не предоставляет
каких-либо гарантий на материалы курса. Материалы курса
предоставляются на основе принципа «как есть» и компания Postgres
Professional не обязана предоставлять сопровождение, поддержку,
обновления, расширения и изменения.
2
Темы
Проблема переполнения счетчика транзакций
Заморозка версий строк и правила видимости
Настройка автоочистки для выполнения заморозки
Заморозка вручную
3
Переполнение счетчика
меньшие номера — прошлое, бóльшие — будущее
разрядность счетчика — 32 бита, что делать при переполнении?
1 2
транзакция 2
началась позже
транзакции 1
прошлое будущее
Кроме освобождения места в страницах, очистка выполняет также
задачу по предотвращению проблем, связанных с переполнением
счетчика транзакций.
Под номер транзакции в PostgreSQL выделено 32 бита. Это довольно
большое число (около 4 млрд), но при активной работе сервера оно
вполне может быть исчерпано. Например при нагрузке 1000 транзакций
в секунду это произойдет всего через полтора месяца непрерывной
работы.
Но мы говорили о том, что механизм многоверсионности полагается на
последовательную нумерацию транзакций — из двух транзакций
транзакция с меньшим номером считается начавшейся раньше.
Понятно, что нельзя просто обнулить счетчик и продолжить нумерацию
заново.
Почему под номер транзакции не выделено 64 бита — ведь это
полностью исключило бы проблему? Дело в том, что (как
рассматривалось в теме «Страницы и версии строк») в заголовке
каждой версии строки хранятся два номера транзакций — xmin и xmax.
Заголовок и так достаточно большой, а увеличение разрядности
привело бы к его увеличению еще на 8 байт.
4
пространство номеров транзакций закольцовано
половина номеров — прошлое, половина — будущее
Нумерация по кругу
1 1
2
возраст транзакции
Поэтому вместо линейной схемы все номера транзакций закольцованы.
Для любой транзакции половина номеров «против часовой стрелки»
считается принадлежащей прошлому, а половина «по часовой
стрелке» — будущему.
Возрастом транзакции называется число транзакций, прошедших
с момента ее появления в системе (независимо от того, переходил ли
счетчик через ноль или нет).
5
Проблема видимости
1 1
2
2
3
3
4
транзакция 1
находится далеко
в прошлом
транзакция 1
внезапно оказалась
в будущем?
В такой закольцованной схеме возникает неприятная ситуация.
Транзакция, находившаяся в далеком прошлом (транзакция 1
на слайде), через некоторое время окажется в той половине круга,
которая относится к будущему. Это, конечно, нарушит правила
видимости и приведет к проблемам.
6
замороженные версии строк считаются «бесконечно старыми»
номер транзакции xmin может быть использован заново
Заморозка версий строк
2
3
3
4
Чтобы не допустить путешествий из прошлого в будущее, процесс
очистки выполняет еще одну задачу. Он находит достаточно старые и
«холодные» версии строк (которые видны во всех снимках и изменение
которых уже маловероятно) и специальным образом помечает —
«замораживает» — их. Замороженная версия строки считается старше
любых обычных данных и всегда видна во всех снимках данных. При
этом уже не требуется смотреть на номер транзакции xmin, и этот
номер может быть безопасно использован заново. Таким образом,
замороженные версии строк всегда остаются в прошлом.
7
Заморозка версий строк
xmin committed
xmin aborted
xmax committed
xmax aborted
данные
100 42,FOO(0,1)
статус
normal 0 t t
признак
заморозки
на номер
больше не
смотрим
xmaxxmin
Еще одна задача процесса очистки
если вовремя не заморозить версии строк, они окажутся в будущем
и сервер остановится для предотвращения ошибки
Для того чтобы пометить версию строки как замороженную, для
транзакции xmin выставляются одновременно оба бита-подсказки
бит фиксации и бит отмены.
Заметим, что транзакцию xmax замораживать не нужно. Ее наличие
означает, что данная версия строки больше не актуальна. После того,
как она перестанет быть видимой в снимках данных, такая версия
строки будет очищена.
Многие источники (включая документацию) упоминают специальный
номер FrozenTransactionId = 2, который записывается на место xmin
в замороженных версиях. Такая система действовала до версии 9.4,
но сейчас заменена на биты-подсказки — это позволяет сохранить
в версии строки исходный номер транзакции, что удобно для целей
поддержки и отладки. Однако транзакции с номером 2 еще могут
встретиться в старых системах, даже обновленных до последних
версий.
Важно, чтобы версии строк замораживались вовремя. Если возникнет
ситуация, при которой еще не замороженный номер транзакции рискует
попасть в будущее, PostgreSQL аварийно остановится. Это возможно
в двух случаях: либо транзакция не завершена и, следовательно, не
может быть заморожена, либо не сработала очистка.
При запуске сервера транзакция будет автоматически отменена;
дальше администратор должен вручную выполнить очистку, и после
этого система сможет продолжить работу.
8
vacuum_freeze_min_age
минимальный возраст,
с которого начинается заморозка
Настройка
vacuum_freeze_min_age
Заморозкой управляют три основных параметра.
Параметр vacuum_freeze_min_age определяет минимальный возраст
транзакции xmin, с которого начинается заморозка.
Чем меньше это значение, тем больше может быть накладных
расходов. Если строка «горячая» и активно меняется, заморозка ее
версий будет пропадать без пользы: уже замороженные версии будут
вычищаться, а новые версии придется снова замораживать.
Поэтому более молодые версии строк замораживаются только в тех
случаях, когда это точно не добавляет работы, например, при полной
очистке таблицы.
Заметим, что очистка просматривает только страницы, не отмеченные
в карте видимости. Если на странице остались только актуальные
версии, то очистка не придет в такую страницу и не заморозит их.
9
vacuum_freeze_table_age
при достижении такого возраста
замораживаются версии строк на всех страницах
(«агрессивная» заморозка)
для ускорения используется карта заморозки
Настройка
vacuum_freeze_min_age
vacuum_freeze_table_age
Параметр vacuum_freeze_table_age определяет возраст транзакции,
при котором пора выполнять заморозку версий строк на всех страницах
таблицы. Такая заморозка называется «агрессивной».
Каждая таблица хранит номер транзакции (pg_class.relfrozenxid), для
которого известно, что в версиях строк не осталось более старых
незамороженных номеров транзакций. Возраст этой транзакции и
сравнивается со значением параметра.
Чтобы не просматривать всю таблицу целиком, вместе с картой
видимости ведется карта заморозки. В ней отмечены страницы,
в которых заморожены все версии строк. Такие страницы можно
пропускать при заморозке.
Даже в агрессивном режиме версии строк с транзакциями младше
vacuum_freeze_min_age не замораживаются, поэтому после заморозки
новый возраст транзакции relfrozenxid будет равен не нулю,
а vacuum_freeze_min_age. Таким образом, заморозка всех страниц
выполняется раз в (vacuum_freeze_table_age vacuum_freeze_min_age)
транзакций.
Мы уже говорили, что слишком маленькое значение параметра
vacuum_freeze_min_age увеличивает накладные расходы на очистку.
Но при больших значениях агрессивная заморозка будет выполняться
слишком часто, что тоже плохо. Установка этого параметра требует
компромисса.
10
autovacuum_freeze_max_age
при достижении такого возраста
заморозка запускается принудительно
определяет размер CLOG
VACUUM (index_cleanup off)
Настройка
vacuum_freeze_min_age
vacuum_freeze_table_age
autovacuum_freeze_max_age
Параметр autovacuum_freeze_max_age определяет возраст
транзакции, при котором заморозка будет выполняться принудительно.
Автоочистка для предотвращения последствий переполнения счетчика
транзакций запустится, даже если она отключена параметрами.
Этот параметр также определяет размер структуры CLOG: данные
о статусе более старых транзакций точно никогда не понадобятся,
поэтому часть файлов из PGDATA/pg_xact может быть удалена.
Если администратор понимает, что автоочистка не успеет заморозить
версии строк до переполнения счетчика транзакций, можно
воспользоваться ручной очисткой с параметром index_cleanup off.
В этом случае индексы не будут очищаться, но за счет этого версии
строк в таблицах будут заморожены быстрее.