Я не совсем понимаю, почему он сделал эту проверку, может кто-нибудь объяснить мне конкретный пример.
Предполагается, что хранилища событий будут постоянными, в том смысле, что после того, как вынаписать событие, оно будет видно для каждого последующего чтения.Поэтому каждое действие в базе данных должно быть дополнением.Полезная ментальная модель - думать о односвязном списке.
Если база данных будет поддерживать более одного потока выполнения, имеющего доступ на запись, то вы столкнулись с проблемой «потерянного обновления».Отрисованные как связанные списки, это может выглядеть следующим образом:
Thread(1) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(2) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(1) set(/x, [ ... <- 69726c3e <- 709726c3 <- /x.tail])
Thread(2) set(/x, [ ... <- 69726c3e <- 83b97195 <- /x.tail])
История, записанная потоком (2), не включает событие: 709726c3, записанное потоком (1).Таким образом, «потерянное обновление».
В общей базе данных вы обычно управляете этим с помощью транзакций: некоторая магия под прикрытием отслеживает все ваши зависимости данных, и если предварительные условия не выполняются, когда вы пытаетесьЕсли вы совершите транзакцию, вся ваша работа будет отклонена.
Но хранилищам событий не нужны все степени свободы, которые поддерживают общий случай - редактирование событий, хранящихся в базе данных, как и запрещеноизменение зависимостей между событиями.
Единственная изменяемая часть изменения - единственное место, где мы заменяем перезапись старого значения новым значением - это когда мы меняем /x.tail
Thread(1) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(2) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(1) set(/x, [ ... <- 69726c3e <- 709726c3 <- /x.tail])
Thread(2) set(/x, [ ... <- 69726c3e <- 83b97195 <- /x.tail])
Проблема здесь в том, что Thread (2) считал 6 <- /x.tail
истинным и заменил его значением, потерявшим событие 7. Если мы изменим нашу запись с set
на compare-and-set
...
Thread(1) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(2) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(1) compare-and-set(/x, 69726c3e <- /x.tail, [ ... <- 69726c3e <- 709726c3 <- /x.tail])
Thread(2) compare-and-set(/x, 69726c3e <- /x.tail, [ ... <- 69726c3e <- 83b97195 <- /x.tail]) // FAILS
, тогда хранилище данных может обнаружить конфликт и отклонить недопустимую запись.
Конечно, если хранилище данных видит действия потоковв другом порядке, то команда, которая потерпит неудачу, может измениться
Thread(1) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(2) [... <- 69726c3e <- /x.tail] = get(/x)
Thread(2) compare-and-set(/x, 69726c3e <- /x.tail, [ ... <- 69726c3e <- 83b97195 <- /x.tail])
Thread(1) compare-and-set(/x, 69726c3e <- /x.tail, [ ... <- 69726c3e <- 709726c3 <- /x.tail]) // FAILS
Проще говоря, где set
дает нам семантику «выиграл последний писатель», compare-and-set
дает нам «выигрыш первого писателя», чтоустраняет потерянную проблему обновления.