(medium) Avito enrichment: пойманная ошибка оставляет общий Session в aborted-state → каскад на все оставшиеся строки #1368

Closed
opened 2026-06-14 18:09:47 +00:00 by bot-backend · 1 comment
Collaborator

Описание

save_house_catalog_enrichment выполняет 5 последовательных INSERT/UPSERT, затем единственный db.commit() в конце, без внутреннего savepoint или rollback (avito_houses.py:1194-1223). Если любой промежуточный statement упадёт (например, constraint-ошибка в save_sellers/_upsert_house_with_url), исключение ловится except Exception в pipeline (scrape_pipeline.py:250-253), который только логирует + считает houses_failed и продолжает — без db.rollback(). Общий Session теперь в InFailedSqlTransaction, поэтому первый же db.execute следующего дома (а также Step 5 priority_rows SELECT на строке 261 и Step 5b на строках 370-375) падают с «current transaction is aborted» и так же проглатываются. Одна транзиентная DB-ошибка превращает весь остаток enrichment якоря в failures. save_detail_enrichment (avito_detail.py:522-...) и detail-цикл (Step 5, строки 338-342) имеют ту же форму.

Корень

Тот же класс, что Sber/forecasting: пойманное DB-исключение оставляет общий Session (SessionLocal(autocommit=False)) в aborted-state без db.rollback(). save_house_catalog_enrichment делает 5 db.execute с одним терминальным commit и без внутреннего try/except. CIAN-ветка (scrape_pipeline.py:1383, 1415) и Yandex-ветка (scrape_pipeline.py:866) в аналогичных except-блоках db.rollback() вызывают — Avito-ветка просто не получила этот фикс.

Evidence

scrape_pipeline.py:250-253 (Step 4, house enrich):

except Exception as e:
    logger.warning("pipeline:house_enrich failed for %s: %s", house_path, e)
    counters.houses_failed += 1
    counters.errors.append(f"house {house_path}: {e}")
# нет db.rollback() — следующий db.execute падёт с InFailedSqlTransaction

avito_houses.py:1194-1223 — 5 операций (_upsert_house_with_url, save_house_reviews, save_sellers, link_listings_to_seller, save_placement_history) и один db.commit() на строке 1223, без savepoint/rollback. Те же незащищённые except-блоки: Step 5 detail (scrape_pipeline.py:338-342), Step 5b (scrape_pipeline.py:370-375).

Suggested fix

В каждой пойманной ветке (Step 4 строки 250-253, Step 5 detail 338-342, Step 5b 370-375) вызывать db.rollback() перед continue, ЛИБО обернуть каждый вызов save_house_catalog_enrichment/save_detail_enrichment в with db.begin_nested():, чтобы сбойное обогащение откатывало только свой SAVEPOINT и оставляло сессию пригодной для следующих строк (как уже сделано для CIAN/Yandex).

Severity-обоснование

medium: pipeline продолжается и логирует ошибки, данные не повреждаются, но enrichment всего остатка sweep'а якоря молча теряется при единственной DB-ошибке во время house-enrich. Меньше Sber high (там весь pull стирается), сопоставимо с forecasting.


week ревью 1 · multi-agent аудит 2026-06-14 · severity=medium · category=error-handling

Затронутые файлы:

  • /Users/anton/Птица/gendesign/tradein-mvp/backend/app/services/scrape_pipeline.py
  • /Users/anton/Птица/gendesign/tradein-mvp/backend/app/services/scrapers/avito_houses.py
  • /Users/anton/Птица/gendesign/tradein-mvp/backend/app/services/scrapers/avito_detail.py
## Описание `save_house_catalog_enrichment` выполняет 5 последовательных INSERT/UPSERT, затем единственный `db.commit()` в конце, без внутреннего savepoint или rollback (`avito_houses.py:1194-1223`). Если любой промежуточный statement упадёт (например, constraint-ошибка в `save_sellers`/`_upsert_house_with_url`), исключение ловится `except Exception` в pipeline (`scrape_pipeline.py:250-253`), который только логирует + считает `houses_failed` и продолжает — без `db.rollback()`. Общий Session теперь в `InFailedSqlTransaction`, поэтому первый же `db.execute` следующего дома (а также Step 5 priority_rows SELECT на строке 261 и Step 5b на строках 370-375) падают с «current transaction is aborted» и так же проглатываются. Одна транзиентная DB-ошибка превращает весь остаток enrichment якоря в failures. `save_detail_enrichment` (`avito_detail.py:522-...`) и detail-цикл (Step 5, строки 338-342) имеют ту же форму. ## Корень Тот же класс, что Sber/forecasting: пойманное DB-исключение оставляет общий Session (`SessionLocal(autocommit=False)`) в aborted-state без `db.rollback()`. `save_house_catalog_enrichment` делает 5 db.execute с одним терминальным commit и без внутреннего try/except. CIAN-ветка (`scrape_pipeline.py:1383, 1415`) и Yandex-ветка (`scrape_pipeline.py:866`) в аналогичных except-блоках `db.rollback()` вызывают — Avito-ветка просто не получила этот фикс. ## Evidence `scrape_pipeline.py:250-253` (Step 4, house enrich): ```python except Exception as e: logger.warning("pipeline:house_enrich failed for %s: %s", house_path, e) counters.houses_failed += 1 counters.errors.append(f"house {house_path}: {e}") # нет db.rollback() — следующий db.execute падёт с InFailedSqlTransaction ``` `avito_houses.py:1194-1223` — 5 операций (`_upsert_house_with_url`, `save_house_reviews`, `save_sellers`, `link_listings_to_seller`, `save_placement_history`) и один `db.commit()` на строке 1223, без savepoint/rollback. Те же незащищённые except-блоки: Step 5 detail (`scrape_pipeline.py:338-342`), Step 5b (`scrape_pipeline.py:370-375`). ## Suggested fix В каждой пойманной ветке (Step 4 строки 250-253, Step 5 detail 338-342, Step 5b 370-375) вызывать `db.rollback()` перед continue, ЛИБО обернуть каждый вызов `save_house_catalog_enrichment`/`save_detail_enrichment` в `with db.begin_nested():`, чтобы сбойное обогащение откатывало только свой SAVEPOINT и оставляло сессию пригодной для следующих строк (как уже сделано для CIAN/Yandex). ## Severity-обоснование **medium**: pipeline продолжается и логирует ошибки, данные не повреждаются, но enrichment всего остатка sweep'а якоря молча теряется при единственной DB-ошибке во время house-enrich. Меньше Sber high (там весь pull стирается), сопоставимо с forecasting. --- _week ревью 1 · multi-agent аудит 2026-06-14 · severity=**medium** · category=`error-handling`_ Затронутые файлы: - `/Users/anton/Птица/gendesign/tradein-mvp/backend/app/services/scrape_pipeline.py` - `/Users/anton/Птица/gendesign/tradein-mvp/backend/app/services/scrapers/avito_houses.py` - `/Users/anton/Птица/gendesign/tradein-mvp/backend/app/services/scrapers/avito_detail.py`
bot-backend added the
week ревью 1
label 2026-06-14 18:09:47 +00:00
bot-backend added the
scope/backend
label 2026-06-15 11:01:53 +00:00
Owner

🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)

Текущее состояние кода (git show forgejo/main): scrape_pipeline.py — три незащищённых except в Avito-ветке: Step 4 house enrich (except Exception as e:counters.houses_failed += 1, без db.rollback()), Step 5 detail (except Exception as e:counters.detail_failed += 1), Step 5b house(detail) enrich (except Exception as e:counters.houses_failed += 1). avito_houses.py:save_house_catalog_enrichment:1178 — 5 последовательных INSERT/UPSERT + единственный db.commit() на :1223, без savepoint/внутреннего try. avito_detail.py:save_detail_enrichment:522 — та же форма, db.commit() на :596.

Корень: Промежуточный statement падает → исключение ловится в pipeline-except, который только логирует/считает и continue, без db.rollback(). Общий Session в InFailedSqlTransaction → первый же db.execute следующего дома (и SELECT priority_rows) падает каскадом. Контраст: CIAN-ветка :1383/:1415 и Yandex-ветка :866 в аналогичных except вызывают db.rollback() — Avito-ветка не получила фикс.

Что править:

  • В каждом пойманном except Avito-ветки (Step 4 / Step 5 detail / Step 5b) вызывать db.rollback() перед continue, ЛИБО
  • Обернуть каждый вызов save_house_catalog_enrichment / save_detail_enrichment в with db.begin_nested(): (SAVEPOINT откатывает только сбойное обогащение, оставляя сессию пригодной) — как уже сделано в matching (#849) и в CIAN/Yandex.

Что посмотреть:

  • Код: scrape_pipeline.py Avito Step 4/5/5b except-блоки + эталоны rollback :866/:1383/:1415; avito_houses.py:save_house_catalog_enrichment:1178-1223 (5 ops + commit), avito_detail.py:save_detail_enrichment:522-596. Тест: одна DB-ошибка на доме N → дома N+1.. всё равно обогащаются.
  • Vault: tests/test_849_matching_savepoint.py — прецедент SAVEPOINT-паттерна (no whole-batch rollback). Заметок vault по Avito-каскаду не найдено.

Примечание: верификация против forgejo/main (рабочая ветка трогает scrape_pipeline.py — строки могут сдвинуться).

**🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)** **Текущее состояние кода (git show forgejo/main):** `scrape_pipeline.py` — три незащищённых except в Avito-ветке: Step 4 house enrich (`except Exception as e:` → `counters.houses_failed += 1`, без `db.rollback()`), Step 5 detail (`except Exception as e:` → `counters.detail_failed += 1`), Step 5b house(detail) enrich (`except Exception as e:` → `counters.houses_failed += 1`). `avito_houses.py:save_house_catalog_enrichment:1178` — 5 последовательных INSERT/UPSERT + единственный `db.commit()` на `:1223`, без savepoint/внутреннего try. `avito_detail.py:save_detail_enrichment:522` — та же форма, `db.commit()` на `:596`. **Корень:** Промежуточный statement падает → исключение ловится в pipeline-except, который только логирует/считает и continue, без `db.rollback()`. Общий Session в `InFailedSqlTransaction` → первый же `db.execute` следующего дома (и SELECT priority_rows) падает каскадом. Контраст: CIAN-ветка `:1383`/`:1415` и Yandex-ветка `:866` в аналогичных except вызывают `db.rollback()` — Avito-ветка не получила фикс. **Что править:** - В каждом пойманном except Avito-ветки (Step 4 / Step 5 detail / Step 5b) вызывать `db.rollback()` перед continue, ЛИБО - Обернуть каждый вызов `save_house_catalog_enrichment` / `save_detail_enrichment` в `with db.begin_nested():` (SAVEPOINT откатывает только сбойное обогащение, оставляя сессию пригодной) — как уже сделано в matching (#849) и в CIAN/Yandex. **Что посмотреть:** - Код: `scrape_pipeline.py` Avito Step 4/5/5b except-блоки + эталоны rollback `:866`/`:1383`/`:1415`; `avito_houses.py:save_house_catalog_enrichment:1178-1223` (5 ops + commit), `avito_detail.py:save_detail_enrichment:522-596`. Тест: одна DB-ошибка на доме N → дома N+1.. всё равно обогащаются. - Vault: `tests/test_849_matching_savepoint.py` — прецедент SAVEPOINT-паттерна (no whole-batch rollback). Заметок vault по Avito-каскаду не найдено. Примечание: верификация против `forgejo/main` (рабочая ветка трогает scrape_pipeline.py — строки могут сдвинуться).
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#1368
No description provided.