Ограничения и рекомендации
У любой комплексной системы есть некий набор правил, рекомендаций и ограничений, которыми пользователь должен руководствоваться в работе. О том, какие подходы и действия считаются допустимыми, а какие — нежелательными, расскажет эта статья.
Соблюдение определённых в статье правил и ограничений обязательно для всех пользователей системы. Мы оставляем за собой право заблокировать пользователя до устранения выявленных нарушений.
Общие принципы
Предоставляя пользователям системы доступ к API, мы в первую очередь рассчитываем на добросовестность и ответственность в их действиях.
Под добросовестным использованием понимается взаимодействие с системой, при котором действия клиента:
- не создают избыточную или неоправданную нагрузку;
- не ухудшают доступность и стабильность сервиса;
- не влияют негативно на других пользователей системы.
Недобросовестным поведением при этом считаются любые действия, приводящие к деградации производительности, неоправданному увеличению нагрузки на инфраструктуру или нарушению корректной работы системы. При этом не имеет значения, является ли такое поведение намеренным или нет.
Пользователь несёт полную ответственность за действия, совершённые от его имени, в том числе автоматизированными API-клиентами (например, торговыми роботами). В случае блокировки доступа к системе мы можем сообщить о причинах, но путь их устранения остаётся на усмотрение заблокированного пользователя.
Примеры нежелательного поведения
В этом разделе собраны примеры пользовательских сценариев взаимодействия с системой, негативно влияющих на её производительность. Настоятельно рекомендуем проанализировать свой API-клиент на наличие описанных сценариев и исключить их в соответствии с предоставленными рекомендациями.
Представленными ниже примерами нежелательное на наш взгляд поведение демонстрируется, но не ограничивается. Мы оставляем за собой право заблокировать пользователя за негативное влияние на систему даже в случае, если его действия не были ранее описаны в этом разделе.
HTTP
Использование HTTP API для получения потока данных в реальном времени
Описание сценария
При проектировании API-клиента было принято решение получать данные об изменениях на бирже через HTTP API вместо WebSocket. Это решение позволило в моменте упростить разработку клиента, но впоследствии привело к тому, что теперь клиент отправляет от нескольких сотен до нескольких тысяч запросов ежесекундно с целью получить актуальные данные.
Обработка избыточных запросов приводит к созданию неоправданной нагрузки, что увеличивает задержку при выполнении прочих операций и ухудшает общую стабильность системы.
Рекомендации
Использовать HTTP API уместно для:
- первичной загрузки данных;
- получения исторических данных;
- сверки и восстановления;
- других точечных или нечастых запросов.
Для получения высокочастотных изменений в реальном времени мы настоятельно рекомендуем перейти на WebSocket-подписки.
Для снижения нагрузки на систему для HTTP API действует лимит до 100 запросов в секунду.
Если для используемого запроса нет аналогов в WebSocket API, мы считаем обращение к HTTP API добросовестным.
Повторный запрос статичных данных
Описание сценария
Часть данных, доступных через HTTP API, носит справочный характер: списки инструментов и торговых площадок, валюты, справочники статусов и т.д. Такие данные обновляются крайне редко или не обновляются вообще, но API-клиент всё равно запрашивает их повторно в рамках каждого рабочего сценария.
Подобные регулярные запросы неизменных данных занимают ресурсы на выполнение бессмысленной работы, что при высокой интенсивности влечёт за собой общее ухудшение работы системы.
Рекомендации
- Разделяйте статические, редко меняющиеся и оперативные данные.
- Для статических данных используйте локальный кэш с явной политикой инвалидации:
- прогревайте кэш при старте приложения, если справочник нужен для выполнения основной торговой логики;
- задавайте TTL и опирайтесь на версионирование там, где это возможно;
- обновляйте справочники при смене торгового дня или явном уведомлении о необходимости инвалидации.
- Соблюдайте основной принцип: чтение статических данных по умолчанию должно происходить из локального кэша вместо HTTP API.
Неравномерное распределение нагрузки
Описание сценария
Даже умеренное среднее количество запросов от API-клиента может негативно сказываться на работе системы, если эти запросы передаются всем массивом одновременно.
В моменты резких всплесков активности система вынуждена одновременно обрабатывать большое количество запросов, что приводит к увеличению времени ответа и росту вероятности ошибок. При этом периоды низкой активности не компенсируют влияние пиков.
Рекомендации
Распределяйте запросы по времени равномерно и избегайте резких всплесков нагрузки, используя:
- throttling и rate limiting на стороне клиента;
- jitter при повторных запросах и периодических операциях;
- очереди и механизмы планирования отправки;
- event-driven подход и подписки на события вместо периодической массовой проверки изменений.
Запрос больших объёмов данных без фильтрации
Описание сценария
Для получения данных об одном определённом инструменте API-клиент выгружает все доступные на бирже инструменты и отфильтровывает нужные данные локально вместо изначального ограничения диапазона поиска.
Такой подход вынуждает систему отдавать на каждый запрос большие объёмы данных, что в результате не только нагружает сеть и вычислительные мощности системы, но и требует дополнительных ресурсов на стороне клиента для обработки полученных данных.
Рекомендации
- Ограничьте выборку до необходимого объёма с помощью ключа лимита выдачи;
- Используйте настройки пагинации и фильтрации для уточнения нужного диапазона;
- Избегайте запроса "полных" данных без необходимости.
Избыточная синхронизация времени
Описание сценария
Доступ к серверному времени может использоваться для оценки рассинхронизации, однако его частое получение не даёт заметного выигрыша.
Если выполнять такие запросы перед каждой операцией, они начинают формировать отдельный поток служебной нагрузки. При этом само значение времени не предназначено для точной синхронизации с внешними источниками.
Рекомендации
Синхронизация часов должна быть редкой и контролируемой:
- определяйте смещение между клиентским и серверным временем при старте приложения, после длительного простоя, после существенного сетевого сбоя или периодически с невысокой интенсивностью;
- не выполняйте синхронизацию времени перед каждым запросом;
- используйте локально сохраненную поправку на смещение.
Обратите внимание, что возвращаемое серверное время не обладает необходимым уровнем точности, разрешающей способности и синхронизацией с биржевыми часами. По этим причинам использование агрессивной политики синхронизации с данным временем в сценариях высокочастотной торговли является нецелесообразным.
Агрессивные retry-политики
Описание сценария
В API-клиенте не было предусмотрено прерывания выполнения сценария в случае возникновения ошибки, в связи с чем к HTTP API автоматически отправляются многочисленные некорректно составленные запросы без возможности их выполнения. В зависимости от типа ошибки пользователь рано или поздно будет заблокирован подсистемами автоматической защиты, но до этого момента его запросы будут генерировать мусорную нагрузку в системе и сети.
Рекомендации
- Разделяйте ошибки (как минимум) на:
- временные;
- постоянные;
- ошибки валидации;
- ошибки аутентификации/авторизации;
- неопределенные.
- Предусмотрите механизм автоматического прерывания сценария в случае множественных отказов в выполнении;
- Делайте retry только в случаях, где он действительно необходим и допустим, применяя механики backoff и jitter.
Дублирование запросов
Описание сценария
Из-за особенностей реализации API-клиент отправляет несколько одинаковых запросов с одним и тем же диапазоном поиска одновременно. С точки зрения системы такие запросы не отличаются друг от друга как основные и дополнительные, в связи с чем одна и та же операция выполняется несколько раз, занимая ресурсы системы на бесполезную работу.
Рекомендации
- Кэшируйте данные, используемые в нескольких сценариях одновременно;
- Используйте в первую очередь данные из локального кэша.
Частая генерация токенов доступа
Описание сценария
Для авторизации запросов к системе используются токены доступа, время жизни которых ограничено 30 минутами с момента создания. Для непрерывной работы с системой требуется регулярно обновлять токены, но слишком частые запросы на получение нового токена создают избыточную нагрузку на систему.
Рекомендации
- Предусмотрите механизм локального хранения токена с его переиспользованием во всех запросах;
- Обновляйте токен независимо от выполнения прочих операций;
- Избегайте избыточных обращений за новыми токенами.
Оптимальная частота обновления токена — раз в 20-25 минут с момента получения предыдущего.
WebSocket
Использование одного соединения для всех подписок
Описание сценария
Ввиду особенностей архитектуры API-клиента все подписки концентрируются в одном WebSocket-соединении вместо распределения по нескольким отдельным. Такое соединение становится бутылочным горлышком клиент-серверного взаимодействия: растет объем трафика, увеличивается нагрузка на парсинг, сериализацию и внутренние очереди. Увеличивается общая очередь сообщений и растёт задержка их доставки. Кроме того, потеря такого соединения означает потерю сразу большого числа подписок и усложняет их восстановление.
Рекомендации
- Логически распределяйте подписки по разным соединениям (например, по типам данных);
- Отделяйте высокочастотные потоки от потоков с редкими, но критичными событиями;
- Разделяйте большое количество однотипных высокочастотных подписок на несколько соединений.
Рекомендуем ограничить количество подписок до 5000 штук на одно соединение.
Отсутствие локального буфера сообщений
Описание сценария
Частный случай предыдущего сценария. Со стороны системы действует ограничение на количество необработанных сообщений в буфере сервера — до 5000 записей на одно соединение. При превышении этого объёма система принудительно прервёт проблемное WebSocket-соединение с ошибкой Too many (>5009) messages in server buffer, closing WebSocket.. Если в API-клиенте не предусмотрен локальный буфер для первичного приёма данных, активное использование подписок может привести к регулярному разрыву WebSocket-соединений, что в свою очередь ведёт к потере части данных и возникновению проблемы агрессивной политики переподключений.
Рекомендации
- Вместо последовательности "Принять сообщение и сразу обработать" следуйте подходу "Принять, сохранить, обработать" за счёт локального буфера данных;
- Уменьшите общий объём получаемых сообщений отменой части подписок;
- Разделите обработку получаемых сообщений в несколько потоков.
Разрыв WebSocket-соединения из-за переполнения буфера входит в число отслеживаемых подсистемами автоматизированной защиты событий. Частые разрывы могут привести к блокировке пользователя.
Избыточное количество соединений
Описание сценария
Обратная сторона сценариев выше, при которой API-клиент создаёт новое WebSocket-соединение на каждую новую подписку, чем нагружает и сеть, и систему.
Рекомендации
- Принудительно ограничьте для API-клиента максимальное количество открытых WebSocket-соединений;
- Логически группируйте подписки в нескольких соединениях.
Рекомендуемое количество одновременно активных WebSocket-соединений — не более 10 штук.
Отсутствие контроля жизненного цикла подписок
Описание сценария
Из-за отсутствия механизмов контроля жизненного цикла подписок API-клиент может создавать дубликаты уже существующих подписок, не отписываться от утративших актуальность потоков и терять соответствие между подпиской и локальным потребителем.
Такое поведение создает избыточную нагрузку на систему, приводит к дублированию сообщений и недетерминированному поведению торговой логики стороне клиента.
Рекомендации
- Каждая подписка должна быть легко идентифицируемой для API-клиента (например, за счёт параметра
guid); - У подписок должен быть явный и контролируемый жизненный цикл, например:
- создание;
- использование;
- отмена при утрате необходимости;
- опциональное восстановление после сетевых сбоев.
Высокая интенсивность перестроения подписок
Описание сценария
Частое создание и отмена одинаковых подписок в рамках одного или нескольких соединений, равно как частая смена наборов короткоживущих подписок, создаёт избыточную нагрузку на клиент и сервер, увеличивает число служебных сообщений и усложняет управление жизненным циклом подписок. Возникает гонка между командами подписки, отписки и фактической доставкой данных. В переходных состояниях повышается риск потери событий, дублирования потоков и неверной интерпретации того, какие подписки действительно активны.
Рекомендации
- Набор подписок должен меняться осознанно:
- определите списки
- избегайте интенсивных subscribe/unsubscribe операций для одной и той же подписки;
- избегайте частого перестроения набора подписок из-за короткоживущих локальных условий.
- Если некоторые подписки используются для получения одного сообщения раз в n минут, рассмотрите возможность перевода таких сценариев на HTTP API.
Агрессивные переподключения
Описание сценария
При разрыве соединения API-клиент пытается немедленно восстановить его без анализа причин сбоя. Такое поведение не только нагружает систему резким всплеском активности, но и может привести к автоматической блокировке пользователя, если разрыв произошёл из-за переполнения буфера необработанных сообщений (см. Отсутствие локального буфера сообщений выше).
Рекомендации
- Используйте ограниченный цикл переподключения с механизмами backoff и jitter;
- Избегайте одновременного восстановления соединений с одинаковыми настройками backoff и jitter;
- После восстановления соединения избегайте «шторма» подписок, распределяйте процесс восстановление подписок по времени.
GraphQL
На текущий момент для GraphQL нет определённых ограничений или рекомендаций по использованию. Для успешного взаимодействия с ним достаточно инструкции и здравого смысла.
Общие паттерны
Выбор единственного канала для всех задач
Описание сценария
При проектировании API-клиента было решено реализовать все сценарии взаимодействия с помощью какого-то одного интерфейса системы — HTTP или WebSocket. Такое решение упрощает разработку в моменте, но при последующей эксплуатации приводит либо к задержкам в получении данных, либо к потере надежного механизма восстановления.
Рекомендации
Используйте доступные интерфейсы системы для соответствующих их назначению задач:
- HTTP API для отправки команд, получения снапшотов, истории и сверки данных;
- WebSocket API для получения потока изменений и уведомлений в реальном времени;
- GraphQL API для получения справочников.
Условия блокировки
Если поведение пользователя было выявлено как нежелательное, для него может быть заблокирован доступ к системе. При этом, в отличии от автоматизированной защиты такая блокировка накладывается вручную и не имеет конечного срока действия.
Если вы были заблокированы и в ответах от системы нет типовых для автоматизированной защиты сообщений — свяжитесь со службой технической поддержки любым удобным способом:
- 📧 Электронная почта support@alor.ru
- 🗃 Личный кабинет
- ☎ Горячая линия: 8 800 775-11-99, +7 495 980-24-98
Краткая сводка
| Допустимые сценарии | Нежелательные сценарии |
|---|---|
| Использовать HTTP для команд и состояния, WebSocket — для потока real-time данных | Использовать HTTP как замену потоковым данным или игнорировать один из каналов |
| Равномерно распределять нагрузку и соблюдать лимиты (до 100 запросов/с для HTTP) | Создавать взрывную нагрузку большими группами запросов, высокую частоту опросов и избыточные запросы |
| Кэшировать статические данные и запрашивать только необходимый объём | Повторно запрашивать неизменяемые данные и получать избыточные выборки |
| Осознанно обрабатывать ошибки и выполнять ограниченные retry | Безусловно повторять любые неуспешные запросы |
| Переиспользовать токены и управлять аутентификацией | Генерировать новый токен под каждый запрос |
| Контролировать WebSocket-соединения и подписки (до 5000 на соединение, до 10 соединений) | Перегружать соединения, создавать дубли подписок и лишние соединения |
| Использовать локальный буфер для первичного хранения сообщений | Пытаться обработать поступающие сообщения сразу |
| Поддерживать стабильный набор подписок и соединений | Часто пересоздавать подписки и соединения без необходимости |
| Использовать контролируемые reconnect-политики | Выполнять агрессивные переподключения |
| Реализовывать восстановление состояния после сбоев | Игнорировать рассинхронизацию и не восстанавливать состояние |
| Проверять корректность данных и исключать дублирование запросов | Отправлять некорректные, дублирующие или бессмысленные запросы |
Фигурирующие в статье количественные ограничения обязательны для соблюдения всеми пользователями:
- До 100 HTTP-запросов в секунду;
- До 10 WebSocket-соединений;
- До 5000 подписок на одно WebSocket-соединение;
- До 5000 необработанных сообщений в буфере соединения.