03 Занятие 03: Архитектура / Очистка
Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета. HTML source: dev2_03_arch_vacuum.html Archive: DEV2-handouts-16.zip Страниц PDF: 27
Page 1: Архитектура / Очистка
Авторские права
© Postgres Professional, 2017–2024 Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Игорь Гнатюк Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.
Page 2: Темы
- Очистка версий строк и анализ таблиц
- Заморозка версий строк
Page 3: Очистка и анализ
Как устроен процесс очистки
- Анализ таблиц
- Полная очистка
Page 4: Очистка
Выполняется командой VACUUM
- очищает ненужные версии строк в табличных страницах
- (пропуская страницы, уже отмеченные в карте видимости)
- очищает индексные записи, ссылающиеся на очищенные версии строк
- обновляет карты видимости и свободного пространства
- Обычно работает в автоматическом режиме
- частота обработки зависит от интенсивности изменений в таблице
- процессы autovacuum launcher и autovacuum worker
Комментарии преподавателя
Как мы уже знаем, при реализации многоверсионности в таблицах накапливаются исторические версии строк, а в индексах — ссылки на такие исторические версии. Когда версия строки уходит за горизонт базы данных, ее можно удалить, освобождая место для других версий.
Очистка, выполняемая командой VACUUM, обрабатывает всю таблицу, включая все созданные на ней индексы. При этом удаляются и ненужные версии строк, и указатели на них в начале страницы.
Обычно очистка запускается не вручную, а работает в автоматическом режиме, учитывая фактическое количество изменений в таблице. Чем активнее изменяются, добавляются и удаляются строки, тем чаще автоочистка обрабатывает таблицу и ее индексы.
При включенной автоочистке в системе всегда присутствует процесс autovacuum launcher, который занимается планированием работы процессов autovacuum worker, непосредственно выполняющих очистку. Несколько экземпляров таких рабочих процессов могут действовать параллельно.
Важно, чтобы процесс автоочистки был правильно настроен. Как это делается, рассматривается в курсе DBA2 «Администрирование PostgreSQL. Настройка и мониторинг». Если автоочистка не будет срабатывать вовремя, таблицы будут разрастаться в размерах. Также важно, чтобы в системе с OLTP-нагрузкой не было длинных транзакций, подолгу удерживающих горизонт базы данных и мешающих очистке.
https://postgrespro.ru/docs/postgresql/16/sql-vacuum
https://postgrespro.ru/docs/postgresql/16/routine-vacuuming
Page 5: Пример
{#source-figure-005 caption=“Source slide page 5: Пример”}
Комментарии преподавателя
Рассмотрим пример.
На рисунке приведена ситуация до очистки. В табличной странице три версии одной строки. Две из них неактуальны, не видны ни в одном снимке и могут быть удалены.
В индексе есть три ссылки: на каждую из версий строки.
Page 6: Фаза очистки индексов
{#source-figure-006 caption=“Source slide page 6: Фаза очистки индексов”}
Комментарии преподавателя
Очистка обрабатывает данные постранично. Таблица при этом может использоваться обычным образом и для чтения, и для изменения (однако одновременное выполнение для таблицы таких команд, как CREATE INDEX, ALTER TABLE и др. будет невозможно — подробнее см. тему «Блокировки»).
Сначала таблица сканируется в поисках версий строк, которые можно очистить. Для этого (с помощью карты видимости) читаются только те страницы, в которых есть неактуальные (мертвые) версии строк. Идентификаторы очищаемых строк запоминаются в оперативной памяти.
Затем начинается фаза очистки индексов. Каждый из индексов, созданных на таблице, полностью сканируется в поисках записей, которые ссылаются на очищаемые версии строк (это еще один повод не создавать лишних индексов!). Найденные индексные записи очищаются. Эту фазу могут выполнять несколько фоновых процессов параллельно, по одному процессу на каждый индекс.
После этой фазы в индексе уже нет ссылок на устаревшие версии строк, но сами версии строк еще присутствуют в таблице.
Page 7: Фаза очистки таблицы
{#source-figure-007 caption=“Source slide page 7: Фаза очистки таблицы”}
Комментарии преподавателя
Затем выполняется фаза очистки таблицы. На этой фазе таблица снова сканируется, и из нее вычищаются версии строк с запомненными ранее идентификаторами. Несмотря на то, что индексы на предыдущей фазе могут очищаться параллельно, при очистке самой таблицы параллелизма нет: она очищается одним (ведущим) процессом.
После этой фазы устаревших версий строк нет ни в индексах, ни в таблице.
Заметим, что фазы очистки индексов и таблицы могут поочередно выполняться несколько раз, если не хватает памяти для того чтобы сразу запомнить все идентификаторы очищаемых строк. Такой ситуации стараются избегать правильной настройкой автоочистки.
В процессе очистки таблицы обновляются и карта видимости (если на странице не остается неактуальных версий строк), и карта свободного пространства, в которой отражается наличие свободного места в страницах.
Page 8: Очистка
Создадим таблицу и запретим для нее автоматическую очистку (чтобы управлять моментом срабатывания).
Вставим в таблицу некоторое количество строк и узнаем размер ее основного слоя:
Теперь, чтобы еще раз напомнить про понятие горизонта, откроем в другом сеансе транзакцию с активным снимком данных.
Горизонт базы данных определяется этим снимком:
Обновим все строки таблицы.
Как изменится размер таблицы?
Page 9: Page 9
{#source-figure-009 caption=“Source slide page 9: Page 9”}
Размер основного слоя таблицы увеличился примерно вдвое. Выполним теперь очистку и попросим ее рассказать о том, что происходит:
Обратите внимание: tuples: 0 removed, 2000 remain — ни одна версия строк не может быть очищена, 1000 are dead but not yet removable — хотя две трети из них — неактуальные, removable cutoff: 818 — текущий горизонт, индекс не очищался. Завершим параллельную транзакцию…
…и снова вызовем очистку:
Теперь: removable cutoff: 819 — горизонт подвинулся, tuples: 1000 removed — очищены все неактуальные версии строк, index scan needed … 1000 dead item identifiers removed — очищены указатели на них из индекса. Посмотрим на размер:
Он не изменился, поскольку все свободное место находится внутри страниц — оно не возвращается операционной системе, но будет использовано для размещения новых версий строк.
Page 10: Анализ
Выполняется командой ANALYZE
- собирает статистику для планировщика
- можно вместе с очисткой командой VACUUM ANALYZE
- Обычно работает в автоматическом режиме
- вместе с автоматической очисткой
Комментарии преподавателя
Еще одна задача, которую обычно совмещают с очисткой, — анализ, то есть сбор статистической информации для планировщика запросов. Анализируется число строк в таблице, распределение данных по столбцам и т. п. Более подробно сбор статистики рассматривается в курсе QPT «Оптимизация запросов».
Вручную анализ выполняется командой ANALYZE (только анализ) или VACUUM ANALYZE (и очистка, и анализ).
Обычно очистка и анализ запускаются не вручную, а работают вместе в автоматическом режиме. Частота автоанализа также зависит от интенсивности изменений и допускает тонкую настройку.
https://postgrespro.ru/docs/postgresql/16/sql-analyze
Page 11: Полная очистка
Перестраивает файлы и возвращает место файловой системе
- Команда VACUUM FULL
- полностью перестраивает таблицу и все ее индексы
- монопольно блокирует таблицу и ее индексы на все время работы
- Команда REINDEX
- полностью перестраивает только индексы
- монопольно блокирует индекс и запрещает запись в таблицу
- вариант REINDEX CONCURRENTLY не мешает чтению и записи,
- но выполняется дольше
Комментарии преподавателя
Обычная очистка не решает всех задач по освобождению места. Если таблица или индекс сильно выросли в размерах, то очистка не приведет к сокращению числа страниц (и к уменьшению файлов). Вместо этого внутри существующих страниц появятся «дыры», которые могут быть использованы для вставки новых строк или изменения существующих. Единственное исключение составляют полностью очищенные страницы, находящиеся в конце файла — такие страницы «откусываются» и возвращаются операционной системе.
Если размер файлов превышает некие разумные пределы, можно выполнить команду VACUUM FULL для полной очистки. При этом таблица и все ее индексы перестраиваются полностью с нуля, а данные упаковываются максимально компактно.
Полная очистка не предполагает регулярного использования, так как полностью блокирует всякую работу с таблицей (включая и выполнение запросов к ней) на все время своего выполнения.
https://postgrespro.ru/docs/postgresql/16/sql-vacuum
Команда REINDEX перестраивает индексы, не трогая при этом таблицу. Фактически, VACUUM FULL использует эту команду для того, чтобы перестроить индексы. Вариант REINDEX CONCURRENTLY работает дольше, но не блокирует индекс и не мешает чтению и обновлению данных.
https://postgrespro.ru/docs/postgresql/16/sql-reindex
Page 12: Пример
{#source-figure-012 caption=“Source slide page 12: Пример”}
Комментарии преподавателя
На рисунке приведена та же самая ситуация до очистки, которую мы уже рассматривали.
В табличной странице три версии одной строки. Две из них неактуальны, не видны ни в одном снимке и могут быть удалены.
В индексе есть три ссылки на каждую из версий строки.
Page 13: После полной очистки
{#source-figure-013 caption=“Source slide page 13: После полной очистки”}
Комментарии преподавателя
После выполнения полной очистки файлы данных индекса и таблицы перестроены заново, нумерация версий строк изменилась. В страницах отсутствуют «дыры», данные упакованы максимально плотно.
Page 14: Полная очистка
Вызываем полную очистку таблицы:
Таблица и индекс теперь полностью перестроены. Как изменился размер?
Размер вернулся к первоначальному.
Page 15: Заморозка
Проблема переполнения счетчика транзакций
- Заморозка версий строк
Page 16: Переполнение счетчика
{#source-figure-016 caption=“Source slide page 16: Переполнение счетчика”}
пространство номеров транзакций (32 бита) закольцовано половина номеров — прошлое, половина — будущее
Комментарии преподавателя
Под номер транзакции в PostgreSQL выделено 32 бита. Это довольно большое число (около 4 млрд номеров), но при активной работе сервера оно вполне может быть исчерпано. Например при нагрузке 1000 транзакций в секунду это произойдет всего через полтора месяца непрерывной работы.
Но мы говорили о том, что механизм многоверсионности полагается на последовательную нумерацию транзакций — из двух транзакций транзакция с меньшим номером считается начавшейся раньше. Понятно, что нельзя просто обнулить счетчик и продолжить нумерацию заново.
Почему под номер транзакции не выделено 64 бита — ведь это полностью исключило бы проблему? Дело в том, что (как рассматривалось в теме «Многоверсионность») в заголовке каждой версии строки хранятся два номера транзакций — xmin и xmax. Заголовок и так достаточно большой, а увеличение разрядности привело бы к его увеличению еще на 8 байт.
Page 17: Переполнение счетчика
{#source-figure-017 caption=“Source slide page 17: Переполнение счетчика”}
Комментарии преподавателя
Поэтому вместо линейной схемы все номера транзакций закольцованы. Для любой транзакции половина номеров «против часовой стрелки» считается принадлежащей прошлому, а половина «по часовой стрелке» — будущему.
Возрастом транзакции называется число транзакций, прошедших с момента ее появления в системе (независимо от того, переходил ли счетчик через ноль или нет).
Page 18: Заморозка
{#source-figure-018 caption=“Source slide page 18: Заморозка”}
процесс очистки выполняет заморозку старых версий строк после заморозки номер транзакции xmin может быть использован заново
Комментарии преподавателя
В такой закольцованной схеме возникает неприятная ситуация. Транзакция, находившаяся в далеком прошлом (транзакция 1), через некоторое время окажется в той половине круга, которая относится к будущему. Это, конечно, нарушило бы правила видимости для версий строк, созданных этой транзакцией, и привело бы к проблемам.
При этом проблемы вызывают только номера транзакций в поле xmin, поскольку неактуальные версии с ненулевым xmax достаточно быстро удаляются при очистке (как только перестают быть видимыми в снимках данных).
Чтобы не допустить путешествий из прошлого в будущее, процесс очистки выполняет еще одну задачу. Он находит достаточно старые и «холодные» версии строк (которые видны во всех снимках и изменение которых уже маловероятно) и специальным образом помечает — «замораживает» — их.
Page 19: Заморозка
{#source-figure-019 caption=“Source slide page 19: Заморозка”}
замороженные версии строк считаются «бесконечно старыми»
Комментарии преподавателя
Замороженная версия строки считается старше любых обычных данных и всегда видна во всех снимках. При этом уже не требуется смотреть на номер транзакции xmin, и этот номер может быть безопасно использован заново. Таким образом, замороженные версии строк всегда остаются в прошлом.
https://postgrespro.ru/docs/postgresql/16/routine-vacuuming#VACUUM-FOR -WRAPAROUND
Page 20: Пример
{#source-figure-020 caption=“Source slide page 20: Пример”}
Комментарии преподавателя
На рисунке приведена ситуация до заморозки. В табличной странице после очистки осталась только одна актуальная версия строки.
Page 21: После заморозки
{#source-figure-021 caption=“Source slide page 21: После заморозки”}
замораживается только номер xmin версии строк с xmax являются неактуальными и будут очищены
Комментарии преподавателя
Для того чтобы пометить номер транзакции xmin как замороженный, выставляется специальная комбинация информационных битов в заголовке версии строки.
Важно, чтобы версии строк замораживались вовремя. Если возникнет ситуация, при которой еще не замороженная транзакция рискует попасть в будущее, PostgreSQL аварийно остановится. Это возможно в двух случаях: либо транзакция не завершена и, следовательно, не может быть заморожена, либо не сработала очистка.
При запуске сервера после аварийного останова транзакция будет автоматически оборвана; дальше администратор должен вручную выполнить очистку и после этого система сможет продолжить работать дальше.
Page 22: Заморозка вручную
VACUUM FREEZE
- принудительная заморозка версий строк с xmin любого возраста
- тот же эффект и при VACUUM FULL, CLUSTER
- COPY … WITH FREEZE
- принудительная заморозка во время загрузки
- таблица должна быть создана или опустошена в той же транзакции
- могут нарушаться правила изоляции транзакции
Комментарии преподавателя
Иногда бывает удобно управлять заморозкой вручную, а не дожидаться автоочистки.
Заморозку можно вызвать вручную командой VACUUM FREEZE — при этом будут заморожены все версии строк, без оглядки на возраст транзакций. При перестройке таблицы командами VACUUM FULL или CLUSTER все строки также замораживаются.
https://postgrespro.ru/docs/postgresql/16/sql-vacuum
Данные можно заморозить и при начальной загрузке с помощью команды COPY, указав параметр FREEZE. Для этого таблица должна быть создана (или опустошена командой TRUNCATE) в той же транзакции, что и COPY. Поскольку для замороженных строк действуют отдельные правила видимости, такие строки будут видны в снимках данных других транзакций в нарушение обычных правил изоляции (это касается транзакций с уровнем Repeatable Read или Serializable), хотя обычно это не представляет проблемы.
https://postgrespro.ru/docs/postgresql/16/sql-copy
Page 23: Итоги
Многоверсионность требует очистки старых версий строк
- обычная очистка — как правило в автоматическом режиме
- полная очистка — в экстренных случаях
- Переполнение счетчика транзакций требует заморозки
- Долгие транзакции и одномоментное обновление большого объема данных приводят к проблемам
- таблицы и индексы увеличиваются в размерах
- многократно сканируются индексы при очистке
Page 24: Практика
- В демонстрации было показано, что при обновлении всех строк таблицы одной командой UPDATE объем данных на диске увеличивается вдвое. Проделайте сходный эксперимент, но обновляйте таблицу небольшими пакетами строк, чередуя обновление с запуском очистки. Как сильно увеличилась таблица на этот раз? 2. Сравните время, за которое удаляются все строки таблицы при использовании команд DELETE и TRUNCATE.
Комментарии преподавателя
- С размером пакета можно экспериментировать; возьмите, например, 1 % от числа строк в таблице.
Для пакетной обработки нужно решить, как именно выбирать N строк из имеющихся в таблице.
Учтите, что обработка какого-то пакета может (в принципе) завершиться ошибкой. Важно, чтобы это не повлияло на возможность продолжить обработку и довести ее до конца. Например, если выбирать строки по диапазонам значений идентификатора, то как определить, какие пакеты успешно обработаны, а какие — нет?
Page 25: 1. Пакетное обновление
Создадим таблицу, сходную с той, что использовалась в демонстрации, и наполним ее данными:
Для упрощения задачи мы отказались от столбца с внешним хранением (TOAST), а дополнительный столбец processed пригодится нам для пакетного обновления. Размер таблицы:
Еще раз проверим показанное в демонстрации. Обновляем все строки:
Размер таблицы после обновления:
Таблица увеличилась в два раза. Теперь нам надо очистить таблицу, поэтому заодно засечем время выполнения команды TRUNCATE:
Заново вставляем те же строки:
Page 26: Page 26
Для того чтобы контролировать обработанные строки, будем использовать столбец processed. Для удобства создадим функцию, выполняющую обновление пакета строк, причем размер пакета задается параметром:
Предложение FOR UPDATE SKIP LOCKED будет подробнее рассмотрено в теме «Блокировки». Такой способ даст необходимое количество необработанных строк (из числа имеющихся в таблице), причем выполнение не будет задержано другими обновлениями, которые могут выполняться в это же время. Удобно также, что этот способ не требует вычисления каких-либо диапазонов. Обновление просто надо продолжать до тех пор, пока в таблице не останется необработанных строк. Поскольку выполнение команды VACUUM не допускается в блоке транзакции, воспользуется средствами bash, чтобы выполнить нужные команды:
Проверим, что все строки обработаны:
Размер таблицы после пакетного обновления:
Таблица увеличилась чуть больше, чем на 2%.
2. Удаление строк
Удаление всех строк командой DELETE выполняется значительно дольше, чем TRUNCATE. Кроме того, после DELETE в таблице остаются удаленные версии строк, которые должны быть очищены.
Page 27: Page 27
По принципу работы команда TRUNCATE напоминает VACUUM FULL: она заменяет файл данных новым (пустым) файлом, и для этого полностью блокирует работу с таблицей для других транзакций.