Firebase обновляет поля документов, ожидая, что этого не будет делать (используя транзакции) - PullRequest
0 голосов
/ 11 октября 2019

Для моего веб-сайта я храню уведомления для каждого пользователя в 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 в любом случае.

Что я делаю неправильно?

1 Ответ

1 голос
/ 16 октября 2019

После некоторого поиска я решил свою конкретную проблему, не используя транзакции, а используя write_option.

В основном я получаю снимок update_time (поле, заданное Firebase), и когдаЯ делаю обновление, я передаю это как write_option. Заявление: если при обновлении last_update не совпадает, произойдет сбой действия update.

    from google.api_core.exceptions import FailedPrecondition

    [...]
        path = f"users/{uid}/notifications"
        notification_ref = self.client.collection(path).document(id)
        snapshot = notification_ref.get()

        if snapshot.exists:
            if snapshot.get("generated_id") == generated_id:
                write_option = self.client.write_option(
                    last_update_time=snapshot.update_time
                )
                try:
                    notification_ref.update({"is_read": True}, option=write_option)
                except FailedPrecondition:
                    pass
     [...]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...