СправочникПрактика › Лимит потоков в прокси

Лимит потоков в прокси: сколько ставить под задачу и как посчитать нагрузку на пакет

Приватный серверный пул IPv4 и SOCKS5 с лимитом до 3000 потоков

Лимит потоков это верхняя граница одновременно открытых соединений, которые сервис держит для вашего доступа. Обычный пакет даёт 1000 потоков, корпоративный поднимает границу до 3000. Счётчик смотрит на одновременность: сколько сокетов открыто в конкретную секунду, столько потоков и занято. Сколько запросов вы через них прогнали за час, счётчику безразлично.

Отсюда и путаница, из-за которой пакет берут вслепую. Число потоков просят назвать заранее, при оформлении доступа, а величина эта производная: она вытекает из объёма задачи, срока прогона и времени ответа площадки. Ниже я показываю, как перевести три известные цифры в четвёртую, разбираю три задачи разного масштаба, показываю деление лимита между двумя привязками и способ замерить фактическую одновременность у себя на машине.

Связь между потоком, временем ответа и темпом запросов в час

Поток и запрос в секунду: две разные величины

Поток занимает слот, пока соединение открыто. Запрос в секунду измеряет темп: сколько обращений уходит за единицу времени. Величины связаны, совпадают редко, и подмена одной другой при подборе пакета обходится дорого.

Проверяется это одним умножением. Десять потоков при среднем ответе 0,5 секунды дают 20 запросов в секунду. Те же десять потоков на медленной площадке, где ответ приходит через 5 секунд, дадут 2 запроса в секунду, разница в десять раз при одинаковом числе слотов. Слоты вы покупаете, темп получаете, а множителем между ними работает время ответа, которое зависит от площадки и от веса страницы.

ВеличинаЧто измеряетЕдиницаОт чего зависитГде видно
ПотокОдновременностьОткрытые соединенияНастройки клиента и число воркеровss, счётчик сервиса, пул клиента
Запрос в секундуТемпОбращения за секундуПотоки и время ответаЛоги прогона, счётчик приложения
Время ответаЗадержкуСекундыПлощадка, вес страницы, маршрутМетрика клиента, поле elapsed

Формула перехода короткая: темп равен числу потоков, делённому на время ответа. С одного потока за час выходит 3600, делённые на T. Я держу эту таблицу под рукой, когда прикидываю пакет без калькулятора.

Время ответа TЗапросов в час с одного потока10 потоков100 потоков
0,4 с900090 000900 000
0,8 с450045 000450 000
1,2 с300030 000300 000
2,0 с180018 000180 000
3,0 с120012 000120 000
5,0 с720720072 000

Правая часть таблицы объясняет, почему тысяча потоков закрывает почти любой прогон средней руки. Даже на тяжёлой площадке с ответом 3 секунды сотня слотов вывозит 120 тысяч запросов в час, а тысяча слотов при том же ответе поднимает потолок до миллиона с лишним. Упор в число потоков случается у тех, кто открывает соединения быстрее, чем закрывает.

Откуда берётся ограничение и как его считает сервис

Счёт идёт по одновременным соединениям к точке входа. Открыли сокет, счётчик прибавил единицу, закрыли, счётчик вернул её обратно. Пул на стороне сервиса держит около 12 000 активных адресов, ротация внутри пула автоматическая, и на арифметику лимита это никак не влияет: слот занимает соединение, независимо от того, через какой адрес оно вышло наружу. Подробности про перебор выходов собраны на странице смена выхода внутри пула.

Три вещи занимают слоты дольше, чем ожидает автор кода.

Первая это keep-alive. HTTP-клиент по умолчанию держит соединение открытым после ответа, рассчитывая переиспользовать его под следующий запрос. Пока сокет живёт, слот занят, даже когда запросов через него сейчас нет. Пул на 200 keep-alive соединений съедает 200 потоков в состоянии простоя.

Вторая это отсутствие таймаута. Зависший сокет висит до тех пор, пока его не оборвёт операционная система по своим настройкам, и счёт там идёт на минуты. Двадцать зависших соединений в час при прогоне на сутки превращаются в постоянно занятые слоты, которые вы нигде не учли.

Третья это параллельные копии прогона. Крон запустил задачу в девять утра, она затянулась, через час крон запустил её снова, и одновременность удвоилась. Я такое видел на своём же расписании и с тех пор ставлю файловую блокировку на каждый прогон.

Отдельно скажу про трафик. Он безлимитный на всех пакетах, и это заметно упрощает расчёт: вес страницы, число картинок и объём выкачанного добра в формулу не входят вовсе. Считать нужно две величины, потоки и время, что снимает целый пласт прикидок. Если вы работаете с тяжёлыми страницами, каталогами картинок или большими выгрузками, посмотрите условия пакетов с безлимитным трафиком, там граница проходит только по потокам и сроку доступа.

Ещё одна вещь, которую полезно знать заранее: сервис следит за аномальной активностью, чтобы пул оставался быстрым для всех. Аккаунты с подозрительно высокой активностью ставятся на паузу до выяснения. Поэтому темп я поднимаю ступенями, по 20 процентов за шаг, с паузой между шагами, и такой подъём даёт понятную картину: видно, на какой ступени начинают расти отказы, и видно, что рост пришёл со стороны площадки.

Формула перевода объёма задачи в число потоков

Четыре входа, два действия, один результат. Объём задачи V в запросах, срок прогона D в часах, запас K на повторы и редиректы, среднее время ответа T в секундах.

Сначала считаем требуемый темп: R равно V, умноженному на K, делённому на D. Получаем запросы в час. Затем переводим темп в одновременность: P равно R, умноженному на T и делённому на 3600, с округлением вверх. Округление вверх обязательно, дробных слотов не бывает.

from math import ceil

V = 200_000      # запросов в задаче
D = 12           # срок прогона, часы
K = 1.15         # запас на повторы, редиректы и пустые ответы
T = 2.4          # среднее время ответа, секунды

R = V * K / D               # требуемый темп, запросов в час
P = ceil(R * T / 3600)      # потоки, округление вверх
R2 = P * 3600 / T           # фактический темп после округления

print(round(R), P, round(R2))   # 19167 13 19500

Запас K закрывает то, чего формула не видит: повторы после сетевых сбоев, редиректы, страницы с кодом 200 и пустым телом, дубли в исходном списке ссылок. На спокойной площадке я беру 1,1. На площадке с проверочными окнами беру 1,25. Ниже единицы K не опускается никогда, иначе прогон гарантированно выйдет за срок.

Обратный пересчёт R2 нужен для проверки. После округления вверх темп получается выше требуемого, и разницу полезно видеть глазами: если фактический темп выскочил на треть выше расчётного, прогон закончится раньше срока, и это повод либо снять поток, либо честно сказать себе, что запас лежит в кармане.

Время ответа T меряется до расчёта, на своей же задаче. Я снимаю 200 пробных запросов в один поток, беру медиану, и только потом считаю. Среднее арифметическое здесь врёт: пара таймаутов по 30 секунд утягивает его вверх и раздувает число потоков вдвое против нужного.

Число потоков, которое дала формула на трёх задачах разного объёма

Пример первый: прайс на 4200 позиций за час

Задача бытовая. Поставщик отдаёт прайс страницами, нужно снять 4200 карточек и уложиться в час, чтобы цифры попали в утреннюю выгрузку. Медиана ответа на пробнике вышла 1,8 секунды, площадка спокойная, запас беру 1,2 с учётом того, что часть карточек отдаётся редиректом на новый адрес.

Требуемый темп: 4200, умноженные на 1,2 и делённые на 1, дают 5040 запросов в час. Потоки: 5040, умноженные на 1,8 и делённые на 3600, дают 2,52, округляем вверх до 3. Каждый поток при ответе 1,8 секунды выдаёт 2 тысячи запросов в час, три потока дают 6000. Прогон закроется примерно за 50 минут, десять минут остаются в резерве.

Три потока из тысячи. Цифра выглядит скромно, и это нормальная картина для задач такого масштаба: подавляющее большинство прайсов, выгрузок и ежедневных сверок укладывается в первые два десятка слотов. Пакет здесь подбирается по сроку доступа к пулу, потоки берутся с огромным запасом сами собой, а под разовую выгрузку хватает суточного доступа.

Отдельно отмечу поведение клиента. Три воркера в ThreadPoolExecutor при keep-alive держат больше трёх сокетов, если библиотека открывает соединение под каждый хост отдельно. Прайс жил на двух доменах, картинки на третьем, и фактическая одновременность подскочила до девяти. На тысяче слотов это незаметно, при расчёте под жёсткую границу такое расхождение ломает всю прикидку.

Пример второй: каталог на 200 тысяч карточек

Здесь начинается взрослая арифметика. Каталог маркетплейса, 200 тысяч карточек, срок 12 часов ночного окна. Медиана ответа 2,4 секунды, страницы тяжёлые, запас 1,15.

Темп: 200 тысяч умножить на 1,15, поделить на 12, получаем 19 167 запросов в час. Потоки: 19 167 умножить на 2,4, поделить на 3600, получаем 12,78, округляем до 13. Фактический темп: каждый поток выдаёт 1500 запросов в час, тринадцать дают 19 500. За 12 часов уходит 234 тысячи обращений, чего хватает на 200 тысяч карточек плюс повторы.

Тринадцать потоков под каталог в 200 тысяч позиций. Многих эта цифра удивляет, потому что в голове сидит связка «много данных, много потоков», а работает связка «короткий срок, много потоков». Сожмите окно вчетверо, и картина поменяется: при сроке 4 часа темп поднимается до 57 500 в час, потоки до 39. Растяните до суток, и хватит 7 слотов.

ЗадачаОбъём VСрок DЗапас KОтвет TТемп R, запросов в часПотоки PФактический темп
Прайс поставщика42001 час1,21,8 с504036000
Каталог маркетплейса200 00012 часов1,152,4 с19 1671319 500
Съём выдачи по расписанию900040 минут1,252,0 с16 8751018 000
Каталог в сжатом окне200 0004 часа1,152,4 с57 5003958 500

На каталоге упор пришёл со стороны площадки. Мой пакет к тому моменту был занят на процент с небольшим, при этом на 13 потоках доля кодов 429 держалась около 9 процентов, и добавление слотов её только поднимало. Помогло обратное движение: срок растянут до 16 часов, темп упал до 14 375 в час, потоки до 10, отказы ушли под 2 процента. Пакет при этом не менялся ни разу, менялось распределение той же работы по времени. Приёмы такого рода чаще всего нужны в многопоточных заданиях: тайминги, повторы и раздача нагрузки описаны на странице прокси под задания A-Parser.

Пример третий: ежедневный съём по расписанию

Третий случай отличается тем, что срок задан жёстко и повторяется каждый день. Съём выдачи по 900 ключам, по 10 страниц на ключ, итого 9000 запросов, окно 40 минут между двумя другими задачами в расписании. Медиана ответа 2 секунды, запас 1,25, потому что проверочные окна на такой площадке появляются регулярно и часть запросов уходит в повтор.

Переводим 40 минут в часы: 0,667. Темп: 9000 умножить на 1,25, поделить на 0,667, получаем 16 875 запросов в час. Потоки: 16 875 умножить на 2, поделить на 3600, получаем 9,375, округляем до 10. Фактический темп 18 000 в час, работа закроется за 37 минут с копейками.

Расписание добавляет второй вопрос: пересекаются ли окна. У меня съём идёт четыре раза в сутки, окна разнесены, и пиковая одновременность равна 10. Сдвиньте одно окно на полчаса, окна наложатся, и пик станет 20. Пиковую величину я считаю по худшему совпадению окон, среднее за сутки здесь бесполезно, потому что счётчик сервиса смотрит именно на секунду пика.

Постоянные ежедневные прогоны удобно держать на длинном доступе: расписание живёт месяцами, продлевать доступ каждую неделю утомительно. Условия доступа к пулу на месяц под такую регулярность подходят лучше всего, а суточные пакеты я оставляю под разовые выгрузки.

Сложим три задачи. Три потока плюс тринадцать плюс десять дают 26 занятых слотов из тысячи. Даже если все три прогона случайно совпадут по времени, до потолка обычного пакета остаётся расстояние в сорок раз. Такой запас нужен для дела: он покрывает keep-alive, зависшие сокеты и вторую копию задачи, запущенную кроном поверх первой.

Две привязки и деление лимита пополам

В пакет входит два адреса привязки, менять их в кабинете можно свободно. Механика деления простая: при двух привязанных адресах общий лимит делится между ними пополам. Обычный пакет даёт по 500 потоков на каждую привязку, корпоративный по 1500. Сумма остаётся прежней, меняется то, сколько слотов доступно одной машине.

ПакетПривязано адресовПотоков на привязкуВсего потоков
Обычный110001000
Обычный25001000
Корпоративный130003000
Корпоративный215003000

Практика отсюда выводится в одну строку. Весь прогон идёт с одного сервера, привязку держите одну, тогда машине доступна вся тысяча. Работа разнесена по двум машинам, планируйте каждую под 500 слотов и проверяйте расчёт по худшей из них. Ошибка обычно выглядит так: человек посчитал 700 потоков на общий пакет, привязал два сервера, каждый упёрся в свои 500, и половина воркеров встала в очередь.

Есть и второй способ подключения, без привязки адреса: доступ по логину и паролю. Он удобен, когда исходящий адрес машины меняется или когда прогон запускается с ноутбука в дороге. Строки выдаются в двух форматах, простом IP:PORT и расширенном IP:PORT:LOGIN:PASS, забирать список можно ссылкой или файлом. Обзор адресного парка и форматов подключения лежит на странице купить прокси IPv4.

Почему разные задачи удобнее держать в разных пакетах

Потоки считаются внутри пакета. Пакеты по потокам между собой не складываются, поэтому два обычных пакета не превращаются в две тысячи слотов под одну задачу. Зато пакетов на аккаунте можно держать сколько угодно, и каждый несёт собственный лимит, собственные привязки и собственный срок доступа.

Из этой механики вырастает схема, к которой я пришёл после года возни с общим пакетом на всё: один пакет на одну задачу. У меня их три. Первый под ночной каталог, второй под съём по расписанию, третий под ручную работу в браузере и разовые проверки. Выгода складывается из четырёх вещей.

Изоляция нагрузки. Ночной каталог перестал влиять на утренний съём, потому что физически не может занять его слоты. Раньше зависшие сокеты каталога доживали до девяти утра и отъедали часть одновременности у расписания, а найти причину по логам приложения было тяжело.

Понятный учёт. Когда пакет один на всё, счётчик показывает сумму, и разложить её по задачам можно только по своим меткам. Когда пакетов три, каждый счётчик отвечает за свою работу, и вопрос «кто съел слоты» отпадает сам.

Разные ритмы. Каталог живёт месяцами и просит длинного доступа. Разовая выгрузка под клиента укладывается в сутки. Держать их в одном пакете значит подгонять срок под самую долгую задачу.

Разные привязки. Ночной каталог крутится на арендованном сервере, ручная работа идёт с рабочей машины, адреса у них разные. Отдельные пакеты позволяют привязать по одному адресу к каждому и получить полный лимит там и там, вместо деления пополам внутри одного пакета.

Как замерить фактическое число одновременных соединений

Расчёт даёт ожидание, замер даёт правду. Между ними обычно лежит разрыв в полтора-два раза, и его источник почти всегда в настройках HTTP-клиента, который открывает соединений больше, чем у вас воркеров.

Самый быстрый способ на Linux смотрит на сокеты к порту прокси.

  # сколько соединений к точке входа открыто прямо сейчас
ss -tn state established '( dport = :8000 )' | tail -n +2 | wc -l

  # полка за прогон: снимаем раз в секунду в файл, потом берём максимум
while true; do
  printf '%s %s\n' "$(date +%T)" \
    "$(ss -tn state established '( dport = :8000 )' | tail -n +2 | wc -l)"
  sleep 1
done > conn.log

sort -k2 -n conn.log | tail -1

Максимум из conn.log и есть ваша пиковая одновременность. Я сравниваю его с расчётным P: совпадение в пределах 10 процентов означает, что клиент настроен так, как задумано. Разрыв вдвое означает keep-alive без ограничения пула.

Вторая точка контроля живёт внутри приложения. Ограничитель ставится сразу в двух местах, в семафоре и в пуле соединений клиента, иначе одна половина настройки обходит другую.

import asyncio, httpx

LIMIT = 13
sem = asyncio.Semaphore(LIMIT)
PROXY = "http://LOGIN:PASS@gate.example:8000"

async def fetch(client, url):
    async with sem:
        r = await client.get(url, timeout=httpx.Timeout(15.0, connect=5.0))
        return r.status_code

async def main(urls):
    limits = httpx.Limits(max_connections=LIMIT, max_keepalive_connections=LIMIT)
    async with httpx.AsyncClient(proxy=PROXY, limits=limits) as client:
        return await asyncio.gather(*(fetch(client, u) for u in urls))

Таймаут в примере стоит не для красоты. Без него зависший запрос держит слот десятками минут, семафор его не отпускает, и прогон постепенно сваливается в один рабочий поток при формально занятых тринадцати. Значение подбирается от медианы: я беру пять медиан на полный ответ и пару секунд на установку соединения.

Для Scrapy та же настройка живёт в CONCURRENT_REQUESTS, для requests в размере пула у HTTPAdapter, для curl в параметре --parallel-max. Везде смысл один: верхняя граница одновременных сокетов должна стоять явной цифрой, совпадающей с вашим расчётным P.

Признаки упора в лимит потоков и признаки нагрузки со стороны площадки

Признаки упора на своей стороне и признаки нагрузки со стороны площадки

Картинки у двух ситуаций разные, и различить их можно за пару минут по одному признаку: на каком этапе ломается запрос.

Упор в собственный лимит ломает запрос до отправки. Соединение к точке входа не устанавливается, клиент отдаёт ошибку уровня сокета, в логе видно connection refused или обрыв на этапе CONNECT. Тела ответа нет, кода состояния нет, времени ответа тоже нет, потому что запрос никуда не ушёл. Второй признак свой: число установленных соединений в ss встаёт полкой и перестаёт расти, сколько бы воркеров вы ни добавили сверху. Третий признак свой: время задачи в очереди приложения растёт, при этом время ответа тех запросов, которые прошли, держится ровным.

Нагрузка со стороны площадки ломает запрос после отправки. Соединение установилось, запрос ушёл, ответ пришёл, только содержимое ответа вам не нравится. Коды 429 с заголовком Retry-After, коды 403 после определённого числа обращений, проверочные окна вместо страницы, пустые тела с кодом 200. Четвёртый признак площадки самый коварный: время ответа плавно растёт вместе с темпом, отказов пока нет, а прогон уже отстаёт от графика.

Что видноГде искатьКуда смотреть дальше
Обрыв до отправки запросаЛог сокетов клиентаСвоя сторона: снять потоки до 80 процентов лимита
Полка по ss при росте воркеровЗамер одновременностиСвоя сторона: поднять пакет или разнести прогоны
Очередь растёт, ответ ровныйМетрики приложенияСвоя сторона: проверить семафор и пул клиента
Коды 429 и Retry-AfterЗаголовки ответаПлощадка: растянуть срок при тех же потоках
Рост времени ответа при ровном темпеМедиана по окнам в 5 минутПлощадка: вернуть прежнее число потоков
Пустые тела с кодом 200Проверка разметкиПлощадка: сбавить темп и повторить позже

Разделяющий приём я применяю всегда один и тот же: снимаю потоки вдвое и смотрю пять минут. Отказы ушли и время ответа вернулось к медиане, значит давление шло от площадки, работаем со сроком и с паузами. Картина осталась прежней, значит смотрим свою сторону: пул клиента, таймауты, вторую копию прогона, число привязок в пакете. Половина случаев закрывается на этом шаге за десять минут.

Ещё одна привычка, которая экономит много времени: записывать в лог прогона три цифры каждую минуту. Число активных соединений, медиану времени ответа и долю ответов с кодом 429. Три ряда чисел рядом друг с другом показывают причину быстрее любых догадок, потому что упор в свой лимит держит первую цифру ровной полкой, а давление площадки поднимает вторую и третью при неизменной первой.

Возвращаясь к исходному вопросу «сколько потоков ставить». Ставьте столько, сколько дала формула, плюс округление вверх. Проверяйте цифру замером, ощущение здесь подводит регулярно. Держите разные задачи в разных пакетах, следите за числом привязок, поднимайте темп ступенями. Тысяча слотов обычного пакета покрывает подавляющую часть работы, а корпоративные три тысячи берутся под сжатые окна и параллельные прогоны на несколько машин. Объём выкачанного в эту арифметику вообще не входит: пакеты без счётчика объёма оставляют в расчёте две величины, потоки и срок.

Продолжают тему соседние заметки этого раздела. Как та же арифметика ложится на площадки объявлений, где темп ограничен ещё и правилами самой площадки, разобрано в материале прокси для Авито. Расчёт под регулярный обход карточек и агрегаторов показан в заметке сбор отзывов о бренде, там же про частоту повторных проходов. Как выглядит выданный список доступов и чем простой формат отличается от расширенного, описано в статье формат списка адресов. Из раздела о сборе данных ближе всего ротация адресов: режим смены выхода напрямую влияет на то, как часто клиент открывает новые соединения и сколько слотов при этом занято.