Один из важнейших аспектов, за которым должен следить каждый администратор баз данных PostgreSQL — процесс поддержания «здоровья» базы данных vacuum / autovacuum, удаляющий из памяти неактуальные версии табличных строк и сбрасывающий счётчик транзакций.  Однако не все советы и шаблоны действий, актуальные для «ванильного» PostgreSQL, в полной мере применимы для DWH с массивно-параллельной архитектурой (MPP — massive parallel processing) на основе ядра PostgreSQL, таких как Greenplum, Amazon Redshift и Citus. При этом с увеличением размера хранилища и нагрузки на БД растут и масштабы проблем с отслеживанием состояния счётчика транзакций, «раздувания» (bloat) таблиц и индексов за счёт «мёртвых» строк. В процессе подготовки к миграции с классического кластера PostgreSQL на распределённое хранилище данных DBA должны учитывать в т. ч. и отличия в подходе к очистке БД.   В этой статье я попытаюсь систематизировать особенности vacuum / autovacuum, с которыми сталкиваются администраторы MPP-РСУБД. В первом приближении, базовые инструкции для решения типичных проблем с wraparound (переполнение 32-битного счётчика транзакций) и «раздуванием» таблиц в немодифицированном PostgreSQL можно свести к нескольким простым пунктам: Не отключать autovacuum. В низконагруженных БД в часы наименьшей нагрузки стоит время от времени запускать пересборку таблиц через VACUUM FULL. В высоконагруженных БД для уплотнения хранения таблиц вместо VACUUM FULL лучше использовать неблокирующие инструменты (pg_repack, pg_squeeze, pgcompacttable и т. п.). Статичные таблицы (справочники и т. п.) полезно кластеризовать. Не нарушать пункт 1 и следить, что ничего не мешает автовакууму – настройки репликации, длинные/зависшие транзакции, «битые» страницы на диске (при выключенных контрольных суммах). Соблюдение этих пунктов позволит: Никогда не задумываться о том, как спасать БД из single-user режима (защитная мера, которая включается при переполнении счётчика транзакций);  Эффективно использовать место на жёстком диске и буферный кэш. Подробный обзор про инструментарий очистки PostgreSQL есть в этом цикле статей от PostgresPro – дальнейший технический обзор подразумевает, что читатель знаком с общей архитектурой PostgreSQL и механизмом работы VACUUM. Прежде чем переходить к особенностям VACUUM в MPP-РСУБД, проведём краткий обзор рассматриваемых систем, чтобы лучше понимать сценарии их использования и связь с базовым PostgreSQL: open-source MPP-РСУБД; доступна к standalone-установке на Unix-системах; крупнейшие контрибьюторы – VMWare Tanzu (экс-Pivotal), Arenadata, Alibaba; актуальная версия (6.22.1) базируется на основе PostgreSQL 9.4.26; создана для масштабирования OLAP DWH. проприетарная MPP-РСУБД, часть Amazon Web Services; актуальная версия (1.0.12103) базируется на основе PostgreSQL 8.0.2; создана для масштабирования OLAP DWH и интеграции с BI-инструментарием Amazon. open-source расширение, превращающее PostgreSQL в MPP-РСУБД; доступно к установке на Unix-системах и в облаке Microsoft Azure; крупнейший контрибьютор – Citus Data, с 2019 года принадлежит Microsoft; актуальная версия (11.1.3) доступна к установке на любую версию PostgreSQL от 12.0; создано для масштабирования OLTP БД и простой real-time аналитики. Можно также вспомнить проект Postgres-XL – ещё одна ранее популярная open-source MPP-вариация PostgreSQL. Официальных объявлений о прекращении разработки не было, однако последний релиз вышел осенью 2018 года. Обзор особенностей очистки MPP-РСУБД начнём с Greenplum, т.к. он более всего отличается от оригинального PostgreSQL. В Greenplum можно выделить 3 основных способа хранения табличных данных: Heap-таблицы со строковым хранением; Append-Optimized (AO)-таблицы со строковым хранением; AO-таблицы с колончатым хранением                  В колоночных AO-таблицах каждый     столбец хранится в отдельном файле. Основное техническое отличие таких     таблиц от строковых AO-таблиц – наличие дополнительных метаданных,     позволяющих сопоставлять сегментный файл с колонкой.  В старых версиях Greenplum такие таблицы назывались Append-Only и не     поддерживали операции UPDATE/DELETE. В актуальных версиях эти опции добавили с     помощью вспомогательных таблиц, метод переименовали в Append-Optimized. Метод доступа Heap перекочевал в Greenplum из PostgreSQL практически без изменений (разве что размер страницы увеличен с 8 до 32 кбайт), и по сей день остаётся дефолтным методом при создании новых таблиц в Greenplum. Логично, что и VACUUM для heap-таблиц работает ровно так же, как в и PostgreSQL. Если взять актуальный Greenplum 6, то команда VACUUM имеет ровно тот же набор опций, что и в PostgreSQL 9.4 (т.е. только VERBOSE, FREEZE, ANALYZE и FULL). Все настройки управления vacuum по кластеру и по конкретным таблицам соответствуют своим аналогам из PG. AO-таблицы состоят из страниц произвольной длины — примерно как TOAST — таблицы в базовом PostgreSQL; только AO-таблицы могут иметь ещё и собственные TOAST —  таблицы. Все метаданные, обеспечивающие транзакционность, хранятся в своеобразной вариации карты видимости (в отличие от PG, это не просто файл на диске, а доступная heap — таблица с именем вида pg_aoseg.pg_aovisimap_<oid_родительской_AO-таблицы>). Сегментные файлы, в которых хранятся страницы AO — таблиц, не поддерживают удаление данных. При выполнении команд UPDATE/DELETE старая версия строки помечается в карте видимости как удалённая, а новая версия (если есть) пишется в конец текущего сегментного файла. Соответственно, и VACUUM не может вычищать «мёртвые» строки из этих файлов – вместо этого все активные версии строк копируются в конец нового сегментного файла, а старый файл удаляется целиком (на этой стадии на таблицу на короткое время навешивается Access Exclusive Lock).  При этом идентификаторы кортежей (ctid) меняются и приходится перезаписывать ссылки на них во всех индексах таблицы – с одной стороны, это дополнительная работа, которая не требуется для heap-таблиц; с другой стороны, в MPP-БД использование индексов сведено к минимуму, и эта особенность редко действительно влияет на время выполнения VACUUM. Другой незначительный побочный эффект – даже для обычного VACUUM требуется свободное место на диске, чтобы разместить новый сегмент.  VACUUM для AO-таблиц управляется отдельным набором настроек: gp_appendonly_compaction – включает/отключает VACUUM для AO-таблиц; gp_appendonly_compaction_threshold – пороговое количество «мёртвых» кортежей в сегментном файле, после которого VACUUM начнёт чистить этот файл (по умолчанию, 10%). Если кратко – автовакуума нет. Одна из первых проблем, с которыми исторически столкнулись разработчики всех MPP-РСУБД на базе PostgreSQL – что делать с счётчиками транзакций на каждом отдельном узле? Количество транзакций на разных сегментах, как правило, отличается друг от друга, в зависимости от нагрузки, и не предусмотрен никакой механизм для синхронизации/объединения счётчиков между серверами. В Greenplum 4 в своё время autovacuum работал точно так же, как и в «ванильном» PostgreSQL – каждый сегмент самостоятельно запускал autovacuum, как того требовали настройки. В результате, из-за этого проседала производительность всего кластера Greenplum (по известному закону «производительность запроса в Greenplum = производительность самого медленного сегмента, на котором выполняется запрос»). Начиная с Greenplum 5 это поведение изменили —  процесс autovacuum теперь есть только для БД template0 (чистая БД с дефолтными настройками для клонирования, как в PostgreSQL). Во всех остальных БД автовакуума нет совсем и включить его нельзя. Кстати, любимые многими PostgreSQL DBA инструменты для автоматизированного и неблокирующего vacuum full (pg_repack, pgcompacttable, pg_squeeze) в Greenplum также недоступны. Подразумевается, что администраторы Greenplum должны самостоятельно настраивать и планировать ручной vacuum на каждом сегменте, исходя из профиля нагрузки на сегменты и кластер в целом, а также ETL-процессов, чтобы минимизировать просадки производительности сегментов. 16 декабря 2022 вышла первая бета-версия Greenplum 7. Среди её ключевых особенностей в Release Notes упомянуто, что autovacuum теперь будет работать по всему кластеру, а не только в шаблонных БД. Остаётся только надеяться, что этот функционал доживёт до релиза. Vacuum full / reindex / cluster CLUSTER для heap-таблиц работает аналогично «ванильному» PostgreSQL, а для AO-таблиц не поддерживается (у них много сложностей с индексированием, проще просто забыть про индексы для AO-таблиц). REINDEX также полностью идентичен команде из PostgreSQL, только нельзя забывать про версию ядра – в 9.4.26 ещё не было опций CONCURRENTLY (без блокировки таблицы) и TABLESPACE (перенос в другое табличное пространство). Про VACUUM FULL в разделе «Best Practices» на сайте VMWare есть лаконичная рекомендация никогда его не использовать: Но совсем без него обойтись, конечно, не получится. Самый распространённый сценарий использования Greenplum – OLAP DWH на сотни/тысячи таблиц, зачастую с десятками/сотнями партиций, и примерно таким же количеством представлений-витрин. В результате, системный каталог на каждом сегменте кластера может очень сильно распухать, и в первую очередь – таблицы pg_class (список объектов в БД) и pg_attribute (список столбцов в таблицах).  Дополнительные проблемы может также доставить утилита gpload. Это один из встроенных инструментов Greenplum для импорта данных из внешних источников, процедура загрузки конфигурируется с помощью простого YAML-файла. По умолчанию, в процессе создаётся промежуточная внешняя таблица (external table), из которой данные потом перекладываются в целевую таблицу; внешняя таблица при этом удаляется. При импорте больших объёмов данных микропорциями можно получить раздутый системный каталог ещё до начала работы с БД. Методы решения: опция REUSE_TABLES: true в конфигурации gpload, использовать другие инструменты импорта (gpfdist, pxf, copy и т. д.) Чтобы производительность чтения метаданных не проседала, необходимо время от времени запускать VACUUM FULL для таблиц системного каталога, держа в уме несколько важных моментов: Чистка системного каталога на сегменте кластера фактически эквивалентна остановке этого сегмента. Никогда, ни при каких обстоятельствах нельзя прерывать VACUUM FULL системного каталога, если он уже запущен. Это может полностью сломать сервис Greenplum на сегменте – известен кейс, когда после остановки очистки таблица pg_class осталась в несогласованном состоянии, и на сегмент больше нельзя было добавлять новые объекты. Надо помнить про жёсткий лимит на объём «мёртвых» строк для VACUUM – не более 1 ГБ указателей строк на процесс. Эта величина «прибита гвоздями» в исходниках ядра PostgreSQL, и в Greenplum её перенесли без изменения. Вот только в гигантских DWH на базе Greenplum гораздо проще добраться до этого предельного значения, если своевременно не выполнять VACUUM. Например, если в pg_attributes накопилось свыше 1 ГБ указателей на «мёртвые» строки, то VACUUM FULL этой таблицы упадёт с ошибкой, что может привести к последствиям из п. 2. Для AO-таблиц большой пользы от выполнения VACUUM FULL нет, т.к. он полностью аналогичен обычному VACUUM с параметром gp_appendonly_compaction_threshold равным 0 (т.е. пересобираются все сегментные файлы, независимо от количества «мёртвых» кортежей). Кроме того, VACUUM FULL для AO-таблиц не сжимает связанные индексы как в случае с heap-таблицами.   Хотя Redshift и построен на базе доисторического ядра PostgreSQL 8.0, на сегодняшний день их пути разошлись уже достаточно далеко. В Redshift не реализованы или реализованы иначе ряд ключевых особенностей даже опорной для него версии PostgreSQL, т.к. они не требуются для архитектуры данной РСУБД.   Главные отличительные особенности хранения данных в Amazon Redshift – колоночное хранение (блоки по 1 МБ) и принудительная сортировка значений (подобные принципы используются также в Snowflake). У каждой таблицы обязательно должен быть определён набор ключей сортировки, причём сделать это можно двумя методами – compound (с иерархией ключей) и interleaved (все ключи равноправны; не более 8 ключей сортировки). Исторически сложилось, что процесс обязательной автоматической сортировки разработчики Amazon интегрировали именно в операцию VACUUM. Синтаксис вызова очистки и происходящие процессы заметно отличаются от стандарта PostgreSQL: VACUUM [ FULL | SORT ONLY | DELETE ONLY | REINDEX | RECLUSTER ]  [ [ table_name ] [ TO threshold PERCENT ] [ BOOST ] ] Дефолтный вариант —  полная очистка, т.е. команды VACUUM и VACUUM FULL эквивалентны. Процедура сначала вычищает «мёртвые» кортежи и сжимает таблицу, кратковременно блокируя её, а затем сортирует строки (уже без блокировки), если в таблице отсортированы менее 95% строк. Можно указать опцию TO threshold PERCENT, чтобы изменить данный порог на другое значение в процентах. Отсутствие полной блокировки таблицы на всё время VACUUM FULL в Redshift достигается за счёт добавления новых блоков к каждому столбцы таблицы на каждом сегменте в кластере, где хранится данная таблица – подобно Append-Optimized-таблицам в Greenplum, описанным ранее. Если столбцов и сегментов в кластере много, то могли бы возникать ситуации, когда очистка добавляла бы больше новых блоков, чем сжимала. Поэтому в Redshift выполняется предварительный расчёт обработки блоков памяти, и если оверхед будет больше потенциального сжатия, то операция очистки «мёртвых» кортежей пропускается.   Опции SORT ONLY и DELETE ONLY ограничивают работу VACUUM только сортировкой или только очисткой и сжатием, соответственно (практически никогда не применяется на практике). В режиме RECLUSTER происходит только сортировка неотсортированной части таблицы, удаление «мёртвых» кортежей не выполняется. Этот режим подходит для быстрой обработки новых строк после INSERT в большие таблицы. Самая требовательная к ресурсам операция — VACUUM REINDEX. Эта процедура применяется только к таблицам с методом сортировки interleaved (чередующиеся ключи) —сначала производится полное сканирование таблицы с статистическим анализом распределения всех ключей сортировки, а затем выполняется стандартный для Redshift VACUUM FULL. Опция BOOST блокирует таблицу от записи и выделяет дополнительные системные ресурсы для очистки, ускоряя её. BOOST не работает с REINDEX и игнорируется с DELETE ONLY. Достаточно долгое время (2012 - 2019) в Redshift отсутствовали инструменты автоматизации очистки. Затем в течение 2019 года были последовательно добавлены фоновые процессы для VACUUM DELETE ONLY, ANALYZE и VACUUM SORT ONLY. Можно только догадываться о том, как именно они настроены и реализованы, т. к. описание этих процессов в документации отсутствует, и нет способов их отключить или изменить параметры. Позиция Amazon заключается в том, что обслуживание БД теперь считается полностью автоматизированным, и администраторам/разработчикам не следует в него вмешиваться; для оперативного сглаживания аномалий оставили ручной VACUUM. Можно отметить, что в Redshift отсутствует проблема с wraparound, т.к. используются 64-битные номера транзакций. Как упоминалось в начале статьи, Citus является расширением к «ванильному» PostgreSQL, поэтому в нём отсутствуют какие бы то ни было глубокие переработки ядра. Шардирование таблиц между сегментами кластера реализовано с помощью нативной потоковой репликации PostgreSQL. Также добавлен новый метод доступа для колоночного хранения данных. Очистка распределённых таблиц не отличается от стандартных процессов в PostgreSQL, но запускается параллельно сразу на всех сегментах кластера, где располагается данная таблица. В случае autovacuum на каждый сегмент выделяется отдельный worker из пула. Проблему с рассинхронизацией транзакций на сегментах кластера в Citus частично решают с помощью двухфазного коммита (через prepared transactions и обратную связь между сегментами). В результате получилось сохранить работу autovacuum (в отличие от Greenplum) с общим счётчиком транзакций на всех сегментах кластера, но не обеспечивается консистентность чтения в распараллеленных транзакциях с изменением данных. Колоночные таблицы в Citus имеют целый ряд ограничений, по сравнению со стандартными heap-таблицами или колоночными-таблицами в Greenplum или Redshift. Тем не менее, с их помощью всё равно можно добиваться хороших уровней компрессии данных и оптимизации запросов. С точки зрения очистки данных нужно выделить несколько моментов: Колоночные таблицы работают только в режиме append-only – «мёртвые» кортежи в нормальных условиях не генерируются, т.к. нет операций UPDATE и DELETE. Побочный эффект append-only – «мёртвые» кортежи всё равно могут появляться, если транзакция с INSERT откатилась. Обычный VACUUM (без FULL) не сканирует данные в колоночной таблице, только метаданные (карту видимости, карту свободного пространства). Колоночные таблицы не поддерживают индексы.  По этим причинам VACUUM в колоночных таблицах в Citus работает значительно быстрее, чем для heap-таблиц. Перенос хранилища с Single-Node на распределённую архитектуру может оказаться нетривиальной задачей, даже если выполняется внутри одной экосистемы. В этой статье был рассмотрен один из аспектов этого процесса, а именно некоторые разложенные грабли и другие особенности при обслуживании таблиц в Greenplum, Amazon Redshift и Citus по сравнению с базовой версией PostgreSQL. Автор: Николай Налбантов@NickNal