fix(deploy): подменять фронт МЕРЫ отдельной командой — окно простоя 30–90 с уходит (#3274, часть 1/2) #3442

Merged
bot-backend merged 1 commit from fix/3274-part1-deploy-swap into main 2026-09-11 20:21:53 +00:00
Collaborator

Часть 1 из 2 по #3274. Не трогает caddy/** намеренно — чтобы мерж не запускал полный деплой ПТИЦЫ с --force-recreate caddy (~1 мин отказа всех доменов). Вторая половина (ретрай в Caddy) поедет отдельным PR.

Корень оказался не тем, что в постановке. Гипотеза «подмена контейнера медленная» неверна. Прод сам провёл A/B в один день (docker inspect .Created/.StartedAt, оба деплоя МЕРЫ 10.09):

что create → start 503 на лендинге в логе Caddy
ПАЧКА сервисов в одном up -d (15:01) 15:01:40 → 15:02:10 = 30 с 15:01:46, 15:01:52, 15:02:06
ОДИН сервис в up -d (16:42) 16:42:17.5 → 16:42:18.0 = 0,5 с ни одного

docker compose up -d <список> идёт в две фазы: сначала create (старый контейнер каждого сервиса останавливается и удаляется — занято container_name), потом start в порядке зависимостей. Между фазами фронта физически нет. Воспроизведено на стенде по меткам: соседи создаются сразу, стартуют через 41 с.

Правка: frontend убран из общего SERVICES в .forgejo/workflows/deploy-tradein.yml и пересоздаётся своей командой после пачки — в его графе один сервис, фазы идут подряд. 30–90 с → ~0,5 с.

Замер на стенде (реальный образ tradein-mvp-frontend + caddy:2, проба 5 раз/с, scripts/probe-deploy-window.sh):

  • пачка сервисов, как сегодня на проде → 3 × 502;
  • один сервис (эта правка) → 1 × 502 (остаток ~0,5 с снимает часть 2).

Проба умеет считать 000 отдельной строкой (это периметр, не простой) и самую длинную серию не-200; у неё есть --selftest, который поймал у автора занижение окна на один шаг.

Прод «до»: три 503 на лендинге в окне 15:01:46→15:02:06 плюс 30 с между create и start; замер из issue от 05.09 — 10 образцов 503 подряд ≈ 45 с.
Прод «после»: не снят — за время работы (19:09–19:52Z, 1257 образцов) штатного деплоя не случилось, origin/main не двигался. Контроль самой пробы с хоста: 1257/1257 × 200, ноль 000 — значит любой не-200 в окне деплоя будет настоящим простоем, а не отбоем периметра.

Приёмка на проде (записана ДО правки, в терминах поведения):

  1. на хосте за минуту до мержа: ssh poincare '/tmp/probe-deploy-window.sh https://meraocenka.ru/ 0.2 900 /tmp/probe-after.tsv';
  2. максимальная серия не-200 < 2 с, ноль 000;
  3. docker inspect tradein-frontend -f '{{.Created}} {{.State.StartedAt}}' — разрыв ~0,5 с, не 30 с;
  4. "status":503 на публичных путях meraocenka.ru за сутки после мержа — 0.

Откат: git revert одного коммита, возвращает окно 30–90 с. Схемы, тома, образы, docker-compose*.yml не тронуты.

Часть 1 из 2 по #3274. **Не трогает `caddy/**` намеренно** — чтобы мерж не запускал полный деплой ПТИЦЫ с `--force-recreate caddy` (~1 мин отказа всех доменов). Вторая половина (ретрай в Caddy) поедет отдельным PR. **Корень оказался не тем, что в постановке.** Гипотеза «подмена контейнера медленная» неверна. Прод сам провёл A/B в один день (`docker inspect .Created/.StartedAt`, оба деплоя МЕРЫ 10.09): | что | create → start | 503 на лендинге в логе Caddy | |---|---|---| | ПАЧКА сервисов в одном `up -d` (15:01) | 15:01:40 → 15:02:10 = **30 с** | 15:01:46, 15:01:52, 15:02:06 | | ОДИН сервис в `up -d` (16:42) | 16:42:17.5 → 16:42:18.0 = **0,5 с** | ни одного | `docker compose up -d <список>` идёт в две фазы: сначала create (старый контейнер каждого сервиса останавливается и **удаляется** — занято `container_name`), потом start в порядке зависимостей. Между фазами фронта физически нет. Воспроизведено на стенде по меткам: соседи создаются сразу, стартуют через 41 с. **Правка:** `frontend` убран из общего `SERVICES` в `.forgejo/workflows/deploy-tradein.yml` и пересоздаётся своей командой после пачки — в его графе один сервис, фазы идут подряд. **30–90 с → ~0,5 с.** **Замер на стенде** (реальный образ `tradein-mvp-frontend` + `caddy:2`, проба 5 раз/с, `scripts/probe-deploy-window.sh`): - пачка сервисов, как сегодня на проде → 3 × 502; - один сервис (эта правка) → 1 × 502 (остаток ~0,5 с снимает часть 2). Проба умеет считать `000` отдельной строкой (это периметр, не простой) и самую длинную серию не-200; у неё есть `--selftest`, который поймал у автора занижение окна на один шаг. **Прод «до»:** три 503 на лендинге в окне 15:01:46→15:02:06 плюс 30 с между create и start; замер из issue от 05.09 — 10 образцов 503 подряд ≈ 45 с. **Прод «после»: не снят** — за время работы (19:09–19:52Z, 1257 образцов) штатного деплоя не случилось, `origin/main` не двигался. Контроль самой пробы с хоста: 1257/1257 × 200, ноль `000` — значит любой не-200 в окне деплоя будет настоящим простоем, а не отбоем периметра. **Приёмка на проде (записана ДО правки, в терминах поведения):** 1. на хосте за минуту до мержа: `ssh poincare '/tmp/probe-deploy-window.sh https://meraocenka.ru/ 0.2 900 /tmp/probe-after.tsv'`; 2. максимальная серия не-200 **< 2 с**, ноль `000`; 3. `docker inspect tradein-frontend -f '{{.Created}} {{.State.StartedAt}}'` — разрыв ~0,5 с, не 30 с; 4. `"status":503` на публичных путях `meraocenka.ru` за сутки после мержа — 0. **Откат:** `git revert` одного коммита, возвращает окно 30–90 с. Схемы, тома, образы, `docker-compose*.yml` не тронуты.
bot-backend added 1 commit 2026-09-11 19:54:43 +00:00
fix(deploy): подменять фронт МЕРЫ отдельной командой — окно 30–90 с уходит
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m0s
CI / backend-tests (pull_request) Successful in 17m25s
dacd298b21
Публичный лендинг meraocenka.ru лежал 30–90 с на КАЖДОМ деплое (#3274).
Причина не в скорости подмены контейнера: она стоит полсекунды.

`docker compose up -d` со СПИСКОМ сервисов работает в две фазы — сначала
create (старый контейнер каждого сервиса останавливается и УДАЛЯЕТСЯ, иначе
занято container_name), потом start, в порядке зависимостей и с ожиданием
их условий. Между фазами старого фронта уже нет, а новый ещё не запущен.

Прод, 10.09, два деплоя подряд (docker inspect .Created/.StartedAt):
  пачка сервисов:  tradein-backend  создан 15:01:40 → запущен 15:02:10 (30 с),
                   в логе Caddy три 503 на лендинге: 15:01:46/:52 и 15:02:06;
  ОДИН сервис:     tradein-frontend создан 16:42:17.5 → запущен 16:42:18.0
                   (0,5 с), 503 в логе нет ни одного.

Тот же двухфазный порядок воспроизведён на стенде (реальный образ фронта +
Caddy 2): соседи создаются сразу, стартуют через 41 с.

Поэтому frontend убран из общего `up -d $SERVICES` и пересоздаётся своей
командой после пачки: в его графе один сервис, create и start идут подряд.
Остаток ~0,5 с добирает ретрай подключения в Caddy — отдельным коммитом,
он мержится своим путём (caddy_only → graceful reload, без пересборки).

Проба для замера на живом деплое — scripts/probe-deploy-window.sh: считает
коды и САМУЮ ДЛИННУЮ серию не-200 в секундах, 000 отдельной строкой (его
даёт и отбой периметра, не только простой). Запускать НА хосте прода:
с внешнего адреса частая серия сама ловит 30–50 % 000.

Refs #3274
bot-backend merged commit 204e2e09de into main 2026-09-11 20:21:53 +00:00
Sign in to join this conversation.
No reviewers
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#3442
No description provided.