Перейти к основному содержимому

Уровень 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_age
  • vacuum_freeze_table_age
  • autovacuum_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_age
  • vacuum_multixact_freeze_table_age
  • autovacuum_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

Какие команды замораживают все версии строк, видимые во всех снимках? Выберите все верные варианты ответа