Гарантия отправки "ровно один раз"
June 2, 2023
В Проекте Э, мы интегрируемся с внешней системой, отправляя туда часть записей дневника пользователя по протоколу MQTT. И коллеги попросили делать это ровно один раз. Что невозможно в нашей вселенной.
Я написал для них обоснование невозможности выполнить их просьбу и решил это оформить в микропост в блог.
Строго говоря обеспечить обеспечить гарантию доставки “ровно один раз” невозможно. Доказать невозможность обеспечения этой гарантии проще всего через приведение её к задаче двух генералов, неразрешимость которой формально доказана в статье Лесли Лэмпорта (корифея теории распределённых систем, лауреата премии Тюринга) “Solved Problems, Unsolved Problems and Problems in Concurrency”, опубликованной в журнале Association for Computing Machinery - одном из старейших и уважаемых научных сообществ в области информатики.
Если упрощенно и переводя на наши реалии, мы не в состоянии выяснить причину отсутствия подтверждения получения события - проблемы с передачей сообщения (в случае чего отправку надо повторить) или задержка в обработке или проблемы с передачей подтверждения (в случае чего отправку повторять не надо).
Подробно и на русском доказательство описано в этой статье.
Однако и сам MQTT предлагает гарантию доставки ровно один раз и в сети можно найти статьи, описывающие такую гарантию (1, 2, 3, 4).
Но если вчитаться в эти статьи, то они все опираются на то, что в случае сбоя отправка выполняется множество раз, но получатель выполняет дедупликацию на своей стороне.
И даже если предположить, что MQTT может гарантировать (хотя он гарантирует это только в случае сетевых сбоев, но не в случае сбоев постоянного хранилища клиента или сервера), что сообщение уровня протокола будет доставлено один раз, мы со своей стороны не можем гарантировать отправку ровно одного MQTT-сообщения.
Проблема в том, для того чтобы гарантировать отправку всех событий, нам необходимо повторять отправку до тех пор, пока мы у себя в БД не пометим сообщение как отправленное. Однако в распределенной системе может случиться такая последовательность:
- Сервер отправляет сообщение;
- MQTT выполняет успешную доставку и сообщает об этом серверу;
- Сервер пытается отметить сообщение отправленным в БД;
- Происходит сбой БД;
- После восстановления работы БД, сервер не знает по какой причине помечено осталось не отправленным - сбой в доставке или сбой в пометке отправленным.
Обычно дедупликацию делают на стороне получателя, но если получатель предоставит нам АПИ для проверки наличия у них события по идентификатору, мы сможем при повторной отправке выполнить дедупликацию на своей стороне.