Jak zrobić SQLAlchemy w Tornado, aby był asynchroniczny?

Jak sprawić, by SQLAlchemy W Tornado było async ? Znalazłem przykład dla MongoDB na async Mongo example , ale nie mogłem znaleźć czegoś takiego jak motor dla SQLAlchemy. Czy ktoś wie jak zrobić SQLAlchemy zapytania do wykonania z tornado.gen (używam MySQL poniżej SQLAlchemy, w tej chwili moje programy obsługi czytają z bazy danych i zwracają wynik, chciałbym zrobić to asynchroniczne).

Author: Damir, 2013-05-11

4 answers

ORM są słabo przystosowane do jawnego programowania asynchronicznego, czyli tam, gdzie programista musi wywoływać jawne wywołania zwrotne za każdym razem, gdy wystąpi coś, co korzysta z dostępu do sieci. Głównym powodem tego jest to, że ORMs szeroko wykorzystuje wzorzec lazy loading , który jest mniej lub bardziej niezgodny z jawnym asynchronizacją. Kod który wygląda tak:

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

Będzie faktycznie emitować dwa oddzielne zapytania - jedno, gdy powiesz first(), aby załadować wiersz, a następne, gdy powiesz user.addresses, w przypadek, że kolekcja .addresses nie jest już obecna lub wygasła. Zasadniczo prawie każda linia kodu, która zajmuje się konstrukcjami ORM, może blokować na IO, więc będziesz w rozległym spaghetti oddzwaniania w ciągu kilku sekund - i co gorsza, zdecydowana większość tych linii kodu nie będzie faktycznie blokować na IO, więc wszystkie koszty połączenia wywołań zwrotnych razem dla tego, co w przeciwnym razie byłoby prostymi operacjami dostępu do atrybutów, sprawią, że twój program będzie znacznie mniej wydajny też.

Głównym problemem jawnych modeli asynchronicznych jest to, że dodają one ogromne koszty wywołania funkcji Pythona do złożonych systemów - nie tylko po Stronie Użytkownika, jak w przypadku leniwego ładowania, ale także po stronie wewnętrznej, jeśli chodzi o sposób, w jaki system zapewnia abstrakcję wokół API bazy danych Pythona (DBAPI). Jeśli SQLAlchemy ma nawet podstawową obsługę asynchroniczną, nałoży to poważną karę za wydajność na zdecydowaną większość programów, które nie używają wzorców asynchronicznych, a nawet te programy asynchroniczne, które nie są wysoce równoległe. SQLAlchemy, lub jakakolwiek inna warstwa ORM lub abstrakcji, może mieć następujący kod:

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

Powyższy kod wykonuje coś, co wydaje się być prostą operacją, wykonując polecenie SQL na połączeniu. Ale używając w pełni asynchronicznego DBAPI, takiego jak rozszerzenie async psycopg2, powyższy kod blokuje się na IO co najmniej trzy razy. Tak więc, aby napisać powyższy kod w jawnym stylu asynchronicznym, nawet jeśli nie ma silnika asynchronicznego w użyciu i wywołania zwrotne w rzeczywistości nie blokują, oznacza to, że powyższe zewnętrzne wywołanie funkcji staje się co najmniej trzema wywołaniami funkcji, zamiast jednego, nie wliczając narzutu narzuconego przez jawny system asynchroniczny lub same wywołania DBAPI. Tak więc prosta aplikacja otrzymuje automatycznie karę 3x narzutu wywołania funkcji otaczającego prostą abstrakcję wokół wykonania instrukcji. A w Pythonie, wywołanie funkcji jest wszystkim .

Z tych powodów, nadal jestem mniej niż podekscytowany szumem otaczającym jawne systemy asynchroniczne, przynajmniej do tego stopnia, że niektórzy ludzie wydają się chcieć przejść wszystkie asynchroniczne dla wszystkiego, jak dostarczanie stron internetowych (patrz węzeł.js). Polecam zamiast tego użycie systemów asynchronicznych, w szczególności gevent , gdzie otrzymujesz wszystkie nieblokujące zalety IO modelu asynchronicznego i brak strukturalnej zwięzłości/wad jawnych wywołań zwrotnych. Nadal staram się zrozumieć przypadki użycia dla tych dwóch podejść, więc jestem zaskoczony atrakcyjność jawnego podejścia asynchronicznego jako rozwiązania wszystkich problemów, tj. jak widać z node.js - używamy języków skryptowych w pierwszej kolejności, aby zmniejszyć szczegółowość i złożoność kodu, a Jawna asynchronizacja dla prostych rzeczy, takich jak dostarczanie stron internetowych, wydaje się nie robić nic, ale dodać boilerplate, który może być równie dobrze zautomatyzowany przez gevent lub podobne, jeśli blokowanie IO jest nawet takim problemem w takim przypadku (wiele stron o dużej objętości radzi sobie dobrze z synchronicznym modelem IO). Systemy oparte na Gevent są sprawdzone pod względem produkcji, a ich popularność rośnie, więc jeśli podoba ci się automatyzacja kodu zapewniana przez ORMs, możesz również skorzystać z automatyzacji async-IO-scheduling, którą zapewnia system taki jak gevent.

Update : Nick Coghlan wskazał na swójświetny artykuł na temat jawnego vs. niejawnego asynchronicznego , który również należy przeczytać tutaj. / Align = "center" bgcolor = "# e0ffe0 " / cesarz Chin / / align = center / gevent , odwracając wcześniej deklarowany brak zainteresowania geventem, w dużej mierze dzięki artykułowi Nicka. Tak więc w przyszłości zalecałbym hybrydę Tornado za pomocą gevent dla logiki bazy danych, gdy system integracji tych podejść jest dostępny.

 71
Author: zzzeek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:33:17

Miałem ten sam problem w przeszłości i nie mogłem znaleźć niezawodnej Biblioteki Async-MySQL. Jednak jest fajne rozwiązanie za pomocą Asyncio + Postgres . Wystarczy użyć aiopg biblioteka, która jest dostarczana z obsługą SQLAlchemy po wyjęciu z pudełka:

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
Author: Ander,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-30 13:21:26

Używam tornado z sqlalchemy w następny sposób:


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

W takim przypadku możemy używać modeli sqlalchemy i narzędzi SQLAlchemy do zapytań constructig.

 2
Author: Mykola Kharechko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-18 14:32:13

Nie tornado, ale tak jakby zrobiliśmy SQLAlchemy async w asyncio w GINO project :

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

Wygląda trochę jak, ale w rzeczywistości całkiem inaczej niż SQLAlchemy ORM. Ponieważ użyliśmy tylko części rdzenia SQLAlchemy i zbudowaliśmy na nim prosty ORM. Używa asyncpg pod spodem, więc jest Tylko dla PostgreSQL .

Aktualizacja: GINO wspiera Tornado teraz, dzięki wkładowi Vladimira Gonczarowa. Zobacz docs tutaj

 2
Author: Fantix King,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-03 06:34:03