I am trying to use Spring Data MongoDB transactions to avoid write skew errors. This diagram explains the flow that happens in my example. I also posted the source code as a whole in this repository.
My service method that reads and updates the same document is @Transactional
:
fun nextMessage(id: Int, text: String) = transactionReadWrite(id, text)
?.let { messages.save(it) }
@Transactional
fun transactionReadWrite(id: Int, text: String) = threads.findById(id)
?.write(text)
?.let { (threadUpdate, previousLatest) ->
threads.save(threadUpdate)
log.info("Replacing \"${previousLatest.text}\" with \"${threadUpdate.latestMessage.text}\"")
previousLatest
}
But when I call nextMessage()
with 5 different inputs in parallel, I can see some of them reading the same values at the same time, and then successfully updating the document:
15:37:06.964 : Opened connection [connectionId{localValue:3, serverValue:1264}] to localhost:27017
15:37:07.308 : Replacing "0" with "3"
15:37:07.308 : Replacing "0" with "4"
15:37:07.310 : Replacing "0" with "1"
15:37:07.430 : Opened connection [connectionId{localValue:5, serverValue:1265}] to localhost:27017
15:37:07.430 : Opened connection [connectionId{localValue:6, serverValue:1266}] to localhost:27017
15:37:07.435 : Replacing "1" with "2"
15:37:07.440 : Replacing "1" with "5"
15:37:07.447 : Replacing "5" with "another one"
How do you avoid write skew errors with Spring Data MongoDB transactions?
According to the diagram, B3.update should not succeed, you need to use optmistic locking in the update, like updateOne({_id:xx, text:"1"}, $set: { text: "3" })
that will return 0 modified
Then Bobby needs to find the current entity and attempt the update again.