- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
306 lines
11 KiB
PHP
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%
|
|
}
|
|
}
|
|
});
|