Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Changes can also be flagged with a GitHub label for tracking purposes. The URL o
- Added location filter to new request manager screen [#7132](https://github.com/ethyca/fides/pull/7132)
- Added handling for "page-level" errors [#7144](https://github.com/ethyca/fides/pull/7144)
- Added sorting to new request manager screen [#7138](https://github.com/ethyca/fides/pull/7138)

- Pre-warming the async DB pool on server startup [#7141](https://github.com/ethyca/fides/pull/7141)

### Changed
- Bulk privacy request actions now accept filter sets as well as lists, enables select all functionality. [#7027](https://github.com/ethyca/fides/pull/7027)
Expand Down
43 changes: 42 additions & 1 deletion src/fides/api/db/ctl_session.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import asyncio
import ssl
from typing import Any, AsyncGenerator, Dict

from sqlalchemy import create_engine
from loguru import logger
from sqlalchemy import create_engine, text
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker

Expand Down Expand Up @@ -64,3 +66,42 @@ async def get_async_db() -> AsyncGenerator:
"""Return an async session generator for dependency injection into API endpoints"""
async with async_session() as session:
yield session


async def warmup_async_pool() -> None:
"""
Warm up the async connection pool by pre-creating connections.

This function creates up to pool_size connections concurrently and executes
a simple query on each to ensure they are fully established and validated.
This reduces latency for the first requests after server startup.
"""

pool_size = CONFIG.database.api_async_engine_pool_size
if pool_size <= 0:
logger.debug("Skipping async pool warmup: pool_size is 0")
return

logger.info(
"Warming up async connection pool (creating {} connections)...", pool_size
)

async def create_and_validate_connection() -> None:
"""Create a single connection and validate it with a query."""
try:
async with async_session() as session:
await session.execute(text("SELECT 1"))
except Exception as exc:
logger.warning("Failed to create connection during pool warmup: {}", exc)
# Don't re-raise - we want to continue warming up other connections

try:
# Create all connections concurrently
await asyncio.gather(
*[create_and_validate_connection() for _ in range(pool_size)],
return_exceptions=True,
)
Comment thread
galvana marked this conversation as resolved.
logger.info("Async connection pool warmup completed successfully")
except Exception as exc:
logger.error("Error during async pool warmup: {}", exc)
# Don't raise - pool will still work, just without pre-warmed connections
3 changes: 3 additions & 0 deletions src/fides/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
)
from fides.api.common_exceptions import MalisciousUrlException
from fides.api.cryptography.identity_salt import get_identity_salt
from fides.api.db.ctl_session import warmup_async_pool
from fides.api.middleware import handle_audit_log_resource
from fides.api.migrations.hash_migration_job import initiate_bcrypt_migration_task
from fides.api.migrations.post_upgrade_index_creation import (
Expand Down Expand Up @@ -89,6 +90,8 @@ async def lifespan(wrapped_app: FastAPI) -> AsyncGenerator[None, None]:

await run_database_startup(wrapped_app)

await warmup_async_pool()
Comment thread
galvana marked this conversation as resolved.

check_redis()

if not scheduler.running:
Expand Down
Loading