Files
michaelschiemer/tests/Framework/Queue/Performance/LoadBalancingPerformanceTest.php
Michael Schiemer 5050c7d73a docs: consolidate documentation into organized structure
- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
2025-10-05 11:05:04 +02:00

411 lines
15 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Framework\Queue\Performance;
use App\Framework\Database\DatabaseManager;
use App\Framework\Queue\Distribution\JobDistributionService;
use App\Framework\Queue\Distribution\LoadBalancer;
use App\Framework\Queue\Jobs\JobPriority;
use App\Framework\Queue\Queue\QueueName;
use App\Framework\Queue\Workers\WorkerRegistry;
use App\Framework\Queue\Workers\WorkerStatus;
use PHPUnit\Framework\TestCase;
final class LoadBalancingPerformanceTest extends TestCase
{
private DatabaseManager $database;
private WorkerRegistry $workerRegistry;
private JobDistributionService $distributionService;
private LoadBalancer $loadBalancer;
protected function setUp(): void
{
$this->database = $this->createTestDatabase();
$this->workerRegistry = new WorkerRegistry($this->database);
$this->loadBalancer = new LoadBalancer($this->workerRegistry);
$this->distributionService = new JobDistributionService(
$this->database,
$this->workerRegistry
);
$this->cleanupTestData();
PerformanceTestHelper::warmupDatabase($this->database->getConnection());
}
protected function tearDown(): void
{
$this->cleanupTestData();
}
public function testWorkerSelectionLatency(): void
{
// Create workers with different loads
$workers = [
PerformanceTestHelper::createTestWorker('worker_1', 20),
PerformanceTestHelper::createTestWorker('worker_2', 15),
PerformanceTestHelper::createTestWorker('worker_3', 10),
PerformanceTestHelper::createTestWorker('worker_4', 25),
PerformanceTestHelper::createTestWorker('worker_5', 30)
];
$this->registerWorkers($workers);
$selectionTimes = [];
$iterations = 1000;
for ($i = 0; $i < $iterations; $i++) {
$time = PerformanceTestHelper::measureTime(function() {
$this->loadBalancer->selectWorker(new QueueName('test_queue'));
});
$selectionTimes[] = $time;
}
$stats = PerformanceTestHelper::calculateStatistics($selectionTimes);
// Validate performance benchmarks
$this->assertLessThan(5.0, $stats['avg'], 'Average worker selection time exceeds 5ms');
$this->assertLessThan(10.0, $stats['p95'], 'P95 worker selection time exceeds 10ms');
$this->assertLessThan(20.0, $stats['p99'], 'P99 worker selection time exceeds 20ms');
echo "\nWorker Selection Latency Results:\n";
echo "Iterations: {$iterations}\n";
echo "Performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n";
PerformanceTestHelper::assertPerformance(
$selectionTimes,
5.0,
10.0,
'Worker selection'
);
}
public function testJobDistributionLatency(): void
{
$workers = $this->createBalancedWorkers(10, 20);
$this->registerWorkers($workers);
$distributionTimes = [];
$iterations = 500;
for ($i = 0; $i < $iterations; $i++) {
$job = PerformanceTestHelper::createTestJob("dist_job_{$i}");
$time = PerformanceTestHelper::measureTime(function() use ($job) {
$this->distributionService->distributeJob($job);
});
$distributionTimes[] = $time;
}
$stats = PerformanceTestHelper::calculateStatistics($distributionTimes);
// Validate performance benchmarks
$this->assertLessThan(10.0, $stats['avg'], 'Average job distribution time exceeds 10ms');
$this->assertLessThan(20.0, $stats['p95'], 'P95 job distribution time exceeds 20ms');
$this->assertLessThan(50.0, $stats['p99'], 'P99 job distribution time exceeds 50ms');
echo "\nJob Distribution Latency Results:\n";
echo "Iterations: {$iterations}\n";
echo "Performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n";
PerformanceTestHelper::assertPerformance(
$distributionTimes,
10.0,
20.0,
'Job distribution'
);
}
public function testHighThroughputDistribution(): void
{
$workers = $this->createBalancedWorkers(15, 25);
$this->registerWorkers($workers);
$jobsPerSecond = 1000;
$testDuration = 10; // seconds
$totalJobs = $jobsPerSecond * $testDuration;
echo "\nHigh Throughput Distribution Test:\n";
echo "Target: {$jobsPerSecond} jobs/second for {$testDuration} seconds\n";
$loadResult = PerformanceTestHelper::simulateLoad(
function($index) {
$job = PerformanceTestHelper::createTestJob("load_job_{$index}");
return $this->distributionService->distributeJob($job);
},
$totalJobs,
50, // High concurrency
$testDuration
);
$actualThroughput = $loadResult['throughput_ops_per_sec'];
$distributionTimes = array_column($loadResult['results'], 'time_ms');
$stats = PerformanceTestHelper::calculateStatistics($distributionTimes);
echo "Actual Throughput: {$actualThroughput} jobs/second\n";
echo "Distribution Performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n";
// Should achieve at least 80% of target throughput
$this->assertGreaterThan(
$jobsPerSecond * 0.8,
$actualThroughput,
'High throughput distribution below 80% of target'
);
// Distribution times should remain reasonable under high load
$this->assertLessThan(30.0, $stats['avg'], 'Average distribution time too high under load');
$this->assertLessThan(100.0, $stats['p95'], 'P95 distribution time too high under load');
}
public function testFairDistributionUnderLoad(): void
{
$workers = [
PerformanceTestHelper::createTestWorker('worker_1', 10),
PerformanceTestHelper::createTestWorker('worker_2', 20),
PerformanceTestHelper::createTestWorker('worker_3', 30),
PerformanceTestHelper::createTestWorker('worker_4', 15),
PerformanceTestHelper::createTestWorker('worker_5', 25)
];
$this->registerWorkers($workers);
$jobCount = 1000;
$workerAssignments = [];
for ($i = 0; $i < $jobCount; $i++) {
$job = PerformanceTestHelper::createTestJob("fair_job_{$i}");
$selectedWorker = $this->distributionService->distributeJob($job);
if ($selectedWorker) {
$workerId = $selectedWorker->id->toString();
$workerAssignments[$workerId] = ($workerAssignments[$workerId] ?? 0) + 1;
}
}
echo "\nFair Distribution Results:\n";
$totalCapacity = array_sum(array_map(fn($w) => $w->capacity->value, $workers));
foreach ($workers as $worker) {
$workerId = $worker->id->toString();
$assignments = $workerAssignments[$workerId] ?? 0;
$expectedRatio = $worker->capacity->value / $totalCapacity;
$actualRatio = $assignments / $jobCount;
$efficiency = ($actualRatio / $expectedRatio) * 100;
echo sprintf(
"Worker %s: Capacity=%d, Assignments=%d, Expected=%.1f%%, Actual=%.1f%%, Efficiency=%.1f%%\n",
$workerId,
$worker->capacity->value,
$assignments,
$expectedRatio * 100,
$actualRatio * 100,
$efficiency
);
// Each worker should get jobs roughly proportional to their capacity
// Allow 20% variance for fair distribution
$this->assertGreaterThan(
80.0,
$efficiency,
"Worker {$workerId} received fewer jobs than expected (efficiency: {$efficiency}%)"
);
$this->assertLessThan(
120.0,
$efficiency,
"Worker {$workerId} received more jobs than expected (efficiency: {$efficiency}%)"
);
}
}
public function testMixedCapacityLoadBalancing(): void
{
// Create workers with very different capacities
$workers = [
PerformanceTestHelper::createTestWorker('small_worker', 5),
PerformanceTestHelper::createTestWorker('medium_worker_1', 15),
PerformanceTestHelper::createTestWorker('medium_worker_2', 20),
PerformanceTestHelper::createTestWorker('large_worker', 50),
PerformanceTestHelper::createTestWorker('xlarge_worker', 100)
];
$this->registerWorkers($workers);
$selectionTimes = [];
$iterations = 500;
for ($i = 0; $i < $iterations; $i++) {
$time = PerformanceTestHelper::measureTime(function() {
$this->loadBalancer->selectWorker(new QueueName('test_queue'));
});
$selectionTimes[] = $time;
}
$stats = PerformanceTestHelper::calculateStatistics($selectionTimes);
echo "\nMixed Capacity Load Balancing Results:\n";
echo "Worker capacities: 5, 15, 20, 50, 100\n";
echo "Selection performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n";
// Selection should still be fast even with mixed capacities
$this->assertLessThan(8.0, $stats['avg'], 'Mixed capacity selection average time too high');
$this->assertLessThan(15.0, $stats['p95'], 'Mixed capacity selection P95 time too high');
}
public function testPriorityJobDistribution(): void
{
$workers = $this->createBalancedWorkers(8, 15);
$this->registerWorkers($workers);
$priorities = [JobPriority::LOW, JobPriority::NORMAL, JobPriority::HIGH, JobPriority::CRITICAL];
$distributionTimes = [];
foreach ($priorities as $priority) {
$iterationsPerPriority = 100;
for ($i = 0; $i < $iterationsPerPriority; $i++) {
$job = PerformanceTestHelper::createTestJob(
"priority_job_{$priority->value}_{$i}",
$priority
);
$time = PerformanceTestHelper::measureTime(function() use ($job) {
$this->distributionService->distributeJob($job);
});
$distributionTimes[$priority->value][] = $time;
}
}
echo "\nPriority Job Distribution Results:\n";
foreach ($priorities as $priority) {
$stats = PerformanceTestHelper::calculateStatistics($distributionTimes[$priority->value]);
echo "Priority {$priority->value}: " . PerformanceTestHelper::formatStatistics($stats) . "\n";
// All priorities should have similar distribution performance
$this->assertLessThan(15.0, $stats['avg'], "Priority {$priority->value} distribution too slow");
}
}
public function testWorkerOverloadHandling(): void
{
// Create workers that will quickly become overloaded
$workers = [
PerformanceTestHelper::createTestWorker('worker_1', 2),
PerformanceTestHelper::createTestWorker('worker_2', 3),
PerformanceTestHelper::createTestWorker('worker_3', 2)
];
$this->registerWorkers($workers);
$distributionTimes = [];
$successfulDistributions = 0;
$failedDistributions = 0;
// Try to distribute more jobs than total worker capacity
$jobCount = 20; // Total capacity is only 7
for ($i = 0; $i < $jobCount; $i++) {
$job = PerformanceTestHelper::createTestJob("overload_job_{$i}");
$result = PerformanceTestHelper::measureTimeWithResult(function() use ($job) {
return $this->distributionService->distributeJob($job);
});
$distributionTimes[] = $result['time_ms'];
if ($result['result'] !== null) {
$successfulDistributions++;
} else {
$failedDistributions++;
}
}
$stats = PerformanceTestHelper::calculateStatistics($distributionTimes);
echo "\nWorker Overload Handling Results:\n";
echo "Total jobs: {$jobCount}\n";
echo "Successful distributions: {$successfulDistributions}\n";
echo "Failed distributions: {$failedDistributions}\n";
echo "Distribution performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n";
// Should successfully distribute up to total capacity
$this->assertGreaterThanOrEqual(7, $successfulDistributions, 'Should distribute at least 7 jobs');
// Distribution times should remain reasonable even when workers are overloaded
$this->assertLessThan(20.0, $stats['avg'], 'Distribution time too high during overload');
}
private function createBalancedWorkers(int $count, int $capacity): array
{
$workers = [];
for ($i = 1; $i <= $count; $i++) {
$workers[] = PerformanceTestHelper::createTestWorker(
"balanced_worker_{$i}",
$capacity,
WorkerStatus::AVAILABLE
);
}
return $workers;
}
private function registerWorkers(array $workers): void
{
foreach ($workers as $worker) {
$this->workerRegistry->registerWorker($worker);
}
}
private function createTestDatabase(): DatabaseManager
{
$pdo = new \PDO('sqlite::memory:');
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// Create required tables with indexes for performance
$pdo->exec('
CREATE TABLE workers (
id TEXT PRIMARY KEY,
queue_names TEXT NOT NULL,
capacity INTEGER NOT NULL,
status TEXT NOT NULL,
last_heartbeat TEXT NOT NULL,
metadata TEXT
)
');
$pdo->exec('
CREATE TABLE jobs (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
payload TEXT NOT NULL,
queue_name TEXT NOT NULL,
priority INTEGER NOT NULL,
status TEXT NOT NULL,
worker_id TEXT,
created_at TEXT NOT NULL,
started_at TEXT,
completed_at TEXT,
attempts INTEGER DEFAULT 0,
error_message TEXT
)
');
// Performance-optimized indexes
$pdo->exec('CREATE INDEX idx_workers_status_capacity ON workers(status, capacity DESC)');
$pdo->exec('CREATE INDEX idx_workers_queue_status ON workers(queue_names, status)');
$pdo->exec('CREATE INDEX idx_jobs_worker_status ON jobs(worker_id, status)');
$pdo->exec('CREATE INDEX idx_jobs_priority_created ON jobs(priority DESC, created_at)');
return new DatabaseManager($pdo);
}
private function cleanupTestData(): void
{
$pdo = $this->database->getConnection();
$pdo->exec('DELETE FROM workers');
$pdo->exec('DELETE FROM jobs');
}
}