Веб-службы часто ограничивают скорость запросов к своим API. Давайте поговорим о нескольких случаях использования ограничений скорости API.

Облачные провайдеры, такие как AWS (Amazon Web Services) и GCP (Google Cloud Platform), взимают плату с приложений за каждый запрос API и применяют ограничения на основе своих планов обслуживания.

При создании веб-службы, в зависимости от того, какие типы ресурсов вы обслуживаете, вы можете убедиться, что ваш API обеспечивает справедливый доступ к вашим клиентам.

С точки зрения безопасности ограничение скорости вашего сервиса гарантирует, что ни один клиент не сможет спамить ваши конечные точки и монополизировать доступ к вашим ресурсам. Логика ограничения скорости может отслеживать ваш IP-адрес, и когда частота ваших запросов превышает пороговое значение, последующие запросы отклоняются в течение некоторого периода времени.

Злоумышленники могут попытаться провести атаку типа отказ в обслуживании на ваши серверы. Для защиты от одного злоумышленника мы можем использовать стратегию отслеживания IP-адресов. Чтобы обойти это, злоумышленники могут организовать распределенную группу машин для рассылки спама на ваши серверы, что называется распределенной атакой типа отказ в обслуживании или сокращенно DDoS. Вместо того, чтобы строить собственную защиту, вы можете воспользоваться сервисом вроде Cloudflare.

Недавно я столкнулся с проблемой, когда моя программа достигла предела скорости в общедоступном API SEC EDGAR filings. Вот уведомление от https://www.sec.gov/developer:

Честный доступ

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

Чтобы обеспечить доступность SEC.gov для всех пользователей, мы оставляем за собой право блокировать IP-адреса, отправляющие чрезмерные запросы. См. веб-сайт SEC Политика конфиденциальности и безопасности.

Вот функция, которую я написал:

Я беру все заявки из своего контекста и фильтрую список, выбирая только заявки между интересующими меня датами. Затем в цикле я извлекаю детали и сохраняю их.

Первоначально мой модуль Sec не имел состояния, а fetch_filing_detail не имел логики ограничения скорости, поэтому серверы SEC быстро начали отклонять мои запросы.

Я решил сделать свой модуль Sec сервером генерации с отслеживанием состояния, ответственным за ограничение скорости запросов к SEC API.

Я решил, что синхронного сервера будет достаточно, потому что этот модуль используется только внутренними процессами и не получает большого количества трафика. Я бы полагался на почтовый ящик сервера gen для постановки запросов в очередь и на синхронный обработчик для оказания обратного давления на клиентов.

Вот как выглядит модуль Sec:

Единственное состояние, которое мне нужно было отслеживать, — это время последнего запроса. Я использую часовой пояс UTC и инициализирую состояние сервера текущим временем. Я определяю ограничение скорости 100 мс, минимальное количество времени, которое я должен ждать между запросами.

Я предоставляю клиентам API для вызова серверного процесса. Это стандартная практика для генерирующих серверов.

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

Вот как я разобрался с проблемой ограничения скорости. Используя текущее время для расчета 100 мс назад, я знаю, что время последнего запроса может находиться только в трех состояниях по сравнению с ним (меньше, равно и больше). Только когда время последнего запроса превышает пороговое значение, мне нужно отложить выполнение запроса.

Вот как выглядит реализация моей функции rate_limit:

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

Удачи и удачного кодирования!