Performance Optimization #

Complete guide to optimizing Server Monitor plugin performance for high-scale deployments.

Performance Overview #

The Server Monitor plugin is designed to handle thousands of endpoints efficiently through:

  • Queue-based processing for non-blocking operations
  • Plan-based prioritization (premium vs free)
  • Worker scaling for high-throughput monitoring
  • Database optimization with proper indexing
  • Caching strategies for reduced database load

Queue Performance #

Worker Configuration #

Optimal Worker Allocation:

# /etc/supervisor/conf.d/servermonitor-workers.conf

# Premium workers (1-minute SLA)
[program:servermonitor-premium]
numprocs=4                    # Scale based on premium server count
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/site/artisan queue:work --queue=premium --sleep=3 --tries=3 --timeout=90 --max-time=3600 --max-jobs=1000
autostart=true
autorestart=true
user=www-data

# Free workers (5-minute SLA)
[program:servermonitor-free]
numprocs=2                    # Scale based on free server count
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/site/artisan queue:work --queue=free --sleep=3 --tries=3 --timeout=300 --max-time=3600 --max-jobs=1000
autostart=true
autorestart=true
user=www-data

# Calculation workers
[program:servermonitor-calculations]
numprocs=1                    # Usually 1 is sufficient
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/site/artisan queue:work --queue=calculations --sleep=5 --tries=3 --timeout=300 --max-time=1800 --max-jobs=100
autostart=true
autorestart=true
user=www-data

Worker Scaling Guidelines #

Premium Workers:

  • Capacity: ~60 checks per worker per minute
  • Formula: Workers = (Premium Servers รท 60) ร— 1.5
  • Example: 200 premium servers = 5 workers minimum

Free Workers:

  • Capacity: ~12 checks per worker per minute
  • Formula: Workers = (Free Servers รท 300) ร— 1.2
  • Example: 600 free servers = 3 workers minimum

Queue Driver Optimization #

Redis (Recommended for Production):

# .env
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=null

# Redis memory optimization
REDIS_DATABASE=1              # Use dedicated database

Redis Configuration (/etc/redis/redis.conf):

# Memory optimization
maxmemory 512mb
maxmemory-policy allkeys-lru

# Persistence (optional for queue data)
save ""                       # Disable RDB snapshots
appendonly no                 # Disable AOF

# Connection limits
tcp-keepalive 60
timeout 300

Database Queue Optimization:

// config/queue.php
'database' => [
    'driver' => 'database',
    'table' => 'jobs',
    'queue' => 'default',
    'retry_after' => 3600,
    'after_commit' => false,    # Improve performance
],

Database Performance #

Index Optimization #

Critical Indexes:

-- Server queries
CREATE INDEX idx_servers_org_status ON servers(organization_id, status);
CREATE INDEX idx_servers_org_deleted ON servers(organization_id, deleted_at);

-- Log queries
CREATE INDEX idx_server_logs_server_created ON server_logs(server_id, created_at);
CREATE INDEX idx_server_logs_created ON server_logs(created_at);

-- Performance monitoring
CREATE INDEX idx_check_logs_plan_created ON server_check_logs(plan_type, created_at);
CREATE INDEX idx_check_logs_delayed ON server_check_logs(is_delayed, created_at);

-- Communication logs
CREATE INDEX idx_alert_logs_org_type ON alert_logs(organization_id, type);
CREATE INDEX idx_alert_logs_server_created ON alert_logs(server_id, created_at);

Add Missing Indexes:

-- Check for missing indexes
EXPLAIN SELECT * FROM servers WHERE organization_id = 123 AND status = 200;

-- Add composite indexes for common queries
ALTER TABLE servers ADD INDEX idx_org_status_updated (organization_id, status, updated_at);

Query Optimization #

Efficient Queries:

// โœ… Good: Use relationships and select specific fields
$servers = Server::with('organization:id,name,selected_plan')
    ->select('id', 'title', 'status', 'organization_id', 'percent_uptime')
    ->where('organization_id', $orgId)
    ->get();

// โŒ Avoid: N+1 queries
foreach ($servers as $server) {
    echo $server->organization->name; // This causes N+1
}

// โœ… Good: Eager loading
$servers = Server::with('organization')->get();
foreach ($servers as $server) {
    echo $server->organization->name; // Already loaded
}

Chunked Processing:

// Process large datasets in chunks
Server::where('require_calculation', true)
    ->chunk(100, function ($servers) {
        foreach ($servers as $server) {
            $server->updatePercentUptime();
        }
    });

Database Maintenance #

Regular Maintenance:

-- Analyze table statistics
ANALYZE TABLE servers, server_logs, server_check_logs, alert_logs;

-- Optimize table structure
OPTIMIZE TABLE servers, server_logs, server_check_logs, alert_logs;

-- Check table health
CHECK TABLE servers, server_logs, server_check_logs, alert_logs;

Automated Maintenance Script:

#!/bin/bash
# db-maintenance.sh - Run weekly

mysql -u root -p <<EOF
USE your_database;
ANALYZE TABLE servers, server_logs, server_check_logs, alert_logs;
OPTIMIZE TABLE servers, server_logs, server_check_logs, alert_logs;
EOF

# Purge old logs
curl -s "https://yoursite.com/api/servermonitor/purge-logs/YOUR_API_KEY"

Caching Strategies #

Application Caching #

Cache Configuration (config/cache.php):

'default' => 'redis',

'stores' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'cache',
        'prefix' => env('CACHE_PREFIX', 'servermonitor'),
    ],
],

// Disable request cache for queue workers
'disableRequestCache' => true,

Cached Queries:

// Cache server counts for dashboard
$onlineCount = Cache::remember('servers.online.' . $orgId, 300, function () use ($orgId) {
    return Server::where('organization_id', $orgId)
        ->whereIn('status', Server::getSuccessCodes())
        ->count();
});

// Cache uptime calculations
$avgUptime = Cache::remember('uptime.avg.' . $orgId, 3600, function () use ($orgId) {
    return Server::where('organization_id', $orgId)
        ->avg('percent_uptime');
});

Redis Optimization #

Redis Memory Management:

# Monitor Redis memory usage
redis-cli info memory

# Key expiration monitoring
redis-cli info keyspace

# Flush specific cache patterns
redis-cli --scan --pattern "cache:*" | xargs redis-cli del

Cache Warming:

// Warm important caches during off-peak hours
Artisan::command('cache:warm', function () {
    $organizations = Organization::all();

    foreach ($organizations as $org) {
        // Pre-calculate dashboard metrics
        Cache::forget('servers.online.' . $org->id);
        Cache::forget('servers.offline.' . $org->id);
        Cache::forget('uptime.avg.' . $org->id);

        // Trigger cache population
        $this->call('servermonitor:dashboard-cache', ['--org' => $org->id]);
    }
});

Server Resource Optimization #

Memory Management #

Worker Memory Limits:

# In supervisor config
command=php -d memory_limit=256M /var/www/site/artisan queue:work --max-jobs=1000

# Monitor memory usage
ps aux | grep "queue:work" | awk '{print $2, $4, $6}' # PID, %MEM, RSS

PHP Configuration (php.ini):

# Memory settings
memory_limit = 256M
max_execution_time = 90

# Opcache optimization
opcache.enable = 1
opcache.memory_consumption = 128
opcache.max_accelerated_files = 4000
opcache.validate_timestamps = 0      # Production only
opcache.enable_file_override = 1

CPU Optimization #

Process Distribution:

# Distribute workers across CPU cores
# For 4-core server with 8 premium workers:
taskset -c 0,1 supervisorctl start servermonitor-premium:00-03
taskset -c 2,3 supervisorctl start servermonitor-premium:04-07

Connection Pooling:

// Use persistent database connections
'mysql' => [
    // ... other config
    'options' => [
        PDO::ATTR_PERSISTENT => true,
        PDO::ATTR_TIMEOUT => 30,
    ],
],

Network Performance #

HTTP Client Optimization #

cURL Settings for Server Checks:

// In CheckServerJob
$ch = curl_init($domain);
curl_setopt_array($ch, [
    CURLOPT_NOBODY => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_TIMEOUT => 10,              # Reasonable timeout
    CURLOPT_CONNECTTIMEOUT => 5,        # Quick connection timeout
    CURLOPT_USERAGENT => 'ServerMonitor/1.0',
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2TLS, # HTTP/2 if available
    CURLOPT_SSL_VERIFYPEER => true,
    CURLOPT_SSL_VERIFYHOST => 2,
    CURLOPT_MAXREDIRS => 3,             # Limit redirects
]);

DNS Optimization #

DNS Caching:

# Install and configure dnsmasq for local DNS caching
sudo apt install dnsmasq

# Configure dnsmasq
echo "cache-size=1000" >> /etc/dnsmasq.conf
echo "local-ttl=300" >> /etc/dnsmasq.conf

sudo systemctl restart dnsmasq

DNS Resolution in PHP:

// Pre-resolve frequently checked domains
$domains = ['example.com', 'google.com', 'github.com'];
foreach ($domains as $domain) {
    gethostbyname($domain); // Populate DNS cache
}

Monitoring and Alerting #

Performance Metrics #

Key Performance Indicators:

  • Queue Processing Time: Jobs should complete within SLA
  • Worker CPU Usage: Should stay below 70%
  • Memory Usage: Monitor for memory leaks
  • Database Response Time: Queries should complete < 100ms
  • Uptime Calculation Time: Should complete within 5 minutes

Monitoring Dashboard: Access /albrightlabs/servermonitor/workerperformance for real-time metrics.

Automated Scaling #

Auto-scaling Script:

#!/bin/bash
# auto-scale-workers.sh - Run every 5 minutes

API_KEY="your-api-key"
SITE_URL="https://yoursite.com"

# Get worker status
STATUS=$(curl -s "${SITE_URL}/api/servermonitor/worker-status/${API_KEY}")
QUEUE_SIZE=$(echo $STATUS | jq -r '.queue_status.premium')

# Scale up if queue is backing up
if [ "$QUEUE_SIZE" -gt 50 ]; then
    echo "Scaling up premium workers (queue size: $QUEUE_SIZE)"
    supervisorctl start servermonitor-premium:04
    supervisorctl start servermonitor-premium:05
fi

# Scale down during low usage
if [ "$QUEUE_SIZE" -lt 10 ]; then
    echo "Scaling down premium workers (queue size: $QUEUE_SIZE)"
    supervisorctl stop servermonitor-premium:04
    supervisorctl stop servermonitor-premium:05
fi

Load Testing #

Simulated Load Testing #

Generate Test Servers:

// Create test servers for load testing
php artisan tinker

$org = Organization::first();
for ($i = 0; $i < 1000; $i++) {
    Server::create([
        'title' => "Test Server {$i}",
        'endpoint' => "https://httpbin.org/status/200",
        'organization_id' => $org->id,
    ]);
}

Load Test Script:

#!/bin/bash
# load-test.sh

API_KEY="your-api-key"
SITE_URL="https://yoursite.com"

# Simulate high load
for i in {1..10}; do
    curl -s "${SITE_URL}/api/servermonitor/check/premium/${API_KEY}" &
    curl -s "${SITE_URL}/api/servermonitor/check/free/${API_KEY}" &
done

wait
echo "Load test completed"

Performance Benchmarks #

Target Performance:

  • Premium Checks: 95% completed within 60 seconds
  • Free Checks: 95% completed within 300 seconds
  • Dashboard Load: < 2 seconds
  • API Response: < 500ms
  • Uptime Calculation: < 10 seconds per server

Benchmark Script:

// Benchmark uptime calculation
$start = microtime(true);
$server = Server::find(123);
$server->updatePercentUptime();
$duration = microtime(true) - $start;
echo "Uptime calculation took: {$duration} seconds\n";

Troubleshooting Performance Issues #

Common Bottlenecks #

Queue Backlog:

# Check queue sizes
redis-cli llen queues:premium
redis-cli llen queues:free

# Solution: Add more workers or optimize job processing

Database Slow Queries:

-- Enable slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

-- Check slow queries
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10;

Memory Leaks:

# Monitor worker memory over time
watch 'ps aux | grep "queue:work" | grep -v grep | awk "{print \$6}" | sort -nr'

# Restart workers if memory usage is high
supervisorctl restart servermonitor-premium:*

Performance Profiling #

Database Query Profiling:

// Enable query logging
DB::enableQueryLog();

// Perform operation
$servers = Server::with('organization')->get();

// View queries
$queries = DB::getQueryLog();
foreach ($queries as $query) {
    echo $query['query'] . "\n";
    echo "Time: " . $query['time'] . "ms\n\n";
}

Memory Profiling:

// Track memory usage
$memStart = memory_get_usage();
$memPeakStart = memory_get_peak_usage();

// Perform operation
$servers = Server::all();

echo "Memory used: " . (memory_get_usage() - $memStart) . " bytes\n";
echo "Peak memory: " . (memory_get_peak_usage() - $memPeakStart) . " bytes\n";

Production Optimization Checklist #

Pre-Deployment #

  • [ ] Opcache enabled and configured
  • [ ] Redis cache configured and tested
  • [ ] Database indexes verified
  • [ ] Queue workers scaled appropriately
  • [ ] Supervisor configuration optimized
  • [ ] Log rotation configured
  • [ ] Monitoring alerts set up

Post-Deployment #

  • [ ] Performance baseline established
  • [ ] Load testing completed
  • [ ] Memory usage monitored
  • [ ] Queue performance verified
  • [ ] Database performance optimized
  • [ ] Alert thresholds configured
  • [ ] Scaling procedures documented

Ongoing Maintenance #

  • [ ] Weekly performance review
  • [ ] Monthly worker scaling assessment
  • [ ] Quarterly database optimization
  • [ ] Annual architecture review

Previous: โ† Branding | Next: Back to Documentation โ†—