Регулярные задачи обслуживания базы данных

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

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

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

Другая задача, которая может потребовать периодического внимания, — это управление файлами журналов. Она рассматривается в разделе Обслуживание файлов журнала.

QHB нетребовательна в обслуживании по сравнению с другими системами управления базами данных. Тем не менее надлежащее внимание к данным задачам позволит обеспечить комфортный и продуктивный опыт работы с этой системой.



Регулярная очистка

Базы данных QHB требуют периодического обслуживания, известного как очистка. Для многих установок достаточно, чтобы очистку выполнял процесс «Автовакуум», описываемый в подразделе Процесс «Автовакуум». Возможно, для получения наилучших результатов в вашей системе вам придется настроить описанные там параметры автоматической очистки. Некоторые администраторы баз данных захотят дополнить или заменить действия автоочистки управляемыми вручную командами VACUUM, которые обычно выполняются по расписанию с помощью скриптов cron. Чтобы правильно настроить очистку вручную, важно понимать вопросы, рассматриваемые в следующих нескольких подразделах. Администраторы, которые полагаются на автоочистку, могут ознакомится с этим материалом, чтобы лучше понять как настроить процесс «Автовакуум».


Основы очистки

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

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

Каждая из этих причин требует выполнение операций VACUUM с различной частотой и в разном объеме, что будет рассмотрено в следующих подразделах.

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

Команда VACUUM создает значительный объем трафика ввода/вывода, что может привести к снижению производительности других активных сеансов. Существуют параметры конфигурации, которые можно регулировать с целью снижения влияния фоновой очистки на производительность — см. подраздел Задержка очистки по стоимости.


Восстановление дискового пространства

В QHB команды UPDATE или DELETE не сразу удаляют старую версию обрабатываемой строки. Этот подход необходим для получения преимуществ многоверсионного управления параллельным доступом (MVCC; см. главу Управление параллельным доступом): нельзя удалять версию строки, пока она потенциально может быть видна другим транзакциям. Но в конце концов устаревшая или удаленная версия строки перестает интересовать все транзакции. Занимаемое ей пространство должно стать доступным для повторного использования новыми строками, чтобы избежать неограниченного роста потребности в дисковом пространстве. Это достигается с помощью команды 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.


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

При создании хороших планов запросов планировщик запросов QHB полагается на статистическую информацию о содержимом таблиц. Эта статистика собирается с помощью команды 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 вручную, чтобы поддерживать эту статистику актуальной.


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

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

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


Предотвращение ошибок зацикливания идентификаторов транзакций

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

Причина, по которой периодическая очистка решает проблему, состоит в том, что VACUUM помечает строки как замороженные, указывая на то, что они были добавлены транзакцией, зафиксированной достаточно давно, поэтому эффект добавляющей транзакции будет видим для всех текущих и будущих транзакций. Нормальные XID сравниваются с использованием арифметики модуля 232. Это означает, что для каждого нормального XID существует два миллиарда «более старых» и два миллиарда «более новых» XID; иными словами, пространство нормальных XID циклично и не имеет конечной точки. Таким образом, после создания версии строки с определенным нормальным XID эта версия окажется «в прошлом» для следующих двух миллиардов транзакций, независимо от того, о каком нормальном XID идет речь. Если после более двух миллиардов транзакций эта версия строки еще существует, она внезапно оказывается в будущем. Чтобы предотвратить это, QHB резервирует специальный XID, FrozenTransactionId, который не следует правилам сравнения нормальных XID и всегда считается старше любого нормального XID. Версии замороженных строк воспринимаются как получившие в качестве XID добавления FrozenTransactionId, так что для всех обычных транзакций они будут относиться к «прошлому», независимо от проблем с зацикливанием, и поэтому такие версии строк будут действительны до момента удаления, когда бы это ни произошло.

Примечание
На самом деле XID добавления строки не заменяется на FrozenTransactionId. Вместо этого просто устанавливается битовый флаг, а исходное значение системного столбца xmin добавленной строки сохраняется для возможной экспертизы в будущем.

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

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

VACUUM использует карту видимости, чтобы определить, какие страницы таблицы необходимо сканировать. Обычно он пропускает страницы, которые не имеют неиспользуемых версий строк, даже если на этих страницах все еще могут быть версии строк со старыми значениями XID. Поэтому обычные операции VACUUM не всегда замораживают каждую старую версию строки в таблице. Периодически VACUUM будет проводить агрессивную очистку, пропуская только те страницы, которые не содержат ни пустых строк, ни незамороженных значений XID или MXID. Параметр vacuum_freeze_table_age определяет, когда VACUUM выполнит следующие действия: сканируются все видимые, но не полностью замороженные страницы, если число транзакций, прошедших с момента последнего такого сканирования, больше, чем 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_age (вместе с vacuum_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 старше этого граничного 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 транзакций, когда используется параметр FREEZE команды VACUUM или когда все страницы, которые еще не полностью заморожены, требуют очистки для удаления неиспользуемых версий строк. Когда VACUUM сканирует каждую страницу в таблице, которая еще не полностью заморожена, она должна задать для age(relfrozenxid) значение, чуть превышающее установленное в vacuum_freeze_min_age (больше на количество транзакций, начатых с момента запуска VACUUM). Если ни одна команда VACUUM, увеличивающая relfrozenxid, не обработает таблицу до достижения значения autovacuum_freeze_max_age, то вскоре для этой таблицы будет принудительно запущена автоочистка.

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

WARNING:  database "mydb" must be vacuumed within 177009986 transactions
HINT:  To avoid a database shutdown, execute a database-wide VACUUM in "mydb".
-- ПРЕДУПРЕЖДЕНИЕ: база данных «mydb» должна быть очищена в течение следующих 177009986 транзакций
-- СОВЕТ: Во избежание отключения базы данных выполните VACUUM всей базы «mydb».

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

ERROR:  database is not accepting commands to avoid wraparound data loss in database "mydb"
HINT:  Stop the qhbmaster and vacuum that database in single-user mode.
-- ОШИБКА: база данных «mydb» не принимает команды во избежание потери данных вследствие зацикливания
-- СОВЕТ: Остановите qhbmaster и очистите эту базу данных в однопользовательском режиме.

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


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

Идентификаторы мультитранзакций используются для поддержки блокировки строк несколькими транзакциями. Поскольку в заголовке кортежа имеется только ограниченное пространство для хранения информации о блокировке, эта информация кодируется как «идентификатор множественной транзакции», или сокращенно идентификатор мультитранзакции, когда существует несколько транзакций, одновременно блокирующих строку. Информация о том, какие идентификаторы транзакций включены в любой конкретный идентификатор мультитранзакции, хранится отдельно в подкаталоге 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. Кроме того, если объем хранилища, занятого членами мультитранзакций, превышает 2 ГБ, агрессивное сканирование будет происходить чаще для всех таблиц, начиная с имеющих самые старые мультитранзакции. Оба этих вида агрессивного сканирования будут выполняться, даже если автоочистка номинально выключена.


Процесс «Автовакуум»

QHB имеет необязательную, но настоятельно рекомендуемую функцию, называемую автоочисткой, цель которой — автоматизировать выполнение команд 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, превышает «порог очистки», таблица очищается. Порог очистки определяется как:

vacuum threshold = vacuum base threshold + vacuum scale factor * number of tuples

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

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

vacuum insert threshold = vacuum base insert threshold + vacuum insert scale factor * number of tuples

где базовый порог очистки при добавлении равен 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 threshold = analyze base threshold + analyze scale factor * number of tuples

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

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

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

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

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

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

ПРЕДУПРЕЖДЕНИЕ!
Регулярно выполняемые команды, которые получают блокировки, конфликтующие с блокировкой SHARE UPDATE EXCLUSIVE (например ANALYZE), могут вообще не давать автоочистке завершаться.



Регулярная переиндексация

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

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

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

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

Во всех случаях можно легко и безопасно использовать команду REINDEX. Эта команда по умолчанию требует блокировку ACCESS EXCLUSIVE, поэтому зачастую лучше выполнять ее с параметром CONCURRENTLY, который требует только блокировку SHARE UPDATE EXCLUSIVE.



Обслуживание файлов журнала

Никогда не помешает сохранять куда-нибудь вывод журнала сервера баз данных, а не просто сбрасывать его в /dev/null. Вывод журнала бесценен при диагностике проблем. Однако этот вывод зачастую весьма объемен (особенно на более высоких уровнях отладки), поэтому нежелательно хранить его бесконечно долго. Вам понадобится настроить ротацию файлов журнала так, чтобы новые файлы создавались, а старые удалялись через разумный промежуток времени.

Если вы просто направите в файл stderr утилиты qhb, у вы получите в нем вывод журнала, но единственный способ опустошить этот файл — это остановить и перезапустить сервер. Это может быть приемлемо, если вы используете QHB в среде разработки, но мало какой производственный сервер сочтет такое поведение приемлемым.

Лучшим подходом является отправка вывода stderr сервера в какую-нибудь программу ротации журналов. Существует встроенное средство ротации журналов, которое можно использовать, установив в qhb.conf для параметра конфигурации logging_collector значение true . Параметры управления для этой программы описаны в подразделе Куда протоколировать. Этот подход также можно использовать для получения данных журнала в машиночитаемом формате CSV (значения, разделенные запятыми).

Как вариант, можно использовать внешнюю программу ротации журналов, если вы уже применяете такую с другим серверным программным обеспечением. Например, с QHB можно использовать утилиту rotatelogs, включенную в дистрибутив Apache. Один из способов сделать это — направить вывод stderr сервера в нужную программу. Если вы запускаете сервер с помощью qhb_ctl, то stderr уже будет перенаправлен на stdout, поэтому вам просто нужна команда с вертикальной чертой, например:

qhb_ctl start | rotatelogs /var/log/pgsql_log 86400

Вы можете объединить эти подходы, настроив программу logrotate для сбора файлов журналов, создаваемых встроенным сборщиком журналов QHB. В этом случае сборщик журналов определяет имена и расположение файлов журналов, а logrotate периодически архивирует эти файлы. При запуске ротации журнала logrotate нужно, чтобы приложение отправляло дальнейшие выходные данные в новый файл. Обычно это делается с помощью скрипта postrotate, отправляющего сигнал SIGHUP приложению, которое затем повторно открывает файл журнала. В QHB можно вместо этого запустить qhb_ctl с параметром logrotate. Когда сервер получает эту команду, он либо переключается на новый файл журнала, либо повторно открывает существующий файл, в зависимости от конфигурации ведения журнала (см. подраздел Куда протоколировать).

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

Другой производственный подход к управлению выводом журнала — отправить его в syslog и позволить этой службе заниматься ротацией файлов. Для этого установите в qhb.conf в параметре конфигурации log_destination значение syslog (для вывода только в syslog). Затем можно отправлять сигнал SIGHUP демону syslog всякий раз, когда вы хотите заставить его начать запись нового файла журнала. Если вы хотите автоматизировать ротацию журнала, программу logrotate можно настроить и для работы с файлами журналов из syslog.

Однако во многих системах служба syslog не очень надежна, особенно с большими сообщениями журнала; оно может обрезать или удалить сообщения именно тогда, когда они вам нужны больше всего. Кроме того, в Linux syslog будет сбрасывать все сообщения на диск, вызывая снижение производительности. (Для выключения синхронизации можно указать «-» в начале имени файла в файле конфигурации syslog.)

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