Tenant Architecture
Detailed architecture documentation for the Burdenoff multi-tenant system.
System Architectureβ
βββββββββββββββββββββββββββββββββββββββββββ
β Workspaces Platform β
β (Tenant Management & Authentication) β
βββββββββββββββββββ¬ββββββββββββββββββββββββ
β
βββββββββββββββΌββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββ βββββββββββ βββββββββββ
βProduct 1β βProduct 2β βProduct Nβ
βββββββββββ βββββββββββ βββββββββββ
Componentsβ
Tenant Backendβ
- Technology: Python FastAPI or Node.js
- Database: PostgreSQL
- Cache: Redis
- Authentication: JWT + OAuth
Responsibilitiesβ
- Tenant CRUD operations
- User authentication
- Authorization & permissions
- Billing integration
- Usage tracking
Database Designβ
Tenant Schemaβ
CREATE TABLE tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
domain TEXT UNIQUE NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
plan_id UUID REFERENCES plans(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE tenant_users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
user_id UUID NOT NULL REFERENCES users(id),
role TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, user_id)
);
CREATE TABLE tenant_config (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID UNIQUE NOT NULL REFERENCES tenants(id),
config JSONB NOT NULL DEFAULT '{}',
limits JSONB NOT NULL DEFAULT '{}',
features JSONB NOT NULL DEFAULT '{}',
updated_at TIMESTAMPTZ DEFAULT NOW()
);
Product-Specific Tablesβ
Every product table includes:
CREATE TABLE product_data (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL REFERENCES tenants(id),
-- other columns
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Index for tenant queries
CREATE INDEX idx_product_data_tenant
ON product_data(tenant_id);
-- Row-level security
ALTER TABLE product_data ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON product_data
FOR ALL
USING (tenant_id = current_setting('app.tenant_id')::uuid);
Authentication Flowβ
1. Login Requestβ
POST /auth/login
{
"email": "[email protected]",
"password": "***"
}
2. Tenant Resolutionβ
async def resolve_tenant(email: str):
domain = email.split('@')[1]
tenant = await db.tenants.find_by_domain(domain)
return tenant
3. Token Generationβ
def create_access_token(user, tenant):
payload = {
"sub": user.id,
"tenant_id": tenant.id,
"email": user.email,
"exp": datetime.utcnow() + timedelta(minutes=15)
}
return jwt.encode(payload, SECRET_KEY)
4. Token Usageβ
async def get_current_user(token: str):
payload = jwt.decode(token, SECRET_KEY)
tenant_id = payload["tenant_id"]
user_id = payload["sub"]
# Set tenant context
await db.execute(
f"SET LOCAL app.tenant_id = '{tenant_id}'"
)
return await db.users.get(user_id)
Request Flowβ
1. Incoming Requestβ
User Request β Load Balancer β Ingress β Service β Pod
2. Middleware Chainβ
@app.middleware("http")
async def tenant_middleware(request, call_next):
# Extract JWT token
token = request.headers.get("Authorization")
# Decode and validate
payload = validate_token(token)
# Set tenant context
request.state.tenant_id = payload["tenant_id"]
request.state.user_id = payload["sub"]
# Set database context
await set_tenant_context(payload["tenant_id"])
# Process request
response = await call_next(request)
return response
Tenant Isolation Strategiesβ
1. Database per Tenantβ
Pros:
- Strong isolation
- Easy backup/restore
- Independent scaling
Cons:
- Higher operational cost
- Complex migrations
- Resource overhead
2. Schema per Tenantβ
Pros:
- Good isolation
- Shared resources
- Easier management
Cons:
- Schema proliferation
- Migration complexity
- Limited by database
3. Row-Level Security (Current)β
Pros:
- Efficient resource use
- Simple operations
- Easy scaling
Cons:
- Requires careful design
- Application-level checks
- Query complexity
Scaling Considerationsβ
Horizontal Scalingβ
- Stateless services
- Load balancing
- Database connection pooling
Vertical Scalingβ
- Resource limits per pod
- Auto-scaling based on metrics
- Database read replicas
Caching Strategyβ
@cache(key="tenant:{tenant_id}:config")
async def get_tenant_config(tenant_id: str):
return await db.tenant_config.get(tenant_id)
Performance Optimizationβ
Query Optimizationβ
-- Always filter by tenant_id first
SELECT * FROM users
WHERE tenant_id = $1
AND status = 'active';
-- Use covering indexes
CREATE INDEX idx_users_tenant_status
ON users(tenant_id, status)
INCLUDE (email, name);
Connection Poolingβ
DATABASE_POOL_SIZE = 20
DATABASE_POOL_MAX_OVERFLOW = 10
engine = create_async_engine(
DATABASE_URL,
pool_size=DATABASE_POOL_SIZE,
max_overflow=DATABASE_POOL_MAX_OVERFLOW
)
Security Measuresβ
Data Encryptionβ
- At Rest: Database encryption
- In Transit: TLS 1.3
- Application: Field-level encryption
Access Controlβ
- Role-Based Access Control (RBAC)
- Tenant-scoped permissions
- API key management
- Audit logging
Complianceβ
- GDPR compliance
- Data residency
- Right to deletion
- Data portability
Monitoringβ
Key Metricsβ
- Tenant count
- Active users per tenant
- Resource usage per tenant
- API calls per tenant
- Storage per tenant
Alertsβ
- Quota violations
- Unusual activity
- Performance degradation
- Failed authentication attempts