(low) POST /geo/bulk игнорирует rate_ms — все bulk-джобы создаются с дефолтом 600ms без контроля оператора #1394

Closed
opened 2026-06-14 18:11:22 +00:00 by bot-backend · 0 comments
Collaborator

Описание

Эндпоинт POST /geo/bulk (bulk_enqueue_geo) не позволяет оператору задать частоту запросов (rate_ms) при массовом backfill geo. Модель запроса BulkGeoEnqueueRequest вообще не содержит поля rate_ms, а вызов enqueue_helper (= enqueue_geo_job) не передаёт этот параметр. В результате каждая bulk-джоба персистится в БД с дефолтом функции rate_ms=600 (nspd_geo.py:299). Для anti-bot-чувствительного NSPD-фетчера это жёстко фиксирует троттлинг и повышает риск WAF-блокировки на крупных прогонах (например, UrFO-wide backfill). Это не краш и не порча данных — но контракт API лишает оператора контроля над частотой, в отличие от POST /geo/jobs, который пробрасывает payload.rate_ms.

Корень

Две связанные причины в одном эндпоинте:

  1. BulkGeoEnqueueRequest (строки 751-771) не объявляет поле rate_ms — оператор физически не может передать значение через API.
  2. Вызов enqueue_helper в bulk-цикле (строки 922-933) не передаёт аргумент rate_ms, поэтому срабатывает дефолт enqueue_geo_job (rate_ms=600, nspd_geo.py:299), и именно 600 сохраняется в БД (nspd_geo.py:324, "r": rate_ms).

Дополнительно: воркер НЕ компенсирует это. На nspd_geo.py:383 стоит if job["rate_ms"]: — значение 600 truthy, поэтому fallback на job_settings (строки 386-388) не выполняется. Fallback срабатывает только при rate_ms NULL/0, но bulk-путь персистит именно 600.

Evidence

admin_scrape.py:751-771 — модель без rate_ms:

class BulkGeoEnqueueRequest(BaseModel):
    """Параметры для параллельного backfill geo по Свердловской обл."""
    parallelism: int = Field(default=5, ge=1, le=10)
    thematic_ids: list[int] = Field(default=[2], ...)
    region_codes: list[int] = Field(default=[66], ...)
    only_ddu: bool = Field(default=False, ...)
    source: str = Field(default="rosreestr_pending", ...)
    # <-- поля rate_ms нет

admin_scrape.py:922-933 — bulk-вызов без rate_ms:

job_id = enqueue_helper(
    name=f"bulk_{label}_t{thematic_id}_{idx + 1}/{n_jobs}",
    job_kind=label,
    source_kind=f"{payload.source}_chunk",
    source_params={...},
    cad_nums_with_thematic=cad_with_thematic,
    triggered_by="bulk_admin",
)   # <-- rate_ms не передан → дефолт 600

admin_scrape.py:737 — single-job путь для сравнения (передаёт rate_ms):

job_id = enqueue_helper(
    name=payload.name,
    ...
    rate_ms=payload.rate_ms,
    triggered_by="manual",
)

Suggested fix

Добавить в BulkGeoEnqueueRequest поле rate_ms: int = Field(default=600, ge=100, le=10000) и передавать rate_ms=payload.rate_ms в каждый вызов enqueue_helper в bulk-цикле, зеркаля поведение POST /geo/jobs (строка 737).

Обоснование severity (low)

Не краш и не порча данных. Путь достижим в проде при любом bulk backfill, и реально отнимает у оператора контроль над троттлингом на anti-bot-чувствительном фетчере, повышая риск WAF-блокировки на больших прогонах. Но дефолт 600ms сам по себе рабочий, обходной путь существует (single-job эндпоинт), поэтому влияние ограниченное — low корректна.


week ревью 1 · multi-agent аудит 2026-06-14 · severity=low · category=api-contract

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

  • /Users/anton/Птица/gendesign/backend/app/api/v1/admin_scrape.py
## Описание Эндпоинт `POST /geo/bulk` (`bulk_enqueue_geo`) не позволяет оператору задать частоту запросов (`rate_ms`) при массовом backfill geo. Модель запроса `BulkGeoEnqueueRequest` вообще не содержит поля `rate_ms`, а вызов `enqueue_helper` (= `enqueue_geo_job`) не передаёт этот параметр. В результате каждая bulk-джоба персистится в БД с дефолтом функции `rate_ms=600` (`nspd_geo.py:299`). Для anti-bot-чувствительного NSPD-фетчера это жёстко фиксирует троттлинг и повышает риск WAF-блокировки на крупных прогонах (например, UrFO-wide backfill). Это не краш и не порча данных — но контракт API лишает оператора контроля над частотой, в отличие от `POST /geo/jobs`, который пробрасывает `payload.rate_ms`. ## Корень Две связанные причины в одном эндпоинте: 1. `BulkGeoEnqueueRequest` (строки 751-771) не объявляет поле `rate_ms` — оператор физически не может передать значение через API. 2. Вызов `enqueue_helper` в bulk-цикле (строки 922-933) не передаёт аргумент `rate_ms`, поэтому срабатывает дефолт `enqueue_geo_job` (`rate_ms=600`, `nspd_geo.py:299`), и именно 600 сохраняется в БД (`nspd_geo.py:324`, `"r": rate_ms`). Дополнительно: воркер НЕ компенсирует это. На `nspd_geo.py:383` стоит `if job["rate_ms"]:` — значение 600 truthy, поэтому fallback на `job_settings` (строки 386-388) не выполняется. Fallback срабатывает только при `rate_ms` NULL/0, но bulk-путь персистит именно 600. ## Evidence `admin_scrape.py:751-771` — модель без `rate_ms`: ```python class BulkGeoEnqueueRequest(BaseModel): """Параметры для параллельного backfill geo по Свердловской обл.""" parallelism: int = Field(default=5, ge=1, le=10) thematic_ids: list[int] = Field(default=[2], ...) region_codes: list[int] = Field(default=[66], ...) only_ddu: bool = Field(default=False, ...) source: str = Field(default="rosreestr_pending", ...) # <-- поля rate_ms нет ``` `admin_scrape.py:922-933` — bulk-вызов без `rate_ms`: ```python job_id = enqueue_helper( name=f"bulk_{label}_t{thematic_id}_{idx + 1}/{n_jobs}", job_kind=label, source_kind=f"{payload.source}_chunk", source_params={...}, cad_nums_with_thematic=cad_with_thematic, triggered_by="bulk_admin", ) # <-- rate_ms не передан → дефолт 600 ``` `admin_scrape.py:737` — single-job путь для сравнения (передаёт rate_ms): ```python job_id = enqueue_helper( name=payload.name, ... rate_ms=payload.rate_ms, triggered_by="manual", ) ``` ## Suggested fix Добавить в `BulkGeoEnqueueRequest` поле `rate_ms: int = Field(default=600, ge=100, le=10000)` и передавать `rate_ms=payload.rate_ms` в каждый вызов `enqueue_helper` в bulk-цикле, зеркаля поведение `POST /geo/jobs` (строка 737). ## Обоснование severity (low) Не краш и не порча данных. Путь достижим в проде при любом bulk backfill, и реально отнимает у оператора контроль над троттлингом на anti-bot-чувствительном фетчере, повышая риск WAF-блокировки на больших прогонах. Но дефолт 600ms сам по себе рабочий, обходной путь существует (single-job эндпоинт), поэтому влияние ограниченное — low корректна. --- _week ревью 1 · multi-agent аудит 2026-06-14 · severity=**low** · category=`api-contract`_ Затронутые файлы: - `/Users/anton/Птица/gendesign/backend/app/api/v1/admin_scrape.py`
bot-backend added the
week ревью 1
label 2026-06-14 18:11:22 +00:00
bot-backend added the
scope/backend
label 2026-06-15 11:01:57 +00:00
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#1394
No description provided.