2016-10-01 2 views
4

Я пытаюсь обернуть голову вокруг Tornado и асинхронных подключений к Postgresql. Я нашел библиотеку, которая может сделать это по адресу http://peewee-async.readthedocs.io/en/latest/.peewee and peewee-async: почему async slower

Я разработал небольшое испытание для сравнения традиционных 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 Benchmark:

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) 

почему синхронизация быстрее? где это узкое место, которое мне не хватает?

ответ

6

Долгое объяснение:

http://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/

За короткое объяснение: синхронный код Python является простым и в основном реализованы в модуле розеткой из стандартной библиотеки, которая является чистый код C. Асинхронный Python является более сложным, чем синхронный код. Каждый запрос требует нескольких исполнений кода цикла основного события, который написан на Python (в данном случае asyncio) и поэтому имеет множество накладных расходов по сравнению с кодом C.

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

Аргумент Майка Байера, связанный выше, заключается в том, что подобные сценарии с низкой задержкой типичны для приложений баз данных, и поэтому операции базы данных не должны выполняться в цикле событий.

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

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

Если вам не нужна асинхронизация по другой причине, не делайте асинхронные вызовы базы данных, потому что они немного медленнее.

+0

так асинхронный веб-фреймворк, как Sanic https://github.com/channelcat/sanic, может ускориться? Он использует Python3.5 + uvloop – Pegasus

3

База данных ORM представляет множество сложностей для асинхронных архитектур. В ORM есть несколько мест, где может произойти блокировка и может быть подавляющим, чтобы изменить форму асинхронизации. Места, где происходит блокирование, также могут варьироваться в зависимости от базы данных. Мое предположение о том, почему ваши результаты настолько медленны, состоит в том, что в цикл событий и из цикла событий существует много неоптимизированных вызовов (я мог бы быть сильно ошибочным, в основном я использую SQLAlchemy или raw SQL в эти дни). По моему опыту, как правило, быстрее выполнять код базы данных в потоке и давать результат, когда он доступен. Я не могу говорить на PeeWee, но SQLAlchemy хорошо подходит для работы в нескольких потоках, и не так много сторонних сторон (но те, которые существуют, очень ОЧЕНЫ раздражают).

Я бы порекомендовал вам попробовать свой эксперимент с использованием ThreadPoolExecutor и синхронного модуля Peewee и запустить функции базы данных в потоке. Вам придется внести изменения в свой основной код, однако это было бы полезно, если вы спросите меня.Например, предположим, что вы решили использовать код обратного вызова, то ваши ОРМ запросы могут выглядеть следующим образом:

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, если разработчики будут осторожны в отношении блокировок, сеансов db и транзакций. Эти факторы могут действительно замедлить ваше приложение, если что-то пойдет не так в потоке.

Если вы чувствуете себя очень предприимчивым, MagicStack (компания, занимающаяся asyncio) имеет проект под названием asyncpg, и его предполагается очень быстро! Я хотел попробовать, но не нашел времени :(

+0

I может согласиться на большинство ваших ответов, но это предложение: «MagicStack (компания, занимающаяся asyncio)» ошибочно порождает идею, по которой они несут ответственность, или авторы asyncio. Они внесли вклад в async/await, но это делает их не чем иным, как другим вкладчики, другая часть в системе. Во всяком случае, я поддержал вас, так как ваш пример полезен и может помочь другим операциям исследовать эту арену. – mydaemon