Files
JQC_multi_tenant/app/tenancy/engine_cache.py
T
2026-06-26 16:37:44 -04:00

60 lines
1.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
app/tenancy/engine_cache.py
---------------------------
Process-local cache of per-tenant SQLAlchemy engines, keyed by tenant id.
Each tenant has its own database, hence its own engine + connection pool.
Engines are created lazily on first use and reused across requests. Total
backend connections ≈ workers × cached-tenants × pool_size, so pool sizing is
a real scaling lever (see MULTI_TENANT_PLAN.md §4); tune via config, or set a
small pool / switch to NullPool when the tenant count grows large.
`invalidate(tenant_id)` drops a cached engine (e.g. after credential rotation
or tenant suspension); the next request rebuilds it.
"""
import threading
from flask import current_app
from sqlalchemy import create_engine
_engines = {}
_lock = threading.Lock()
def get_tenant_engine(tenant):
"""Return (building if needed) the cached engine for a TenantContext."""
engine = _engines.get(tenant.id)
if engine is not None:
return engine
with _lock:
engine = _engines.get(tenant.id)
if engine is None:
engine = create_engine(
tenant.db_uri,
pool_pre_ping=True,
pool_size=current_app.config.get('TENANT_ENGINE_POOL_SIZE', 5),
max_overflow=current_app.config.get('TENANT_ENGINE_MAX_OVERFLOW', 5),
pool_recycle=current_app.config.get('TENANT_ENGINE_POOL_RECYCLE', 1800),
future=True,
)
_engines[tenant.id] = engine
return engine
def invalidate(tenant_id):
"""Drop and dispose a cached tenant engine, if present."""
with _lock:
engine = _engines.pop(tenant_id, None)
if engine is not None:
engine.dispose()
def clear():
"""Dispose and drop all cached engines (test/teardown helper)."""
with _lock:
engines = list(_engines.values())
_engines.clear()
for engine in engines:
engine.dispose()