fix(migration-133): dedup via cascade-delete, not child re-point (prod hotfix) #1914

Merged
bot-backend merged 1 commit from fix/migration-133-offer-price-history-collision into main 2026-06-26 08:16:28 +00:00
Collaborator

🔥 Прод-хотфикс — миграция 133 (из #1912) падала на проде, блокируя tradein-деплой (deployed SHA застрял на 4eaf184, 133 отсутствует в _schema_migrations).

Корень

offer_price_history имеет UNIQUE (listing_id, change_time), но re-point-UPDATE переносил ВСЕ loser-строки на keeper → duplicate key violation. Deep-review валидировал на throwaway schema subset, где этого констрейнта не было. Хуже: keeper-only NOT EXISTS тоже недостаточен — несколько loser'ов одной группы несут одинаковый change_time → коллизия МЕЖДУ loser'ами в одном UPDATE (та же дыра у listings_snapshots PK + external_valuations partial-UNIQUE).

Фикс

Убраны все data-child re-point'ы; полагаемся на FK ON DELETE CASCADE (все 4 FK = confdeltype='c') — дети loser'ов удаляются автоматически при DELETE loser. Оставлен только collision-free merged_into re-point. Это дубли ОДНОГО оффера → keeper хранит свою полную историю, история loser-дублей избыточна.

Прод dry-run (BEGIN…ROLLBACK, ничего не записано)

NOTICE: migration 133: dedup done — groups=9840 losers=9889 deleted=9889
DO / CREATE INDEX / ROLLBACK  — zero errors, abort-guard passed

9840 дубль-групп на проде (дедуп реально нужен). Миграция 133 нигде не применялась (нет в _schema_migrations) → правка in-place безопасна.

Урок: destructive-миграцию валидировать dry-run'ом на РЕАЛЬНОЙ прод-схеме (BEGIN…ROLLBACK), а не на hand-built subset — subset пропустил реальный констрейнт.

Refs #1773

🔥 **Прод-хотфикс** — миграция 133 (из #1912) **падала на проде**, блокируя tradein-деплой (deployed SHA застрял на 4eaf184, 133 отсутствует в `_schema_migrations`). ## Корень `offer_price_history` имеет `UNIQUE (listing_id, change_time)`, но re-point-UPDATE переносил ВСЕ loser-строки на keeper → `duplicate key violation`. Deep-review валидировал на throwaway schema **subset**, где этого констрейнта не было. Хуже: keeper-only `NOT EXISTS` тоже недостаточен — **несколько loser'ов одной группы несут одинаковый change_time** → коллизия МЕЖДУ loser'ами в одном UPDATE (та же дыра у listings_snapshots PK + external_valuations partial-UNIQUE). ## Фикс Убраны все data-child re-point'ы; полагаемся на `FK ON DELETE CASCADE` (все 4 FK = `confdeltype='c'`) — дети loser'ов удаляются автоматически при DELETE loser. Оставлен только collision-free `merged_into` re-point. Это дубли ОДНОГО оффера → keeper хранит свою полную историю, история loser-дублей избыточна. ## Прод dry-run (`BEGIN…ROLLBACK`, ничего не записано) ``` NOTICE: migration 133: dedup done — groups=9840 losers=9889 deleted=9889 DO / CREATE INDEX / ROLLBACK — zero errors, abort-guard passed ``` 9840 дубль-групп на проде (дедуп реально нужен). Миграция 133 нигде не применялась (нет в `_schema_migrations`) → правка in-place безопасна. **Урок:** destructive-миграцию валидировать dry-run'ом на РЕАЛЬНОЙ прод-схеме (`BEGIN…ROLLBACK`), а не на hand-built subset — subset пропустил реальный констрейнт. Refs #1773
bot-backend added 1 commit 2026-06-26 08:15:23 +00:00
fix(migration-133): dedup via cascade-delete, not child re-point (prod hotfix)
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
f1141f3578
Migration 133 FAILED on prod: offer_price_history has UNIQUE (listing_id,
change_time), but the re-point UPDATE moved ALL loser rows to the keeper →
duplicate key violation (the deep-review validated on a throwaway schema subset
that lacked this constraint). A keeper-only NOT EXISTS guard is also insufficient
— multiple losers in one (source,source_id) group share a change_time, so they
collide WITH EACH OTHER inside the single UPDATE (same vulnerability for
listings_snapshots PK + external_valuations partial-UNIQUE).

Fix: drop all data-child re-points; rely on FK ON DELETE CASCADE (all four FKs
are confdeltype='c') to clean loser children when losers are deleted. Keep only
the collision-free merged_into re-point. These are duplicates of the SAME offer —
the keeper retains its own full history; loser-dup history is redundant.

Dry-run on prod (BEGIN…ROLLBACK): 9840 groups, 9889 losers deleted, index
created, abort-guard passed, zero errors. Migration 133 never applied anywhere
(absent from _schema_migrations), so amending it in place is safe.

Refs #1773
bot-backend merged commit e0cd378d19 into main 2026-06-26 08:16:28 +00:00
bot-backend deleted branch fix/migration-133-offer-price-history-collision 2026-06-26 08:16:28 +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#1914
No description provided.