web-dev-qa-db-ja.com

トルネードのSQLAlchemyを非同期にする方法は?

SQLAlchemyTornadoasyncにする方法 async mongo example でMongoDBの例を見つけましたが、motorSQLAlchemyのようなものは見つかりませんでした。 SQLAlchemyクエリをtornado.genで実行する方法を知っている人はいますか(私はMySQLの下でSQLAlchemyを使用しています。これを非同期にしたい)。

43
Damir

ORMは、明示的な非同期プログラミング、つまり、ネットワークアクセスを使用する何かが発生するたびにプログラマが明示的なコールバックを作成する必要がある場合にはあまり適していません。これの主な理由は、ORMが lazy loading パターンを広範囲に使用することです。これは、明示的な非同期と多少互換性がありません。次のようなコード:

_user = Session.query(User).first()
print user.addresses
_

実際には2つの個別のクエリを発行します。1つは行をロードするためにfirst()と言うとき、そして_user.addresses_コレクションがまだない場合は_.addresses_と言うときです存在するか、有効期限が切れています。基本的に、ORM構造を処理するコードのほぼすべての行がIOでブロックされる可能性があるため、数秒以内に大規模なコールバックスパゲッティになります-さらに悪いことに、これらのコード行の大部分は実際にはIOでブロックするため、そうでなければ単純な属性アクセス操作のためにコールバックを接続するすべてのオーバーヘッドにより、プログラムの効率も大幅に低下します。

明示的な非同期モデルの主要な問題は、複雑なシステムに膨大なPython関数呼び出しのオーバーヘッドを追加することです。これは、遅延読み込みで得られるユーザー側だけでなく、内部側でも同様です。システムは、PythonデータベースAPI(DBAPI)の抽象化を提供します。 SQLAlchemyが基本的な非同期サポートさえ持っていると、非同期パターンを使用しないプログラムの大部分や、高度に同時実行されない非同期プログラムでさえ、重大なパフォーマンスペナルティを課します。 SQLAlchemy、または他のORMまたは抽象化レイヤーには、次のようなコードがある可能性があります。

_def execute(connection, statement):
     cursor = connection.cursor()
     cursor.execute(statement)
     results = cursor.fetchall()
     cursor.close()
     return results
_

上記のコードは、単純な操作と思われる処理を実行し、接続でSQLステートメントを実行します。ただし、psycopg2の非同期拡張のような完全に非同期のDBAPIを使用すると、上記のコードはIOで少なくとも3回ブロックします。したがって、使用中の非同期エンジンがなく、コールバックが実際にブロックされていない場合でも、明示的な非同期スタイルで上記のコードを記述することは、上記の外部関数呼び出しが、1つではなく少なくとも3つの関数呼び出しになり、課されるオーバーヘッドを含まないことを意味します明示的な非同期システムまたはDBAPI呼び出し自体によって。そのため、単純なアプリケーションには、ステートメント実行に関する単純な抽象化を囲む関数呼び出しのオーバーヘッドの3倍のペナルティが自動的に与えられます。 Pythonでは、- 関数呼び出しのオーバーヘッドがすべてです

これらの理由から、少なくとも一部の人々がWebページの配信(node.jsを参照)などのすべてについて非同期にしたい程度にまで、明示的な非同期システムを取り巻く誇大広告についてはあまり興奮していません。代わりに暗黙的な非同期システムを使用することをお勧めします。最も顕著なのは gevent で、非同期モデルのすべての非ブロッキングIOの利点が得られ、構造的な冗長性/欠点はありません。明示的なコールバック。私はこれら2つのアプローチのユースケースを理解しようとし続けているので、すべての問題の解決策として明示的な非同期アプローチの魅力に困惑しています。つまり、node.jsで見られるように-私たちはスクリプト言語を冗長性とコードの複雑さを削減する最初の場所、およびWebページの配信などの単純なものに対する明示的な非同期は、IOがブロックされている場合、geventなどによって自動化できるボイラープレートを追加すること以外は何もしませんそのような場合でも、このような問題はあります(大量のWebサイトが同期IOモデルで問題なく動作します)。 Geventベースのシステムは生産実績があり、人気が高まっているため、ORMが提供するコードオートメーションが好きな場合は、geventのようなシステムが提供する非同期IOスケジューリングオートメーションを採用することもできます。

Update:Nick Coghlanが 明示的vs暗黙的非同期のテーマに関する素晴らしい記事 を指摘しました。 。また、私は pep-3156がgeventとの相互運用性を歓迎している という事実に更新されました。これは主にNickの記事のおかげで、以前に述べられたgeventの無関心を覆します。そのため、将来、これらのアプローチを統合するシステムが利用可能になったら、データベースロジックにgeventを使用するTornadoのハイブリッドをお勧めします。

74
zzzeek

過去にも同じ問題があり、信頼できるAsync-MySQLライブラリが見つかりませんでした。ただし、 Asyncio +Postgresを使用したクールなソリューションがあります。 aiopg ライブラリを使用するだけで、すぐにSQLAlchemyがサポートされます:

import asyncio
from aiopg.sa import create_engine
import sqlalchemy as sa


metadata = sa.MetaData()

tbl = sa.Table('tbl', metadata,
           sa.Column('id', sa.Integer, primary_key=True),
           sa.Column('val', sa.String(255)))

@asyncio.coroutine
def go():
    engine = yield from create_engine(user='aiopg',
                                      database='aiopg',
                                      Host='127.0.0.1',
                                      password='passwd')

    with (yield from engine) as conn:
        yield from conn.execute(tbl.insert().values(val='abc'))

        res = yield from conn.execute(tbl.select().where(tbl.c.val=='abc'))
        for row in res:
            print(row.id, row.val)


loop = asyncio.get_event_loop()
loop.run_until_complete(go())
24
Ander

竜巻ではありませんが、sort ofGINOプロジェクト のasyncioでSQLAlchemyを非同期にしました:

import asyncio
from gino import Gino, enable_task_local
from sqlalchemy import Column, Integer, Unicode, cast

db = Gino()


class User(db.Model):
    __table= 'users'

    id = Column(Integer(), primary_key=True)
    nickname = Column(Unicode(), default='noname')


async def main():
    await db.create_pool('postgresql://localhost/gino')

    # Create object, `id` is assigned by database
    u1 = await User.create(nickname='fantix')
    print(u1.id, u1.nickname)  # 1 fantix

    # Retrieve the same row, as a different object
    u2 = await User.get(u1.id)
    print(u2.nickname)  # fantix

    # Update affects only database row and the operating object
    await u2.update(nickname='daisy')
    print(u2.nickname)  # daisy
    print(u1.nickname)  # fantix

    # Returns all user objects with "d" in their nicknames
    users = await User.query.where(User.nickname.contains('d')).gino.all()

    # Find one user object, None if not found
    user = await User.query.where(User.nickname == 'daisy').gino.first()

    # Execute complex statement and return command status
    status = await User.update.values(
        nickname='No.' + cast(User.id, Unicode),
    ).where(
        User.id > 10,
    ).gino.status()

    # Iterate over the results of a large query in a transaction as required
    async with db.transaction():
        async for u in User.query.order_by(User.id).gino.iterate():
            print(u.id, u.nickname)


loop = asyncio.get_event_loop()
enable_task_local(loop)
loop.run_until_complete(main())

少し似ていますが、実際にはかなり異なる SQLAlchemy ORMとは異なります。 SQLAlchemyコアの一部のみを使用し、その上に単純なORMを構築したためです。下で asyncpg を使用するため、PostgreSQLのみです。

更新:ウラジミールゴンチャロフの貢献のおかげで、GINOはトルネードをサポートしています。 こちらのドキュメント をご覧ください

4
Fantix King

私は次の方法でsqlalchemyで竜巻を使用しています:


from tornado_mysql import pools
from sqlalchemy.sql import table, column, select, join
from sqlalchemy.dialects import postgresql, mysql

# from models import M, M2

t = table(...)
t2 = table(...)

xxx_id = 10

j = join(t, t2, t.c.t_id == t2.c.id)
s = select([t]).select_from(j).where(t.c.xxx == xxx_id)

sql_str = s.compile(dialect=mysql.dialect(),compile_kwargs={"literal_binds": True})


pool = pools.Pool(conn_data...)
cur = yield pool.execute(sql_str)
data = cur.fetchone()

その場合、sqlalchemyモデル、およびstructalクエリにsqlalchemyツールを使用できます。

3