Resource Constraints
Resource Constraints
System resource management and constraint enforcement for safe autoscaling.
Overview
Resource constraints ensure autoscaling doesn't:
- Exhaust system memory
- Overload CPU
- Exceed budget limits
- Violate infrastructure capacity
Goal: Scale efficiently while respecting physical and economic limits.
Constraint Types
1. System Resource Constraints
Prevent resource exhaustion:
'resource_limits' => [
'max_total_workers' => 100, // Global worker limit
'max_memory_percent' => 80, // Max 80% memory usage
'max_cpu_percent' => 90, // Max 90% CPU usage
'reserved_memory_mb' => 1024, // Reserve 1GB for system
'max_workers_per_queue' => 50, // Per-queue limit
],
2. Configuration Constraints
User-defined limits:
'queues' => [
[
'min_workers' => 1, // Never scale below
'max_workers' => 20, // Never scale above
'max_worker_memory' => 512, // MB per worker
],
],
3. Economic Constraints
Cost-based limits:
'cost_limits' => [
'max_hourly_cost' => 100.00, // Budget cap
'worker_cost_per_hour' => 0.50, // Cost per worker
'alert_threshold' => 80.00, // Alert at 80%
],
Implementation
System Resource Checker
class ResourceConstraintChecker
{
public function canAddWorkers(int $additionalWorkers, QueueConfiguration $config): bool
{
// Check 1: Total worker limit
if (!$this->checkTotalWorkerLimit($additionalWorkers)) {
return false;
}
// Check 2: Memory availability
if (!$this->checkMemoryAvailability($additionalWorkers, $config)) {
return false;
}
// Check 3: CPU capacity
if (!$this->checkCpuCapacity($additionalWorkers)) {
return false;
}
// Check 4: Budget limit
if (!$this->checkBudgetLimit($additionalWorkers)) {
return false;
}
return true;
}
private function checkTotalWorkerLimit(int $additional): bool
{
$current = $this->getCurrentTotalWorkers();
$limit = config('queue-autoscale.resource_limits.max_total_workers');
return ($current + $additional) <= $limit;
}
private function checkMemoryAvailability(int $additional, QueueConfiguration $config): bool
{
$systemMemoryMb = $this->getSystemMemoryMb();
$usedMemoryMb = $this->getUsedMemoryMb();
$reservedMemoryMb = config('queue-autoscale.resource_limits.reserved_memory_mb');
$workerMemoryMb = $config->workerMemory ?? 256;
$availableMemoryMb = $systemMemoryMb - $usedMemoryMb - $reservedMemoryMb;
$requiredMemoryMb = $additional * $workerMemoryMb;
return $requiredMemoryMb <= $availableMemoryMb;
}
private function checkCpuCapacity(int $additional): bool
{
$currentCpuPercent = $this->getCurrentCpuPercent();
$maxCpuPercent = config('queue-autoscale.resource_limits.max_cpu_percent');
$cpuPerWorker = $this->estimateCpuPerWorker();
$projectedCpu = $currentCpuPercent + ($additional * $cpuPerWorker);
return $projectedCpu <= $maxCpuPercent;
}
private function checkBudgetLimit(int $additional): bool
{
$currentCost = $this->getCurrentHourlyCost();
$maxCost = config('queue-autoscale.cost_limits.max_hourly_cost');
$workerCost = config('queue-autoscale.cost_limits.worker_cost_per_hour');
$projectedCost = $currentCost + ($additional * $workerCost);
return $projectedCost <= $maxCost;
}
}
Constraint Enforcement Policy
class ResourceConstraintPolicy implements ScalingPolicyContract
{
public function beforeScaling(object $metrics, QueueConfiguration $config, int $currentWorkers): void
{
// Check current resource usage
$memoryPercent = $this->getMemoryUsagePercent();
$cpuPercent = $this->getCpuUsagePercent();
if ($memoryPercent > 90 || $cpuPercent > 95) {
logger()->warning('System resources critically high', [
'memory_percent' => $memoryPercent,
'cpu_percent' => $cpuPercent,
'queue' => $config->queue,
]);
}
}
public function afterScaling(ScalingDecision $decision, QueueConfiguration $config, int $currentWorkers): void
{
$workerChange = $decision->targetWorkers - $currentWorkers;
if ($workerChange <= 0) {
return; // Scaling down, no constraint check needed
}
// Enforce constraints on scale-up
$checker = new ResourceConstraintChecker();
if (!$checker->canAddWorkers($workerChange, $config)) {
// Reduce target to maximum allowed
$maxAllowed = $this->calculateMaxAllowedWorkers($currentWorkers, $config);
// Modify decision (using reflection for readonly properties)
$this->modifyDecision($decision, $maxAllowed);
logger()->warning('Scaling limited by resource constraints', [
'requested_workers' => $decision->targetWorkers,
'allowed_workers' => $maxAllowed,
'queue' => $config->queue,
]);
}
}
private function calculateMaxAllowedWorkers(int $current, QueueConfiguration $config): int
{
$limits = [
$this->maxByTotalLimit($current),
$this->maxByMemoryLimit($current, $config),
$this->maxByCpuLimit($current),
$this->maxByBudgetLimit($current),
$config->maxWorkers, // Configuration limit
];
return min($limits);
}
}
Examples
Example 1: Memory Constraint
Scenario:
- System memory: 16 GB
- Used memory: 12 GB
- Reserved memory: 2 GB
- Available: 2 GB
- Worker memory: 512 MB
- Target workers: 10
Calculation:
$availableMb = 16384 - 12288 - 2048 = 2048 MB
$requiredMb = 10 workers × 512 MB = 5120 MB
2048 < 5120 // NOT ENOUGH MEMORY
$maxWorkers = floor(2048 / 512) = 4 workers
Scale to 4 workers instead of 10.
Example 2: CPU Constraint
Scenario:
- Current CPU: 70%
- Max CPU: 90%
- CPU per worker: 5%
- Target workers: 8 additional
Calculation:
$projectedCpu = 70% + (8 × 5%) = 110%
110% > 90% // EXCEEDS LIMIT
$availableCpu = 90% - 70% = 20%
$maxAdditionalWorkers = floor(20% / 5%) = 4 workers
Add 4 workers instead of 8.
Example 3: Budget Constraint
Scenario:
- Hourly budget: $100
- Current cost: $80
- Cost per worker: $0.50
- Target: 50 additional workers
Calculation:
$projectedCost = $80 + (50 × $0.50) = $105
$105 > $100 // EXCEEDS BUDGET
$availableBudget = $100 - $80 = $20
$maxWorkers = floor($20 / $0.50) = 40 workers
Add 40 workers instead of 50.
Example 4: Multiple Constraints
Scenario:
- Memory allows: 10 workers
- CPU allows: 8 workers
- Budget allows: 12 workers
- Config max: 20 workers
Calculation:
$maxWorkers = min(10, 8, 12, 20) = 8 workers
Most restrictive constraint wins (CPU).
Advanced Constraint Handling
Dynamic Reserve Calculation
Adjust reserved resources based on load:
public function calculateDynamicReserve(): int
{
$baseReserve = 1024; // 1 GB base
$currentLoad = $this->getCurrentSystemLoad();
// Increase reserve during high load
if ($currentLoad > 0.8) {
return (int) ($baseReserve * 1.5); // 1.5 GB
}
return $baseReserve;
}
Predictive Resource Planning
Forecast resource needs:
public function predictResourceNeeds(object $metrics, int $targetWorkers, QueueConfiguration $config): array
{
$workerMemory = $config->workerMemory ?? 256;
$estimatedCpuPerWorker = 5; // percentage
return [
'memory_mb' => $targetWorkers * $workerMemory,
'cpu_percent' => $targetWorkers * $estimatedCpuPerWorker,
'cost' => $targetWorkers * $this->workerCostPerHour,
];
}
Constraint Violation Handling
Handle resource exhaustion gracefully:
public function handleConstraintViolation(string $constraint, int $requested, int $allowed): void
{
logger()->error('Resource constraint violated', [
'constraint' => $constraint,
'requested_workers' => $requested,
'allowed_workers' => $allowed,
'system_memory_mb' => $this->getSystemMemoryMb(),
'used_memory_mb' => $this->getUsedMemoryMb(),
'cpu_percent' => $this->getCurrentCpuPercent(),
]);
// Alert operations team
$this->alert->send([
'severity' => 'warning',
'title' => "Autoscaling limited by {$constraint}",
'message' => "Requested {$requested} workers, limited to {$allowed}",
]);
// Optionally trigger infrastructure scaling
if ($constraint === 'memory' || $constraint === 'cpu') {
event(new InfrastructureScalingNeeded($constraint, $requested));
}
}
Constraint Priorities
When multiple constraints conflict:
public function applyConstraintPriorities(ScalingDecision $decision, QueueConfiguration $config): int
{
$limits = [
// Priority 1: Safety limits (prevent system crash)
'memory' => $this->maxByMemoryLimit($decision->targetWorkers, $config),
'cpu' => $this->maxByCpuLimit($decision->targetWorkers),
// Priority 2: Hard configuration limits
'config_max' => $config->maxWorkers,
'total_workers' => $this->maxByTotalWorkerLimit(),
// Priority 3: Soft economic limits
'budget' => $this->maxByBudgetLimit($decision->targetWorkers),
];
// Safety limits are mandatory
$safeLimits = min($limits['memory'], $limits['cpu']);
// Configuration limits
$configLimits = min($limits['config_max'], $limits['total_workers']);
// Take most restrictive of safety and config
$hardLimit = min($safeLimits, $configLimits);
// Budget is advisory - can exceed with warning
if ($limits['budget'] < $hardLimit) {
logger()->warning('Exceeding budget limit for SLA compliance', [
'budget_allows' => $limits['budget'],
'scaling_to' => $hardLimit,
]);
}
return $hardLimit;
}
Performance Characteristics
Time Complexity
- O(1): Constant time constraint checks
- Very fast, negligible overhead
Space Complexity
- O(1): No additional storage required
- May cache system metrics briefly
Overhead
- Minimal: <1ms for all constraint checks
- Can be performed on every evaluation
Best Practices
- Set appropriate reserves: 10-20% system resources reserved
- Monitor constraint hits: Track which constraints are activated
- Alert on violations: Notify when scaling is limited
- Plan capacity: Provision infrastructure ahead of known peaks
- Use soft limits: Warning thresholds before hard limits
- Test constraints: Validate limits work as expected
Integration with Hybrid Strategy
class HybridPredictiveStrategy
{
public function calculateTargetWorkers(object $metrics, QueueConfiguration $config): int
{
// Calculate ideal workers (unconstrained)
$idealWorkers = $this->calculateIdeal($metrics, $config);
// Apply resource constraints
$constrainedWorkers = $this->applyConstraints($idealWorkers, $config);
// Log if constrained
if ($constrainedWorkers < $idealWorkers) {
logger()->info('Scaling limited by constraints', [
'ideal_workers' => $idealWorkers,
'constrained_workers' => $constrainedWorkers,
'queue' => $config->queue,
]);
}
return $constrainedWorkers;
}
private function applyConstraints(int $ideal, QueueConfiguration $config): int
{
$checker = app(ResourceConstraintChecker::class);
$current = $this->getCurrentWorkers($config);
$additional = $ideal - $current;
if ($additional <= 0) {
return $ideal; // Scaling down, no constraints
}
if (!$checker->canAddWorkers($additional, $config)) {
return $checker->calculateMaxAllowed($current, $config);
}
return $ideal;
}
}
Monitoring Constraint Impact
Track how often constraints limit scaling:
DB::table('constraint_events')->insert([
'queue' => $config->queue,
'constraint_type' => 'memory',
'requested_workers' => $requested,
'allowed_workers' => $allowed,
'memory_available_mb' => $availableMemory,
'cpu_percent' => $currentCpu,
'timestamp' => now(),
]);
// Query for analysis
$constraintFrequency = DB::table('constraint_events')
->where('timestamp', '>=', now()->subHours(24))
->groupBy('constraint_type')
->selectRaw('constraint_type, COUNT(*) as occurrences')
->get();
See Also
- Little's Law - Base calculation
- Trend Prediction - Predictive scaling
- Backlog Drain - SLA protection
- Performance Tuning - Performance tuning