@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()
@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

SQLAlchemy

 

@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

30 RPS

SQLAlchemy

 

@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

SQLAlchemy

Core

Encode/

Databases

@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

170 RPS

SQLAlchemy

Core

Encode/

Databases

@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

SQLAlchemy

 

@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

170 RPS

SQLAlchemy

 

Damian Wysocki

Deep Dive into Asynchronous SQLAlchemy -

Transactions and Connections

  • Python Developer for 6 years
  • Currently im a python dev at Mirumee Software
  • Mentor at Mirumee Starter
  • Organizer of the local IT meetup MeeTTech
  • Speaker at Europython, 2023 Prague

About me

Let's jump into the code

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)

app = FastAPI()


async def get_db():
    async with async_session() as session:
        yield session


@app.get("/notes/{note_id}")
async def get_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

Implicit transaction example

Code

@app.get("/notes/{note_id}")
async def get_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

Implicit transaction example

Code

@app.get("/notes/{note_id}")
async def get_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()
                                                                                  . 

Implicit transaction example

Code

Logs

@app.get("/notes/{note_id}")
async def get_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()
DEBUG Connection checked out from pool
INFO sqlalchemy.engine.Engine BEGIN (implicit)
INFO sqlalchemy.engine.Engine SELECT (...)

Implicit transaction example

Code

Logs

@app.get("/notes/{note_id}")
async def get_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()
# exit function <-
DEBUG Connection checked out from pool
INFO sqlalchemy.engine.Engine BEGIN (implicit)
INFO sqlalchemy.engine.Engine SELECT (...)


INFO:     127.0.0.1:63630 - "GET /note?note_id=1  HTTP/1.1" 200 OK
INFO sqlalchemy.engine.Engine ROLLBACK
DEBUG Connection being returned to pool
DEBUG Connection rollback-on-return

Implicit transaction example

Code

Logs

1

Request validation

session is created

  • close transaction
  • connection is released
  • session is closed

2

request
middleware

3

dependency injection

4

request execution

5

response generation

6

response
middleware

7

response validation

8

exception

handling

9

return response

10

async

exit

stack

  • connection is aquired
  • transaction created

Simplified request lifecycle

Implicit transaction example

Possible solution

                   .

Old code

Logs

@app.get("/notes/{note_id}")
async def get_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

Possible solution

                   .

Old code

Logs

@app.get("/notes/{note_id}")
async def get_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

Possible solution

@app.get("/notes/{note_id}")
async def get_note(database=Depends(get_db)):
  async with database.begin():
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()
                   .

Possible solution

New code

Logs

@app.get("/notes/{note_id}")
async def get_note(database=Depends(get_db)):
  async with database.begin():
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()
                   .

Possible solution

Code

Logs

@app.get("/notes/{note_id}")
async def get_note(database=Depends(get_db)):
  async with database.begin():
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()
DEBUG Connection checked out from pool
INFO sqlalchemy.engine.Engine BEGIN (implicit)
INFO sqlalchemy.engine.Engine SELECT (...)

Possible solution

Code

Logs

@app.get("/notes/{note_id}")
async def get_note(database=Depends(get_db)):
  async with database.begin():
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()
    # exit context manager <-
DEBUG Connection checked out from pool
INFO sqlalchemy.engine.Engine BEGIN (implicit)
INFO sqlalchemy.engine.Engine SELECT (...)

INFO sqlalchemy.engine.Engine COMMIT
DEBUG Connection being returned to pool
DEBUG Connection rollback-on-return

Possible solution

Code

Logs

@app.get("/notes/{note_id}")
async def get_note(database=Depends(get_db)):
  async with database.begin():
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()
    # exit context manager <-
# exit function <-
DEBUG Connection checked out from pool
INFO sqlalchemy.engine.Engine BEGIN (implicit)
INFO sqlalchemy.engine.Engine SELECT (...)

INFO sqlalchemy.engine.Engine COMMIT
DEBUG Connection being returned to pool
DEBUG Connection rollback-on-return
INFO: 127.0.0.1:63630 - "GET /note?note_id=1  HTTP/1.1" 200 OK

Possible solution

Code

Logs

Compare logs

Compare logs

DEBUG Connection checked out from pool
INFO sqlalchemy.engine.Engine BEGIN (implicit)
INFO sqlalchemy.engine.Engine SELECT (...)

INFO sqlalchemy.engine.Engine COMMIT
DEBUG Connection being returned to pool
DEBUG Connection rollback-on-return
INFO: 127.0.0.1:63630 - "GET /note?note_id=1  HTTP/1.1" 200 OK

Improved solution's logs

DEBUG Connection checked out from pool
INFO sqlalchemy.engine.Engine BEGIN (implicit)
INFO sqlalchemy.engine.Engine SELECT (...)


INFO: 127.0.0.1:63630 - "GET /note?note_id=1  HTTP/1.1" 200 OK
INFO sqlalchemy.engine.Engine ROLLBACK
DEBUG Connection being returned to pool
DEBUG Connection rollback-on-return

Initial solution's logs

Compare request lifecycle

1

Request validation

session is created

  • close transaction
  • connection is released
  • session is closed

2

middleware

3

dependency injection

4

request execution

5

response geneation

6

middleware

7

response validation

8

exception

handling

9

return response

10

async

exit

stack

  • connection is aquired
  • transaction created

Initial solution

1

Request validation

session is created

  • session is closed

3

dependency injection

4

request execution

5

response geneation

6

middleware

7

response validation

8

exception

handling

9

return response

10

async

exit

stack

  • connection is aquired
  • transaction created
  • close transaction
  • connection is released

2

middleware

Possible solution

Summary

  • Transaction and connection are tightly coupled.
  • Implicit operations performed by SQLAlchemy
  • Even simple select statements initiate transactions implicitly.
  • Releasing the connection requires closing the transaction, resulting in implicit rollback or commit.

A more real life example

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

A more real life example

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

A more real life example

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

Response time benchmarks

Response time benchmarks

5 concurrent requests

pool size is equal to 5

1

2

3

4

5

6

time in seconds

Response time benchmarks

10 concurrent requests

pool size is equal to 5

1

2

3

4

5

6

7

8

9

10

time in seconds

Response time benchmarks

10 concurrent requests

pool size is equal to 5

1

2

3

4

5

6

7

8

9

10

time in seconds

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(note_id: int, database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

Solution

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

Solution

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

requests

connections pool

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

requests

time

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

connections pool

Old benchmarks

10 concurrent requests

1

2

3

4

5

6

7

8

9

10

time in seconds

Old benchmarks

10 concurrent requests

1

2

3

4

5

6

7

8

9

10

time in seconds

Benchmarks - for solution

1

2

3

4

5

6

7

8

9

10

10 concurrent requests

time in seconds

1

2

3

4

5

6

7

8

9

10

time in seconds

Benchmarks - for solution

10 concurrent requests

What do programmers love the most?

Diagrams!

Bonus

encode/databases

sqlalchemy

engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("notes/{note_id}/synchronize")
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

encode/databases

database = databases.Database(DATABASE_URL)


@app.post("notes/{note_id}/synchronize")
async def synchronize_note():
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

encode/databases

database = databases.Database(DATABASE_URL)

@app.on_event("startup")
async def startup():
    await database.connect()

@app.on_event("shutdown")
async def shutdown():
    await database.disconnect()

@app.post("notes/{note_id}/synchronize")
async def synchronize_note():
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

Benchmarks

1000 concurrent users

10 available connections

1 worker

Benchmarks

implicit session management - SQLAlchemy

engine = create_async_engine(DATABASE_URL, pool_size=10, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)

    return result.scalar_one()

20

40

60

80

100

120

140

RPS (requests per second)

160

180

200

explicit session management - SQLAlchemy

engine = create_async_engine(DATABASE_URL, pool_size=10, max_overflow=0)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@app.post("/notes/{note_id}/synchronize", response_model=Note)
async def synchronize_note(database=Depends(get_db)):
    validate_note_id(note_id)
    
    async with database.begin():
      query = sqlalchemy.select(notes).where(notes.c.id == note_id)
      result = await database.execute(query)
    
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    
    async with database.begin():
      query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
      result = await database.execute(query)
      await database.commit()

    return result.scalar_one()

20

40

60

80

100

120

140

160

180

200

Benchmarks

RPS (requests per second)

database = databases.Database(DATABASE_URL)


@app.post("notes/{note_id}/synchronize")
async def synchronize_note():
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

encode/databases

20

40

60

80

100

120

140

160

180

200

Benchmarks

RPS (requests per second)

database = databases.Database(DATABASE_URL)


@app.post("notes/{note_id}/synchronize")
async def synchronize_note():
    validate_note_id(note_id)
    query = sqlalchemy.select(notes).where(notes.c.id == note_id)
    result = await database.execute(query)
    updated_note_data = await external_api.sync_note(note_id)  # takes around 5 seconds
    query = sqlalchemy.update(notes).values(data=updated_note_data).where(notes.c.id == note_id)
    result = await database.execute(query)
    await database.commit()

    return result.scalar_one()

encode/databases

20

40

60

80

100

120

140

160

180

200

Benchmarks

RPS (requests per second)

Summary

  • explicit is better than implicit
  • release the connection when we dont need them
  • connection is passed as a reference

Thank you

Let's connect

Deep Dive into Asynchronous SQLAlchemy - Transactions and Connections

By Damian Wysocki

Deep Dive into Asynchronous SQLAlchemy - Transactions and Connections

  • 186