ADR-0027: Migrate from Psycopg2 to Psycopg3ΒΆ
- Status: Accepted
- Date: 2025-01-15
- Deciders: Mihai Criveti
ContextΒΆ
The MCP Gateway uses PostgreSQL as one of its supported production database backends. Historically, the project used psycopg2 (via psycopg2-binary) as the PostgreSQL adapter. Psycopg2 has been the standard PostgreSQL adapter for Python for over 15 years.
Psycopg3 (the psycopg package) is a complete rewrite of the PostgreSQL adapter, offering significant improvements in architecture, performance, and features. SQLAlchemy 2.x provides native support for psycopg3 via the postgresql+psycopg:// dialect.
Key benefits of psycopg3:
- Parameter binding: Psycopg3 uses server-side parameter binding by default (parameters sent separately from query), improving security
- Prepared statements: Native support for prepared statements, improving performance for repeated queries
- Binary protocol: Support for binary data transfer, reducing parsing overhead
- Async support: First-class async support built into the core library
- Connection pooling: Native connection pool implementation (
psycopg_pool) - Active maintenance: Psycopg3 is actively developed; psycopg2 is in maintenance mode
DecisionΒΆ
We will use psycopg[binary] (psycopg3) as the only supported PostgreSQL adapter. Psycopg2 is no longer supported.
Changes MadeΒΆ
- Dependencies (
pyproject.toml): -
Use
psycopg[c,binary]>=3.2.0for thepostgresextra (C extension + pre-compiled libpq) -
Driver Detection (
mcpgateway/db.py): - Only psycopg3 driver is supported:
driver in ("psycopg", "default", "") -
Keep-alive parameters work via libpq
-
Connection URLs:
postgresql+psycopg://- Required for psycopg3postgresql://- Does NOT work (defaults to psycopg2 in SQLAlchemy)
Migration Path for UsersΒΆ
- URL format: Must use
postgresql+psycopg://(notpostgresql://) - Install: Use
pip install 'psycopg[c,binary]'or install the gateway with[postgres]extra - Breaking change: All PostgreSQL URLs must be updated to use
postgresql+psycopg://
Performance OptimizationsΒΆ
The migration to psycopg3 enabled several performance optimizations:
1. COPY Protocol Utility (for Large Bulk Imports)ΒΆ
A utility module provides psycopg3's COPY protocol for very large bulk inserts (1000+ rows).
Location: mcpgateway/utils/psycopg3_optimizations.py
Important: COPY is only faster for large batches. Testing showed: - 1000+ rows: COPY is 5-10x faster than INSERT - 10-100 rows: COPY is actually 2x slower due to protocol overhead - For small batches, use SQLAlchemy's bulk_insert_mappings() instead
Use cases: - Bulk data imports (tools, resources, prompts) - Initial data seeding - NOT recommended for metrics buffering (small frequent batches)
2. Automatic Prepared StatementsΒΆ
Psycopg3 automatically prepares frequently-executed queries server-side after a configurable threshold.
Configuration: DB_PREPARE_THRESHOLD (default: 5)
After N executions of the same query template, psycopg3 creates a server-side prepared statement, reducing: - Query parsing overhead - Network round-trips for query plans - PostgreSQL CPU usage for repeated queries
Set to 0 to disable, 1 to prepare immediately, or higher values to reduce memory usage.
3. Optimized Utility ModuleΒΆ
New utility module provides psycopg3-specific optimizations with automatic fallbacks:
Location: mcpgateway/utils/psycopg3_optimizations.py
Features: - bulk_insert_with_copy() - COPY protocol for bulk inserts - bulk_insert_metrics() - Optimized metrics insertion - execute_pipelined() - Pipeline mode for batch queries - is_psycopg3_backend() - Runtime backend detection
ConsequencesΒΆ
PositiveΒΆ
- Better security: Server-side parameter binding prevents SQL injection at the protocol level
- Improved performance: Prepared statements and binary protocol reduce overhead
- COPY protocol: 5-10x faster bulk inserts for large import operations (1000+ rows)
- Future-ready: Native async support enables future async migration if needed
- Active maintenance: Psycopg3 receives regular updates and security fixes
- Better connection pooling: Option to use psycopg_pool for advanced scenarios
- Simpler codebase: Only one driver to support and test
NegativeΒΆ
- Breaking change: Users with psycopg2 must migrate
- Server-side binding limitations: Some DDL statements don't support parameters (mitigated by using
psycopg.sqlmodule)
NeutralΒΆ
- Keep-alive parameters: Work the same way via libpq
- SQLAlchemy abstraction: Most application code unchanged
Compatibility NotesΒΆ
SQL Patterns That Work UnchangedΒΆ
- Standard SELECT/INSERT/UPDATE/DELETE with named parameters (
:namestyle) - Simple queries without parameters
- SQLAlchemy ORM operations
- Alembic migrations (all use compatible patterns)
Patterns Requiring AttentionΒΆ
These patterns are not used in the MCP Gateway codebase but should be noted:
- Tuple IN clause:
IN %swith tuple doesn't work; use= ANY(%s)with list - IS NULL with parameter:
IS %sdoesn't work; useIS NOT DISTINCT FROM %s - Multiple statements: Can't execute multiple statements with parameters in one call
Alternatives ConsideredΒΆ
| Option | Why Not |
|---|---|
| Keep psycopg2 support | Maintenance burden, psycopg2 in maintenance mode |
| asyncpg | Not DBAPI-compatible, would require major rewrite of database layer |
| pg8000 | Less mature, fewer features, smaller community |
ReferencesΒΆ
StatusΒΆ
This decision is implemented. The MCP Gateway uses psycopg3 as the only supported PostgreSQL adapter.