Проблемы с конфликтами в 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 с реальным хранилищем данных!
Что может добавить к путанице, так это автоматические повторные попытки транзакций, которые могут произойти как после фактических конфликтов записи, так и просто из-за конфликта доступа. Эти повторные попытки могут показаться конечному пользователю подозрительным повторным выполнением некоторых путей кода - обработчиком в вашем случае.
Повторные попытки на самом деле могут усугубить ситуацию (на короткое время) - создавая еще больше обращений к уже активно используемым группам объектов - я видел такие шаблоны с транзакциями, работающими только после того, как экспоненциальные задержки отката вырастают достаточно большими, чтобы позволить чему-то немного остыть (если число повторных попыток достаточно велико), позволяя завершить уже выполняющиеся транзакции.
Мой подход к этому состоял в том, чтобы переместить большую часть транзакций в задачах принудительной очереди, отключить повторные попытки на уровне транзакций и задач и вместо этого полностью переместить в очередь задачу - меньше попыток, но разнесенных друг от друга.
Обычно, когда вы сталкиваетесь с такими проблемами, вам приходится повторно посещать ваши структуры данных и / или способ доступа к ним (ваши транзакции). В дополнение к решениям, поддерживающим строгую согласованность (которая может быть довольно дорогой), вы можете захотеть еще раз проверить, является ли согласованность действительно необходимой. В некоторых случаях это добавляется как общее требование только потому, что, кажется, упрощает вещи. По моему опыту это не так:)
Еще одна вещь, которая может помочь (но только немного), - использование более быстрого (также более дорогого) типа экземпляра - более короткое время выполнения приводит к немного меньшему риску наложения транзакций. Я заметил это, так как мне нужен был экземпляр с большей памятью, который также оказался быстрее:)