2016-10-01 5 views
4

私はTornadoとPostgreSQLへの非同期接続の周りを頭で覆そうとしています。私はhttp://peewee-async.readthedocs.io/en/latest/でこれを行うことができるライブラリを見つけました。peeweeとpeewee-async:なぜasyncが遅いのですか

私は従来のPeeweeとPeewee-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) 

はなぜ速い同期のですか?ボトルネックはどこにありますか?長い説明のため

答えて

6

:同期Pythonコードは、単純な、ほとんどの標準ライブラリのソケットモジュールに実装され、純粋C.非同期Pythonコードがより複雑である:簡単な説明について

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

同期コード。各リクエストは、メインイベントループコードのいくつかの実行を必要とします。これはPythonで書かれています(ここではasyncioのケース)ので、Cコードに比べてオーバーヘッドが大きくなります。

あなたのようなベンチマークでは、アプリケーションとデータベースの間にネットワークの待ち時間がなく、非常に小さなデータベース操作が多数行われているため、非同期のオーバーヘッドが劇的に表示されます。 他ののベンチマークは高速であるため、イベントループロジックのこれらの多くの実行は、合計ランタイムの大部分を追加します。

上記のMike Bayerの主張は、このような低レイテンシのシナリオはデータベースアプリケーションでは一般的なものであり、イベントループではデータベース操作を実行しないことです。

Asyncは、大部分の時間をPythonで実行するのではなく、アプリケーションがピアを待っている時間を費やすWebソケットやWebクローラのような高レイテンシのシナリオに最適です。

結論:アプリケーションに非同期(低速ピアを扱う)が適切な理由がある場合、非同期データベースドライバを使用することは、一貫したコードのためには良い考えですが、オーバーヘッドが予想されます。

別の理由で非同期が必要ない場合は、非同期データベース呼び出しをしないでください。これは少し遅いためです。

+0

Sanicのhttps://github.com/channelcat/sanicのようなasync Webフレームワークは高速化できますか?Python3.5 + uvloopを使用します – Pegasus

3

データベースORMは、非同期アーキテクチャに多くの複雑さをもたらします。 ORMには、ブロッキングが発生する可能性のある場所がいくつかあり、非同期形式に変更するのが圧倒的に可能性があります。ブロッキングが発生する場所は、データベースによって異なる場合もあります。私の推測では、結果が非​​常に遅いのは、イベント・ループとの間で呼び出しが最適化されていないことが多いためです(私はひどく間違っているかもしれませんが、主にSQLAlchemyまたは未加工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を使用することができますが、それは私のために少し問題がありました。また、私はまだコルーチンに精通していません。デベロッパーがデッドロック、dbセッション、トランザクションに注意する限り、このスニペットはTornadoとうまく動作するはずです。これらの要因は、スレッドで何か問題が生じた場合、アプリケーションを実際に遅くする可能性があります。

あなたが非常に冒険的な気がするなら、MagicStack(asyncioの後ろにある会社)にはasyncpgというプロジェクトがあり、非常に速いと思われます!私は試してみる意味がありますが、時間を見つけられませんでした:(

+0

"MagicStack(asyncioの後ろにある会社)"は、彼らが責任を負っているという考えやasyncioの作者を間違って誘導します。彼らはasync/awaitに貢献しましたが、それは別のものにほかなりません寄稿者、システム内の別の部分 私はあなたの例が有用であり、他のオペレーションがその分野で研究するのを助けることができるので、私はあなたをアップアップしました。 – mydaemon

関連する問題