chore(tradein/db): вернуть снос дубля индекса expires_at — теперь с lock_timeout #2795

Merged
bot-backend merged 1 commit from chore/2793-drop-dup-expires-idx into main 2026-08-09 17:10:18 +00:00
Collaborator

Summary

Возврат миграции 250_drop_duplicate_expires_at_index.sql, снятой с деплоя #2792, — теперь с SET LOCAL lock_timeout = '5s'. Сносит побайтовый дубль trade_in_estimates_expires_at_idx (создан 229), оставляет trade_in_estimates_expires_idx из 001.

Файл — первый настоящий вход гейта scripts/check-migration-lock-timeout.py (порог для tradein: NN >= 250; до сих пор под ним было 0 файлов).

Критерий «таблица тиха» — выполнен

Записан заранее в #2791/#2793, перепроверен на проде 2026-08-09 16:54 UTC:

SELECT count(*) FROM pg_locks l JOIN pg_class c ON c.oid = l.relation
 WHERE c.relname='trade_in_estimates' AND l.pid <> pg_backend_pid();  → 0

Долгих транзакций (>1 мин) в БД нет вообще — 0 строк.

Разбор #2752 подтверждён свежим замером

индекс idx_scan 06.08 07.08 09.08 16:54
trade_in_estimates_expires_idx (001, остаётся) 234 234 234
trade_in_estimates_expires_at_idx (229, сносится) 0 19 21

Счётчик пережившего заморожен третьи сутки — планировщик держит весь живой трафик на дубле (relpages 5 против 6). Состояние устойчивое, а не переходное.

EXPLAIN до сноса (прод): Index Scan using trade_in_estimates_expires_at_idx.

Уточнён критерий приёмки

#2793 требовал сдвига idx_scan пережившего «в течение часа». Наблюдаемый темп — ~7 сканов/сутки (21 скан за трое суток), то есть час — окно, в котором отсутствие сдвига ничего не доказывает. В файле записано: детерминированная проверка сразу после деплоя — EXPLAIN с именем пережившего индекса; счётчик честно подтверждается за сутки.

Test plan

  • scripts/check-migration-lock-timeout.py --selftest — 20 утверждений, OK
  • Гейт на дереве: ✓ блокирующий DDL прикрыт lock_timeout (проверено новых миграций: 1)
  • Негативный контроль: тот же файл с вырезанной строкой SET LOCAL::error ... блокирующий DDL без lock_timeout (DROP INDEX ...), exit 1. Зелёный гейт не вакуумный.
  • pytest tests/test_migrations_manifest.py — 4 passed (имя дописано в _manifest_applied.txt)
  • Прогон на одноразовом PostgreSQL 16.4 (postgis/postgis:16-3.4, --network none): применяется, идемпотентна при повторном прогоне (NOTICE ... skipping, exit 0), дубль снят, COMMENT на пережившем проставлен
  • После деплоя: запись в _schema_migrations (а не «деплой зелёный»), EXPLAIN показывает переживший индекс, план той же формы

Refs #2793, #2752, #2791

## Summary Возврат миграции `250_drop_duplicate_expires_at_index.sql`, снятой с деплоя #2792, — теперь с `SET LOCAL lock_timeout = '5s'`. Сносит побайтовый дубль `trade_in_estimates_expires_at_idx` (создан 229), оставляет `trade_in_estimates_expires_idx` из 001. Файл — **первый настоящий вход** гейта `scripts/check-migration-lock-timeout.py` (порог для tradein: NN >= 250; до сих пор под ним было 0 файлов). ## Критерий «таблица тиха» — выполнен Записан заранее в #2791/#2793, перепроверен на проде **2026-08-09 16:54 UTC**: ``` SELECT count(*) FROM pg_locks l JOIN pg_class c ON c.oid = l.relation WHERE c.relname='trade_in_estimates' AND l.pid <> pg_backend_pid(); → 0 ``` Долгих транзакций (>1 мин) в БД нет вообще — 0 строк. ## Разбор #2752 подтверждён свежим замером | индекс | idx_scan 06.08 | 07.08 | 09.08 16:54 | |---|---|---|---| | `trade_in_estimates_expires_idx` (001, остаётся) | 234 | 234 | **234** | | `trade_in_estimates_expires_at_idx` (229, сносится) | 0 | 19 | 21 | Счётчик пережившего заморожен третьи сутки — планировщик держит весь живой трафик на дубле (relpages 5 против 6). Состояние устойчивое, а не переходное. `EXPLAIN` до сноса (прод): `Index Scan using trade_in_estimates_expires_at_idx`. ## Уточнён критерий приёмки #2793 требовал сдвига `idx_scan` пережившего «в течение часа». Наблюдаемый темп — **~7 сканов/сутки** (21 скан за трое суток), то есть час — окно, в котором отсутствие сдвига ничего не доказывает. В файле записано: детерминированная проверка сразу после деплоя — `EXPLAIN` с именем пережившего индекса; счётчик честно подтверждается за сутки. ## Test plan - [x] `scripts/check-migration-lock-timeout.py --selftest` — 20 утверждений, OK - [x] Гейт на дереве: `✓ блокирующий DDL прикрыт lock_timeout (проверено новых миграций: 1)` - [x] **Негативный контроль**: тот же файл с вырезанной строкой `SET LOCAL` → `::error ... блокирующий DDL без lock_timeout (DROP INDEX ...)`, exit 1. Зелёный гейт не вакуумный. - [x] `pytest tests/test_migrations_manifest.py` — 4 passed (имя дописано в `_manifest_applied.txt`) - [x] Прогон на одноразовом PostgreSQL 16.4 (`postgis/postgis:16-3.4`, `--network none`): применяется, идемпотентна при повторном прогоне (`NOTICE ... skipping`, exit 0), дубль снят, COMMENT на пережившем проставлен - [ ] После деплоя: запись в `_schema_migrations` (а не «деплой зелёный»), `EXPLAIN` показывает переживший индекс, план той же формы Refs #2793, #2752, #2791
bot-backend added 1 commit 2026-08-09 17:02:59 +00:00
chore(tradein/db): вернуть снос дубля индекса expires_at — теперь с lock_timeout
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 3m56s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
93dcd71480
Миграция 250 была снята с деплоя #2792: её DROP INDEX ждал ACCESS EXCLUSIVE
29 минут за чужой аналитической сессией и увёл за собой запросы приложения
(четыре смерженных PR не доехали до прода). Конвенция и гейт закреплены
#2791 — возвращаю сам снос с `SET LOCAL lock_timeout = '5s'`.

Файл — первый настоящий вход гейта scripts/check-migration-lock-timeout.py
(порог для tradein: NN >= 250). Прогон локально: зелёный на файле как есть,
красный на нём же с вырезанной строкой SET LOCAL (негативный контроль).

Критерий «таблица тиха», записанный в #2791/#2793 ДО работы, выполнен на
проде 2026-08-09 16:54 UTC: pg_locks по trade_in_estimates = 0, долгих
транзакций в БД нет вообще.

Подтверждение разбора #2752 свежим замером (16:54 UTC, +2 суток к прошлому):
дубль trade_in_estimates_expires_at_idx — 21 скан, переживший
trade_in_estimates_expires_idx — по-прежнему 234, счётчик заморожен.
Планировщик держит весь живой трафик на дубле (relpages 5 против 6), то есть
состояние устойчивое. EXPLAIN до сноса: Index Scan using
trade_in_estimates_expires_at_idx.

Уточнён критерий приёмки: наблюдаемый темп ~7 сканов/сутки, поэтому «сдвиг
idx_scan в течение часа» — недостаточное окно. Детерминированная проверка
сразу после деплоя — EXPLAIN с именем пережившего индекса; счётчик честно
подтверждается за сутки.

Refs #2793, #2752, #2791
bot-backend merged commit 45924021a7 into main 2026-08-09 17:10:18 +00:00
bot-backend deleted branch chore/2793-drop-dup-expires-idx 2026-08-09 17:10:18 +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#2795
No description provided.