gendesign/data/sql/189_land_reservation_nulls_not_distinct.sql
bot-backend 03926a5d0b
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
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) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m11s
CI / backend-tests (pull_request) Successful in 17m21s
fix(ptica): lock_timeout в миграции 189 + разбор SQL в репетиции (#2464)
Гейт репозитория поймал упущение: блокирующий DDL без lock_timeout (#2752).
Без него ALTER TABLE встаёт в очередь за чужой сессией и уводит за собой запросы
приложения. Добавлен `SET LOCAL lock_timeout = '5s'` первой строкой после BEGIN.
Пять секунд — про ОЖИДАНИЕ блокировки, не про работу: таблица 297 строк, сам DDL
мгновенный. Не дождались — миграция падает, а не подвешивает прод.

Заодно переписан разбор миграции в тесте-репетиции. Он дважды подвёл на мне же:

  1) пропускал куски, начинающиеся с «--» → терялся DELETE, репетиция падала
     на ADD CONSTRAINT;
  2) резал SQL по «;», встреченной ВНУТРИ текста комментария.

Теперь комментарии снимаются ДО разбиения на выражения — оба класса закрыты.

Прогон: tests/sql/test_2464_land_reservation_dedup.py — 6 passed rc=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 14:56:37 +05:00

58 lines
3.8 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 189_land_reservation_nulls_not_distinct.sql
-- #2464 — дедуп land_reservation и UNIQUE NULLS NOT DISTINCT на живой таблице.
--
-- БАГ. `_UPSERT_NO_ACT_SQL` (workers/tasks/izyatie_ocr_ingest.py) заканчивается
-- `ON CONFLICT DO NOTHING`, а единственный подходящий констрейнт —
-- `uq_land_reservation_cad_act UNIQUE (cad_num, act_number)` с обычной NULL-семантикой.
-- В Postgres NULL != NULL, поэтому у записей БЕЗ номера акта конфликт не наступает
-- никогда: `ON CONFLICT DO NOTHING` не срабатывает, и каждый недельный прогон
-- вставляет копию. Docstring таски при этом обещает per-batch дедуп и двухшаговый
-- upsert по (cad_num, doc_url) — ни того, ни другого в коде нет.
--
-- ЗАМЕР ПРОДА 2026-08-20 (до правки):
-- строк всего 297
-- из них с act_number IS NULL 297 (то есть все)
-- групп (cad_num, doc_url) с дублями 27
-- максимум копий в группе 11
-- лишних строк 270 (91% таблицы)
--
-- Проверено, что ключ подходит: ни у одного cad_num нет более одного doc_url
-- (max = 1), то есть NULLS NOT DISTINCT по (cad_num, act_number) НЕ схлопнет
-- разные документы одного участка. Дубли внутри групп — точные копии: по одному
-- различному значению reservation_kind и act_date на группу.
--
-- ЧТО УДАЛЯЕТСЯ. Строки-копии сверх первой (по возрастанию id) в каждой группе
-- (cad_num, act_number) среди act_number IS NULL. Это порождение бага, а не
-- пользовательские данные; таблица — кэш OCR-разбора PDF с сайта, пересобираемый
-- прогоном таски. Первая строка группы (минимальный id) сохраняется целиком.
--
-- ПОЧЕМУ ОТДЕЛЬНОЙ МИГРАЦИЕЙ, а не правкой CREATE TABLE: та же причина, что в
-- м.158 — исходный файл уже в _schema_migrations и на деплое пропускается.
-- Прецеденты NULLS NOT DISTINCT в репо: м.110, м.125, м.140, м.158. Prod = PG16.4.
-- Apply after: 188_regrant_quarter_price_index_fdw.sql
BEGIN;
-- #2752: блокирующий DDL обязан иметь lock_timeout — иначе ALTER TABLE встанет в
-- очередь за чужой сессией и уведёт за собой запросы приложения. Таблица крошечная
-- (297 строк), сам DDL мгновенный; пять секунд — про ОЖИДАНИЕ блокировки, не про
-- работу. Не дождались — миграция падает, а не подвешивает прод.
SET LOCAL lock_timeout = '5s';
-- 1) Дедуп: оставляем строку с минимальным id в каждой группе.
DELETE FROM land_reservation a
USING land_reservation b
WHERE a.act_number IS NULL
AND b.act_number IS NULL
AND a.cad_num = b.cad_num
AND a.id > b.id;
-- 2) Пересоздаём констрейнт с NULL-семантикой, при которой ON CONFLICT матчит.
ALTER TABLE land_reservation
DROP CONSTRAINT IF EXISTS uq_land_reservation_cad_act;
ALTER TABLE land_reservation
ADD CONSTRAINT uq_land_reservation_cad_act
UNIQUE NULLS NOT DISTINCT (cad_num, act_number);
COMMIT;