Files
michaelschiemer/tests/Framework/Database/MasterSlaveRouterTest.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

306 lines
11 KiB
PHP

<?php
declare(strict_types=1);
test('weighted selection respects configuration weights', function () {
// Test the weighted selection algorithm
// Scenario: 3 replicas with different weights
$replicas = [
['weight' => 100, 'name' => 'replica_1'],
['weight' => 200, 'name' => 'replica_2'], // 2x weight
['weight' => 50, 'name' => 'replica_3'], // 0.5x weight
];
$totalWeight = array_sum(array_column($replicas, 'weight')); // 350
// Simulate weighted selection distribution
$selections = [];
$iterations = 1000;
for ($i = 0; $i < $iterations; $i++) {
$randomValue = mt_rand(0, $totalWeight - 1);
$currentWeight = 0;
foreach ($replicas as $index => $replica) {
$currentWeight += $replica['weight'];
if ($randomValue < $currentWeight) {
$selections[$index] = ($selections[$index] ?? 0) + 1;
break;
}
}
}
// Calculate percentages
$percentages = [];
foreach ($selections as $index => $count) {
$percentages[$index] = ($count / $iterations) * 100;
}
// Expected distributions (with some tolerance for randomness)
$expectedPercentages = [
0 => (100 / 350) * 100, // ~28.57%
1 => (200 / 350) * 100, // ~57.14%
2 => (50 / 350) * 100, // ~14.29%
];
// Verify distribution is roughly correct (±5% tolerance)
foreach ($expectedPercentages as $index => $expected) {
$actual = $percentages[$index] ?? 0;
expect($actual)->toBeGreaterThan($expected - 5)
->and($actual)->toBeLessThan($expected + 5);
}
// Verify replica_2 gets roughly twice as many selections as replica_1
$ratio = ($percentages[1] ?? 0) / ($percentages[0] ?? 1);
expect($ratio)->toBeGreaterThan(1.5)
->and($ratio)->toBeLessThan(2.5); // 2.0 ± 0.5
});
test('weight adjustment based on load factor works correctly', function () {
// Test load-based weight adjustment
$baseWeight = 100;
$maxConnections = 10;
// Test cases: [current_connections, expected_load_factor]
$testCases = [
[0, 1.0], // No load = full weight
[5, 0.5], // 50% load = 50% weight
[8, 0.2], // 80% load = 20% weight
[10, 0.1], // 100% load = minimum 10% weight
[15, 0.1], // Over capacity = minimum 10% weight
];
foreach ($testCases as [$currentConnections, $expectedLoadFactor]) {
$loadFactor = $maxConnections > 0 ? 1 - ($currentConnections / $maxConnections) : 1;
$loadFactor = max(0.1, $loadFactor); // Minimum 10% weight
expect($loadFactor)->toBeGreaterThanOrEqual($expectedLoadFactor - 0.001)
->and($loadFactor)->toBeLessThanOrEqual($expectedLoadFactor + 0.001);
$adjustedWeight = (int)round($baseWeight * $loadFactor);
$expectedWeight = (int)round($baseWeight * $expectedLoadFactor);
expect($adjustedWeight)->toBe($expectedWeight);
}
});
test('response time factor adjusts weight correctly', function () {
// Test response time-based weight adjustment
$baseWeight = 100;
// Test cases: [avg_response_time_ms, expected_min_factor]
$testCases = [
[50.0, 1.0], // Very fast = full weight (100/50 = 2.0, capped at 1.0)
[100.0, 1.0], // Fast = full weight (100/100 = 1.0)
[200.0, 0.5], // Slow = 50% weight (100/200 = 0.5)
[500.0, 0.2], // Very slow = 20% weight (100/500 = 0.2)
[1000.0, 0.1], // Extremely slow = minimum 10% weight
];
foreach ($testCases as [$responseTime, $expectedMinFactor]) {
$responseFactor = $responseTime > 0 ? min(1.0, 100 / $responseTime) : 1;
$responseFactor = max(0.1, $responseFactor); // Minimum 10% weight
expect($responseFactor)->toBeGreaterThanOrEqual($expectedMinFactor - 0.01)
->and($responseFactor)->toBeLessThanOrEqual($expectedMinFactor + 0.01);
}
});
test('connection counting tracks connections correctly', function () {
// Test connection count tracking logic
$connectionCounts = [0, 0, 0]; // 3 replicas, all start at 0
// Simulate connection increments
$operations = [
['replica' => 0, 'operation' => 'increment'],
['replica' => 1, 'operation' => 'increment'],
['replica' => 0, 'operation' => 'increment'],
['replica' => 2, 'operation' => 'increment'],
['replica' => 1, 'operation' => 'increment'],
['replica' => 0, 'operation' => 'decrement'],
['replica' => 1, 'operation' => 'increment'],
];
foreach ($operations as $op) {
$replicaIndex = $op['replica'];
if ($op['operation'] === 'increment') {
$connectionCounts[$replicaIndex]++;
} else {
$connectionCounts[$replicaIndex] = max(0, $connectionCounts[$replicaIndex] - 1);
}
}
// Expected final counts
expect($connectionCounts[0])->toBe(1); // 2 increments, 1 decrement = 1
expect($connectionCounts[1])->toBe(3); // 3 increments = 3
expect($connectionCounts[2])->toBe(1); // 1 increment = 1
// Test decrement doesn't go below 0
$connectionCounts[2]--;
$connectionCounts[2] = max(0, $connectionCounts[2]);
expect($connectionCounts[2])->toBe(0);
$connectionCounts[2]--; // Should stay at 0
$connectionCounts[2] = max(0, $connectionCounts[2]);
expect($connectionCounts[2])->toBe(0);
});
test('response time history maintains correct window size', function () {
// Test response time history window management
$maxHistorySize = 5; // Smaller window for testing
$responseHistory = [];
// Add response times
$responseTimes = [100, 150, 200, 120, 180, 90, 110, 160];
foreach ($responseTimes as $time) {
$responseHistory[] = $time;
// Keep only last N response times
if (count($responseHistory) > $maxHistorySize) {
array_shift($responseHistory);
}
}
// Should contain only the last 5 values
expect($responseHistory)->toHaveCount($maxHistorySize);
expect($responseHistory)->toBe([120, 180, 90, 110, 160]); // Last 5 values
// Let's recalculate correctly
$responseHistory = [];
foreach ($responseTimes as $time) {
$responseHistory[] = $time;
if (count($responseHistory) > $maxHistorySize) {
array_shift($responseHistory);
}
}
expect($responseHistory)->toHaveCount($maxHistorySize);
expect($responseHistory)->toBe([120, 180, 90, 110, 160]); // Last 5 values
// Test average calculation
$average = array_sum($responseHistory) / count($responseHistory);
$expectedAverage = (120 + 180 + 90 + 110 + 160) / 5; // 132
expect($average)->toBe($expectedAverage);
});
test('load balancing strategy selection logic', function () {
// Test different load balancing strategies
$strategies = [
'ROUND_ROBIN' => 'Simple round-robin selection',
'WEIGHTED' => 'Weight-based selection with load adjustment',
'LEAST_CONNECTIONS' => 'Select replica with fewest connections',
'RESPONSE_TIME' => 'Select replica with best response time',
];
// Simulate replica data
$replicas = [
['connections' => 5, 'weight' => 100, 'response_time' => 120],
['connections' => 8, 'weight' => 150, 'response_time' => 90],
['connections' => 3, 'weight' => 80, 'response_time' => 200],
];
// Test LEAST_CONNECTIONS logic
$minConnections = min(array_column($replicas, 'connections'));
$leastConnectionsIndex = array_search($minConnections, array_column($replicas, 'connections'));
expect($leastConnectionsIndex)->toBe(2); // Index 2 has 3 connections (minimum)
// Test RESPONSE_TIME logic
$minResponseTime = min(array_column($replicas, 'response_time'));
$fastestReplicaIndex = array_search($minResponseTime, array_column($replicas, 'response_time'));
expect($fastestReplicaIndex)->toBe(1); // Index 1 has 90ms response time (minimum)
// Test weight calculation with all factors
foreach ($replicas as $index => $replica) {
$maxConnections = 10;
$loadFactor = max(0.1, 1 - ($replica['connections'] / $maxConnections));
$responseFactor = max(0.1, min(1.0, 100 / $replica['response_time']));
$adjustedWeight = (int)($replica['weight'] * $loadFactor * $responseFactor);
// Verify minimum weight is maintained
expect($adjustedWeight)->toBeGreaterThanOrEqual(1);
}
});
test('routing statistics provide comprehensive monitoring data', function () {
// Test statistics generation logic
$replicas = [
[
'healthy' => true,
'connections' => 5,
'max_connections' => 10,
'weight' => 100,
'total_queries' => 1000,
'failed_queries' => 10,
'response_samples' => 50,
],
[
'healthy' => false,
'connections' => 0,
'max_connections' => 10,
'weight' => 150,
'total_queries' => 0,
'failed_queries' => 0,
'response_samples' => 0,
],
[
'healthy' => true,
'connections' => 8,
'max_connections' => 10,
'weight' => 80,
'total_queries' => 500,
'failed_queries' => 5,
'response_samples' => 25,
],
];
// Calculate expected statistics
$totalReplicas = count($replicas);
$healthyReplicas = count(array_filter($replicas, fn ($r) => $r['healthy']));
expect($totalReplicas)->toBe(3);
expect($healthyReplicas)->toBe(2);
// Test load percentage calculation
foreach ($replicas as $index => $replica) {
$loadPercentage = ($replica['connections'] / $replica['max_connections']) * 100;
if ($index === 0) {
expect($loadPercentage)->toBe(50.0); // 5/10 = 50%
} elseif ($index === 2) {
expect($loadPercentage)->toBe(80.0); // 8/10 = 80%
}
}
// Test success rate calculation
foreach ($replicas as $replica) {
$totalQueries = $replica['total_queries'];
$failedQueries = $replica['failed_queries'];
if ($totalQueries === 0) {
$successRate = 100.0;
} else {
$successfulQueries = $totalQueries - $failedQueries;
$successRate = ($successfulQueries / $totalQueries) * 100;
}
if ($replica['total_queries'] === 1000) {
expect($successRate)->toBe(99.0); // 990/1000 = 99%
} elseif ($replica['total_queries'] === 500) {
expect($successRate)->toBe(99.0); // 495/500 = 99%
} else {
expect($successRate)->toBe(100.0); // No queries = 100%
}
}
});