fix(storage): address review on PR #799 (stale comments, docs deprecation, unit test)

claude-review on #799 flagged:

1. Stale inline comment in ``initialize()`` (line 466) still said
   "Postgres uses a small bounded pool". Updated to reflect both
   backends now use NullPool.

2. Stale ``close()`` docstring referenced pool-size starving
   max_connections — irrelevant with NullPool. Replaced with the
   NullPool-aware rationale (dispose still tears down in-flight
   asyncpg connections cleanly).

3. ``docs/configuration.md`` actively directed operators to tune
   DATABASE_POOL_SIZE / DATABASE_MAX_OVERFLOW, with worked
   examples and pool math. Both are now deprecated no-ops; the
   table entries explain the deprecation and link to PR #799.
   Operators reading the docs will no longer be confused into
   tuning settings that don't do anything.

4. ``config.py`` comment for the deprecated fields updated to
   record the deprecation. Validators are intentionally kept
   (still reject < 1 / < 0) so misconfigured deploys fail loudly
   rather than silently — the reviewer flagged this as a minor
   UX wart but explicitly "not a blocker"; the docs change in (3)
   keeps operators away from the config altogether.

5. New ``tests/unit/test_storage_engine.py`` with three tests:
   - ``test_postgres_engine_uses_nullpool`` — pins ``isinstance(
     engine.pool, NullPool)`` so a refactor back to QueuePool /
     SingletonThreadPool can't silently re-introduce the cross-
     event-loop crashes.
   - ``test_postgres_engine_ignores_pool_sizing_settings`` —
     setting DATABASE_POOL_SIZE / DATABASE_MAX_OVERFLOW to huge
     values must not change pool type (proves the deprecated
     fields are wired-up no-ops).
   - ``test_postgres_engine_missing_asyncpg_driver_message`` —
     guards the existing actionable-error branch when the
     optional ``[postgres]`` extra isn't installed.

Verified:
- ``uv run pytest tests/unit/`` — 1028 passed
- ``uv run ruff check`` clean on the touched python files

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-05-17 18:45:14 +02:00
co-authored by Claude Opus 4.7
parent 8cd3092e87
commit e98903c502
4 changed files with 114 additions and 24 deletions
+8 -8
View File
@@ -138,15 +138,15 @@ TOKEN_ENCRYPTION_KEY=<fernet-key>
| `TOKEN_STORAGE_DB` | Optional | Legacy SQLite-only path. Used when `DATABASE_URL` is unset. Falls back to a per-process ephemeral tempfile when both are unset. |
| `DATABASE_VERIFY_SSL` | Optional | TLS verification toggle for the Postgres backend. Unset (default) → asyncpg's `prefer` mode (TLS if offered, no verification — keeps cluster-internal Postgres working). `true` → full cert verification. `false` → silence cert errors (homelab / self-signed). |
| `DATABASE_CA_BUNDLE` | Optional | Path to a PEM file containing a private CA. Implies `DATABASE_VERIFY_SSL=true`. Use this for self-hosted Postgres signed by your homelab CA instead of disabling verification. |
| `DATABASE_POOL_SIZE` | Optional (default `2`) | Per-pod SQLAlchemy connection pool size for the Postgres backend. asyncpg connections are single-flight, so this only needs to cover concurrent storage ops (not concurrent tool calls). See [ADR-026 § Concurrency model and pool sizing](ADR-026-pluggable-database-backend.md). |
| `DATABASE_MAX_OVERFLOW` | Optional (default `5`) | Per-pod burst connections beyond `DATABASE_POOL_SIZE`. Max per-pod = `pool_size + max_overflow` (default 7). Set to `0` for a hard cap. With 3 replicas the default totals 21 connections — well under managed-Postgres `max_connections=100`. |
| `DATABASE_POOL_SIZE` | Deprecated, no-op | Was per-pod SQLAlchemy pool size for the Postgres backend. The engine now uses `NullPool` (one fresh asyncpg connection per checkout) to avoid cross-event-loop crashes under anyio TaskGroups — see [ADR-026 § Connection pool](ADR-026-pluggable-database-backend.md) and [#799](https://github.com/cbcoutinho/nextcloud-mcp-server/pull/799). Still accepted for backward compatibility; setting it has no effect. |
| `DATABASE_MAX_OVERFLOW` | Deprecated, no-op | Was per-pod burst connection cap on top of `DATABASE_POOL_SIZE`. Now ignored (see above). |
Operators with very high concurrency (many MCP clients per pod, or
expensive Nextcloud round-trips holding storage locks) should tune these
up; single-user / homelab deployments can drop to `DATABASE_POOL_SIZE=1
DATABASE_MAX_OVERFLOW=2` for the smallest possible footprint. The
server logs the configured sizes at startup so over-allocation is
visible without grepping config.
The asyncpg engine is `NullPool`-only: each `engine.connect()` opens
and tears down a fresh asyncpg connection in the caller's current
event loop. On LAN-local Postgres the per-connection overhead is a
single round-trip (~5 ms), so the throughput cost is negligible for
the MCP server's traffic shape (low concurrency, bursty per-user
requests).
Homelab example (self-signed Postgres with a private CA):