Explorar el Código

fix(db): PostgreSQL pool default fits a stock server

maziggy hace 1 día
padre
commit
56e067753a

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 0
CHANGELOG.md


+ 1 - 1
backend/app/core/config.py

@@ -75,7 +75,7 @@ class Settings(BaseSettings):
     database_url: str = _external_db_url or f"sqlite+aiosqlite:///{_db_path}"
 
     # Database connection pool sizing. ``None`` = use the built-in, dialect-aware
-    # default (PostgreSQL: pool_size 20 + max_overflow 80; SQLite: 20 + 200).
+    # default (PostgreSQL: pool_size 20 + max_overflow 60; SQLite: 20 + 200).
     # Large PostgreSQL printer farms can raise these via the DB_POOL_SIZE /
     # DB_MAX_OVERFLOW / DB_POOL_TIMEOUT / DB_POOL_RECYCLE env vars (issue #2572).
     # Make sure PostgreSQL ``max_connections`` comfortably exceeds

+ 5 - 3
backend/app/core/database.py

@@ -37,10 +37,12 @@ def _resolve_pool_kwargs() -> dict:
     """Build the pool kwargs for ``create_async_engine`` (issue #2572).
 
     Dialect-aware defaults, each overridable via env (``DB_POOL_SIZE`` etc.):
-      - PostgreSQL: pool_size 20 + max_overflow 80, ``pool_pre_ping`` (recover
+      - PostgreSQL: pool_size 20 + max_overflow 60, ``pool_pre_ping`` (recover
         server-dropped connections instead of erroring the request) and
         ``pool_recycle`` 1800s. The old hard-coded 10 + 20 exhausted on large
-        farms while printer callbacks held connections.
+        farms while printer callbacks held connections. The 80-connection
+        ceiling fits a stock server (max_connections 100, 3 reserved for
+        superusers); 20 + 80 did not, and tripped the startup pool check.
       - SQLite: pool_size 20 + max_overflow 200 (unchanged); no pre-ping /
         recycle — the connection is a local file, not a server socket.
     """
@@ -50,7 +52,7 @@ def _resolve_pool_kwargs() -> dict:
         kwargs = {"pool_size": pool_size, "max_overflow": max_overflow}
     else:
         pool_size = settings.db_pool_size if settings.db_pool_size is not None else 20
-        max_overflow = settings.db_max_overflow if settings.db_max_overflow is not None else 80
+        max_overflow = settings.db_max_overflow if settings.db_max_overflow is not None else 60
         kwargs = {
             "pool_size": pool_size,
             "max_overflow": max_overflow,

+ 2 - 2
backend/tests/unit/test_db_pool_and_auth_cache.py

@@ -27,7 +27,7 @@ class TestPoolConfiguration:
         assert "pool_recycle" not in kwargs
 
     def test_postgres_defaults_raise_the_old_limits(self, monkeypatch):
-        """Postgres default is now 20 + 80 (was 10 + 20) with pre-ping + recycle."""
+        """Postgres default is 20 + 60 (was 10 + 20) with pre-ping + recycle."""
         from backend.app.core import database
 
         for attr in ("db_pool_size", "db_max_overflow", "db_pool_timeout", "db_pool_recycle"):
@@ -36,7 +36,7 @@ class TestPoolConfiguration:
 
         kwargs = database._resolve_pool_kwargs()
         assert kwargs["pool_size"] == 20
-        assert kwargs["max_overflow"] == 80
+        assert kwargs["max_overflow"] == 60
         assert kwargs["pool_pre_ping"] is True
         assert kwargs["pool_recycle"] == 1800
 

+ 22 - 1
backend/tests/unit/test_pool_fits_server.py

@@ -63,7 +63,7 @@ async def _run_check(*, pool_size, max_overflow, max_conn, reserved, in_use=0, s
 @pytest.mark.asyncio
 @pytest.mark.unit
 async def test_warns_when_the_ceiling_exceeds_what_the_server_allows(caplog):
-    """Bambuddy's own PostgreSQL default against a stock server: 100 vs 100-3."""
+    """A 100-connection ceiling against a stock server: 100 vs 100-3."""
     with caplog.at_level(logging.WARNING, logger="backend.app.core.database"):
         await _run_check(pool_size=20, max_overflow=80, max_conn=100, reserved=3)
 
@@ -74,6 +74,27 @@ async def test_warns_when_the_ceiling_exceeds_what_the_server_allows(caplog):
         assert expected in msg, f"warning omits {expected!r}"
 
 
+@pytest.mark.asyncio
+@pytest.mark.unit
+async def test_the_postgres_default_fits_a_stock_server(caplog, monkeypatch):
+    """Without DB_POOL_* set, a stock server (100, 3 reserved) must not warn.
+
+    The default was 20 + 80, one over the 97 a stock server leaves, so every
+    PostgreSQL install that didn't size its pool logged this at each start.
+    """
+    from backend.app.core import database
+
+    for attr in ("db_pool_size", "db_max_overflow", "db_pool_timeout", "db_pool_recycle"):
+        monkeypatch.setattr(database.settings, attr, None, raising=False)
+    monkeypatch.setattr(database, "is_sqlite", lambda: False)
+    kwargs = database._resolve_pool_kwargs()
+
+    with caplog.at_level(logging.WARNING, logger="backend.app.core.database"):
+        await _run_check(pool_size=kwargs["pool_size"], max_overflow=kwargs["max_overflow"], max_conn=100, reserved=3)
+
+    assert not [r for r in caplog.records if r.levelno >= logging.WARNING]
+
+
 @pytest.mark.asyncio
 @pytest.mark.unit
 async def test_silent_when_the_pool_fits(caplog):

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio