fix(migrations): закрепить lock_timeout для блокирующего DDL и ловить невалидные индексы #2791

Merged
bot-backend merged 2 commits from fix/migrations-lock-timeout into main 2026-08-07 11:21:29 +00:00

2 commits

Author SHA1 Message Date
80b40d5414 Merge remote-tracking branch 'origin/main' into fix/migrations-lock-timeout
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / changes (pull_request) Successful in 11s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m36s
CI / openapi-codegen-check (pull_request) Successful in 2m34s
CI / backend-tests (pull_request) Successful in 15m57s
# Conflicts:
#	tradein-mvp/backend/data/sql/250_drop_duplicate_expires_at_index.sql
2026-08-07 16:04:27 +05:00
26c35a0c18 fix(migrations): ограничить ожидание лока в 250 и закрепить lock_timeout гейтом
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m44s
CI / openapi-codegen-check (pull_request) Successful in 2m37s
CI Trade-In / backend-tests (pull_request) Successful in 4m28s
CI / backend-tests (pull_request) Successful in 15m57s
Миграция 250 (DROP INDEX на таблице в 1061 строку) 2026-08-07 встала на боевой
БД: сам DROP берёт лок за миллисекунды, но ЖДАЛ его выдачи 29 минут за чужой
аналитической psql-сессией, вторая попытка деплоя — ещё 16. Записи в
_schema_migrations нет, схема не изменена — следующий деплой упёрся бы так же.

Опасность не в простое деплоя: ждущий ACCESS EXCLUSIVE встаёт в очередь ПЕРЕД
новыми запросами, поэтому за ним начинают ждать обычные SELECT приложения.

- 250: SET LOCAL lock_timeout = '5s' сразу после BEGIN. Значение не наугад:
  снизу ограничено deadlock_timeout (1 s на проде) — автоотмена мешающего
  autovacuum срабатывает только после того, как ждущий отстоял эту секунду,
  так что 1-2 s гонялись бы с рутинным autovacuum; сверху 5 s — потолок
  простоя очереди приложения, против наблюдённых 1740 s это в 348 раз меньше.
  Проверено в форме запуска раннера (psql < файл, PostgreSQL 16.4, встречная
  сессия держит ACCESS SHARE): со строкой — отказ через 5 s и exit 3, без неё
  команда всё ещё висела в очереди на 15-й секунде. SET LOCAL доживает до DROP
  потому, что файл идёт одной psql-сессией и весь завёрнут в BEGIN/COMMIT.

- scripts/check-migration-lock-timeout.py + шаг в ci.yml: новая миграция с
  блокирующим DDL обязана нести SET LOCAL lock_timeout, внутри транзакции и ДО
  первого DDL. Гейт бежит на каждом PR (обоих лэйнов), у него --selftest.

  Вариант «задать lock_timeout один раз в раннере» отвергнут замером, а не
  вкусом: session-wide значение обрывает CREATE INDEX CONCURRENTLY (тот ждёт
  параллельные транзакции через VirtualXactLock, и это ожидание тоже под
  lock_timeout) и оставляет невалидный индекс — то есть изготавливало бы ровно
  ту аварию, от которой заведена вторая проверка. Блокирующий DDL и
  CONCURRENTLY хотят противоположной политики → granularity = файл.

- deploy.yml / deploy-tradein.yml: после цикла миграций — отказ, если в БД
  есть индексы с indisvalid=false (#2752). Оборванный CIC оставляет такой
  индекс молча: планировщик им не пользуется, а re-run миграции не чинит —
  CREATE INDEX CONCURRENTLY IF NOT EXISTS печатает «already exists, skipping»
  и выходит с кодом 0, после чего миграция помечается применённой. На проде
  таких индексов сейчас 0 (обе БД) — это профилактика.

Refs #2752
2026-08-07 15:39:33 +05:00