Статьи

Ответ на «Асинхронный Python и базы данных»

Тюльпаны

В своей замечательной статье несколько недель назад «Асинхронный Python и базы данных» автор SQLAlchemy Майк Байер пишет:

Асинхронное программирование — это всего лишь один из возможных подходов, который мы должны использовать на полке, и это ни в коем случае не тот подход, который мы должны использовать все время или даже большую часть времени, если только мы не пишем HTTP-серверы, серверы чата или другие приложения, которые специально необходимы для одновременной работы. поддерживать большое количество произвольно медленных или бездействующих TCP-соединений (где под «произвольно» мы подразумеваем, нам не важно, являются ли отдельные соединения медленными, быстрыми или бездействующими, пропускная способность может поддерживаться независимо).

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

И асинхронный не только для серверов. Клиенты, которые открывают очень большое количество соединений и ждут событий в течение неопределенного времени, будут лучше масштабироваться, если они асинхронные. Это реже требуется на стороне клиента. Но для программ, сильно связанных с вводом / выводом, таких как веб-сканеры, вы можете начать видеть преимущество с помощью async.

Общий принцип: если вы не контролируете обе стороны сокета, одна сторона может быть сколь угодно медленной. Возможно, злонамеренно медленно. Ваша сторона должна иметь возможность эффективно обрабатывать медленные соединения.

Но как насчет подключения вашего приложения к вашей базе данных? Здесь вы контролируете обе стороны и несете ответственность за быстрое выполнение всех запросов к базе данных. Как показали тесты Майка, ваше приложение может совсем не тратить много времени на ожидание ответов базы данных. Он тестировал с Postgres, но хорошо настроенный экземпляр MongoDB так же отзывчив. С базой данных с малой задержкой ваша приоритетная скорость, а не масштабируемость вашей программы. В этом случае асинхронный ответ не является правильным, по крайней мере, в Python: небольшой пул потоков, обслуживающий соединения с низкой задержкой, обычно быстрее, чем асинхронная среда.

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

Стратегия, которую Майк, похоже, поддерживает, заключается в том, чтобы отделить асинхронный API для драйвера базы данных от асинхронной реализации для него. Например, в asyncio важно, чтобы вы могли читать данные из базы данных с помощью следующего кода:

@asyncio.coroutine
def my_query_method():
    # "yield from" unblocks the event loop while
    # waiting for the database.
    result = yield from my_db.query("query")

Но нет необходимости переопределять сам драйвер, используя неблокирующие сокеты и цикл обработки событий asyncio. Если db.queryперенести вашу операцию в пул потоков и вставить результат в цикл обработки событий основного потока, когда он будет готов, он может быть быстрее и отлично масштабироваться для небольшого количества соединений с базой данных, которые вам нужны.

Так как насчет Motor , моего асинхронного драйвера для MongoDB и Tornado? С некоторыми усилиями я написал Motor для предоставления асинхронного API для MongoDB для приложений Tornado и использования неблокирующих соединений с MongoDB с помощью цикла событий Tornado. («Мотор» использует гринлеты для облегчения последней задачи, но «гринлеты» не обсуждают эту дискуссию.) Если статья Майка Байера верна, и я верю, что Мотор — пустая трата?

С мотором я достиг двух целей. Одно было необходимо, но я пересматриваю другое. Необходимой целью было предоставить асинхронный API для приложений Tornado, которые хотят использовать MongoDB; Мотор преуспевает в этом. Но мне интересно, если бы у Motor не было немного лучшей пропускной способности, если бы он использовал пул потоков и блокирующие сокеты вместо цикла событий Tornado для связи с MongoDB. Если бы я начал заново, особенно сейчас, когда поток потоков concurrent.futures является более распространенным, я мог бы вместо этого использовать потоки. Может быть возможно получить десять или двадцать процентов на некоторых тестах, и оптимизировать будущее развитие также. Позже в этом году я надеюсь найти время, чтобы поэкспериментировать с производительностью и ремонтопригодностью этого подхода для некоторых будущих версий Motor.