Для моего веб-сайта я храню уведомления для каждого пользователя в UserDocument
. Каждый UserDocument имеет коллекцию с именем notifications
. В этой коллекции есть несколько NotificationDocuments
.
users/<uid>/notifications/<notification_id>
NotificationDocument:
message: str
is_read: bool
generated_id: str
Уведомление может быть следующим: «У вас есть 2 ответа на ваш комментарий X»
Когда на комментарий пользователя добавляется другой ответ, ЖЕ ЖЕ NotificationDocument обновляется и становится:
«У вас есть 3 ответа на ваш комментарий X».
Теперь NotifcationDocument имеет статус is_read. По умолчанию is_read=False
. Однако когда пользователь читает уведомление, для этого элемента NotifcationDocument устанавливается значение is_read = True.
Тогда возникает условие гонки. Что делать, если пользователь хочет пометить свое уведомление как прочитанное, но между тем появляется другое уведомление, обновляющее содержимое до «У вас есть 4 ответа на ваш комментарий X». Если NotificationDocument изменился за время, когда пользователь хочет пометить его как is_read=True
, я хочу пропустить обновление.
Итак, я подумал, давайте использовать транзакции Firebase: https://cloud.google.com/firestore/docs/manage-data/transactions
Это код, который я имею для пометки NotificationDocument как прочитанного:
def mark_notification_as_read(
self, notification_id: str, expected_generated_id: str, uid: str
) -> None:
"""
Mark a notification with given `notification_id` as read, but only if it hasn't been updated in the meanwhile.
This is done by checking if `generated_id` is still the same.
"""
path = f"users/{uid}/notifications"
transaction = self.client.transaction()
notification_ref = self.client.collection(path).document(notification_id)
@firestore.transactional
def update_in_transaction(transaction, notification_ref):
snapshot = notification_ref.get(transaction=transaction)
time.sleep(10)
found_generated_id = snapshot.get('generated_id')
if found_generated_id == expected_generated_id:
transaction.update(notification_ref, {
'is_read': True,
})
else:
# Log for now, to be able to monitor if this is handled well
logger.info(msg="Can't mark notification as read as it has been updated!")
update_in_transaction(transaction, notification_ref)
Обратите внимание, что в time.sleep (10) я обновляю generated_id
до нового значения в консоли Firebase. Я ожидаю, что эта транзакция должна провалиться. Однако по истечении этих 10 секунд я вижу, что уведомление помечается как is_read = True в любом случае.
Что я делаю неправильно?