(medium) Блокирующий синхронный psycopg-IO в async-хендлере get_parcel_connection_points #1346

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

Описание

Хендлер async def get_parcel_connection_points напрямую вызывает синхронную блокирующую get_connection_points(db, ...) из quarter_dump_lookup.py. Эта функция делает несколько тяжёлых блокирующих psycopg3-запросов (_get_parcel_wkt: 2 fallback SELECT + ST_AsText; проверка nspd_quarter_dumps; _get_engineering_structures_by_boundary: jsonb_array_elements + ST_Distance; _get_zouit_engineering_overlaps: ST_Intersects). Всё это выполняется в event loop без выхода в threadpool, блокируя все конкурентные coroutines на время spatial-запросов.

Корень

Блокирующий IO внутри async def без executor. Сосед analyze_parcel (parcels.py:1267) корректно объявлен sync def и крутится в threadpool — расхождение подтверждает, что connection-points должен быть таким же sync def. Нарушение .claude/rules/backend.md.

Evidence

parcels.py:2909-2925:

async def get_parcel_connection_points(
    cad_num: str,
    db: Annotated[Session, Depends(get_db)],
    radius_m: Annotated[int, Query(ge=50, le=2000)] = 500,
) -> ConnectionPointsResponse:
    ...
    try:
        data = get_connection_points(db, cad_num, radius_m)  # блокирующий psycopg IO в event loop

Grep по parcels.py: ни run_in_threadpool, ни run_in_executor не найдено.

Suggested fix

Сделать хендлер синхронным def get_parcel_connection_points(...) (как analyze_parcel) — Starlette выполнит его в threadpool. Альтернатива: await run_in_threadpool(get_connection_points, db, cad_num, radius_m).

Обоснование severity

medium: деградация пропускной способности под нагрузкой, но не data corruption и не crash. confidence 0.92.


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

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

  • /Users/anton/Птица/gendesign/backend/app/api/v1/parcels.py
## Описание Хендлер `async def get_parcel_connection_points` напрямую вызывает синхронную блокирующую `get_connection_points(db, ...)` из quarter_dump_lookup.py. Эта функция делает несколько тяжёлых блокирующих psycopg3-запросов (`_get_parcel_wkt`: 2 fallback SELECT + ST_AsText; проверка `nspd_quarter_dumps`; `_get_engineering_structures_by_boundary`: jsonb_array_elements + ST_Distance; `_get_zouit_engineering_overlaps`: ST_Intersects). Всё это выполняется в event loop без выхода в threadpool, блокируя все конкурентные coroutines на время spatial-запросов. ## Корень Блокирующий IO внутри `async def` без executor. Сосед `analyze_parcel` (parcels.py:1267) корректно объявлен sync `def` и крутится в threadpool — расхождение подтверждает, что connection-points должен быть таким же sync `def`. Нарушение `.claude/rules/backend.md`. ## Evidence `parcels.py:2909-2925`: ```python async def get_parcel_connection_points( cad_num: str, db: Annotated[Session, Depends(get_db)], radius_m: Annotated[int, Query(ge=50, le=2000)] = 500, ) -> ConnectionPointsResponse: ... try: data = get_connection_points(db, cad_num, radius_m) # блокирующий psycopg IO в event loop ``` Grep по parcels.py: ни `run_in_threadpool`, ни `run_in_executor` не найдено. ## Suggested fix Сделать хендлер синхронным `def get_parcel_connection_points(...)` (как analyze_parcel) — Starlette выполнит его в threadpool. Альтернатива: `await run_in_threadpool(get_connection_points, db, cad_num, radius_m)`. ## Обоснование severity medium: деградация пропускной способности под нагрузкой, но не data corruption и не crash. confidence 0.92. --- _week ревью 1 · multi-agent аудит 2026-06-14 · severity=**medium** · category=`async-concurrency`_ Затронутые файлы: - `/Users/anton/Птица/gendesign/backend/app/api/v1/parcels.py`
bot-backend added the
week ревью 1
label 2026-06-14 18:08:48 +00:00
bot-backend added the
scope/backend
label 2026-06-15 11:01:50 +00:00
Owner

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

Текущее состояние кода: На forgejo/main backend/app/api/v1/parcels.py:2945async def get_parcel_connection_points(...) напрямую вызывает синхронную get_connection_points(db, cad_num, radius_m) (2961) без offload. В рабочей ветке зеркально: parcels.py:2867 (async def) → get_connection_points(...) на 2883. Сосед analyze_parcel корректно объявлен sync def (1269 на main / 1265 локально). Grep по parcels.py: ни run_in_threadpool, ни run_in_executor, ни to_thread в этом хендлере нет.

Корень: Блокирующий psycopg3 spatial-IO (get_connection_points_get_parcel_wkt + nspd_quarter_dumps + ST_Distance/ST_Intersects-запросы) выполняется в async def без выхода в threadpool → деградация пропускной способности под нагрузкой (все coroutines воркера ждут на время spatial-запросов).

Что править:

  • Сделать хендлер синхронным def get_parcel_connection_points(...) (Starlette выполнит в threadpool, как analyze_parcel). Альтернатива: data = await run_in_threadpool(get_connection_points, db, cad_num, radius_m).

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

  • Код: backend/app/api/v1/parcels.py:2945 (main) / 2867 (рабочая ветка); quarter_dump_lookup.py:get_connection_points. Тест: смоук на конкурентность endpoint'а.
  • Vault: релевантных заметок не найдено.
**🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)** **Текущее состояние кода:** На `forgejo/main` `backend/app/api/v1/parcels.py:2945` — `async def get_parcel_connection_points(...)` напрямую вызывает синхронную `get_connection_points(db, cad_num, radius_m)` (2961) без offload. В рабочей ветке зеркально: `parcels.py:2867` (`async def`) → `get_connection_points(...)` на 2883. Сосед `analyze_parcel` корректно объявлен sync `def` (1269 на main / 1265 локально). Grep по parcels.py: ни `run_in_threadpool`, ни `run_in_executor`, ни `to_thread` в этом хендлере нет. **Корень:** Блокирующий psycopg3 spatial-IO (`get_connection_points` → `_get_parcel_wkt` + `nspd_quarter_dumps` + ST_Distance/ST_Intersects-запросы) выполняется в `async def` без выхода в threadpool → деградация пропускной способности под нагрузкой (все coroutines воркера ждут на время spatial-запросов). **Что править:** - Сделать хендлер синхронным `def get_parcel_connection_points(...)` (Starlette выполнит в threadpool, как `analyze_parcel`). Альтернатива: `data = await run_in_threadpool(get_connection_points, db, cad_num, radius_m)`. **Что посмотреть:** - Код: `backend/app/api/v1/parcels.py:2945` (main) / 2867 (рабочая ветка); `quarter_dump_lookup.py:get_connection_points`. Тест: смоук на конкурентность endpoint'а. - Vault: релевантных заметок не найдено.
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#1346
No description provided.