К содержанию
ActionPulse

Лимиты и валидация приёма

Все лимиты приёма событий 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" } }

Что с этим делать:

  1. Уважайте Retry-After: не повторяйте раньше, чем указано.
  2. Повторяйте с экспоненциальной задержкой, а не в цикле.
  3. Отправляйте пакетами: один запрос на 200 событий стоит дешевле, чем 200 запросов.
  4. Не заводите дополнительные ключи ради обхода бюджета. Каждый ключ — это отдельный объект, который придётся хранить, ротировать и отзывать, а разложенный по ключам поток труднее и разбирать, и останавливать.

Квоты тарифа

Лимиты выше — про форму запроса. Квота — про объём за месяц.

Что происходит по мере расходования:

Состояние Приём событий Ответ
Объём исчерпан на 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 с сообщением про размер. Это не место для выгрузки профиля: передавайте только те атрибуты, которые действительно нужны в аналитике.

Что проверить

  1. Логируйте rejected из каждого ответа. Пустой массив — норма; непустой должен попадать в ваш мониторинг с кодом и индексом.
  2. Сравните на одном дне количество отправленных событий и количество событий в проекте. Расхождение при кодах ответа 202 означает поэлементные отказы.
  3. Проверьте, что клиент повторяет только 429 и 5xx. Ответы 401, 402, 403, 413 и 422 окончательные: повтор их не исправит.
  4. Убедитесь, что размер пакета считается в байтах, а не только в элементах.
  5. Посмотрите расход объёма в разделе оплаты кабинета до того, как он упрётся в лимит.

Разбор конкретного кода ответа — в статье про коды ошибок приёма.