I've got a document
{ key : 'key1', value : 'value1', update_time : 100 }
That I'd like to change only with more recent (greater) update times. What I'm doing now is:
def update_key1(new_value, new_time):
record = find_one( { key : 'key1' } )
if not record or record['update_time'] < new_time:
update( { key : 'key1', value : new_value, update_time : new_time }, upsert=True)
Obviously this is an extra roundtrip to the db, but more importantly there's no lock on the document, and concurrent calls could result in a lower new_time's value remaining in the db. Is there a way to perform an upsert only if a condition is true?
EDIT: Just to clarify, the intention is not to create multiple documents for each key and then sort on lookup. Though that would solve my problem, these values change a lot and would waste a lot of space.
Short of being able to do the whole thing atomically, there are two kinds of existing conditions where you want to make a change, and you can deal with each of them atomically:
update_time
is older than new_time
Update an existing record for key:
def update_if_stale(key, new_value, new_time):
collection.update({'key': key,
'update_time': {'$lt': new_time}
},
{'$set': {'value': new_value,
'update_time': new_time
}
}
)
Insert if a record for key didn't exist before:
def insert_if_missing(key, new_value, new_time):
collection.update({'key': key},
{'$setOnInsert': {'value': new_value,
'update_time': new_time
}
},
upsert=True
)
($setOnInsert
was added in MongoDB 2.4)
You might be able to put those together to get what you need, e.g.:
def update_key(key, new_value, new_time):
insert_if_missing(key, new_value, new_time)
update_if_stale(key, new_value, new_time)
However, depending on what remove/insert time scales might be possible in your system, you might need multiple calls (update/insert/update) or other shenanigans.
Aside: If you want a record missing the update_time
field to be treated as a stale record to update, change {'$lt': new_time}}
to {'$not': {'$gte': new_time}}
If you add a unique constraint on key
, then updateOne
with an update_time
filter and upsert=True
will
collection.updateOne({'key': key,
'update_time': {'$lt': new_time}
},
{'$set': {'value': new_value,
'update_time': new_time
}
},
upsert=True
)
You can catch errors and check for code: 11000
(unique constraint violations) to specifically handle those cases of having tried to update to a past state.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With