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

64-битные идентификаторы транзакций

Описание механизма

В ядре оригинального PostgreSQL для невиртуальных идентификаторов транзакций (TransactionId, или XID) выделено 32 бита, которые покрывают всего 4 млрд транзакций. Во избежание исчерпания номеров транзакций применяется закольцованная схема счетчика транзакций.

Закольцованная схема счетчика транзакций

Чтобы понять, какая транзакция произошла раньше, а какая позже, используются определения «старше» и «младше» вместо «меньше» и «больше». Все пространство номеров транзакций (XID) в количестве, равном 2322^{32}, поделено на 2 половины:

  • номера транзакций, отстоящие на -2312^{31} от текущей, находятся в прошлом;
  • номера транзакций, отстоящие на +2312^{31} от текущей, считаются в будущем.

Логическое сравнение двух номеров транзакций выполняется путем приведения их значений к началу отсчета (ноль) и сравнения разницы получившихся значений с половиной всего диапазона номеров 2312^{31}. Если результат меньше 2312^{31}, то первый номер транзакции предшествует второму номеру, то есть старше, иначе – следует после второго номера, а значит, младше. Ниже приведена формула для определения, предшествует ли транзакция xid1 транзакции xid2:

(232x_id1+x_id2)mod232231(2^{32} - x\_{id1} + x\_{id2}) \bmod 2^{32} \le 2^{31}

Например, определим, старше ли транзакция с номером 3 000 000 000 (xid1) транзакции с номером 1 000 (xid2):

(2323000000000+1000)mod232231    (1294967296+1000)mod232231    12949682962147483648(2^{32} - 3\,000\,000\,000 + 1\,000) \bmod 2^{32} \le 2^{31} \;\rightarrow\; (1\,294\,967\,296 + 1\,000) \bmod 2^{32} \le 2^{31} \;\rightarrow\; 1\,294\,968\,296 \le 2\,147\,483\,648

В закольцованной схеме транзакция 3 000 000 000 оказывается старше транзакции 1 000, а в прямой последовательной схеме транзакция была бы младше.

На рисунке «Закольцованная схема счетчика транзакций» текущая транзакция 100 будет старше транзакции 2312^{31}+100 (транзакция 2312^{31}+100 в будущем):

(232100+231+100)mod232231    (6442450944)mod232231    21474836482147483648(2^{32} - 100 + 2^{31} + 100) \bmod 2^{32} \le 2^{31} \;\rightarrow\; (6\,442\,450\,944) \bmod 2^{32} \le 2^{31} \;\rightarrow\; 2\,147\,483\,648 \le 2\,147\,483\,648

Но младше транзакции 2312^{31}+100 (транзакция 2312^{31}+100 в прошлом):

(232100+231+101)mod232231    (6442450945)mod232231    21474836492147483648(2^{32} - 100 + 2^{31} + 101) \bmod 2^{32} \le 2^{31} \;\rightarrow\; (6\,442\,450\,945) \bmod 2^{32} \le 2^{31} \;\rightarrow\; 2\,147\,483\,649 \le 2\,147\,483\,648

Использование закольцованного пространства номеров счетчика транзакций может привести к проблеме видимости версий строк (кортежей) в механизме многоверсионности (MVCC) СУБД Pangolin. В заголовке каждой версии строки таблицы хранятся номера транзакций в полях:

  • t_xmin – 32-битное значение номера транзакции, создавшей строку;
  • t_xmax – 32-битное значение номера транзакции, удалившей строку.

Организация хранения данных в СУБД Pangolin:

В каждой транзакции видимость версий строк таблиц определяется согласно установленным правилам в соответствии с созданным снимком данных. Версия строки видна, если в снимке видны изменения, сделанные транзакцией, чей номер указан в поле t_xmin заголовка версии строки, то есть проверяется, что t_xmin старше xmin в снимке данных, где xmin – номер самой ранней активной транзакции в системе (транзакции с номерами старше номера xmin точно зафиксированы). Проблема видимости строк возникает при увеличении счетчика, когда номер транзакции t_xmin в заголовке версии строки становится младше xmin из снимка данных, то есть версия строки оказывается в будущем для текущей транзакции, что трактуется как повреждение данных.

На рисунке «Проблема видимости строк при использовании закольцованной схемы счетчика транзакций» версия строки, созданная транзакцией 2312^{31}+101 пропадает для текущей активной транзакции 101 (он же xmin в снимке данных), поскольку 101 теперь предшествует 2312^{31}+101:

(232101+231+101)mod232231    (6442450944)mod232231    21474836482147483648(2^{32} - 101 + 2^{31} + 101) \bmod 2^{32} \le 2^{31} \;\rightarrow\; (6\,442\,450\,944) \bmod 2^{32} \le 2^{31} \;\rightarrow\; 2\,147\,483\,648 \le 2\,147\,483\,648

Проблема видимости строк при использовании закольцованной схемы счетчика транзакций

Важным условием использования закольцованной схемы номеров транзакций является отсутствие в какой-либо из таблиц БД версий строк, возраст которых превышает 2 млрд транзакций и в которых не проставлены флаги (hint bits), указывающие на то, что строки уже достаточно старые, чтобы быть видимыми всем текущим транзакциям (во всех снимках). Процесс установки данных флагов называется заморозкой, а версии строк – замороженными. Замороженные версии строк всегда остаются в прошлом. Идентификаторы транзакций в полях t_xmin этих версий строк могут быть безопасно переиспользованы.

Заморозка старых версий строк для решения проблемы видимости:

Заморозка может выполняться процессом автоочистки (autovacuum), либо вручную командой vacuum freeze. Настройка выполняется тремя параметрами:

  • vacuum_freeze_min_age (50 000 000) - возраст транзакции, начиная с которой заодно с очисткой страниц будет происходить заморозка;
  • vacuum_freeze_table_age (150 000 000) - при достижении такого возраста замораживаются версии строк на всех страницах таблицы (агрессивная заморозка);
  • autovacuum_freeze_max_age (200 000 000) - начиная с этого возраста, запускается принудительная заморозка, даже если она была отключена.

Настройка процедуры заморозки

При частых переполнениях счетчика транзакций затраты на эту процедуру оказываются существенными: осуществляется лишний доступ к диску и увеличивается размера журнала предзаписи. На рисунке далее представлен возможный жизненный цикл одной из страниц таблицы:

  1. Таблица модифицируется. В страницу буферного кеша добавляются версии строк, эти же строки пишутся в WAL.
  2. Наступает момент записи контрольной точки (CHECKPOINT). «Грязная» страница записывается в файл таблицы. Поскольку autovacuum по ней не проходил, версии строк страницы остались незамороженными.
  3. Автоочистка (autovacuum) принимает решение заморозить версии строк, расположенные на этой странице. В худшем случае страница могла быть вытеснена на диск, тогда автовакууму придется прочитать ее с диска. После выполнения контрольной точки, при первом изменении страницы, ее содержимое целиком записывается в WAL (Full Page Write), даже если изменилось всего несколько бит во флагах заголовка версии строки.
  4. Процесс контрольной точки (CHECKPOINT) снова записывает страницу на диск.

При следующем изменении страницы ее жизненный цикл может повториться.

Затраты на процедуру заморозки

Внимание!

Если заморозка не будет успевать обрабатывать версии строк, то возраст самой старой незамороженной транзакции может приблизиться к 2 млрд, вследствие чего Pangolin не сможет больше выдавать номера транзакций для запросов на запись, требуя ручного вмешательства и проведения очистки/заморозки в однопользовательском режиме:

ERROR: database is not accepting commands to avoid wraparound data loss in database "postgres"
HINT: Stop the postmaster and vacuum that database in single-user mode.
You might also need to commit or roll back old prepared transactions, or drop stale replication slots.

В СУБД Pangolin используются и 64-битные идентификаторы транзакций (тип xid8), добавляющие к короткому идентификатору общую для всего кластера 32-битную эпоху (поле Latest checkpoint's NextXID в контрольном файле), но только для служебных целей, и это не решает проблему переполнения счетчика. 64-битные идентификаторы транзакций используются, например, в выдаче результата выполнения SQL-функции pg_current_xact_id(). Они существуют только в памяти и не попадают в страницы данных. Эпоха обновляется в контрольном файле при выполнении CHECKPOINT в контрольном файле (указывается перед двоеточием):

pg_controldata -D /opt/pgdata-5xx/ | grep NextXID
Latest checkpoint's NextXID: 0:724 # 0 - эпоха

Помимо механизма обычных транзакций (включая вложенные) в СУБД Pangolin реализован механизм мультитранзакций, применяемый для разделяемых блокировок. Для выделения номеров мультитранзакций используется свой независимый счетчик, то есть номера транзакций и мультитранзакций могут пересекаться. Счетчик мультитранзакций закольцован так же, как и счетчик обычных транзакций. При активном использовании разделяемых блокировок счетчик мультитранзакций может переполниться, что приведет к тем же проблемам, что и при переполнении счетчика обычных транзакций: отказ в обслуживании при долгоживущей мультритранзакции и частая заморозка мультитранзакций.

Описание решения

В СУБД Pangolin реализовано увеличение разрядности счетчиков обычных транзакций и мультитранзакций до 64-бит.

Назначение

Переход на 64-битный счетчик транзакций решает проблему их переполнения, делая этот момент практически недостижимым. Даже при нагрузке 2322^{32} транзакций в день (~50 000 TPS) граница раздела между прошлым и будущим будет достигнута через 2312^{31} дней (5,9 млн лет). В результате появляется возможность:

  • Не отказывать в выполнении обновляющих запросов при высокой транзакционной нагрузке, если autovacuum не успевает выполнить заморозку во всех БД или имеется долгоживущая транзакция.

    В случае 32-битного счетчика 13 часовой OLAP-запрос стал бы причиной отказа в обслуживании OLTP-запросов со скоростью 50 000 TPS спустя 12 часов. К этому моменту возраст самой старой транзакции достиг бы 2312^{31}. Горизонт транзакций (xmin), удерживаемый OLAP-запросом, не дает autovacuum заморозить версии строк младше горизонта транзакций для продвижения счетчика. В результате новый номер для пишущей транзакции не выделяется и Pangolin завершает OLTP-запросы с ошибкой, чтобы не допустить потерю данных.

    64-битный счетчик снимает ограничение на предельный возраст самой старой транзакции 2312^{31}, поэтому рассмотренная выше проблема отсутствует.

  • Снизить риск деградации производительности при интенсивной транзакционной нагрузке за счет настройки более редкого запуска процедуры заморозки и ее планирования на период с меньшей нагрузкой. В случае использования 32-битного счетчика частота выполнения заморозки определяется ограничением по возрасту самой старой версии строки, который не должен превышать 2 млрд транзакций.

Настройка

Конфигурационные параметры

Добавлены новые GUC-параметры для настройки функциональности:

Параметр

Описание

Значение по умолчанию

Применение параметра

xid64_full_page_write_sampling

Параметр определяет, как часто в WAL будут записываться полные образы страниц, преобразованных в формат XID64. По умолчанию в WAL записывается полный образ для каждой 128-й преобразованной страницы при ее чтении с диска. Для оставшихся преобразованных страниц в WAL будут записаны их полные образы, в случае изменения страниц в обновляющем запросе

Значением по умолчанию 128.

Диапазон значений: [1, 2**31-1]

Изменение параметра доступно без перезапуска сервера СУБД

max_notify_queue_pages

Параметр ограничивает число SLRU-страниц, зарезервированных для уведомлений в механизме NOTIFY/LISTEN. В стандартной конфигурации размер очереди 8 ГБ (в случае 8 КБ страницы)

Значение по умолчанию 1048576.

Диапазон значений [64, 2**31-1]

Для изменения параметра требуется перезапуск сервера СУБД

Также изменены максимальные значения и значения по умолчанию следующих параметров:

ПараметрЗначение по умолчанию (было → стало)Максимальное значение (было → стало)
vacuum_freeze_min_ageбез изменений1000000000 → 0x7FFFFFFFFFFFFFFF
vacuum_freeze_table_ageбез изменений2000000000 → 0x7FFFFFFFFFFFFFFF
vacuum_multixact_freeze_min_ageбез изменений1000000000 → 0x7FFFFFFFFFFFFFFF
vacuum_multixact_freeze_table_ageбез изменений2000000000 → 0x7FFFFFFFFFFFFFFF
autovacuum_freeze_max_age200000000 → 100000000002000000000 → 0x7FFFFFFFFFFFFFFF
autovacuum_multixact_freeze_max_age400000000 → 200000000002000000000 → 0x7FFFFFFFFFFFFFFF

Следующие параметры конфигурации не используются: vacuum_defer_cleanup_age, vacuum_failsafe_age, vacuum_multixact_failsafe_age. В следующих версиях планируется их исключение из списка возможных параметров.

Управление

Новый формат страницы

64-битные номера транзакций требуется сохранять на диске: в полях t_xmin и t_xmax в заголовках версий строк и в pd_prune_xid в заголовке страницы. Прямой метод увеличения разрядности этих полей до 8 байт приведет к существенному расходу дискового пространства и потребует переупаковки каждой версии строки при обновлении через pg_upgrade, поэтому вводится новый формат страницы при сохранении формата версии строки, то есть разрядность t_xmin и t_xmax остается 32-битной. В конце каждой страницы резервируется место под специальную область для трех полей: pd_xid_base, pd_multi_base и pd_magic.

Новый формат страниц

В полях pd_xid_base и pd_multi_base хранятся базовые значения, относительно которых вычисляются 64-битные номера транзакций. Поле pd_magic используется как маркер для обозначения типа страниц, чтобы отличать их от индексных страниц, у которых тоже есть специальная область.

примечание

В TOAST-таблицах не применяется механизм мультитранзакций, поэтому в специальной области страниц этих объектов хранится одна база pd_xid_base и «магическое» число pd_magic.

Данные в специальное области страницы:

ПолеРазмерОписание
pd_xid_base8 байтБаза для коротких 4-байтных идентификаторов транзакций на этой странице (поля заголовков версий строк t_xmin, t_xmax и поле заголовка страницы pd_prune_xid)
pd_multi_base8 байтБаза для коротких 4-байтных идентификаторов мультитранзакций на этой странице (поле заголовков версий строк t_xmax). Отсутствует в страницах TOAST-таблиц
pd_magic4 байтаЗарезервированное число, обозначающее тип страницы:

0x1010 – для обычной, партиционированной или TOAST-таблицы;
0x1717 – для последовательности.

64-битные номера транзакций t_xmin и t_xmax в заголовке версии строки вычисляются при каждом доступе к ней следующим образом:

XMIN=t_xmin+pd_xid_baseXMIN = t\_{xmin} + pd\_{xid\_base}

XMAX=t_xmax+pd_xid_base (для обычных транзакций)XMAX = t\_{xmax} + pd\_{xid\_base} \text{ (для обычных транзакций)}

XMAX=t_xmax+pd_multi_base (в случае мультитранзакций)XMAX = t\_{xmax} + pd\_{multi\_base} \text{ (в случае мультитранзакций)}

PRUNE_XID=pd_prune_xid+pd_xid_basePRUNE\_XID = pd\_{prune\_xid} + pd\_{xid\_base}

Вычисление 64-битных номеров транзакций относительно базовых значений

Поскольку базовое значение общее для всей страницы, то на одной странице не могут быть размещены строки, созданные транзакциями, разница в возрасте которых превышает 4 млрд транзакций (2322^{32}).

примечание

Сократить разрядность базы до 4-х байт и использовать формулы, представленные ниже, не представляется возможным.

XMIN=(pd_xid_base32)    t_xminXMIN = (pd\_{xid\_base} \ll 32)\;|\; t\_{xmin}

XMAX=(pd_xid_base32)    t_xmax (для обычных транзакций)XMAX = (pd\_{xid\_base} \ll 32)\;|\; t\_{xmax} \text{ (для обычных транзакций)}

XMAX=(pd_multi_base32)    t_xmax (в случае мультитранзакций)XMAX = (pd\_{multi\_base} \ll 32)\;|\; t\_{xmax} \text{ (в случае мультитранзакций)}

Если значение базы будет кратным 2322^{32}, то при вставке новой версии строки в страницу и пересечении текущим идентификатором транзакции границы 2322^{32} значение базы инкрементируется, что приведет к инвалидации значений полей t_xmin/t_xmax актуальных версий строк: транзакции окажутся в будущем. Например, на странице есть одна строка с t_xmin равным 3 млрд, база pd_xid_base равна 0. Чтобы добавить в эту же страницу строку с номером транзакции 5 млрд, потребуется инкрементировать pd_xid_base (будет 1) и результат вычитания 4 млрд из 5 млрд присвоить t_xmin (1 млрд). Увеличение pd_xid_base привело бы к увеличению t_xmin существующей версии строки с 3 млрд до 7 млрд. В результате версия строки окажется невидимой для текущей транзакции.

Удаление или обновление версии строки

При удалении, либо обновлении строки выполняется проверка, что текущий идентификатор транзакции (XID=XMAXXID = XMAX), в сочетании с базой, не пересекает границу 2322^{32} (XMAXpd_xid_base232XMAX - pd\_{xid\_base} \le 2^{32}). Если условие удовлетворяется, то полю строки t_xmax (в случае удаления/обновления) присваивается значение XMAXpd_xid_baseXMAX - pd\_{xid\_base}. Если нет, то корректируется база и обновляются поля t_xmin/t_xmax во всех версиях строк на странице. Если базу не удалось подстроить в силу наличия версий строк, созданных старыми транзакциями, выполняется удаление мертвых версий строк и заморозка оставшихся старых версий строк на этой странице. Если после этих действий не удалось изменить базу, выдается ошибка.

Вставка новой версии строки

При вставке строки выполняется проверка, что текущий идентификатор транзакции (XID=XMINXID = XMIN), в сочетании с базой, не пересекает границу 2322^{32} (XMINpd_xid_base232XMIN - pd\_{xid\_base} \le 2^{32}). Если условие удовлетворяется, то полю строки t_xmin присваивается значение XMINpd_xid_baseXMIN - pd\_{xid\_base}. Если нет, то корректируется база и обновляются поля t_xmin/t_xmax во всех версиях строк на странице. Если базу не удалось подстроить в силу наличия версий строк, созданных старыми транзакциями, выполняется удаление неактуальных версий строк и заморозка оставшихся старых версий строк на этой странице. Если после этих действий не удалось изменить базу, выдается ошибка.

примечание

Очистка страницы от мертвых версий строк и заморозка старых могут не отработать из-за висящих, долгих транзакций, незавершенных подготовленных транзакций и неактивных слотов репликации.

Изменения в WAL

  • Изменен порядок полей в заголовке WAL записи XLogRecord для сокращения размера заголовка:

    Старый форматРазмерНовый форматРазмер
    xl_tot_len4 байтаxl_tot_len4 байта
    xl_xid4 байтаxl_crc4 байта
    xl_prev8 байтxl_xid8 байт
    xl_info1 байтxl_prev8 байт
    xl_rmid1 байтxl_info1 байт
    padding2 байта для выравнивания на границе 4 байтовxl_rmid1 байт
    xl_crc4 байта

    Размер заголовка WAL-записи в старом формате 24 байта, в новом - 26 байт.

  • Добавлена новая WAL-запись Heap3 с информацией о корректировке базового значения:

    • «desc: BASE_SHIFT XactId» - для pd_xid_base;
    • «desc: BASE_SHIFT MultiXactId» - для pd_multi_base.
    rmgr: Heap3       len (rec/tot):     58/    58, tx:           4294968069, lsn: 0/02A5ED88, prev 0/02A5EC68, desc: BASE_SHIFT XactId delta 4294967293 , blkref #0: rel 1663/5/16444 blk 539
    rmgr: Heap3 len (rec/tot): 58/ 58, tx: 4294968069, lsn: 0/02A5ED88, prev 0/02A5EC68, desc: BASE_SHIFT MultiXactId delta 4294967293 , blkref #0: rel 1663/5/16444 blk 539
  • Добавлена дедупликация WAL-записей заморозки версий строк для сокращения размера WAL (один план заморозки теперь может включать несколько версий строк, если их планы совпадают):

    rmgr: Heap2       len (rec/tot):    127/   127, tx:                    0, lsn: 0/02556C20, prev 0/02556BD8, desc: FREEZE_PAGE latestRemovedXid 790 nplans 1, blkref #0: rel 1663/1/2619 blk 23
    rmgr: Heap2 len (rec/tot): 137/ 137, tx: 0, lsn: 0/02556CE8, prev 0/02556CA0, desc: FREEZE_PAGE latestRemovedXid 790 nplans 2, blkref #0: rel 1663/1/2619 blk 24
    rmgr: Heap2 len (rec/tot): 133/ 133, tx: 0, lsn: 0/02556DC0, prev 0/02556D78, desc: FREEZE_PAGE latestRemovedXid 790 nplans 3, blkref #0: rel 1663/1/2619 blk 25
    rmgr: Heap2 len (rec/tot): 285/ 285, tx: 4294968069, lsn: 0/02A5EC68, prev 0/02A5EC20, desc: FREEZE_PAGE latestRemovedXid 4294968068 nplans 1, blkref #0: rel 1663/5/16444 blk 539
  • Повышена версия страницы WAL в ее заголовке: 0xD1100xD1110xD110 \rightarrow 0xD111.

Обновление

При обновлении СУБД с переносом данных с версии ранее чем 6.1.0 будет задействована утилита pg_upgrade. Утилита pg_upgrade выполняет следующие действия:

  • Проверяет, есть ли в обновляемой СУБД пользовательские таблицы со столбцами типа xid. Если есть, выводит сообщение с требованием их удалить перед обновлением. Список таких объектов БД перечислен в файле tables_using_xid.txt.

    Checking for incompatible "xid" data type                   fatal

    Your installation contains the "xid" data type in user tables.
    The internal format of "xid" changed in Pangolin 6.1.0 so this cluster
    cannot currently be upgraded. Note that even dropped attributes cause a problem.
    You can remove the problem tables and restart the upgrade.
    A list of the problem columns is in the file:
    tables_using_xid.txt

    В скрипт-разведчик добавлена аналогичная проверка. Пример сообщения:

    {{ control_name }}.FAIL__Обнаружена(ы) таблица(ы) в которой(ых) присутствует(ют) поле(я) с типом xid.
    В новой версии СУБД Pangolin применяется 64-битный счетчик транзакций.
    Необходимо удалить поле(я) с типом xid в таблице(ах).
    Список таблиц, использующих их (<имя таблицы>(<имя колонки>)):
    types04.typ_xid(val),
    types04.typ_xid(val_arr),
    types05fail.typ_pgc_pg_class(val),
    types05fail.typ_pgc_pg_class(val),
    types05fail.typ_pgc_pg_database(val),
    types05fail.typ_pgc_pg_database(val).{{ control_name }}.FAIL
  • Инвалидирует индексы и создает SQL-скрипты (reindex_spgist.sql, reindex_gin.sql, reindex_external.sql) для их перестроения:

    Checking for spgist indexes                                 warning

    Your installation contains spgist indexes. These indexes have different
    internal formats between your old and new clusters, so they must be
    reindexed with the REINDEX command. The file
    reindex_spgist.sql
    when executed by psql by the database superuser will recreate all invalid
    indexes; until then, none of these indexes will be used.

    Checking for gin indexes warning

    Your installation contains gin indexes. These indexes have different
    internal formats between your old and new clusters, so they must be
    reindexed with the REINDEX command. The file
    reindex_gin.sql
    when executed by psql by the database superuser will recreate all invalid
    indexes; until then, none of these indexes will be used.
  • Копирует файлы карт видимости (VM) и свободного пространства (FSM), устанавливая новую версию страниц, чтобы не выполнять это действие лениво в работающей СУБД.

  • Переносит файлы из директории обновляемой СУБД PGDATA/pg_xact в новую папку, выполняя обработку переполнения счетчика (если оно произошло в обновляемой БД) и переименовывая сегменты файлов. При использовании 32-битного счетчика выполнялась ротация таких файлов из-за переполнения счетчика, а в случае с 64-битным счетчиком переполнение не возможно, поэтому для поступающих транзакций всегда создаются новые файлы. Имена файлов расширены до 15 символов, чтобы не путать их с WAL-файлами:

    32 бит      64 бит
    0000 -> 000000000000000
    0001 -> 000000000000001
    ...
    0FFE -> 000000000000FFE
    0FFF -> 000000000000FFF
    0000 -> 000000000001000
    0001 -> 000000000001001
  • Переносит файлы из директорий обновляемой PGDATA/pg_multixact (offsets и members) в новую, выполняя обработку переполнения счетчика (если оно произошло в обновляемой БД) и переименовывая сегменты файлов. В файлах offsets ссылки на members будут начинаться с 1. В файлах members записи с XID станут 64-битными.

  • Устанавливает текущий счетчик в 2322^{32}, чтобы номера транзакций, созданных после обновления, были младше номеров транзакций, которые существовали до обновления.

  • Выполняет обработку переполнения счетчика в обновляемой СУБД в случае, когда следующее значение счетчика NextXID перешагнуло через максимальное значение UINT32 (4 млрд), а номер самой старой незамороженной транзакции (datfrozenxid) еще нет. Поскольку новое значение счетчика NextXID в обновленной СУБД устанавливается в 2322^{32}, нельзя оставлять прежнее значение datfrozenxid (2322^{32}-100), так как это означало бы, что все транзакции до этого значения в обновленной СУБД заморожены, но это не так: транзакции с 3 по NextXID могут остаться незамороженными, поэтому значение datfrozenxid устанавливается в значение 3 (первый неслужебный номер транзакции).

Переполнение счетчика транзакций

Обновление посредством pg_upgrade не конвертирует страницы таблиц в новый формат 64-битных счетчиков транзакций. Эта процедура выполняется на ведущем сервере «лениво» в процессе работы при чтении страниц с диска. Это может снизить производительность после обновления. Каждая переупакованная страница должна приводить к записи в WAL полного образа страницы. Чтобы избежать интенсивной записи в WAL и нагрузки на диск, сразу после старта обновленной СУБД, прочитанные с диска страницы переупаковываются, но полный образ записывается в WAL для каждой 128-й страницы. Частота записи определяется параметром xid64_full_page_write_sampling. Изменение параметра доступно без остановки СУБД. Оставшиеся переупакованные страницы в буферном кеше отмечаются специальным флагом BM_CONVERTED и не маркируются признаком того, что данные на странице изменились (флаг BM_DIRTY), поэтому не будут записываться на диск при их вытеснении. Флаг BM_CONVERTED служит для определения, требуется ли запись в WAL полного образа страницы при первом ее изменении в пишущем запросе или нет.

На резервном сервере при чтении страниц с диска выполняется их переупаковка в 64-битный формат, а буферы, содержащие эти страницы, отмечаются флагом BM_CONVERTED. Резервный сервер не записывает страницы на диск. Флаг BM_CONVERTED служит для определения, требуется ли запись в WAL полного образа страницы при первом ее изменении в пишущем запросе после повышения резервного сервера до ведущего или нет.

В случае нехватки места на странице для размещения специальной области, конвертация формата страницы откладывается до появления на ней свободного пространства, когда страница преобразуется во временный формат double XMAX. Поскольку после обновления все страницы заведомо заморожены, так как более старые транзакции не могут быть запущены, поле t_xmin больше не требуется и переиспользуется для хранения старших 32 бит XMAX при удалении строки. После очистки страницы формат преобразуется в 64-битный.

Диагностика

В журнале лог-файлов:

  • В вывод статистики по таблицам автоочистки добавлена информация о заморозке версий строк:

    LOG:  automatic aggressive vacuum of table "template1.pg_catalog.pg_statistic": index scans: 1
    pages: 0 removed, 29 remain, 29 scanned (100.00% of total)
    ...
    frozen: 15 pages from table (51.72% of total) had 172 tuples frozen
    ...
  • За неактуальностью исключен вывод сообщений с предупреждением о приближении счетчика к переполнению:

    WARNING:  database "postgres" must be vacuumed within 10999807 transactions
    HINT: To avoid a database shutdown, execute a database-wide VACUUM in that database.
    You might also need to commit or roll back old prepared transactions, or drop stale replication slots.

    WARNING: database "postgres" must be vacuumed before 100 more MultiXactIds are used
    HINT: Execute a database-wide VACUUM in that database.
    You might also need to commit or roll back old prepared transactions, or drop stale replication slots.
    STATEMENT: SELECT * FROM wraparound_test FOR KEY SHARE;
  • За неактуальностью исключен вывод сообщений об отказе в выполнении пишущих запросов:

    ERROR:  database is not accepting commands to avoid wraparound data loss in database "postgres"
    HINT: Stop the postmaster and vacuum that database in single-user mode.
    You might also need to commit or roll back old prepared transactions, or drop stale replication slots.
    STATEMENT: INSERT INTO wraparound_test(data) VALUES ('wraparound');

    database is not accepting commands that generate new MultiXactIds to avoid wraparound data loss in database "postgres"
    HINT: Execute a database-wide VACUUM in that database.
    You might also need to commit or roll back old prepared transactions, or drop stale replication slots.
    STATEMENT: SELECT * FROM wraparound_test FOR KEY SHARE;

Накладные расходы

Ниже перечислены потенциальные накладные расходы, связанные с использованием функциональности:

  • Процедура заморозки все еще требуется для отслеживания самой старой транзакции для удаления лишних файлов:

    • CLOG с состояниями транзакций в директории PGDATA/pg_xact;
    • со смещениями мультитранзакций (offsets) и данными о членах мультитранзакций (members) в директории PGDATA/pg_multixact;
    • с информацией о вложенных транзакциях в директории PGDATA/pg_subtrans.
  • Размер файлов в директориях PGDATA/pg_multixact и PGDATA/pg_subtrans увеличивается, поскольку в них хранятся номера транзакций. Для хранения информации о 2 млрд мультитранзакций необходимо предусмотреть:

    ДиректорииXID32XID64Во сколько раз требуется больше места на диске
    PGDATA/pg_xact512 Мбайт512 Мбайт1
    PGDATA/pg_multixact/offsets8 Гбайт16 Гбайт2
    PGDATA/pg_multixact/members10,2 Гбайт18,5 Гбайт1.81
    PGDATA/pg_subtrans8 Гбайт16 Гбайт2
  • Ожидается большее потребление CPU/RAM в связи с вычислением 8-байтных номеров транзакций и их хранением.

  • Скрипт для оценки увеличения размера WAL и файлов данных:

    Скрипт
    psql -c 'CREATE TABLE phonebook(
    id SERIAL PRIMARY KEY NOT NULL,
    name TEXT NOT NULL,
    phone INT NOT NULL);'

    echo 'INSERT INTO phonebook (name, phone) VALUES (random(), random());' > insert.sql

    pgbench -j 8 -c 8 -f insert.sql -t 100000 postgres

    psql -c "vacuum phonebook;"
    psql -c "select relpages, reltuples from pg_class where relname = 'phonebook';"

    pg_waldump -p /opt/pgdata/pg_wal/ $(ls /opt/pgdata/pg_wal | grep 00000001 | head -n1 | tr '\n' ' ') \
    $(ls -r /opt/pgdata/pg_wal | grep 00000001 | head -n1 | tr '\n' ' ') | \
    perl -e 'while(<>) { $_ =~ m#len \(rec/tot\):\s*(\d+)/\s*(\d+),#; $rec += $1; $tot += $2; $count++; } $rec /= $count; $tot /= $count; print "rec: $rec, tot: $tot\n";'

    Результат оценки:

XID32

XID64

Во сколько раз больше размер записей WAL и страниц файлов данных

Отчет pgbench

transaction type: insert.sql
scaling factor: 1
query mode: simple
number of clients: 8
number of threads: 8
maximum number of tries: 1
number of transactions per client: 100000
number of transactions actually processed: 800000/800000
number of failed transactions: 0 (0.000%)
latency average = 0.974 ms
initial connection time = 7.808 ms
tps = 8214.922077 (without initial connection time)
transaction type: insert.sql
scaling factor: 1
query mode: simple
number of clients: 8
number of threads: 8
maximum number of tries: 1
number of transactions per client: 100000
number of transactions actually processed: 800000/800000
number of failed transactions: 0 (0.000%)
latency average = 0.973 ms
initial connection time = 6.525 ms
tps = 8219.807348 (without initial connection time)

WAL

rec: 62.3198553257918, tot: 63.4172311004759

rec: 64.3823835120851, tot: 65.4697221194211

rec: 1.033/tot: 1.032 (~3%)

Число страниц в файлах данных

relpages | reltuples
---------+----------
5893 | 800000
relpages | reltuples
---------+----------
6003 | 800000

1.018 (1.8%)

  • Затраты CPU/RAM на переупаковку страниц после процесса обновления.

Ограничения

Функциональность имеет следующие ограничения:

  • Процедура vacuum freeze все еще необходима для удаления лишних сегментов CLOG (состояний транзакций) в директории PGDATA/pg_xact, а также сегментов со смещениями мультитранзакций (offsets) и данными о членах мультитранзакций (members) в директории PGDATA/pg_multixact.

  • На одной странице, которая никогда не очищалась, не могут быть размещены данные, зафиксированные транзакциями, отстоящими друг от друга более чем на 4 млрд (2322^{32}), то есть должно выполняться условие:

    max(t_xmin)min(t_xmin)232\max(t\_{xmin}) - \min(t\_{xmin}) \le 2^{32}

    Где:

    • min(t_xmin) – минимальное значение t_xmin среди всех кортежей на странице;
    • max(t_xmin) – максимальное (то же условие справедливо и для t_xmax).

    Невыполнение этого условия крайне маловероятно, поскольку для этого требуется принимать специальные меры: отключить autovacuum, использовать такой профиль нагрузки, чтобы в страницу записывалась сначала одна транзакция, потом через 4 млрд транзакций другая.

  • После обновления требуется перестроение индексов GIN и GiST, хранящих в страницах идентификаторы транзакций, а также других нестандартных индексов, о которых в общем случае неизвестно, хранятся ли идентификаторы транзакций в страницах или нет.

  • В pg_upgrade рекомендуется мигрировать файлы данных путем копирования или клонирования (copy-on-write) во избежание повреждения данных в обновляемой БД из-за непредвиденных обстоятельств.