Hierarchical Multi-Tenancy: Organization-Aware Resource Isolation
Simple multi-tenant systems have only one "tenant" layer. Enterprise scenarios are far more complex:
CoomiaPublished on December 29, 20256 min read
Share this articleTwitter / X
Hierarchical Multi-Tenancy: Organization-Aware Resource Isolation
“Series: S10 Design Patterns · Article 14 | Level: Advanced | Reading Time: 18 min
#TL;DR
- Hierarchical Multi-Tenancy supports Group, Subsidiary, Region, Branch, and Team organizational structures, with each level having independent resource quotas, data isolation, and permission boundaries.
- The coomia-dip tenant model is not a flat "tenant_id" but an organization tree. Child tenants inherit parent configurations while being able to override specific settings.
- Through the combination of World Context and Nessie branching, coomia-dip achieves multi-level data isolation while supporting cross-level aggregation queries.
#Introduction: Enterprise Multi-Tenancy Complexity
Simple multi-tenant systems have only one "tenant" layer. Enterprise scenarios are far more complex:
Code
Wanda Group
├── Wanda Commercial (Subsidiary A)
│ ├── East Region
│ │ ├── Shanghai Branch
│ │ └── Hangzhou Branch
│ └── North Region
│ └── Beijing Branch
├── Wanda Tourism (Subsidiary B)
└── Wanda Finance (Subsidiary C)
Requirements:
- Group HQ needs to see aggregated data across all subsidiaries
- Subsidiaries can only see their own regional data
- Branches can only see their own data
- Certain rules defined at group level, inherited by subsidiaries
- Subsidiaries can override some group rules but not mandatory ones
#Part 1: Tenant Hierarchy Model
#1.1 Organization Tree Definition
Python
from dataclasses import dataclass, field
from typing import Any
@dataclass
class TenantNode:
tenant_id: str
name: str
display_name: str
level: str # group | subsidiary | region | branch | team
parent_id: str | None = None
children: list[str] = field(default_factory=list)
config: dict[str, Any] = field(default_factory=dict)
quotas: dict[str, Any] = field(default_factory=dict)
overrides: dict[str, Any] = field(default_factory=dict)
status: str = "active"
class TenantHierarchy:
def __init__(self):
self._nodes: dict[str, TenantNode] = {}
async def create_tenant(self, tenant: TenantNode) -> TenantNode:
if tenant.parent_id and tenant.parent_id not in self._nodes:
raise ValueError(f"Parent tenant '{tenant.parent_id}' not found")
self._nodes[tenant.tenant_id] = tenant
if tenant.parent_id:
self._nodes[tenant.parent_id].children.append(tenant.tenant_id)
return tenant
def get_ancestors(self, tenant_id: str) -> list[TenantNode]:
ancestors = []
current = self._nodes.get(tenant_id)
while current and current.parent_id:
parent = self._nodes.get(current.parent_id)
if parent:
ancestors.append(parent)
current = parent
return ancestors
def get_descendants(self, tenant_id: str) -> list[TenantNode]:
descendants = []
node = self._nodes.get(tenant_id)
if not node:
return descendants
queue = list(node.children)
while queue:
child_id = queue.pop(0)
child = self._nodes.get(child_id)
if child:
descendants.append(child)
queue.extend(child.children)
return descendants
def get_effective_config(self, tenant_id: str) -> dict:
config: dict[str, Any] = {}
for ancestor in reversed(self.get_ancestors(tenant_id)):
config = self._deep_merge(config, ancestor.config)
current = self._nodes[tenant_id]
config = self._deep_merge(config, current.config)
config = self._deep_merge(config, current.overrides)
return config
def _deep_merge(self, base: dict, override: dict) -> dict:
result = dict(base)
for key, value in override.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = self._deep_merge(result[key], value)
else:
result[key] = value
return result
#1.2 Quota Inheritance and Allocation
Python
class QuotaManager:
async def allocate_quota(
self, parent_id: str, child_id: str, resource_type: str, amount: int
) -> bool:
parent_quota = await self._get_quota(parent_id, resource_type)
available = parent_quota.total_limit - parent_quota.allocated
if amount > available:
return False
parent_quota.allocated += amount
child_quota = QuotaDefinition(resource_type=resource_type, total_limit=amount)
await self._save_quota(parent_id, parent_quota)
await self._save_quota(child_id, child_quota)
return True
async def get_quota_usage(self, tenant_id: str) -> dict:
descendants = self._hierarchy.get_descendants(tenant_id)
usage = {}
for resource_type in ["storage_gb", "compute_cores", "objects", "rules"]:
own_quota = await self._get_quota(tenant_id, resource_type)
child_allocated = sum(
(await self._get_quota(d.tenant_id, resource_type)).total_limit
for d in descendants
)
usage[resource_type] = {
"total_limit": own_quota.total_limit,
"allocated_to_children": child_allocated,
"own_usage": own_quota.allocated - child_allocated,
"available": own_quota.total_limit - own_quota.allocated,
}
return usage
#Part 2: Data Isolation Strategies
#2.1 Multi-Level Isolation via World and Nessie
Python
class HierarchicalDataIsolation:
async def setup_isolation(self, tenant: TenantNode) -> dict:
world = await self._world_service.create_world(
name=f"tenant-{tenant.tenant_id}",
parent_world_id=self._get_parent_world(tenant),
isolation_level=self._get_isolation_level(tenant.level),
)
namespace = await self._nessie.create_namespace(
f"tenant/{tenant.tenant_id}",
properties={"tenant_id": tenant.tenant_id, "level": tenant.level},
)
return {
"world_id": world.world_id,
"namespace": namespace.name,
"isolation_level": self._get_isolation_level(tenant.level),
}
def _get_isolation_level(self, level: str) -> str:
return {
"group": "full",
"subsidiary": "namespace",
"region": "schema",
"branch": "row",
"team": "row",
}.get(level, "row")
#2.2 Cross-Level Aggregation Queries
Python
class CrossTenantQueryEngine:
async def aggregate_query(
self, query: str, tenant_id: str,
include_descendants: bool = True, depth: int | None = None
) -> list[dict]:
tenants = [tenant_id]
if include_descendants:
descendants = self._hierarchy.get_descendants(tenant_id)
if depth:
descendants = [d for d in descendants if self._get_depth(d, tenant_id) <= depth]
tenants.extend([d.tenant_id for d in descendants])
for tid in list(tenants):
if not await self._check_cross_tenant_permission(tenant_id, tid):
tenants.remove(tid)
results = await asyncio.gather(
*[self._query_tenant(query, tid) for tid in tenants]
)
merged = []
for tenant_results in results:
merged.extend(tenant_results)
return merged
#Part 3: Rule Inheritance and Override
#3.1 Rule Hierarchy
Python
class HierarchicalRuleEngine:
async def get_effective_rules(self, tenant_id: str, rule_type: str) -> list[dict]:
ancestors = self._hierarchy.get_ancestors(tenant_id)
all_tenants = list(reversed(ancestors)) + [self._hierarchy.get_node(tenant_id)]
effective_rules = []
overridden_ids = set()
for tenant in reversed(all_tenants):
tenant_rules = await self._rule_store.get_rules(tenant.tenant_id, rule_type)
for rule in tenant_rules:
if rule.get("mandatory") and rule["id"] in overridden_ids:
raise PolicyViolation(
f"Mandatory rule '{rule['id']}' cannot be overridden"
)
if rule["id"] not in overridden_ids:
effective_rules.append(rule)
if rule.get("overrides"):
overridden_ids.add(rule["overrides"])
return effective_rules
#3.2 Mandatory vs Optional Rules
Python
class RuleInheritancePolicy:
RULE_CATEGORIES = {
"mandatory": {"description": "Cannot be overridden by child tenants"},
"default": {"description": "Applied by default, can be overridden"},
"optional": {"description": "Not applied unless explicitly enabled"},
}
async def can_override(self, tenant_id: str, rule_id: str) -> bool:
rule = await self._rule_store.get(rule_id)
return rule.get("category") != "mandatory"
#Part 4: Tenant Management API
#4.1 Tenant CRUD
Python
class TenantManagementService:
async def create_subsidiary(
self, group_id: str, name: str, config: dict, quotas: dict
) -> TenantNode:
tenant = TenantNode(
tenant_id=generate_id(), name=name,
display_name=config.get("display_name", name),
level="subsidiary", parent_id=group_id,
config=config, quotas=quotas,
)
for resource_type, limit in quotas.items():
await self._quota_manager.allocate_quota(
group_id, tenant.tenant_id, resource_type, limit
)
await self._data_isolation.setup_isolation(tenant)
parent_config = self._hierarchy.get_effective_config(group_id)
tenant.config = self._hierarchy._deep_merge(parent_config, config)
await self._hierarchy.create_tenant(tenant)
return tenant
async def get_organization_tree(self, root_tenant_id: str) -> dict:
root = self._hierarchy.get_node(root_tenant_id)
return self._build_tree(root)
def _build_tree(self, node: TenantNode) -> dict:
return {
"id": node.tenant_id, "name": node.name,
"level": node.level, "status": node.status,
"children": [
self._build_tree(self._hierarchy.get_node(cid))
for cid in node.children
if self._hierarchy.get_node(cid)
],
}
#Part 5: Security Boundaries
#5.1 Cross-Tenant Access Control
Python
class CrossTenantAccessControl:
async def check_access(
self, actor_tenant_id: str, target_tenant_id: str, operation: str
) -> bool:
if actor_tenant_id == target_tenant_id:
return True
if self._is_ancestor(actor_tenant_id, target_tenant_id):
return operation in ["read", "aggregate"]
return False
def _is_ancestor(self, potential_ancestor: str, tenant_id: str) -> bool:
ancestors = self._hierarchy.get_ancestors(tenant_id)
return any(a.tenant_id == potential_ancestor for a in ancestors)
#Key Takeaways
- Organization Tree Model: Tenants are not flat IDs but a multi-level tree reflecting real organizational structure
- Configuration Inheritance: Child tenants inherit parent configurations and can override non-mandatory settings
- Quota Management: Quotas are allocated from parent to child tenants with recursive usage tracking
- Multi-Level Isolation: Different isolation levels selected by organizational tier (full/namespace/row-level)
- Downward Visibility: Parent tenants can aggregate query all child tenant data; children see only their own
- Mandatory Rules: Group-level mandatory rules cannot be overridden by subsidiaries, ensuring compliance consistency
#Next Article
S10-15: AI Pair Programming: LLM-Assisted Ontology Modeling and Rule Writing
#Tags
#DesignPatterns #MultiTenancy #Hierarchical #OrganizationTree #QuotaManagement #DataIsolation #RuleInheritance