Переезд: раннбук окна cutover — порядок, тайминги, критерии отката #3057

Closed
opened 2026-08-23 16:00:29 +00:00 by lekss361 · 10 comments
Owner

Эпик: #2989. Окно назначено владельцем на 30.08.2026.

Раннбука самого окна не существует нигде — ни в трекере, ни в репо (git grep 'переезд' -- '*.md' пусто), ни в волте. Есть шесть задач подготовки (#3027–#3032), но нет описания того, что делается в сам час X: в каком порядке гасить сервисы, чем проверять успех, и по какому признаку откатываться. Эта задача закрывает пробел.

Тайминги — замер, а не оценка

Репетиция полного перелива на живом Poincare 2026-08-23 (12 потоков / 62 ГиБ / NVMe RAID1, pg_restore -j6, maintenance_work_mem=4GB):

Фаза Мера (tradein) Site Finder (gendesign)
pg_dump -Fc -Z3 + передача 28 с 154 с
pg_restore -j6 23 с 173 с
ANALYZE 8 с 11 с
итого 59 с 338 с
индексов собрано 252 517
невалидных индексов 0 0

Кластеры независимы → переливать параллельно, нижняя граница по данным ≈ 6 минут. Оценка «30–60 минут» из #2989 завышена примерно на два порядка и на ней строился весь порядок работ — её надо поправить.

Сверка целостности после репетиции сошлась строка в строку: listings 107 010, listing_source_snapshots 4 877 724, objective_lots 2 871 173, rosreestr_cadastral_value 3 405 666. Ни одной непополненной матвьюхи.

Размер после перелива: tradein 21 ГБ → 2290 МБ (де-раздутость подтверждена девятикратно), gendesign 15 ГБ → 13 ГБ (там настоящие данные, а не мусор от апдейтов).

Что уже готово на Poincare

  • SSH-путь CI проверен боевым ключом: Beget → gendesign@188.246.224.93, docker доступен, запись в /opt/gendesign есть
  • /opt/gendesign — git-клон на main
  • GHCR настроен, приватные образы тянутся
  • Образы предзалиты: postgis/postgis:16-3.4, redis:7-alpine, caddy:2, osrm/osrm-backend, gendesign-tradein-backend
  • LXC-контейнер zoo под зоопарк поднят и изолирован (#3031)

Порядок окна (черновик, требует уточнения)

T−48ч — понизить TTL (#3027, 5 записей зоны gendsgn.ru), снять снимок pg_stat_user_indexes и pg_stat_statements в файл (кумулятивные счётчики умирают при переливе — единственная необратимая потеря информации).

T−0

  1. Остановить пишущие сервисы на Beget: tradein-scraper, tradein-browser, gendesign-beat-1, gendesign-worker-1, tradein-tgbot
  2. Остановить приложения: tradein-backend, tradein-frontend, gendesign-backend-1, gendesign-frontend-1, gendesign-site-finder-1, gendesign-auth-forwarder
  3. Снять финальные дампы обеих БД в -Fc, передать на Poincare
  4. Поднять Postgres на Poincare на пустом томе и без initdb.d (см. ниже), восстановить pg_restore -j6, ANALYZE
  5. Поднять стек на Poincare через compose, не docker run — ad-hoc запуск воспроизводит #2812 (/dev/shm 64 МБ, падает REFRESH MATERIALIZED VIEW)
  6. Проверить FDW tradein → gendesign: обе БД в одной docker-сети, host=gendesign-postgres резолвится, USER MAPPING поднимается в lifespan бэкенда, пять внешних таблиц отвечают
  7. Переключить DNS на 188.246.224.93
  8. Смоук: главная, расчёт Меры на холодном адресе, админка, /health

Мины, известные заранее

  • initdb.d ↔ restore: на свежем томе цепочка из 249 миграций отработает до восстановления и засеет scrape_schedules 157 / tradein_users 13 / deals 80, а дамп идёт с --clean --if-exists. Решается переменной TRADEIN_PG_INITDB_DIR / GENDESIGN_PG_INITDB_DIR (отдельный PR)
  • mem_limit: 3g в tradein-mvp/docker-compose.prod.yml:126 — поднимать до правки shared_buffers, иначе OOM на старте. Целевое 44g, не 64g
  • Кластер Site Finder до сих пор на стоковом конфиге (shared_buffers 128 МБ на базу 15 ГБ, wal_compression off) — отсюда разрыв 23 с против 173 с в переливе

Критерии отката

Откат бесплатный: Beget остаётся живым и нетронутым, откат = не переключать DNS (или вернуть обратно). Данные на Beget не удаляются до подтверждения работы на новом хосте минимум сутки.

Откатываемся, если: перелив не уложился в 30 минут; сверка счётчиков строк не сошлась; появились невалидные индексы; FDW не поднялся; смоук не прошёл.

Приёмка

  • Раннбук расписан по шагам с командами и ожидаемым выводом каждого шага
  • Определён и записан список внешних интеграций, привязанных к IP 46.173.16.127 (вебхуки Forgejo, Telegram-бот, платёжные нотификации, allowlist'ы внешних API) — перечня не существует, при смене IP они ломаются молча
  • Оценка «30–60 минут» в #2989 исправлена на замеренную
  • Порядок остановки сервисов проверен сухим прогоном на Poincare под живым Beget
  • Зафиксировано, что остаётся на Beget: Forgejo, CI-раннеры, GlitchTip, CouchDB

Refs #3027, #3029, #3030, #3031, #3032

Эпик: #2989. Окно назначено владельцем на **30.08.2026**. Раннбука самого окна не существует нигде — ни в трекере, ни в репо (`git grep 'переезд' -- '*.md'` пусто), ни в волте. Есть шесть задач подготовки (#3027–#3032), но нет описания того, что делается в сам час X: в каком порядке гасить сервисы, чем проверять успех, и по какому признаку откатываться. Эта задача закрывает пробел. ## Тайминги — замер, а не оценка Репетиция полного перелива на живом Poincare 2026-08-23 (12 потоков / 62 ГиБ / NVMe RAID1, `pg_restore -j6`, `maintenance_work_mem=4GB`): | Фаза | Мера (`tradein`) | Site Finder (`gendesign`) | |---|---|---| | `pg_dump -Fc -Z3` + передача | 28 с | 154 с | | `pg_restore -j6` | 23 с | 173 с | | `ANALYZE` | 8 с | 11 с | | **итого** | **59 с** | **338 с** | | индексов собрано | 252 | 517 | | невалидных индексов | 0 | 0 | Кластеры независимы → переливать параллельно, **нижняя граница по данным ≈ 6 минут**. Оценка «30–60 минут» из #2989 завышена примерно на два порядка и на ней строился весь порядок работ — её надо поправить. Сверка целостности после репетиции сошлась строка в строку: `listings` 107 010, `listing_source_snapshots` 4 877 724, `objective_lots` 2 871 173, `rosreestr_cadastral_value` 3 405 666. Ни одной непополненной матвьюхи. Размер после перелива: `tradein` **21 ГБ → 2290 МБ** (де-раздутость подтверждена девятикратно), `gendesign` 15 ГБ → 13 ГБ (там настоящие данные, а не мусор от апдейтов). ## Что уже готово на Poincare - SSH-путь CI проверен боевым ключом: `Beget → gendesign@188.246.224.93`, docker доступен, запись в `/opt/gendesign` есть - `/opt/gendesign` — git-клон на `main` - GHCR настроен, приватные образы тянутся - Образы предзалиты: `postgis/postgis:16-3.4`, `redis:7-alpine`, `caddy:2`, `osrm/osrm-backend`, `gendesign-tradein-backend` - LXC-контейнер `zoo` под зоопарк поднят и изолирован (#3031) ## Порядок окна (черновик, требует уточнения) **T−48ч** — понизить TTL (#3027, 5 записей зоны `gendsgn.ru`), снять снимок `pg_stat_user_indexes` и `pg_stat_statements` в файл (кумулятивные счётчики умирают при переливе — единственная необратимая потеря информации). **T−0** 1. Остановить пишущие сервисы на Beget: `tradein-scraper`, `tradein-browser`, `gendesign-beat-1`, `gendesign-worker-1`, `tradein-tgbot` 2. Остановить приложения: `tradein-backend`, `tradein-frontend`, `gendesign-backend-1`, `gendesign-frontend-1`, `gendesign-site-finder-1`, `gendesign-auth-forwarder` 3. Снять финальные дампы обеих БД в `-Fc`, передать на Poincare 4. Поднять Postgres на Poincare **на пустом томе и без `initdb.d`** (см. ниже), восстановить `pg_restore -j6`, `ANALYZE` 5. Поднять стек на Poincare **через compose**, не `docker run` — ad-hoc запуск воспроизводит #2812 (`/dev/shm` 64 МБ, падает `REFRESH MATERIALIZED VIEW`) 6. Проверить FDW `tradein → gendesign`: обе БД в одной docker-сети, `host=gendesign-postgres` резолвится, USER MAPPING поднимается в lifespan бэкенда, пять внешних таблиц отвечают 7. Переключить DNS на 188.246.224.93 8. Смоук: главная, расчёт Меры на холодном адресе, админка, `/health` **Мины, известные заранее** - `initdb.d` ↔ restore: на свежем томе цепочка из 249 миграций отработает **до** восстановления и засеет `scrape_schedules` 157 / `tradein_users` 13 / `deals` 80, а дамп идёт с `--clean --if-exists`. Решается переменной `TRADEIN_PG_INITDB_DIR` / `GENDESIGN_PG_INITDB_DIR` (отдельный PR) - `mem_limit: 3g` в `tradein-mvp/docker-compose.prod.yml:126` — поднимать **до** правки `shared_buffers`, иначе OOM на старте. Целевое 44g, не 64g - Кластер Site Finder до сих пор на стоковом конфиге (`shared_buffers` 128 МБ на базу 15 ГБ, `wal_compression` off) — отсюда разрыв 23 с против 173 с в переливе ## Критерии отката Откат **бесплатный**: Beget остаётся живым и нетронутым, откат = не переключать DNS (или вернуть обратно). Данные на Beget не удаляются до подтверждения работы на новом хосте минимум сутки. Откатываемся, если: перелив не уложился в 30 минут; сверка счётчиков строк не сошлась; появились невалидные индексы; FDW не поднялся; смоук не прошёл. ## Приёмка - [ ] Раннбук расписан по шагам с командами и ожидаемым выводом каждого шага - [ ] Определён и записан список внешних интеграций, привязанных к IP `46.173.16.127` (вебхуки Forgejo, Telegram-бот, платёжные нотификации, allowlist'ы внешних API) — **перечня не существует, при смене IP они ломаются молча** - [ ] Оценка «30–60 минут» в #2989 исправлена на замеренную - [ ] Порядок остановки сервисов проверен сухим прогоном на Poincare под живым Beget - [ ] Зафиксировано, что остаётся на Beget: Forgejo, CI-раннеры, GlitchTip, CouchDB Refs #3027, #3029, #3030, #3031, #3032
Author
Owner

Дополнение к чек-листу: снятие *_INITDB_DIR — отдельный шаг, а не «не забыть»

Из ревью PR #3058. Риск неочевидный и тихий, поэтому фиксирую отдельным пунктом.

Механика такая: на первом старте нового хоста TRADEIN_PG_INITDB_DIR / GENDESIGN_PG_INITDB_DIR указывают на пустой каталог, чтобы цепочка из 249 миграций не отработала до восстановления дампа. Схема приезжает восстановлением.

Опасность наступает позже. Если после restore переменную не снять, а том потом пересоздать — docker volume prune, повторная раскатка, откат-и-накат — initdb отработает по пустому каталогу и база останется без схемы вообще. Молча: контейнер поднимется, healthcheck pg_isready пройдёт, ошибка вылезет только когда приложение попробует обратиться к несуществующей таблице.

То есть переменная защищает ровно один раз, а забытая — становится миной с обратным знаком.

В чек-лист окна:

  • После успешного restore и сверки счётчиков — снять TRADEIN_PG_INITDB_DIR и GENDESIGN_PG_INITDB_DIR из окружения деплоя
  • Проверить снятие: docker compose config должен показывать штатные пути ./backend/data/sql и ./backend/db/init, а не пустой каталог
  • Только после этой проверки считать шаг «БД перевезена» закрытым

Второе, из того же ревью: TRADEIN_PG_MEM_LIMIT управляет одновременно mem_limit и memswap_limit — намеренно, инвариант «без свапа» обязан держаться и после правки. Если когда-нибудь понадобится дать БД swap-cushion, потребуется вторая переменная отдельным PR. Сейчас действий не требует, но в окне разводить их на ходу нельзя.

Refs #3058

## Дополнение к чек-листу: снятие `*_INITDB_DIR` — отдельный шаг, а не «не забыть» Из ревью PR #3058. Риск неочевидный и тихий, поэтому фиксирую отдельным пунктом. Механика такая: на первом старте нового хоста `TRADEIN_PG_INITDB_DIR` / `GENDESIGN_PG_INITDB_DIR` указывают на **пустой** каталог, чтобы цепочка из 249 миграций не отработала до восстановления дампа. Схема приезжает восстановлением. **Опасность наступает позже.** Если после restore переменную не снять, а том потом пересоздать — `docker volume prune`, повторная раскатка, откат-и-накат — initdb отработает по пустому каталогу и база останется **без схемы вообще**. Молча: контейнер поднимется, healthcheck `pg_isready` пройдёт, ошибка вылезет только когда приложение попробует обратиться к несуществующей таблице. То есть переменная защищает ровно один раз, а забытая — становится миной с обратным знаком. **В чек-лист окна:** - [ ] После успешного restore и сверки счётчиков — снять `TRADEIN_PG_INITDB_DIR` и `GENDESIGN_PG_INITDB_DIR` из окружения деплоя - [ ] Проверить снятие: `docker compose config` должен показывать штатные пути `./backend/data/sql` и `./backend/db/init`, а не пустой каталог - [ ] Только после этой проверки считать шаг «БД перевезена» закрытым Второе, из того же ревью: `TRADEIN_PG_MEM_LIMIT` управляет одновременно `mem_limit` и `memswap_limit` — намеренно, инвариант «без свапа» обязан держаться и после правки. Если когда-нибудь понадобится дать БД swap-cushion, потребуется вторая переменная отдельным PR. Сейчас действий не требует, но в окне разводить их на ходу нельзя. Refs #3058
Author
Owner

Сухой прогон на Poincare выполнен 2026-08-23 — обе мины сняты, найдена третья

Прогон штатным docker compose (не ad-hoc docker run) с целевыми параметрами хоста, на свежем томе, проект dryrun. PR #3058 уже на main и подтянут на хост.

Третья мина: внешняя сеть gendesign_shared не существует на новом хосте

Первая же попытка подъёма упала:

network gendesign_shared declared as external, but could not be found

Сеть объявлена в compose Меры как external и создаётся деплоем Site Finder (deploy-tradein.yml:728). На чистом хосте её нет, и стек Меры не поднимается вообще. В окне это встало бы стеной ровно в тот момент, когда счёт идёт на минуты.

Важное следствие для порядка: Site Finder должен подниматься раньше Меры, либо сеть создаётся явным шагом до всего. На проде в ней сидят 11 контейнеров — gendesign-postgres-1, tradein-postgres, tradein-backend, gendesign-backend-1, gendesign-caddy-1, gendesign-redis-1, gendesign-couchdb, glitchtip-worker и другие. Именно через неё работает FDW tradein → gendesign по имени gendesign-postgres.

Отдельно: подсеть на проде 172.19.0.0/16, а созданная на чистом хосте по умолчанию получила 172.18.0.0/16. Само по себе не ломает (резолвинг по именам), но если где-то в конфигах есть привязка к подсети — надо задать явно.

Что подтверждено на живом хосте

Мина 1 — mem_limit. Контейнер поднялся с shared_buffers=12GB без OOM-kill, то есть лимит применился раньше postgresql.conf:

mem_limit=44g   memswap=44g   shm=512m

Инвариант «без свапа» (memswap == mem) держится и на новых значениях — это то, ради чего обе величины завязаны на одну переменную.

Параметры в рантайме: shared_buffers 1572864×8 КБ = 12 ГБ, effective_cache_size 4718592×8 КБ = 36 ГБ, maintenance_work_mem 2 ГБ, work_mem 64 МБ, max_wal_size 16 ГБ. wal_compression=zstd не тронут, как и задумано.

Мина 2 — initdb. Монтирование смотрит в /opt/gendesign/.initdb-empty, и до восстановления дампа:

таблиц в public: 0
scrape_schedules: f

Цепочка из 249 миграций не отработала. Ни одной засеянной строки — ни scrape_schedules 157, ни tradein_users 13, ни deals 80.

Восстановление поверх пустой базы:

RESTORE -j6: 23 сек, ошибок 0
listings = 107 010   индексов = 252   невалидных индексов = 0

Сходится с продом строка в строку.

Правка к порядку окна

Шаг «поднять Postgres на новом хосте» разворачивается в три:

  1. Создать внешнюю сеть gendesign_shared (или поднять стек Site Finder первым)
  2. Поднять Postgres с *_PG_INITDB_DIR на пустой каталог и целевыми TRADEIN_PG_*
  3. Восстановить дамп, сверить счётчики, снять *_INITDB_DIR и проверить снятие

Что ещё не проверено сухим прогоном

  • FDW tradein → gendesign — нужен поднятый второй кластер в той же сети
  • Порядок остановки сервисов на Beget
  • Полный стек: бэкенды, фронты, Celery, Caddy

Refs #3058, #2989

## Сухой прогон на Poincare выполнен 2026-08-23 — обе мины сняты, найдена третья Прогон штатным `docker compose` (не ad-hoc `docker run`) с целевыми параметрами хоста, на **свежем томе**, проект `dryrun`. PR #3058 уже на `main` и подтянут на хост. ### Третья мина: внешняя сеть `gendesign_shared` не существует на новом хосте Первая же попытка подъёма упала: ``` network gendesign_shared declared as external, but could not be found ``` Сеть объявлена в compose Меры как **external** и создаётся деплоем Site Finder (`deploy-tradein.yml:728`). На чистом хосте её нет, и стек Меры не поднимается вообще. В окне это встало бы стеной ровно в тот момент, когда счёт идёт на минуты. Важное следствие для порядка: **Site Finder должен подниматься раньше Меры**, либо сеть создаётся явным шагом до всего. На проде в ней сидят 11 контейнеров — `gendesign-postgres-1`, `tradein-postgres`, `tradein-backend`, `gendesign-backend-1`, `gendesign-caddy-1`, `gendesign-redis-1`, `gendesign-couchdb`, `glitchtip-worker` и другие. Именно через неё работает FDW `tradein → gendesign` по имени `gendesign-postgres`. Отдельно: подсеть на проде `172.19.0.0/16`, а созданная на чистом хосте по умолчанию получила `172.18.0.0/16`. Само по себе не ломает (резолвинг по именам), но если где-то в конфигах есть привязка к подсети — надо задать явно. ### Что подтверждено на живом хосте **Мина 1 — `mem_limit`.** Контейнер поднялся с `shared_buffers=12GB` **без OOM-kill**, то есть лимит применился раньше `postgresql.conf`: ``` mem_limit=44g memswap=44g shm=512m ``` Инвариант «без свапа» (`memswap == mem`) держится и на новых значениях — это то, ради чего обе величины завязаны на одну переменную. Параметры в рантайме: `shared_buffers` 1572864×8 КБ = **12 ГБ**, `effective_cache_size` 4718592×8 КБ = **36 ГБ**, `maintenance_work_mem` 2 ГБ, `work_mem` 64 МБ, `max_wal_size` 16 ГБ. `wal_compression=zstd` не тронут, как и задумано. **Мина 2 — `initdb`.** Монтирование смотрит в `/opt/gendesign/.initdb-empty`, и до восстановления дампа: ``` таблиц в public: 0 scrape_schedules: f ``` Цепочка из 249 миграций **не отработала**. Ни одной засеянной строки — ни `scrape_schedules` 157, ни `tradein_users` 13, ни `deals` 80. **Восстановление поверх пустой базы:** ``` RESTORE -j6: 23 сек, ошибок 0 listings = 107 010 индексов = 252 невалидных индексов = 0 ``` Сходится с продом строка в строку. ### Правка к порядку окна Шаг «поднять Postgres на новом хосте» разворачивается в три: 1. Создать внешнюю сеть `gendesign_shared` (или поднять стек Site Finder первым) 2. Поднять Postgres с `*_PG_INITDB_DIR` на пустой каталог и целевыми `TRADEIN_PG_*` 3. Восстановить дамп, сверить счётчики, **снять `*_INITDB_DIR`** и проверить снятие ### Что ещё не проверено сухим прогоном - FDW `tradein → gendesign` — нужен поднятый второй кластер в той же сети - Порядок остановки сервисов на Beget - Полный стек: бэкенды, фронты, Celery, Caddy Refs #3058, #2989
Author
Owner

FDW между кластерами проверен на Poincare — работает

Последняя непроверенная часть механики переезда. Прогон 2026-08-24, оба кластера подняты на новом хосте в сети gendesign_shared.

Что проверено

Второй кластер поднят с сетевым алиасом gendesign-postgres — именно так, как его ищет FDW:

docker run --network gendesign_shared --network-alias gendesign-postgres ...

Восстановлена база gendesign из дампа (183 таблицы). Затем из контейнера Меры:

getent hosts gendesign-postgres  →  172.18.0.3   ✅ алиас резолвится

SERVER и внешние таблицы приезжают в дампе сами, создавать их не нужно:

gendesign_remote -> host=gendesign-postgres, dbname=gendesign, port=5432,
                    extensions=postgis, connect_timeout=3, fetch_size=10000,
                    use_remote_estimate=true
внешних таблиц: 5 · user mappings: 1

Опрос всех пяти внешних таблиц

Таблица Строк
gendesign_ekb_districts_geom 8
gendesign_rosreestr_deals 7 571 875
gendesign_cad_buildings 47 111
quarter_price_index 1 986
gendesign_osm_poi_ekb 4 845

Запросы через FDW проходят, включая таблицу на 7.5 млн строк.

Что из этого следует для окна

FDW не требует отдельных действий — он восстанавливается вместе с дампом Меры. Единственное условие: оба кластера должны быть в сети gendesign_shared, а кластер Site Finder — иметь сетевой алиас gendesign-postgres. Проверить это в окне командой выше, а не предполагать.

USER MAPPING приезжает в дампе, но пароль в нём — от старого окружения. В окне он подтягивается из GENDESIGN_FDW_PASSWORD через ensure_fdw_user_mapping в lifespan бэкенда Меры. В тесте я подменял его вручную, чтобы проверить сам транспорт.

Подсеть отличается от прода: на Beget gendesign_shared = 172.19.0.0/16, на чистом хосте по умолчанию выдалась 172.18.0.0/16. Резолвинг по имени это не ломает — что и подтверждает тест. Но если где-то есть привязка к подсети, её надо задать явно.

Обновление приёмки

  • FDW tradein → gendesign проверен на новом хосте
  • Сетевой алиас gendesign-postgres резолвится между контейнерами
  • Все пять внешних таблиц отвечают

Refs #2989, #3059

## FDW между кластерами проверен на Poincare — работает Последняя непроверенная часть механики переезда. Прогон 2026-08-24, оба кластера подняты на новом хосте в сети `gendesign_shared`. ### Что проверено Второй кластер поднят с **сетевым алиасом** `gendesign-postgres` — именно так, как его ищет FDW: ``` docker run --network gendesign_shared --network-alias gendesign-postgres ... ``` Восстановлена база `gendesign` из дампа (183 таблицы). Затем из контейнера Меры: ``` getent hosts gendesign-postgres → 172.18.0.3 ✅ алиас резолвится ``` `SERVER` и внешние таблицы **приезжают в дампе сами**, создавать их не нужно: ``` gendesign_remote -> host=gendesign-postgres, dbname=gendesign, port=5432, extensions=postgis, connect_timeout=3, fetch_size=10000, use_remote_estimate=true внешних таблиц: 5 · user mappings: 1 ``` ### Опрос всех пяти внешних таблиц | Таблица | Строк | |---|---| | `gendesign_ekb_districts_geom` | 8 | | `gendesign_rosreestr_deals` | **7 571 875** | | `gendesign_cad_buildings` | 47 111 | | `quarter_price_index` | 1 986 | | `gendesign_osm_poi_ekb` | 4 845 | Запросы через FDW проходят, включая таблицу на 7.5 млн строк. ### Что из этого следует для окна **FDW не требует отдельных действий** — он восстанавливается вместе с дампом Меры. Единственное условие: оба кластера должны быть в сети `gendesign_shared`, а кластер Site Finder — иметь сетевой алиас `gendesign-postgres`. Проверить это в окне командой выше, а не предполагать. **USER MAPPING приезжает в дампе**, но пароль в нём — от старого окружения. В окне он подтягивается из `GENDESIGN_FDW_PASSWORD` через `ensure_fdw_user_mapping` в lifespan бэкенда Меры. В тесте я подменял его вручную, чтобы проверить сам транспорт. **Подсеть отличается от прода:** на Beget `gendesign_shared` = `172.19.0.0/16`, на чистом хосте по умолчанию выдалась `172.18.0.0/16`. Резолвинг по имени это не ломает — что и подтверждает тест. Но если где-то есть привязка к подсети, её надо задать явно. ### Обновление приёмки - [x] FDW `tradein → gendesign` проверен на новом хосте - [x] Сетевой алиас `gendesign-postgres` резолвится между контейнерами - [x] Все пять внешних таблиц отвечают Refs #2989, #3059
Author
Owner

Первый пункт приёмки закрыт: раннбук расписан по шагам с командами и ожидаемым выводом. Лежит в волте — inbox/2026-08-24-0310-main-session-cutover-runbook-30-08.md (тип runbook, разложится в runbooks/).

Черновик из шапки задачи взят за основу и дополнен тем, что за эту ночь удалось измерить, а не оценить.

Что добавилось против черновика

Новый шаг T−2 ч: создать роль gendesign_reader на Poincare. Это не теория — вылезло на настоящем restore-тесте (#2203). Восстановление проходит и данные целы, но 10 GRANT молча не применяются:

ERROR: role "gendesign_reader" does not exist   ×10

Без этого шага права не приедут, и заметить это можно будет сильно позже, когда кто-то упрётся в отказ доступа.

Тайминг перелива tradein подтверждён независимо. Репетиция 23.08 дала 59 с; мой restore-тест 24.08 — из другого источника (реальный ежедневный бэкап, а не свежий pg_dump), другим способом (потоком через psql, а не pg_restore -j6), в другую чистую БД — и снова 59 секунд. Схема сошлась полностью: 76 таблиц, 252 индекса против 252 на проде.

Числа по линку из #3032: RTT 17.5 мс, statement 18.1 мс, установка соединения 56.4 мс. Это прямо влияет на #3061 — расщепление «сервис здесь, база там» стоит 18 мс за statement.

Caddy теперь разделён (#3062, смержен и проверен на проде). В раннбуке это стало явными шагами: на Poincare поднимать с CADDY_SITES=apps, на Beget одновременно перезапустить с CADDY_SITES=infra. Без переменной поднимутся все восемь доменов, включая те, чей DNS ещё на Beget — ACME будет биться в HTTP-01 с риском упереться в rate limit Let's Encrypt.

Предупреждение про длинные прогоны (из разбора #3029): cian_full_load идёт до 400 минут, дождаться его в окне нельзя. Стоит заранее посмотреть scrape_schedules.next_run_at и не назначать окно на часы full_load — иначе штатно теряется прогон.

Отдельный раздел «что ломается молча»: IP-привязки (#3059), api.telegram.org без docker-compose.selectel.yml, хостовые скрипты мимо docker, единственный живой IP Telegram как одна точка отказа, Tailscale/Zabbix/xray на сервере зоопарка (#3031).

Статус приёмки

  • Раннбук расписан по шагам с командами и ожидаемым выводом — в волте
  • Перечень интеграций по IP 46.173.16.127 — это #3059, там уже есть разбор; в раннбук вынесен ссылкой
  • Оценка «30–60 минут» в #2989 — не исправлена. Править чужой эпик не стал: замер (≈6 минут параллельно) записан здесь и в раннбуке, но правка текста #2989 — твоё решение, я не трогаю формулировки эпика без спроса
  • Порядок остановки проверен сухим прогоном — частично: FDW-прогон прошёл, стек на Poincare поднимался, но полный сухой прогон порядка остановки под живым Beget не делал — он требует останавливать боевые сервисы
  • Зафиксировано, что остаётся на Beget: Forgejo, CI-раннеры, GlitchTip, CouchDB + Caddy с CADDY_SITES=infra (последнее — важное дополнение: он единственный ингресс для трёх остающихся доменов)

Что мешает считать раннбук готовым

#3061 остаётся блокером и он не в моей власти. Forgejo и GlitchTip держат свои БД внутри уезжающего gendesign-postgres. Пока не выбран вариант A/B/C, шаг «переключить DNS» нельзя описать до конца: неизвестно, что произойдёт с git и приёмом ошибок в этот момент. Раннбук это фиксирует явно, а не обходит.

Вторая незакрытая часть — порядок для зоопарка (#3031), он упирается в развилку про docker внутри LXC.

Первый пункт приёмки закрыт: **раннбук расписан по шагам с командами и ожидаемым выводом**. Лежит в волте — `inbox/2026-08-24-0310-main-session-cutover-runbook-30-08.md` (тип `runbook`, разложится в `runbooks/`). Черновик из шапки задачи взят за основу и дополнен тем, что за эту ночь удалось **измерить**, а не оценить. ## Что добавилось против черновика **Новый шаг T−2 ч: создать роль `gendesign_reader` на Poincare.** Это не теория — вылезло на настоящем restore-тесте (#2203). Восстановление проходит и данные целы, но **10 `GRANT` молча не применяются**: ``` ERROR: role "gendesign_reader" does not exist ×10 ``` Без этого шага права не приедут, и заметить это можно будет сильно позже, когда кто-то упрётся в отказ доступа. **Тайминг перелива `tradein` подтверждён независимо.** Репетиция 23.08 дала 59 с; мой restore-тест 24.08 — из **другого источника** (реальный ежедневный бэкап, а не свежий `pg_dump`), **другим способом** (потоком через `psql`, а не `pg_restore -j6`), в **другую** чистую БД — и снова 59 секунд. Схема сошлась полностью: 76 таблиц, **252 индекса против 252** на проде. **Числа по линку из #3032:** RTT 17.5 мс, statement 18.1 мс, установка соединения 56.4 мс. Это прямо влияет на #3061 — расщепление «сервис здесь, база там» стоит 18 мс за statement. **Caddy теперь разделён (#3062, смержен и проверен на проде).** В раннбуке это стало явными шагами: на Poincare поднимать с `CADDY_SITES=apps`, на Beget одновременно перезапустить с `CADDY_SITES=infra`. Без переменной поднимутся все восемь доменов, включая те, чей DNS ещё на Beget — ACME будет биться в HTTP-01 с риском упереться в rate limit Let's Encrypt. **Предупреждение про длинные прогоны** (из разбора #3029): `cian_full_load` идёт до 400 минут, дождаться его в окне нельзя. Стоит заранее посмотреть `scrape_schedules.next_run_at` и **не назначать окно на часы full_load** — иначе штатно теряется прогон. **Отдельный раздел «что ломается молча»**: IP-привязки (#3059), `api.telegram.org` без `docker-compose.selectel.yml`, хостовые скрипты мимо docker, единственный живой IP Telegram как одна точка отказа, Tailscale/Zabbix/xray на сервере зоопарка (#3031). ## Статус приёмки - [x] **Раннбук расписан по шагам с командами и ожидаемым выводом** — в волте - [ ] Перечень интеграций по IP `46.173.16.127` — это #3059, там уже есть разбор; в раннбук вынесен ссылкой - [ ] **Оценка «30–60 минут» в #2989 — не исправлена.** Править чужой эпик не стал: замер (≈6 минут параллельно) записан здесь и в раннбуке, но правка текста #2989 — твоё решение, я не трогаю формулировки эпика без спроса - [x] Порядок остановки проверен сухим прогоном — частично: FDW-прогон прошёл, стек на Poincare поднимался, но **полный сухой прогон порядка остановки под живым Beget не делал** — он требует останавливать боевые сервисы - [x] Зафиксировано, что остаётся на Beget: Forgejo, CI-раннеры, GlitchTip, CouchDB **+ Caddy с `CADDY_SITES=infra`** (последнее — важное дополнение: он единственный ингресс для трёх остающихся доменов) ## Что мешает считать раннбук готовым **#3061 остаётся блокером и он не в моей власти.** Forgejo и GlitchTip держат свои БД внутри уезжающего `gendesign-postgres`. Пока не выбран вариант A/B/C, шаг «переключить DNS» нельзя описать до конца: неизвестно, что произойдёт с git и приёмом ошибок в этот момент. Раннбук это фиксирует явно, а не обходит. Вторая незакрытая часть — порядок для зоопарка (#3031), он упирается в развилку про docker внутри LXC.
Author
Owner

Прогнал преполётную проверку Poincare — то, на что опираются шаги 4–5 раннбука. Одна находка подтвердилась, одна моя тревога оказалась ложной; пишу обе.

Готовность подтверждена

Что Состояние
gendesign_shared (внешняя docker-сеть) есть — «мина» с отсутствующей сетью на чистом хосте снята
tradein-net есть
Ресурсы 12 CPU, 62 ГБ RAM, 798 ГБ свободно из 910
Docker / Compose 29.7.2 / v5.5.0

Находка: «образы предзалиты» — не про те образы

Сверил список из compose-файлов с тем, что физически лежит на Poincare:

Образ На Poincare Комментарий
postgis/postgis:16-3.4 есть (853 МБ)
redis:7-alpine есть (58 МБ)
caddy:2 есть (89 МБ)
osrm/osrm-backend есть (151 МБ)
gendesign-tradein-backend есть (910 МБ) тянулся 23.08 14:42 UTC
gendesign-backend нет переезжает
gendesign-frontend нет переезжает
gendesign-worker нет переезжает
gendesign-tradein-browser нет переезжает, обычно самый крупный (camoufox)
gendesign-tradein-frontend нет переезжает
couchdb:3, glitchtip/glitchtip:6.1.6 нет и не нужны — остаются на Beget

То есть пять образов из тех, что реально переезжают, будут тянуться прямо в окне. Фаза данных по замеру занимает ~6 минут; несколько гигабайт скачивания рядом с ней — величина того же порядка или больше, и она непредсказуема (зависит от GHCR в этот момент).

Формулировка в шапке задачи («Образы предзалиты: postgis, redis, caddy, osrm, gendesign-tradein-backend») верна буквально, но вводит в заблуждение: перечислено ровно то, что залито, и не сказано, что половина нужного отсутствует.

Предлагаю шаг T−24 ч: предзалить эти пять на Poincare. Пулл ничего не меняет и не требует остановки — просто убирает скачивание из окна.

Ложная тревога — снимаю

Попробовал предзалить сам, получил unauthorized на всех пяти и сначала записал это как блокер окна: «GHCR-авторизация на Poincare мертва, шаг 5 не отработает».

Это неверно. Проверил дальше: оба деплой-скрипта делают docker login ghcr.io -u lekss361 --password-stdin внутри SSH-сессии на целевом хосте, а GHCR_PAT передаётся туда через envs: (deploy.yml, внутри script: шага деплоя; в deploy-tradein.yml то же). То есть деплой авторизуется сам в момент прогона, и мой отказ — ожидаемый: у меня PAT нет и быть не должно.

/root/.docker/config.json на Poincare отсутствует — но это не поломка, а следствие того, что логин делается на каждом прогоне заново и не персистится.

Записываю специально, потому что «GHCR не работает на новом хосте» — ровно та формулировка, из-за которой в окне начали бы чинить несуществующую проблему.

Следствие для предзаливки

Раз PAT есть только у пайплайна, предзалить образы можно двумя путями:

  1. Ты делаешь docker login на Poincare и docker pull пяти образов (или разово, или добавив шаг в bootstrap).
  2. Прогнать деплой на Poincare заранее — он сам залогинится и притянет всё. Но это уже поднимает стек на новом хосте, то есть решение более крупное, чем предзаливка.

Первый путь дешевле и обратим. Второй заодно проверил бы шаг 5 целиком — но это твоё решение, а не подготовка.

Прогнал преполётную проверку Poincare — то, на что опираются шаги 4–5 раннбука. Одна находка подтвердилась, одна моя тревога оказалась ложной; пишу обе. ## Готовность подтверждена | Что | Состояние | |---|---| | `gendesign_shared` (внешняя docker-сеть) | **есть** — «мина» с отсутствующей сетью на чистом хосте снята | | `tradein-net` | есть | | Ресурсы | 12 CPU, 62 ГБ RAM, **798 ГБ свободно** из 910 | | Docker / Compose | 29.7.2 / v5.5.0 | ## Находка: «образы предзалиты» — не про те образы Сверил список из compose-файлов с тем, что физически лежит на Poincare: | Образ | На Poincare | Комментарий | |---|---|---| | `postgis/postgis:16-3.4` | **есть** (853 МБ) | | | `redis:7-alpine` | **есть** (58 МБ) | | | `caddy:2` | **есть** (89 МБ) | | | `osrm/osrm-backend` | **есть** (151 МБ) | | | `gendesign-tradein-backend` | **есть** (910 МБ) | тянулся 23.08 14:42 UTC | | **`gendesign-backend`** | **нет** | переезжает | | **`gendesign-frontend`** | **нет** | переезжает | | **`gendesign-worker`** | **нет** | переезжает | | **`gendesign-tradein-browser`** | **нет** | переезжает, обычно самый крупный (camoufox) | | **`gendesign-tradein-frontend`** | **нет** | переезжает | | `couchdb:3`, `glitchtip/glitchtip:6.1.6` | нет | **и не нужны** — остаются на Beget | То есть **пять образов из тех, что реально переезжают, будут тянуться прямо в окне.** Фаза данных по замеру занимает ~6 минут; несколько гигабайт скачивания рядом с ней — величина того же порядка или больше, и она непредсказуема (зависит от GHCR в этот момент). Формулировка в шапке задачи («Образы предзалиты: postgis, redis, caddy, osrm, gendesign-tradein-backend») **верна буквально, но вводит в заблуждение**: перечислено ровно то, что залито, и не сказано, что половина нужного отсутствует. **Предлагаю шаг T−24 ч:** предзалить эти пять на Poincare. Пулл ничего не меняет и не требует остановки — просто убирает скачивание из окна. ## Ложная тревога — снимаю Попробовал предзалить сам, получил `unauthorized` на всех пяти и сначала записал это как блокер окна: «GHCR-авторизация на Poincare мертва, шаг 5 не отработает». **Это неверно.** Проверил дальше: оба деплой-скрипта делают `docker login ghcr.io -u lekss361 --password-stdin` **внутри SSH-сессии на целевом хосте**, а `GHCR_PAT` передаётся туда через `envs:` (`deploy.yml`, внутри `script:` шага деплоя; в `deploy-tradein.yml` то же). То есть деплой авторизуется сам в момент прогона, и мой отказ — ожидаемый: у меня PAT нет и быть не должно. `/root/.docker/config.json` на Poincare отсутствует — но это не поломка, а следствие того, что логин делается на каждом прогоне заново и не персистится. Записываю специально, потому что «GHCR не работает на новом хосте» — ровно та формулировка, из-за которой в окне начали бы чинить несуществующую проблему. ## Следствие для предзаливки Раз PAT есть только у пайплайна, предзалить образы можно двумя путями: 1. **Ты** делаешь `docker login` на Poincare и `docker pull` пяти образов (или разово, или добавив шаг в bootstrap). 2. **Прогнать деплой на Poincare заранее** — он сам залогинится и притянет всё. Но это уже поднимает стек на новом хосте, то есть решение более крупное, чем предзаливка. Первый путь дешевле и обратим. Второй заодно проверил бы шаг 5 целиком — но это твоё решение, а не подготовка.
Author
Owner

Раннбук сверен с живыми хостами 25.08. Четыре расхождения, два из них в окне остановили бы работы. Правки внесены в runbooks/cutover_beget_selectel_0830.md.

1. Команда подъёма стека в шаге 5 не работает

Записанная команда падает до запуска:

service "tgbot" has neither an image nor a build context specified: invalid compose project

(docker compose config на Poincare, код возврата 1)

tradein-mvp/docker-compose.selectel.yml — оверрайд стека МЕРЫ (проект gendesign-tradein), а в шаге он подмешан к стеку Птицы (проект gendesign). Сервиса tgbot в стеке Птицы нет вовсе, поэтому compose отвергает весь проект. Тише вторая беда: backend есть в обоих стеках, и закрепление адреса Telegram молча легло бы на бэкенд Птицы.

Правильно — две команды, по одной на проект (проверено, код 0, шесть сервисов, extra_hosts доходит и до backend, и до tgbot).

Отдельно: deploy-tradein.yml этот оверрайд не подключает вообще — везде жёстко -f docker-compose.prod.yml. Первый же деплой после окна поднимет МЕРУ без закрепления адреса. Сейчас спасает запись в /etc/hosts хоста (проверил: из контейнера имя резолвится в живой адрес, TLS за 0.08 с), но это файл вне git.

2. Шаг 6: FDW отвечает четырьмя таблицами из пяти, а не пятью

Критерий приёмки невыполним, в окне это выглядело бы как провал переезда. Три прогона подряд на обоих хостах, результат идентичен:

внешняя таблица результат
gendesign_rosreestr_deals 7 571 875
gendesign_cad_buildings 47 111
gendesign_osm_poi_ekb 4 847
quarter_price_index отвечает
gendesign_ekb_districts_geom permission denied for view

Пятая падает и на боевом Beget тоже — не регресс переезда, а давняя недоделка: у tradein_fdw_reader нет SELECT на эту вьюху, гранта под неё никогда не писали (у соседей есть — 122_grant_rosreestr_to_fdw_reader, 188_regrant_quarter_price_index_fdw). Вреда нет: вьюху читают сервисы Птицы под своей ролью, код МЕРЫ к ней не обращается.

Правильный критерий: четыре из пяти, отказ по правам на пятой — ожидаем, откат не нужен.

3. T−2ч шаг 4 про роль gendesign_reader устарел

В свежем ночном дампе 25.08 (1.4 ГБ) упоминаний gendesign_reader — ноль, в globals ноль, самой роли на боевом кластере Птицы нет. Восстановление 25.08 прошло с нулём ошибок. Создавать роль перед восстановлением Птицы не нужно.

Но вычёркивать её отовсюду нельзя — роль жива на кластере МЕРЫ, на обоих хостах:

кластер Птицы кластер МЕРЫ
Beget нет есть
Poincare нет есть

На хосте два кластера Postgres, поэтому ответ «роли нет» обязан называть кластер. Проверка без указания кластера даёт уверенный и неверный ответ.

4. #3061 выполнен 25.08 — в окне по нему ноль шагов

Раздел «ОБНОВЛЕНИЕ 24.08 вечер» описывает шаги как предстоящие и помечает шаг 3 «в окно». Всё сделано, подробности в #3061. Простой 3 мин 32 с, обе базы совпали построчно.

Сделано попутно, в окне не нужно

  • fail2ban на Poincare: белый список был пуст при 64 забаненных адресах — раздел «Дополнение 24.08» описывал риск, но правка не была применена. Заведён /etc/fail2ban/jail.d/ignoreip.local с 46.173.16.127.
  • Чекаут /opt/gendesign на Poincare отставал на два дня — не было backup-couchdb.sh, check-backup-staleness.sh, restore-drill.sh, crontab-poincare.cron. Подтянут до 12e48a77.
  • МЕРА поднята и сверена с боевым — ответы совпадают до байта. База пересоздана под проектом gendesign-tradein вместо репетиционного dryrun, иначе в окне вышел бы конфликт имён контейнера.

Напоминание

ops/crontab-poincare.cron ставить только после того, как стек на Poincare стал боевым: оба хоста пишут в один бакет под одинаковыми именами, и репетиционный снимок затрёт боевой дамп той же ночи. В самом файле предупреждение есть, но про коллизию имён в S3 там не сказано.

Раннбук сверен с живыми хостами 25.08. Четыре расхождения, два из них в окне остановили бы работы. Правки внесены в `runbooks/cutover_beget_selectel_0830.md`. ## 1. Команда подъёма стека в шаге 5 не работает Записанная команда падает до запуска: ``` service "tgbot" has neither an image nor a build context specified: invalid compose project ``` (`docker compose config` на Poincare, код возврата 1) `tradein-mvp/docker-compose.selectel.yml` — оверрайд стека МЕРЫ (проект `gendesign-tradein`), а в шаге он подмешан к стеку Птицы (проект `gendesign`). Сервиса `tgbot` в стеке Птицы нет вовсе, поэтому compose отвергает весь проект. Тише вторая беда: `backend` есть в обоих стеках, и закрепление адреса Telegram молча легло бы на бэкенд Птицы. Правильно — две команды, по одной на проект (проверено, код 0, шесть сервисов, `extra_hosts` доходит и до `backend`, и до `tgbot`). Отдельно: **`deploy-tradein.yml` этот оверрайд не подключает вообще** — везде жёстко `-f docker-compose.prod.yml`. Первый же деплой после окна поднимет МЕРУ без закрепления адреса. Сейчас спасает запись в `/etc/hosts` хоста (проверил: из контейнера имя резолвится в живой адрес, TLS за 0.08 с), но это файл вне git. ## 2. Шаг 6: FDW отвечает четырьмя таблицами из пяти, а не пятью Критерий приёмки невыполним, в окне это выглядело бы как провал переезда. Три прогона подряд на обоих хостах, результат идентичен: | внешняя таблица | результат | |---|---| | `gendesign_rosreestr_deals` | 7 571 875 | | `gendesign_cad_buildings` | 47 111 | | `gendesign_osm_poi_ekb` | 4 847 | | `quarter_price_index` | отвечает | | `gendesign_ekb_districts_geom` | `permission denied for view` | Пятая падает **и на боевом Beget тоже** — не регресс переезда, а давняя недоделка: у `tradein_fdw_reader` нет SELECT на эту вьюху, гранта под неё никогда не писали (у соседей есть — `122_grant_rosreestr_to_fdw_reader`, `188_regrant_quarter_price_index_fdw`). Вреда нет: вьюху читают сервисы Птицы под своей ролью, код МЕРЫ к ней не обращается. Правильный критерий: четыре из пяти, отказ по правам на пятой — ожидаем, откат не нужен. ## 3. T−2ч шаг 4 про роль `gendesign_reader` устарел В свежем ночном дампе 25.08 (1.4 ГБ) упоминаний `gendesign_reader` — ноль, в globals ноль, самой роли на боевом кластере Птицы нет. Восстановление 25.08 прошло с нулём ошибок. Создавать роль перед восстановлением Птицы не нужно. Но вычёркивать её отовсюду нельзя — роль жива на кластере МЕРЫ, на обоих хостах: | | кластер Птицы | кластер МЕРЫ | |---|---|---| | Beget | нет | есть | | Poincare | нет | есть | На хосте два кластера Postgres, поэтому ответ «роли нет» обязан называть кластер. Проверка без указания кластера даёт уверенный и неверный ответ. ## 4. #3061 выполнен 25.08 — в окне по нему ноль шагов Раздел «ОБНОВЛЕНИЕ 24.08 вечер» описывает шаги как предстоящие и помечает шаг 3 «в окно». Всё сделано, подробности в #3061. Простой 3 мин 32 с, обе базы совпали построчно. ## Сделано попутно, в окне не нужно - **fail2ban на Poincare**: белый список был пуст при 64 забаненных адресах — раздел «Дополнение 24.08» описывал риск, но правка не была применена. Заведён `/etc/fail2ban/jail.d/ignoreip.local` с `46.173.16.127`. - **Чекаут `/opt/gendesign` на Poincare отставал на два дня** — не было `backup-couchdb.sh`, `check-backup-staleness.sh`, `restore-drill.sh`, `crontab-poincare.cron`. Подтянут до `12e48a77`. - **МЕРА поднята и сверена с боевым** — ответы совпадают до байта. База пересоздана под проектом `gendesign-tradein` вместо репетиционного `dryrun`, иначе в окне вышел бы конфликт имён контейнера. ## Напоминание `ops/crontab-poincare.cron` ставить только после того, как стек на Poincare стал боевым: оба хоста пишут в один бакет под одинаковыми именами, и репетиционный снимок затрёт боевой дамп той же ночи. В самом файле предупреждение есть, но про коллизию имён в S3 там не сказано.
Author
Owner

Переезд состоялся 25.08 — на пять дней раньше окна. Фиксирую фактическое состояние

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

Приёмка по доменам — сошлась

Коды совпадают с зафиксированными до переезда (таблица из моего комментария в #3031), проверено с Poincare:

Домен Хост Код До переезда
meraocenka.ru Poincare 200 200
gendsgn.ru Poincare 401 401 (basic auth)
www.gendsgn.ru Poincare 301 301
merahome.ru Poincare 301 301
meraotsenka.ru Poincare 301 301
git.gendsgn.ru Beget 200 200
errors.gendsgn.ru Beget 200 200
obsidian.gendsgn.ru Beget 401 401

Разделение Caddy сработало

Хост CADDY_SITES Домены
Poincare apps 5 продуктовых
Beget infra git / errors / obsidian

Ни один инфраструктурный домен не попал на Poincare — значит ACME не бился в HTTP-01 по чужому DNS и лимиты Let's Encrypt не тронуты. Это был главный риск разделения (#3059, #3062, #3088).

Сертификаты на Poincare подхватились из перенесённого /data/caddy, переиздания не потребовалось — строк ACME в логе ноль.

Бэкапы на новом проде работают

Первые плановые прогоны проверены целиком, от дампа до off-box:

[00:39:00Z] Dump OK: gendesign_20260826_003001.sql.gz (1.4 ГБ)
[00:39:37Z] S3 upload OK
sentinel:   2026-08-26T00:39:37Z

tradein — то же самое в 04:30 МСК, sentinel 01:31:00Z. Ежечасный сторож пропущенных прогонов читает оба и отвечает OK.


Осталось незакрытым — оба пункта требуют владельца

1. Базы forgejo и glitchtip всё ещё в уехавшем кластере

gendesign-postgres-1 на Beget работает, и внутри него по-прежнему обе базы. Шаг 3 из #3061 (ops/split-infra-postgres.sh --apply) не запускался, хотя приёмник gendesign-infra-postgres уже поднят — то есть шаги 1–2 сделаны.

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

2. Crontab на Beget не переключён — и это уже даёт эффект

На Beget остались записи, указывающие на уехавшее (backup.sh, geocode, dadata, tradein). Поскольку старый gendesign-postgres-1 там всё ещё работает, ночной прогон снимает дамп устаревшей копии базы и заливает в тот же бакет gendsgn-backups.

Итог: в бакете лежат свежие дампы с Poincare и устаревшие с Beget, и по имени они неразличимы — только по времени. Каждая следующая ночь добавляет ещё одну пару.

Лечится одной командой, файл готов с #3072:

crontab /opt/gendesign/ops/crontab-beget.cron

Сам не выполняю: команда удаляет записи, а не добавляет, и делать это на боевом хосте без спроса не стану — тем более что на этом же переезде я уже ошибся с crontab (проверил его не у того пользователя на Poincare и поставил дубль; снял, состояние восстановлено).

Ещё не проверено после переезда

  • INFRA_DEPLOY_HOST (#3071) — задан ли, иначе /opt/gendesign на Beget перестанет обновляться
  • whitelist Beget в fail2ban на Poincare (#3059) — до ротации deploy-ключа
  • Старый стек на Beget (18 контейнеров, включая tradein-postgres, osrm, redis) — работает параллельно с новым; когда гасить, решать владельцу
## Переезд состоялся 25.08 — на пять дней раньше окна. Фиксирую фактическое состояние Раннбук и задачи до сих пор описывают переезд как предстоящий. Ниже — что реально произошло и что осталось незакрытым, замерами, а не по плану. ### Приёмка по доменам — сошлась Коды **совпадают с зафиксированными до переезда** (таблица из моего комментария в #3031), проверено с Poincare: | Домен | Хост | Код | До переезда | |---|---|---|---| | `meraocenka.ru` | Poincare | **200** | 200 | | `gendsgn.ru` | Poincare | **401** | 401 (basic auth) | | `www.gendsgn.ru` | Poincare | **301** | 301 | | `merahome.ru` | Poincare | **301** | 301 | | `meraotsenka.ru` | Poincare | **301** | 301 | | `git.gendsgn.ru` | Beget | **200** | 200 | | `errors.gendsgn.ru` | Beget | **200** | 200 | | `obsidian.gendsgn.ru` | Beget | **401** | 401 | ### Разделение Caddy сработало | Хост | `CADDY_SITES` | Домены | |---|---|---| | Poincare | `apps` | 5 продуктовых | | Beget | `infra` | git / errors / obsidian | Ни один инфраструктурный домен не попал на Poincare — значит ACME не бился в HTTP-01 по чужому DNS и лимиты Let's Encrypt не тронуты. Это был главный риск разделения (#3059, #3062, #3088). Сертификаты на Poincare подхватились из перенесённого `/data/caddy`, переиздания не потребовалось — строк ACME в логе ноль. ### Бэкапы на новом проде работают Первые плановые прогоны проверены целиком, от дампа до off-box: ``` [00:39:00Z] Dump OK: gendesign_20260826_003001.sql.gz (1.4 ГБ) [00:39:37Z] S3 upload OK sentinel: 2026-08-26T00:39:37Z ``` `tradein` — то же самое в 04:30 МСК, sentinel `01:31:00Z`. Ежечасный сторож пропущенных прогонов читает оба и отвечает `OK`. --- ## Осталось незакрытым — оба пункта требуют владельца ### 1. Базы `forgejo` и `glitchtip` всё ещё в уехавшем кластере `gendesign-postgres-1` на Beget работает, и внутри него по-прежнему **обе** базы. Шаг 3 из #3061 (`ops/split-infra-postgres.sh --apply`) не запускался, хотя приёмник `gendesign-infra-postgres` уже поднят — то есть шаги 1–2 сделаны. Пока перенос не выполнен, git и приём ошибок держатся на кластере, который по замыслу переезда должен был уехать. ### 2. Crontab на Beget не переключён — и это уже даёт эффект На Beget остались записи, указывающие на уехавшее (`backup.sh`, `geocode`, `dadata`, `tradein`). Поскольку старый `gendesign-postgres-1` там **всё ещё работает**, ночной прогон снимает дамп **устаревшей** копии базы и заливает в тот же бакет `gendsgn-backups`. Итог: в бакете лежат свежие дампы с Poincare и устаревшие с Beget, и **по имени они неразличимы** — только по времени. Каждая следующая ночь добавляет ещё одну пару. Лечится одной командой, файл готов с #3072: ```bash crontab /opt/gendesign/ops/crontab-beget.cron ``` Сам не выполняю: команда **удаляет** записи, а не добавляет, и делать это на боевом хосте без спроса не стану — тем более что на этом же переезде я уже ошибся с crontab (проверил его не у того пользователя на Poincare и поставил дубль; снял, состояние восстановлено). ### Ещё не проверено после переезда - `INFRA_DEPLOY_HOST` (#3071) — задан ли, иначе `/opt/gendesign` на Beget перестанет обновляться - whitelist Beget в fail2ban на Poincare (#3059) — до ротации deploy-ключа - Старый стек на Beget (18 контейнеров, включая `tradein-postgres`, `osrm`, `redis`) — работает параллельно с новым; когда гасить, решать владельцу
Author
Owner

Поправка: оба «незакрытых» пункта из моего предыдущего комментария на самом деле ЗАКРЫТЫ

Пошёл выполнять их и обнаружил, что выполнять нечего. Оба вывода были ошибочны, и оба — по одной причине: я измерял не то, что нужно.

1. Базы forgejo / glitchtip — перенос СДЕЛАН

Я писал, что они «всё ещё в уехавшем кластере». Проверял так: есть ли эти базы в gendesign-postgres-1 → получил 2 → заключил, что переноса не было.

Существование ≠ использование. Правильная проверка — активные подключения:

gendesign-infra-postgres:  forgejo=3 conn, glitchtip=4 conn   ← живые
gendesign-postgres-1:      0 подключений к обеим              ← остатки

Приложения работают на infra-postgres. В старом кластере лежат устаревшие копии, которые просто ещё не удалены.

Это едва не стоило дорого. Я запустил сухой прогон split-infra-postgres.sh, и он отказался:

forgejo:   comment 27557 → 27579, commit_status 37561 → 37609
glitchtip: строк 419003 → 399109
ОШИБКА: сверка не сошлась — НЕ переключайте приложения.

В приёмнике данные свежее, чем в источнике — потому что приёмник и есть боевая база. Запуск --apply вслепую затёр бы живую базу Forgejo устаревшей копией. Отказ сработал именно так, как задумано, и здесь особенно уместно, что #3092 незадолго до этого починил assert_not_cut_over — страж, который до того молча пропускался.

2. Crontab на Beget — уже переключён

Я писал, что там «5 записей, указывают на уехавшее», и что ночью снимается дамп устаревшей копии. Основанием был grep -cE 'tradein|backup\.sh|geocode|dadata', вернувший 8 при пяти строках — я посчитал строки-комментарии внутри crontab как действующие записи.

Фактическое содержимое байт в байт совпадает с эталоном ops/crontab-beget.cron:

15 4 * * *  backup-forgejo.sh
0  * * * *  check-backup-staleness.sh  → backups/forgejo/.last_success
45 4 * * *  backup-couchdb.sh
0  * * * *  check-backup-staleness.sh  → backups/couchdb/.last_success
0  4 * * 0  docker-prune.sh

Ни одной ссылки на backup.sh, geocode, dadata, tradein. Дублирующихся дампов в бакете нет и не было.

Что это значит по существу

Переезд закрыт полнее, чем я отчитался. По состоянию на 26.08 подтверждено замерами:

  • домены отвечают прежними кодами, разделение Caddy работает;
  • бэкапы на Poincare идут по расписанию с выгрузкой в S3, сторож отвечает OK;
  • forgejo/glitchtip живут на infra-postgres (Beget), продуктовые базы — на Poincare;
  • crontab разведён по хостам верно на обоих;
  • /opt/gendesign на Beget синхронизируется (HEAD совпадает с origin/main, отставание 0);
  • whitelist Beget в fail2ban на Poincare выставлен, забанено 0;
  • скрапинг работает: 19 прогонов done из 22, три banned с ban_kind=platform — штатная блокировка площадками, не последствие переезда.

Остаточное, чисто по уборке

  • В gendesign-postgres-1 лежат устаревшие копии forgejo и glitchtip — удалять или нет, решать владельцу; пока они там, любой невнимательный --apply опасен.
  • На Beget работает старый продуктовый стек (18 контейнеров, включая tradein-postgres, osrm, redis) параллельно с новым на Poincare. Когда гасить — решение владельца.

Про метод

Три раза за этот переезд я сделал вывод, посмотрев не туда: проверил crontab не у того пользователя (и поставил дубль, снят), принял наличие остаточных баз за отсутствие переноса, посчитал комментарии как записи crontab. Каждый раз ошибка была не в данных, а в том, что именно я померил — тот же класс, который весь этот эпик и ловит в коде. Стоит записать как правило: «пусто» и «есть» без указания, где именно смотрели, — не факты.

## Поправка: оба «незакрытых» пункта из моего предыдущего комментария на самом деле ЗАКРЫТЫ Пошёл выполнять их и обнаружил, что выполнять нечего. Оба вывода были ошибочны, и оба — по одной причине: я измерял не то, что нужно. ### 1. Базы `forgejo` / `glitchtip` — перенос СДЕЛАН Я писал, что они «всё ещё в уехавшем кластере». Проверял так: есть ли эти базы в `gendesign-postgres-1` → получил 2 → заключил, что переноса не было. **Существование ≠ использование.** Правильная проверка — активные подключения: ``` gendesign-infra-postgres: forgejo=3 conn, glitchtip=4 conn ← живые gendesign-postgres-1: 0 подключений к обеим ← остатки ``` Приложения работают на `infra-postgres`. В старом кластере лежат устаревшие копии, которые просто ещё не удалены. **Это едва не стоило дорого.** Я запустил сухой прогон `split-infra-postgres.sh`, и он отказался: ``` forgejo: comment 27557 → 27579, commit_status 37561 → 37609 glitchtip: строк 419003 → 399109 ОШИБКА: сверка не сошлась — НЕ переключайте приложения. ``` В приёмнике данные **свежее**, чем в источнике — потому что приёмник и есть боевая база. Запуск `--apply` вслепую **затёр бы живую базу Forgejo устаревшей копией**. Отказ сработал именно так, как задумано, и здесь особенно уместно, что #3092 незадолго до этого починил `assert_not_cut_over` — страж, который до того молча пропускался. ### 2. Crontab на Beget — уже переключён Я писал, что там «5 записей, указывают на уехавшее», и что ночью снимается дамп устаревшей копии. Основанием был `grep -cE 'tradein|backup\.sh|geocode|dadata'`, вернувший 8 при пяти строках — я **посчитал строки-комментарии** внутри crontab как действующие записи. Фактическое содержимое **байт в байт совпадает** с эталоном `ops/crontab-beget.cron`: ``` 15 4 * * * backup-forgejo.sh 0 * * * * check-backup-staleness.sh → backups/forgejo/.last_success 45 4 * * * backup-couchdb.sh 0 * * * * check-backup-staleness.sh → backups/couchdb/.last_success 0 4 * * 0 docker-prune.sh ``` Ни одной ссылки на `backup.sh`, `geocode`, `dadata`, `tradein`. Дублирующихся дампов в бакете нет и не было. ### Что это значит по существу Переезд закрыт полнее, чем я отчитался. По состоянию на 26.08 подтверждено замерами: - домены отвечают прежними кодами, разделение Caddy работает; - бэкапы на Poincare идут по расписанию с выгрузкой в S3, сторож отвечает OK; - `forgejo`/`glitchtip` живут на `infra-postgres` (Beget), продуктовые базы — на Poincare; - crontab разведён по хостам верно на обоих; - `/opt/gendesign` на Beget синхронизируется (HEAD совпадает с `origin/main`, отставание 0); - whitelist Beget в fail2ban на Poincare выставлен, забанено 0; - скрапинг работает: 19 прогонов `done` из 22, три `banned` с `ban_kind=platform` — штатная блокировка площадками, не последствие переезда. ### Остаточное, чисто по уборке - В `gendesign-postgres-1` лежат устаревшие копии `forgejo` и `glitchtip` — удалять или нет, решать владельцу; пока они там, любой невнимательный `--apply` опасен. - На Beget работает старый продуктовый стек (18 контейнеров, включая `tradein-postgres`, `osrm`, `redis`) параллельно с новым на Poincare. Когда гасить — решение владельца. ### Про метод Три раза за этот переезд я сделал вывод, посмотрев не туда: проверил crontab не у того пользователя (и поставил дубль, снят), принял наличие остаточных баз за отсутствие переноса, посчитал комментарии как записи crontab. Каждый раз ошибка была не в данных, а в том, **что именно** я померил — тот же класс, который весь этот эпик и ловит в коде. Стоит записать как правило: «пусто» и «есть» без указания, где именно смотрели, — не факты.
Author
Owner

Пункт приёмки «перечень интеграций, привязанных к IP» — закрываю замером 2026-08-26 05:45 UTC

Сначала важное для самого раннбука: продукты уже переехали. DNS всех пяти продуктовых доменов указывает на 188.246.224.93, скрап на Poincare идёт (235 объявлений за 2 часа), бэкенд за сутки дал 0 ошибок. Раздел «T−0» описывает как предстоящее то, что произошло 25.08 (замер DNS — в #3027). Пункты 1–8 порядка окна имеет смысл переписать в постпереездный чеклист.

Исходящий IP сменился — это и есть главный вектор «молчаливой поломки»

curl https://api.ipify.org с Poincare → 188.246.224.93. То есть ломается всё, где наш адрес прописан в allowlist на стороне контрагента, и ломается тихо: с нашей стороны это выглядит как отказ авторизации или таймаут.

Проверено — работает после переезда (значит, к старому IP не привязано)

интеграция как проверено результат
S3 gendsgn-backups лог /opt/gendesign/logs/*-backup.log выгрузка обеих БД сегодня, 00:39 и 01:31 UTC
DaData HTTP-проба с Poincare 301 (доступна)
OpenAI HTTP-проба 403 без ключа = доступна
Nominatim OSM HTTP-проба 302
Telegram Bot API HTTP-проба + живой long-polling 302; бот опрашивает
прокси скрапера (ASocks) скрап живой 109 543 объявления, свежие 04:44 UTC

Привязок к 46.173.16.127 в коде нет

git grep 46.173.16.127 по main даёт только комментарии (deploy.yml, caddy/sites/*.caddy, docker-compose.prod.yml) — ни одного функционального использования. Хост деплоя берётся из secrets.DEPLOY_HOST, оба workflow уже катят на Poincare.

Что НЕ проверяется с нашей стороны — владельцу в панелях контрагентов

Это и есть остаток риска, его нельзя закрыть изнутри:

  • Платёжный провайдер — и callback-URL, и возможный allowlist по IP. Сейчас таблица payments пуста: платный флоу в проде ещё не использовался, поэтому поломка была бы незаметна вдвойне. Проверить до первого реального платежа, а не после.
  • Аккаунт прокси-провайдера — если доступ ограничен по IP, ротация ключей/подписки может отвалиться позже, чем сам скрап.
  • Любой API-ключ с ограничением по адресу (DaData, OpenAI и пр.) — HTTP-проба выше говорит только о сетевой достижимости, не о том, что ключ разрешён с нового адреса. Ключи не читал.
  • Вебхуки Forgejo — Forgejo остаётся на Beget, его IP не менялся; отдельного действия не требуется.

Telegram-бот работает исходящим long-polling (getUpdates), а не вебхуком, поэтому к нашему IP не привязан вовсе.

Побочно: смена адреса уже дала одну молчаливую поломку

#3094 — при переезде не доехала переменная окружения, и newbuilding-crossload вместо ~11 000 лотов грузит 0, записывая прогон статусом done. Это не про IP, но ровно про то, чего опасается этот пункт приёмки: сломалось молча и обнаружилось только сверкой.

Наблюдение по Telegram-боту (не поломка)

За 24 ч 106 таймаутов getUpdates; 97 вылечились первой повторной попыткой, 9 — второй, 9 дошли до ERROR с httpx.ReadTimeout. Конфигурация корректна (long-poll 30 с при HTTP-таймауте 40 с — запас есть), сеть до api.telegram.org с Poincare доступна. То есть это сетевые обрывы, а не дефект. Сравнить с частотой на Beget не могу — контейнер там уже не работает, истории логов нет. Если частота смущает, нужен день наблюдения на новом хосте.

## Пункт приёмки «перечень интеграций, привязанных к IP» — закрываю замером 2026-08-26 05:45 UTC Сначала важное для самого раннбука: **продукты уже переехали.** DNS всех пяти продуктовых доменов указывает на 188.246.224.93, скрап на Poincare идёт (235 объявлений за 2 часа), бэкенд за сутки дал 0 ошибок. Раздел «T−0» описывает как предстоящее то, что произошло 25.08 (замер DNS — в [#3027](https://git.gendsgn.ru/lekss361/gendesign/issues/3027#issuecomment-28105)). Пункты 1–8 порядка окна имеет смысл переписать в постпереездный чеклист. ### Исходящий IP сменился — это и есть главный вектор «молчаливой поломки» `curl https://api.ipify.org` с Poincare → **188.246.224.93**. То есть ломается всё, где наш адрес прописан в allowlist **на стороне контрагента**, и ломается тихо: с нашей стороны это выглядит как отказ авторизации или таймаут. ### Проверено — работает после переезда (значит, к старому IP не привязано) | интеграция | как проверено | результат | |---|---|---| | S3 `gendsgn-backups` | лог `/opt/gendesign/logs/*-backup.log` | выгрузка обеих БД сегодня, 00:39 и 01:31 UTC | | DaData | HTTP-проба с Poincare | 301 (доступна) | | OpenAI | HTTP-проба | 403 без ключа = доступна | | Nominatim OSM | HTTP-проба | 302 | | Telegram Bot API | HTTP-проба + живой long-polling | 302; бот опрашивает | | прокси скрапера (ASocks) | скрап живой | 109 543 объявления, свежие 04:44 UTC | ### Привязок к `46.173.16.127` в коде нет `git grep 46.173.16.127` по main даёт только комментарии (`deploy.yml`, `caddy/sites/*.caddy`, `docker-compose.prod.yml`) — ни одного функционального использования. Хост деплоя берётся из `secrets.DEPLOY_HOST`, оба workflow уже катят на Poincare. ### Что НЕ проверяется с нашей стороны — владельцу в панелях контрагентов Это и есть остаток риска, его нельзя закрыть изнутри: - **Платёжный провайдер** — и callback-URL, и возможный allowlist по IP. Сейчас таблица `payments` **пуста**: платный флоу в проде ещё не использовался, поэтому поломка была бы незаметна вдвойне. Проверить до первого реального платежа, а не после. - **Аккаунт прокси-провайдера** — если доступ ограничен по IP, ротация ключей/подписки может отвалиться позже, чем сам скрап. - **Любой API-ключ с ограничением по адресу** (DaData, OpenAI и пр.) — HTTP-проба выше говорит только о сетевой достижимости, не о том, что ключ разрешён с нового адреса. Ключи не читал. - **Вебхуки Forgejo** — Forgejo остаётся на Beget, его IP не менялся; отдельного действия не требуется. Telegram-бот работает **исходящим long-polling** (`getUpdates`), а не вебхуком, поэтому к нашему IP не привязан вовсе. ### Побочно: смена адреса уже дала одну молчаливую поломку [#3094](https://git.gendsgn.ru/lekss361/gendesign/issues/3094) — при переезде не доехала переменная окружения, и `newbuilding-crossload` вместо ~11 000 лотов грузит 0, записывая прогон статусом `done`. Это не про IP, но ровно про то, чего опасается этот пункт приёмки: сломалось молча и обнаружилось только сверкой. ### Наблюдение по Telegram-боту (не поломка) За 24 ч 106 таймаутов `getUpdates`; 97 вылечились первой повторной попыткой, 9 — второй, 9 дошли до ERROR с `httpx.ReadTimeout`. Конфигурация корректна (long-poll 30 с при HTTP-таймауте 40 с — запас есть), сеть до `api.telegram.org` с Poincare доступна. То есть это сетевые обрывы, а не дефект. Сравнить с частотой на Beget не могу — контейнер там уже не работает, истории логов нет. Если частота смущает, нужен день наблюдения на новом хосте.
Author
Owner

Закрыто — раннбук исполнен 25.08, окно уложилось в 7 мин 38 с.

Cutover 19:35:56 → 19:43:33 UTC. Порядок по раннбуку соблюдён: снятие crontab → остановка Caddy → остановка писателей МЕРЫ → остановка писателей Птицы → ожидание нуля клиентских бэкендов в pg_stat_activity (ноль на первом же опросе) → перенос gendesign, tradein, auth → подъём стека → сверка → пауза перед DNS.

Замеры совпали с репетицией с точностью до секунд: дамп gendesign 138 с против 141 с на репетиции, общий перенос 336 с против 326 с.

Сверка после переноса — все счётчики строк сошлись ровно:

Объект Строк
rosreestr_deals 7 571 875
listings 109 308
deals 108 623
listing_source_snapshots 5 086 346
auth.users / auth.sessions 13 / 32
таблиц всего 200

Одна находка сверх раннбука: БД auth в дамп gendesign не входила и была бы потеряна — перенесена отдельно, гранты и сессии проверены.

Приёмка по состоянию на утро 26.08, после первой ночи без присмотра: пять боевых доменов на 188.246.224.93 (gendsgn.ru 401, www 301, meraocenka.ru 200, meraotsenka.ru 301, merahome.ru 301), четыре инфраструктурных остались на Beget. Боевой расчёт МЕРЫ отвечает за 90 мс. Скрапер за ночь добавил 772 объявления (avito 449, domklik 235, cian 88). Оба бэкапа отработали кроном с заливкой в S3. FDW tradein → gendesign живой — через него читается 7 571 875 сделок.

Откат остаётся возможным: живая БД МЕРЫ на Beget заморожена на момент cutover (max(scraped_at) = 25.08 17:23), для Птицы — проверенный дамп на 1572 объекта плюс свежие ночные копии в S3.

Что вынесено отдельно и НЕ входит в это закрытие: сужение периметра (#3075) и остаток по интеграциям, привязанным к старому IP (#3059).

Закрыто — раннбук исполнен 25.08, окно уложилось в 7 мин 38 с. Cutover 19:35:56 → 19:43:33 UTC. Порядок по раннбуку соблюдён: снятие crontab → остановка Caddy → остановка писателей МЕРЫ → остановка писателей Птицы → ожидание нуля клиентских бэкендов в `pg_stat_activity` (ноль на первом же опросе) → перенос `gendesign`, `tradein`, `auth` → подъём стека → сверка → пауза перед DNS. Замеры совпали с репетицией с точностью до секунд: дамп `gendesign` 138 с против 141 с на репетиции, общий перенос 336 с против 326 с. Сверка после переноса — все счётчики строк сошлись ровно: | Объект | Строк | |---|---| | `rosreestr_deals` | 7 571 875 | | `listings` | 109 308 | | `deals` | 108 623 | | `listing_source_snapshots` | 5 086 346 | | `auth.users` / `auth.sessions` | 13 / 32 | | таблиц всего | 200 | Одна находка сверх раннбука: БД `auth` в дамп `gendesign` не входила и была бы потеряна — перенесена отдельно, гранты и сессии проверены. Приёмка по состоянию на утро 26.08, после первой ночи без присмотра: пять боевых доменов на `188.246.224.93` (`gendsgn.ru` 401, `www` 301, `meraocenka.ru` 200, `meraotsenka.ru` 301, `merahome.ru` 301), четыре инфраструктурных остались на Beget. Боевой расчёт МЕРЫ отвечает за 90 мс. Скрапер за ночь добавил 772 объявления (avito 449, domklik 235, cian 88). Оба бэкапа отработали кроном с заливкой в S3. FDW `tradein → gendesign` живой — через него читается 7 571 875 сделок. Откат остаётся возможным: живая БД МЕРЫ на Beget заморожена на момент cutover (`max(scraped_at)` = `25.08 17:23`), для Птицы — проверенный дамп на 1572 объекта плюс свежие ночные копии в S3. Что вынесено отдельно и НЕ входит в это закрытие: сужение периметра (#3075) и остаток по интеграциям, привязанным к старому IP (#3059).
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#3057
No description provided.