fix(msk): сухой прогон пред-геокода падал на отсутствующей кэш-таблице
Some checks failed
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Failing after 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Failing after 58s

Кэш `msk_raw.avito_geocode` создаётся только боевым прогоном (`geocode and not
dry_run`), а читается безусловно. На проде это роняло `--dry-run` первым же
запросом — UndefinedTable msk_raw.avito_geocode, то есть ломалась ровно та
репетиция, ради которой сухой прогон и существует.

Наличие отношения проверяется через `to_regclass`, а не ловится исключением: в
Postgres упавший оператор кладёт транзакцию целиком, и except потребовал бы
rollback посреди чужого батча.

Замер после правки (500 карточек, прод): отобрано 458, область 7, не разрешено
35 (7%), геокод-вызовов 338 на 500 карточек — дедупликация адреса внутри
страницы работает. Счётчики сходятся.

Заодно выяснилось, что дневная квота DaData на подсказки — 200 000, а не 10 000:
`stat/daily` на проде показывает suggestions remaining 200000 при нулевом
расходе. Весь корпус (21 565 различных адресов) проходит за один заход, дробить
на трое суток не нужно.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ8jqr4SFirX5tFLwdSrXh
This commit is contained in:
bot-backend 2026-09-11 18:51:38 +03:00
parent c5186883a9
commit de5f4a32cd
2 changed files with 51 additions and 1 deletions

View file

@ -224,6 +224,7 @@ CREATE TABLE IF NOT EXISTS msk_raw.avito_geocode (
resolved_at timestamptz NOT NULL DEFAULT NOW()
)
"""
_GEO_CACHE_EXISTS = "SELECT to_regclass('msk_raw.avito_geocode')"
_GEO_CACHE_SELECT = """
SELECT cache_key, lat, lon, region_code, full_address
FROM msk_raw.avito_geocode
@ -350,7 +351,17 @@ def _resolve_points(
return {}
points: dict[str, GeoPoint | None] = {}
cached = db.execute(text(_GEO_CACHE_SELECT), {"keys": list(wanted)}).mappings().all()
# Кэш создаётся только боевым прогоном (`geocode and not dry_run`), поэтому на
# первом `--dry-run` таблицы ещё нет и SELECT по ней роняет весь замер — то
# есть ломается ровно та репетиция, ради которой сухой прогон и существует.
# Проверяем наличие отношения, а не ловим исключение: в Postgres упавший
# оператор кладёт транзакцию целиком, и except потребовал бы rollback.
cache_exists = db.execute(text(_GEO_CACHE_EXISTS)).scalar() is not None
cached = (
db.execute(text(_GEO_CACHE_SELECT), {"keys": list(wanted)}).mappings().all()
if cache_exists
else ()
)
for row in cached:
points[row["cache_key"]] = (
None

View file

@ -105,6 +105,10 @@ class _FakeResult:
def all(self) -> list[dict[str, Any]]:
return self._rows
def scalar(self) -> Any:
"""Проба существования кэш-таблицы: None = таблицы ещё нет."""
return self._rows[0] if self._rows else None
class _FakeSession:
"""Отдаёт строки постранично по тому же keyset-условию, что и настоящая вью."""
@ -361,3 +365,38 @@ def test_geocode_is_refused_for_cian(captured: list[dict[str, Any]]) -> None:
"""У Циана город виден по префиксу округа даром — квоту на него не тратим."""
with pytest.raises(SystemExit):
mod.import_msk_raw(_FakeSession(_rows()), source="cian", batch_size=10, geocode=True)
def test_dry_run_survives_missing_geocode_cache(monkeypatch: pytest.MonkeyPatch) -> None:
"""Первый `--dry-run` идёт по проду, где кэш-таблицы ещё нет.
Таблицу создаёт только боевой прогон, поэтому SELECT по ней в сухом ронял
ровно ту репетицию, ради которой сухой прогон и существует (замер на проде
11.09: UndefinedTable msk_raw.avito_geocode). Резолвер обязан молча считать
кэш пустым и идти к DaData за остатком.
"""
class _NoCacheSession:
def __init__(self) -> None:
self.cache_reads = 0
def execute(self, sql: Any, params: dict[str, Any] | None = None) -> _FakeResult:
if "to_regclass" in str(sql):
return _FakeResult([]) # таблицы нет
self.cache_reads += 1
raise AssertionError("к отсутствующей кэш-таблице ходить нельзя")
point = mod.GeoPoint(55.6412, 37.6089, 77, "г Москва, ш Варшавское, д 62 к 1")
async def _fake_geocode(misses: list[Any]) -> dict[str, Any]:
return {misses[0][0]: point}
monkeypatch.setattr(mod, "_geocode_many", _fake_geocode)
db = _NoCacheSession()
points = mod._resolve_points(
db, [{"id": 1, "payload": _avito()}], mod.GeocodeBudget(10), dry_run=True
)
assert db.cache_reads == 0
assert list(points.values()) == [point]