-- 198_scrape_proxy_rotations.sql -- Issue #2600 п.5 — ротация exit-IP прокси ASocks по бану, со счётчиком и громким -- отказом. АДДИТИВНО, не трогает scrape_proxies (157_scrape_proxies.sql) кроме -- FK-ссылки; не трогает proxy_pool.py (параллельный PR #2609). -- -- WHY: -- Провайдер (ASocks, безлимитные порты) ограничивает ручную ротацию exit-IP тремя -- вызовами в сутки на порт (эмпирика, владелец аккаунта). app.services.proxy_rotation -- должен и проверять этот лимит ПЕРЕД обращением к API, и вести аудит попыток — -- без отдельной таблицы истории лимит негде считать (scrape_proxies хранит только -- текущее состояние, не историю). -- -- Semantics: -- Одна строка = одна попытка ротации (успешная ИЛИ неуспешная), но НЕ каждый -- вызов rotate_proxy() пишет строку — локально отклонённые попытки (нет -- rotate_url / нет ASOCKS_API_TOKEN / лимит уже исчерпан) вообще не доходят до -- HTTP-вызова и в таблицу не пишутся (см. app.services.proxy_rotation docstring -- за полным обоснованием "какие попытки считать против лимита"). -- http_status NULL = сетевая ошибка (ответа от провайдера не было вообще). -- -- Idempotency: -- CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS → повторный прогон -- no-op (auto-apply strict на деплое это требует). Весь файл в BEGIN/COMMIT. -- -- Dependencies: -- 157_scrape_proxies.sql (scrape_proxies.id — FK-таргет). BEGIN; CREATE TABLE IF NOT EXISTS scrape_proxy_rotations ( id bigserial PRIMARY KEY, proxy_id bigint NOT NULL REFERENCES scrape_proxies (id), rotated_at timestamptz NOT NULL DEFAULT now(), success boolean NOT NULL, http_status integer, note text ); COMMENT ON TABLE scrape_proxy_rotations IS 'Аудит + суточный лимит (#2600 п.5) ручных ротаций exit-IP через ASocks ' 'refresh-ip. Лимит провайдера — 3 попытки/сутки на порт; app.services.' 'proxy_rotation._quota_used_today считает только строки с http_status ' 'IS NOT NULL AND != 401 (реально дошедшие до провайдера) за последние 24ч.'; -- Проверка суточного лимита + выборка истории по прокси: (proxy_id, rotated_at). CREATE INDEX IF NOT EXISTS idx_scrape_proxy_rotations_proxy_time ON scrape_proxy_rotations (proxy_id, rotated_at); COMMIT;