Что происходит, когда асинхронный ввод приводит к исключению конкуренции после завершения запроса в Appengine с NDB?

Используя ndb, скажем, я поставил 40 элементов put_async с @ndb.toplevel, написал вывод пользователю и завершил запрос, однако один из этих put_async привел к исключению конкуренции, будет ли ответ 500 или 200? Или скажем, если это задача, будет ли задача выполняться повторно?

Одним из решений является get_result() для всех этих 40 запросов до завершения запроса и перехвата этих исключений, если они происходят, но я не уверен, повлияет ли это на производительность.


person Kaan Soral    schedule 01.09.2012    source источник


Ответы (2)


Это странно, я использую верхний уровень и ожидаю противоположного поведения. И это то, что я наблюдаю. Что-то изменилось с момента первого ответа на этот вопрос? Как говорит док:

Это, в свою очередь, позволяет вам отправить запрос и не беспокоиться о результате.

Вы можете попробовать следующий модульный тест (используя тестовый стенд):

@ndb.tasklet
def raiseSomething():
    yield ndb.Key('foo','bar').get_async()
    raise Exception()

@ndb.toplevel
def callRaiseSomething():
    future = raiseSomething()
    return "hello"

response = callRaiseSomething()
self.assertEqual(response, "hello")

Этот тест проходит. NDB регистрирует предупреждение: «приостановленный генератор raiseSomething(tests.py:90) поднял Exception()», но не вызывает повторно исключение.

ndb.toplevel только ожидает RPC, но ничего не делает с фактическим результатом. Если ваша украшенная функция сама является тасклетом, она сначала вызовет для нее get_result(). В этот момент будут подняты исключения. Затем он будет ждать оставшихся «осиротевших» RPC и будет что-то регистрировать только в случае возникновения исключения.

Итак, мой ответ: запрос будет успешным (вернуть 200)

person Alexis    schedule 15.08.2013
comment
Я думаю, вам нужно добавить yield для вызова raiseSomething() для применения верхнего уровня. - person Rob Curtis; 07.11.2013
comment
Если вы уступаете, то вы просто ждете результата raiseSomething перед выполнением остальной части callRaiseSomething, и поэтому не используете функцию «выстрелил и забыл» верхнего уровня. - person Alexis; 07.11.2013
comment
Да, верно, использование yield в данном случае не имеет смысла. Проработав ваш ответ, я обновил свой ответ. Как ни странно, вызов put_async() в обработчике, украшенном @ndb.toplevel, будет периодически поднимать 500. Я проголосовал за ваш ответ. - person Rob Curtis; 08.11.2013
comment
Я заметил, что некоторые ошибки действительно не обрабатываются должным образом, когда они возникают во внутреннем коде ndb (batcher). Например, если вместо этого вы используете ndb.Key('foo', 0), будет вызвана ошибка BadRequestError, но не из тасклета, поэтому обработка ndb.toplevel будет пропущена. - person Alexis; 08.11.2013
comment
@RobCurtis Мне любопытно узнать, какая ошибка периодически поднимает 500 в ваших тестах. - person Alexis; 08.11.2013
comment
украсить обработчик с помощью ndb.toplevel. В обработчике вызовите put_async() для модели. Отключите запись хранилища данных в консоли администратора (в режиме реального времени). Обработчик вызовов из браузера. Иногда я получаю 500, а иногда 200 с ответом. Исключение: CapabilityDisabledError: запись в хранилище данных отключена администратором приложения. Запись можно повторно включить в консоли администратора. - person Rob Curtis; 08.11.2013

Насколько я понимаю, использование @ndb.toplevel заставляет обработчик ждать завершения всех асинхронных операций перед выходом. Из документов:

Для удобства вы можете украсить обработчик запроса @ndb.toplevel. Это говорит обработчику не выходить, пока его асинхронные запросы не будут завершены. Это, в свою очередь, позволяет вам отправить запрос и не беспокоиться о результате. https://developers.google.com/appengine/docs/python/ndb/async#intro

Таким образом, добавив @ndb.toplevel, ответ на самом деле не будет возвращен до тех пор, пока не завершится выполнение асинхронных методов. Использование @ndb.toplevel устраняет необходимость вызывать get_result для всех запущенных асинхронных вызовов (для удобства). Исходя из этого, запрос по-прежнему будет возвращать 500, если асинхронные запросы завершатся неудачно, потому что все асинхронные запросы должны быть завершены перед возвратом. Обновлено: ниже

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

def get(self):
    deferred.defer(execute_stuff_in_background, param,param1)
    template.render(...)

а execute_stuff_in_background выполнит все дорогостоящие операции после возврата обработчика. Если в задаче возникнет конфликт, ваш исходный обработчик все равно вернет 200.

Если вы подозреваете, что возникнет конфликт, рассмотрите возможность сегментирования или использования реализации очереди fork-join для обработки записей (см. реализацию здесь: http://www.youtube.com/watch?v).=zSDC_TU7rtc#t=41m35)

Редактировать: краткий ответ Запрос завершится неудачно (возвратит 500), если асинхронные запросы не пройдут, потому что @ndb.toplevel ожидает завершения всех результатов перед выходом. Обновлено:Посмотрев на ответ @alexis ниже, я повторно запустил свой первоначальный тест (где я отключил запись в хранилище данных и вызвал put_async в обработчике, украшенном @ndb.toplevel), response периодически поднимает 500 (я предполагаю, что это зависит от времени выполнения). Основываясь на этом и ответе @alexis ниже, не ожидайте, что результат будет 500, если асинхронная задача выдает исключение, а вызывающая функция украшена @ndb.toplevel

person Rob Curtis    schedule 01.09.2012
comment
Большое спасибо за ссылку на разногласие, но, кроме этого, я боюсь, что в вашем ответе нет ответа на вопрос, вопрос в том, будет ли запрос неудачным? - person Kaan Soral; 02.09.2012
comment
@Kaan, Круто, обновил ответ. Я был недостаточно ясен. Я провел несколько тестов, чтобы увидеть, когда обработчик запроса вернется. Похоже, что ВСЕ асинхронные запросы должны быть завершены до того, как обработчик вернет ответ. Так что да, будет 500. Ответ обновлен. - person Rob Curtis; 02.09.2012
comment
Для проверки: отключите запись в хранилище данных и удалите ndb.toplevel из вашего обработчика. Функция put_async() не будет генерировать исключение, и обработчик вернет 200. Однако включите ndb.toplevel в обработчик (с отключенной записью в хранилище данных), и обработчик вернет 500, поскольку исключения генерируются только тогда, когда get_result вызывается (что, по сути, и делает @ndb.toplevel, он вызывает get_result). - person Rob Curtis; 02.09.2012
comment
Еще одна дополнительная информация: то, что вы вызываете put_async() 40 раз, не означает, что есть 40 ожидающих RPC. Вполне возможно, что все они объединены в единый RPC или, что более вероятно, в небольшом их количестве. Поскольку RPC завершается сбоем или завершается успешно как единое целое, все вызовы put_async(), которые были объединены в один и тот же RPC, завершатся с ошибкой (все вызовут одно и то же исключение). Наконец, помните, что это распределенная система — возможно, вы получили сообщение об ошибке от RPC, но на самом деле оно было выполнено сервером хранилища данных. - person Guido van Rossum; 03.09.2012