Можете ли вы выполнить несколько запросов Flask SQLAlchemy параллельно? - PullRequest
0 голосов
/ 04 апреля 2019

В моих текущих настройках, если я делаю пять запросов по 100 мс, они занимают всего 500 мс.Есть ли способ, которым я могу запустить их параллельно, так что это займет всего 100 мс?

Я использую Flask за nginx / uwsgi, но могу изменить любой из них.

В частности, я быхотел бы иметь возможность превратить код из этого:

result_1 = db.session.query(...).all()
result_2 = db.session.query(...).all()
result_3 = db.session.query(...).all()

В что-то вроде этого:

result_1, result_2, result_3 = run_in_parallel([
  db.session.query(...).all(),
  db.session.query(...).all(),
  db.session.query(...).all(),
])

Есть ли способ сделать это с помощью Flask и SQLAlchemy?

1 Ответ

3 голосов
/ 05 апреля 2019

Параллелизм в целом

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

concurrent.futures

В вашем случае темы идеальны. В Python есть модуль threading , на который вы можете посмотреть, но распаковать его достаточно просто: безопасное использование потоков обычно означает ограничение числа потоков, которые можно запустить, используя пул потоков и очередь для задач. , По этой причине я предпочитаю библиотеку concurrent.futures , которая предоставляет обертки вокруг threading, чтобы предоставить вам простой в использовании интерфейс и справиться со многими сложностями для вас.

При использовании concurrent.futures вы создаете исполнителя, а затем отправляете ему задачи вместе со списком аргументов. Вместо вызова функции, подобной этой:

# get 4 to the power of 5
result = pow(4, 5)
print(result)

Вы отправляете функцию и ее аргументы:

Обычно вы используете concurrent.futures примерно так:

from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor()
future = executor.submit(pow, 4, 5)
print(future.result())

Обратите внимание, что мы не вызываем функцию с помощью pow(), мы передаем объект функции pow, который исполнитель будет вызывать внутри потока.

Чтобы упростить использование библиотеки concurrent.futures с Flask, вы можете использовать flask-executor , который работает как любое другое расширение Flask. Он также обрабатывает крайние случаи, когда ваши фоновые задачи требуют доступа к контекстным локальным элементам Flask (таким как объекты app, session, g или request) внутри фоновой задачи. Полное раскрытие: я написал и поддерживаю эту библиотеку.

(Интересный факт: concurrent.futures оборачивает как многопоточность, так и многопроцессорность, используя один и тот же API - поэтому, если в будущем вам понадобится многопроцессорность для задач, связанных с ЦП, вы можете использовать одну и ту же библиотеку одинаковым образом для достижения своей цели)

Собираем все вместе

Вот как выглядит flask-executor для параллельного запуска задач SQLAlchemy:

from flask_executor import Executor
# ... define your `app` and `db` objects

executor = Executor(app)   

# run the same query three times in parallel and collect all the results
futures = []
for i in range(3):
    # note the lack of () after ".all", as we're passing the function object, not calling it ourselves
    future = executor.submit(db.session.query(MyModel).all) 
    futures.append(future)

for future in futures:
    print(future.result())

Бум, теперь вы выполняете несколько запросов Flask SQLAlchemy параллельно.

...