Saltar al contenido

¿Cómo hacer que SQLAlchemy en Tornado sea asíncrono?

Este equipo redactor ha estado por horas buscando la resolución a tu pregunta, te dejamos la soluciones de modo que deseamos resultarte de mucha ayuda.

Solución:

Los ORM no son adecuados para la programación asincrónica explícita, es decir, donde el programador debe producir devoluciones de llamada explícitas cada vez que ocurre algo que utiliza el acceso a la red. Una razón principal de esto es que los ORM hacen un uso extensivo del patrón de carga diferida, que es más o menos incompatible con la asíncrona explícita. Código que tiene este aspecto:

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

emitirá dos consultas independientes, una cuando diga first() para cargar una fila, y la siguiente cuando dices user.addresses, en el caso de que el .addresses la colección aún no está presente o ha caducado. Esencialmente, casi todas las líneas de código que se ocupan de las construcciones ORM podrían bloquearse en IO, por lo que estaría en un extenso espagueti de devolución de llamada en segundos y, para empeorar las cosas, la gran mayoría de esas líneas de código no lo harán. Realmente bloquear en IO, por lo que toda la sobrecarga de conectar devoluciones de llamada juntas para lo que de otra manera sería simple attribute Las operaciones de acceso también harán que su programa sea mucho menos eficiente.

Un problema importante con los modelos asincrónicos explícitos es que agregan una enorme sobrecarga de llamadas a funciones de Python a sistemas complejos, no solo en el lado que mira al usuario como ocurre con la carga diferida, sino también en el lado interno con respecto a cómo el sistema proporciona abstracción alrededor del API de base de datos de Python (DBAPI). Para que SQLAlchemy incluso tenga soporte asíncrono básico, impondría una grave penalización de rendimiento en la gran mayoría de los programas que no usan patrones asíncronos, e incluso en aquellos programas asíncronos que no son muy concurrentes. Considere SQLAlchemy, o cualquier otro ORM o capa de abstracción, podría tener un código como el siguiente:

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

El código anterior realiza lo que parece ser una operación simple, ejecutando una declaración SQL en una conexión. Pero usando una DBAPI totalmente asíncrona como la extensión asíncrona de psycopg2, el código anterior se bloquea en IO al menos tres veces. Entonces, para escribir el código anterior en estilo asíncrono explícito, incluso cuando no hay un motor asíncrono en uso y las devoluciones de llamada no están realmente bloqueando, significa que la llamada de función externa anterior se convierte en al menos tres llamadas de función, en lugar de una, sin incluir la sobrecarga impuesta. por el sistema asincrónico explícito o las llamadas DBAPI a sí mismas. Por lo tanto, una aplicación simple recibe automáticamente una penalización de 3 veces la sobrecarga de la llamada a la función que rodea una abstracción simple en torno a la ejecución de la declaración. Y en Python, la sobrecarga de llamadas a funciones lo es todo.

Por estas razones, sigo menos que entusiasmado con la exageración que rodea a los sistemas asíncronos explícitos, al menos en la medida en que algunas personas parecen querer ir totalmente asíncronas para todo, como la entrega de páginas web (consulte node.js). En su lugar, recomendaría usar sistemas asíncronos implícitos, sobre todo gevent, donde obtiene todos los beneficios de IO sin bloqueo de un modelo asincrónico y ninguna de las verbosidades / desventajas estructurales de las devoluciones de llamada explícitas. Sigo intentando comprender los casos de uso de estos dos enfoques, por lo que me desconcierta el atractivo del enfoque asincrónico explícito como solución a todos los problemas, es decir, como ve con node.js: estamos usando lenguajes de scripting en el el primer lugar para reducir la verbosidad y la complejidad del código, y la asíncrona explícita para cosas simples como la entrega de páginas web parece no hacer nada más que agregar un texto estándar que también puede ser automatizado por gevent o similar, si el bloqueo de IO es incluso un problema en un caso como ese (muchos sitios web de alto volumen funcionan bien con un modelo IO síncrono). Los sistemas basados ​​en Gevent están probados en producción y su popularidad está creciendo, por lo que si le gusta la automatización de código que brindan los ORM, es posible que también desee adoptar la automatización de programación de E / S asíncrona que proporciona un sistema como gevent.

Actualizar: Nick Coghlan señaló su gran artículo sobre el tema de la asincrónica explícita frente a la implícita, que también es una lectura obligada aquí. Y también me han actualizado sobre el hecho de que pep-3156 ahora da la bienvenida a la interoperabilidad con gevent, revirtiendo su desinterés previamente declarado en gevent, en gran parte gracias al artículo de Nick. Entonces, en el futuro, recomendaría un híbrido de Tornado usando gevent para la lógica de la base de datos, una vez que el sistema de integración de estos enfoques esté disponible.

Tuve este mismo problema en el pasado y no pude encontrar una biblioteca Async-MySQL confiable. Sin embargo, hay una solución genial usando Asyncio + Postgres. Solo necesitas usar el aiopg biblioteca, que viene con soporte SQLAlchemy listo para usar:

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

async def create_table(engine):
    async with engine.acquire() as conn:
        await conn.execute('DROP TABLE IF EXISTS tbl')
        await conn.execute('''CREATE TABLE tbl (
                                  id serial PRIMARY KEY,
                                  val varchar(255))''')

async def go():
    async with create_engine(user='aiopg',
                             database='aiopg',
                             host='127.0.0.1',
                             password='passwd') as engine:

        async with engine.acquire() as conn:
            await conn.execute(tbl.insert().values(val='abc'))

            async for row in conn.execute(tbl.select()):
                print(row.id, row.val)

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

Actualizado como lo menciona @cglacet

No tornado, pero nosotros algo así como hizo SQLAlchemy async en asyncio en el proyecto 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())

Se parece un poco, pero en realidad bastante diferente que SQLAlchemy ORM. Porque usamos solo una parte del núcleo de SQLAlchemy y construimos un ORM simple sobre él. Utiliza asyncpg debajo, por lo que es solo para PostgreSQL.

Actualizar: GINO apoya Tornado ahora, gracias a la contribución de Vladimir Goncharov. Ver documentos aquí

Comentarios y calificaciones

Si haces scroll puedes encontrar las interpretaciones de otros sys admins, tú todavía tienes la habilidad dejar el tuyo si te apetece.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *