#python #postgresql #tornado #python-asyncio #peewee
#python #postgresql #торнадо #python-asyncio #peewee
Вопрос:
Я пытаюсь разобраться в Tornado и асинхронных подключениях к Postgresql. Я нашел библиотеку, которая может это сделать в http://peewee-async.readthedocs.io/en/latest /.
Я разработал небольшой тест для сравнения традиционного Peewee и Peewee-async, но почему-то async работает медленнее.
Это мое приложение:
import peewee
import tornado.web
import logging
import asyncio
import peewee_async
import tornado.gen
import tornado.httpclient
from tornado.platform.asyncio import AsyncIOMainLoop
AsyncIOMainLoop().install()
app = tornado.web.Application(debug=True)
app.listen(port=8888)
# ===========
# Defining Async model
async_db = peewee_async.PooledPostgresqlDatabase(
'reminderbot',
user='reminderbot',
password='reminderbot',
host='localhost'
)
app.objects = peewee_async.Manager(async_db)
class AsyncHuman(peewee.Model):
first_name = peewee.CharField()
messenger_id = peewee.CharField()
class Meta:
database = async_db
db_table = 'chats_human'
# ==========
# Defining Sync model
sync_db = peewee.PostgresqlDatabase(
'reminderbot',
user='reminderbot',
password='reminderbot',
host='localhost'
)
class SyncHuman(peewee.Model):
first_name = peewee.CharField()
messenger_id = peewee.CharField()
class Meta:
database = sync_db
db_table = 'chats_human'
# defining two handlers - async and sync
class AsyncHandler(tornado.web.RequestHandler):
async def get(self):
"""
An asynchronous way to create an object and return its ID
"""
obj = await self.application.objects.create(
AsyncHuman, messenger_id='12345')
self.write(
{'id': obj.id,
'messenger_id': obj.messenger_id}
)
class SyncHandler(tornado.web.RequestHandler):
def get(self):
"""
An traditional synchronous way
"""
obj = SyncHuman.create(messenger_id='12345')
self.write({
'id': obj.id,
'messenger_id': obj.messenger_id
})
app.add_handlers('', [
(r"/receive_async", AsyncHandler),
(r"/receive_sync", SyncHandler),
])
# Run loop
loop = asyncio.get_event_loop()
try:
loop.run_forever()
except KeyboardInterrupt:
print(" server stopped")
и это то, что я получаю из теста Apache:
ab -n 100 -c 100 http://127.0.0.1:8888/receive_async
Connection Times (ms)
min mean[ /-sd] median max
Connect: 2 4 1.5 5 7
Processing: 621 1049 256.6 1054 1486
Waiting: 621 1048 256.6 1053 1485
Total: 628 1053 255.3 1058 1492
Percentage of the requests served within a certain time (ms)
50% 1058
66% 1196
75% 1274
80% 1324
90% 1409
95% 1452
98% 1485
99% 1492
100% 1492 (longest request)
ab -n 100 -c 100 http://127.0.0.1:8888/receive_sync
Connection Times (ms)
min mean[ /-sd] median max
Connect: 2 5 1.9 5 8
Processing: 8 476 277.7 479 1052
Waiting: 7 476 277.7 478 1052
Total: 15 481 276.2 483 1060
Percentage of the requests served within a certain time (ms)
50% 483
66% 629
75% 714
80% 759
90% 853
95% 899
98% 1051
99% 1060
100% 1060 (longest request)
почему синхронизация быстрее? где узкое место, которого мне не хватает?
Ответ №1:
Для длинного объяснения:
http://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/
Для краткого объяснения: синхронный код Python прост и в основном реализован в модуле сокета стандартной библиотеки, который является чистым C. Асинхронный код Python более сложный, чем синхронный код. Каждый запрос требует нескольких выполнений основного кода цикла событий, который написан на Python (в данном asyncio
случае) и, следовательно, имеет много накладных расходов по сравнению с кодом C.
Тесты, подобные вашему, резко показывают накладные расходы на асинхронность, потому что между вашим приложением и вашей базой данных нет задержек в сети, и вы выполняете большое количество очень маленьких операций с базой данных. Поскольку все остальные аспекты теста выполняются быстро, эти многочисленные выполнения логики цикла событий увеличивают значительную долю общего времени выполнения.
Аргумент Майка Байера, приведенный выше, заключается в том, что подобные сценарии с низкой задержкой типичны для приложений баз данных, и поэтому операции с базой данных не должны выполняться в цикле событий.
Асинхронность лучше всего подходит для сценариев с высокой задержкой, таких как websockets и веб-сканеры, где приложение проводит большую часть своего времени в ожидании однорангового узла, а не тратит большую часть своего времени на выполнение Python.
В заключение: если у вашего приложения есть веская причина быть асинхронным (оно имеет дело с медленными одноранговыми узлами), наличие драйвера асинхронной базы данных является хорошей идеей для обеспечения согласованности кода, но ожидайте некоторых накладных расходов.
Если вам не нужна асинхронность по другой причине, не выполняйте асинхронные вызовы базы данных, потому что они немного медленнее.
Комментарии:
1. итак, асинхронный веб-фреймворк, такой как Sanic github.com/channelcat/sanic может ускорить? Он использует Python3.5 uvloop
Ответ №2:
ORM базы данных создают много сложностей для асинхронных архитектур. В ORM есть несколько мест, где может иметь место блокировка, и переход на асинхронную форму может быть затруднительным. Места, где происходит блокировка, также могут различаться в зависимости от базы данных. Я предполагаю, почему ваши результаты такие медленные, потому что существует много неоптимизированных вызовов в цикл событий и из него (я могу сильно ошибаться, в наши дни я в основном использую SQLAlchemy или raw SQL). По моему опыту, обычно быстрее выполнять код базы данных в потоке и выдавать результат, когда он доступен. Я не могу говорить за PeeWee, но SQLAlchemy хорошо подходит для работы в нескольких потоках, и у него не так много недостатков (но те, которые существуют, очень раздражают).
Я бы рекомендовал вам попробовать свой эксперимент с использованием ThreadPoolExecutor и синхронного модуля Peewee и запускать функции базы данных в потоке. Вам придется внести изменения в свой основной код, однако это того стоило бы, если вы спросите меня. Например, допустим, вы решили использовать код обратного вызова, тогда ваши запросы ORM могут выглядеть следующим образом:
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(max_workers=10)
def queryByName(name):
query = executor.submit(db_model.findOne, name=name)
query.add_done_callback(processResult)
def processResult(query):
orm_obj = query.results()
# do stuff with the results
Вы могли бы использовать yeild from
или await
в сопрограммах, но для меня это было немного проблематично. Кроме того, я еще не очень хорошо разбираюсь в сопрограммах. Этот фрагмент должен хорошо работать с Tornado, если разработчики будут осторожны с взаимоблокировками, сеансами БД и транзакциями. Эти факторы могут действительно замедлить работу вашего приложения, если что-то пойдет не так в потоке.
Если вы чувствуете себя очень предприимчивым, у MagicStack (компании, стоящей за asyncio) есть проект под названием asyncpg
, и он должен быть очень быстрым! Я хотел попробовать, но не нашел времени:(
Комментарии:
1. Я могу согласиться с большей частью вашего ответа, но это предложение: «MagicStack (компания, стоящая за asyncio)» ошибочно наводит на мысль, что они несут ответственность или авторы asyncio. Они внесли свой вклад в async / await, но это делает их ничем иным, как еще одним участником, еще одной частью системы. В любом случае, я поддержал вас, поскольку ваш пример полезен и может помочь другим операторам исследовать эту область.