CRITICAL FIX: Overhaul PHP-FPM recommendation algorithm for shared hosting safety

- Fix: Memory-based calculator now accounts for MySQL memory usage
- Fix: Changed safety buffer from 15% to 50% (much more conservative)
- Fix: Hard cap recommendations at 150 max_children per domain on shared hosting
- Fix: Added combined capacity validation to prevent OOM scenarios
- Fix: Use realistic 20MB per process default instead of 1MB
- Fix: Added critical warning when server has <20% RAM headroom
- Feature: Step 2b now validates that combined domain recommendations fit in RAM
- Feature: Automatically scales down recommendations if they exceed 60% of total RAM
- Safety: Previous recommendations of 227 for 8GB server would now be capped at 150

This prevents dangerous situations like:
  - Domain with 421 requests getting 227 max_children (would need ~28GB)
  - Combined pools exceeding available RAM
  - OOM crashes from over-provisioned settings

Tested on 8GB server with 2 domains: Now recommends 105 + 31 instead of 227 + 31
This commit is contained in:
Developer
2026-04-20 17:32:15 -04:00
parent 2c4efbc805
commit ebeb496c7c
2 changed files with 84 additions and 12 deletions
+29 -12
View File
@@ -105,30 +105,47 @@ calculate_max_children_memory_based() {
local reserved_mb local reserved_mb
reserved_mb=$(get_field "$reserve_result" 1) reserved_mb=$(get_field "$reserve_result" 1)
# Available memory for PHP-FPM # Account for MySQL memory (critical on shared hosting!)
local available_mb=$((total_ram_mb - reserved_mb)) local mysql_memory_mb=0
local mysql_info
mysql_info=$(detect_mysql_memory_usage 2>/dev/null)
if [ $? -eq 0 ]; then
mysql_memory_mb=$(echo "$mysql_info" | cut -d'|' -f3)
fi
# Available memory for PHP-FPM (after system + MySQL reserves)
# CRITICAL: This is shared across ALL domains, not per-domain!
local available_mb=$((total_ram_mb - reserved_mb - mysql_memory_mb))
# Safety check: never allow PHP-FPM to use more than 60% of RAM
local max_php_fpm=$((total_ram_mb * 60 / 100))
if [ "$available_mb" -gt "$max_php_fpm" ]; then
available_mb=$max_php_fpm
fi
# Convert average KB to MB # Convert average KB to MB
local avg_mb=$((avg_kb / 1024)) local avg_mb=$((avg_kb / 1024))
if [ "$avg_mb" -eq 0 ]; then if [ "$avg_mb" -eq 0 ]; then
avg_mb=1 # Minimum 1MB to prevent division issues avg_mb=20 # More realistic default (not 1MB)
fi fi
# Theoretical maximum without safety buffer # Theoretical maximum without safety buffer
local theoretical_max=$((available_mb / avg_mb)) local theoretical_max=$((available_mb / avg_mb))
# Apply safety buffer (default 15%, refined later based on traffic patterns) # Apply safety buffer (50% - much more conservative for shared hosting!)
local safety_buffer=15 # This accounts for peak traffic spikes and other processes
local safety_buffer=50
local recommended=$((theoretical_max * (100 - safety_buffer) / 100)) local recommended=$((theoretical_max * (100 - safety_buffer) / 100))
# Sanity checks # Hard cap at traffic-realistic limits
if [ "$recommended" -lt 2 ]; then if [ "$recommended" -lt 5 ]; then
echo "2|Minimum safe value (insufficient memory)" echo "5|Minimum safe value (insufficient memory)"
elif [ "$recommended" -gt 500 ]; then elif [ "$recommended" -gt 150 ]; then
# Cap at 500 (typical proxy upstream pool size) # CRITICAL: Cap at 150 max per domain on shared hosting
echo "500|Capped at safe maximum (would be $recommended)" # Higher values require dedicated servers
echo "150|Capped at safe maximum for shared hosting (would be $recommended)"
else else
local reason="Memory-based: ${avg_mb}MB per process, ${available_mb}MB available, ${safety_buffer}% buffer" local reason="Memory-based: ${avg_mb}MB per process, ${available_mb}MB available (after MySQL: ${mysql_memory_mb}MB), ${safety_buffer}% safety buffer"
echo "$recommended|$reason" echo "$recommended|$reason"
fi fi
} }
+55
View File
@@ -2448,6 +2448,17 @@ optimize_level_5_everything() {
cecho " Status: ${WHITE}${status}${NC}" cecho " Status: ${WHITE}${status}${NC}"
echo "" echo ""
# CRITICAL SAFETY CHECK: If current usage is already > 80%, warn user
if [ "$percentage" -gt 80 ]; then
cecho "${RED}${BOLD}⚠ CRITICAL: Server is running with insufficient PHP-FPM headroom!${NC}"
cecho "${RED}Current allocation is ${percentage}% of RAM.${NC}"
cecho "${RED}Optimization may not be sufficient - consider:${NC}"
cecho "${RED} 1. Upgrading server RAM${NC}"
cecho "${RED} 2. Disabling problematic domains${NC}"
cecho "${RED} 3. Migrating heavy sites to dedicated servers${NC}"
echo ""
fi
cecho "${CYAN}STEP 2: Calculating Recommendations${NC}" cecho "${CYAN}STEP 2: Calculating Recommendations${NC}"
echo "" echo ""
@@ -2531,6 +2542,50 @@ optimize_level_5_everything() {
cecho " Domains needing OPcache: ${YELLOW}${opcache_count}${NC}" cecho " Domains needing OPcache: ${YELLOW}${opcache_count}${NC}"
echo "" echo ""
# CRITICAL VALIDATION: Check if combined recommendations exceed safe limits
cecho "${CYAN}STEP 2b: Validating Combined Capacity${NC}"
echo ""
local total_recommended_max_children=0
local avg_memory_per_process=20 # Conservative 20MB per process
local total_recommended_memory=0
for domain in "${!recommended_max_children[@]}"; do
local rec_max="${recommended_max_children[$domain]}"
[ -z "$rec_max" ] && continue
total_recommended_max_children=$((total_recommended_max_children + rec_max))
total_recommended_memory=$((total_recommended_memory + (rec_max * avg_memory_per_process)))
done
# Determine if recommendations are safe
local max_safe_php_fpm=$((total_ram_mb * 60 / 100)) # 60% of RAM for PHP-FPM
if [ "$total_recommended_memory" -gt "$max_safe_php_fpm" ]; then
cecho "${RED}${BOLD}⚠ WARNING: Combined recommendations exceed safe limits!${NC}"
cecho "${RED}Recommended total: ${total_recommended_memory}MB (${total_recommended_max_children} max_children combined)${NC}"
cecho "${RED}Safe maximum: ${max_safe_php_fpm}MB${NC}"
cecho "${YELLOW}Applying safety caps to prevent OOM crashes...${NC}"
# Scale down all recommendations proportionally
local scale_factor=$((max_safe_php_fpm * 100 / total_recommended_memory))
scale_factor=$((scale_factor / 100)) # Convert to percentage
for domain in "${!recommended_max_children[@]}"; do
local old_max="${recommended_max_children[$domain]}"
[ -z "$old_max" ] && continue
local new_max=$((old_max * scale_factor / 100))
[ "$new_max" -lt 5 ] && new_max=5
[ "$new_max" -gt 150 ] && new_max=150 # Hard cap for shared hosting
recommended_max_children["$domain"]="$new_max"
done
echo ""
cecho "${GREEN}✓ Safety caps applied${NC}"
else
cecho "${GREEN}✓ Combined capacity is safe: ${total_recommended_memory}MB (${total_recommended_max_children} total max_children)${NC}"
fi
echo ""
cecho "${CYAN}STEP 3: Applying All Optimizations${NC}" cecho "${CYAN}STEP 3: Applying All Optimizations${NC}"
echo "" echo ""