ValueError: Не удалось зафиксировать транзакцию за 5 попыток

TL;DR Транзакции не обновляют счетчики должным образом

Сценарий:

У меня есть подколлекции с именами projects и activities. коллекция проектов используется для хранения информации о любых текущих проектах, а коллекция действий служит контрольным журналом для любых изменений, происходящих в коллекции проектов.

Я написал облачную функцию, которая запускается для коллекции действий on create event. После создания документа в коллекции действий мне нужно обновить счетчик с именем activityCount в документе, обозначенном projectId в подколлекции проектов.

Облачная функция (некоторые части удалены из-за краткости) выглядит следующим образом:

@firestore.transactional
def update_activity_count_in_transaction(transaction, activity):
    """
    updates the activity count for a project
    """
    try:
        # get the project details of associated project at home/{orgId}/projects/{projectId}
        project = activity.project.doc_ref.get(transaction=transaction)

        # get the activity count from the transactional snapshot
        activity_count = project.get(FireBaseConfig.ACTIVITY_COUNT)

        # increment the counter using transaction
        transaction.update(activity.project.doc_ref, {FireBaseConfig.ACTIVITY_COUNT: activity_count + 1})
    except KeyError as err:
        print(err)

        # create activityCount key if not present in projects collection and increment the counter using transaction
        transaction.update(activity.project.doc_ref, {FireBaseConfig.ACTIVITY_COUNT: 1})
    print('transaction success')

    return True


def activity_on_create(event, context):
    """
    update the activity count in projects sub collection
    This is invoked on create event at home/{orgId}/activities/{activityId}
    """

    # parse the event data received from cloud functions into a FireStoreActivity object
    activity = FireStoreActivity(event, context)
    try:
        if not activity.project.id:
            print('no project id')
            return True

        # get firestore transaction object
        # activity.transaction() is equivalent to firestore.client().transaction()
        transaction = activity.transaction()

        # update the counter using transaction
        update_activity_count_in_transaction(transaction, activity)
    except Exception as err:
        pass
    return True

При этом возникает следующая ошибка:

ValueError: Failed to commit transaction in 5 attempts

Проследить:

File "/env/local/lib/python3.7/site-packages/google/cloud/functions/worker.py", line 333, in run_background_function
    _function_handler.invoke_user_function(event_object)
  File "/env/local/lib/python3.7/site-packages/google/cloud/functions/worker.py", line 199, in invoke_user_function
    return call_user_function(request_or_event)
  File "/env/local/lib/python3.7/site-packages/google/cloud/functions/worker.py", line 196, in call_user_function
    event_context.Context(**request_or_event.context))
  File "/user_code/main.py", line 149, in activity_on_create
    raise err
  File "/user_code/main.py", line 146, in activity_on_create
    update_activity_count_in_transaction(transaction, activity)
  File "/env/local/lib/python3.7/site-packages/google/cloud/firestore_v1beta1/transaction.py", line 327, in __call__
    raise ValueError(msg)
ValueError: Failed to commit transaction in 5 attempts.

Примечание. Эта ошибка возникает случайным образом и не постоянно. Но это происходит.

Любая идея о том, что могло пойти не так здесь?


person Adarsh    schedule 31.01.2019    source источник
comment
Я предполагаю, что один и тот же документ обновляется многими одновременными транзакциями. В результате некоторые транзакции не могут получить успешную фиксацию документа после 5 последовательных попыток.   -  person Hiranya Jayathilaka    schedule 31.01.2019
comment
Но вся цель наличия транзакции состоит в том, чтобы иметь согласованные операции чтения и записи даже при одновременном редактировании. В их документах упоминается, что в таких случаях firestore повторно запускает всю транзакцию для согласованности.   -  person Adarsh    schedule 01.02.2019
comment
Да, но перед лицом повторяющихся сбоев при создании согласованного моментального снимка клиент в какой-то момент должен сдаться. В случае клиента Python он повторяет попытку до 5 раз по умолчанию. Кажется, вы можете управлять этим параметром, передав аргумент max_attempts в client.transaction().   -  person Hiranya Jayathilaka    schedule 01.02.2019
comment
Да, я увеличил его до 10 попыток. Возможно, следует использовать распределенные счетчики в случае большого количества одновременных операций чтения. Добавьте приведенное выше в качестве ответа, чтобы я мог пометить его как ответ.   -  person Adarsh    schedule 01.02.2019


Ответы (1)


Когда транзакцию не удается зафиксировать из-за большого количества одновременных записей в одни и те же целевые документы, клиенты Firestore будут повторять транзакцию до N раз, прежде чем сдаться. В случае клиента Python Firestore N = 5 по умолчанию. Вы можете управлять этим параметром, передав аргумент max_attempts в вызов метода client.transaction().

person Hiranya Jayathilaka    schedule 01.02.2019