Jun 26 MT-1 phase
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user