Регулярные задачи обслуживания базы данных
QHB, как и любому другому программному обеспечению для баз данных, для достижения оптимальной производительности требуется, чтобы определенные задачи выполнялись регулярно. Разбираемые здесь задачи являются обязательными, но они по своей сути периодически повторяющиеся и их можно легко автоматизировать с помощью стандартных инструментов, например скриптов cron. Ответственность за установку соответствующих скриптов и проверку их успешного выполнения лежит на администраторе баз данных.
Одной из очевидных задач обслуживания является создание на регулярной основе резервных копий данных. Без свежей резервной копии у вас нет шансов на восстановление системы после катастрофы (сбой диска, пожар, ошибочное удаление критической таблицы и т. д.). Механизмы резервного копирования и восстановления, доступные в QHB, подробно рассматриваются в главе Резервное копирование и восстановление.
Другой основной категорией задачи обслуживания является периодическая «очистка» базы данных. Эта задача рассматривается в разделе Регулярная очистка. С ней тесно связана задача обновления статистики, которая будет использоваться планировщиком запросов, как описано в подразделе Обновление статистики планировщика.
Другая задача, которая может потребовать периодического внимания, — это управление файлами журналов. Она рассматривается в разделе Обслуживание файлов журнала.
QHB нетребовательна в обслуживании по сравнению с другими системами управления базами данных. Тем не менее надлежащее внимание к данным задачам позволит обеспечить комфортный и продуктивный опыт работы с этой системой.
Регулярная очистка
Базы данных QHB требуют периодического обслуживания, известного
как очистка. Для многих установок достаточно, чтобы очистку выполнял процесс
«Автовакуум», описываемый в подразделе Процесс «Автовакуум». Возможно, для
получения наилучших результатов в вашей системе вам придется настроить описанные
там параметры автоматической очистки. Некоторые администраторы баз данных захотят
дополнить или заменить операции автоочистки управляемыми вручную командами VACUUM
,
которые обычно выполняются по расписанию с помощью скриптов cron. Чтобы
правильно настроить очистку вручную, важно понимать вопросы, рассматриваемые в
следующих нескольких подразделах. Администраторам, которые полагаются на
автоочистку, все равно рекомендуется ознакомится с этим материалом, чтобы лучше
понять как настроить процесс «Автовакуум».
Основы очистки
Команда QHB VACUUM
должна регулярно обрабатывать каждую
таблицу по нескольким причинам:
- Для восстановления или повторного использования дискового пространства, занятого измененными или удаленными строками.
- Для обновления статистики данных, используемой планировщиком запросов QHB.
- Для обновления карты видимости, которая ускоряет сканирование только по индексу.
- Для защиты от потери очень старых данных из-за зацикливания идентификаторов транзакций или мультитранзакций.
Каждая из этих причин требует выполнение операций 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. Как и 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
, которая успешно увеличила значение relfrozenxid
(обычно это последний агрессивный VACUUM
). Аналогично столбец 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
задается параметр VERBOSE,VACUUM
выводит различную статистику по таблице. В числе прочего сюда входит информация об увеличении значений relfrozenxid и relminmxid. Та же информация вносится в журнал сервера при протоколировании (которое управляется параметром log_autovacuum_min_duration) сообщений об операцииVACUUM
, выполненной процессом «Автовакуум».
Обычно команда VACUUM
сканирует только те страницы, которые были изменены с
момента последней очистки, но значение relfrozenxid может увеличиться только
после сканирования в таблице каждой страницы, которая может содержать
незамороженные XID. Это происходит, когда значение relfrozenxid превышает
vacuum_freeze_table_age транзакций, когда используется параметр FREEZE
команды VACUUM
или когда все страницы, которые еще не полностью заморожены,
требуют очистки для удаления неиспользуемых версий строк. Когда VACUUM
сканирует
в таблице каждую страницу, которая еще не полностью заморожена, она должна задать
для age(relfrozenxid) значение, чуть превышающее установленное в
vacuum_freeze_min_age (больше на количество транзакций, начатых с момента
запуска VACUUM
). VACUUM
установит для relfrozenxid значение, равное самому
старому XID, остающемуся в таблице, поэтому возможно, что итоговое значение будет
гораздо новее, чем требовалось. Если ни одна команда VACUUM
, увеличивающая
значение relfrozenxid, не обработает таблицу до достижения значения
autovacuum_freeze_max_age, то вскоре для этой таблицы будет принудительно
запущена автоочистка.
Если по какой-то причине автоочистке не удается удалить из таблицы старые XID, то когда самым старым XID базы данных останется сорок миллионов транзакций до точки зацикливания, система начнет выдавать предупреждающие сообщения вроде этого:
WARNING: database "mydb" must be vacuumed within 39985967 transactions
HINT: To avoid a database shutdown, execute a database-wide VACUUM in that database.
-- ПРЕДУПРЕЖДЕНИЕ: база данных «mydb» должна быть очищена в течение следующих 39985967 транзакций
-- СОВЕТ: Во избежание отключения базы данных выполните VACUUM для всей этой базы.
(Запуск 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 и очистите эту базу данных в однопользовательском режиме.
В этом состоянии любые уже выполняемые транзакции могут продолжаться, но запущены
могут быть лишь транзакции только для чтения. Операции, модифицирующие записи
базы данных или усекающие отношения, завершаться ошибкой. Команда VACUUM
по-прежнему может запускаться обычным образом. Вопреки тому, что утверждается в
совете, для восстановления нормального функционирования нет необходимости или даже
нежелательно останавливать qhbmaster или входить в однопользовательский режим.
Вместо этого выполните следующие шаги:
- Разрешите старые подготовленные транзакции. Их можно найти, проверив представление pg_prepared_xacts на наличие строк с большим значением age(transactionid). Такие транзакции следует зафиксировать или откатить.
- Завершите длительно выполняющиеся открытые транзакции. Их можно найти, проверив представление pg_stat_activity на наличие строк с большим значением age(backend_xid) или age(backend_xmin). Такие транзакции следует зафиксировать или откатить, или можно отменить сеанс с помощью функции pg_terminate_backend.
- Удалите все старые слоты репликации. Воспользуйтесь представлением pg_stat_replication, чтобы найти слоты с большим значением age(xmin) или age(catalog_xmin). Во многих случаях такие слоты были созданы для репликации на серверы, которые уже не существуют или уже давно выключены. Если вы удалите слот для сервера, который еще существует и может по-прежнему пытаться подключиться к этому слоту, возможно, эту реплику придется пересобрать.
- Выполните
VACUUM
в целевой базе данных. Самой простой операцией являетсяVACUUM
на уровне базы данных; чтобы сократить необходимые для этого время, также можно вручную выполнить командыVACUUM
для таблиц с самым старым relminxid. Не используйтеVACUUM FULL
в этом сценарии, поскольку ему требуется XID, и поэтому он завершится ошибкой, за исключение режима суперпользователя, в котором он вместо этого будет расходовать XID, тем самым повышая риск зацикливания идентификаторов транзакций. Не используйте иVACUUM FREEZE
, поскольку он выполнит больше минимального объема работы, необходимого для восстановления нормальной функционирования. - После восстановления нормального функционирования проверьте, что автоочистка правильно сконфигурирована в целевой базе данных, чтобы избежать проблем в будущем.
Примечание
В предыдущих версиях иногда приходилось останавливать qhbmaster и выполнятьVACUUM
для базы данных в однопользовательском режиме. В типичных сценариях в этом больше нет необходимости, и по возможности этой операции следует избегать, так как она предполагает выключение системы. Кроме того, это более рискованно, поскольку отключает защиту от зацикливания идентификаторов транзакций, предназначенную предотвращать потерю данных. Единственным оправданием использования однопользовательского режима в этом сценарии может служить желание выполнитьTRUNCATE
илиDROP
для ненужных таблиц, чтобы избежать необходимости выполнять для них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
, независимо от того, что их вызывает,
гарантированно способны повысить значение relminmxid для таблицы. В конце
концов, поскольку сканируются все таблицы во всех базах данных, а самые старые
из их значений мультитранзакций увеличиваются, пространство на диске, занятое
информацией о старых мультитранзакциях, может освободиться.
В качестве способа защиты агрессивное сканирование при очистке будет выполняться для любой таблицы, возраст мультитранзакций которой больше значения параметра autovacuum_multixact_freeze_max_age. Кроме того, если объем хранилища, занятого членами мультитранзакций, превышает 2 ГБ, агрессивное сканирование будет происходить чаще для всех таблиц, начиная с имеющих самые старые мультитранзакции. Оба этих вида агрессивного сканирования будут выполняться, даже если автоочистка номинально выключена.
Как и в случае с XID, если автоочистке не удается удалить из таблицы старые MXID, система начнет выдавать предупреждающие сообщения когда самым старым MXID базы данных останется сорок миллионов транзакций до точки зацикливания. И опять же, как в случае с XID, если эти предупреждения игнорируются, система откажется генерировать новые MXID, когда до зацикливания останется менее трех миллионов.
Когда заканчиваются MXID, нормальное функционирование можно восстановить практически тем же способом, что и когда заканчиваются XID. Выполните те же шаги, что и в предыдущем подразделе, но со следующими отличиями:
- Запущенные и подготовленные транзакции можно игнорировать, если они ни при каких обстоятельствах не могут появиться в мультитранзакции.
- Информация о MXID не видима напрямую в системных представлениях, таких как pg_stat_activity; однако поиск старых XID по-прежнему остается хорошим методом определения того, какие транзакции вызывают проблемы с зацикливанием MXID.
- Когда заканчиваются XID, блокируются все пишущие транзакции, а когда заканчиваются MXID, блокируется только подмножество пишущих транзакций, а именно те, которые включают блокировки строк, требующие MXID.
Процесс «Автовакуум»
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
, превышает «порог очистки», таблица очищается. Порог очистки
определяется как:
порог очистки = базовый порог очистки + коэффициент масштабирования очистки * число кортежей
где базовый порог очистки равен 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.
Пороговые значения по умолчанию и коэффициенты масштабирования берутся из файла 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. Вывод журнала бесценен при диагностике проблем.
Примечание
Журнал сервера может содержать конфиденциальную информацию, поэтому его следует защищать, как или где бы он ни хранился и куда бы не передавался. К примеру, некоторые операторы DDL могут содержать незашифрованные пароли или другие сведения аутентификации. В операторах, протоколируемых на уровне ERROR может выводиться исходный код SQL для приложений, а также могут содержаться некоторые фрагменты строк данных. Целевым предназначением журнала как раз и является запись данных, событий и связанной с ними информации, так что это не утечка и не дефект. Поэтому обязательно следует позаботиться о том, чтобы журналы сервера были видимыми только для лиц с соответствующими полномочиями.
Вывод журнала зачастую весьма объемен (особенно на более высоких уровнях отладки), поэтому хранить его бесконечно долго нежелательно. Вам понадобится настроить ротацию файлов журнала так, чтобы новые файлы создавались, а старые удалялись через разумный промежуток времени.
Если вы просто направите в файл stderr утилиты qhb, то получите в нем вывод журнала, но единственный способ опустошить этот файл — это остановить и перезапустить сервер. Это может быть приемлемо, если вы используете QHB в среде разработки, но мало какой производственный сервер сочтет такое поведение приемлемым.
Лучшим подходом является отправка вывода stderr сервера в какую-нибудь программу ротации журналов. Существует встроенное средство ротации журналов, которое можно использовать, установив в файле qhb.conf для параметра конфигурации logging_collector значение true . Параметры управления для этой программы описаны в подразделе Куда протоколировать. Этот подход также можно использовать для получения данных журнала в машиночитаемом формате CSV (comma-separated values, значения, разделенные запятыми).
Как вариант, можно использовать внешнюю программу ротации журналов, если вы уже
применяете такую с другим серверным программным обеспечением. Например, с
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.)
Обратите внимание, что все вышеописанные решения предусматривают запуск новых файлов журнала через настраиваемые интервалы, но не удаление старых, ставших бесполезными файлов. Возможно, вы захотите настроить пакетное задание для периодического удаления старых файлов журнала. Другая возможность — настроить программу ротации так, чтобы старые файлы журналов циклически перезаписывались.