My understanding so far is that a good pattern to update “value” based on state and avoid data races is to perform a compare-and-swap operation, like so:
In this example, IIRC, mongo guarantee that value is set to <desired_value_if_desired_state> iif state is in <desired_state>. This means that there can’t be a data race where the filter matches the document, some other process changes the value, and then the update happens. Is this correct?
This means that there can’t be a data race where the filter matches the document, some other process changes the value, and then the update happens.
There is a misconception here - you don’t need to do anything special to avoid this race when you do an update. You can use updateOne - you don’t need find_one_and_update which is the same as an update_one but it also returns back the document that you updated (and therefore is a bit more heavy-weight).
Every time MongoDB is about to update a document it checks that the document still matches the filter that caused it to be selected. Every time. No special operation needed.
However, aggregation with $merge is not an update operation, or rather it is an update operation but it’s updating based on the value of on field. Meaning it updates by _id and it’s not including any other conditions in the update filter. Which makes it not atomic as far as previously matched state of the document.
This doesn’t mean that you can’t do additional checks to make sure that the document is still in the “right” state before applying the update, but $merge isn’t really meant for that, and I would recommend using update command when you are doing an update - since you can express the update using a pipeline with aggregation expressions, it’s almost infinitely powerful.
Asya
P.S. checking desired state during update is critical in the thread-unsafe “read-update-write” pattern which is less desirable than an atomic update command but may be sometimes unavoidable due to application requirements.