АйТи Фреш
Главная / Статьи / 1С и базы данных
1С и базы данных

PostgreSQL 18: почему ATTACH PARTITION тормозит запись и как убрать сканы через CHECK

Автор: Семёнов Евгений Сергеевич, директор ООО «АйТи-Фреш» · · ~22 мин чтения
PostgreSQL 18: почему ATTACH PARTITION тормозит запись и как убрать сканы через CHECK
Иллюстрация к статье «PostgreSQL 18: почему ATTACH PARTITION тормозит запись и как убрать сканы через CHECK».

Скрипт ротации месячных секций десятки раз завершался за секунды, а однажды простоял двадцать шесть минут и остановил запись событий. Причина оказалась не в размере новой таблицы, а в живом DEFAULT-разделе, очереди на ACCESS EXCLUSIVE и ограничениях, которым PostgreSQL не мог доверять. Ниже я разберу реальные блокировки ATTACH PARTITION в PostgreSQL 18, покажу корректные CHECK для новой и DEFAULT-секции и соберу регламент, при котором операция быстро завершается либо так же быстро отступает по lock_timeout.

Какие блокировки действительно берёт ATTACH PARTITION

Фраза «ATTACH почти не блокирует» появилась не на пустом месте. В руководстве PostgreSQL сказано, что ALTER TABLE ... ATTACH PARTITION запрашивает на родительской секционированной таблице SHARE UPDATE EXCLUSIVE, тогда как CREATE TABLE ... PARTITION OF требует ACCESS EXCLUSIVE. SHARE UPDATE EXCLUSIVE совместима с обычными ACCESS SHARE и ROW EXCLUSIVE, поэтому чтение и стандартный DML по родителю сами по себе не должны останавливаться. Но родитель — только одна из целей блокировки.

Полный перечень находится в описании ALTER TABLE. Помимо SHARE UPDATE EXCLUSIVE на родителе, сервер запрашивает ACCESS EXCLUSIVE на присоединяемой таблице и на DEFAULT-разделе, если тот существует. Блокировка присоединяемой таблицы обычно безобидна, когда это заранее созданная и никому пока не доступная заготовка. ACCESS EXCLUSIVE на рабочем DEFAULT-разделе гораздо опаснее: она конфликтует со всеми обычными обращениями именно к этому разделу.

Важное уточнение: подходящий CHECK убирает сканирование, но не отменяет сам запрос ACCESS EXCLUSIVE на DEFAULT-раздел. Поэтому даже при идеально подготовленных ограничениях ATTACH может ждать долгую транзакцию, ручной VACUUM или autovacuum, защищающий таблицу от wraparound. Разница в том, что после получения блокировок операция без сканов завершается быстро и не удерживает их во время чтения миллионов строк.

Без доказательства границ PostgreSQL проводит две отдельные проверки. Присоединяемую обычную таблицу сканируют под ACCESS EXCLUSIVE, чтобы найти строки вне объявленного диапазона. DEFAULT-раздел сканируют под собственной ACCESS EXCLUSIVE, чтобы убедиться, что там нет строк новой секции. Если присоединяемая таблица или DEFAULT сами секционированы, дополнительные блокировки и проверки распространяются вниз по иерархии, пока сервер не встретит достаточное ограничение или листовую таблицу.

Валидный CHECK здесь нужен не планировщику запроса, а механизму проверки ограничений во время DDL. PostgreSQL должен суметь логически вывести из выражения CHECK требуемую границу. Текст не обязан совпадать посимвольно с FOR VALUES, но простые одинаково типизированные сравнения надёжнее сложных функций и преобразований. В PostgreSQL 18 также нельзя рассчитывать на CHECK с convalidated = false или conenforced = false: такой объект не доказывает, что все старые строки соответствуют условию.

CHECK сокращает время удержания блокировки, но не превращает ATTACH в неблокирующую операцию. Именно поэтому в рабочем регламенте одновременно нужны ограничения, короткая транзакция и `lock_timeout`.

Разбор инцидента: 1,4 млрд строк и 26 минут ожидания

Условный клиент в этом разборе — ветеринарная сеть «Айболит-Вет», 5 клиник, 41 РМ. Стенд работал на PostgreSQL 18: виртуальная машина с 8 vCPU, 32 ГБ RAM и NVMe-хранилищем. Таблица events принимала события лабораторного оборудования и контроллеров, была разбита по RANGE-ключу ts типа timestamptz на месячные секции и хранила данные за 24 месяца. На момент разбора в ней было около 1,4 млрд строк, 26 активных секций и примерно 780 ГБ данных.

Рядом существовал events_default, заведённый как страховка от отсутствующей секции. Ночной скрипт первого числа создавал очередную events_YYYY_MM и выполнял ATTACH. В проблемную ночь команда стартовала в 03:00:04, но к 03:26 ещё оставалась активной. В pg_stat_activity накопилось 340 клиентских сессий с wait_event_type = 'Lock'. Это не означало, что остановился весь кластер: встала запись текущих событий, которые до появления новой месячной секции маршрутизировались именно в DEFAULT.

В events_default обнаружилось 4,2 млн строк общим размером около 3,1 ГБ. Они копились 11 месяцев. Восемь контроллеров работали без нормальной синхронизации времени: часть событий имела отметку 1970 года, часть уходила вперёд на 18 месяцев. В итоге DEFAULT содержал не только заведомо старый мусор, но и даты будущих месячных диапазонов. Когда очередь дошла до месяца, уже представленного такими строками, ATTACH должен был проверить заведомо конфликтующее содержимое.

Вторым участником инцидента оказался autovacuum по events_default, запущенный для предотвращения wraparound. Это принципиальная деталь. Обычный autovacuum PostgreSQL, как правило, прерывает при появлении конфликтующей DDL-блокировки. Процесс, защищающий от wraparound, автоматически не прерывается; в pg_stat_activity его запрос заканчивается пометкой (to prevent wraparound). Поэтому правдоподобное длительное ожидание нельзя объяснять просто словами «в это время шёл любой autovacuum».

Около 19 минут ATTACH ждал ACCESS EXCLUSIVE. Новые события текущего месяца продолжали направляться в events_default и вставали за ожидающей блокировкой, поэтому очередь быстро росла. Получив блокировку, PostgreSQL примерно 7 минут читал 3,1 ГБ и проверял предикат на фоне нагрузки от репликации и дисковых операций. Затем команда не могла успешно присоединить секцию, потому что нашла строки её диапазона в DEFAULT. Общий ущерб составили те же 26 минут: большая часть ушла на ожидание, меньшая — на скан, а результатом стала ошибка ограничения.

Из этого случая я вынес два разных правила. Первое: пустота DEFAULT должна контролироваться до начала DDL, а не выясняться сканированием внутри ATTACH. Второе: даже корректный CHECK не гарантирует мгновенное получение ACCESS EXCLUSIVE, поэтому команда не должна бесконечно стоять в очереди. Если блокировка не выдана за несколько секунд, безопаснее отменить попытку и повторить её позже.

Если ATTACH уже стоит в очереди, сначала найдите его PID и блокирующую цепочку. Отмена именно ожидающего ATTACH через `pg_cancel_backend()` обычно снимает созданную им пробку; завершать все клиентские сессии в хвосте очереди бессмысленно.
PostgreSQL 18: почему ATTACH PARTITION тормозит запись и как убрать сканы через CHECK — схема
Схема к статье. Открыть схему в полном размере

CHECK на присоединяемой таблице: убираем первый скан

Для RANGE-секции нижняя граница включительна, а верхняя исключительна. Поэтому месячный CHECK записываю через >= и <. BETWEEN здесь неудобен: он включает обе границы и при соседних месяцах разрешил бы значение, принадлежащее следующей секции. Константы для timestamptz я типизирую явно и указываю смещение, чтобы результат не зависел от TimeZone сессии, запускающей DDL.

Заготовку удобнее создавать обычной таблицей через LIKE, а не сразу через PARTITION OF. В примере используется INCLUDING ALL: в PostgreSQL 18 это сокращение для копирования всех поддерживаемых свойств, включая CHECK, defaults, generated- и identity-описания, параметры хранения, индексы и ограничения. Для реальной схемы я всё равно сравниваю получившиеся индексы, владельца, права и поведение identity-последовательностей: LIKE создаёт независимую таблицу, а для скопированной identity-колонки — отдельную последовательность.

Если таблица пустая, обычный ADD CONSTRAINT проверит её почти мгновенно. Для наполненной архивной таблицы этот же шаг потребует скан под ACCESS EXCLUSIVE. Тогда я сначала добавляю CHECK как NOT VALID, фиксирую короткую транзакцию, а затем отдельно выполняю VALIDATE CONSTRAINT. Валидация читает старые строки под SHARE UPDATE EXCLUSIVE и совместима с обычными изменениями данных, хотя конкурирующая DDL всё равно может ждать.

Само наличие ограничения ещё ничего не гарантирует. После валидации проверяю convalidated = true; для PostgreSQL 18 также оставляю ограничение в состоянии ENFORCED, то есть conenforced = true. Выражение должно логически разрешать только будущий диапазон секции. Посимвольное равенство не требуется, но я генерирую CHECK и FOR VALUES из одних параметров, чтобы не получить расхождение типов, часового пояса или верхней даты.

Для LIST-секции, которая не принимает NULL, документация требует дополнительно установить NOT NULL на колонку ключа, если ключ не является выражением. PostgreSQL 18 хранит NOT NULL в pg_constraint и позволяет добавлять именованный NOT NULL как NOT VALID, а затем валидировать его. Корректная форма — ALTER TABLE ... ADD CONSTRAINT имя NOT NULL колонка NOT VALID, а не выдуманный флаг после определения колонки. Для ключа-выражения действует отдельное ограничение: при секции, не принимающей NULL, CHECK-трюк может не избавить от скана.

После успешного ATTACH дополнительный CHECK границ можно удалить: встроенное ограничение секции уже обеспечивает тот же диапазон. Я делаю DROP отдельной короткой командой после присоединения, потому что блокировки DDL живут до конца транзакции. Для часто используемой секции удаление дублирующей проверки также избавляет каждую последующую вставку от лишнего вычисления.

```sql CREATE TABLE events_2026_10 (LIKE events INCLUDING ALL); ALTER TABLE events_2026_10 ADD CONSTRAINT events_2026_10_bounds CHECK ( ts >= TIMESTAMPTZ '2026-10-01 00:00:00+03' AND ts < TIMESTAMPTZ '2026-11-01 00:00:00+03' ); BEGIN; SET LOCAL lock_timeout = '3s'; SET LOCAL statement_timeout = '2min'; ALTER TABLE events ATTACH PARTITION events_2026_10 FOR VALUES FROM ('2026-10-01 00:00:00+03') TO ('2026-11-01 00:00:00+03'); COMMIT; ALTER TABLE events_2026_10 DROP CONSTRAINT events_2026_10_bounds; ```
Памятка: CHECK на присоединяемой таблице: убираем первый скан — схема
Памятка: CHECK на присоединяемой таблице: убираем первый скан. Открыть схему в полном размере

DEFAULT-раздел: годовой CHECK без логической ошибки

DEFAULT — главный источник длинного ATTACH. Когда добавляется обычная секция, PostgreSQL изменяет внутреннее ограничение DEFAULT: теперь этот раздел должен исключать и новый диапазон. Если сервер не может доказать отсутствие нужных строк по существующему CHECK, он читает DEFAULT под ACCESS EXCLUSIVE. Решение принимается по ограничениям, а не по результату вчерашнего count(*). Даже логически пустая, но раздутая таблица может заставить сервер просмотреть множество физических страниц.

Если в DEFAULT уже есть хотя бы одна строка нового диапазона, ATTACH закончится ошибкой о нарушении обновлённого ограничения DEFAULT. Такой набор данных сначала надо разобрать: исправить ошибочные даты, создать целевые секции безопасным способом и перенести данные порциями. Попытка многократно перезапускать тот же ATTACH ничего не исправляет и лишь повторяет блокировку и скан.

В исходном варианте регламента была логическая неточность: CHECK вида ts < начало_2026 исключает не только 2026 год, но и всё последующее время. Такое ограничение не надо ежегодно заменять, однако оно навсегда запрещает DEFAULT принимать будущие даты. Если нужен ежегодный компромисс, выражение должно исключать ровно один год: дата меньше его начала либо не меньше начала следующего года.

Ограничение для конкретного года позволяет без скана DEFAULT присоединять все двенадцать месячных секций этого года. Но краткая ACCESS EXCLUSIVE на DEFAULT всё равно запрашивается. Перед следующим годом я сначала добавляю новое ограничение как NOT VALID, валидирую его и только потом удаляю старое. ADD CONSTRAINT ... NOT VALID и DROP всё равно требуют коротких сильных блокировок, тогда как длительный VALIDATE CONSTRAINT использует SHARE UPDATE EXCLUSIVE.

Такой CHECK сознательно меняет поведение аварийного маршрута. Если секция за октябрь ещё не создана, строка с октябрьской датой будет направлена в DEFAULT, после чего нарушит events_default_no_2026. При полном отсутствии DEFAULT сервер вместо этого сообщил бы, что подходящая секция не найдена. Я предпочитаю явную ошибку и ретрай молчаливому накоплению данных, но для систем без повторной доставки это решение надо согласовать с владельцем приложения.

Есть ещё одно ограничение, которое нельзя спрятать за CHECK: ALTER TABLE ... DETACH PARTITION ... CONCURRENTLY запрещён, если у родительской таблицы существует DEFAULT-раздел. Для таблиц с регулярным ретеншеном это весомый аргумент отказаться от DEFAULT полностью. Обычный DETACH остаётся доступен, но требует более сильной блокировки родителя.

```sql ALTER TABLE events_default ADD CONSTRAINT events_default_no_2026 CHECK ( ts < TIMESTAMPTZ '2026-01-01 00:00:00+03' OR ts >= TIMESTAMPTZ '2027-01-01 00:00:00+03' ) NOT VALID; ALTER TABLE events_default VALIDATE CONSTRAINT events_default_no_2026; ```

Регламент ротации: premake, тайм-ауты и статистика

Ротацию я разделяю на подготовку и короткое изменение дерева секций. Заготовки создаются не в первые часы нового месяца, а заранее. Для большинства рабочих схем начинаю с горизонта от трёх до шести месяцев, после чего корректирую его по максимальному допустимому смещению входных дат. Это эксплуатационная рекомендация, а не предел PostgreSQL. В случае сети «Айболит-Вет» горизонт должен был перекрывать известные отклонения оборудования либо кривые будущие даты следовало отбрасывать ещё на приёме.

Premake решает проблему на уровне маршрутизации: корректная будущая строка сразу попадает в готовую секцию и не загрязняет DEFAULT. Но один premake не защищает от 1970 года, NULL или даты, улетевшей дальше горизонта. Поэтому рядом нужны метрика непустого DEFAULT, контроль часов источников и отдельный карантин для событий, которые нельзя надёжно исправить автоматически.

На каждую короткую DDL-попытку устанавливаю lock_timeout = '3s'. Это выбранный мной эксплуатационный порог, а не встроенное значение PostgreSQL. Если блокировка не получена, транзакция откатывается, планировщик заданий ждёт минуту и повторяет попытку до двадцати раз. Эти числа не обещают ровно двадцатиминутное окно: продолжительность зависит от времени каждой попытки и пауз. Их надо подбирать под допустимую задержку конкретного сервиса.

Помимо CHECK я готовлю индексы. По правилам ATTACH для каждого индекса родителя PostgreSQL присоединяет валидный эквивалентный индекс новой таблицы либо создаёт соответствующий индекс сам. На пустой секции это обычно быстро; на наполненной архивной таблице неожиданное создание индекса способно сделать ATTACH долгим. Поэтому перед миграцией проверяю эквивалентные индексы заранее и не считаю отсутствие скана данных гарантией мгновенного завершения.

После изменения состава секций проверяю статистику родителя. Autovacuum обрабатывает обычные дочерние секции, но не саму секционированную таблицу и не запускает для неё автоматический ANALYZE. В PostgreSQL 18 появился синтаксис ANALYZE ONLY events: он обрабатывает родительскую секционированную таблицу, не запуская отдельный ANALYZE для каждого потомка. Сбор наследуемой статистики всё равно может получать выборки из секций, поэтому формулировка «вообще не читает дочерние данные» была бы неверной.

Руководство рекомендует повторять ANALYZE, когда распределение данных по секциям заметно изменилось. Я не запускаю его механически после каждого пустого ATTACH, если статистика ещё репрезентативна, но выполняю после крупного переноса, массовой загрузки или существенного DETACH. Это уменьшает риск неоптимальных планов без лишней фоновой работы.

Если таблиц много, вместо самописной автоматики можно использовать pg_partman. На дату проверки актуальный релиз — 5.5.0; его документация описывает premake, retention, check_default() и процедуры переноса данных. У check_default(false) отключается точный подсчёт: функция может быстро сообщить о первой найденной строке. Для PostgreSQL 18 я не ставил бы произвольную старую версию ветки 5.x: релиз 5.5.0 содержит в том числе исправления уязвимостей повышения привилегий и меняет рекомендуемую роль фонового worker на непривилегированную.

```bash for attempt in $(seq 1 20); do if psql -X -v ON_ERROR_STOP=1 -d prod <<'SQL' BEGIN; SET LOCAL lock_timeout = '3s'; SET LOCAL statement_timeout = '2min'; ALTER TABLE events ATTACH PARTITION events_2026_10 FOR VALUES FROM ('2026-10-01 00:00:00+03') TO ('2026-11-01 00:00:00+03'); COMMIT; SQL then break fi sleep 60 done ```
Памятка: Регламент ротации: premake, тайм-ауты и статистика — схема
Памятка: Регламент ротации: premake, тайм-ауты и статистика. Открыть схему в полном размере

Ошибки, которые превращают короткую DDL в простой

Первая ошибка — использовать CREATE TABLE ... PARTITION OF на нагруженном родителе только ради короткого SQL. Эта форма требует ACCESS EXCLUSIVE на родительской таблице. Такой режим конфликтует и с чтением, и с записью через родителя, поэтому цена удобной одной строки резко возрастает. Связка обычного CREATE TABLE, подготовки объектов и короткого ATTACH оставляет на родителе более слабую SHARE UPDATE EXCLUSIVE.

Вторая ошибка — остановиться на NOT VALID. Такое ограничение уже проверяет новые или изменяемые строки, но не доказывает корректность исторических данных. Пока convalidated = false, ATTACH имеет право выполнить собственную проверку. В PostgreSQL 18 дополнительно появился режим NOT ENFORCED для CHECK, и он тоже не подходит: нужный ограничитель должен быть и валидирован, и принудительно применяться.

Третья ошибка — проверять DEFAULT запросом SELECT count(*) FROM events_default LIMIT 1. LIMIT стоит после агрегирования и не спасает от полного подсчёта строк. Для быстрого сигнала использую SELECT EXISTS (SELECT FROM events_default). Если нужен точный объём проблемы, считаю строки отдельным диагностическим запросом в подходящее время, а не в критической части ротации.

Четвёртая ошибка — считать любой autovacuum причиной многоминутного ожидания. Обычный autovacuum PostgreSQL обычно прерывает при конфликтующей блокировке. Исключение — процесс, предотвращающий wraparound. Кроме того, блокировать могут ручной VACUUM, открытая транзакция приложения или другая DDL. Поэтому я смотрю backend_type, текст запроса, xact_start, pg_locks.granted и реальную цепочку блокировок, а не убиваю первый процесс со словом vacuum.

Пятая ошибка — выполнять ATTACH внутри длинной транзакции вместе с созданием индексов, отчётными запросами и служебными обновлениями. Табличные блокировки обычно удерживаются до COMMIT, а не освобождаются в конце отдельной команды. Даже завершившийся за секунду ATTACH продолжит блокировать DEFAULT, пока приложение занимается остальными действиями.

Шестая ошибка — позволить ATTACH самому создать недостающие индексы на наполненной таблице. Документация прямо говорит, что при отсутствии валидного эквивалентного индекса создаётся новый. Подготовка CHECK устраняет проверочный скан строк, но не стоимость построения индекса. Для архивного присоединения я сначала создаю необходимые индексы, при допустимости использую CREATE INDEX CONCURRENTLY на ещё отдельной таблице, проверяю их валидность и лишь затем запускаю ATTACH.

```sql SELECT conname, contype, convalidated, conenforced, pg_get_constraintdef(oid) AS definition FROM pg_constraint WHERE conrelid IN ( 'events_default'::regclass, 'events_2026_10'::regclass ) ORDER BY conrelid, conname; ```

Чек-лист перед ATTACH и действия при ожидании

Сначала проверяю DEFAULT быстрым EXISTS, затем просматриваю сами ограничения. Простого convalidated = true недостаточно: выражение на DEFAULT должно действительно исключать присоединяемый диапазон, а CHECK новой таблицы — разрешать только его. Для PostgreSQL 18 проверяю также conenforced. Если используется timestamptz, сравниваю типизированные константы и смещение времени в CHECK и границах секции.

Затем сверяю структуру заготовки с родителем. ATTACH требует одинаковых колонок и типов, а также обязательных NOT NULL и CHECK родителя. Проверяю валидные эквивалентные индексы, потому что отсутствующий индекс будет создан во время ATTACH. Отдельно убеждаюсь, что заготовка не используется приложением: ACCESS EXCLUSIVE на ней неизбежна.

Перед DDL смотрю транзакции и блокировки. Особенно интересуют длительные idle in transaction, ручной VACUUM, другая DDL и autovacuum по events_default. Если процесс предотвращает wraparound, отменять его автоматически опасно; безопаснее отложить ATTACH. Сам ATTACH запускаю в отдельной транзакции с SET LOCAL lock_timeout = '3s', чтобы настройка действовала только до COMMIT или ROLLBACK.

Во время операции держу вторую сессию с pg_stat_activity и pg_locks. Если команда не получила блокировку за ожидаемые несколько секунд, lock_timeout должен завершить попытку сам. При ошибке проверяю, не осталось ли неудачной транзакции в состоянии aborted, и делаю ROLLBACK до повтора. Если тайм-аут забыли установить, нахожу точный PID ATTACH, подтверждаю цепочку и вызываю pg_cancel_backend() для него.

После успеха удаляю дополнительный CHECK с новой секции отдельной короткой командой, проверяю, что секция видна в дереве, и оцениваю необходимость ANALYZE ONLY events. Наконец, снова выполняю быстрый контроль DEFAULT. Такая последовательность отделяет подготовку, способную долго читать данные, от короткой структурной операции и не оставляет результат на веру сообщению cron.

```sql SELECT EXISTS (SELECT FROM events_default) AS default_has_rows; SELECT a.pid, a.backend_type, now() - a.xact_start AS transaction_age, a.wait_event_type, a.wait_event, l.mode, l.granted, c.relname, left(a.query, 120) AS query FROM pg_stat_activity AS a LEFT JOIN pg_locks AS l ON l.pid = a.pid LEFT JOIN pg_class AS c ON c.oid = l.relation WHERE a.pid <> pg_backend_pid() AND (a.xact_start IS NOT NULL OR l.relation IS NOT NULL) ORDER BY a.xact_start NULLS LAST, a.pid; ```
Порядок действий: Чек-лист перед ATTACH и действия при ожидании — схема
Порядок действий: Чек-лист перед ATTACH и действия при ожидании. Открыть схему в полном размере

Частые вопросы

ATTACH PARTITION блокирует всю секционированную таблицу?

На родителе ATTACH использует SHARE UPDATE EXCLUSIVE, совместимую с обычным чтением и записью. ACCESS EXCLUSIVE берётся на присоединяемую таблицу и DEFAULT. Поэтому чаще блокируется не весь кластер и не все секции, а запросы, которым нужен DEFAULT. В разобранном случае почти вся текущая запись шла именно туда, поскольку новая месячная секция ещё не была присоединена.

Почему PostgreSQL сканирует пустой DEFAULT-раздел?

Серверу требуется доказательство, что в DEFAULT нет строк нового диапазона. Если подходящего валидного и принудительно применяемого CHECK нет, PostgreSQL проверяет содержимое сам. У логически пустой, но раздутой таблицы скан может прочитать множество физических страниц. Исключение из документации — DEFAULT, реализованный как foreign table: для него такая проверка пропускается.

Достаточно ли добавить CHECK как NOT VALID?

Нет. `NOT VALID` пропускает первоначальный скан при добавлении и начинает проверять новые изменения, но не подтверждает старые строки. Затем нужно выполнить `VALIDATE CONSTRAINT`; только после этого `convalidated` станет true. В PostgreSQL 18 ограничение также должно быть ENFORCED, то есть иметь `conenforced = true`.

Границы CHECK должны совпадать с FOR VALUES посимвольно?

Нет, PostgreSQL проверяет логическое следствие, а не равенство строк SQL. Однако простые сравнения над одной колонкой и одинаково типизированными константами надёжнее. Для RANGE нижняя граница включительна, верхняя исключительна. При `timestamptz` лучше явно указывать тип и смещение времени.

Стоит ли оставлять DEFAULT-раздел?

Он полезен как ловушка для данных вне подготовленных диапазонов, но требует строгого мониторинга, участвует в каждом ATTACH и запрещает `DETACH PARTITION CONCURRENTLY`. Если приложение умеет повторять отклонённые вставки и старые секции регулярно отцепляются, отсутствие DEFAULT часто проще. Если потеря входного события недопустима, DEFAULT можно оставить, но его надо контролировать и разбирать до ротации.

Что делать, если ATTACH уже ждёт, а запись остановилась?

Найдите PID команды и её блокирующую цепочку через `pg_stat_activity` и `pg_locks`. Если безопаснее вернуть запись, отмените ожидающий ATTACH функцией `pg_cancel_backend()` и повторите его после устранения причины. Не завершайте вслепую autovacuum для предотвращения wraparound. На будущие запуски задайте короткий `lock_timeout` в транзакции ATTACH.

Гарантирует ли CHECK мгновенный ATTACH?

Нет. Он устраняет проверочный скан, но ACCESS EXCLUSIVE на новой и DEFAULT-таблицах всё равно запрашивается. Кроме того, ATTACH может создавать отсутствующие индексы. Быстрое завершение получается только при сочетании валидных ограничений, заранее подготовленных индексов, отсутствия долгих держателей блокировок и короткого `lock_timeout`.

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 💬 MAX: +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи