23.1. Регламентная очистка

Базы данных Postgres Pro требуют периодического проведения процедуры обслуживания, которая называется очисткой. Во многих случаях очистку достаточно выполнять с помощью демона автоочистки, который описан в Подразделе 23.1.6. Возможно, в вашей ситуации для получения оптимальных результатов потребуется настроить описанные там же параметры автоочистки. Некоторые администраторы СУБД могут дополнить или заменить действие этого демона командами VACUUM (обычно они выполняются по расписанию в заданиях cron или Планировщика задач). Чтобы правильно организовать очистку вручную, необходимо понимать темы, которые будут рассмотрены в следующих подразделах. Администраторы, которые полагаются на автоочистку, возможно, всё же захотят просмотреть этот материал, чтобы лучше понимать и настраивать эту процедуру.

23.1.1. Основные принципы очистки

Команды VACUUM в Postgres Pro должны обрабатывать каждую таблицу по следующим причинам:

  1. Для высвобождения или повторного использования дискового пространства, занятого изменёнными или удалёнными строками.
  2. Для обновления статистики по данным, используемой планировщиком запросов Postgres Pro.
  3. Для обновления карты видимости, которая ускоряет сканирование только индекса.
  4. Для предотвращения потери очень старых данных из-за зацикливания идентификаторов транзакций или мультитранзакций.

Разные причины диктуют выполнение действий VACUUM с разной частотой и в разном объёме, как рассматривается в следующих подразделах.

Существует два варианта VACUUM: обычный VACUUM и VACUUM FULL. Команда VACUUM FULL может высвободить больше дискового пространства, однако работает медленнее. Кроме того, обычная команда VACUUM может выполняться параллельно с использованием производственной базы данных. (При этом такие команды как SELECT, INSERT, UPDATE и DELETE будут выполняться нормально, хотя нельзя будет изменить определение таблицы командами типа ALTER TABLE.) Команда VACUUM FULL требует блокировки обрабатываемой таблицы в режиме ACCESS EXCLUSIVE и поэтому не может выполняться параллельно с другими операциями с этой таблицей. По этой причине администраторы, как правило, должны стараться использовать обычную команду VACUUM и избегать VACUUM FULL.

Команда VACUUM порождает существенный объём трафика ввода/вывода, который может стать причиной низкой производительности в других активных сеансах. Это влияние фоновой очистки можно регулировать, настраивая параметры конфигурации (см. Подраздел 18.4.4).

23.1.2. Высвобождение дискового пространства

В Postgres Pro команды UPDATE или DELETE не вызывают немедленного удаления старой версии изменяемых строк. Этот подход необходим для реализации эффективного многоверсионного управления конкурентным доступом (MVCC, см. Главу 13): версия строки не должна удаляться до тех пор, пока она остаётся потенциально видимой для других транзакций. Однако в конце концов устаревшая или удалённая версия строки оказывается не нужна ни одной из транзакций. После этого занимаемое ей место должно быть освобождено и может быть отдано новым строкам, во избежание неограниченного роста потребности в дисковом пространстве. Это происходит при выполнении команды VACUUM.

Обычная форма VACUUM удаляет неиспользуемые версии строк в таблицах и индексах и помечает пространство свободным для дальнейшего использования. Однако это дисковое пространство не возвращается операционной системе, кроме особого случая, когда полностью освобождаются одна или несколько страниц в конце таблицы и можно легко получить исключительную блокировку таблицы. Команда VACUUM FULL, напротив, кардинально сжимает таблицы, записывая абсолютно новую версию файла таблицы без неиспользуемого пространства. Это минимизирует размер таблицы, однако может занять много времени. Кроме того, для этого требуется больше места на диске для записи новой копии таблицы до завершения операции.

Обычно цель регулярной очистки — выполнять простую очистку (VACUUM) достаточно часто, чтобы не возникала необходимость в VACUUM FULL. Демон автоочистки пытается работать в этом режиме, и на самом деле он сам никогда не выполняет VACUUM FULL. Основная идея такого подхода не в том, чтобы минимизировать размер таблиц, а в том, чтобы поддерживать использование дискового пространства на стабильном уровне: каждая таблица занимает объём, равный её минимальному размеру, плюс объём, который был занят между процедурами очистки. Хотя с помощью VACUUM FULL можно сжать таблицу до минимума и возвратить дисковое пространство операционной системе, большого смысла в этом нет, если в будущем таблица так же вырастет снова. Следовательно, для активно изменяемых таблиц лучше с умеренной частотой выполнять VACUUM, чем очень редко выполнять VACUUM FULL.

Некоторые администраторы предпочитают планировать очистку БД самостоятельно, например, проводя все работы ночью в период низкой загрузки. Однако очистка только по фиксированному расписанию плоха тем, что при резком скачке интенсивности изменений раздувание таблицы может привести к тому, что для высвобождения пространства действительно понадобится выполнить VACUUM FULL. Использование демона автоочистки снимает эту проблему, поскольку он планирует очистку динамически, отслеживая интенсивность изменений. Полностью отключать этот демон может иметь смысл, только если вы имеете дело с предельно предсказуемой загрузкой. Возможен и компромиссный вариант — настроить параметры демона автоочистки так, чтобы он реагировал только на необычайно высокую интенсивность изменений и мог удержать ситуацию под контролем, в то время как команды VACUUM, запускаемые по расписанию, будут выполнять основную работу в периоды нормальной загрузки.

Если же автоочистка не применяется, обычно планируется выполнение VACUUM для всей базы данных раз в сутки в период низкой активности, и в случае необходимости оно дополняется более частой очисткой интенсивно изменяемых таблиц. (В некоторых ситуациях, когда изменения производятся крайне интенсивно, самые востребованные таблицы могут очищаться раз в несколько минут.) Если в вашем кластере несколько баз данных, не забывайте выполнять VACUUM для каждой из них; при этом может быть полезна программа vacuumdb.

Подсказка

Результат обычного VACUUM может быть неудовлетворительным, когда вследствие массового изменения или удаления в таблице оказывается много мёртвых версий строк. Если у вас есть такая таблица и вам нужно освободить лишнее пространство, которое она занимает, используйте команду VACUUM FULL или, в качестве альтернативы, CLUSTER или один из вариантов ALTER TABLE, выполняющий перезапись таблицы. Эти команды записывают абсолютно новую копию таблицы и строят для неё индексы. Все эти варианты требуют блокировки в режиме ACCESS EXCLUSIVE. Заметьте, что они также на время требуют дополнительного пространства на диске в объёме, приблизительно равном размеру таблицы, поскольку старые копии таблицы и индексов нельзя удалить до завершения создания новых копий.

Подсказка

Если у вас есть таблица, всё содержимое которой периодически нужно удалять, имеет смысл делать это, выполняя только TRUNCATE, а не DELETE и затем VACUUM. TRUNCATE немедленно удаляет всё содержимое таблицы, не требуя последующей очистки (VACUUM или VACUUM FULL) для высвобождения неиспользуемого дискового пространства. Недостатком такого подхода является нарушение строгой семантики MVCC.

23.1.3. Обновление статистики планировщика

Планировщик запросов в Postgres Pro, выбирая эффективные планы запросов, полагается на статистическую информацию о содержимом таблиц. Эта статистика собирается командой ANALYZE, которая может вызываться сама по себе или как дополнительное действие команды VACUUM. Статистика должна быть достаточно точной, так как в противном случае неудачно выбранные планы запросов могут снизить производительность базы данных.

Демон автоочистки, если он включён, будет автоматически выполнять ANALYZE после существенных изменений содержимого таблицы. Однако администраторы могут предпочесть выполнение ANALYZE вручную, в частности, если известно, что производимые в таблице изменения не повлияют на статистику по «интересным» столбцам. Демон же планирует выполнение ANALYZE в зависимости только от количества вставленных или изменённых строк; он не знает, приведут ли они к значимым изменениям статистики.

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

Как и процедура очистки для высвобождения пространства, частое обновление статистики полезнее для интенсивно изменяемых таблиц, нежели для тех таблиц, которые изменяются редко. Однако даже в случае часто изменяемой таблицы обновление статистики может не требоваться, если статистическое распределение данных меняется слабо. Как правило, достаточно оценить, насколько меняются максимальное и минимальное значения в столбцах таблицы. Например, максимальное значение в столбце timestamp, хранящем время изменения строки, будет постоянно увеличиваться по мере добавления и изменения строк; для такого столбца может потребоваться более частое обновление статистики, чем, к примеру, для столбца, содержащего адреса страниц (URL), которые запрашивались с сайта. Столбец с URL-адресами может меняться столь же часто, однако статистическое распределение его значений, вероятно, будет изменяться относительно медленно.

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

Подсказка

Хотя индивидуальная настройка частоты ANALYZE для отдельных столбцов может быть не очень полезной, смысл может иметь настройка детализации статистики, собираемой командой ANALYZE. Для столбцов, которые часто используются в предложениях WHERE, и имеют очень неравномерное распределение данных, может потребоваться более детальная, по сравнению с другими столбцами, гистограмма данных. В таких случаях можно воспользоваться командой ALTER TABLE SET STATISTICS или изменить значение по умолчанию параметра уровня БД default_statistics_target.

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

Подсказка

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

Подсказка

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

23.1.4. Обновление карты видимости

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

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

23.1.5. Предотвращение ошибок из-за зацикливания счётчика транзакций

В Postgres Pro семантика транзакций MVCC зависит от возможности сравнения номеров идентификаторов транзакций (XID): версия строки, у которой XID добавившей её транзакции больше, чем XID текущей транзакции, относится «к будущему» и не должна быть видна в текущей транзакции. Однако поскольку идентификаторы транзакций имеют ограниченный размер (32 бита), кластер, работающий долгое время (более 4 миллиардов транзакций) столкнётся с зацикливанием идентификаторов транзакций: счётчик XID прокрутится до нуля, и внезапно транзакции, которые относились к прошлому, окажутся в будущем — это означает, что их результаты станут невидимыми. Одним словом, это катастрофическая потеря данных. (На самом деле данные никуда не пропадают, однако если вы не можете их получить, то это слабое утешение.) Для того чтобы этого избежать, необходимо выполнять очистку для каждой таблицы в каждой базе данных как минимум единожды на два миллиарда транзакций.

Периодическое выполнение очистки решает эту проблему, потому что процедура VACUUM помечает строки как замороженные, указывая, что они были вставлены транзакцией, зафиксированной достаточно давно, так что эффект добавляющей транзакции с точки зрения MVCC определённо будет виден во всех текущих и будущих транзакциях. Обычные значения XID сравниваются по модулю 232. Это означает, что для каждого обычного XID существуют два миллиарда значений XID, которые «старше» него, и два миллиарда значений, которые «младше» него; другими словами, пространство значений XID циклично и не имеет конечной точки. Следовательно, как только создаётся версия строки с обычным XID, для следующих двух миллиардов транзакций эта версия строки оказывается «в прошлом», неважно о каком значении обычного XID идет речь. Если после двух миллиардов транзакций эта версия строки всё ещё существует, она внезапно окажется в будущем. Для того чтобы это предотвратить, в какой-то момент значение XID для старых версий строк должно быть заменено на FrozenTransactionId (заморожено) до того, как будет достигнута граница в два миллиарда транзакций. После получения этого особенного XID для всех обычных транзакций эти версии строк будут относиться «к прошлому», независимо от зацикливания, и, таким образом, эти версии строк будут действительны до момента их удаления, когда бы это ни произошло.

Примечание

В версиях PostgreSQL до 9.4 замораживание было реализовано как замена XID добавления строки специальным идентификатором FrozenTransactionId, который можно было увидеть в системной колонке xmin данной строки. В новых версиях просто устанавливается битовый флаг, а исходный xmin строки сохраняется для возможного расследования в будущем. Однако строки с xmin, равным FrozenTransactionId (2), можно по-прежнему встретить в базах данных, обновлённых (с применением pg_upgrade) с версий до 9.4.

Также системные каталоги могут содержать строки со значением xmin, равным BootstrapTransactionId (1), показывающим, что они были вставлены на первом этапе initdb. Как и FrozenTransactionId, этот специальный XID считается более старым, чем любой обычный XID.

Параметр vacuum_freeze_min_age определяет, насколько старым должен стать XID, чтобы строки с таким XID были заморожены. Увеличение его значения помогает избежать ненужной работы, если строки, которые могли бы быть заморожены в ближайшее время, будут изменены ещё раз, а уменьшение приводит к увеличению количества транзакций, которые могут выполниться, прежде чем потребуется очередная очистка таблицы.

VACUUM определяет, какие страницы таблицы нужно сканировать, анализируя карту видимости. Обычно при этой операции пропускаются страницы, в которых нет мёртвых версий строк, даже если в них могут быть версии строк со старыми XID. Таким образом, обычная команда VACUUM не будет всегда замораживать все версии строк, имеющиеся в таблице. Периодически VACUUM будет также производить агрессивную очистку, пропуская только те страницы, которые не содержат ни мёртвых строк, ни незамороженных значений XID или MXID. Когда VACUUM будет делать это, зависит от параметра vacuum_freeze_table_age: полностью видимые, но не полностью замороженные страницы будут сканироваться, если число транзакций, прошедших со времени последнего такого сканирования, оказывается больше чем vacuum_freeze_table_age минус vacuum_freeze_min_age. Если vacuum_freeze_table_age равно 0, VACUUM будет применять эту более агрессивную стратегию при каждом сканировании.

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

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

Фактический максимум для vacuum_freeze_table_age составляет 0.95 * autovacuum_freeze_max_age; большее значение будет ограничено этим пределом. Значение, превышающее autovacuum_freeze_max_age, не имело бы смысла, поскольку по достижении этого значения в любом случае вызывалась бы автоочистка для предотвращения зацикливания, а коэффициент 0.95 оставляет немного времени для того, чтобы запустить команду VACUUM вручную до того, как это произойдёт. Как правило, установленное значение vacuum_freeze_table_age должно быть несколько меньше autovacuum_freeze_max_age, чтобы оставленный промежуток был достаточен для выполнения в этом окне VACUUM по расписанию или автоочистки, управляемой обычной активностью операций удаления и изменения. Если это значение будет слишком близким к максимуму, автоочистка для предотвращения зацикливания будет выполняться, даже если таблица только что была очищена для высвобождения пространства, в то же время при небольшом значении будет чаще производиться агрессивная очистка.

Единственный минус увеличения autovacuum_freeze_max_agevacuum_freeze_table_age с ним) заключается в том, что подкаталоги pg_xact и pg_commit_ts в кластере баз данных будут занимать больше места, поскольку в них нужно будет хранить статус и (при включённом track_commit_timestamp) время фиксации всех транзакций вплоть до горизонта autovacuum_freeze_max_age. Для статуса фиксации используется по два бита на транзакцию, поэтому если в autovacuum_freeze_max_age установлено максимально допустимое значение в два миллиарда, то размер pg_xact может составить примерно половину гигабайта, а pg_commit_ts примерно 20 ГБ. Если по сравнению с объёмом вашей базы данных этот объём незначителен, тогда рекомендуется установить для autovacuum_freeze_max_age максимально допустимое значение. В противном случае установите значение этого параметра в зависимости от объёма, который вы готовы выделить для pg_xact и pg_commit_ts. (Значению по умолчанию, 200 миллионам транзакций, соответствует приблизительно 50 МБ в pg_xact и около 2 ГБ в pg_commit_ts.)

Уменьшение значения vacuum_freeze_min_age, с другой стороны, чревато тем, что команда VACUUM может выполнять бесполезную работу: замораживание версии строки — пустая трата времени, если эта строка будет вскоре изменена (и в результате получит новый XID). Поэтому значение этого параметра должно быть достаточно большим для того, чтобы строки не замораживались, пока их последующее изменение не станет маловероятным.

Для отслеживания возраста самых старых значений XID в базе данных команда VACUUM сохраняет статистику по XID в системных таблицах pg_class и pg_database. В частности, столбец relfrozenxid в записи для определённой таблицы в pg_class содержит граничное значение XID, с которым в последний раз выполнялась агрессивная очистка (VACUUM) этой таблицы. Все строки, добавленные транзакциями с более ранними XID, гарантированно будут заморожены. Аналогично столбец datfrozenxid в записи для базы данных в pg_database представляет нижнюю границу обычных значений XID, встречающихся в этой базе — он просто хранит минимальное из всех значений relfrozenxid для таблиц этой базы. Эту информацию удобно получать с помощью таких запросов:

SELECT c.oid::regclass as table_name,
       greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind IN ('r', 'm');

SELECT datname, age(datfrozenxid) FROM pg_database;

Столбец age показывает количество транзакций от граничного значения XID до XID текущей транзакции.

Обычно VACUUM сканирует только те страницы, которые изменялись после последней очистки, однако relfrozenxid может увеличиться только при сканировании всех страниц таблицы, включая те, что могут содержать незамороженные XID. Это происходит, когда возраст relfrozenxid превышает vacuum_freeze_table_age транзакций, когда VACUUM вызывается с указанием FREEZE, или когда оказывается, что очистку для удаления мёртвых версий строк нужно провести во всех ещё не замороженных страницах. Когда VACUUM сканирует в таблице каждую ещё не полностью замороженную страницу, значение age(relfrozenxid) в результате должно стать немного больше, чем установленное значение vacuum_freeze_min_age (больше на число транзакций, начатых с момента запуска VACUUM). Если по достижении autovacuum_freeze_max_age для таблицы ни разу не будет выполнена операция relfrozenxid, в скором времени для неё будет принудительно запущена автоочистка.

Если по какой-либо причине автоочистка не может вычистить старые значения XID из таблицы, система начинает выдавать предупреждающие сообщения, подобные приведённому ниже, когда самое старое значение XID в базе данных оказывается в сорока миллионах транзакций от точки зацикливания:

ПРЕДУПРЕЖДЕНИЕ:  база данных "mydb" должна быть очищена (предельное число транзакций: 39985967)
ПОДСКАЗКА:  Во избежание отключения базы данных выполните очистку (VACUUM) всей базы.

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

ОШИБКА:  база данных не принимает команды во избежание потери данных из-за зацикливания в БД "mydb"
ПОДСКАЗКА:  Остановите управляющий процесс (postmaster) и выполните очистку (VACUUM) базы данных в однопользовательском режиме.

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

  1. Разберите старые подготовленные транзакции. Их можно найти, проверив pg_prepared_xacts на наличие строк с большим значением age(transactionid). Такие транзакции следует фиксировать или отменять.
  2. Завершите длительные открытые транзакции. Их можно найти, проверив pg_stat_activity на наличие строк с большим значением age(backend_xid) или age(backend_xmin). Такие транзакции следует фиксировать или отменять, либо можно завершить сеанс с помощью pg_terminate_backend.
  3. Удалите все старые слоты репликации. Используйте pg_stat_replication, чтобы найти слоты с большим значением age(xmin) или age(catalog_xmin). Во многих случаях такие слоты создавались для репликации на серверы, которых уже нет или которые давно не работают. Если удалить слот для сервера, который всё ещё существует и может по-прежнему пытаться подключиться к этому слоту, возможно, эту реплику придётся пересоздать.
  4. Выполните VACUUM в целевой базе данных. Проще всего использовать VACUUM для всей базы данных. Чтобы сократить время выполнения, также можно вручную выполнить команды VACUUM для таблиц с самым старым relminxid. Не используйте VACUUM FULL в этом сценарии, поскольку для него требуется XID и, следовательно, произойдёт сбой, за исключением режима суперпользователя, где напротив будет обрабатываться XID и, таким образом, увеличится риск зацикливания счётчика идентификатора транзакции. Не используйте VACUUM FREEZE, поскольку при этом выполнится объём работы, который будет больше минимально необходимого для восстановления нормального функционирования.
  5. После восстановления нормальной работы проверьте, что автоочистка правильно настроена в целевой базе данных, чтобы избежать проблем в будущем.

Примечание

В более ранних версиях иногда было необходимо останавливать процесс postmaster и выполнять VACUUM для базы данных в однопользовательском режиме. В типичных сценариях в этом больше нет необходимости, поэтому по возможности следует избегать этих действий, поскольку они могут привести к сбою системы и повышают риски, так как отключают защиту от зацикливания идентификатора транзакции, предназначенную для предотвращения потери данных. Использовать однопользовательский режим в этом сценарии следует лишь при желании выполнить TRUNCATE или DROP ненужных таблиц, чтобы избежать необходимости выполнять для них VACUUM. Резерв в три миллиона транзакций позволяет администратору это сделать. За подробной информацией об использовании однопользовательского режима обратитесь к странице справки по postgres.

23.1.5.1. Мультитранзакции и зацикливание

Идентификаторы мультитранзакций используются для поддержки блокировки строк несколькими транзакциями одновременно. Поскольку в заголовке строки есть только ограниченное пространство для хранения информации о блокировках, в нём указывается «идентификатор множественной транзакции», или идентификатор мультитранзакции для краткости, когда строку блокируют одновременно несколько транзакций. Информация о том, какие именно идентификаторы транзакций относятся к определённой мультитранзакции, хранится отдельно в подкаталоге pg_multixact, а в поле xmax в заголовке строки сохраняется только идентификатор мультитранзакции. Как и идентификаторы транзакций, идентификаторы мультитранзакций исполнены в виде 32-разрядного счётчика и хранятся аналогично, что требует аккуратного управления их возрастом, очисткой хранилища и предотвращением зацикливаний. Существует отдельная область, в которой содержится список членов каждой мультитранзакции, где счётчики также 32-битные и требуют должного контроля.

Когда VACUUM сканирует какую-либо часть таблицы, каждый идентификатор мультитранзакции старее чем vacuum_multixact_freeze_min_age заменяется другим значением, которое может быть нулевым, идентификатором одиночной транзакции или новым идентификатором мультитранзакции. Для каждой таблицы в pg_class.relminmxid хранится самый старый возможный идентификатор мультитранзакции, всё ещё задействованный в какой-либо строке этой таблицы. Если это значение оказывается старее vacuum_multixact_freeze_table_age, выполняется агрессивная очистка. Как рассказывалось в предыдущем разделе, при агрессивной очистке будут пропускаться только те страницы, которые считаются полностью замороженными. Узнать возраст pg_class.relminmxid можно с помощью функции mxid_age().

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

В качестве меры защиты, агрессивное сканирование с целью очистки будет происходить для любой таблицы, возраст мультитранзакций которой больше, чем autovacuum_multixact_freeze_max_age. Агрессивное сканирование также будет выполняться чаще со всеми таблицами, начиная с имеющих старейшие мультитранзакции, если объём занятой области членов мультитранзакций превышает 2GB. Эти два варианта агрессивного сканирования осуществляются, даже если процесс автоочистки отключён.

Как и в случае с XID, если при автоочистке не удаётся удалить старые MXID из таблицы, система начнет выдавать сообщения с предупреждением, когда самые старые MXID базы данных достигнут сорока миллионов транзакций от точки зацикливания. И снова, как и в случае с XID, если эти предупреждения игнорировать, система перестанет генерировать новые MXID, если до точки зацикливания останется менее трёх миллионов транзакций.

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

  1. Запущенные транзакции и подготовленные транзакции можно игнорировать, если они точно не могут появиться в мультитранзакции.
  2. Информация о MXID не отображается напрямую в системных представлениях, таких как pg_stat_activity, однако поиск старых XID по-прежнему позволяет эффективно определить, какие транзакции вызывают проблемы с зацикливанием MXID.
  3. Когда заканчиваются XID, блокируются все пишущие транзакции, а когда заканчиваются MXID, блокируется только подмножество пишущих транзакций, а именно те, которые включают блокировки строк, требующие MXID.

23.1.6. Демон автоочистки

В Postgres Pro имеется не обязательная, но настоятельно рекомендуемая к использованию функция, называемая автоочисткой, предназначение которой — автоматизировать выполнение команд VACUUM и ANALYZE. Когда автоочистка включена, она проверяет, в каких таблицах было вставлено, изменено или удалено много строк. При этих проверках используются средства сбора статистики; поэтому автоочистка будет работать, только если параметр track_counts имеет значение true. В конфигурации по умолчанию автоочистка включена и соответствующие параметры имеют подходящие значения.

«Демон автоочистки» на самом деле состоит из нескольких процессов. Существует постоянный фоновый процесс, называемый процессом запуска автоочистки, который отвечает за запуск рабочих процессов автоочистки для всех баз данных. Этот контролирующий процесс распределяет работу по времени, стараясь запускать рабочий процесс для каждой базы данных каждые autovacuum_naptime секунд. (Следовательно, если всего имеется N баз данных, новый рабочий процесс будет запускаться каждые autovacuum_naptime/N секунд.) Одновременно могут выполняться до autovacuum_max_workers рабочих процессов. Если число баз данных, требующих обработки, превышает autovacuum_max_workers, обработка следующей базы начинается сразу по завершении первого рабочего процесса. Каждый рабочий процесс проверяет все таблицы в своей базе данных и в случае необходимости выполняет VACUUM и/или ANALYZE. Для отслеживания действий рабочих процессов можно установить параметр log_autovacuum_min_duration.

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

Для таблиц с relfrozenxid, устаревшим более чем на autovacuum_freeze_max_age транзакций, очистка выполняется всегда (это также применимо к таблицам, для которых максимальный порог заморозки был изменён через параметры хранения; см. ниже). В противном случае очистка таблицы производится, если количество кортежей, устаревших с момента последнего выполнения VACUUM, превышает «пороговое значение очистки». Пороговое значение очистки определяется как:

порог очистки = базовый порог очистки + коэффициент доли для очистки * количество кортежей

где базовый порог очистки — значение autovacuum_vacuum_threshold, коэффициент доли — autovacuum_vacuum_scale_factor, а количество кортежей — pg_class.reltuples.

Таблица также очищается, если число кортежей, добавленных после предыдущей очистки, превышает установленный порог очистки при добавлении, который определяется так:

порог очистки при добавлении = базовый порог очистки при добавлении + коэффициент доли для очистки при добавлении * количество кортежей

Базовый порог очистки при добавлении и коэффициент доли для очистки при добавлении определяются параметрами autovacuum_vacuum_insert_threshold и autovacuum_vacuum_insert_scale_factor, соответственно. При такой очистке часть страниц таблицы могут быть помечены как полностью видимые и могут быть также заморожены кортежи, что уменьшит объём работы, которую нужно будет проделать при следующей очистке. Для таблиц, в которых выполняются в основном операции INSERT и практически не выполняются UPDATE/DELETE, может иметь смысл уменьшить параметр таблицы autovacuum_freeze_min_age, так как это позволит замораживать кортежи раньше. Количество устаревших и добавленных кортежей получается от сборщика статистики; оно представляет собой приблизительное число, обновляемое после каждой операции UPDATE, DELETE и INSERT. (Точность не гарантируется, потому что при большой нагрузке часть информации может быть потеряна.) Если значение relfrozenxid для таблицы старее vacuum_freeze_table_age транзакций, производится агрессивная очистка с целью заморозить старые версии строк и увеличить значение relfrozenxid; в противном случае сканируются только страницы, изменённые после последней очистки.

Для выполнения сбора статистики используется аналогичное условие: пороговое значение, определяемое как:

порог анализа = базовый порог анализа + коэффициент доли для анализа * количество кортежей

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

В секционированных таблицах кортежи не хранятся напрямую и, следовательно, не обрабатываются автоочисткой. (Автоочистка обрабатывает секции таблицы так же, как и другие таблицы.) К сожалению, это означает, что автоочистка не запускает ANALYZE для секционированных таблиц, в результате чего создаются неоптимальные планы для запросов, ссылающихся на статистику секционированных таблиц. Эту проблему можно обойти, вручную запуская ANALYZE для секционированных таблиц при их первом заполнении, а также всякий раз, когда распределение данных в их секциях существенно меняется.

Автоочистка не обрабатывает временные таблицы. Поэтому очистку и сбор статистики в них нужно производить с помощью SQL-команд в обычном сеансе.

Используемые по умолчанию пороговые значения и коэффициенты берутся из postgresql.conf, однако их (и многие другие параметры, управляющие автоочисткой) можно переопределить для каждой таблицы; за подробностями обратитесь к разделу Параметры хранения. Если какие-либо значения определены через параметры хранения таблицы, при обработке этой таблицы действуют они, а в противном случае — глобальные параметры. За более подробной информацией о глобальных параметрах обратитесь к Разделу 18.10.

Когда выполняются несколько рабочих процессов, параметры задержки автоочистки по стоимости (см. Подраздел 18.4.4) «распределяются» между всеми этими процессами, так что общее воздействие на систему остаётся неизменным, независимо от их числа. Однако этот алгоритм распределения нагрузки не учитывает процессы, обрабатывающие таблицы с индивидуальными значениями параметров хранения autovacuum_vacuum_cost_delay и autovacuum_vacuum_cost_limit.

Рабочие процессы автоочистки обычно не мешают выполнению других команд. Если какой-либо процесс попытается получить блокировку, конфликтующую с блокировкой SHARE UPDATE EXCLUSIVE, которая удерживается в ходе автоочистки, автоочистка прервётся и процесс получит нужную ему блокировку. Конфликтующие режимы блокировок отмечены в Таблице 13.2. Однако если автоочистка выполняется для предотвращения зацикливания идентификаторов транзакций (т. е. описание запроса автоочистки в представлении pg_stat_activity заканчивается на (to prevent wraparound)), автоочистка не прерывается без ручного вмешательства.

Предупреждение

При частом выполнении таких команд, как ANALYZE, которые затребуют блокировки, конфликтующие с SHARE UPDATE EXCLUSIVE, может получиться так, что автоочистка не будет успевать завершаться в принципе.