Pull to refresh
283.33

Агрессивный автовакуум —  ужасный to prevent wraparound, зачем он нужен и как держать его в узде

Level of difficultyEasy
Reading time17 min
Views965

Привет! На связи Дмитрий Руденко из команды Databases Т-Банка. Благодаря обновленному мониторингу команды стали чаще обращать внимание на различные аспекты работы Postgres, так или иначе влияющие на общий перформанс сервиса. 

Один из таких аспектов — процесс уборки мусора, autovacuum, вычищающий мертвые версии строк таблиц. В статье рассмотрим причины возникновения одного из самых неприятных подвидов — autovacuum: to prevent wraparound.

Autovacuum: to prevent wraparound

На дашборде DB Performance процессам vacuum/autovacuum посвящена отдельная секция Vacuum & Analyze. В ней можно увидеть моменты запуска, тип вакуума, его тяжесть и даже процент выполнения.

График количества воркеров. Таблица с прогрессом выполнения автовакуума
График количества воркеров. Таблица с прогрессом выполнения автовакуума

Среди всех типов вакуума особенно выделяется суровый подвид автовакуума — autovacuum: to prevent wraparound. Он привлекает внимание пользователей, в связи с чем в последнее время в канале технической поддержки Databases регулярно возникают связанные с ним вопросы.

Вот некоторые из них:

  • У нас тяжелый многочасовой (а иногда и многодневный) wraparound-вакуум. Как сделать так, чтобы он больше не появлялся?

  • У нас активность на таблице закончилась несколько недель назад, почему wraparound пришел именно сейчас?

  • У нас тяжелый wraparound-вакуум работает с таблицей, в которой вообще не происходит изменений! Как такое может быть?

  • У нас инсерт-онли-таблица, мертвых версий строк нет в принципе. Что тут забыл wraparound-вакуум?

  • Полгода назад для веса мы создали таблицу сразу с 100 млн строк — кирпич. Не читали и не писали в нее никогда. Ваш Postgres сошел с ума!

А действительно, зачем Postgres решил сделать autovacuum: to prevent wraparound кирпичу? Давайте попытаемся разобраться и ответить на этот и другие вопросы.

В свое время, столкнувшись со зловещим явлением LOG: automatic aggressive vacuum to prevent wraparound of table, я начал с Гугла и довольно быстро вышел на статью Егора Рогова из его цикла по Postgres. Прочел и пошел гуглить дальше, набирать информацию, потому что понимания, несмотря на блестящий академический стиль Егора, увы, не состоялось. 

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

На мой взгляд, главное — понять, зачем, для чего вводились те или иные параметры, механизмы. Понять причину!

Коротко о MVCC

В Postgres используется так называемый MVCC-механизм управления параллельным доступом к базе данных. Для контекста важно, что любой апдейт создает новую версию строки. Старая версия при этом не удаляется, а помечается специальным образом. Аналогично при удалении строки она всего лишь помечается как неактуальная.

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

Каждая строка в таблице Postgres содержит два важных скрытых поля : xmin и xmax (беззнаковые 32-х битные).

Когда строка создается, транзакция, делающая это, пишет свой номер в поле xmin.

Транзакция с номером 42 создает новую строку

Данные

xmin

xmax

.... 

42 

0

Когда транзакция меняет какую-либо строку, она пишет в старую версию в поле xmax свой номер, помечая ее как неактуальную, и создает новую версию, в которой также пишет свой номер в xmin.

Транзакция с номером 1984 меняет строку, созданную транзакцией 42

Данные

xmin

xmax

....

42 

1984

Старая версия

....

....

....

1984

0

Новая версия


Новая версия строки может в общем случае появиться физически совсем в другом месте, даже на совсем другой странице.

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

Со временем количество неактуальных версий становится значительным — и увеличивается объем работы, которую необходимо проделать Postgres, чтобы извлечь нужные нам данные. На каждой странице теперь много мусора, и процент полезных данных уменьшается. 

Вывод: после того как мертвая версия строки стареет настолько, что более не нужна никому, ее желательно вычистить со страницы.

Тут у нас и появляется его величество Vacuum и важная его ипостась Autovacuum — процесс, который вычищает мертвые версии строк. Это основная задача для вакуума, но отнюдь не единственная, и об этом следует помнить!

Autovacuum, помимо очистки старых версий строк, выполняет еще несколько задач!

Длительные транзакции

Вакуум вычищает никому не нужные версии строк. А как понять, что версия строки никому не нужна? Только по номерам текущих активных транзакций. 

Теперь понятно, почему Postgres и его администраторы не любят долгих транзакций. Допустим, транзакция началась шесть часов назад и все еще не завершена. С момента ее рождения и старта появилось великое множество других, более воспитанных, корректных транзакций. Все они совершали какие-то изменения и завершались. Все они помечали своими номерами версии модифицированных строк как устаревшие.

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

Причем транзакция может заниматься своими делами с какими-то другими таблицами. Она вообще может ничего не делать, просто ее открыли (begin), сделали одно изменение и забыли, не закрыв. Postgres же не знает, вдруг ей понадобится именно эта таблица и именно эти строки в одном из следующих стейтментов.

Важно стараться не допускать длительных транзакций.

Параметры активации автовакуума

Autovacuum — процесс, запускаемый автоматически, без участия пользователя, а значит, нужен критерий для запуска. В самом деле, инициировать процесс уборки и запускать его после каждого апдейта — безумие (версии еще нужны живущим ныне транзакциям, да и просто невыгодно). Значит, нужно немного подождать, пока все текущие транзакции закончатся, поднакопить изменений и за раз проделать какое-то вменяемое количество работы. 

Сколько ждать? Логично предположить некую зависимость от общего количества строк. Например, со времени последнего вакуума изменилось (или удалилось) 10% строк таблицы — пора делать вакуум.

Тут на сцене появляется первый параметр автовакуума: autovacuum_vacuum_scale_factor (дефолтное значение — 0,2, то есть при изменении 20% строк таблицы будет запускаться автовакуум). Ну окей, вот у нас новая пустая таблица. Мы вставили одну строку, потом проапдейтили ее… Ой, уже 100% — вакуум. Еще один апдейт — и снова 100%, снова вакуум. Мда, для худых таблиц вакуум будет запускаться постоянно, и нам это точно не нужно. 

Так появляется второй параметр автовакуума: autovacuum_vacuum_threshold (значение по дефолту — 50, то есть минимум 50 строк должно измениться, чтобы пришел автовакуум).

Итого — сколько строк должно измениться после последнего вакуума, чтобы начался новый? Вот сколько:

autovacuum_vacuum_scale_factor × кол-во строк в таблице + autovacuum_vacuum_threshold

Оба этих параметра (как и большинство других, связанных с процессами автовакуума) задаются на весь инстанс, но их можно перекрывать на уровне каждой таблицы.

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

Для таблиц в 1 млрд строк (а такие у нас встречаются) 20% — слишком много. Для таких таблиц зачастую проходят недели, иногда месяцы, прежде чем удастся накопить достаточное количество изменений, чтобы запустился автовакуум. И в этот момент Postgres-у, его воркерам, да и нам всем приходится несладко! 

Надо переработать 200 млн строк, разбросанных по всей таблице. Фактически перебрать и обработать все страницы! Поэтому для больших таблиц параметр autovacuum_vacuum_scale_factor уменьшают на порядки. Иногда даже сбрасывают его в ноль, а объем изменяемых данных для запуска автовакуума контролируют вторым параметром, autovacuum_vacuum_threshold. Его значение устанавливают, например, приблизительно равным количеству изменяемых за сутки строк — чтобы вакуум запускался ежедневно.

Отслеживать модификационную активность и влияние указанных параметров удобно в наших дашбордах DB Performance table details. Там собраны различные метрики по таблицам. В секции Activity можно увидеть количество изменений в таблице.

График модификационной активности таблицы
График модификационной активности таблицы

А в секции Vacuum Analyze Bloat можно посмотреть частоту запуска и интенсивность процессов автовакуума и анализа.

График количества вызовов vacuum и analyze
График количества вызовов vacuum и analyze

С модифицируемыми таблицами все понятно. Поменяли часть записей, появились мертвые версии — значит, нужно провести уборку. А что с таблицами, в которых записи не меняются (insert-only)? В них устаревших версий с заполненным xmax нет в принципе. Ну и зачем тут вакуум?

Вот как выглядит такая таблица.

Данные

xmin

xmax

  .... 

42

0

  .... 

48

0

  .... 

544

0

  .... 

1984

0

Если брать текущий момент, то, скорее всего, активные транзакции имеют заведомо большие номера: 20 030, 20 031, 20 035. И выходит, что номера xmin, хранящиеся в таблице, которые определяют видимость версий строк и играют очень важную роль в окрестностях горизонта событий, по мере устаревания становятся бесполезными.

Какое-то время эти номерки были на коне. Каждый запрос, каждая транзакция вежливо интересовались у строки ее xmin и xmax, сравнивали со своим номером и принимали соответствующие решения о ее видимости или невидимости. Кому-то они показывались, а кому-то нет. Но теперь они опустились достаточно глубоко в эту виртуальную исторически-транзакционную пучину — и продолжают опускаться, будто Титаник, с каждой секундой, с каждой новой транзакцией в базе. Ну и зачем они теперь? Кому они нужны? Зачем эти сравнения? Все эти строки видны всем — хочется сказать так и стереть все эти бесполезные ныне циферки!

Но уменьшение полезности — это еще полбеды. Истинная катастрофа нас терпеливо поджидает впереди.

Номера транзакций — 32 битные числа. Максимально возможное количество комбинаций — 2³². Для Postgres важны не сами эти числа, а отношения между ними. Именно отношения определяют порядок: что было раньше, что позже. Именно отношения между номером читающей транзакции и номерами, указанными в xmin и xmax строки, определяют ее видимость. Это отношение реализовано довольно просто: знаменитое транзакционное кольцо.

Колесо номеров транзакций
Колесо номеров транзакций

Весь диапазон, 2³², делится пополам. Номера, меньшие (с учетом закольцованности), чем у нашей транзакции, считаются номерами транзакций, начавшихся до старта нашей, большие — начавшихся после нашей. 

Чувствуете приближение катастрофы?

Некая транзакция за номером 42 записала строку. В поле xmin сохранено значение 42. В тот момент это было важное значение, потому что на фоне трудились транзакции 53, 37, 65. Все они сравнивали свои номера с полем xmin и понимали, видна им эта запись или нет. 

Но время шло, и сейчас актуальны транзакции 23 354, 23 376, 23 320. И все они видят, что запись xmin = 42 глубоко в прошлом, а значит, видна им всем. Но прошло еще немного времени, началась эпоха транзакций 2 147 483 700 — и бабах! Номер 42 преодолел половину колеса и стал номером будущей транзакции! А значит, запись, отмеченная xmin = 42, неожиданно пропала со всех радаров, как будто ее и не было никогда. А ведь это мог быть чей-то вклад! Вот незадача.

Необходимо периодически проходить по таблице и помечать особым признаком достаточно старые живые записи: «Эта запись видна всем, не смотри на xmin!» Это и есть заморозка записи.

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

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

Старение номеров транзакций, записанных в таблице, никак не зависит от активности запросов к этой таблице — только от общей транзакционной нагрузки на базу!

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

Откроем любую таблицу, в которой не происходит изменений, — и в дашборде DB Performance table details, в секции Vacuum Analyze Bloat, на графике tx_freeze_age (возраст самой старой незамороженной записи) на достаточно большом периоде увидим примерно такую картину.

График возраста самой старой не замороженной записи
График возраста самой старой не замороженной записи

Значит нужно периодически проходить по таблице и помечать достаточно старые живые записи, видимые всем, особым признаком. Ну и кому, как не нашему автовакууму, поручить эту работу? Он же регулярно ходит и чистит мертвые (с проставленным xmax) версии. Пусть дополнительно автовакуум поглядывает и на живые версии. Если встречает какую-то живую запись (xmax = 0) и видит, что ее xmin достаточно старый и она видна всем текущим транзакциям, пусть помечает ее как замороженную. И тогда уже безразлично, что там хранится 42, на это число никто смотреть не будет.

Автовакууму добавили работы, теперь у него новый алгоритм. Выгружаем одну страницу, видим мертвые строки — вычищаем. Видим одну (для простоты) живую, xmin = 42. Она была добавлена всего два часа назад — и 20 тысяч номеров назад по транзакциям. Все текущие активные транзакции (20 044, 20 056, 20 067) начались уже после завершения той, 42-й, так что строка видна им всем. Почему бы не заморозить ее? Замораживаем. Лучше сразу, чтоб потом не беспокоиться.

Но есть тут если и не проблема, то некая неоптимальность. Есть вероятность, что эта версия не окончательная и будут еще апдейты. Распространенное поведение — создали, потом несколько апдейтов и потом уже хранится в неизменном виде. И какой смысл ее замораживать, такую молодую (20 тысяч номеров всего), когда, скорее всего, ее не раз еще проапдейтят? Так появляется первый параметр, связанный с to prevent wraparound:

vacuum_freeze_min_age (дефолтное значение — 50 млн транзакций). 

Вакуум, помимо очистки, замораживает строки, возраст которых превышает указанный параметр. Важно закрепить, что по дефолту вакуум не будет замораживать слишком молодые строки! После появления строки возможен целый ряд вакуумов, которые будут просто проходить мимо нее, ожидая дальнейших изменений. Это важный момент!

Карта видимости

Так, с одной страницей разобрались, но их же вон сколько. Автовакууму нужно перебрать их все? Представим себя на его месте. Вот он где-то отдыхает, и тут ему летит пейдж: «В такой-то таблице со времени прошлой твоей уборки накопилось слишком много изменений. Иди и уберись». Ну пошел, что еще делать. Вот он встал перед этой таблицей, а там уйма страниц силуэтами виднеется в темноте, на каждой по сотне строк. И где там именно произошли эти ваши изменения? Опять весь объем перебирать?

Нет. На помощь ему приходит некая оптимизация. Для каждой таблицы есть особый справочник — карта видимости. В карте видимости отмечены страницы, на которых все версии строк видны всем активным транзакциям (наличие этой карты позволяет Postgres использовать истинный index only scan, без захода в таблицу)

Давайте временно остановим всю пользовательскую активность в базе, пока будет работать автовакуум (в реальности такого, конечно же, не происходит). И карту видимости сбросим. 

В этот раз автовакууму все же придется просканировать таблицу полностью. Он сканирует страницу, вычищает мертвое, смотрит на активные транзакции. Записи в странице видны всем активным? А в нашем случае активных и нет. Отлично (отмечает страницу в карте видимости). Переходит к следующей странице.

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

Наконец, пользователи набрали 20% изменений, пришел автовакуум, но теперь ему нет нужды перебирать все страницы. Достаточно пройтись только по тем, что потеряли бит в карте. В самом деле, в остальных страницах изменений-то не было! Чудесная оптимизация! 

Автовакуум не заходит на страницы, отмеченные в карте видимости.

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

На подходе еще один важный параметр, влияющий на заморозку: vacuum_freeze_table_age (дефолтное значение — 150 млн транзакций).

Это что за зверь? 

В таблице много страниц, на каждой из них множество строк. Какие-то из них заморожены, какие-то нет. Очевидно, среди всех не замороженных найдется строка с самым большим возрастом. За этим возрастом Postgres следит очень внимательно — и хранит его отдельно в pg_class, в поле relfrozenxid (на самом деле это не возраст, а номер транзакции, сути это не меняет). 

Как только возраст самой старой не замороженной строки в таблице достигает значения vacuum_freeze_table_age, вакуум перестает обращать внимание на бит видимости и заходит в том числе и на страницы, отмеченные как видимые всем. Но только если не отмечен бит «страница заморожена полностью» (о нем чуть ниже).

Ну и совсем недалеко от vacuum_freeze_table_age (по дефолтным настройкам) на шкале возрастов расположен еще один параметр:

vacuum_freeze_max_age (дефолтное значение — 200 млн транзакций). 

Именно этот параметр определяет, когда Postgres запустит autovacuum: to prevent wraparound.

Оценить его влияние легко можно в табличном дашборде DB Performance table details на вышеупомянутом графике tx_freeze_age. 

У любой таблицы на длительном промежутке времени можно увидеть характерную пилу
У любой таблицы на длительном промежутке времени можно увидеть характерную пилу

Как только возраст самой старой не замороженной строки в таблице достигает значения vacuum_freeze_max_age (а дефолтное значение — всего одна десятая половины колеса 2³²), все и случается. Postgres бросает все дела и вызывает автовакуум с дополнительным указанием: «срочно замораживай». 

В этот момент многое зависит от текущего состояния таблицы, а вернее — всех ее страниц. И вот почему. Уже в версии Postgres 9.6 в карту видимости добавили еще один бит — all_frozen. Таким битом Postgres отмечает страницы, в которых все версии строк замороженные. 

Наличие all_frozen существенно влияет на общую картину и тяжесть выполнения to prevent wraparound. В этом режиме Postgres нет нужды сканировать страницы, отмеченные all_frozen. Чем больше страниц отмечено, тем легче проходит агрессивный вакуум.

Кто может увеличить число полностью замороженных страниц? Наш старый знакомый — обычный регулярный автовакуум. Ведь он еще и заморозкой занимается — и если видит, что после его действий вся страница стала заморожена, он проставляет бит all_frozen.

Если мы не хотим тяжелых wraparound-вакуумов — нужно добиться достаточно частых, регулярных обычных вакуумов!

Событие autovacuum: to prevent wraparound происходит всегда и с каждой таблицей. Postgres должен проверить строки, заморозить что можно, сбросить relfrozenxid и ждать повтора через 200 млн транзакций. Главное, чтобы к этому моменту таблица была подготовлена.

Кейсы с to-prevent-wraparound автовакуумом

Мы ознакомились с механизмом to-prevent-wraparound и с основными регулирующими его параметрами. Теперь, вооруженные этими знаниями, давайте рассмотрим четыре важных кейса, связанных с to-prevent-wraparound вакуумом.

Кейс первый: кирпич. Многим командам требуется создать объемную никому не нужную таблицу, в простонародье именуемую кирпичом.

Стандартный подход: запустили какой-нибудь generate_series(1, 10000000000), повставляли записи в таблицу, получили необходимые 200 ГБ веса и успокоились. Ничего не меняется в ней, ничего не читается из нее. 

Тем неожиданнее в какой-то момент получить тяжеленный to prevent wraparound на несколько часов, а то и дней. Давайте посмотрим, что же происходит. Полагаем, что все настройки дефолтные, версия Postgres не менее 13, длинных транзакций нет.

Большая транзакция за номером 42 вставила свои 100 млн строк. Postgres встрепенулся (сработала парочка autovacuum_vacuum_insert_scale_factor, autovacuum_vacuum_insert_threshold), щелкнул пальцами, автовакуум пошел смотреть, что там.

Фигасе. Накидали.

В карте видимости-заморозки отмеченных страниц нет, значит, придется перелопатить все. Автовакуум открывает первую страницу: о! Мертвых версий нет, все записи видны всем — ставит бит в карте видимости. Может, заморозить сразу? Нет, не могу, записи еще слишком молодые. Создала 42-я транзакция, а сейчас активны 239 500 и выше (дефолтных 50 млн еще не прошло). Жаль, отложим до следующего раза (эх, не понимает он, что следующего раза не будет). 

Так автовакуум проходит по всем страницам. Итог — все страницы отмечены в карте видимости, ни одна запись не заморожена (все отмечены 42-й транзакцией). Он докладывает, что дело сделано, и идет отдыхать.

Ну а дальше происходит самое страшное: модификационная активность на кирпиче прекращается полностью. Нет модификаций, нет мертвых версий — нет вакуума. Возраст 42-х уже превысил 50 млн, и любой вакуум бы их смело заморозил, но вот вакуумов-то и нет. А у нас, на минуточку, 100 млн строк, отборные 200 ГБ страниц, полностью беззащитны перед неумолимым старением. Ну и дальше понятно. Время проходит, в какой-то момент возраст 42-й достигает 200 млн, и теперь уже Postgres кидает alarm-пейдж — бросать все дела и идти замораживать строки, помеченные 42-й транзакцией. Только строк этих не одна и не две, к сожалению.

Рекомендаций две, особой разницы между ними нет, главное — заморозить.

Вариант первый: сразу после накатки данных вызвать вручную vacuum freeze в таблице. Vacuum freeze игнорирует параметры оптимизации и не смотрит ни на vacuum_freeze_table_age, ни на vacuum_freeze_min_age. Он пропускает только страницы, отмеченные как уже полностью замороженные.

Вариант второй: при создании таблицы указать vacuum_freeze_min_age = 0. В этом случае первый вакуум, который придет в таблицу после массового инсерта, будет попутно с анализом и простановкой флага видимости страниц замораживать все записи (и ставить соответствующий признак в карте).

Кейс второй: большая таблица. Простой кейс, в связи с чем особенно печально наблюдать его распространенность. 

Представьте, что у нас гигантская таблица — миллиард записей. Обычный вакуум при дефолтных настройках приходит сюда редко, даже если активность в таблице большая. Суточная активность велика, но она просто теряется в недрах этой туши. И вот что мы получаем:

  1. Частота прихода вакуума в эту таблицу, по дефолтным настройкам, — раз в несколько недель.

  2. Общая транзакционная активность проворачивает 200 млн транзакций vacuum_freeze_max_age за несколько дней.

  3. За эти несколько дней пользователи добавляют, изменяют, загрязняют, размораживают достаточно большое количество страниц (могут и 100% разморозить за счет случайности изменений).

  4. Через 200 млн транзакций приходит агрессивный вакуум, а там полный треш. 

Рекомендация: следить за настройками автовакуума для больших таблиц. Уменьшать scalefactor, использовать threshold и не допускать долгого отсутствия регулярного вакуума на больших таблицах.

В случае необходимости можно самостоятельно запустить заморозку с помощью vacuum freeze tablename.

Кейс третий: однодневка. Также весьма частый кейс. У нас партиционированная по времени таблица. Ширина партиции — один день. Начинаются сутки, и приложение активно пишет в текущую партицию. Изменений много, и автовакуум постоянно кружится тут. Вначале — очень часто (scale factor на еще маленькой таблице), но с течением дня и ростом таблицы все реже и реже. 

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

Ну а зачем сюда заглядывать-то? Данные практически не меняются, мертвых версий очень мало по сравнению с общим количеством записей — тут и так, по большому счету, порядок. А что там с заморозкой? А вот тут не все так хорошо. 

Давайте вспоминать предыдущие сутки. Активность высокая, автовакуум — частый гость, но вот записи-то все молодые! А у нас по дефолту 50 млн транзакций — vacuum_freeze_min_age. Это не так уж и мало, за сутки такое вряд ли набьешь. А значит, все, что было вставлено за сутки, осталось не замороженным и теперь, выходит, забытым. Ну а дальше как обычно: тик-так, тик-так — и вот тебе неожиданность, to prevent wraparound в таблице, в которой давно не было изменений.

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

Если этот период достаточно велик, упомянутые 50 млн успеют накопиться для самых ранних версий строк и они, возможно, будут заморожены одним из финальных автовакуумов. Но для последних версий морозильщика все равно не найдется. И какая-то часть таблицы, иногда достаточно существенная, уйдет в заряженном состоянии во тьму дожидаться прихода его величества autovacuum: to prevent wraparound.

Рекомендации во многом зависят от конкретной ситуации и профиля модификационной нагрузки.

Возможные варианты решения:

  • Если после вставки не происходит многократных модификаций (а если они и происходят, то вскоре после вставки), можно сильно занизить vacuum_freeze_min_age. Это позволит автовакууму зацепить в заморозке большее количество строк. 

  • Если по бизнес-логике партиции в прошлом живут недолго и хвост постоянно укорачивается, можно попробовать увеличить vacuum_freeze_max_age. Увеличивая это значение для таблицы, можно добиться того, что возраст срабатывания to-prevent-wraparound-вакуума будет наступать после физического удаления партиции и проблема этого вида вакуума вообще не будет беспокоить.

  • Ручной vaccum freeze, вызванный после завершения основной модификационной нагрузки (скажем, в одну из последующих ночей), также будет подходящим решением.

Кейс четвертый: Insert-only-таблица. Тут все просто. Модификаций нет, мертвых версий нет. До 13-й версии автовакуум вообще не заходил в такие таблицы, что создавало проблемы с картой видимости и заморозкой. 

В 13-й версии появились два новых параметра: autovacuum_vacuum_insert_scale_factor (дефолт — 0,2) и autovacuum_vacuum_insert_threshold (дефолт — 1 000), они помогают активировать автовакуум для подобных таблиц. Главное — для таких таблиц имеет смысл сразу выставить vacuum_freeze_min_age в ноль. В самом деле, раз модификаций не предвидится, можно сразу замораживать строки. При первом же вакууме. 

Рекомендации:

  • Установить для таких таблиц vacuum_freeze_min_age = 0.

  • Если версия Postgres меньше 13-й, регулярно вызывать vacuum freeze вручную. Если больше — установить autovacuum_vacuum_insert_scale_factor и autovacuum_vacuum_insert_threshold таким образом, чтобы автовакуум регулярно заходил в таблицу и благодаря тому что vacuum_freeze_min_age = 0 замораживал все новые строки.

Выводы

Мы познакомились с базовыми механиками работы автовакуума и причинами возникновения to prevent wraparound. Рассмотрели несколько практических кейсов и сделали выводы:

  • Не стоит полагаться на дефолтные настройки автовакуума в нагруженных системах.

  • Параметры автовакуума должны быть адаптированы под специфику нагрузки и жизненного цикла объектов БД.

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

  • Vacuum_freeze_min_age — параметр, который может кардинально повлиять на ситуацию с autovacuum: to prevent wraparound.

  • Для больших таблиц необходимо обеспечить регулярное обслуживание автовакуумом.

Залетайте в комментарии поделиться своим опытом или оставляйте вопросы, если вдруг они остались.

Tags:
Hubs:
+13
Comments3

Articles

Information

Website
l.tbank.ru
Registered
Founded
Employees
over 10,000 employees
Location
Россия