From 2db65647eed4827245b87ecfdeb1c8a2ee346c49 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 07:41:38 +0000 Subject: [PATCH 1/2] Initial plan From dc0a19bd118d3503ad50608f55fb0bc4ce104948 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 07:53:16 +0000 Subject: [PATCH 2/2] fix: add missing SETTING_METADATA entries for db pool and upload rate limit settings - Add db_pool_size, db_max_overflow, db_pool_timeout, db_pool_recycle fields to app/config.py - Add upload_rate_limit_per_user, upload_rate_limit_window fields to app/config.py - Update app/database.py to use NullPool for SQLite and QueuePool with config-driven pool settings for PostgreSQL/MySQL - Add all 6 settings to SETTING_METADATA in app/utils/settings_service.py Fixes test_all_config_settings_have_metadata failure Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- app/config.py | 39 +++++++++++++++++++++ app/database.py | 16 ++++++++- app/utils/settings_service.py | 66 +++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) diff --git a/app/config.py b/app/config.py index cff42826..14080ac5 100644 --- a/app/config.py +++ b/app/config.py @@ -1115,6 +1115,45 @@ class Settings(BaseSettings): ), ) + # Database Connection Pool Configuration + # Controls SQLAlchemy QueuePool behaviour for PostgreSQL/MySQL. + # SQLite uses NullPool and ignores these settings. + db_pool_size: int = Field( + default=5, + description="Number of persistent connections kept in the pool. Ignored for SQLite.", + ) + db_max_overflow: int = Field( + default=10, + description=("Maximum number of connections that can be opened beyond db_pool_size. Ignored for SQLite."), + ) + db_pool_timeout: int = Field( + default=30, + description="Seconds to wait for a connection from the pool before raising an error. Ignored for SQLite.", + ) + db_pool_recycle: int = Field( + default=1800, + description=( + "Seconds after which a connection is recycled to prevent stale connections. " + "Ignored for SQLite. Default: 1800 (30 minutes)." + ), + ) + + # Per-user upload rate limiting (health-aware limiter) + # Controls how many uploads a single user may submit within a sliding window. + upload_rate_limit_per_user: int = Field( + default=20, + description=( + "Maximum number of uploads allowed per user within the upload_rate_limit_window. " + "The limiter may dynamically reduce this value when Redis queue depth or CPU load is high." + ), + ) + upload_rate_limit_window: int = Field( + default=60, + description=( + "Sliding window in seconds over which upload_rate_limit_per_user is enforced. Default: 60 seconds." + ), + ) + # Rate Limiting Configuration (see SECURITY_AUDIT.md and docs/API.md) # Protects against DoS attacks and API abuse rate_limiting_enabled: bool = Field( diff --git a/app/database.py b/app/database.py index f563931a..06fc7471 100644 --- a/app/database.py +++ b/app/database.py @@ -10,6 +10,7 @@ from typing import Any from sqlalchemy import create_engine, exc from sqlalchemy.engine.url import make_url from sqlalchemy.orm import Session, declarative_base, sessionmaker +from sqlalchemy.pool import NullPool, QueuePool from app.config import settings @@ -19,7 +20,20 @@ Base = declarative_base() # Parse the DATABASE_URL DB_URL = settings.database_url -engine = create_engine(DB_URL, connect_args={"check_same_thread": False}) +_db_url = make_url(DB_URL) +if _db_url.get_backend_name() == "sqlite": + # SQLite does not benefit from connection pooling; NullPool avoids contention. + engine = create_engine(DB_URL, connect_args={"check_same_thread": False}, poolclass=NullPool) +else: + # PostgreSQL / MySQL / other: use a configurable QueuePool. + engine = create_engine( + DB_URL, + poolclass=QueuePool, + pool_size=settings.db_pool_size, + max_overflow=settings.db_max_overflow, + pool_timeout=settings.db_pool_timeout, + pool_recycle=settings.db_pool_recycle, + ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/app/utils/settings_service.py b/app/utils/settings_service.py index 41e870ec..fef1429d 100644 --- a/app/utils/settings_service.py +++ b/app/utils/settings_service.py @@ -2557,6 +2557,72 @@ SETTING_METADATA = { "required": False, "restart_required": False, }, + # Database Connection Pool + "db_pool_size": { + "category": "Core", + "description": ( + "Number of persistent connections kept in the SQLAlchemy QueuePool. " + "Has no effect for SQLite databases. Default: 5." + ), + "type": "integer", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "db_max_overflow": { + "category": "Core", + "description": ( + "Maximum extra connections that can be opened beyond db_pool_size. " + "Has no effect for SQLite databases. Default: 10." + ), + "type": "integer", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "db_pool_timeout": { + "category": "Core", + "description": ( + "Seconds to wait for a connection from the pool before raising an error. " + "Has no effect for SQLite databases. Default: 30." + ), + "type": "integer", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "db_pool_recycle": { + "category": "Core", + "description": ( + "Seconds after which idle connections are recycled to prevent stale connections. " + "Has no effect for SQLite databases. Default: 1800 (30 minutes)." + ), + "type": "integer", + "sensitive": False, + "required": False, + "restart_required": True, + }, + # Per-user upload rate limiting + "upload_rate_limit_per_user": { + "category": "Security", + "description": ( + "Maximum number of uploads a single user may submit within upload_rate_limit_window seconds. " + "The health-aware limiter may reduce this dynamically under high Redis queue depth or CPU load. " + "Default: 20." + ), + "type": "integer", + "sensitive": False, + "required": False, + "restart_required": False, + }, + "upload_rate_limit_window": { + "category": "Security", + "description": ("Sliding window in seconds over which upload_rate_limit_per_user is enforced. Default: 60."), + "type": "integer", + "sensitive": False, + "required": False, + "restart_required": False, + }, # Rate Limiting "rate_limiting_enabled": { "category": "Security",