[Python] My endpoints through locust are running very slowly

Discussão em 'Python' iniciado por Stack, Maio 13, 2025.

  1. Stack

    Stack Membro Participativo

    I use FastAPI, MySQL and asynchronous approach. The problem is that with a heavy load, for example, if there are 100 users through locust, I make a ramp for 5 users, then requests can take 20-40 seconds to complete.

    I tried to make connections with the database for each core, it only made it worse. I am running the application and testing on my machine and on a remote server and the problem was both there and there.

    Who has encountered such a thing, how did you solve the problem? GPT didn't help me. I will provide the code for my database class and the endpoint that is slowing down (I have also set up indexes). (the server and the machine have 4 cores and 16GB ram And it's 60% loaded.)

    Base = declarative_base()

    class Database:
    def __init__(self):
    self.test_mode = os.getenv("TEST_MODE", "false").lower() == "true"
    self.db_name = "test" if self.test_mode else os.getenv('DB_NAME')
    # URL for connecting to an existing database
    self.SQLALCHEMY_DATABASE_URL = (
    f"mysql+aiomysql://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}@"
    f"{os.getenv('DB_HOST')}:3306/{self.db_name}?charset=utf8mb4"
    )
    logger.info(f"DATABASE_URL {self.SQLALCHEMY_DATABASE_URL}")
    # Creating a single asynchronous engine
    self.engine = create_async_engine(
    self.SQLALCHEMY_DATABASE_URL,
    **database_settings.DB_CONFIG,
    execution_options={"isolation_level": "READ COMMITTED"} # Important for high-load systems
    )

    # Creating a session factory
    self.session_factory = sessionmaker(
    autocommit=False,
    autoflush=False,
    bind=self.engine,
    class_=AsyncSession,
    expire_on_commit=False
    )

    async def initialize_database(self):
    """
    Initializing the database and creating tables
    """
    try:
    # Create a database if it does not exist
    init_engine = create_async_engine(
    f"mysql+aiomysql://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}@"
    f"{os.getenv('DB_HOST')}:3306?charset=utf8mb4",
    **{k: v for k, v in database_settings.DB_CONFIG.items() if k != 'pool_size'}
    )

    async with init_engine.begin() as conn:
    await conn.execute(text(f"CREATE DATABASE IF NOT EXISTS {self.db_name} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"))
    await conn.execute(text(f"SET SESSION sql_mode = (SELECT REPLACE(@@SESSION.sql_mode, 'ONLY_FULL_GROUP_BY', ''));"))

    logger.info("DATABASE SCHEMA CREATED OR EXISTS")

    # Creating tables using the engine
    async with self.engine.begin() as connection:
    def sync_inspect(conn):
    inspector = inspect(conn)
    return inspector.get_table_names()

    existing_tables = await connection.run_sync(sync_inspect)
    logger.info(f"Existing tables: {existing_tables}")

    if not existing_tables:
    await connection.run_sync(Base.metadata.create_all)
    logger.info("Tables created successfully")
    else:
    logger.info("Tables already exist")

    # Checking the availability of all necessary tables
    required_tables = {
    'users', 'auth', 'history', 'websites', 'user_files', 'messages',
    'assistants', 'assistant_usage_history', 'assistant_tags',
    'messages_websites', 'tags', 'user_assistant_settings'
    }
    missing_tables = required_tables - set(existing_tables)

    if missing_tables:
    logger.info(f"Creating missing tables: {missing_tables}")
    await connection.run_sync(Base.metadata.create_all)

    # Connection test
    async with self.engine.connect() as conn:
    result = await conn.execute(text("SELECT 1"))
    result.fetchone()

    except Exception as e:
    logger.error(f"Database initialization error: {str(e)}")
    raise

    @contextlib.asynccontextmanager
    async def get_db_session(self):
    """
    Context manager for working with a database session.
    Automatically selects a connection based on the current thread.
    This ensures that each thread uses its own dedicated connection,
    preventing "Packet sequence number wrong" errors.
    """
    session = self.session_factory()
    try:
    yield session
    await session.commit()
    except HTTPException:
    raise
    except Exception as e:
    await session.rollback()
    logger.error(f"Database error occurred: {str(e)}")
    if "Packet sequence number wrong" in str(e):
    logger.critical("Detected packet sequence error. Consider recycling the connection pool.")
    raise HTTPException(
    status_code=500,
    detail="Database error occurred"
    )
    finally:
    await session.close()


    @router.post(
    "/create_share_link/{history_url}", response_model=schemas.ShareUrlResponse
    )
    async def share_history(
    history_url: str,
    request: Request,
    user_id: int = Depends(get_current_user),
    session: AsyncSession = Depends(get_db),
    ) -> schemas.ShareUrlResponse:
    """
    Opening access to the chat history
    """
    try:
    if not re.match(r"^[a-z0-9\-]{1,35}\-[a-f0-9]{22}$", history_url):
    raise HTTPException(status_code=400, detail="Invalid history URL format")

    # Checking the ownership of the history
    history_query = (
    select(History)
    .join(Messages, History.id == Messages.history_id)
    .where(and_(History.url == history_url, Messages.user_id == user_id))
    .limit(1)
    )

    history_result = await session.execute(history_query)
    history = history_result.scalar_one_or_none()

    if not history:
    raise HTTPException(
    status_code=404,
    detail="The history was not found or does not belong to you",
    )

    if not history.is_shared:
    history.is_shared = True
    await session.commit()

    # Creating a link for public access
    share_url = f"/history/{history_url}"

    return schemas.ShareUrlResponse(share_url=share_url)

    except SQLAlchemyError as e:
    logger.error(f"Database error in share_history: {str(e)}")
    await session.rollback()
    raise HTTPException(status_code=500, detail="Database error occurred")
    except HTTPException:
    # Re-raise HTTP exceptions
    raise
    except Exception as e:
    logger.error(f"Unexpected error in share_history: {str(e)}")
    await session.rollback()
    raise HTTPException(status_code=500, detail="An unexpected error occurred")


    async def get_db():
    async with database.get_db_session() as session:
    try:
    yield session
    except HTTPException as e:
    raise e
    except Exception as e:
    logger.error(f"Database session error: {e}")
    raise

    DB_CONFIG: dict = {
    "pool_size": 20,
    "max_overflow": 30,
    "pool_timeout": 10,
    "pool_recycle": 1800,
    "pool_pre_ping": True,
    "echo": False,
    }

    Continue reading...

Compartilhe esta Página