Архитектура
Очистка
12
Авторские права
© Postgres Professional, 2020 год.
Авторы: Егор Рогов, Павел Лузанов
Использование материалов курса
Некоммерческое использование материалов курса (презентации,
демонстрации) разрешается без ограничений. Коммерческое
использование возможно только с письменного разрешения компании
Postgres Professional. Запрещается внесение изменений в материалы
курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу:
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за
любые повреждения и убытки, включая потерю дохода, нанесенные
прямым или непрямым, специальным или случайным использованием
материалов курса. Компания Postgres Professional не предоставляет
каких-либо гарантий на материалы курса. Материалы курса
предоставляются на основе принципа «как есть» и компания Postgres
Professional не обязана предоставлять сопровождение, поддержку,
обновления, расширения и изменения.
2
Темы
Очистка версий строк и анализ таблиц
Заморозка версий строк
3
Очистка и анализ
Как устроен процесс очистки
Анализ таблиц
Полная очистка
4
Очистка
Выполняется командой VACUUM
очищает ненужные версии строк в табличных страницах
(пропуская страницы, уже отмеченные в карте видимости)
очищает индексные записи, ссылающиеся на очищенные версии строк
обновляет карты видимости и свободного пространства
Обычно работает в автоматическом режиме
частота обработки зависит от интенсивности изменений в таблице
процессы autovacuum launcher и autovacuum worker
Как мы уже знаем, для реализации многоверсионности в таблице
накапливаются исторические версии строк, а в индексах — ссылки на
такие исторические версии. Когда версия строки уходит за «горизонт»
базы данных, ее можно удалить, освобождая место для других версий.
Очистка, выполняемая командой VACUUM, обрабатывает всю таблицу,
включая все созданные на ней индексы. При этом удаляются и
ненужные версии строк, и указатели на них в начале страницы.
Обычно очистка запускается не вручную, а работает в автоматическом
режиме в зависимости от количества изменений в таблицах. Чем чаще
изменяются строки, тем чаще автоочистка обрабатывает таблицу и ее
индексы.
При включенной автоочистке в системе всегда присутствует процесс
autovacuum launcher, который занимается планированием работы.
Реальную очистку выполняют процессы autovacuum worker, несколько
экземпляров которых могут работать параллельно.
Важно, чтобы процесс автоочистки был правильно настроен. Как это
делается, рассматривается в курсе DBA2 «Администрирование
PostgreSQL. Настройка и мониторинг». Если автоочистка не будет
срабатывать вовремя, таблицы будут разрастаться в размерах. Кроме
того, важно, чтобы в системе, в которой данные активно изменяются
(OLTP), не было длинных транзакций, удерживающих горизонт базы
данных и мешающих очистке.
5
xmaxxmin данные
101 42,BAR102
Пример
100 42,FOO
ctid ключ
(0,2) BAR
(0,1) 101
(0,3) BAZ
(0,2)
(0,1) FOO
102 42,BAZ0(0,3)
Рассмотрим пример.
На рисунке приведена ситуация до очистки. В табличной странице три
версии одной строки. Две из них неактуальны, не видны ни в одном
снимке и могут быть удалены.
В индексе есть три ссылки на каждую из версий строки.
6
xmaxxmin данные
Фаза очистки индексов
ctid ключ
(0,3) BAZ
требуется полное
сканирование всех индексов,
созданных на таблице
101 42,BAR102
100 42,FOO(0,1) 101
(0,2)
102 42,BAZ0(0,3)
Очистка обрабатывает данные постранично. Таблица при этом может
использоваться обычным образом и для чтения, и для изменения
днако одновременное выполнение для таблицы таких команд, как
CREATE INDEX, ALTER TABLE и др. будет невозможно — подробнее
см. тему «Обзор блокировок»).
Сначала таблица сканируется в поисках версий строк, которые можно
очистить. Для этого (с помощью карты видимости) читаются только те
страницы, в которых происходила какая-то активность.
Идентификаторы очищаемых строк запоминаются.
Затем начинается фаза очистки индексов. Каждый из индексов,
созданных на таблице полностью сканируется в поисках записей,
которые ссылаются на очищаемые версии строк (это еще один повод не
создавать лишних индексов!). Найденные индексные записи
очищаются.
После этой фазы в индексе уже нет ссылок на устаревшие версии
строк, но сами версии строк еще присутствуют в таблице.
7
xmaxxmin данные
Фаза очистки таблицы
ctid ключ
(0,1)
(0,3) BAZ
(0,2)
102 42,BAZ0(0,3)
при большом объеме
очищаемых данных
потребуется повторить
весь процесс несколько раз
Затем выполняется фаза очистки таблицы. На этой фазе таблица снова
сканируется, и из нее вычищаются версии строк с запомненными ранее
идентификаторами.
После этой фазы устаревших версий строк нет ни в индексах, ни
в таблице.
Заметим, что фазы очистки индексов и таблицы могут поочередно
выполняться несколько раз, если не хватает памяти для того чтобы
сразу запомнить все идентификаторы очищаемых строк. Такой
ситуации стараются избегать правильной настройкой автоочистки.
В процессе очистки таблицы обновляются и карта видимости (если на
странице не остается неактуальных версий строк), и карта свободного
пространства, в которой отражается наличие свободного места
в страницах.
9
Анализ
Выполняется командой ANALYZE
собирает статистику для планировщика
можно вместе с очисткой командой VACUUM ANALYZE
Обычно работает в автоматическом режиме
вместе с автоматической очисткой
Еще одна задача, которую обычно совмещают с очисткой, — анализ,
то есть сбор статистической информации для планировщика запросов.
Анализируется число строк в таблице, распределение данных по
столбцам и т. п. Более подробно это рассматривается в курсе QPT
«Оптимизация запросов».
Вручную анализ выполняется командой ANALYZE (только анализ) или
VACUUM ANALYZE (и очистка, и анализ).
Обычно очистка и анализ запускаются не вручную, а работают вместе
в автоматическом режиме. Частота автоанализа также зависит от
интенсивности изменений и допускает тонкую настройку.
10
Полная очистка
Перестраивает файлы и возвращает место файловой системе
Команда VACUUM FULL
полностью перестраивает таблицу и все ее индексы
монопольно блокирует таблицу и ее индексы на все время работы
Команда REINDEX
полностью перестраивает только индексы
монопольно блокирует индекс и запрещает запись в таблицу
вариант REINDEX CONCURRENTLY не мешает чтению и записи,
но выполняется дольше
Обычная очистка не решает всех задач по освобождению места.
Если таблица или индекс сильно выросли в размерах, то очистка не
приведет к сокращению числа страниц (и к уменьшению файлов).
Вместо этого внутри существующих страниц появятся «дыры», которые
могут быть использованы для вставки новых строк или изменения
существующих. Единственно исключение составляют полностью
очищенные страницы, находящиеся в конце файла — такие страницы
«откусываются» и возвращаются операционной системе.
Если размер файлов превышает некие разумные пределы, можно
выполнить команду VACUUM FULL для полной очистки. При этом
таблица и все ее индексы перестраиваются полностью с нуля,
а данные упаковываются максимально компактно.
Полная очистка не предполагает регулярного использования, так как
полностью блокирует всякую работу с таблицей (включая и выполнение
запросов к ней) на все время своей работы.
Команда REINDEX перестраивает индексы, не трогая при этом таблицу.
Фактически, VACUUM FULL использует эту команду для того, чтобы
перестроить индексы. Вариант этой команды с ключевым словом
CONCURRENTLY работает дольше, но не блокирует индекс и не
мешает чтению и обновлению данных.
11
Пример
(0,1)
(0,2)
(0,3)
xmaxxmin данные
101 42,BAR102
100 42,FOO
ctid ключ
(0,2) BAR
101
(0,3) BAZ
(0,1) FOO
102 42,BAZ0
На рисунке приведена та же самая ситуация до очистки, которую мы
уже рассматривали.
В табличной странице три версии одной строки. Две из них
неактуальны, не видны ни в одном снимке и могут быть удалены.
В индексе есть три ссылки на каждую из версий строки.
12
xmaxxmin данные
После полной очистки
ctid ключ
(0,1) BAZ
(0,1) 102 42,BAZ0
После выполнения полной очистки файлы данных индекса и таблицы
перестроены заново, нумерация версий строк изменилась. В страницах
отсутствуют «дыры», данные упакованы максимально плотно.
14
Заморозка
Проблема переполнения счетчика транзакций
Заморозка версий строк
15
Переполнение счетчика
3
пространство номеров транзакций (32 бита) закольцовано
половина номеров — прошлое, половина — будущее
1
2
Под номер транзакции в PostgreSQL выделено 32 бита. Это довольно
большое число (около 4 млрд номеров), но при активной работе
сервера оно вполне может быть исчерпано. Например при нагрузке
1000 транзакций в секунду это произойдет всего через полтора месяца
непрерывной работы.
Но мы говорили о том, что механизм многоверсионности полагается на
последовательную нумерацию транзакций — из двух транзакций
транзакция с меньшим номером считается начавшейся раньше.
Понятно, что нельзя просто обнулить счетчик и продолжить нумерацию
заново.
Почему под номер транзакции не выделено 64 бита — ведь это
полностью исключило бы проблему? Дело в том, что (как
рассматривалось в теме «Обзор внутреннего устройства») в заголовке
каждой версии строки хранятся два номера транзакций — xmin и xmax.
Заголовок и так достаточно большой, а увеличение разрядности
привело бы к его увеличению еще на 8 байт.
16
Переполнение счетчика
4
3
1
2
возраст транзакции
Поэтому вместо линейной схемы все номера транзакций закольцованы.
Для любой транзакции половина номеров «против часовой стрелки»
считается принадлежащей прошлому, а половина «по часовой
стрелке» — будущему.
Возрастом транзакции называется число транзакций, прошедших
с момента ее появления в системе (независимо от того, переходил ли
счетчик через ноль или нет).
17
Переполнение счетчика
процесс очистки выполняет заморозку старых версий строк
после заморозки номер транзакции xmin может быть использован заново
4
3
2
5
версия строки
заморожена
В такой закольцованной схеме возникает неприятная ситуация.
Транзакция, находившаяся в далеком прошлом (транзакция 1), через
некоторое время окажется в той половине круга, которая относится
к будущему. Это, конечно, нарушило бы правила видимости для версий
строк, созданных этой транзакцией, и привело бы к проблемам.
Чтобы не допустить путешествий из прошлого в будущее, процесс
очистки выполняет еще одну задачу. Он находит достаточно старые и
«холодные» версии строк (которые видны во всех снимках и изменение
которых уже маловероятно) и специальным образом помечает —
«замораживает» — их.
18
Переполнение счетчика
замороженные версии строк считаются «бесконечно старыми»
4
3
5
1
номер
транзакции
использован
заново
версия строки
видна в прошлом
Замороженная версия строки считается старше любых обычных
данных и всегда видна во всех снимках данных. При этом уже не
требуется смотреть на номер транзакции xmin, и этот номер может быть
безопасно использован заново. Таким образом, замороженные версии
строк всегда остаются в прошлом.
19
xmaxxmin данные
Пример
(0,1) 102 42,BAZ0
ctid ключ
(0,1) BAZ
На рисунке приведена ситуация до заморозки. В табличной странице
после очистки осталась только одна актуальная версия строки.
20
xmaxxmin данные
После заморозки
(0,1) 42,BAZ0
ctid ключ
(0,1) BAZ
замораживается только номер xmin
версии строк с xmax являются неактуальными и будут очищены
Для того чтобы пометить номер транзакции xmin как замороженный,
выставляется специальная комбинация информационных битов
в заголовке версии строки.
Заметим, что транзакцию xmax замораживать не нужно. Ее наличие
означает, что данная версия строки больше не актуальна. После того,
как она перестанет быть видимой в снимках данных, такая версия
строки будет очищена.
Важно, чтобы версии строк замораживались вовремя. Если возникнет
ситуация, при которой еще не замороженная транзакция рискует
попасть в будущее, PostgreSQL аварийно остановится. Это возможно
в двух случаях: либо транзакция не завершена и, следовательно, не
может быть заморожена, либо не сработала очистка.
При запуске сервера после аварийного останова транзакция будет
автоматически оборвана; дальше администратор должен вручную
выполнить очистку и после этого система сможет продолжить работать
дальше.
21
Заморозка вручную
VACUUM FREEZE
принудительная заморозка версий строк с xmin любого возраста
тот же эффект и при VACUUM FULL, CLUSTER
COPYWITH FREEZE
принудительная заморозка во время загрузки
таблица должна быть создана или опустошена в той же транзакции
могут нарушаться правила изоляции транзакции
Иногда бывает удобно управлять заморозкой вручную, а не дожидаться
автоочистки.
Заморозку можно вызвать вручную командой VACUUM FREEZE —
при этом будут заморожены все версии строк, без оглядки на возраст
транзакций. При перестройке таблицы командами VACUUM FULL или
CLUSTER все строки также замораживаются.
Данные можно заморозить и при начальной загрузке с помощью
команды COPY, указав параметр FREEZE. Для этого таблица должна
быть создана (или опустошена командой TRUNCATE) в той же
транзакции, что и COPY. Поскольку для замороженных строк действуют
отдельные правила видимости, такие строки будут видны в снимках
данных других транзакций в нарушение обычных правил изоляции (это
касается транзакций с уровнем Repeatable Read или Serializable), хотя
обычно это не представляет проблемы.
22
Итоги
Многоверсионность требует очистки старых версий строк
обычная очистка — как правило в автоматическом режиме
полная очистка — в экстренных случаях
Переполнение счетчика транзакций требует заморозки
Долгие транзакции и одномоментное обновление большого
объема данных приводят к проблемам
таблицы и индексы увеличиваются в размерах
избыточные сканирования индексов при очистке
23
Практика
1. В демонстрации было показано, что при обновлении всех
строк таблицы одной командой UPDATE объем данных
на диске увеличивается вдвое.
Повторите эксперимент, обновляя таблицу небольшими
пакетами строк, чередуя обновление с запуском очистки.
Как сильно увеличилась таблица на этот раз?
2. Сравните время, за которое удаляются все строки таблицы
при использовании команд DELETE и TRUNCATE.
1. С размером пакета можно экспериментировать; возьмите, например,
1 % от числа строк в таблице.
Для пакетной обработки нужно решить, как именно выбирать N строк
из имеющихся в таблице.
Учтите, что обработка какого-то пакета может (в принципе) завершиться
ошибкой. Важно, чтобы это не повлияло на возможность продолжить
обработку и довести ее до конца. Например, если выбирать строки по
диапазонам значений идентификатора, то как определить, какие пакеты
успешно обработаны, а какие — нет?
2. Воспользуйтесь командой psql \timing.