Проблемы с конфликтами в Google App Engine

У меня проблемы с конкуренцией в Google App Engine, и я пытаюсь понять, что происходит.

У меня есть обработчик запросов с пометкой:

@ndb.transactional(xg=True, retries=5) 

... и в этом коде я получаю некоторые вещи, обновляю некоторые другие и т. д. Но иногда такая ошибка появляется в журнале во время запроса:

16:06:20.930 suspended generator _get_tasklet(context.py:329) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "s~my-appname"
path <
  Element {
    type: "PlayerGameStates"
    name: "hannes2"
  }
>
)
16:06:20.930 suspended generator get(context.py:744) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "s~my-appname"
  path <
    Element {
      type: "PlayerGameStates"
      name: "hannes2"
    }
  >
  )
16:06:20.930 suspended generator get(context.py:744) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "s~my-appname"
  path <
    Element {
      type: "PlayerGameStates"
      name: "hannes2"
    }
  >
  )
16:06:20.936 suspended generator transaction(context.py:1004) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "s~my-appname"
  path <
    Element {
      type: "PlayerGameStates"
      name: "hannes2"
    }
  >
  )

... сопровождается следом стека. При необходимости я могу обновить всю трассировку стека, но она довольно длинная.

Я не понимаю, почему это происходит. Глядя на строку в моем коде, там появляется исключение, я запускаю get_by_id на совершенно другую сущность (Раунд). "PlayerGameStates", имя "hannes2", которое упоминается в сообщениях об ошибках, является родителем другого объекта GameState, который был get_async: редактировать из базы данных несколькими строками ранее;

# GameState is read by get_async
gamestate_future = GameState.get_by_id_async(id, ndb.Key('PlayerGameStates', player_key))
...
gamestate = gamestate_future.get_result()
...

Странная (?) Вещь в том, что в хранилище данных нет записей, происходящих для этой сущности. Насколько я понимаю, могут возникать конфликтные ошибки, если одна и та же сущность обновляется в одно и то же время параллельно, или, может быть, если за короткое время происходит слишком много записей.

Но может ли это произойти и при чтении сущностей? ("Приостановлено получение генератора..."??) И происходит ли это после 5 повторных транзакций ndb.transaction..? Я не вижу ничего в журнале, который указывает, что были сделаны какие-либо попытки.

Любая помощь с благодарностью.

1 ответ

Решение

Да, конфликт может возникнуть как для операций чтения, так и записи.

После начала транзакции - в вашем случае, когда обработчик помечается @ndb.transactional() вызывается - любая группа сущностей, к которой осуществляется доступ (с помощью операций чтения или записи, не имеет значения), немедленно помечается как таковая. На данный момент неизвестно, будет ли в конце транзакции операция записи или нет - это даже не имеет значения.

Слишком большая ошибка конкуренции (которая отличается от ошибки конфликта!) Указывает на то, что слишком много параллельных транзакций одновременно пытаются получить доступ к одной и той же группе объектов. Это может произойти, даже если ни одна из транзакций на самом деле не пытается написать!

Примечание: этот конфликт НЕ эмулируется сервером разработки, его можно увидеть только при развертывании в GAE с реальным хранилищем данных!

Что может добавить к путанице, так это автоматические повторные попытки транзакций, которые могут произойти как после фактических конфликтов записи, так и просто из-за конфликта доступа. Эти повторные попытки могут показаться конечному пользователю подозрительным повторным выполнением некоторых путей кода - обработчиком в вашем случае.

Повторные попытки на самом деле могут усугубить ситуацию (на короткое время) - создавая еще больше обращений к уже активно используемым группам объектов - я видел такие шаблоны с транзакциями, работающими только после того, как экспоненциальные задержки отката вырастают достаточно большими, чтобы позволить чему-то немного остыть (если число повторных попыток достаточно велико), позволяя завершить уже выполняющиеся транзакции.

Мой подход к этому состоял в том, чтобы переместить большую часть транзакций в задачах принудительной очереди, отключить повторные попытки на уровне транзакций и задач и вместо этого полностью переместить в очередь задачу - меньше попыток, но разнесенных друг от друга.

Обычно, когда вы сталкиваетесь с такими проблемами, вам приходится повторно посещать ваши структуры данных и / или способ доступа к ним (ваши транзакции). В дополнение к решениям, поддерживающим строгую согласованность (которая может быть довольно дорогой), вы можете захотеть еще раз проверить, является ли согласованность действительно необходимой. В некоторых случаях это добавляется как общее требование только потому, что, кажется, упрощает вещи. По моему опыту это не так:)

Еще одна вещь, которая может помочь (но только немного), - использование более быстрого (также более дорогого) типа экземпляра - более короткое время выполнения приводит к немного меньшему риску наложения транзакций. Я заметил это, так как мне нужен был экземпляр с большей памятью, который также оказался быстрее:)

Другие вопросы по тегам