Skip to main content

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

Next Steps​