Лимиты и валидация приёма
Все лимиты приёма событий ActionPulse в одной таблице и что именно происходит при превышении каждого: отказ запроса, отказ элемента или обрезка значения.
На этой странице
Лимитов у приёма немного, но реагирует он на них тремя разными способами: отказывает всему запросу, отказывает отдельному элементу пакета или молча обрезает значение. Разница принципиальная: в первом случае вы теряете весь пакет, во втором — одно событие, в третьем — часть значения, о которой вам никто не сообщит.
Таблица лимитов
| Что ограничено | Значение | Что происходит при превышении | Код |
|---|---|---|---|
| Элементов в пакете | до 500 |
отказ всего запроса | 422 validation_failed |
| Размер тела запроса | до 256 КБ |
отказ всего запроса | 413 validation_failed |
| Количество свойств | до 64 |
отказ элемента | too_many_props |
| Длина имени свойства | до 64 байт |
отказ элемента | prop_key_too_long |
| Размер значения свойства | до 8 КБ |
отказ элемента | prop_value_too_large |
Длина user_id и anon_id |
до 256 байт |
отказ элемента | invalid_actor |
Длина session_id |
до 256 байт |
обрезается | нет |
Длина url и referrer |
до 2048 байт |
обрезается | нет |
| Окно времени события | ±48 ч |
заменяется временем приёма | нет |
| Имя события | ^[a-z0-9_.]{1,128}$ |
отказ элемента | invalid_event |
Все размеры считаются в байтах, а не в символах. Русская буква в UTF-8 занимает
два байта, эмодзи — четыре, поэтому имя свойства из русских слов упирается в
лимит вдвое быстрее. Обрезка тоже байтовая: очень длинный url может быть
разрезан посередине символа.
Отказ всего запроса
Два лимита закрывают запрос целиком, до разбора отдельных событий:
{
"error": {
"code": "validation_failed",
"message": "batch exceeds 500 items",
"details": { "max": 500 }
}
}
Сюда же относится пустой массив batch — тоже 422, сообщение
batch is empty. И некорректный JSON, в том числе неизвестное поле в структуре
запроса: разбор строгий.
Отказ отдельного элемента
Остальные проверки — поэлементные. Ответ остаётся 202, а
отказы приходят списком:
{
"accepted": 2,
"n": 3,
"rejected": [{ "i": 1, "code": "prop_value_too_large" }]
}
i — индекс элемента в том пакете, который вы отправили. n — сколько
элементов вы прислали, accepted — сколько принято.
Полный список кодов отказа элемента:
| Код | Причина |
|---|---|
invalid_event |
имя не подходит под шаблон или зарезервировано платформой |
invalid_event_id |
event_id не разбирается как UUID |
missing_actor |
нет ни user_id, ни anon_id |
invalid_actor |
идентификатор длиннее лимита или содержит нулевой байт |
too_many_props |
свойств больше 64 |
prop_key_too_long |
имя свойства длиннее 64 байт |
prop_value_too_large |
значение свойства больше 8 КБ |
invalid_schema_version |
schema_version отрицательный |
schema_violation |
событие нарушает схему из реестра в строгом режиме |
credential_event_forbidden |
ключ не имеет права на это имя события |
feature_locked |
событие диагностики без соответствующего доступа |
Обрезка и подмена
Три случая не дают ни отказа, ни признака в ответе:
session_idдлиннее лимита — обрезается;urlиreferrerдлиннее лимита — обрезаются;timeвне окна ±48ч, отсутствует или не разбирается — заменяется временем приёма.
Если вы обнаружили, что все события лежат в одном моменте времени, дело почти
всегда в третьем пункте: метка time не разобралась. Проверьте, что в строке
есть зона (Z или смещение).
Как разбивать большие пакеты
Два лимита действуют одновременно, и упереться можно в любой:
- не больше
500элементов; - не больше
256 КБв теле запроса.
Отсюда рабочее правило: считайте оба размера перед отправкой и режьте по тому, который наступит раньше. Событие со свойствами весит по-разному, поэтому фиксированный размер пакета в элементах не гарантирует, что вы уложитесь в размер тела.
const MAX_ITEMS = 500;
const MAX_BYTES = 262144;
function* batches(events) {
let chunk = [];
let bytes = 2; // квадратные скобки массива
for (const event of events) {
const size = Buffer.byteLength(JSON.stringify(event)) + 1; // плюс запятая
if (chunk.length > 0 && (chunk.length === MAX_ITEMS || bytes + size > MAX_BYTES)) {
yield chunk;
chunk = [];
bytes = 2;
}
chunk.push(event);
bytes += size;
}
if (chunk.length > 0) yield chunk;
}
Если одно событие в одиночку не влезает в тело запроса, дробить пакет бессмысленно — уменьшайте свойства. Обычная причина — попытка положить в свойство целиком JSON ответа стороннего сервиса или текст ошибки со стеком.
Ограничение частоты
Бюджет частоты считается на ключ: все запросы с одним и тем же ключом делят один бюджет в секунду, независимо от того, сколько у вас серверов. Точное значение бюджета задаёт конфигурация установки.
Превышение — 429 с кодом rate_limited и заголовком Retry-After в
секундах:
{ "error": { "code": "rate_limited", "message": "ingest rate limit exceeded" } }
Что с этим делать:
- Уважайте
Retry-After: не повторяйте раньше, чем указано. - Повторяйте с экспоненциальной задержкой, а не в цикле.
- Отправляйте пакетами: один запрос на 200 событий стоит дешевле, чем 200 запросов.
- Не заводите дополнительные ключи ради обхода бюджета. Каждый ключ — это отдельный объект, который придётся хранить, ротировать и отзывать, а разложенный по ключам поток труднее и разбирать, и останавливать.
Квоты тарифа
Лимиты выше — про форму запроса. Квота — про объём за месяц.
Что происходит по мере расходования:
| Состояние | Приём событий | Ответ |
|---|---|---|
| Объём исчерпан на 100% | продолжается | 202, в кабинете предупреждение |
| Превышение достигло 120% | остановлен | 429 quota_exceeded |
| Пробный период исчерпал объём | остановлен | 429 trial_quota_exceeded |
| Пробный период закончился без оплаты | остановлен | 402 trial_expired |
| Нет действующего тарифа | остановлен | 402 subscription_required |
| Аккаунт на проверке | остановлен | 403 trial_pending_review |
Запас между 100% и 120% сделан осознанно: всплеск трафика не должен обрывать сбор данных в ту же минуту. Но это запас, а не второй лимит — за ним приём останавливается.
Ответы 402 несут в details.billing_url адрес страницы оплаты в кабинете:
{
"error": {
"code": "subscription_required",
"message": "доступ приостановлен — выберите тариф, чтобы продолжить",
"details": { "billing_url": "https://…/billing" }
}
}
Блокировка по объёму снимается сама в начале следующего расчётного периода, а
при переходе на более высокий тариф — раньше, после пересчёта. В пробном
периоде объём общий на весь период (30 млн событий за 14 дней), а не
месячный.
Отдельный лимит у операций идентификации
У запросов связывания идентификаторов тело меньше, чем у пакета событий:
64 КБ. Превышение — 413 с сообщением про размер. Это не место для
выгрузки профиля: передавайте только те атрибуты, которые действительно нужны в
аналитике.
Что проверить
- Логируйте
rejectedиз каждого ответа. Пустой массив — норма; непустой должен попадать в ваш мониторинг с кодом и индексом. - Сравните на одном дне количество отправленных событий и количество событий в
проекте. Расхождение при кодах ответа
202означает поэлементные отказы. - Проверьте, что клиент повторяет только
429и5xx. Ответы401,402,403,413и422окончательные: повтор их не исправит. - Убедитесь, что размер пакета считается в байтах, а не только в элементах.
- Посмотрите расход объёма в разделе оплаты кабинета до того, как он упрётся в лимит.
Разбор конкретного кода ответа — в статье про коды ошибок приёма.