[SF] analyze: InFailedSqlTransaction в ird_overlays → пустой neighbors_summary + другие downstream-секции (#1130 follow-up) #1196

Closed
opened 2026-06-12 18:00:16 +00:00 by bot-backend · 1 comment
Collaborator

Проблема

На проде в hot-path POST /api/v1/parcels/{cad}/analyze обнаружено: parcel_ird_overlaps (запрос к ird_overlays) под определёнными условиями (parcel WKT с длинным MULTIPOLYGON?) бросает исключение, оставляя transactional state в InFailedSqlTransaction. Следующий SQL в той же сессии (_neighbors_summary через CTE WITH neighbors AS ... overlaps AS ... SELECT ...) падает с:

psycopg.errors.InFailedSqlTransaction: current transaction is aborted, commands ignored until end of transaction block

_neighbors_summary обёрнут try/except → graceful data_available: False, note: ..., но фронт получает пустой neighbors_summary блок с 0 соседей, даже если соседи реально есть в БД.

Воспроизведение

cad 66:41:0304011:1124, replay × 3 через TestClient(app) + POST /analyze (X-Authenticated-User: admin) — НЕ в каждом replay, но воспроизводится регулярно. Полная trace в логах backend контейнера показывает первый failed SQL = ird_overlays ST_Intersects на длинном MULTIPOLYGON parcel_wkt.

Что чинить

  1. Root cause в parcel_ird_overlaps (или его вызывающем) — почему конкретный input роняет SQL? Скорее всего нужен SAVEPOINT через db.begin_nested() вокруг тех запросов, что могут падать на конкретных геометриях. Это паттерн уже задокументирован в .claude/rules/backend.md для loop upserts, но применим и здесь.
  2. Defensive: hot-path analyze_parcel должен явно делать db.rollback() после graceful failure любого ИЗ ird-вызовов, чтобы транзакция не оставалась aborted для последующих read-only запросов в той же сессии.

Импакт

  • Фронт получает neighbors_summary.data_available: False для участков с проблемной геометрией → пустой блок соседей в analyze-отчёте.
  • Возможно, тот же эффект на granddoc, okn_objects, pat_subzones и др. (всё, что идёт ПОСЛЕ ird-вызовов в той же сессии).

Discovered through

Замер #1130 Phase B (2026-06-12) — побочное наблюдение в prod replay'ях после деплоя PR #1195. Не блокер perf-цели (она достигнута: p50 2.35s vs цели ≤3s), но реальная регрессия в проде.

scope/backend, site-finder

## Проблема На проде в hot-path `POST /api/v1/parcels/{cad}/analyze` обнаружено: `parcel_ird_overlaps` (запрос к `ird_overlays`) под определёнными условиями (parcel WKT с длинным MULTIPOLYGON?) бросает исключение, оставляя transactional state в `InFailedSqlTransaction`. Следующий SQL в той же сессии (`_neighbors_summary` через CTE `WITH neighbors AS ... overlaps AS ... SELECT ...`) падает с: ``` psycopg.errors.InFailedSqlTransaction: current transaction is aborted, commands ignored until end of transaction block ``` `_neighbors_summary` обёрнут try/except → graceful `data_available: False, note: ...`, **но фронт получает пустой `neighbors_summary` блок с 0 соседей**, даже если соседи реально есть в БД. ## Воспроизведение cad `66:41:0304011:1124`, replay × 3 через TestClient(app) + POST /analyze (X-Authenticated-User: admin) — НЕ в каждом replay, но воспроизводится регулярно. Полная trace в логах backend контейнера показывает первый failed SQL = `ird_overlays` ST_Intersects на длинном MULTIPOLYGON parcel_wkt. ## Что чинить 1. **Root cause** в `parcel_ird_overlaps` (или его вызывающем) — почему конкретный input роняет SQL? Скорее всего нужен `SAVEPOINT` через `db.begin_nested()` вокруг тех запросов, что могут падать на конкретных геометриях. Это паттерн уже задокументирован в `.claude/rules/backend.md` для loop upserts, но применим и здесь. 2. **Defensive**: hot-path `analyze_parcel` должен явно делать `db.rollback()` после graceful failure любого ИЗ ird-вызовов, чтобы транзакция не оставалась aborted для последующих read-only запросов в той же сессии. ## Импакт - Фронт получает `neighbors_summary.data_available: False` для участков с проблемной геометрией → пустой блок соседей в analyze-отчёте. - Возможно, тот же эффект на `granddoc`, `okn_objects`, `pat_subzones` и др. (всё, что идёт ПОСЛЕ ird-вызовов в той же сессии). ## Discovered through Замер #1130 Phase B (2026-06-12) — побочное наблюдение в prod replay'ях после деплоя PR #1195. Не блокер perf-цели (она достигнута: p50 2.35s vs цели ≤3s), но реальная регрессия в проде. scope/backend, site-finder
bot-backend added the
bug
label 2026-06-12 18:00:16 +00:00
Author
Collaborator

Fixed in PR #1197 (rebase-merged sha 2b23a4b, deployed 2026-06-13 04:13 UTC)

Root cause — OVERLAPS PG keyword conflict (регрессия моего PR #1195)

Не баг в parcel_ird_overlaps как я думал изначально — регрессия от PR #1195 Phase B. SQL-мердж в _neighbors_summary использовал CTE alias overlaps:

WITH neighbors AS (...),
     overlaps AS (...)        -- ← PostgreSQL parse error
SELECT ...

OVERLAPS — keyword PostgreSQL (binary operator (start1,end1) OVERLAPS (start2,end2) для time-periods). Парсер видит overlaps AS ( и ждёт OVERLAPS operator. Прямой psql-replay подтвердил:

WITH neighbors AS (...), overlaps AS (...) → ERROR: syntax error at or near "overlaps"
WITH neighbors AS (...), overlap_rows AS (...) → OK

Каждый POST /analyze с момента мерджа PR #1195 (~17:56 UTC) валил _neighbors_summary → транзакция aborted → дальше падали ird_overlays, persist analysis_run и весь ird блок. Фронт получал пустой neighbors_summary + пустой ird ~30 минут.

Fix

PR #1197 — переименование CTE alias overlapsoverlap_rows, 4 строки изменений. Семантика идентична PR #1195.

Проверка на проде после деплоя

Replay через TestClient (cad 66:41:0304011:1124):

neighbors_summary.data_available=True
neighbors_summary.neighbors_count=9      # было: 0 (пустой блок)
ird.ird_overlaps_count=10                # было: 0
ird.ird_by_kind={'airfield':2, 'servitude':1, 'zouit_engineering':4, 'zouit_other':3}

Lessons learned

  • tests/api/v1/test_*.py патчат БД через MagicMock — SQL не выполняется на реальной PG, keyword conflicts не ловятся. Это второй такой случай (первый — psycopg v3 :x::type antipattern).
  • Future-proofing: завожу follow-up — минимальный integration-тест с реальной (testcontainers / live PG) на analyze hot-path.
  • Перед мерджем SQL-меняющих PR имеет смысл прямой psql smoke на dev/staging БД.

Закрываю #1196.

## ✅ Fixed in PR #1197 (rebase-merged sha 2b23a4b, deployed 2026-06-13 04:13 UTC) ### Root cause — `OVERLAPS` PG keyword conflict (регрессия моего PR #1195) Не баг в `parcel_ird_overlaps` как я думал изначально — **регрессия от PR #1195 Phase B**. SQL-мердж в `_neighbors_summary` использовал CTE alias `overlaps`: ```sql WITH neighbors AS (...), overlaps AS (...) -- ← PostgreSQL parse error SELECT ... ``` `OVERLAPS` — keyword PostgreSQL (binary operator `(start1,end1) OVERLAPS (start2,end2)` для time-periods). Парсер видит `overlaps AS (` и ждёт OVERLAPS operator. Прямой psql-replay подтвердил: ``` WITH neighbors AS (...), overlaps AS (...) → ERROR: syntax error at or near "overlaps" WITH neighbors AS (...), overlap_rows AS (...) → OK ``` Каждый POST /analyze с момента мерджа PR #1195 (~17:56 UTC) валил _neighbors_summary → транзакция aborted → дальше падали `ird_overlays`, `persist analysis_run` и весь `ird` блок. **Фронт получал пустой `neighbors_summary` + пустой `ird` ~30 минут.** ### Fix PR #1197 — переименование CTE alias `overlaps` → `overlap_rows`, 4 строки изменений. Семантика идентична PR #1195. ### Проверка на проде после деплоя Replay через TestClient (cad `66:41:0304011:1124`): ``` neighbors_summary.data_available=True neighbors_summary.neighbors_count=9 # было: 0 (пустой блок) ird.ird_overlaps_count=10 # было: 0 ird.ird_by_kind={'airfield':2, 'servitude':1, 'zouit_engineering':4, 'zouit_other':3} ``` ### Lessons learned - `tests/api/v1/test_*.py` патчат БД через MagicMock — SQL не выполняется на реальной PG, keyword conflicts не ловятся. Это второй такой случай (первый — psycopg v3 `:x::type` antipattern). - Future-proofing: завожу follow-up — минимальный integration-тест с реальной (testcontainers / live PG) на analyze hot-path. - Перед мерджем SQL-меняющих PR имеет смысл прямой psql smoke на dev/staging БД. Закрываю #1196.
Sign in to join this conversation.
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#1196
No description provided.