Динамо предлагает только атомарные операции на уровне элемента, но не на уровне транзакции, но вы можете иметь нечто похожее на атомарную транзакцию, применяя некоторые правила в вашем приложении.
Допустим, вам нужно выполнить транзакцию с двумя операциями:
- Удалить
Business(id=123)
из таблицы.
- Обновление
Home(id=456)
для удаления связи с Business(id=123)
из массива home.businesses
.
Вот что вы можете сделать, чтобы имитировать транзакцию :
Создание отметки времени для блокировки элементов
Допустим, наша текущая временная метка 1234567890
. Использование метки времени позволит вам очистить неудачные транзакции (я объясню позже).
Блокировка двух предметов
Обновите Business-123
и Home-456
и установите атрибут lock=1234567890
.
Не изменяйте никакие другие атрибуты в этой операции обновления!
Используйте ConditionalExpression
(см. Руководство разработчика и API ), чтобы проверить, что attribute_not_exists(lock)
перед обновлением. Таким образом, вы уверены, что нет другого процесса, использующего те же предметы.
Обрабатывать ответы блокировки обновления
Проверьте, успешно ли оба обновления для дома и бизнеса. Если да для обоих, это означает, что вы можете приступить к фактическим изменениям, которые необходимо внести: удалите Business-123
и обновите Home-456
, удалив Business Association.
Для дополнительной осторожности также используйте ConditionExpression
в обоих обновлениях снова, но теперь убедитесь, что lock == 1234567890
. Таким образом, вы уверены, что никакой другой процесс не перезаписал вашу блокировку.
Если оба обновления снова пройдут успешно, вы можете считать эти два элемента обновленными и согласованными для чтения другими процессами. Для этого запустите третье обновление, удалив атрибут lock
из обоих элементов.
При сбое одной из операций вы можете, например, повторить попытку X раз. Если это не удалось все X раз, убедитесь, что процесс очищает другие lock
, которые были выполнены ранее.
Обеспечить блокировку транзакции через ваш код
Всегда используйте ConditionExpression
в любой части вашего кода, которая может обновлять / удалять элементы Home и Business. Это очень важно для того, чтобы решение работало.
При чтении предметов «Дом» и «Бизнес» вам необходимо это сделать ( это может не понадобиться при всех чтениях, вы решите, нужно ли вам обеспечивать согласованность от начала до конца при работе с прочитанным элементом из БД ):
- Получить элемент, который вы хотите прочитать
- Создать временную метку блокировки
- Обновить элемент с помощью
lock=timestamp
, используя ConditionExpression
- Если обновление выполнено успешно, продолжайте использовать элемент в обычном режиме; если нет, подождите одну или две секунды и повторите попытку;
- Когда вы закончите, обновите элемент, удалив
lock
Регулярно очищать неудачные транзакции
Каждую минуту или около того запускайте фоновый процесс для поиска потенциально неудачных транзакций. Если вашим процессам требуется максимум 60 секунд, и есть элемент со значением lock
старше, скажем, 5 минут (помните, что значение lock
- это время начала транзакции), можно с уверенностью сказать, что эта транзакция в какой-то момент завершилась неудачно и какой бы процесс ни выполнялся, он не очищал блокировки должным образом.
Это фоновое задание гарантирует, что ни один элемент не будет заблокирован на вечность.
Остерегайтесь этой реализации, не обеспечивающей реальной атомарной и согласованной транзакции в том смысле, как это делают традиционные БД ACID. Если это важно для вас (например, вы имеете дело с финансовыми операциями), не пытайтесь реализовать это. Поскольку вы сказали, что с вами все в порядке, если атомное происшествие нарушается в редких случаях отказа, вы можете жить с этим счастливо. ;)
Надеюсь, это поможет!