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