ets-labs/python-dependency-injector

High CPU usage and poor performance in FastAPI with python-dependency-injector due to SQLAlchemy engine recreation

Opened this issue · 4 comments

Description

Problem

Our FastAPI application with python-dependency-injector is experiencing high CPU usage (100%+) and poor performance under load.

Example Implementation

class Container(containers.DeclarativeContainer):
    config = providers.Configuration()

    db = providers.Singleton(
        Database, 
        config.sqlalchemy_database_url,
        echo=config.postgres_echo,
        application_name=config.postgres_application_name,
        is_bouncer=config.postgres_pg_bouncer
    )
    
    my_repo = providers.Singleton(
        MyProfileRepository,
        async_session=db.provided.async_session,
    )
    
    user_service = providers.Singleton(
        UserService,
        users_repository=my_repo,
    )


@router.post('/hello', tags=['login'])
@inject
async def login(
    user_service: UserService = Depends(Provide[Container.user_service]),
):
    return await user_service.hello()


def app():
    container: Container = Container()
    container.wire(
        modules=[__name__],
        keep_cache=True
        )
    app: FastAPI = FastAPI()

Performance Issues

  • Dependency injection overhead: 400ms+ per request
  • CPU utilization: 100%+ under load

Profiling Results

Using pyinstruments profiling, we identified the bottlenecks:

  • _patched in dependency_injector/wiring.py taking 400ms+
Image

Environment

  • FastAPI 0.103+
  • python-dependency-injector 4.48.0
  • SQLAlchemy 2.0+
  • asyncpg
  • Python 3.12

Hello! That's something interesting, especially the way you set up SA. But I need more info:

  • Can you show Database and MyProfileRepository implementations? In general it would be nice to have distilled example, runnable as e.g. pyinstruments issue904.py.
  • To confirm that this is not a regression, can you test it with previous DI version (v4.47.1)?
  • It would be nice to see numbers one stack frame down from _patched.

Hello! That's something interesting, especially the way you set up SA. But I need more info:

  • Can you show Database and MyProfileRepository implementations? In general it would be nice to have distilled example, runnable as e.g. pyinstruments issue904.py.
  • To confirm that this is not a regression, can you test it with previous DI version (v4.47.1)?
  • It would be nice to see numbers one stack frame down from _patched.

Hi. Ok, on the weekend I'll build an application similar to my real project and run tests. Source code and load test results will be attached in the next comment.
UPD:
I'll add that the python-dependency-injector library in my project was upgraded to the latest version 4.48.0 2 days ago in hopes of improving application performance. Before that, the application was running with library version 4.46.0, which was the latest version in our working nexus until recently. The same performance issue was observed when we started running performance tests.

Thanks! Meanwhile, check v4.48.1. I've did some benchmarking myself, there were few spots to optimize. I've refactored this piece of code recently (v4.48.0) to add support for async generator injections, but looks like I've missed some stuff that is not so obvious and requires manual inspection of generated code.

I think your setting make a new connection for every request, not using pre made connection pool.
If Database is wrapper of Engine, check pool type. Never Use NullPool
Or check, how async_session is made. Do not use scoped_session!
Or check, db is really singleton by seeing the memory id. It could happen .