Как Fastapi управляет одновременными вызовами к базе данных с помощью sqlalchemy

#python #postgresql #sqlalchemy #fastapi

#python #postgresql #sqlalchemy #fastapi

Вопрос:

Я хочу понять, как база данных остается согласованной после одновременных вызовов конечной точки, которые обновляют счетчик. Извините за названия классов / методов, которые я скопировал большую часть кода из документации инструментов.

Код сервера выглядит следующим образом

 import uvicorn
from fastapi import FastAPI
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker

app = FastAPI()

engine = create_engine('postgresql://postgres:test@localhost/test')
Base = declarative_base()


class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    counter = Column(Integer)

    def __repr__(self):
        return "counter='%s'" % (self.counter)


Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)

# create
session = Session()
ed_user = User(name='ed', counter=0)
session.add(ed_user)
session.commit()
session.close()


async def updates():
    # update
    for i in range(50):
        session = Session()
        our_user = session.query(User).filter_by(name='ed').first()
        c = our_user.counter
        our_user.counter = c   1
        session.add(our_user)
        session.commit()
        session.close()


@app.get("/update")
async def update():
    await updates()
    return {"Hello": "World"}


@app.get("/counter")
async def counter():
    session = Session()
    our_user = session.query(User).filter_by(name='ed').first()
    print(our_user)


if __name__ == '__main__':
    uvicorn.run(app, host="0.0.0.0", port=8000)
 

Я запускаю его, а затем запускаю следующий скрипт:

 from threading import Thread
import requests


def info():
    r = requests.get('http://localhost:8000/counter')


def thread():
    threads = []

    for _ in range(100):
        t = Thread(target=fetch())
        t.start()
        threads.append(t)

    for t in threads:
        t.join()


def fetch():
    requests.get("http://localhost:8000/update")


thread()
info()
 

Результат счетчика в конце всегда правильный, даже при использовании конечных точек и updates() метода, отличных от asnyc. Я попытался сделать то же самое без запуска Fastapi и использовать только потоки, которые запускают updates() функцию, но в этом случае счетчик непоследователен.

Итак, по какой-то причине Fastapi творит волшебство, но что именно происходит? И разве база данных не должна также предоставлять какую-то функцию изоляции?

Редактировать

На самом деле это была ошибка в моем коде, когда я неправильно создавал потоки. Я передал результат fetch() вместо самой функции. Теперь, когда я запускаю его правильно, он ведет себя так, как ожидалось, а не асинхронная версия имеет несогласованный счетчик.

Комментарии:

1. Вы читали Concurrency и async / await в документации fastapi? Это очень хорошо объясняет, как это работает!

2. на самом деле да! Но в моем коде была ошибка, которая сделала неасинхронную версию согласованной. Целью потоков была не функция, а ее результат.

3. Вопрос заключался в том, как одновременные вызовы асинхронных и неасинхронных конечных точек приводят к одному и тому же результату, хотя в неасинхронных должны быть несоответствия из-за доступа к одному и тому же ресурсу. Но причиной такого поведения была ошибка в моем примере кода, я исправил ее, и теперь она ведет себя так, как ожидалось. Итак, вопросов больше нет.

Ответ №1:

На самом деле в моем примере кода была ошибка. Это должно быть

 def thread():
    threads = []

    for _ in range(100):
        t = Thread(target=fetch)
        t.start()
        threads.append(t)

    for t in threads:
        t.join()
 

Я передал целевому параметру результат функции, а не саму функцию. Вот почему он вел себя не так, как ожидалось, а асинхронные и неасинхронные конечные точки показали одинаковые результаты. Теперь он ведет себя так, как ожидалось, и неасинхронная конечная точка показывает противоречивые результаты из-за одновременного доступа к одному и тому же ресурсу.