Flask. Безопасна ли многопоточность при длительном расчете?
Есть задача - скажем загрузить три разных файла, когда файлы загружены, надо запустить их обработку (секунд 20). Вот такой шаблон вроде работает, но есть сомнения в безопасности такого подхода из-за коммментариев в смежном вопросе .
@amz.route('/<code>/<amz_id>/calculate', methods=['GET', 'POST'])
def calculate(code, amz_id):
work_dir=current_upload_path(code, amz_id)
files_in_dir = [ el.name for el in work_dir.iterdir()]
...
import time
import threading
def super_calc():
time.sleep(20)
t1 = threading.Thread(target=super_calc)
t1.start()
return render_template('calculate.html',
code=code, amz_id=amz_id,
test_list=files_in_dir)
Это всего лишь шаблон. интересует сам механизм отсутствия "зависания" на 20 секунд. Если интересено - после расчета отправляется итоговый файл на один из адресов клиента. То есть окончательный результат отложенный...
То есть меня интересует вопрос - не возникнет ли каких сбоев, если во время обработки кто то запустит новый поток, введя три файла другого клиента?
.
Дополню вопрос проьбой привести пример, как это правильно делать. Неужели действительно здесь нужны Celery или RQ (Redis Queue) ?
.................................................................
Конкретизирую вопрос. Есть ли какой то конструктивный способ определить, когда достаточно использования threading, а когда нужно обращаться к Celery?
Ответы (1 шт):
Первая проблема решения с потоками в том, что никак не ограничивается количество запущенных потоков и ресурсов сервера под это выделенных. Решения типа celery позволяют это контролировать.
Следующая проблема с потоками в том, что если, например, после начала выполнения такого фонового потока на сервере произойдет сбой (приложение рухнет, сервер перезагрузится, виртуалку AWS прикончит или вообще весь сервер сгорит), то информация о том, что есть такая задача и ее нужно выполнить пропадет полностью.
Можно, конечно, придумывать и пытаться как-то сохранять факт, что нужно сделать такую задачу, потом при старте веб приложения, смотреть, нет ли там невыполненных задач. Но это очень нетривиально, там есть куча нюансов. И celery как раз - это то, что нужно для такой задачи.
В celery очень просто сконфигурировать задачу, чтобы при сбое, она автоматически запускалась заново. Нужно, конечно, саму задачу тоже реализовывать с учетом того, что, возможно, это повторный запуск.
Что касается долгих задач, то тут нет ничего таинственного, и все решаемо планированием и распределением ресурсов. Потенциальная проблема в том, что если у вас есть задачи критичные по времени (типичный пример, пользователь нажимает кнопку, часть обработки происходит в фоне с использованием celery, и результат действия отображается пользователю, но не в рамках синхронного http запроса, а например push через websocket), то наличие задач, которые выполняются скажем 20 секунд, могут сильно повредить, задержки будут сильно влиять на user experience.
Это обычное дело и решение тоже простое, создаются разные очереди для разных задач. У каждой очереди свой выделенный набор рабочих процессов. Так "длинные" задачи помещаются в одну очередь, а "быстрые" в другую и они друг друга не блокируют. Несложно в таком случае вообще рабочие процессы celery перенести на отдельные сервера.
Теперь об утечках памяти в celery. Действительно, в старых версиях celery есть эта проблема (не знаю как в новых, может исправили, у нас на проде довольно старая версия и я только о ней могу говорить). Не знаю связана ли проблема с длительностью задач - не исследовал, но, по ощущениям - нет. Но самое главное, что это не так важно, так как легко решается. У celery есть опция конфигурации рабочего процесса - перегружать рабочий процесс после того, как он выполнит определенное количество задач. У нас на проде это делается после 1000 задач. Это полностью решает проблему с утечками памяти, без необходимости приложению что-либо знать об этой проблеме.