Дедупликация неидемпотентных запросов¶
Идемпотентные запросы – это операции, повторный вызов которых дает тот же результат, что и при первом применении. Например, идемпотентными являются запрос на чтение данных или операция умножения на единицу. Соответственно, примером неидемпотентной операции можно назвать увеличение на единицу. При повторном применении такой операции значение для поля будет суммарно увеличено уже не на 1, а на 2.
Примечание
Любые запросы на запись, которые планируется выполнять неоднократно (например, повторный вызов запроса после ошибки), должны быть идемпотентны. Идемпотентность таких операций гарантирует, что изменение из операции будет применено только один раз.
Повторный вызов запросов¶
Повторное выполнение запроса может понадобиться в том случае, если возникла ошибка со стороны сервера или клиента. При этом:
Запросы на чтение данных можно выполнять повторно. Для этого в модуле
vshard
у метода vshard.router.call() в режимеread
(mode=read
) используется параметрrequest_timeout
(доступно с версииvshard
0.1.28). Параметрыrequest_timeout
иtimeout
нужно передавать вместе, соблюдая следующее условие:timeout > request_timeout
Например, если
timeout = 10
, аrequest_timeout = 2
, то в течение 10 секунд роутер сможет сделать до 5 попыток обращения (по 2 секунды на каждую) с запросом на разные реплики, пока запрос наконец не преуспеет.Запросы на запись данных (vshard.router.callrw()) в общем случае нельзя выполнять повторно без проверки на то, что запрос не был применен раньше. Отсутствие такой проверки может привести к дубликатам записей или незапланированному изменению данных.
Например, клиент отправил запрос на сервер и ожидает ответа в течение заданного времени. Если сервер отправит ответ об успешном выполнении уже после истечения этого времени, клиент не увидит этот ответ из-за таймаута и будет считать запрос неудавшимся. При повторной отправке запроса без дополнительной проверки операция может быть применена дважды. Выполнять запрос на запись повторно без проверки можно только в двух случаях:
запрос является идемпотентным;
точно известно, что предыдущий запрос завершился ошибкой до выполнения какой-либо операции записи. Например, сервер выдал ошибку
ER_READONLY
. В этом случае понятно, что запрос не удалось выполнить из-за того, что сервер работает в режиме только для чтения.
Способы дедупликации запросов¶
Чтобы гарантировать идемпотентность запросов на запись, таких как вставка и обновление данных, а также автоинкремент, необходимо внедрить в код проверку, что запрос применяется впервые.
Примечание
В Tarantool DB отсутствует встроенная дедупликация запросов. На текущий момент проверка на дедупликацию может быть добавлена пользователем самостоятельно в коде приложения.
Например, при добавлении нового кортежа в спейс для проверки можно использовать уникальный ключ, по которому производится вставка. В таком запросе в рамках одной транзакции:
Проверяется, есть ли в спейсе
bands
кортеж с ключомkey
.Если записи с таким ключом в спейсе нет, выполняется вставка кортежа.
box.begin()
if box.space.bands:get{key} == nil then
box.space.bands:insert{key, value}
end
box.commit()
Для запросов на обновление кортежа можно создать отдельный спейс дедупликации, в который будут сохраняться ID запросов.
Спейс дедупликации – это пользовательский спейс, который содержит список уникальных идентификаторов.
Каждый такой идентификатор соответствует одному выполненному запросу.
Этот спейс может иметь любое имя, в примере он называется deduplication
.
В примере ниже в рамках одной транзакции:
В спейсе
deduplication
проверяется наличие ID запросаdeduplication_key
.Если такого ID нет, этот ID добавляется в спейс дедупликации.
После запрос увеличивает заданное поле в спейсе
bands
на единицу.
Такой подход гарантирует, что каждый запрос на изменение данных будет выполнен только один раз.
function update_1(deduplication_key, key)
box.begin()
if box.space.deduplication:get{deduplication_key} == nil then
box.space.deduplication:insert{deduplication_key}
box.space.bands:update(key, {{'+', 'value', 1 }})
end
box.commit()
end