awesome-everything EN
↑ Обратно к восхождению

Базы данных

CLOG, XID wraparound и MultiXact

Суть HeapTupleSatisfiesMVCC ходит в CLOG когда hint-биты не выставлены. XID — 32-битный: заморозка на 200M транзакций, отказ на 2B. MultiXact хранит shared row locks с собственным wraparound счётчиком.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 18 min

Sentry 2017: autovacuum отстал, 32-битный XID-счётчик приблизился к потолку, Postgres отказался принимать новые соединения и завершился с сообщением «database is not accepting commands to avoid wraparound data loss». Ручная заморозка под огнём. Разберём механику до байта.

HeapTupleSatisfiesMVCC и hint-биты

Решение видимости на каждом tuple реализовано в HeapTupleSatisfiesMVCC(HeapTuple, Snapshot, Buffer) в heapam_visibility.c. Функция проходит по дереву решений через биты t_infomask:

БитЗначение
HEAP_XMIN_COMMITTEDt_xmin закоммичен — короткий путь
HEAP_XMIN_INVALIDt_xmin откатан или невалиден
HEAP_XMAX_COMMITTEDt_xmax закоммичен (строка мёртвая)
HEAP_XMAX_INVALIDt_xmax откатан или нулевой (строка живая)

Когда биты выставлены — прямое решение без CLOG. Когда нет — функция лезет в CLOG, получает ответ и записывает hint-бит обратно на страницу.

Почему это важно операционно: SELECT сразу после долгого INSERT может быть медленнее, чем тот же SELECT секундой позже. Первый скан трогает CLOG-запись каждого tuple и пишет hint-биты; второй скан читает биты напрямую.

CLOG (commit log)

Каждый коммиченный или откатанный XID записывается в pg_xact/ (раньше pg_clog/): 2 бита на transaction id, упакованные в страницы по 8 КБ. Одна страница покрывает 16 384 транзакции.

CLOG активно кешируется (clog_buffers). Hint-биты на tuple избегают CLOG-lookup; но на свежезаписанном tuple первый читатель обязан спросить CLOG.

После коммита транзакции биты в CLOG надёжно на диске благодаря WAL. Именно поэтому fsync = on и synchronous_commit = on непереговариваемы: коммит, подтверждённый до того как WAL ушёл на диск, может оставить CLOG несогласованным на восстановлении.

XID wraparound и заморозка

Transaction id в Postgres 32-битные. На ~2B транзакций счётчик переполнится, и старые xmin/xmax выглядели бы новее, чем они есть — катастрофа корректности.

Механизм предотвращения: Как только tuple “достаточно старый” (старше autovacuum_freeze_max_age, дефолт 200M транзакций), VACUUM переписывает его t_xmin на FrozenTransactionId — специальное значение, всегда видимое любому snapshot.

Пороги эксплуатации:

SELECT datname, age(datfrozenxid) FROM pg_database;
ПорогДействие
1B транзакцийАлерт
1.5B транзакцийПейджер
2B транзакцийPostgres отказывает в соединениях

VACUUM wraparound-prevention — задача autovacuum высшего приоритета: запускается даже когда autovacuum = off. На базе, приближающейся к 2B без достаточной заморозки, Postgres отказывает в соединениях с сообщением database is not accepting commands to avoid wraparound data loss.

Sentry 2017: autovacuum отстал на кластере с интенсивной записью — закончилось ручной заморозкой VACUUM FREEZE для восстановления.

MultiXact и shared row locks

Когда несколько транзакций хотят держать неэксклюзивную блокировку на одной строке (SELECT ... FOR SHARE, FOR KEY SHARE), Postgres не может разместить несколько XID в единственное поле t_xmax.

Вместо этого выделяется MultiXactId — 32-битный id, указывающий в pg_multixact/, где перечислены участники и их режимы блокировок.

MultiXact имеет собственный wraparound (тоже 2B), отслеживается отдельно и замораживается на отдельном расписании.

Нагрузка с интенсивным SELECT ... FOR KEY SHARE (неявная блокировка от проверок foreign key) может попасть в MultiXact wraparound задолго до XID wraparound.

SELECT datname, age(datfrozenxid), age(datminmxid) FROM pg_database;

Симптом MultiXact проблемы: autovacuum: found xxx nonremovable row versions ... dead row versions cannot be removed yet с высоким “oldest MultiXact” age.

Числа

CLOG, XID, MultiXact: числа
Бит на транзакцию в CLOG
2 бита (4 состояния: in-progress, committed, aborted, subtransaction)
Транзакций на страницу CLOG (8 КБ)
16 384
XID wraparound
~2B транзакций (2^31)
Trigger заморозки
autovacuum_freeze_max_age = 200M
MultiXact wraparound
2B, отдельный от XID
Sentry 2017 recovery
ручная заморозка VACUUM FREEZE

Проверь себя

Викторина

Зачем HeapTupleSatisfiesMVCC пишет hint-биты обратно на страницу после консультации CLOG?

Викторина

На каком пороге возраста XID Postgres сам начинает аварийный anti-wraparound vacuum, игнорируя autovacuum = off?

Викторина

Рабочая нагрузка с интенсивным SELECT ... FOR KEY SHARE может попасть в wraparound задолго до обычного XID wraparound. Почему?

Вспомните перед уходом
  1. 01
    Объясни, почему SELECT сразу после долгого INSERT может быть медленнее, чем тот же SELECT секундой позже.
  2. 02
    Как FrozenTransactionId защищает от XID wraparound?
  3. 03
    Назови SQL-запрос для мониторинга расстояния до XID wraparound и MultiXact wraparound одновременно.
Recap
  • HeapTupleSatisfiesMVCC проверяет hint-биты в t_infomask; при их отсутствии обращается к CLOG и пишет бит обратно
  • CLOG (pg_xact/): 2 бита на XID, 8 КБ на страницу = 16 384 транзакции на страницу
  • XID 32-битный: VACUUM замораживает tuple на 200M (autovacuum_freeze_max_age), Postgres отказывает на 2B
  • FrozenTransactionId = 2, всегда видим для любого snapshot — устраняет tuple из пространства XID-сравнения
  • Anti-wraparound vacuum работает всегда, независимо от autovacuum = off
  • MultiXact: отдельный 32-битный счётчик для shared row locks (FOR SHARE/FOR KEY SHARE), хранится в pg_multixact/; имеет собственный wraparound
Связанные уроки
встречается в140
Продолжить восхождение ↑SSI и production-тюнинг autovacuum
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.