F-07 — ProxyPool (Phase 1 in-memory MVP)
Контекст
После F-06 (BaseCollector + WB collectors) — proxy.py был minimal random.choice без state.
F-07 spec из X-collectors-proxy §3.2-3.4: sticky per-(account, rate_bucket) + weighted rotation +
health checks + per-error-type cooldown.
Plan: 2 дня. Actual: ~1h (~16x speedup, single session).
Что сделано
Phase 1 in-memory implementation в apps/api/src/razmakh_api/etl/proxy.py:
-
Proxydataclass: url, weight (1-10), last_used_at, consecutive_failures, last_error, cooldown_until. Identity = url (eq/hash на url). -
ProxyPoolclass:pick(sticky_key)/pick_proxy(sticky_key)— sticky via SHA256(key) mod len(sorted) + weighted random fallbackmark_success— reset state + weight+1mark_failure(error_type)— per-type penalty:rate_limit_429→ weight-2, cooldown 60sconnection_error→ weight-3, cooldown 300shttp_5xx→ weight-1, no cooldown (WB issue, не IP)other→ weight-1
- Consecutive failures escalation: 3+ failures → 10 min extended cooldown
health_check(proxy)— async HEADhttps://httpbin.org/ipчерез proxy, 5s timeouthealth_check_all()— parallelasyncio.gather- Backward compat F-06:
from_env(),from_string(),pick() -> str | None
-
WBClient integration (apps/api/src/razmakh_api/etl/wb/client.py):
- New args:
pool: ProxyPool | None,sticky_key: str | None - При
poolactive: per-request freshhttpx.AsyncClient(proxy=pool.pick_proxy(sticky_key).url)mark_success/mark_failureoutcome reporting
- При
pool=None(legacy): single shared client с фиксированнымproxy=str(F-06 contract) - Graceful fallback: если все proxies в cooldown → log warning + try direct request
- New args:
-
Collectors sticky_key (НЕ модифицировал логику кроме sticky_key):
wb.statistics.sales→f"{ctx.marketplace_account_id}:wb.statistics"wb.common.seller_info→f"{ctx.marketplace_account_id}:wb.common"- Bucket separation per X-collectors-proxy §3.4 spec
-
Tests (
apps/api/tests/test_proxy.py) — 36 unit tests:- Construction: dedup, whitespace strip, default weights
- from_env: backward compat F-06, blank lines, returns str URL
- Sticky pick: consistency 10/10 same key, different keys distribute, balanced over 400 keys × 4 proxies
- Cooldown handling: sticky fall through, all-in-cooldown returns None
- Weighted random: equal distribution, higher weight picked more
- mark_failure: per-error-type weight + cooldown, escalation 3+ consecutive
- mark_success: resets state + weight+1, cap at 10
- Health check via respx: 200→success, 429→rate_limit, 5xx→http_5xx, ConnectError→connection_error, parallel all
Что заимствовано из X-collectors-proxy
| Spec § | Что взято в F-07 |
|---|---|
| §3.2 architecture | Concept of pool с per-proxy state (weight, health_status, cooldown) |
| §3.3 health check | HEAD к external endpoint (ipify заменён на httpbin.org/ip — simpler, no JSON parse) |
| §3.4 sticky | Per-(account, rate_bucket) deterministic binding via hash |
| §3.4 weighted | random.choices с weights param |
| §3.4 outcome | Per-status-code penalty + cooldown (429 / connection / 5xx) |
Что deferred Phase 1.5+ (НЕ сделано сейчас, документировано в plan)
etl.proxy_pooltable (PG schema) — Phase 1.5 multi-worker shared state. Текущая реализация: in-memory dict per process. Достаточно для Phase 1 (single uvicorn worker).- Redis-backed pool state — Phase 1.5+ (multi-worker shared limiter + pool, нужен Redis stack).
- Cron health monitoring (ipify 30s, WB ping 5min) — F-10 observability. Текущая
реализация:
health_check()доступен но не auto-scheduled. - 14-day proxy rotation — Phase 1.5+ infra automation.
- 10 RU-IP proxies provisioned (Tailscale exit nodes / IPRoyal) — Block 1 prerequisites (provisioning task, не code task).
- secret_ref format для proxy credentials — Block 1 prep (когда real proxies появятся,
они в Infisical как
razmakh://prod/_internal/proxy/<id>/url). - Prometheus metrics exporter (active/banned count, latency p95) — F-10.
- Alert rules
> 50% unhealthy → page on-call— F-10. - Multi-provider failover (Netnik Tailscale + IPRoyal + ProxyMesh) — Phase 2+.
- Per-region geo routing — Phase 2+ (SaaS multi-region).
Lesson learned (главный вывод)
В контексте Phase 1 proxy pool — делай in-memory с state в Proxy dataclass + ProxyPool dict, sticky через deterministic hash, weighted через random.choices. Это покрывает 80% spec из X-collectors-proxy §3.2-3.4 без PG dependency. PG-backed
etl.proxy_pool+ Redis-backed shared state defer на Phase 1.5+ когда появится multi-worker requirement.
Anti-pattern: писать PG migration + Redis client в Phase 1 когда single uvicorn worker — overengineering, удлиняет F-07 с 1h до 1-2 дней без real value.
В каких будущих ситуациях пригодится
- Phase 1.5 Block 1.5.1 (multi-worker scaling) — migrate state в Redis (нужна Redis в stack)
- F-10 observability — добавить Prometheus exporters + Grafana dashboard для pool state
- Block 1 ETL onboarding — provision 10 RU-IP proxies + secret_ref в Infisical
- Phase 2 multi-region SaaS — per-region geo routing layer над текущим pool
Что подтверждено
- 36/36 unit tests pass (respx для httpbin mock, no real network)
- mypy strict clean (10 etl source files)
- ruff clean (Cyrillic комментарии OK per per-file-ignores RUF001/002/003)
- 88/88 full suite pass (нет F-06 regressions: 36 proxy + 15 infisical + 26 manifest + 11 secret_ref)
Process notes
✅ Research consulted перед implementation (per f06-skipped-research lesson):
- Прочитал X-collectors-proxy.md §3.2-3.4 spec полностью
- Прочитал C-wb-api-inventory.md §rate limits (token-level → sticky binding rationale)
- Прочитал lessons/f06-skipped-research-halturность-2026-05-17.md (правило permanent)
- Прочитал estimates-recalibration template для tracking
✅ Scope discipline: НЕ добавил etl.proxy_pool PG migration (deferred Phase 1.5).
НЕ переписал runner / base.py / rate_limiter.py. Только sticky_key в 2 collectors.
✅ Backward compat: F-06 tests pass — ProxyPool.from_env().pick() всё ещё работает,
collectors могут вызывать WBClient(token=..., proxy=str) напрямую.
Связанные lessons
- [[lessons/f06-skipped-research-halturность-2026-05-17]] — что research consultation важна
- [[lessons/estimates-recalibration-2026-05-17]] — 16x speedup на pure-code задачах подтверждён ещё раз
- [[research/X-collectors-proxy]] — full spec (часть приземлена сейчас, часть Phase 1.5+)