База данных, используемая для хранения ключей идемпотентности, может быть реализована, например, как:
- Отдельная таблица или коллекция в транзакционной базе данных, используемой для данных приложения;
- Специальная база данных, обеспечивающая очень низкую задержку при поиске, например, простое хранилище ключ-значение.
В отличие от данных приложения, ключи идемпотентности не нужно хранить вечно. Как только клиент получает подтверждение об успехе отдельной операции, ключ идемпотентности может быть выброшен. Самый простой способ добиться этого — автоматически удалять ключи идемпотентности из хранилища по истечении определенного периода времени, например 60 минут или 24 часа, в зависимости от потребностей приложения и объёма запросов.
Кроме того, реализация идемпотентного API должна обеспечить изменение состояния приложения и сохранение ключа идемпотентности. Для успеха должно произойти и то, и другое. Если состояние приложения изменено, а ключ идемпотентности по какой-то причине не сохранён, то повторная попытка приведет к тому, что операция будет применена дважды. Если ключ идемпотентности сохранён, но по какой-то причине состояние приложения не изменено, то операция не была применена. Если повторная попытка поступит, она будет отфильтрована как дублирующая, поскольку ключ идемпотентности уже существует, и обновление будет потеряно.
Здесь подразумевается, что обновления состояния приложения и хранилища ключей идемпотентности должны происходить одновременно, либо не происходить ни одного из них. Если вы знаете свои базы данных, вы поймёте, что это требование транзакционной семантики. О том, как осуществляются распределённые транзакции, мы поговорим в Главе 12. По сути, транзакции обеспечивают семантику "ровно один раз"
(exactly-once) для операций, что гарантирует, что все сообщения всегда будут обработаны ровно один раз — именно то, что нам нужно для идемпотентности.
Ровно один раз — это не значит, что не будет сбоев в передаче сообщений, повторных попыток и сбоев в работе приложений. Все они неизбежны. Главное, что повторные попытки в конечном итоге заканчиваются успехом и результат всегда один и тот же.
Мы вернёмся к вопросу о гарантиях доставки сообщений в последующих главах. Как показано на Рисунке 3-7, существует целый спектр семантик, каждая из которых имеет свои гарантии и характеристики производительности. Доставка по принципу “максимум один раз”
(at-most-once) — быстрая и ненадёжная — это то, что обеспечивает протокол UDP. Доставка "хотя бы один раз"
(at-least-once) — это гарантия, предоставляемая TCP/IP, что означает неизбежность дублирования. Доставка "ровно один раз", как мы уже обсуждали, требует защиты от дубликатов и, следовательно, является компромиссом между надёжностью и низкой производительностью.