Уровень 2.0
Предусловие:
- Изучен модуль «Внутристраничная очистка и оптимизация HOT» данного курса
В этом задании вы узнаете:
- Как работать со счетчиком транзакций в PostgreSQL и Pangolin
- Что делать при разрастании каталогов
pg_xactиpg_multixact - Как настраивать автоматическую заморозку
- Как работать с командами ручной заморозки
Заморозка версий строк. Счетчик транзакций в PostgreSQL
Как уже известно, механизм многоверсионности MVCC основан на хранении версий строк, время жизни которых определяется номерами транзакций xmin и xmax. Указанные номера хранятся непосредственно в заголовках версий строк табличных страниц и используются для определения видимости в снимках данных.
При этом версии строк с ненулевым значением xmax надолго не задерживаются в страницах данных, поскольку являются устаревшими и вычищаются автоочисткой, когда оказываются за горизонтом очистки.
В то же время актуальные версии строк могут неограниченно долго храниться в базе данных вместе с номерами создавших их транзакций xmin.
Также известно, что номера транзакций выдаются последовательно и определяют время начала транзакций.
Однако изначально в PostgreSQL под номер транзакции было отведено всего 32 бита.
Это составляет чуть больше 4 миллиардов возможных значений. В современных системах такое количество номеров транзакций может израсходоваться относительно быстро.
В связи с этим в PostgreSQL пространство номеров транзакций закольцовано и для определения очередности транзакций используются не их номера, а их возраст относительно текущей транзакции.

Например, на рисунке выше номер текущей транзакции — $\text{xid}_{\text{current}}$, номера против часовой стрелки от нее — это прошлое для текущей транзакции, номера по часовой стрелке — это будущее для текущей транзакции.
Возраст транзакции с номером $\text{xid}_k$ относительно текущей транзакции равен трем.
Поскольку пространство номеров транзакций закольцовано, для определения возраста транзакции используются математические операции по модулю $2^32$.
Такой подход работает, пока при выделении очередного номера транзакции не окажется, что выделяемый номер уже задействован в поле xmin какой-либо хранимой версии строки.
Такая ситуация называется зацикливанием счетчика транзакций.
В случае ее возникновения экземпляр СУБД будет вынужден аварийно остановиться для предотвращения нарушения работы механизма многоверсионности, так как последствия могут быть очень серьезными — потеря данных из-за нарушения правил видимости.
В PostgreSQL для предотвращения зацикливания счетчика транзакций применяется процедура заморозки версий строк.
Под заморозкой понимается пометка старых версий строк, находящихся за горизонтом очистки и видимых во всех снимках.
При наличии такой отметки необходимость в номере xmin отпадает. Номер транзакции, записанный в поле xmin, может быть безопасно переиспользован.
В качестве отметки заморозки используется одновременно два информационных бита: xmin committed и xmin aborted.
Заморозкой занимается процесс автоочистки. Однако она также может быть выполнена вручную командами очистки.
Для предотвращения зацикливания счетчика транзакций заморозка в PostgreSQL требует аккуратной настройки и мониторинга с целью принятия мер в случае, если заморозка не успевает освобождать номера транзакций.
Счетчик транзакций в Pangolin
Как было сказано выше, 32-битные номера транзакций требуют особого внимания администратора для предотвращения зацикливания счетчика транзакций.
В связи с этим в Pangolin решено использовать 64-битный счетчик транзакций.
Использование 64-битных номеров позволяет отказаться от их повторного использования и постоянного мониторинга с целью предотвращения зацикливания счетчика транзакций.
Однако увеличение размерности полей xmin и xmax в заголовке версии строки привело бы к значительному увеличению объема хранимых данных и соответствующих накладных расходов по их обработке.
По этой причине в Pangolin разрядность полей xmin и xmax в заголовке версии строки сохранена и составляет 32 бита.
А увеличение разрядности номера транзакции достигается за счет хранения 64-битного базового значения xid (xid base) в специальном пространстве табличной страницы.
В результате номер транзакции, создавшей или удалившей версию строки, получается путем сложения 64-битного значения xid base и 32-битного значения xmin или xmax соответственно.
Такие 64-битные номера xmin и xmax будут получены, например, при выполнении запроса:
SELECT xmin, xmax, * FROM some_table;
Более того, даже функция heap_page_items расширения pageinspect, предназначенного для низкоуровневого анализа содержимого страниц, выдает уже вычисленные 64-битные значения xmin и xmax.
Для получения физически хранимых 32-битных значений xmin и xmax придется заглянуть непосредственно в страницу данных.
Как, наверное, уже стало понятно, такая реализация не освобождает от необходимости решения проблемы зацикливания на уровне страницы. Действительно, в поле xmin по-прежнему может храниться не более $2^32$ значений, а наличие поля xid base позволяет лишь перенести ограничение количества номеров транзакций на уровень табличной страницы.
В одной странице данных можно использовать номера транзакций от xid base до xid base. В связи с этим в случае необходимости записи в поле xmin номера вне указанного диапазона выполняется процедура смещения (увеличения) значения xid base на странице.
При этом если на странице имеются версии строк с малыми значениями xmin, не позволяющими произвести смещение xid base, то одновременно с процедурой смещения происходит заморозка таких версий строк — выставляются информационные биты xmin committed и xmin aborted.
Помимо этого, в само поле xmin записывается специальный зарезервированный номер транзакции FrozenTransactionId = 2, который всегда считается более старым, чем любой xid.
Вообще говоря, запись в поле xmin номера FrozenTransactionId не является обязательной, так как замороженные версии строк можно определить по соответствующим информационным битам.
Заморозка с использованием номера FrozenTransactionId выполнялась в PostgreSQL до версии 9.4.
В современных версиях PostgreSQL, в отличие от Pangolin, при заморозке поле xmin не перезаписывается.
При этом базовый механизм заморозки, заложенный в PostgreSQL, также продолжает применяться в Pangolin, хотя уже не является критически важным для работы механизма многоверсионности.
Дело в том, что заморозка необходима не только для предотвращения зацикливания счетчика транзакций.
Она также позволяет не разрастаться каталогам pg_xact и pg_multixact каталога данных, речь о которых пойдет ниже.
Разрастание каталогов pg_xact и pg_multixact
Каталог pg_xact
Номера транзакций подвержены длительному хранению не только в заголовке версий строк.
Также они участвуют в журнале статусов транзакций CLOG, используемом при построении снимков данных и хранимом в каталоге pg_xact каталога данных.
Транзакции из журнала CLOG могут быть очищены только в случае гарантии того, что во всех версиях строк, в которых они участвовали, есть информация об их статусах.
Вспомним, что информационные биты xmin committed, xmin aborted, xmax committed и xmax aborted не сразу проставляются при изменении статуса транзакций.
В результате выполнения заморозки в каждой базе данных определяется такой номер транзакции, что все версии строк, созданные более старыми транзакциями, гарантированно заморожены.
Такой номер хранится в поле datfrozenxid системного каталога pg_database.
Транзакции, более старые, чем старейшая из datfrozenxid по всем базам данных, могут быть безопасно вычищены из журнала CLOG.
Таким образом, процедура заморозки также предотвращает разрастание каталога pg_xact.
Каталог pg_mulitxact
Помимо счетчика транзакций в Pangolin также используется счетчик мультитранзакций, имеющий ту же разрядность.
Мультитранзакции используются для удержания разделяемых блокировок одной версии строки.
Более подробно разделяемые блокировки и мультитранзакции рассматриваются в лекции «Блокировки».
Номер мультитранзакции хранится в поле xmax заголовка версии строки вместо номера обычной транзакции.
При этом информация о транзакциях, включенных в состав мультитранзакций, хранится в файлах в каталоге pg_multixact каталога данных.
Для определения того, что в поле xmax хранится номер именно мультитранзакции, используется информационный бит xmax is multi.
При этом в Pangolin для поддержки 64-битных мультитранзакций в специальном пространстве табличной страницы также хранится 64-битное базовое значение multixact base.
Вычисление номера мультитранзакции выполняется по аналогии с обычными 64-битными транзакциями, а именно путем сложения 64-битного значения multixact base и 32-битного значения, записанного в поле xmax.
Счетчик мультитранзакций ведется независимо от счетчика обычных транзакций.
Для номеров мультитранзакций также выполняется своеобразная заморозка.
Старые номера мультитранзакций в поле xmax заменяются:
- На нулевое значение, если блокировка больше не удерживается ни одной из транзакций
- На обычный номер транзакции, если блокировка стала удерживаться только одной транзакцией
- На новый более молодой номер мультитранзакции
Заметим, что наличие ненулевого номера мультитранзакции в поле xmax не говорит о том, что строка является устаревшей и должна быть вычищена. Поэтому заморозка номеров мультитранзакций также нужна.
По аналогии со счетчиком транзакции в системном каталоге pg_database в поле datminmxid хранится такой номер мультитранзакции, что более старых номеров мультитранзакций, задействованных в версиях строк, гарантированно нет.
Соответственно более старые мультитранзакции, чем старейшая из datminmxid по всем базам данных, могут быть безопасно вычищены из каталога pg_multixact.
Таким образом, процедура заморозки также предотвращает разрастание каталога pg_multixact.
Настройка автоматической заморозки
Заморозка выполняется совместно с автоочисткой, создавая дополнительную нагрузку на СУБД.
Для регулировки нагрузки, создаваемой заморозкой номеров транзакций, используются следующие конфигурационные параметры:
vacuum_freeze_min_agevacuum_freeze_table_ageautovacuum_freeze_max_age
Параметр vacuum_freeze_min_age
Параметр vacuum_freeze_min_age определяет минимальный возраст транзакции, с которого может выполняться заморозка.
Чем меньше значение vacuum_freeze_min_age, тем чаще будет выполняться заморозка версий строк.
Для строк, подверженных частым изменениям, при низком значении vacuum_freeze_min_age заморозка будет выполняться впустую, создавая избыточные накладные расходы.
Важно, что автоочистка просматривает только страницы, не отмеченные в карте видимости.
Соответственно для страниц, в которых находятся только актуальные версии строк, пусть даже очень старые, заморозка не будет выполнена, несмотря на превышение возраста соответствующих номеров xmin по отношению к параметру vacuum_freeze_min_age.
Таким образом, заморозка выполняется попутно с автоочисткой, минимизируя создаваемые накладные расходы.
Параметр vacuum_freeze_table_age
Если возраст самой старой незамороженной транзакции в таблице превышает значение vacuum_freeze_table_age, то в ходе автоочистки заморозка выполняется для всех страниц, а не только для тех, которых нет в карте видимости.
Возраст самой старой незамороженной транзакции таблицы хранится в поле relfrozenxid системного каталога pg_class и гарантирует, что в таблице нет незамороженных версий строк старше relfrozenxid.
При этом для оптимизации процесса заморозки в этом случае используется карта заморозки, где отмечены страницы, в которых заморожены все версии строк. Страницы, отмеченные в карте заморозки, пропускаются.
На самом деле карта заморозки хранится вместе с картой видимости, в которой для каждой страницы хранится по два бита: первый — для отметки видимости всех версий строк, второй — для отметки того, что все версии строк заморожены.
Такая заморозка называется агрессивной, поскольку в ходе автоочистки требует обращения ко всем страницам данных (за исключением отмеченных в карте заморозки), а не только не отмеченным в карте видимости.
Необходимо отметить, что даже при агрессивной заморозке версии строк, созданные транзакциями с возрастом менее чем vacuum_freeze_min_age, не замораживаются.
Таким образом, агрессивная заморозка в таблице выполняется не чаще чем раз в vacuum_freeze_table_age - vacuum_freeze_min_age.
Увеличивать значение vacuum_freeze_min_age необходимо с оглядкой на значение vacuum_freeze_table_age, поскольку это может привести к увеличению частоты срабатываний агрессивной заморозки.
Параметр autovacuum_freeze_max_age
Если автоочистка отключена и не выполняется ручная очистка, значения рассмотренных параметров vacuum_freeze_min_age и vacuum_freeze_table_age никак не будут влиять на частоту вызова заморозки.
Для таких случаев предназначен конфигурационный параметр autovacuum_freeze_max_age, определяющий возраст транзакции, при достижении которого старейшей транзакцией в таблице (relfrozenxid) будет принудительно запущена операция очистки с заморозкой версий строк, даже если автоочистка отключена.
Изначально в PostgreSQL параметр autovacuum_freeze_max_age имел большое значение, поскольку являлся определяющим для предотвращения зацикливания счетчика транзакций.
В Pangolin же зацикливание счетчика транзакций невозможно, поэтому параметр autovacuum_freeze_max_age настраивается исходя из допустимого объема журнала CLOG.
Действительно, autovacuum_freeze_max_age определяет предельную границу для вызова заморозки, и чем больше его значение, тем больше номеров транзакций потенциально могут оказаться незамороженными, и для них потребуется хранение статусов в структуре CLOG.
Параметры заморозки мультитранзакций
По аналогии с конфигурационными параметрами, определяющими настройки заморозки транзакций, также имеются соответствующие параметры для мультитранзакций:
vacuum_multixact_freeze_min_agevacuum_multixact_freeze_table_ageautovacuum_multixact_freeze_max_age
В этом случае значение параметра autovacuum_multixact_freeze_max_age определяет размер каталога со статусами мультитранзакций pg_multixact.
Значения по умолчанию
В Pangolin для рассмотренных параметров используются следующие значения по умолчанию:
vacuum_freeze_min_age= 50 млнvacuum_freeze_table_age= 150 млнautovacuum_freeze_max_age= 10 млрдvacuum_multixact_freeze_min_age= 5 млнvacuum_multixact_freeze_table_age= 150 млнautovacuum_multixact_freeze_max_age= 20 млрд
По сравнению с PostgreSQL значения параметров autovacuum_freeze_max_age и autovacuum_multixact_freeze_max_age сильно увеличены в силу отсутствия проблемы с зацикливанием счетчика транзакций.
Для снижения накладных расходов может оказаться целесообразным увеличить значения параметров vacuum_freeze_table_age и vacuum_multixact_freeze_table_age
Команды ручной заморозки
Также заморозка может выполняться в ручном режиме. Для этого используются команды ручной очистки.
- При выполнении команды
VACUUMзамораживаются версии строк в соответствии с установленными настройками заморозки для автоочистки - При выполнении команды
VACUUM FREEZEзамораживаются все версии строк, видимые во всех снимках - Также замораживаются все версии строк, видимые во всех снимках, при полном перестроении таблиц командами
VACUUM FULLиCLUSTER - Замораживать версии строк можно при загрузке данных командой
COPYс параметромFREEZE. Однако заморозка возможна в этом случае, только если таблица для загрузки была создана или очищена командойTRUNCATEв той же транзакции, что и командаCOPY FREEZE. Это необходимо для блокировки таблицы с целью исключения доступа параллельных транзакций к загружаемым данным в процессе загрузки. Дело в том, что загружаемые данные становятся сразу видны в соответствии с правилами видимости замороженных версий строк.
Итоги
Пора подвести итоги задания:
- В Pangolin используются 64-битные номера транзакций
- В Pangolin отсутствует проблема зацикливания счетчика транзакций на уровне кластера
- Заморозка версий строк в Pangolin предотвращает зацикливание счетчика транзакций на уровне табличной страницы
- Также заморозка предотвращает разрастание каталогов
pg_xactиpg_multixact - Заморозка выполняется совместно с автоочисткой, но может выполняться и вручную
Самопроверка
Вопрос 1
Выберите все верные утверждения относительно номеров транзакций, используемых в табличных страницах в Pangolin:
Вопрос 2
Какие информационные биты должны быть одновременно проставлены, чтобы версия строки считалась замороженной?
Вопрос 3
Значение какого конфигурационного параметра ограничивает размер каталога pg_xact?
Вопрос 4
Какие команды замораживают все версии строк, видимые во всех снимках? Выберите все верные варианты ответа