Comment faire pour que SQLAlchemy en Tornado soit async?

comment faire SQLAlchemy en Tornado pour être async ? J'ai trouvé exemple pour MongoDB sur async mongo exemple mais je ne pouvais pas trouver quelque chose comme motor pour SQLAlchemy . Est-ce que quelqu'un sait comment faire des requêtes SQLAlchemy à exécuter avec tornado.gen ( j'utilise MySQL en dessous de SQLAlchemy , au moment où mes gestionnaires lisent dans la base de données et renvoient le résultat, je voudrais faire ceci async).

41
demandé sur Damir 2013-05-11 02:17:09

4 réponses

Formulaires sont mal adaptés pour une programmation asynchrone, c'est là où le programmeur doit produire explicite rappels à tout moment quelque chose qui utilise le réseau d'accès se produit. Une des principales raisons en est que les ORMs font largement usage du modèle lazy loading , qui est plus ou moins incompatible avec async explicite. Code qui ressemble à ceci:

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

émettra en fait deux requêtes séparées - une quand vous dites first() pour charger une rangée, et la suivante quand vous dites user.addresses , dans le cas où la collection .addresses n'est pas déjà présente, ou a été expiré. Essentiellement, presque chaque ligne de code qui traite avec ORM constructions pourrait bloquer sur IO, de sorte que vous seriez dans spaghetti callback vaste en quelques secondes - et pour rendre les choses plus graves, la grande majorité de ces lignes de code ne sera pas en fait bloc sur IO, de sorte que tous les frais généraux de connexion des callbacks ensemble pour ce que sinon, des opérations d'accès aux attributs simples rendront votre programme beaucoup moins efficace aussi.

un problème majeur avec les modèles asynchrones explicites est qu'ils ajoutent énormément de fonction Python appel overhead à des systèmes complexes - pas seulement sur le côté face à l'utilisateur comme vous obtenez avec chargement paresseux, mais sur le côté interne ainsi que sur la façon dont le système fournit l'abstraction autour de L'API de base de données Python (DBAPI). Pour SQLAlchemy même avoir le soutien async de base serait imposer une sévère pénalité de performance à la grande majorité des programmes qui n'utilisent pas les modèles asynchrones, et même ceux qui ne sont pas très concurrents. Considérons que SQLAlchemy, ou toute autre couche d'ORM ou d'abstraction, pourrait avoir le code suivant:

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

le code ci-dessus exécute ce qui semble être une opération simple, en exécutant une instruction SQL sur une connexion. Mais en utilisant un DBAPI entièrement async comme l'extension async de psycopg2, le code ci-dessus bloque IO au moins trois fois. Donc, écrire le code ci-dessus dans un style asynchrone explicite, même s'il n'y a pas de moteur asynchrone en usage et que les callbacks ne sont pas réellement bloquants, signifie que l'appel de fonction externe ci-dessus devient au moins trois appels de fonction, au lieu d'un, sans inclure le overhead imposé par le système asynchrone explicite ou les appels DBAPI eux-mêmes. Ainsi une application simple est automatiquement donnée une pénalité de 3x l'appel de fonction overhead entourant une abstraction simple autour de la déclaration exécution. Et en Python, fonction Appel overhead est tout .

pour ces raisons, je continue à être moins qu'excitée par le battage médiatique entourant les systèmes asynchrones explicites, au moins au point que certains semblent vouloir aller tout asynchrones pour tout, comme la livraison de pages web (voir noeud.js). Je recommande d'utiliser des systèmes asynchrones implicites à la place, notamment gevent , où vous obtenez tous les avantages IO non-bloquants d'un modèle asynchrone et aucune des verbosités structurelles/inconvénients des rappels explicites. Je continue à essayer de comprendre les cas d'utilisation pour ces deux approches, donc je suis perplexe par l'appel de l'approche asynchrone explicite comme une solution à tous les problèmes, i.e. comme vous voyez avec le noeud.js-nous utilisons des langages de script en premier lieu pour réduire la verbosité et la complexité du code, et async explicite pour des choses simples comme la livraison de pages web semble ne rien faire mais ajouter boilerplate qui peut aussi bien être automatisé par gevent ou similaire, si le blocage IO est même un tel problème dans un cas comme celui-ci (beaucoup de sites à haut volume font très bien avec un modèle io synchrone). Les systèmes basés sur Gevent ont fait leurs preuves et leur popularité est en croissance, donc si vous aimez l'automatisation de code que les ORMs fournissent, vous pourriez aussi vouloir embrasser l'automatisation async-IO-scheduling que fournit un système comme gevent.

mise à Jour : Nick Coghlan a souligné son grand article sur le sujet de l'async explicite vs implicite qui est également un must lire ici. Et j'ai aussi été mis au courant du fait que pep-3156 accueille maintenant l'interopérabilité avec gevent , inversant son désintérêt pour gevent, en grande partie grâce à L'article de Nick. Donc, à l'avenir, je recommanderais un hybride de Tornado en utilisant gevent pour la logique de la base de données, une fois que le système d'intégration de ces approches sera disponible.

71
répondu zzzeek 2017-05-23 11:33:17

j'ai eu ce même problème dans le passé et je ne pouvais pas trouver une bibliothèque Async-MySQL fiable. Cependant il y a une solution fraîche en utilisant Asyncio + Postgres . Vous avez juste besoin d'utiliser la aiopg bibliothèque, qui vient avec le soutien de SQLAlchemy de la boîte:

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())
23
répondu Ander 2014-11-30 13:21:26

j'utilise tornado avec sqlalchemy de la façon suivante:


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()

dans ce cas, nous sommes en mesure d'utiliser des modèles sqlalchemy, et des outils sqlalchemy pour les requêtes constructig.

2
répondu Mykola Kharechko 2016-05-18 14:32:13

pas tornade, mais nous sorte de fait SQLAlchemy async à asyncio dans le projet GINO :

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

db = Gino()


class User(db.Model):
    __tablename__ = '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())

il ressemble un peu, mais en fait tout à fait différent que SQLAlchemy ORM. Parce que nous avons utilisé seulement une partie du noyau de SQLAlchemy, et construit un ORM simple sur le dessus de celui-ci. Il utilise asyncpg en dessous, donc il est pour PostgreSQL seulement .

Update : Gino supporte Tornado maintenant, grâce à la contribution de Vladimir Gontcharov. Voir docs ici

2
répondu Fantix King 2017-09-03 06:34:03