Обновить
5
18.1
Геннадий Мовила@NullStack

Пользователь

Отправить сообщение

Да, честно говоря. что для меня, что для нашей постгрес команды это было довольно неожиданно :)

Тут справедливо, из текста статьи не совсем понятно что имел в виду :)

Здесь “зависшие” = не “транзакции должны были завершиться для миграции”, а долгоживущие сессии, которые держали локи/снапшоты и мешали DDL-шагам миграции (создание/переименование/attach partitions, индексация и т.п.).

Типичный кейс — idle in transaction или тяжёлый аналитический SELECT, который взял lock уровня таблицы/держал snapshot, и из-за этого наши операции начинали ждать или ловили дедлок. Такие сессии не являются частью миграции и не “обязаны успешно пройти” — это просто внешняя нагрузка, которая мешает окну работ.

Мы их завершали точечно и только после проверки, что это не критичный прод-запрос (по pg_stat_activity, времени старта, user/app_name, тексту запроса). После terminate БД остаётся консистентной, а клиент просто получает ошибку/отмену запроса.

Справедливая критика, и я здесь не спорю с базовой логикой:
UNLOGGED → нет WAL → нет physical replication — это известное свойство PostgreSQL, и именно поэтому кейс получился таким неприятным.

Но важно уточнить где реально была ошибка:

  1. UNLOGGED использовался сознательно как временный режим на этапе bulk-load (мы ускоряли заливку).

  2. Ошибка была в том, что мы доверились неявному поведению ALTER TABLE … SET LOGGED для partitioned tables в PG14: команда вернула SUCCESS, а мы не провели обязательную пост-проверку по всем партициям (pg_class.relpersistence / выборка по relkind и детям). Это именно процессный промах.

  3. В результате “временное” внезапно стало “боевым”, и реплика тихо осталась пустой.

LINT особо больше не нужен. новые кластеры создаются на новой постгре где просто нельзя задать UNLOGGED для партиционированных таблиц, а написать линтер на N-ое количество микросервисов абсолютно всей компании довольно проблематично :)

Микросервисов море, только в одной нашей команде их 150+, создать единое полиси и линтер + replication test на всю компанию или же на все команды в рамках всего отдела - крайне трудоёмкая задача и вызывает довольно много нюансов и сложностей, от слишком долгой выкладки до слишком сложного контроля :)

А так, идея хорошая, может быть найдём способ внедрить, спасибо большое за отличный комментарий.

Вы правы — в текущей документации PostgreSQL 18 прямо сказано, что ALTER TABLE … SET {LOGGED|UNLOGGED} не поддерживается для partitioned tables, и это ключевой момент. Я в тексте статьи сформулировал это слишком оптимистично/неаккуратно.

Что реально поменялось по линии 17 → 18 (упрощённо, по смыслу):

  • В версиях до 18 ситуация была “плохая и тихая”:
    ALTER TABLE … SET [UN]LOGGED на partitioned table мог вернуть SUCCESS, но по факту ничего полезного не сделать (как минимум — не затронуть партиции/relpersistence так, как ожидает пользователь). Именно на этом мы и обожглись.

  • В PostgreSQL 18 поведение сделали честным и предсказуемым:
    такие операции на partitioned tables теперь запрещены/не поддерживаются, чтобы не создавать ложное ощущение, что вы “переключили в LOGGED”, хотя на самом деле нет. Это отражено и в release notes 18.0.

То есть мой тезис “в 17 распространяется на партиции” — нужно считать неверным. Корректнее так:
до 18 включительно это место было источником путаницы, а в 18 PostgreSQL выбрал стратегию “лучше ошибка, чем молча”.

Про второй вопрос — “для обычных таблиц это полное пересоздание и нужен двойной диск?”
Да, в общем случае смена режима LOGGED/UNLOGGED для обычной таблицы — операция тяжёлая: она требует переписывания данных (table rewrite) и берёт сильные блокировки; по ресурсам это действительно похоже на “пересоздание/перезапись” и может потребовать существенного дополнительного I/O и места на время операции (в зависимости от версии, wal_level, размера таблицы и т.п.).

Поправлю это в исходной статье, спасибо большое :)

Хотел бы отметить, мне казалось это в целом понятно из тона статьи и в целом описания :)

Я не продаю это как “единственно правильный путь”. Это постмортем: какие ограничения были, какие решения приняли, где ошиблись, что получили и какие выводы сделали. Если у кого-то в инфраструктуре проще поднять второй кластер/сделать pg_dump+restore — это часто действительно будет лучше. У нас на тот момент окно решений было уже.

Да, вы правы: жёсткого порога по размеру в документации нет, и критерий “таблица/индексы не помещаются в RAM” — один из базовых ориентиров.

Фраза про “300GB+” у меня была не как правило из доков, а как эмпирический сигнал тревоги из нашего опыта: в этот момент обычно начинают проявляться сразу несколько эффектов:

  • обслуживание (VACUUM/REINDEX/ANALYZE) становится долгим и плохо прогнозируемым,

  • планировщик чаще ошибается без свежей статистики,

  • любой неожиданный bloat/индексный рост начинает очень быстро подъедать storage,

  • а самое неприятное — любые “случайные” операции (DDL, индексы, миграции) становятся рискованнее.

И да, полностью согласен про “активно используемую часть”: если у вас workload обращается к 5% данных, решение может быть вообще другим (частичные индексы, кластеризация, hot/cold split, матвьюхи и т.д.). В нашем кейсе исторически получилось так, что критичные операции упирались именно в монолит и обслуживание целиком, поэтому партиционирование оказалось самым прямым рычагом.

Справедливо, и я с этим не спорю: технический долг тут был наш, и “надо было раньше” — это честный вывод.

Хотелось поделиться опытом, а что делать, если "грабли" уже сделали, и вам надо от них попробовать избавиться? :)

  1. Про cost_delay / cost_limit. Мы действительно пробовали тюнить вакуум (в т.ч. aggressiveness), но упёрлись в то, что на нашем профиле нагрузки “выкрутить вакуум в максимум” означало начать заметно давить прод по I/O и latency, потому что таблицы и индексы очень крупные. То есть это был trade-off: “быстрее вакуум” ↔ “хуже сервис”. На бумаге это лечится, на практике — не всегда приемлемо без выделенного окна обслуживания или без запасов по дисковой подсистеме.

  2. Про “автовакуум выключили и раз в сутки руками VACUUM”. Нет, это было бы слишком грубо. Скорее так: на самых проблемных больших таблицах/индексах мы временно ограничивали/меняли поведение autovacuum, потому что он мог “пилить” систему долго и непредсказуемо под прод-нагрузкой. Параллельно делали точечные обслуживающие операции (VACUUM/ANALYZE по окнам), но основная боль была именно в том, что монолит обслуживать долго в любом режиме.

И как раз партиционирование дало нам возможность вернуть autovacuum в адекватный режим, потому что он стал работать на маленьких объектах, а не на одной огромной таблице.

Хороший вопрос. Тут важно разделить “частоту обновлений” и “стоимость обслуживания таблиц такого размера”.

Даже если обновления приходят раз в неделю, там всё равно есть:

  • массовые UPSERT/DELETE в нормализованной схеме (ways/way_nodes особенно),

  • обновления индексов на сотнях гигабайт,

  • и главное: VACUUM/ANALYZE на монолитной таблице в сотни миллионов/миллиарды строк — это не только про “много мёртвых строк”, а про объём скана + конкуренцию за I/O + стоимость поддержания visibility map/статистики.

Плюс у нас данные “read-mostly”, но не “immutable”: обновления хоть и редкие, но достаточно тяжёлые, чтобы накапливать хвосты по bloat/статистике и деградировать планы :)

Всё именно так, выше 1TB в целом кластеров не дают даже временно (их в целом не существует)

Но вообще скоро будет вторая часть, мы на другой кластер и уехали. но там не без сюрпризов :)

Информация

В рейтинге
434-й
Зарегистрирован
Активность

Специализация

Бэкенд разработчик, Архитектор программного обеспечения
Ведущий