Site icon AppTractor

Push-уведомление — не подтверждение: как построить надёжный поток событий в приложении

Пользователь оформил заказ, экран показал успех, но уведомление пришло дважды. Другой пользователь не получил push вообще, хотя заказ существует. Эти случаи нельзя объяснять только качеством мобильной сети: серверная часть должна различать бизнес-событие, попытку отправки, ответ push-провайдера и действие самого пользователя.

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

Платформа для API и фоновых обработчиков

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

Для локального размещения можно оценить комплектации HPE ProLiant DL380 Gen10 у Сервер Молл: https://servermall.ru/sets/server-hpe-proliant-dl380-gen10/. Выбор процессоров, памяти и накопителей опирается на измеренную нагрузку API и очереди. Покупка сервера не отменяет правил и ограничений внешних push-сервисов.

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

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

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

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

Ответ провайдера сохраняют отдельно от состояния заказа. «Принято к обработке» не означает «пользователь прочитал». Для пользовательского сценария нужны собственные признаки: приложение запросило новое состояние, открыло сообщение или подтвердило действие. Не следует объявлять успешной доставкой все ответы без технической ошибки.

Это различие подтверждает документация Firebase Cloud Messaging Set the lifespan of a message: получение идентификатора сообщения означает принятие для доставки, а не уже состоявшуюся доставку устройству. Поэтому отчёт команды должен отдельно показывать техническое принятие и доступные признаки получения, не превращая один показатель в другой.

Повторная обработка должна быть безопасной

У события есть постоянный идентификатор. Повторная попытка отправки не создаёт новую бизнес-операцию. Если уведомление предлагает подтвердить заявку, API подтверждения также должен безопасно обрабатывать повторный запрос: двойное нажатие не должно дважды списывать ресурс или менять статус по неправильной цепочке.

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

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

Расчёт очереди на массовую отправку

Рассмотрим условную рассылку 60 тысяч уведомлений. При устойчивой скорости 200 сообщений в секунду чистое время обработки составит 300 секунд, то есть пять минут. Это арифметическая оценка без задержек провайдера, повторов, ограничений и конкурирующей нагрузки.

Если в ту же очередь каждую секунду поступает ещё 50 новых событий, на старую рассылку остаётся 150 сообщений в секунду. Те же 60 тысяч будут обработаны примерно за 400 секунд. При входящем потоке, равном пропускной способности, накопленная очередь не уменьшится.

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

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

Что делать с повторными попытками

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

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

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

Какие данные не стоит передавать в push

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

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

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

Набор испытаний перед релизом

Критерии приёмки формулируют на уровне продукта: заказ не исчезает, действие не дублируется, устаревшее напоминание не отправляется, важные события укладываются в согласованное время. Скорость обработчика — только одна из метрик. Надёжность потока определяется тем, сохраняется ли правильное состояние при сбоях и повторах.

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

Exit mobile version