database = $this->createTestDatabase(); $this->lockService = new DatabaseDistributedLock($this->database); $this->cleanupTestData(); PerformanceTestHelper::warmupDatabase($this->database->getConnection()); } protected function tearDown(): void { $this->cleanupTestData(); } public function testLockAcquisitionLatency(): void { $acquisitionTimes = []; $iterations = 1000; for ($i = 0; $i < $iterations; $i++) { $lockKey = new LockKey("test_lock_{$i}"); $owner = new LockOwner("owner_{$i}"); $time = PerformanceTestHelper::measureTime(function() use ($lockKey, $owner) { $acquired = $this->lockService->acquire($lockKey, $owner, 30); if ($acquired) { $this->lockService->release($lockKey, $owner); } return $acquired; }); $acquisitionTimes[] = $time; } $stats = PerformanceTestHelper::calculateStatistics($acquisitionTimes); // Validate performance benchmarks $this->assertLessThan(2.0, $stats['avg'], 'Average lock acquisition time exceeds 2ms'); $this->assertLessThan(5.0, $stats['p95'], 'P95 lock acquisition time exceeds 5ms'); $this->assertLessThan(10.0, $stats['p99'], 'P99 lock acquisition time exceeds 10ms'); echo "\nLock Acquisition Latency Results:\n"; echo "Iterations: {$iterations}\n"; echo "Performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n"; PerformanceTestHelper::assertPerformance( $acquisitionTimes, 2.0, 5.0, 'Lock acquisition' ); } public function testLockReleaseLatency(): void { $releaseTimes = []; $iterations = 1000; // Pre-acquire locks $locks = []; for ($i = 0; $i < $iterations; $i++) { $lockKey = new LockKey("release_lock_{$i}"); $owner = new LockOwner("owner_{$i}"); $acquired = $this->lockService->acquire($lockKey, $owner, 60); if ($acquired) { $locks[] = ['key' => $lockKey, 'owner' => $owner]; } } // Measure release times foreach ($locks as $lock) { $time = PerformanceTestHelper::measureTime(function() use ($lock) { return $this->lockService->release($lock['key'], $lock['owner']); }); $releaseTimes[] = $time; } $stats = PerformanceTestHelper::calculateStatistics($releaseTimes); // Validate performance benchmarks $this->assertLessThan(1.5, $stats['avg'], 'Average lock release time exceeds 1.5ms'); $this->assertLessThan(3.0, $stats['p95'], 'P95 lock release time exceeds 3ms'); $this->assertLessThan(8.0, $stats['p99'], 'P99 lock release time exceeds 8ms'); echo "\nLock Release Latency Results:\n"; echo "Locks released: " . count($releaseTimes) . "\n"; echo "Performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n"; PerformanceTestHelper::assertPerformance( $releaseTimes, 1.5, 3.0, 'Lock release' ); } public function testLockContentionPerformance(): void { $lockKey = new LockKey('contended_lock'); $concurrentAttempts = 20; $attemptsPerWorker = 50; $allResults = []; $successCounts = []; $failureCounts = []; // Simulate concurrent lock acquisition attempts for ($worker = 0; $worker < $concurrentAttempts; $worker++) { $owner = new LockOwner("worker_{$worker}"); $workerResults = []; $successes = 0; $failures = 0; for ($attempt = 0; $attempt < $attemptsPerWorker; $attempt++) { $result = PerformanceTestHelper::measureTimeWithResult(function() use ($lockKey, $owner) { $acquired = $this->lockService->acquire($lockKey, $owner, 1); // 1 second timeout if ($acquired) { // Hold lock briefly then release usleep(100); // 0.1ms $this->lockService->release($lockKey, $owner); return true; } return false; }); $workerResults[] = $result['time_ms']; if ($result['result']) { $successes++; } else { $failures++; } // Brief pause between attempts usleep(50); // 0.05ms } $allResults = array_merge($allResults, $workerResults); $successCounts[$worker] = $successes; $failureCounts[$worker] = $failures; } $stats = PerformanceTestHelper::calculateStatistics($allResults); $totalSuccesses = array_sum($successCounts); $totalFailures = array_sum($failureCounts); $successRate = $totalSuccesses / ($totalSuccesses + $totalFailures) * 100; echo "\nLock Contention Performance Results:\n"; echo "Concurrent workers: {$concurrentAttempts}\n"; echo "Total attempts: " . ($totalSuccesses + $totalFailures) . "\n"; echo "Successful acquisitions: {$totalSuccesses}\n"; echo "Failed acquisitions: {$totalFailures}\n"; echo "Success rate: {$successRate}%\n"; echo "Attempt performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n"; // Under contention, some failures are expected but most should succeed $this->assertGreaterThan(50.0, $successRate, 'Lock success rate too low under contention'); // Performance should degrade gracefully under contention $this->assertLessThan(50.0, $stats['avg'], 'Average lock time too high under contention'); $this->assertLessThan(200.0, $stats['p95'], 'P95 lock time too high under contention'); } public function testLockTimeoutPerformance(): void { $lockKey = new LockKey('timeout_lock'); $owner1 = new LockOwner('owner_1'); $owner2 = new LockOwner('owner_2'); // First owner acquires the lock $acquired = $this->lockService->acquire($lockKey, $owner1, 60); $this->assertTrue($acquired, 'Initial lock acquisition should succeed'); $timeoutResults = []; $iterations = 100; // Second owner repeatedly tries to acquire with short timeouts for ($i = 0; $i < $iterations; $i++) { $result = PerformanceTestHelper::measureTimeWithResult(function() use ($lockKey, $owner2) { return $this->lockService->acquire($lockKey, $owner2, 0.1); // 100ms timeout }); $timeoutResults[] = $result['time_ms']; $this->assertFalse($result['result'], 'Lock acquisition should fail due to timeout'); } // Release the lock $this->lockService->release($lockKey, $owner1); $stats = PerformanceTestHelper::calculateStatistics($timeoutResults); echo "\nLock Timeout Performance Results:\n"; echo "Timeout attempts: {$iterations}\n"; echo "Performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n"; // Timeout should be close to requested timeout (100ms) but not much longer $this->assertGreaterThan(90.0, $stats['avg'], 'Timeout too fast - should wait approximately 100ms'); $this->assertLessThan(150.0, $stats['avg'], 'Timeout too slow - should be close to 100ms'); // Timeouts should be consistent $this->assertLessThan(30.0, $stats['stddev'], 'Timeout timing too inconsistent'); } public function testLockCleanupPerformance(): void { $expiredLockCount = 500; $validLockCount = 100; // Create expired locks $pdo = $this->database->getConnection(); $expiredTime = (new \DateTimeImmutable())->modify('-1 hour'); for ($i = 0; $i < $expiredLockCount; $i++) { $pdo->exec(sprintf( "INSERT INTO distributed_locks (lock_key, owner_id, acquired_at, expires_at) VALUES ('expired_%d', 'owner_%d', '%s', '%s')", $i, $i, $expiredTime->format('Y-m-d H:i:s'), $expiredTime->format('Y-m-d H:i:s') )); } // Create valid locks $validTime = (new \DateTimeImmutable())->modify('+1 hour'); for ($i = 0; $i < $validLockCount; $i++) { $pdo->exec(sprintf( "INSERT INTO distributed_locks (lock_key, owner_id, acquired_at, expires_at) VALUES ('valid_%d', 'owner_%d', '%s', '%s')", $i, $i, $validTime->format('Y-m-d H:i:s'), $validTime->format('Y-m-d H:i:s') )); } // Measure cleanup performance $cleanupTime = PerformanceTestHelper::measureTime(function() { $this->lockService->cleanupExpiredLocks(); }); // Verify cleanup results $remaining = $pdo->query('SELECT COUNT(*) FROM distributed_locks')->fetchColumn(); echo "\nLock Cleanup Performance Results:\n"; echo "Expired locks created: {$expiredLockCount}\n"; echo "Valid locks created: {$validLockCount}\n"; echo "Locks remaining after cleanup: {$remaining}\n"; echo "Cleanup time: {$cleanupTime}ms\n"; $this->assertEquals($validLockCount, $remaining, 'Should only clean up expired locks'); $this->assertLessThan(100.0, $cleanupTime, 'Lock cleanup should complete within 100ms'); // Test cleanup performance with larger dataset $this->cleanupTestData(); // Create many more expired locks $largeExpiredCount = 5000; for ($i = 0; $i < $largeExpiredCount; $i++) { $pdo->exec(sprintf( "INSERT INTO distributed_locks (lock_key, owner_id, acquired_at, expires_at) VALUES ('large_expired_%d', 'owner_%d', '%s', '%s')", $i, $i, $expiredTime->format('Y-m-d H:i:s'), $expiredTime->format('Y-m-d H:i:s') )); } $largeCleanupTime = PerformanceTestHelper::measureTime(function() { $this->lockService->cleanupExpiredLocks(); }); echo "Large cleanup ({$largeExpiredCount} locks): {$largeCleanupTime}ms\n"; $this->assertLessThan(500.0, $largeCleanupTime, 'Large cleanup should complete within 500ms'); } public function testHighThroughputLockOperations(): void { $operationsPerSecond = 500; $testDuration = 10; // seconds $totalOperations = $operationsPerSecond * $testDuration; echo "\nHigh Throughput Lock Operations Test:\n"; echo "Target: {$operationsPerSecond} operations/second for {$testDuration} seconds\n"; $loadResult = PerformanceTestHelper::simulateLoad( function($index) { $lockKey = new LockKey("throughput_lock_{$index}"); $owner = new LockOwner("owner_{$index}"); // Acquire and immediately release $acquired = $this->lockService->acquire($lockKey, $owner, 5); if ($acquired) { $this->lockService->release($lockKey, $owner); return true; } return false; }, $totalOperations, 25, // Moderate concurrency $testDuration ); $actualThroughput = $loadResult['throughput_ops_per_sec']; $operationTimes = array_column($loadResult['results'], 'time_ms'); $stats = PerformanceTestHelper::calculateStatistics($operationTimes); $successfulOperations = count(array_filter( array_column($loadResult['results'], 'result'), fn($result) => $result['result'] === true )); echo "Actual Throughput: {$actualThroughput} operations/second\n"; echo "Successful operations: {$successfulOperations}\n"; echo "Operation Performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n"; // Should achieve at least 70% of target throughput $this->assertGreaterThan( $operationsPerSecond * 0.7, $actualThroughput, 'High throughput lock operations below 70% of target' ); // Most operations should succeed $successRate = $successfulOperations / $loadResult['operations_completed'] * 100; $this->assertGreaterThan(95.0, $successRate, 'Lock operation success rate too low'); // Operation times should remain reasonable $this->assertLessThan(20.0, $stats['avg'], 'Average operation time too high under load'); $this->assertLessThan(100.0, $stats['p95'], 'P95 operation time too high under load'); } public function testLockRetryPerformance(): void { $lockKey = new LockKey('retry_lock'); $owner1 = new LockOwner('blocking_owner'); $owner2 = new LockOwner('retry_owner'); // First owner acquires lock for a short time $this->lockService->acquire($lockKey, $owner1, 60); // Schedule lock release after 200ms $releaseTime = microtime(true) + 0.2; $retryAttempts = []; $maxRetries = 50; $retryDelay = 50; // 50ms between retries for ($i = 0; $i < $maxRetries; $i++) { $startTime = microtime(true); // Release the lock when it's time if ($startTime >= $releaseTime && $this->lockService->isLocked($lockKey)) { $this->lockService->release($lockKey, $owner1); } $result = PerformanceTestHelper::measureTimeWithResult(function() use ($lockKey, $owner2) { return $this->lockService->acquire($lockKey, $owner2, 0.01); // 10ms timeout }); $retryAttempts[] = [ 'time_ms' => $result['time_ms'], 'success' => $result['result'] ]; if ($result['result']) { // Successfully acquired, release it and stop $this->lockService->release($lockKey, $owner2); break; } usleep($retryDelay * 1000); // Convert to microseconds } $retryTimes = array_column($retryAttempts, 'time_ms'); $stats = PerformanceTestHelper::calculateStatistics($retryTimes); $successfulAttempt = array_search(true, array_column($retryAttempts, 'success')); $attemptsUntilSuccess = $successfulAttempt !== false ? $successfulAttempt + 1 : $maxRetries; echo "\nLock Retry Performance Results:\n"; echo "Attempts until success: {$attemptsUntilSuccess}\n"; echo "Retry performance: " . PerformanceTestHelper::formatStatistics($stats) . "\n"; // Should eventually succeed $this->assertNotFalse($successfulAttempt, 'Lock retry should eventually succeed'); // Retry attempts should be fast (mostly just timeout delays) $this->assertLessThan(20.0, $stats['avg'], 'Retry attempts taking too long'); } private function createTestDatabase(): DatabaseManager { $pdo = new \PDO('sqlite::memory:'); $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $pdo->exec(' CREATE TABLE distributed_locks ( lock_key TEXT PRIMARY KEY, owner_id TEXT NOT NULL, acquired_at TEXT NOT NULL, expires_at TEXT NOT NULL ) '); // Performance-optimized indexes $pdo->exec('CREATE INDEX idx_locks_expires ON distributed_locks(expires_at)'); $pdo->exec('CREATE INDEX idx_locks_owner ON distributed_locks(owner_id)'); return new DatabaseManager($pdo); } private function cleanupTestData(): void { $pdo = $this->database->getConnection(); $pdo->exec('DELETE FROM distributed_locks'); } }