Тут справедливо, из текста статьи не совсем понятно что имел в виду :)
Здесь “зависшие” = не “транзакции должны были завершиться для миграции”, а долгоживущие сессии, которые держали локи/снапшоты и мешали DDL-шагам миграции (создание/переименование/attach partitions, индексация и т.п.).
Типичный кейс — idle in transaction или тяжёлый аналитический SELECT, который взял lock уровня таблицы/держал snapshot, и из-за этого наши операции начинали ждать или ловили дедлок. Такие сессии не являются частью миграции и не “обязаны успешно пройти” — это просто внешняя нагрузка, которая мешает окну работ.
Мы их завершали точечно и только после проверки, что это не критичный прод-запрос (по pg_stat_activity, времени старта, user/app_name, тексту запроса). После terminate БД остаётся консистентной, а клиент просто получает ошибку/отмену запроса.
Справедливая критика, и я здесь не спорю с базовой логикой: UNLOGGED → нет WAL → нет physical replication — это известное свойство PostgreSQL, и именно поэтому кейс получился таким неприятным.
Но важно уточнить где реально была ошибка:
UNLOGGED использовался сознательно как временный режим на этапе bulk-load (мы ускоряли заливку).
Ошибка была в том, что мы доверились неявному поведению ALTER TABLE … SET LOGGED для partitioned tables в PG14: команда вернула SUCCESS, а мы не провели обязательную пост-проверку по всем партициям (pg_class.relpersistence / выборка по relkind и детям). Это именно процессный промах.
В результате “временное” внезапно стало “боевым”, и реплика тихо осталась пустой.
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, матвьюхи и т.д.). В нашем кейсе исторически получилось так, что критичные операции упирались именно в монолит и обслуживание целиком, поэтому партиционирование оказалось самым прямым рычагом.
Про cost_delay / cost_limit. Мы действительно пробовали тюнить вакуум (в т.ч. aggressiveness), но упёрлись в то, что на нашем профиле нагрузки “выкрутить вакуум в максимум” означало начать заметно давить прод по I/O и latency, потому что таблицы и индексы очень крупные. То есть это был trade-off: “быстрее вакуум” ↔ “хуже сервис”. На бумаге это лечится, на практике — не всегда приемлемо без выделенного окна обслуживания или без запасов по дисковой подсистеме.
Про “автовакуум выключили и раз в сутки руками VACUUM”. Нет, это было бы слишком грубо. Скорее так: на самых проблемных больших таблицах/индексах мы временно ограничивали/меняли поведение autovacuum, потому что он мог “пилить” систему долго и непредсказуемо под прод-нагрузкой. Параллельно делали точечные обслуживающие операции (VACUUM/ANALYZE по окнам), но основная боль была именно в том, что монолит обслуживать долго в любом режиме.
И как раз партиционирование дало нам возможность вернуть autovacuum в адекватный режим, потому что он стал работать на маленьких объектах, а не на одной огромной таблице.
Хороший вопрос. Тут важно разделить “частоту обновлений” и “стоимость обслуживания таблиц такого размера”.
Даже если обновления приходят раз в неделю, там всё равно есть:
массовые UPSERT/DELETE в нормализованной схеме (ways/way_nodes особенно),
обновления индексов на сотнях гигабайт,
и главное: VACUUM/ANALYZE на монолитной таблице в сотни миллионов/миллиарды строк — это не только про “много мёртвых строк”, а про объём скана + конкуренцию за I/O + стоимость поддержания visibility map/статистики.
Плюс у нас данные “read-mostly”, но не “immutable”: обновления хоть и редкие, но достаточно тяжёлые, чтобы накапливать хвосты по bloat/статистике и деградировать планы :)
Да, честно говоря. что для меня, что для нашей постгрес команды это было довольно неожиданно :)
Тут справедливо, из текста статьи не совсем понятно что имел в виду :)
Здесь “зависшие” = не “транзакции должны были завершиться для миграции”, а долгоживущие сессии, которые держали локи/снапшоты и мешали DDL-шагам миграции (создание/переименование/attach partitions, индексация и т.п.).
Типичный кейс —
idle in transactionили тяжёлый аналитический SELECT, который взял lock уровня таблицы/держал snapshot, и из-за этого наши операции начинали ждать или ловили дедлок. Такие сессии не являются частью миграции и не “обязаны успешно пройти” — это просто внешняя нагрузка, которая мешает окну работ.Мы их завершали точечно и только после проверки, что это не критичный прод-запрос (по
pg_stat_activity, времени старта, user/app_name, тексту запроса). ПослеterminateБД остаётся консистентной, а клиент просто получает ошибку/отмену запроса.Справедливая критика, и я здесь не спорю с базовой логикой:
UNLOGGED → нет WAL → нет physical replication— это известное свойство PostgreSQL, и именно поэтому кейс получился таким неприятным.Но важно уточнить где реально была ошибка:
UNLOGGED использовался сознательно как временный режим на этапе bulk-load (мы ускоряли заливку).
Ошибка была в том, что мы доверились неявному поведению
ALTER TABLE … SET LOGGEDдля partitioned tables в PG14: команда вернула SUCCESS, а мы не провели обязательную пост-проверку по всем партициям (pg_class.relpersistence/ выборка поrelkindи детям). Это именно процессный промах.В результате “временное” внезапно стало “боевым”, и реплика тихо осталась пустой.
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, матвьюхи и т.д.). В нашем кейсе исторически получилось так, что критичные операции упирались именно в монолит и обслуживание целиком, поэтому партиционирование оказалось самым прямым рычагом.
Справедливо, и я с этим не спорю: технический долг тут был наш, и “надо было раньше” — это честный вывод.
Хотелось поделиться опытом, а что делать, если "грабли" уже сделали, и вам надо от них попробовать избавиться? :)
Про cost_delay / cost_limit. Мы действительно пробовали тюнить вакуум (в т.ч. aggressiveness), но упёрлись в то, что на нашем профиле нагрузки “выкрутить вакуум в максимум” означало начать заметно давить прод по I/O и latency, потому что таблицы и индексы очень крупные. То есть это был trade-off: “быстрее вакуум” ↔ “хуже сервис”. На бумаге это лечится, на практике — не всегда приемлемо без выделенного окна обслуживания или без запасов по дисковой подсистеме.
Про “автовакуум выключили и раз в сутки руками VACUUM”. Нет, это было бы слишком грубо. Скорее так: на самых проблемных больших таблицах/индексах мы временно ограничивали/меняли поведение autovacuum, потому что он мог “пилить” систему долго и непредсказуемо под прод-нагрузкой. Параллельно делали точечные обслуживающие операции (VACUUM/ANALYZE по окнам), но основная боль была именно в том, что монолит обслуживать долго в любом режиме.
И как раз партиционирование дало нам возможность вернуть autovacuum в адекватный режим, потому что он стал работать на маленьких объектах, а не на одной огромной таблице.
Хороший вопрос. Тут важно разделить “частоту обновлений” и “стоимость обслуживания таблиц такого размера”.
Даже если обновления приходят раз в неделю, там всё равно есть:
массовые UPSERT/DELETE в нормализованной схеме (ways/way_nodes особенно),
обновления индексов на сотнях гигабайт,
и главное: VACUUM/ANALYZE на монолитной таблице в сотни миллионов/миллиарды строк — это не только про “много мёртвых строк”, а про объём скана + конкуренцию за I/O + стоимость поддержания visibility map/статистики.
Плюс у нас данные “read-mostly”, но не “immutable”: обновления хоть и редкие, но достаточно тяжёлые, чтобы накапливать хвосты по bloat/статистике и деградировать планы :)
Всё именно так, выше 1TB в целом кластеров не дают даже временно (их в целом не существует)
Но вообще скоро будет вторая часть, мы на другой кластер и уехали. но там не без сюрпризов :)