Skip to content

[CHORE][PERFORMANCE]: Reduce mcpgateway.main import-time bootstrap cost for test fixtures #3398

@jonpspri

Description

@jonpspri

Problem

Importing mcpgateway.main takes ~2.7–3.0s due to module-level side effects that run at import time. This cost is paid by every test that uses the function-scoped app fixture in tests/conftest.py, making those tests significantly slower than they need to be.

Affected test examples (setup time, not test execution):

  • tests/unit/mcpgateway/test_main_extended.py::TestAdminAuthMiddleware::test_admin_auth_trailing_slash_root_path — 2.78s setup
  • tests/unit/mcpgateway/routers/test_metrics_maintenance.py::test_metrics_rollup_disabled — 2.73s setup
  • tests/unit/mcpgateway/routers/test_well_known_rfc9728.py::TestRFC9728CompliantEndpoint::test_rfc9728_endpoint_oauth_not_enabled — 2.71s setup
  • tests/unit/mcpgateway/test_main_extended.py::TestConditionalPaths::test_redis_initialization_path — 2.71s setup

Root cause

mcpgateway/main.py performs substantial work at module import time (lines ~160–227):

  1. Database readiness check (wait_for_db_ready) — synchronous retry loop at import time (line 172)
  2. Database bootstrap (bootstrap_db()) — runs Alembic migrations, creates tables, creates admin users, sets up RBAC roles (line 178). This is the most expensive step.
  3. Redis readiness check (wait_for_redis_ready) — another synchronous retry loop (line 217)
  4. Service singleton instantiation — ~10 service singletons created at module level (lines 196–213)
  5. Session registry initialization (lines 220–227)
  6. Heavy import chainmain.py imports ~50 modules at the top level (lines 30–159), each potentially triggering further import-time work

The function-scoped app fixture in tests/conftest.py does from mcpgateway.main import app on every test invocation. While Python caches module objects, the fixture still creates a fresh temp SQLite DB, patches SessionLocal across 6+ modules, and runs Base.metadata.create_all() per test.

Why we can't simply use module-scoped fixtures

A module-scoped app_with_temp_db fixture already exists, but most tests mutate shared state (DB rows, app.dependency_overrides, patched settings) that would leak between tests. Switching to module scope would require adding explicit cleanup/rollback logic to every test, which is fragile and increases maintenance burden.

Suggested investigation areas

  1. Defer bootstrap_db() to lifespan — Move the Alembic migration + admin user creation out of module-level code and into the lifespan() async context manager, which already handles all other service initialization. Test fixtures could then skip it entirely.

  2. Lazy service singletons — Replace module-level singleton instantiation (lines 196–213) with lazy initialization patterns (e.g., functools.lru_cache or get_*_service() factory functions that several services already use).

  3. Guard DB/Redis readiness checkswait_for_db_ready and wait_for_redis_ready run unconditionally at import time. These could be deferred to lifespan() or guarded with a SKIP_STARTUP_CHECKS env var for test environments.

  4. Reduce SessionLocal patch surface — Currently, SessionLocal must be patched in 6+ modules because each does from mcpgateway.db import SessionLocal at import time, capturing the original reference. Migrating these to import mcpgateway.db as _db_mod + _db_mod.SessionLocal() (as was done for base_service.py in PR [EPIC][A2A]: A2A Protocol v0.3.0 Full Compliance Implementation #3150) would allow a single patch on mcpgateway.db.SessionLocal to propagate everywhere.

  5. Lightweight test app factory — Consider a create_test_app() factory that builds a minimal FastAPI app with only the routers/middleware needed for the test, rather than importing the full production app.

Acceptance criteria

  • Identify the top contributors to the ~3s import cost (profile with python -X importtime)
  • Reduce function-scoped app fixture setup time to <1s without sacrificing test isolation
  • No regressions in test pass rate or production startup behavior

Metadata

Metadata

Assignees

No one assigned

    Labels

    COULDP3: Nice-to-have features with minimal impact if left out; included if time permitschoreLinting, formatting, dependency hygiene, or project maintenance choresperformancePerformance related itemstestingTesting (unit, e2e, manual, automated, etc)

    Type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions