[EPIC] МЕРА: пересборка базы начисто при переезде на выделенный сервер #2989

Open
opened 2026-08-20 17:47:29 +00:00 by lekss361 · 1 comment
Owner

Архитектурное ревью схемы tradein (2026-08-20) под задачу владельца: при переезде на выделенный сервер пересобрать базу начисто, а не привезти текущую вместе с раздутостью и дефектами модели.

Разбор вёлся шестью направлениями, каждая рекомендация проверена независимым агентом с установкой опровергать. Из 41 предложения подтвердилось 12 — корректировки в тексте дочерних issue имеют приоритет над исходными выводами.

Полный документ: https://claude.ai/code/artifact/950089eb-be34-44fb-ade3-caf0f899a83f

Замеры с прода 2026-08-20

Метрика Значение
listings 105 276 строк, 91 колонка, 19 ГБ
TOAST 15 ГБ при ~230 МБ живого содержимого
Апдейтов 20 857 049 на 105 тыс. строк (198 на строку)
HOT-обновлений 0,43 %
WAL 7,02 ГБ/сутки при ~4 расчётах в сутки
Чтение 169 млрд blks_read, ~175 МБ/с мимо shared_buffers (128 МБ)
Дамп базы 285 МБ gzip — реальный логический payload

Главное

listings совмещает три несовместимых цикла записи в одном кортеже: «пульс» (меняется на каждом обходе, ~229 тыс. раз в сутки), «описание объекта» (0,3–3 % случаев) и «сырьё» (пишется один раз, не читается никогда). Кортеж один — поэтому каждый пульс-апдейт переписывает ~1,9 КБ inline-данных, вставляет запись в 30 индексов и заново тостит description.

Цена переноса «как есть» на московском объёме (×6,6 по замеру счётчиков выдачи ЦИАН: Москва+МО 60 846 против Свердловской области 9 293): ~125 ГБ только под listings и 46 ГБ WAL в сутки. Целевая схема на тех же данных: 2,5–4 ГБ и 1,5–3 ГБ WAL.

Переезд дешевле, чем кажется. 21 ГБ на диске — иллюзия: раздутость при перезаливке не воспроизводится. Полный цикл «дамп → перелив → индексы → ANALYZE» укладывается в 30–60 минут. Узкое место не перенос данных, а сборка 255 индексов.

Дочерние задачи

Блокеры переезда (сделать до окна):

  • Чистый старт БД падает на миграции 077 — путь ни разу не исполнялся на реальном железе
  • Postgres на стоковом конфиге + mem_limit: 3g сработает раньше shared_buffers

Причины раздутости (окупаются ещё до переезда):

  • Апсерт переписывает 41 колонку безусловно
  • listing_source_snapshot пишет полную копию всех источников каждые сутки
  • Индексы: ~855 МБ мёртвых и дублирующих, нет geography-индекса на houses

Дефекты модели (чинятся только пересборкой):

  • Реконсилятор живости вместо мутабельного is_active
  • Дедуп объявлений невозможен по построению
  • Город входит в ценовую формулу текстом — блокер Москвы

Смежное, уже в трекере: #2203 (off-box бэкап, P0, открыт с 02.07), #2656 (якорь читает is_active без свежести), #2659 (паушальный TTL), #2583 (квартальный индекс нормирован на медиану ЕКБ), #2674 (код написан и ни разу не сработал).

Порядок

Сначала конфиг, замер, и только потом схема — иначе весь эффект спишут на разделение таблиц и не поймут, что сработало.

Архитектурное ревью схемы `tradein` (2026-08-20) под задачу владельца: при переезде на выделенный сервер пересобрать базу начисто, а не привезти текущую вместе с раздутостью и дефектами модели. Разбор вёлся шестью направлениями, **каждая рекомендация проверена независимым агентом с установкой опровергать**. Из 41 предложения подтвердилось 12 — корректировки в тексте дочерних issue имеют приоритет над исходными выводами. Полный документ: https://claude.ai/code/artifact/950089eb-be34-44fb-ade3-caf0f899a83f ## Замеры с прода 2026-08-20 | Метрика | Значение | |---|---| | `listings` | 105 276 строк, 91 колонка, 19 ГБ | | TOAST | 15 ГБ при ~230 МБ живого содержимого | | Апдейтов | 20 857 049 на 105 тыс. строк (198 на строку) | | HOT-обновлений | 0,43 % | | WAL | 7,02 ГБ/сутки при ~4 расчётах в сутки | | Чтение | 169 млрд `blks_read`, ~175 МБ/с мимо `shared_buffers` (128 МБ) | | Дамп базы | 285 МБ gzip — реальный логический payload | ## Главное `listings` совмещает три несовместимых цикла записи в одном кортеже: «пульс» (меняется на каждом обходе, ~229 тыс. раз в сутки), «описание объекта» (0,3–3 % случаев) и «сырьё» (пишется один раз, не читается никогда). Кортеж один — поэтому каждый пульс-апдейт переписывает ~1,9 КБ inline-данных, вставляет запись в 30 индексов и заново тостит `description`. **Цена переноса «как есть»** на московском объёме (×6,6 по замеру счётчиков выдачи ЦИАН: Москва+МО 60 846 против Свердловской области 9 293): ~125 ГБ только под `listings` и 46 ГБ WAL в сутки. Целевая схема на тех же данных: 2,5–4 ГБ и 1,5–3 ГБ WAL. **Переезд дешевле, чем кажется.** 21 ГБ на диске — иллюзия: раздутость при перезаливке не воспроизводится. Полный цикл «дамп → перелив → индексы → ANALYZE» укладывается в 30–60 минут. Узкое место не перенос данных, а сборка 255 индексов. ## Дочерние задачи Блокеры переезда (сделать до окна): - [ ] Чистый старт БД падает на миграции 077 — путь ни разу не исполнялся на реальном железе - [ ] Postgres на стоковом конфиге + `mem_limit: 3g` сработает раньше `shared_buffers` Причины раздутости (окупаются ещё до переезда): - [ ] Апсерт переписывает 41 колонку безусловно - [ ] `listing_source_snapshot` пишет полную копию всех источников каждые сутки - [ ] Индексы: ~855 МБ мёртвых и дублирующих, нет geography-индекса на `houses` Дефекты модели (чинятся только пересборкой): - [ ] Реконсилятор живости вместо мутабельного `is_active` - [ ] Дедуп объявлений невозможен по построению - [ ] Город входит в ценовую формулу текстом — блокер Москвы Смежное, уже в трекере: #2203 (off-box бэкап, P0, открыт с 02.07), #2656 (якорь читает `is_active` без свежести), #2659 (паушальный TTL), #2583 (квартальный индекс нормирован на медиану ЕКБ), #2674 (код написан и ни разу не сработал). ## Порядок Сначала конфиг, замер, и только потом схема — иначе весь эффект спишут на разделение таблиц и не поймут, что сработало.
lekss361 added the
performance
priority/p1
scope/backend
scope/db
tech-debt
tradein
labels 2026-08-20 17:50:50 +00:00
Author
Owner

Переезд состоялся 25.08, но начисто базу не пересобирали — перенесли как есть через pg_dump -Fc / pg_restore. Эпик остаётся открытым: ни один из дефектов модели не тронут. При этом переезд дал бесплатную проверку центрального замера, и её стоит зафиксировать.

Предсказание «21 ГБ — иллюзия, раздутость при перезаливке не воспроизводится» подтвердилось. Факт на Poincare после переноса:

Метрика Было на Beget Стало после перезаливки
БД tradein ~21 ГБ 2391 МБ
том tradein-postgres-data 22,4 ГБ 3855 МБ
listings total 19 ГБ (из них TOAST 15 ГБ) 348 МБ (heap 158 МБ, индексы 92 МБ, TOAST ~98 МБ)
индексов на listings 30 29

То есть девятикратное сжатие, как и обещала репетиция. Оценка «~855 МБ мёртвых и дублирующих индексов» тоже сходится: после пересборки все индексы listings весят 92 МБ.

Но это не решение, а отсрочка. Причина раздутости не тронута: три несовместимых цикла записи по-прежнему живут в одном кортеже, апсерт по-прежнему переписывает 41 колонку безусловно, listing_source_snapshot по-прежнему пишет полную копию каждые сутки (это уже крупнейшая таблица базы — 991 МБ против 440 МБ у самих listings). Раздутость отрастёт с той же скоростью, что и раньше — просто счётчик обнулился.

Что это меняет для Москвы. Проекция «~125 ГБ под listings при переносе как есть» считалась от раздутого состояния. От чистого база на московском объёме (×6,6) даёт порядка 15-25 ГБ — но только в момент заливки, дальше рост по той же кривой. Ограничение по диску при этом снято: на Poincare 910G, занято 95G, свободно 769G против 145 ГБ всего на Beget. Так что дисковое давление больше не аргумент в пользу срочности — аргументом остаются WAL, blks_read мимо shared_buffers и «город входит в ценовую формулу текстом», который остаётся настоящим блокером Москвы.

Порядок из задачи («сначала конфиг, замер, потом схема») сохраняет силу — сейчас как раз чистая точка отсчёта для замера, раздутость не искажает картину.

Переезд состоялся 25.08, но **начисто базу не пересобирали** — перенесли как есть через `pg_dump -Fc` / `pg_restore`. Эпик остаётся открытым: ни один из дефектов модели не тронут. При этом переезд дал бесплатную проверку центрального замера, и её стоит зафиксировать. **Предсказание «21 ГБ — иллюзия, раздутость при перезаливке не воспроизводится» подтвердилось.** Факт на Poincare после переноса: | Метрика | Было на Beget | Стало после перезаливки | |---|---|---| | БД `tradein` | ~21 ГБ | **2391 МБ** | | том `tradein-postgres-data` | 22,4 ГБ | **3855 МБ** | | `listings` total | 19 ГБ (из них TOAST 15 ГБ) | **348 МБ** (heap 158 МБ, индексы 92 МБ, TOAST ~98 МБ) | | индексов на `listings` | 30 | 29 | То есть девятикратное сжатие, как и обещала репетиция. Оценка «~855 МБ мёртвых и дублирующих индексов» тоже сходится: после пересборки все индексы `listings` весят 92 МБ. **Но это не решение, а отсрочка.** Причина раздутости не тронута: три несовместимых цикла записи по-прежнему живут в одном кортеже, апсерт по-прежнему переписывает 41 колонку безусловно, `listing_source_snapshot` по-прежнему пишет полную копию каждые сутки (это уже крупнейшая таблица базы — 991 МБ против 440 МБ у самих `listings`). Раздутость отрастёт с той же скоростью, что и раньше — просто счётчик обнулился. **Что это меняет для Москвы.** Проекция «~125 ГБ под `listings` при переносе как есть» считалась от раздутого состояния. От чистого база на московском объёме (×6,6) даёт порядка 15-25 ГБ — но только в момент заливки, дальше рост по той же кривой. Ограничение по диску при этом снято: на Poincare `910G, занято 95G, свободно 769G` против 145 ГБ всего на Beget. Так что дисковое давление больше не аргумент в пользу срочности — аргументом остаются WAL, `blks_read` мимо `shared_buffers` и «город входит в ценовую формулу текстом», который остаётся настоящим блокером Москвы. Порядок из задачи («сначала конфиг, замер, потом схема») сохраняет силу — сейчас как раз чистая точка отсчёта для замера, раздутость не искажает картину.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2989
No description provided.