Capacity Planning

Capacity Planning

Accurate capacity planning for password hashing systems requires understanding both steady-state and peak loads. Authentication patterns often show significant variance—morning login surges, post-incident password resets, and seasonal variations all impact load. Plan for peak capacity while optimizing for typical load to balance cost and performance.

Memory requirements dominate capacity planning for modern password hashing. Calculate memory needs based on concurrent authentications, not just throughput. A system handling 1,000 authentications per second with 100ms hash time needs memory for 100 concurrent operations. With 64MB per operation, this requires 6.4GB just for password hashing, plus overhead for application memory.

class CapacityPlanner:
    """Capacity planning for password hashing infrastructure"""
    
    def __init__(self):
        self.metrics = {}
        
    def calculate_requirements(self, 
                             peak_auth_per_second: float,
                             hash_time_ms: float,
                             memory_per_hash_mb: float,
                             availability_target: float = 0.999) -> Dict:
        """Calculate infrastructure requirements"""
        
        # Concurrent operations
        concurrent_ops = peak_auth_per_second * (hash_time_ms / 1000)
        
        # Memory requirements
        memory_required_mb = concurrent_ops * memory_per_hash_mb
        
        # Add overhead (OS, application, caching)
        total_memory_mb = memory_required_mb * 1.5
        
        # CPU requirements (rough estimate)
        # Assume 1 vCPU can handle 10 concurrent hashes
        vcpus_required = concurrent_ops / 10
        
        # Add redundancy for availability
        redundancy_factor = 1 / (1 - availability_target)
        
        # Server sizing
        servers_needed = max(2, int(redundancy_factor))  # Minimum 2 for HA
        
        memory_per_server = total_memory_mb / servers_needed
        vcpus_per_server = vcpus_required / servers_needed
        
        # Cost estimation (AWS c5.xlarge example)
        # 4 vCPU, 8GB RAM = $0.17/hour
        if memory_per_server <= 8192 and vcpus_per_server <= 4:
            instance_type = 'c5.xlarge'
            hourly_cost = 0.17
        elif memory_per_server <= 16384 and vcpus_per_server <= 8:
            instance_type = 'c5.2xlarge'
            hourly_cost = 0.34
        elif memory_per_server <= 32768 and vcpus_per_server <= 16:
            instance_type = 'c5.4xlarge'
            hourly_cost = 0.68
        else:
            instance_type = 'c5.9xlarge'
            hourly_cost = 1.53
        
        monthly_cost = hourly_cost * 24 * 30 * servers_needed
        
        return {
            'peak_auth_per_second': peak_auth_per_second,
            'concurrent_operations': concurrent_ops,
            'memory_required_mb': memory_required_mb,
            'total_memory_mb': total_memory_mb,
            'vcpus_required': vcpus_required,
            'servers_needed': servers_needed,
            'memory_per_server_mb': memory_per_server,
            'vcpus_per_server': vcpus_per_server,
            'recommended_instance': instance_type,
            'estimated_monthly_cost': monthly_cost,
            'headroom': {
                'memory': (1 - memory_required_mb / total_memory_mb) * 100,
                'concurrent_ops': (total_memory_mb / memory_per_hash_mb) - concurrent_ops
            }
        }
    
    def simulate_load_patterns(self, base_load: float, 
                             duration_hours: int = 168) -> Dict:
        """Simulate weekly load patterns"""
        
        import numpy as np
        
        hours = np.arange(duration_hours)
        
        # Daily pattern (peak at 9am and 2pm)
        daily_pattern = np.sin((hours % 24 - 6) * np.pi / 12) + 1
        daily_pattern = np.maximum(0.3, daily_pattern)
        
        # Weekly pattern (lower on weekends)
        weekly_pattern = np.where((hours // 24) % 7 < 5, 1.0, 0.4)
        
        # Combined pattern with noise
        load_pattern = base_load * daily_pattern * weekly_pattern
        load_pattern += np.random.normal(0, base_load * 0.1, duration_hours)
        load_pattern = np.maximum(0, load_pattern)
        
        # Add spikes (e.g., security incidents)
        spike_hours = [41, 89, 137]  # Tuesday 5pm, Thursday 5pm, Monday 5pm
        for spike in spike_hours:
            if spike < duration_hours:
                load_pattern[spike:spike+2] *= 3
        
        return {
            'hours': hours,
            'load': load_pattern,
            'peak_load': np.max(load_pattern),
            'average_load': np.mean(load_pattern),
            'p95_load': np.percentile(load_pattern, 95),
            'p99_load': np.percentile(load_pattern, 99)
        }
    
    def recommend_scaling_strategy(self, load_analysis: Dict) -> Dict:
        """Recommend auto-scaling configuration"""
        
        base_capacity = load_analysis['average_load']
        peak_capacity = load_analysis['peak_load']
        
        recommendations = {
            'auto_scaling': {
                'min_instances': 2,
                'desired_instances': int(np.ceil(base_capacity / 100)),
                'max_instances': int(np.ceil(peak_capacity / 100)),
                'scale_up_threshold': 70,  # CPU %
                'scale_down_threshold': 30,
                'scale_up_cooldown': 60,  # seconds
                'scale_down_cooldown': 300
            },
            'caching_strategy': {
                'token_lifetime_minutes': 15,
                'session_lifetime_minutes': 60,
                'enable_negative_cache': True,
                'negative_cache_duration': 300
            },
            'rate_limiting': {
                'per_user_per_minute': 10,
                'per_ip_per_minute': 50,
                'global_per_minute': 10000
            }
        }
        
        # Adjust based on load patterns
        if peak_capacity > base_capacity * 3:
            recommendations['pre_scaling'] = {
                'enabled': True,
                'schedule': 'weekdays at 8:30am',
                'scale_to': int(np.ceil(peak_capacity / 100))
            }
        
        return recommendations