Проход house_dedup_merge удалял проигравшие дома вместе с дочерними строками,
не оставляя восстановимого следа: единственной записью «кто в кого» была строка
logger.info, а логи контейнера ротируются быстрее суток. Через сутки после
прогона нельзя было даже назвать пары, не говоря о возврате.
Это не гипотетическая проблема: расписание на проде enabled=true, dry_run=false,
такт 7 дней; шесть прогонов с 27.06 уже удалили 119 строк houses (2/39/31/9/6/32
по счётчикам scrape_runs), ближайший — 08.08.
Миграция 230: таблица house_merge_log (одна строка = один проигравший) с полным
jsonb-снимком удалённой строки, снимком победителя ДО переноса метаданных,
списком id перенесённых дочерних строк, полными снимками дочерних строк,
уничтоженных коллизией UNIQUE, и основанием слияния — проход, значение ключа,
был ли включён гео-страж и расстояние между домами. Плюс функция
house_merge_undo(batch_id) — обратная операция с построчным отчётом, включая
то, что вернуть не удалось.
Журнал пишется в ТОЙ ЖЕ транзакции, что и слияние, поэтому разрыв «слияние
прошло, запись не легла» невозможен по построению, а dry_run откатывает оба.
Правка НЕЙТРАЛЬНА к спорным решениям прохода: ключ схлопывания, правило выбора
победителя и гео-страж не тронуты. Расстояние теперь ПИШЕТСЯ и на fias-проходе,
где страж выключен, — измерение не есть проверка, но раньше не было и его.
Сторож полноты: если у houses появится FK-ребёнок, которого не знает _STEPS,
слияние падает вместо того, чтобы молча уничтожить его строки по CASCADE
(9 из 11 FK — ON DELETE CASCADE; ровно на пропущенном ребёнке сломалась
миграция 133, но там отказ был громким).
Объём: ~5.3 КБ на запись, наблюдаемый темп 20 слияний/нед → ~5.5 МБ/год,
потолок при схлопывании всех 9 571 домов ~50 МБ. Retention не нужен.
Заодно починены 4 живых теста этого модуля: они самоотключаются без Postgres,
поэтому в CI никогда не запускались и молча разошлись со схемой (houses.url
стал NOT NULL; у пары в первом тесте проигравший без geom, что канон-страж
#2187 справедливо отказывается сливать). Проверено на живом Postgres:
43 passed, из них 6 — на реальной БД.
Refs #2690