Chapter 13: 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
- Tenant Isolation: Complete data and security separation with configurable limits
- Resource Management: Flexible rate limiting, storage quotas, and performance controls
- Security: Tenant-aware data access patterns and cross-tenant prevention
- Monitoring: Real-time analytics and usage tracking per tenant
- Scalability: Dynamic resource allocation and migration capabilities
Next Steps
- Chapter 14: Learn about transport layer integration for distributed multi-tenant systems
- Chapter 15: Explore monitoring and observability for multi-tenant applications
- Chapter 16: Discover performance tuning strategies for multi-tenant workloads
No Comments