Skip to main content

Chapter 11: Multi-Tenant Architecture

Overview

Vektagraf's multi-tenant architecture enables secure isolation and resource management for multiple tenants within a single database instance. This chapter covers tenant configuration, resource quotas, security isolation, and monitoring strategies for building scalable SaaS applications.

Learning Objectives

  • Understand Vektagraf's multi-tenant architecture and isolation mechanisms
  • Configure tenant-specific limits and resource quotas
  • Implement tenant security and data partitioning
  • Monitor tenant usage and performance analytics
  • Design scalable multi-tenant applications

Prerequisites

  • Understanding of Vektagraf core concepts (Chapters 1-3)
  • Knowledge of security fundamentals (Chapters 8-9)
  • Familiarity with database operations (Chapter 4)

Core Concepts

Multi-Tenant Architecture Overview

Vektagraf provides comprehensive multi-tenancy support with:

  • Tenant Isolation: Complete data and security separation between tenants
  • Resource Management: Configurable limits for storage, queries, and performance
  • Rate Limiting: Per-tenant request throttling and burst control
  • Usage Analytics: Detailed monitoring and reporting per tenant
  • Flexible Tiers: Predefined and custom tenant limit configurations
graph TB
    A[Application Layer] --> B[Tenant Middleware]
    B --> C[Tenant Manager]
    C --> D[Usage Tracker]
    C --> E[Storage Tracker]
    C --> F[Tenant Validator]
    
    B --> G[Database Operations]
    G --> H[Tenant A Data]
    G --> I[Tenant B Data]
    G --> J[Tenant C Data]
    
    D --> K[Rate Limits]
    D --> L[Request Quotas]
    E --> M[Storage Limits]
    E --> N[Object Quotas]

Tenant Configuration Model

Each tenant has a comprehensive configuration defining limits and capabilities:

class TenantConfig {
  final String tenantId;
  final String name;
  final TenantLimitTier tier;
  final RateLimitConfig rateLimit;
  final StorageLimitConfig storageLimit;
  final RequestLimitConfig requestLimit;
  final bool enabled;
  final Map<String, dynamic> customSettings;
}

Practical Examples

Basic Tenant Setup

1. Enable Multi-Tenancy

import 'package:vektagraf/vektagraf.dart';

// Configure database with multi-tenancy enabled
final config = VektagrafConfig(
  multiTenancy: MultiTenancyConfig(
    enabled: true,
    strictIsolation: true,
    defaultTier: 'tier2',
    autoCreateTenants: false,
  ),
);

final database = await VektagrafDatabase.open(
  'multi_tenant_app.db',
  config: config,
);

2. Create Tenant Configurations

// Create tenant manager
final tenantManager = TenantManager(config);

// Define tenant configurations for different tiers
final enterpriseTenant = TenantConfig.forTier(
  tenantId: 'acme_corp',
  name: 'ACME Corporation',
  tier: TenantLimitTier.tier3,
  dbConfig: config,
  customSettings: {
    'industry': 'technology',
    'complianceLevel': 'soc2',
    'dataResidency': 'us-west',
    'supportLevel': 'premium',
  },
);

final startupTenant = TenantConfig.forTier(
  tenantId: 'startup_inc',
  name: 'Startup Inc',
  tier: TenantLimitTier.tier1,
  dbConfig: config,
  customSettings: {
    'industry': 'fintech',
    'complianceLevel': 'basic',
    'supportLevel': 'standard',
  },
);

// Register tenants
tenantManager.setTenantConfig(enterpriseTenant);
tenantManager.setTenantConfig(startupTenant);

Advanced Tenant Configuration

Custom Tier Configuration

// Create custom tier with specific limits
final customTier = TenantConfig(
  tenantId: 'premium_client',
  name: 'Premium Client',
  tier: TenantLimitTier.custom,
  rateLimit: RateLimitConfig(
    maxRequests: 5000,
    timeWindow: Duration(minutes: 1),
    burstAllowance: 500,
    operationTypes: {
      'read', 'write', 'query', 'vector_search', 'graph_traverse'
    },
  ),
  storageLimit: StorageLimitConfig(
    maxStorageBytes: 50 * 1024 * 1024 * 1024, // 50GB
    maxObjects: 500000,
    maxObjectSize: 500 * 1024 * 1024, // 500MB
    maxVectors: 500000,
    maxVectorDimensions: 4096,
    warningThreshold: 0.85,
    hardLimits: true,
  ),
  requestLimit: RequestLimitConfig(
    dailyLimit: 5000000,
    monthlyLimit: 50000000,
    concurrentLimit: 200,
    complexityLimit: 5000,
    maxResultSize: 500000,
    priority: RequestPriority.critical,
  ),
  enabled: true,
  createdAt: DateTime.now(),
  customSettings: {
    'dedicatedResources': true,
    'prioritySupport': true,
    'customIntegrations': ['salesforce', 'hubspot', 'slack'],
    'auditRetentionYears': 10,
    'backupFrequency': 'hourly',
  },
);

tenantManager.setTenantConfig(customTier);

Tenant-Aware Operations

Using Tenant Middleware

// Create tenant middleware for automatic limit enforcement
final tenantMiddleware = TenantMiddleware(tenantManager);

// Execute operations with tenant limits
Future<List<Product>> searchProducts(
  String tenantId,
  String userId,
  String query,
) async {
  return await tenantMiddleware.executeWithLimits(
    tenantId,
    userId,
    'vector_search',
    () async {
      // Perform vector search operation
      final results = await database.products()
          .where((p) => p.tenantId == tenantId)
          .vectorSearch(
            query: query,
            limit: 50,
          );
      
      return results;
    },
    expectedResultSize: 50,
    queryComplexity: 100,
  );
}

// Execute storage operations with limits
Future<void> createProduct(
  String tenantId,
  String userId,
  Product product,
) async {
  final productSize = _estimateObjectSize(product);
  
  await tenantMiddleware.executeStorageOperation(
    tenantId,
    'create',
    () async {
      await database.transaction((txn) async {
        await txn.save(product);
      });
    },
    additionalBytes: productSize,
    additionalObjects: 1,
    objectSize: productSize,
    objectId: product.id.toString(),
  );
}

Batch Operations with Tenant Limits

// Execute batch operations respecting tenant limits
Future<List<Product>> createProductsBatch(
  String tenantId,
  String userId,
  List<Product> products,
) async {
  // Split into smaller batches based on tenant limits
  final config = tenantManager.getTenantConfig(tenantId);
  final maxBatchSize = config?.customSettings['maxBatchSize'] as int? ?? 100;
  
  final results = <Product>[];
  
  for (int i = 0; i < products.length; i += maxBatchSize) {
    final batch = products.skip(i).take(maxBatchSize).toList();
    
    final batchResults = await tenantMiddleware.executeBatchOperation(
      tenantId,
      userId,
      'create',
      batch.map((product) => () async {
        await database.transaction((txn) async {
          await txn.save(product);
        });
        return product;
      }).toList(),
      metadata: {'batch_size': batch.length},
    );
    
    results.addAll(batchResults);
  }
  
  return results;
}

Rate Limiting and Resource Management

Implementing Rate Limiting

// Configure different rate limits for different operations
final rateLimitConfig = RateLimitConfig(
  maxRequests: 1000,
  timeWindow: Duration(minutes: 1),
  slidingWindow: true,
  burstAllowance: 100,
  operationTypes: {
    'read': 500,      // 500 reads per minute
    'write': 200,     // 200 writes per minute
    'query': 100,     // 100 queries per minute
    'vector_search': 50, // 50 vector searches per minute
  },
);

// Check rate limits before operations
bool canPerformOperation(String tenantId, String operation) {
  return tenantMiddleware.canPerformOperation(tenantId, operation);
}

// Handle rate limit exceeded scenarios
Future<T> executeWithRetry<T>(
  String tenantId,
  String userId,
  String operation,
  Future<T> Function() action,
) async {
  try {
    return await tenantMiddleware.executeWithRetry(
      tenantId,
      userId,
      operation,
      action,
      maxRetries: 3,
      baseDelay: Duration(seconds: 1),
    );
  } on TenantLimitException catch (e) {
    if (e.limitType == 'rate_limit') {
      // Handle rate limit exceeded
      throw RateLimitExceededException(
        'Rate limit exceeded for tenant $tenantId',
        retryAfter: Duration(seconds: 60),
      );
    }
    rethrow;
  }
}

Storage Quota Management

// Monitor storage usage and enforce limits
class TenantStorageManager {
  final TenantManager _tenantManager;
  
  TenantStorageManager(this._tenantManager);
  
  Future<StorageUsageReport> getStorageUsage(String tenantId) async {
    final usage = _tenantManager.getTenantUsage(tenantId);
    final config = _tenantManager.getTenantConfig(tenantId);
    
    if (config == null) {
      throw TenantNotFoundException(tenantId);
    }
    
    final storage = usage['storage'] as Map<String, dynamic>;
    final limits = usage['limits']['storageLimit'] as Map<String, dynamic>;
    
    return StorageUsageReport(
      tenantId: tenantId,
      currentBytes: storage['currentStorageBytes'] as int,
      maxBytes: limits['maxStorageBytes'] as int,
      currentObjects: storage['currentObjects'] as int,
      maxObjects: limits['maxObjects'] as int?,
      currentVectors: storage['currentVectors'] as int,
      maxVectors: limits['maxVectors'] as int?,
      usageRatio: (storage['currentStorageBytes'] as int) / 
                  (limits['maxStorageBytes'] as int),
      warnings: usage['warnings'] as List<String>,
    );
  }
  
  Future<void> enforceStorageCleanup(String tenantId) async {
    final report = await getStorageUsage(tenantId);
    
    if (report.usageRatio > 0.9) {
      // Implement cleanup strategies
      await _cleanupOldObjects(tenantId);
      await _compressLargeObjects(tenantId);
      await _archiveInactiveData(tenantId);
    }
  }
  
  Future<void> _cleanupOldObjects(String tenantId) async {
    // Remove objects older than retention period
    final retentionDays = 365; // Default retention
    final cutoffDate = DateTime.now().subtract(Duration(days: retentionDays));
    
    await database.transaction((txn) async {
      final oldObjects = await database.query((obj) =>
          obj.tenantId == tenantId && 
          obj.createdAt.isBefore(cutoffDate));
      
      for (final obj in oldObjects) {
        await txn.delete(obj);
        _tenantManager.removeStorageUsage(tenantId, obj.id);
      }
    });
  }
}

Tenant Security and Isolation

Data Partitioning

// Implement tenant-aware data access patterns
extension TenantAwareQueries on VektagrafDatabase {
  // Ensure all queries include tenant filtering
  VektagrafList<T> tenantQuery<T>(String tenantId) {
    return query<T>().where((obj) => obj.tenantId == tenantId);
  }
  
  // Tenant-specific collections
  VektagrafList<Product> tenantProducts(String tenantId) {
    return products().where((p) => p.tenantId == tenantId);
  }
  
  VektagrafList<User> tenantUsers(String tenantId) {
    return users().where((u) => u.tenantId == tenantId);
  }
  
  VektagrafList<Order> tenantOrders(String tenantId) {
    return orders().where((o) => o.tenantId == tenantId);
  }
}

// Tenant validation middleware
class TenantSecurityMiddleware {
  final TenantValidator _validator;
  
  TenantSecurityMiddleware(VektagrafConfig config) 
      : _validator = TenantValidator(config);
  
  Future<T> executeSecurely<T>(
    String tenantId,
    String userId,
    Future<T> Function() operation,
  ) async {
    // Validate tenant access
    _validator.validateTenantId(tenantId);
    
    // Validate user belongs to tenant
    await _validateUserTenantAccess(userId, tenantId);
    
    // Execute with tenant context
    return await _withTenantContext(tenantId, operation);
  }
  
  Future<void> _validateUserTenantAccess(String userId, String tenantId) async {
    // Implement user-tenant relationship validation
    final user = await database.users().findById(userId);
    if (user?.tenantId != tenantId) {
      throw UnauthorizedTenantAccessException(userId, tenantId);
    }
  }
  
  Future<T> _withTenantContext<T>(
    String tenantId,
    Future<T> Function() operation,
  ) async {
    // Set tenant context for the operation
    return await Zone.run(
      () => operation(),
      zoneValues: {'tenantId': tenantId},
    );
  }
}

Cross-Tenant Data Prevention

// Prevent accidental cross-tenant data access
class TenantDataGuard {
  static String getCurrentTenantId() {
    final tenantId = Zone.current['tenantId'] as String?;
    if (tenantId == null) {
      throw MissingTenantContextException();
    }
    return tenantId;
  }
  
  static void validateTenantAccess(String objectTenantId) {
    final currentTenantId = getCurrentTenantId();
    if (objectTenantId != currentTenantId) {
      throw CrossTenantAccessException(currentTenantId, objectTenantId);
    }
  }
}

// Enhanced model with tenant validation
@VektagrafModel()
class SecureProduct extends VektagrafObject {
  @VektagrafProperty()
  String tenantId;
  
  @VektagrafProperty()
  String name;
  
  @VektagrafProperty()
  double price;
  
  SecureProduct({
    required this.tenantId,
    required this.name,
    required this.price,
  }) {
    // Validate tenant access on creation
    TenantDataGuard.validateTenantAccess(tenantId);
  }
  
  @override
  void beforeSave() {
    super.beforeSave();
    TenantDataGuard.validateTenantAccess(tenantId);
  }
  
  @override
  void afterLoad() {
    super.afterLoad();
    TenantDataGuard.validateTenantAccess(tenantId);
  }
}

Best Practices

Tenant Configuration Management

1. Tier-Based Configuration

// Define standard tiers with clear upgrade paths
class TenantTierManager {
  static const Map<String, TenantLimitTier> standardTiers = {
    'starter': TenantLimitTier.tier1,
    'professional': TenantLimitTier.tier2,
    'business': TenantLimitTier.tier3,
    'enterprise': TenantLimitTier.tier4,
  };
  
  static TenantConfig createStandardTier(
    String tenantId,
    String name,
    String tierName,
    VektagrafConfig dbConfig,
  ) {
    final tier = standardTiers[tierName] ?? TenantLimitTier.tier1;
    
    return TenantConfig.forTier(
      tenantId: tenantId,
      name: name,
      tier: tier,
      dbConfig: dbConfig,
      customSettings: _getStandardSettings(tierName),
    );
  }
  
  static Map<String, dynamic> _getStandardSettings(String tierName) {
    switch (tierName) {
      case 'starter':
        return {
          'supportLevel': 'community',
          'backupFrequency': 'weekly',
          'auditRetentionYears': 1,
        };
      case 'professional':
        return {
          'supportLevel': 'standard',
          'backupFrequency': 'daily',
          'auditRetentionYears': 3,
        };
      case 'business':
        return {
          'supportLevel': 'priority',
          'backupFrequency': 'daily',
          'auditRetentionYears': 5,
          'dedicatedAccount': true,
        };
      case 'enterprise':
        return {
          'supportLevel': 'premium',
          'backupFrequency': 'hourly',
          'auditRetentionYears': 10,
          'dedicatedAccount': true,
          'dedicatedResources': true,
        };
      default:
        return {};
    }
  }
}

2. Dynamic Limit Adjustment

// Implement dynamic limit adjustment based on usage patterns
class DynamicLimitManager {
  final TenantManager _tenantManager;
  
  DynamicLimitManager(this._tenantManager);
  
  Future<void> adjustLimitsBasedOnUsage() async {
    final allUsage = _tenantManager.getAllTenantUsage();
    
    for (final entry in allUsage.entries) {
      final tenantId = entry.key;
      final usage = entry.value;
      
      await _adjustTenantLimits(tenantId, usage);
    }
  }
  
  Future<void> _adjustTenantLimits(
    String tenantId,
    Map<String, dynamic> usage,
  ) async {
    final config = _tenantManager.getTenantConfig(tenantId);
    if (config == null) return;
    
    final storage = usage['storage'] as Map<String, dynamic>;
    final currentUsage = storage['currentStorageBytes'] as int;
    final maxStorage = config.storageLimit.maxStorageBytes;
    
    // Auto-scale storage if approaching limits
    if (currentUsage > maxStorage * 0.8) {
      final newLimit = (maxStorage * 1.5).round();
      
      final updatedConfig = config.copyWith(
        storageLimit: config.storageLimit.copyWith(
          maxStorageBytes: newLimit,
        ),
      );
      
      _tenantManager.setTenantConfig(updatedConfig);
      
      // Log the adjustment
      await _logLimitAdjustment(tenantId, 'storage', maxStorage, newLimit);
    }
  }
  
  Future<void> _logLimitAdjustment(
    String tenantId,
    String limitType,
    int oldValue,
    int newValue,
  ) async {
    // Log limit adjustments for audit and billing
    print('Adjusted $limitType limit for tenant $tenantId: '
          '$oldValue -> $newValue');
  }
}

Performance Optimization

1. Tenant-Aware Caching

// Implement tenant-specific caching strategies
class TenantAwareCache {
  final Map<String, Map<String, dynamic>> _tenantCaches = {};
  final Duration _defaultTtl = Duration(minutes: 15);
  
  T? get<T>(String tenantId, String key) {
    final tenantCache = _tenantCaches[tenantId];
    if (tenantCache == null) return null;
    
    final entry = tenantCache[key] as _CacheEntry<T>?;
    if (entry == null || entry.isExpired) {
      tenantCache.remove(key);
      return null;
    }
    
    return entry.value;
  }
  
  void put<T>(String tenantId, String key, T value, {Duration? ttl}) {
    final tenantCache = _tenantCaches.putIfAbsent(tenantId, () => {});
    
    tenantCache[key] = _CacheEntry<T>(
      value: value,
      expiresAt: DateTime.now().add(ttl ?? _defaultTtl),
    );
    
    // Limit cache size per tenant
    if (tenantCache.length > 1000) {
      _evictOldestEntries(tenantCache);
    }
  }
  
  void invalidateTenant(String tenantId) {
    _tenantCaches.remove(tenantId);
  }
  
  void _evictOldestEntries(Map<String, dynamic> cache) {
    final entries = cache.entries.toList();
    entries.sort((a, b) => 
        (a.value as _CacheEntry).expiresAt
            .compareTo((b.value as _CacheEntry).expiresAt));
    
    // Remove oldest 20% of entries
    final toRemove = (entries.length * 0.2).round();
    for (int i = 0; i < toRemove; i++) {
      cache.remove(entries[i].key);
    }
  }
}

class _CacheEntry<T> {
  final T value;
  final DateTime expiresAt;
  
  _CacheEntry({required this.value, required this.expiresAt});
  
  bool get isExpired => DateTime.now().isAfter(expiresAt);
}

2. Resource Pool Management

// Manage shared resources across tenants
class TenantResourcePool {
  final Map<String, ResourceAllocation> _allocations = {};
  final int _totalMemoryMB;
  final int _totalCpuCores;
  
  TenantResourcePool({
    required int totalMemoryMB,
    required int totalCpuCores,
  }) : _totalMemoryMB = totalMemoryMB,
       _totalCpuCores = totalCpuCores;
  
  ResourceAllocation allocateResources(
    String tenantId,
    TenantConfig config,
  ) {
    final priority = _calculatePriority(config);
    final memoryMB = _calculateMemoryAllocation(config, priority);
    final cpuCores = _calculateCpuAllocation(config, priority);
    
    final allocation = ResourceAllocation(
      tenantId: tenantId,
      memoryMB: memoryMB,
      cpuCores: cpuCores,
      priority: priority,
      allocatedAt: DateTime.now(),
    );
    
    _allocations[tenantId] = allocation;
    return allocation;
  }
  
  double _calculatePriority(TenantConfig config) {
    switch (config.requestLimit.priority) {
      case RequestPriority.critical:
        return 1.0;
      case RequestPriority.high:
        return 0.8;
      case RequestPriority.normal:
        return 0.6;
      case RequestPriority.low:
        return 0.4;
    }
  }
  
  int _calculateMemoryAllocation(TenantConfig config, double priority) {
    final baseAllocation = _totalMemoryMB ~/ 10; // Base 10% allocation
    final priorityBonus = (baseAllocation * priority).round();
    
    return math.min(
      baseAllocation + priorityBonus,
      _totalMemoryMB ~/ 2, // Max 50% for any single tenant
    );
  }
  
  double _calculateCpuAllocation(TenantConfig config, double priority) {
    final baseAllocation = _totalCpuCores / 10; // Base 10% allocation
    final priorityBonus = baseAllocation * priority;
    
    return math.min(
      baseAllocation + priorityBonus,
      _totalCpuCores / 2, // Max 50% for any single tenant
    );
  }
}

class ResourceAllocation {
  final String tenantId;
  final int memoryMB;
  final double cpuCores;
  final double priority;
  final DateTime allocatedAt;
  
  ResourceAllocation({
    required this.tenantId,
    required this.memoryMB,
    required this.cpuCores,
    required this.priority,
    required this.allocatedAt,
  });
}

Advanced Topics

Tenant Analytics and Monitoring

Usage Analytics Dashboard

// Comprehensive tenant analytics system
class TenantAnalytics {
  final TenantManager _tenantManager;
  final VektagrafDatabase _database;
  
  TenantAnalytics(this._tenantManager, this._database);
  
  Future<TenantAnalyticsReport> generateReport(
    String tenantId, {
    DateTime? startDate,
    DateTime? endDate,
  }) async {
    final start = startDate ?? DateTime.now().subtract(Duration(days: 30));
    final end = endDate ?? DateTime.now();
    
    final usage = _tenantManager.getTenantUsage(tenantId);
    final config = _tenantManager.getTenantConfig(tenantId);
    
    if (config == null) {
      throw TenantNotFoundException(tenantId);
    }
    
    return TenantAnalyticsReport(
      tenantId: tenantId,
      tenantName: config.name,
      tier: config.tier.name,
      reportPeriod: DateRange(start, end),
      usageMetrics: await _calculateUsageMetrics(tenantId, start, end),
      performanceMetrics: await _calculatePerformanceMetrics(tenantId, start, end),
      costAnalysis: await _calculateCostAnalysis(tenantId, usage, config),
      recommendations: await _generateRecommendations(tenantId, usage, config),
    );
  }
  
  Future<UsageMetrics> _calculateUsageMetrics(
    String tenantId,
    DateTime start,
    DateTime end,
  ) async {
    // Query historical usage data
    final metrics = await _database.systemMetrics()
        .where((m) => m.tenantId?.toString() == tenantId)
        .where((m) => m.timestamp.isAfter(start))
        .where((m) => m.timestamp.isBefore(end))
        .toList();
    
    return UsageMetrics(
      totalRequests: _sumMetric(metrics, 'vektagraf_database_operations_total'),
      totalStorage: _latestMetric(metrics, 'vektagraf_memory_usage_bytes'),
      averageLatency: _averageMetric(metrics, 'vektagraf_query_duration_seconds'),
      errorRate: _calculateErrorRate(metrics),
      peakConcurrency: _maxMetric(metrics, 'concurrent_requests'),
    );
  }
  
  Future<PerformanceMetrics> _calculatePerformanceMetrics(
    String tenantId,
    DateTime start,
    DateTime end,
  ) async {
    // Analyze performance trends
    final logs = await _database.systemLogs()
        .where((l) => l.tenantId?.toString() == tenantId)
        .where((l) => l.timestamp.isAfter(start))
        .where((l) => l.timestamp.isBefore(end))
        .toList();
    
    return PerformanceMetrics(
      averageResponseTime: _calculateAverageResponseTime(logs),
      throughputPerSecond: _calculateThroughput(logs),
      cacheHitRate: _calculateCacheHitRate(logs),
      indexEfficiency: _calculateIndexEfficiency(logs),
    );
  }
  
  Future<CostAnalysis> _calculateCostAnalysis(
    String tenantId,
    Map<String, dynamic> usage,
    TenantConfig config,
  ) async {
    final storage = usage['storage'] as Map<String, dynamic>;
    final requests = usage['usage'] as Map<String, dynamic>;
    
    return CostAnalysis(
      storageGBHours: _calculateStorageGBHours(storage),
      requestUnits: _calculateRequestUnits(requests),
      bandwidthGB: _calculateBandwidthUsage(tenantId),
      estimatedMonthlyCost: _estimateMonthlyCost(config.tier),
    );
  }
  
  Future<List<TenantRecommendation>> _generateRecommendations(
    String tenantId,
    Map<String, dynamic> usage,
    TenantConfig config,
  ) async {
    final recommendations = <TenantRecommendation>[];
    
    // Analyze usage patterns and suggest optimizations
    final storage = usage['storage'] as Map<String, dynamic>;
    final storageRatio = (storage['currentStorageBytes'] as int) / 
                        config.storageLimit.maxStorageBytes;
    
    if (storageRatio > 0.8) {
      recommendations.add(TenantRecommendation(
        type: RecommendationType.tierUpgrade,
        title: 'Consider Storage Upgrade',
        description: 'Storage usage is at ${(storageRatio * 100).toStringAsFixed(1)}% of limit',
        impact: 'Prevent storage limit issues',
        priority: RecommendationPriority.high,
      ));
    }
    
    // Check for tier upgrade opportunities
    if (_shouldRecommendTierUpgrade(usage, config)) {
      recommendations.add(TenantRecommendation(
        type: RecommendationType.tierUpgrade,
        title: 'Tier Upgrade Recommended',
        description: 'Usage patterns suggest benefits from higher tier',
        impact: 'Better performance and higher limits',
        priority: RecommendationPriority.medium,
      ));
    }
    
    return recommendations;
  }
}

Real-Time Monitoring

// Real-time tenant monitoring system
class TenantMonitor {
  final TenantManager _tenantManager;
  final StreamController<TenantEvent> _eventController;
  
  TenantMonitor(this._tenantManager) 
      : _eventController = StreamController<TenantEvent>.broadcast();
  
  Stream<TenantEvent> get events => _eventController.stream;
  
  void startMonitoring() {
    Timer.periodic(Duration(seconds: 30), (_) => _checkTenantHealth());
    Timer.periodic(Duration(minutes: 5), (_) => _checkLimitApproaches());
  }
  
  Future<void> _checkTenantHealth() async {
    final allUsage = _tenantManager.getAllTenantUsage();
    
    for (final entry in allUsage.entries) {
      final tenantId = entry.key;
      final usage = entry.value;
      
      await _checkTenantStatus(tenantId, usage);
    }
  }
  
  Future<void> _checkTenantStatus(
    String tenantId,
    Map<String, dynamic> usage,
  ) async {
    final warnings = usage['warnings'] as List<String>;
    
    if (warnings.isNotEmpty) {
      _eventController.add(TenantEvent(
        type: TenantEventType.warning,
        tenantId: tenantId,
        message: 'Tenant warnings: ${warnings.join(', ')}',
        timestamp: DateTime.now(),
        metadata: {'warnings': warnings},
      ));
    }
    
    // Check for performance issues
    final storage = usage['storage'] as Map<String, dynamic>;
    final storageRatio = storage['currentStorageBytes'] / 
                        storage['maxStorageBytes'];
    
    if (storageRatio > 0.95) {
      _eventController.add(TenantEvent(
        type: TenantEventType.critical,
        tenantId: tenantId,
        message: 'Storage limit critically exceeded',
        timestamp: DateTime.now(),
        metadata: {'storage_ratio': storageRatio},
      ));
    }
  }
  
  Future<void> _checkLimitApproaches() async {
    // Check for tenants approaching their limits
    final allUsage = _tenantManager.getAllTenantUsage();
    
    for (final entry in allUsage.entries) {
      final tenantId = entry.key;
      final usage = entry.value;
      
      await _checkApproachingLimits(tenantId, usage);
    }
  }
}

enum TenantEventType { info, warning, critical }

class TenantEvent {
  final TenantEventType type;
  final String tenantId;
  final String message;
  final DateTime timestamp;
  final Map<String, dynamic> metadata;
  
  TenantEvent({
    required this.type,
    required this.tenantId,
    required this.message,
    required this.timestamp,
    required this.metadata,
  });
}

Tenant Migration and Scaling

Tenant Data Migration

// Tenant data migration utilities
class TenantMigration {
  final VektagrafDatabase _sourceDb;
  final VektagrafDatabase _targetDb;
  
  TenantMigration(this._sourceDb, this._targetDb);
  
  Future<void> migrateTenant(
    String tenantId, {
    bool includeHistory = true,
    DateTime? cutoffDate,
  }) async {
    final migrationId = VektagrafId.generate();
    
    try {
      await _logMigrationStart(migrationId, tenantId);
      
      // Export tenant data
      final exportData = await _exportTenantData(
        tenantId,
        includeHistory: includeHistory,
        cutoffDate: cutoffDate,
      );
      
      // Import to target database
      await _importTenantData(tenantId, exportData);
      
      // Verify migration
      await _verifyMigration(tenantId, exportData);
      
      await _logMigrationComplete(migrationId, tenantId);
    } catch (e) {
      await _logMigrationError(migrationId, tenantId, e);
      rethrow;
    }
  }
  
  Future<TenantExportData> _exportTenantData(
    String tenantId, {
    required bool includeHistory,
    DateTime? cutoffDate,
  }) async {
    final objects = <VektagrafObject>[];
    final metadata = <String, dynamic>{};
    
    // Export all tenant objects
    await _sourceDb.transaction((txn) async {
      final tenantObjects = await _sourceDb.query((obj) => 
          obj.tenantId == tenantId &&
          (cutoffDate == null || obj.createdAt.isAfter(cutoffDate)));
      
      objects.addAll(tenantObjects);
    });
    
    // Export tenant configuration
    final config = await _exportTenantConfig(tenantId);
    
    return TenantExportData(
      tenantId: tenantId,
      objects: objects,
      configuration: config,
      metadata: metadata,
      exportedAt: DateTime.now(),
    );
  }
  
  Future<void> _importTenantData(
    String tenantId,
    TenantExportData exportData,
  ) async {
    await _targetDb.transaction((txn) async {
      // Import tenant configuration
      await _importTenantConfig(exportData.configuration);
      
      // Import objects in batches
      const batchSize = 1000;
      for (int i = 0; i < exportData.objects.length; i += batchSize) {
        final batch = exportData.objects.skip(i).take(batchSize);
        
        for (final obj in batch) {
          await txn.save(obj);
        }
      }
    });
  }
}

class TenantExportData {
  final String tenantId;
  final List<VektagrafObject> objects;
  final TenantConfig configuration;
  final Map<String, dynamic> metadata;
  final DateTime exportedAt;
  
  TenantExportData({
    required this.tenantId,
    required this.objects,
    required this.configuration,
    required this.metadata,
    required this.exportedAt,
  });
}

Summary

This chapter covered Vektagraf's comprehensive multi-tenant architecture, including:

Key Takeaways

  1. Tenant Isolation: Complete data and security separation with configurable limits
  2. Resource Management: Flexible rate limiting, storage quotas, and performance controls
  3. Security: Tenant-aware data access patterns and cross-tenant prevention
  4. Monitoring: Real-time analytics and usage tracking per tenant
  5. Scalability: Dynamic resource allocation and migration capabilities

Next Steps

  • Chapter 12: Learn about transport layer integration for distributed multi-tenant systems
  • Chapter 13: Explore monitoring and observability for multi-tenant applications
  • Chapter 14: Discover performance tuning strategies for multi-tenant workloads