LINUX.ORG.RU

Ответить на тяжелый HTTP запрос сразу, отделив саму задачу в фоне

 , ,


0

5

Имеется сервис на aiohttp. Бывает приходят запросы, где нужно выполнить тяжелую задачу - соответственно клиент отваливается по своему таймауту.

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

Сам метод - это await asyncio.gather(1, 2, 3), которые в свою очередь вызывают еще пару десятков методов в своих await asyncio.gather().

Возможно ли такое и направьте на нужный путь, если это так.

PS. Сразу уточню, что лучше бы было выделить это в отдельный сервис, но пока требование сделать в текущем варианте.

★★★★★

Во-первых celery, это прям канон, но celery это sync библиотека. Если же не хочешь тащить в проект большую либу, для которой надо держать scheduler и шину сообщений, то можешь просто сделать cron команду, которая будет выгребать из базы свежие запросы и решать их.

Aswed ★★★★★
()

запустить тяжелый метод отдельным процессом в фоне

создание файла

Так, может сделать его синхронным и пускать в соответствующем экзекуторе.

vvn_black ★★★★★
()

Сразу делай в отдельном сервисе. REST’ом дергай на клиенте и отключайся, или на вебсокетах и через них оповещай, что задача выполнена, далее в сокетах приходить url на файл и его можно скачать, или что там у тебя. Задача асинхронная, а web 1.0 синхронный. Тут либо каждые три секунды дергать проверку, либо сразу нормально на вебсокетах крутить.

menangen ★★★★★
()

PS. Сразу уточню, что лучше бы было выделить это в отдельный сервис, но пока требование сделать в текущем варианте.

Отдавай сразу id задачи, а клиент по id пусть уже дёргает раз в n секунд результат.

evgeny_aa ★★☆
()

Oh no, как обычно: ЛОР — рассадник вредных советов.
Впрочем, а чего ещё ожидать от сайта с ослинухом в заголовке?

Во-первых не нужно городить сельдерей ради записи файла в результате реквеста, можно просто отспавнить тред и не усложнять бекенд на порядок.

Во-вторых лонгполлинг придумали двадцать лет назад как раз чтобы не надо было дёргать сервер каждые три секунды.

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

Все эти проблемы девяностых были давно и успешно решены.

Goury ★★★★★
()
Ответ на: комментарий от Goury

можно просто отспавнить тред и не усложнять бекенд на порядок

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

В задаче даже нет требований по отслеживанию процесса запросами на бэк или как-то еще. Мне просто нужно запустить в фоне задачу и сразу отдать в респонсе путь к файлу. Что там дальше будут с ответом делать - речи даже не идет.

gruy ★★★★★
() автор топика
Ответ на: комментарий от Goury

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

Очередной лесной житель, возомнивший себя гуру разработки, но не сумевший осилить весь пост ТС? Где чёрным по белому «но пока требование сделать в текущем варианте.»

evgeny_aa ★★☆
()
Ответ на: комментарий от gruy

способ как отцепить это в отдельный процесс

https://docs.python.org/3/library/asyncio-task.html#running-in-threads

https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor

vvn_black ★★★★★
()
Последнее исправление: vvn_black (всего исправлений: 1)

ну всё правильно: создавай в памяти задачу, клиенту отдавай 201 Created, Location: …. X-Task-Id: ….

После чего можно будет периодически дергать твой location.

Ещё можно передать вебхук для этой таски, но Location всё равно нужен на случай, если продолбается вебхук

max_lapshin ★★★★★
()
Ответ на: комментарий от max_lapshin

Да в ТЗ все даже проще - просто вернуть ссылку, откуда можно будет потом забрать файл, без каких-либо дополнительных проверок.

Так вроде помогло такое:

asyncio.create_task(generate_coroutine)
return web.json_response({'path': 'жди меня по этой ссылке'})

Где generate_coroutine запускает весь тяжелый процесс генерации файла.

gruy ★★★★★
() автор топика
Ответ на: комментарий от slovazap

Вопрос-то в чём?

Как сейчас правильно запускать таски в фоне, чтобы все отработало корректно, когда вокруг асинхронщина и т.п.

Вроде на истинный путь направили, нашел что нужно.

gruy ★★★★★
() автор топика

Вариант первый - asyncio.create_task. Минусы - это как шваброй газ зажать и тормоз выкинуть, работает до первого поворота

Вариант второй - очередь на редисе. Быстро, удобно, легко мониторить. Вернее две очереди - одна с новыми тасками и другая с тасками «в процессе». Минусы - очереди надо написать.

Вариант третий - celery и прочие. Почти как второй только кода почти нифига не надо. Минусы - все эти шедулеры работают на «снапшоте приложения» (вызывают метод прям по имени и параметрам, хрен обновишь потом) и имеют наркоманский синтаксис.

Выбирай, могу ещё накидать.

upcFrost ★★★★★
()
Ответ на: комментарий от gruy

Я бы сделал на threading (https://docs.python.org/3/library/threading.html), но способы есть разные и идеальное решение в твоей ситуации может отличаться.
Способ проверенный в боевых условиях, работает исправно и даёт отличные результаты.
Но для успешного применения нужно уметь думать головой и понимать как работает трединг в питоне, потому многие мамкины девелоперы часто гонят какую-то оккультную ахинею на этот способ.

Но ничего сложнее «положить в базу данных и по крону дёргать скрипт» тут точно не нужно.

Goury ★★★★★
()
Последнее исправление: Goury (всего исправлений: 1)
Ответ на: комментарий от evgeny_aa

Очередной мамкин девелопер, возомнивший себя гуру лесной жизни, но не сумевший прочитать мой камент целиком и до конца?

Расшифровываю для мамкиных: я сообщаю ТС что ему нужно придерживаться своего плана, а не слушать местных сумасшедших.
Я там не даю ответа.
Вот так вот.

Не должен каждый комментарий давать ответ на поставленный вопрос, такое вот открытие.
Иногда можно просто порассуждать о чём-нибудь или просто сказать что кто-то в чём-то прав или нет.

Goury ★★★★★
()
Последнее исправление: Goury (всего исправлений: 1)
Ответ на: комментарий от upcFrost

Минусы - это как шваброй газ зажать и тормоз выкинуть, работает до первого поворота

А чего плохого в asyncio.create_task/asyncio.ensure_future ?

ggrn ★★★★★
()
Последнее исправление: ggrn (всего исправлений: 1)
Ответ на: комментарий от agentgoblin

Да хоть триллион.
Во-первых балансировка входящей нагрузки регулируется не из питона, а на предыдущем этапе.
Во-вторых ничего не мешает внутри трединга городить какие хочешь очереди и даже отправлять задачи на другие девайсы.
Тыжпрограммист.

Goury ★★★★★
()
Ответ на: комментарий от ggrn

При выходе из контекста (как в случае ТС, ответ уже ушел) невозможно нормально следить за выполнением таски. Начинается куча «а что если». Что если нода сдохла, что если таск упал по вине удалённого провайдера, что если юзер хочет получать отбивку на каком этапе этот таск.

По сути твой таск становится строго локальным и с очень ограниченными возможностями по масштабированию и обработке исключений.

Так можно делать, я не спорю, просто не всегда.

upcFrost ★★★★★
()
Ответ на: комментарий от vvn_black

Не нужно это использовать. Есть горький опыт. Celery не совместима с asyncio никакими хаками. Можно получить очень больно граблями по лбу. В совершенно неожиданном месте. Может показаться, что всё работает, но в проде оно сломается, причём так, что придётся всё переписывать без Celery. А до этого потратить дни-недели на отладку. Это прям личный опыт. Celery абсолютно несовместима с asyncio.

Я бы поискал другие очереди задач, возможно, уже появились асинхронные. На Celery свет клином не сошёлся.

emorozov
()
Ответ на: комментарий от Goury

Можно вопрос? С веб-программированием в питоне не очень знаком.

Вот ты советуешь трединг. Хорошо, запустил он тред, wait на этом треде он видимо делать не будет, т.е. забывает про него. Тут же сформировал ответ http (со смыслом «задача запущена»), отправил его клиенту.

Основной вопрос у меня вот в чем: за счет чего мы уверены, что процесс этого треда будет продолжать работать? Насколько, я понимаю, веб-приложения в питоне как правило управляются чем-то вроде UWSGI и подобным, которые стартуют эти процессы, повторно их используют и терминируют если нужно.

Второй вопрос, если тред по какой-то причине крешит процесс, получается мы сломали и обслуживание других http запросов которые в этот момент обслуживались. Либо вообще сломали любое обслуживание HTTP запросов. Так что ли?

asdpm
()
Ответ на: комментарий от asdpm

Нельзя вопрос, сначала туториал прочитай. А потом по выбранному фреймворку ещё туториал прочитай.

Основной ответ: если твой uWSGI прибивает работающие процессы посреди рабочего процесса — админу надо руки из ягодиц вырвать и заставить их съесть.
Дополнение к основному ответу: нет, процесс питона может работать так долго как хочется, это не похапе.

Второй ответ: нет, тред крашит только сам себя, с какого перепугу он должен как-то повлиять на родительский процесс или на гипервизора?
Дополнение ко второму ответу: код писать надо пальцами рук, а не анусом ягодиц. И за выпуск без полного покрытия тестами в продакшн нужно выпустившему отрезать руки. И все проблемы решатся ещё до появления.

Goury ★★★★★
()
Ответ на: комментарий от vvn_black

Нет, даже через asyncio.run может сломаться. Celery ломает зачем-то интерпретатор через колено на низком уровне, что приводит просто к фантастическим неожиданным результатам при использовании asyncio в одном процессе с Celery. Типа корутины становятся внезапно не совсем корутинами. Я докопался только до этого, значительную часть кода пришлось переписать из-за этого, потеряв сначала пару недель на отладку, затем ещё пару недель на переписывание. Причём в тестах и на тестовом стенде всё вроде как работало, а сломалось только при выкатывании в прод.

Там такие фантастически неожиданные приключения начинаются… И обойти их невозможно. После этого я собственно и к синхронному Celery с очень большой опаской стал относиться.

emorozov
()

У меня была подобная задача, запускал функцию через multiprocessing.Process. Внутри этой функции выполнял loop = asyncio.new_event_loop(). Вполне нормально работает.

Единственное, укажи daemon=True. Иначе uwsgi прибьет процесс.

dicos ★★
()

вместо веб-сокетов можно и извращение придумать с transfer-encoding chunked:

async def handler():
  finished = asyncio.Event() # ?
  # запускаешь процесс с тяжелой задачей (CPU-Bound)
  while 1:
    # проверяешь finished
    await asyncio.sleep(25)
    # отдаешь пустой ответ ""
    if finished:
      break
  # отдаешь json с результатом 

На клиенте оно читается чем-то типа этого:

https://developer.mozilla.org/ru/docs/Web/API/Streams_API#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D1%8B

Я все это еще лет 5 назад знал, но как было эксперементальной технологией так и осталось

tz4678_2
()

Учти что твое соединение может быть разорвано, сеть пропала, сервер перезагрузили, клиент в туннеле. И после этого клиенту надо будет узнать результаты. А запрос он будет делать может на другой бакенд уже.

Советы про лонг поллинг это круто, но все равно надо хранить стейт где то и уметь при его изменении извещать клиента.

OxiD ★★★★
()
Ответ на: комментарий от asdpm

Зачем такие сложные вопросы задаешь, Goury же только из яслей в детский сад перешел, молодец кстати, качается, а то что считает себя Гуру (Гоуру) это уже отдельная история, в яслях такой же был :-).

ei-grad ★★★★★
()
Последнее исправление: ei-grad (всего исправлений: 1)
Ответ на: комментарий от Goury

Уточнение - ты говоришь про треды, которые threading, а не multiprocessing, правильно?

upd: увидел дальше по треду - threading

Почему-то про спавн процессов (multiprocessing) подумал в прошлый раз, вопрос снимается.

agentgoblin
()
Последнее исправление: agentgoblin (всего исправлений: 2)
Ответ на: комментарий от vvn_black

Единственный адекватный ответ. Оптимальным было бы такое решение:

  • Заспавнить multiprocessing.Process, который будет брать задачи из очереди в бесконечном цикле
  • В обработке вызывать asyncio.gather(asyncio.to_thread(blocking_io)), как в примере
  • Веб-сервер только кладёт задачу в очередь

Конструкция выходит немного побольше. Но у неё есть преимущества:

  • Страховка от нагрузки — если что-то пойдёт не так, то зависнет только обработчик, а не весь веб-сервер
  • Вывезет очень большую очередь без дополнительных костылей
  • Легко масштабировать на большее количество процессов
  • Легко дописать мониторинг состояния задач для отдачи клиенту
  • Отдельный процесс проще мониторить и дебажить, если потребуется
InterVi ★★★★
()
Ответ на: комментарий от InterVi

Если io то хватит, правда стрелять create_task без всякого мониторинга как написано выше по треду всё-таки не совсем production решение.

Тем более что ты сам написал чуть выше «отспавнить процесс». Экзекутор в этом плане имхо удобнее, хоть там и накладных расходов больше. А то нужно будет ТСу два процесса - и будет лапша.

upcFrost ★★★★★
()
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от upcFrost

Предполагается какой-то фасад, а за ним можно будет дописать балансировщик. Можно и сразу, но это уже преждевременная оптимизация. Там скорее дисковое i/o просядет и понадобится переехать в облако, на cloud functions и basket.

InterVi ★★★★
()

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

а потом говорят, что го для хипстоты. на нем такие вещи делаются на раз. без редисов, целери и вот этого всего огорода.

это были просто мысли вслух :), без претензий к ТС и комментаторам. удивляюсь я просто таким образом

ergo ★★★
()
Последнее исправление: ergo (всего исправлений: 2)