Базы данных
MVCC и изоляция: диагностика bloat и аномалии write skew
Читать про неуправляемый bloat и write skew — не то же самое, что смотреть, как таблица удваивается за ночь из-за оставленной открытой транзакции, или видеть, как две транзакции ломают инвариант и обе коммитят. Собери небольшую нагрузку на Postgres, доведи её до обоих сбоев, затем почини рычагами юнита — доказывая каждый шаг числами из системных представлений.
Преврати ментальную модель юнита в воспроизводимый цикл: инструментируй состояние MVCC, воспроизведи bloat от oldest xmin и аномалию write skew на реальной базе, продиагностируй каждое из catalog views, почини минимальным изменением изоляции и autovacuum и проверь числами до/после.
На реальном инстансе PostgreSQL воспроизведи два сбоя MVCC — bloat от oldest xmin и аномалию write skew — затем продиагностируй и почини оба, доказывая каждый фикс измерениями из pg_stat-представлений, а не утверждениями.
- Таблица до/после для сценария bloat: доля мёртвых tuples, размер таблицы и возраст самого старого backend_xmin — измеренные под одной нагрузкой, показывающие падение доли после освобождения пиннинга.
- Снятая строка лога autovacuum «not yet removable» с сопоставленным pid/слотом, найденным запросом к каталогу (а не угаданным).
- Транскрипт коммита аномалии write skew под REPEATABLE READ, плюс транскрипт отмены одной транзакции под SERIALIZABLE с 40001 и ретрая, сохраняющего инвариант.
- Параграф вывода: какую стратегию изоляции ты бы зашипил для инварианта и почему, и какая инфраструктурная настройка (timeout, scale_factor) — долговечная оборона от сбоя bloat.
- Добавь однастраничный on-call runbook: три запроса, которые надо выполнить, когда таблица раздувается (backend_xmin, xmin слота, возрасты wraparound), как читать лог autovacuum и дерево решений для фикса.
- Покажи HOT-путь: добавь неиндексируемый столбец, гоняй цикл только UPDATE и покажи n_tup_hot_upd / n_tup_upd выше 80%; затем смени fillfactor на 100 (или обнови индексируемый столбец) и покажи обвал HOT-доли и примерно удвоение WAL на апдейт.
- Прогони фикс write skew под нагрузкой: забенчмарь SERIALIZABLE против RC + FOR UPDATE на горячей строке при высокой конкуренции и построй график доли ретраев 40001 против пропускной способности, чтобы показать, где доминируют ложные срабатывания SSI.
- Воспроизведи рост MultiXact: гоняй FK-тяжёлую нагрузку (много дочерних INSERT против одного родителя) и следи, как age(datminmxid) растёт, подтверждая, что разделяемые блокировки давят на отдельные от XID часы wraparound.
Это цикл, который ты будешь гонять в каждом реальном инциденте MVCC: сначала инструментируй catalog views, воспроизведи сбой намеренно, продиагностируй из backend_xmin / xmin слота / возрастов wraparound вместо угадывания, почини минимальным рычагом (освободи пиннинг, выбери самый слабый isolation level, защищающий инвариант, тюнингуй autovacuum per table) и проверь числами до/после под одной нагрузкой. Сделав это однажды на игрушечной базе, ты превращаешь production-версию — где таблица удваивается, а пейджер звенит — в мышечную память вместо паники.