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

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

Сначала определите единицу ограничения

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

У разных платформ определения отличаются. Например, Braze описывает частотные правила по каналам и тегам и собственный порядок их применения. Перенос числа «три» из одной платформы в другую без переноса смысла счётчика не сохраняет политику.[1]

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

Объедините проверку и резервирование

Резервирование — временное занятие одного доступного места перед отправкой. Проверить остаток и создать резерв нужно как одну неделимую операцию. Тогда второй сценарий увидит уже занятое место.

В базах данных такое поведение обеспечивают выбранной схемой блокировок, условным обновлением или подходящим уровнем изоляции. Документация PostgreSQL показывает, почему отдельные чтения при обычной изоляции не заменяют управление конкурентными изменениями; сериализуемые операции могут требовать повтора после конфликта.[2]

Это инженерная задача, а не настройка дополнительной задержки на пять секунд. Задержка уменьшает вероятность совпадения, но не доказывает соблюдение лимита.

Опишите жизненный цикл места

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

СобытиеЧто сделать с лимитомПочему
Резерв создан, отправка ещё не началасьСчитать место занятымДругой процесс не должен использовать его
Провайдер однозначно отказал до приёмаОсвободить по согласованному правилуПодтверждённой попытки не произошло
Провайдер подтвердил приёмЗафиксировать расходОтправка уже вышла из локального контроля
Ответ потерян после запросаСохранить неопределённое состояние и сверитьСообщение могло быть принято
Процесс умер до обращения к провайдеруОсвободить после подтверждения безопасного истеченияНельзя навсегда терять место

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

Разделите приоритет и соблюдение лимита

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

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

Коммуникационная политика должна объяснять и судьбу проигравшего сценария. Его можно отменить или пересмотреть позже, но нельзя бесконечно переносить устаревшее предложение до первого свободного места.

Условный пример: один слот, два кандидата

Лимит — одно рекламное сообщение за скользящие 24 часа. В 14:00 два сценария запрашивают место. Сервис принимает заявку корзины и отклоняет снижение цены. Затем заказ уже оформлен, и последняя проверка останавливает корзину до передачи провайдеру.

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

Какие испытания обязательны

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

Отдельно проверьте границу окна, смену даты, объединение профилей и два канала. Условия до отправки должны работать вместе с лимитом, а не обходить его отдельной кнопкой массового запуска.

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

Источники

[1] Braze. Rate limiting and frequency capping. Messaging Fundamentals, документация Braze. Проверено 22.09.2026.

[2] PostgreSQL Global Development Group. Transaction Isolation. PostgreSQL Documentation, раздел 13.2. Проверено 22.09.2026.