Damian Wysocki

Deep Dive into Asynchronous SQLAlchemy -

Transactions and Connections

  • Python Developer for 6 years
  • Currtnely im a python dev at mirumee software
  • Mentor at Mirumee Starter
  • organizer of the local IT meetup MeeTTech

About me

Basic terminology

Session

Transaction

Let's jump into the code

Let's check documentation

https://docs.sqlalchemy.org/en/20/orm/contextual.html#using-thread-local-scope-with-web-applications

 

TO BE REMOVED

implicit transaction example

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

database 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

implicit transaction summary

 

  • create session with dependency injection

  • connection is acquired on demand

  • transaction is created implicitly

  • connection is released after returning the response

  • rollback on return

possible solution

                   .

explicit transaction example

old code

database 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()
@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()
                   .

explicit transaction example

new code

database 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

database 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

database 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

explicit transaction example

code

database 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

explicit transaction example

code

database logs

explicit transaction summary

 

  • create session with dependency injection

  • connection is acquired on demand

  • transaction is created implicitly

  • connection is released before returning the response

  • rollback on return

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

change me

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

explicit transaction example

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.

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

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

Normal async function, right?

let's test it!

benchmarks

benchmarks

5 concurrent requests

pool size is equal to 5

1

2

3

4

5

6

time in seconds

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

implicit transactions

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

explicit transactions

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

old benchmarks

10 concurrent requests

1

2

3

4

5

6

7

8

9

10

time in seconds

benchmarks - for solution

10 concurrent requests

1

2

3

4

5

6

7

8

9

10

time in seconds

benchmarks - for solution

10 concurrent requests

1

2

3

4

5

6

7

8

9

10

time in seconds

What do programmers love the most?

diagrams!

add qrcode to 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

change me

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

benchmarks

explicit transactions - 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

RPS (requests per second)

160

180

200

benchmarks

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

RPS (requests per second)

160

180

200

benchmarks

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

RPS (requests per second)

160

180

200

summary

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

thanks you

let's connect

Code

By Damian Wysocki

Code

  • 56