- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
345 lines
12 KiB
PHP
345 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../vendor/autoload.php';
|
|
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use App\Framework\Database\Profiling\QueryAnalyzer;
|
|
use App\Framework\Database\Profiling\QueryProfile;
|
|
|
|
echo "=== Testing Database Query Optimization Tools ===\n\n";
|
|
|
|
echo "1. Testing Query Analysis System:\n";
|
|
|
|
try {
|
|
// Create mock connection for analyzer
|
|
$mockConnection = new class () {
|
|
public function query(string $sql)
|
|
{
|
|
// Mock implementation that returns a result-like object
|
|
return new class () {
|
|
public function fetch()
|
|
{
|
|
static $called = false;
|
|
if (! $called) {
|
|
$called = true;
|
|
|
|
return [
|
|
'id' => 1,
|
|
'select_type' => 'SIMPLE',
|
|
'table' => 'users',
|
|
'type' => 'ALL',
|
|
'key' => null,
|
|
'rows' => 1000,
|
|
];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
}
|
|
|
|
public function queryScalar(string $sql)
|
|
{
|
|
return '{"Plan": {"Node Type": "Seq Scan", "Relation Name": "users"}}';
|
|
}
|
|
};
|
|
|
|
$analyzer = new QueryAnalyzer($mockConnection);
|
|
|
|
echo " ✅ QueryAnalyzer created successfully\n\n";
|
|
|
|
} catch (\Throwable $e) {
|
|
echo " ❌ Error creating QueryAnalyzer: {$e->getMessage()}\n\n";
|
|
}
|
|
|
|
echo "2. Testing Query Analysis with Different Query Types:\n";
|
|
|
|
$testQueries = [
|
|
[
|
|
'name' => 'Simple SELECT',
|
|
'sql' => 'SELECT id, name FROM users WHERE active = 1',
|
|
'execution_time_ms' => 25.5,
|
|
'memory_usage' => 512000,
|
|
],
|
|
[
|
|
'name' => 'SELECT with wildcard',
|
|
'sql' => 'SELECT * FROM users WHERE email LIKE "%@example.com"',
|
|
'execution_time_ms' => 1250.0,
|
|
'memory_usage' => 5242880,
|
|
],
|
|
[
|
|
'name' => 'Complex JOIN query',
|
|
'sql' => 'SELECT u.*, p.name as profile_name FROM users u LEFT JOIN profiles p ON u.id = p.user_id LEFT JOIN settings s ON u.id = s.user_id WHERE u.active = 1 ORDER BY u.created_at DESC',
|
|
'execution_time_ms' => 2100.7,
|
|
'memory_usage' => 10485760,
|
|
],
|
|
[
|
|
'name' => 'Subquery with aggregation',
|
|
'sql' => 'SELECT COUNT(*) FROM users WHERE id IN (SELECT user_id FROM orders WHERE total > (SELECT AVG(total) FROM orders))',
|
|
'execution_time_ms' => 3500.2,
|
|
'memory_usage' => 15728640,
|
|
],
|
|
[
|
|
'name' => 'Optimized query',
|
|
'sql' => 'SELECT id, email FROM users WHERE created_at >= ? AND status = ? LIMIT 100',
|
|
'execution_time_ms' => 15.3,
|
|
'memory_usage' => 204800,
|
|
],
|
|
];
|
|
|
|
foreach ($testQueries as $testQuery) {
|
|
try {
|
|
echo " 🔍 Analyzing: {$testQuery['name']}\n";
|
|
|
|
// Create a QueryProfile for testing
|
|
$profile = new class (
|
|
$testQuery['sql'],
|
|
$testQuery['execution_time_ms'],
|
|
$testQuery['memory_usage']
|
|
) {
|
|
public string $id;
|
|
|
|
public object $query;
|
|
|
|
public Duration $executionTime;
|
|
|
|
public object $startTimestamp;
|
|
|
|
public object $endTimestamp;
|
|
|
|
public int $memoryUsage;
|
|
|
|
public function __construct(string $sql, float $executionTimeMs, int $memoryUsage)
|
|
{
|
|
$this->id = uniqid('query_');
|
|
$this->query = new class ($sql) {
|
|
public function __construct(public string $sql)
|
|
{
|
|
}
|
|
};
|
|
$this->executionTime = Duration::fromMilliseconds($executionTimeMs);
|
|
$this->startTimestamp = new class () {
|
|
public function __construct()
|
|
{
|
|
}
|
|
};
|
|
$this->endTimestamp = new class () {
|
|
public function __construct()
|
|
{
|
|
}
|
|
};
|
|
$this->memoryUsage = $memoryUsage;
|
|
}
|
|
|
|
public function getComplexityScore(): int
|
|
{
|
|
$sql = strtoupper($this->query->sql);
|
|
$complexity = 0;
|
|
$complexity += substr_count($sql, 'JOIN') * 2;
|
|
$complexity += substr_count($sql, 'SELECT') - 1;
|
|
$complexity += substr_count($sql, 'UNION') * 3;
|
|
$complexity += substr_count($sql, 'GROUP BY') * 2;
|
|
$complexity += substr_count($sql, 'ORDER BY');
|
|
$complexity += substr_count($sql, 'SUBQUERY') * 4;
|
|
|
|
return max(1, $complexity);
|
|
}
|
|
};
|
|
|
|
$analysis = $analyzer->analyzeQuery($profile);
|
|
|
|
echo " • SQL: " . substr($testQuery['sql'], 0, 60) . (strlen($testQuery['sql']) > 60 ? '...' : '') . "\n";
|
|
echo " • Execution time: {$testQuery['execution_time_ms']}ms\n";
|
|
echo " • Memory usage: " . number_format($testQuery['memory_usage'] / 1024 / 1024, 2) . " MB\n";
|
|
echo " • Optimization Score: {$analysis->optimizationScore}/100\n";
|
|
echo " • Issues found: " . count($analysis->issues) . "\n";
|
|
echo " • Suggestions: " . count($analysis->suggestions) . "\n";
|
|
|
|
if (! empty($analysis->issues)) {
|
|
echo " • Top issue: {$analysis->issues[0]}\n";
|
|
}
|
|
|
|
if (! empty($analysis->suggestions)) {
|
|
echo " • Top suggestion: {$analysis->suggestions[0]}\n";
|
|
}
|
|
|
|
if (! empty($analysis->indexRecommendations)) {
|
|
echo " • Index recommendation: {$analysis->indexRecommendations[0]}\n";
|
|
}
|
|
|
|
echo "\n";
|
|
|
|
} catch (\Throwable $e) {
|
|
echo " ❌ Error analyzing query: {$e->getMessage()}\n\n";
|
|
}
|
|
}
|
|
|
|
echo "3. Testing Batch Analysis and Summary:\n";
|
|
|
|
try {
|
|
// Create profiles for batch analysis
|
|
$profiles = [];
|
|
foreach ($testQueries as $i => $testQuery) {
|
|
$profiles[] = new class (
|
|
$testQuery['sql'],
|
|
$testQuery['execution_time_ms'],
|
|
$testQuery['memory_usage'],
|
|
$i
|
|
) {
|
|
public string $id;
|
|
|
|
public object $query;
|
|
|
|
public Duration $executionTime;
|
|
|
|
public object $startTimestamp;
|
|
|
|
public object $endTimestamp;
|
|
|
|
public int $memoryUsage;
|
|
|
|
public function __construct(string $sql, float $executionTimeMs, int $memoryUsage, int $index)
|
|
{
|
|
$this->id = "query_{$index}";
|
|
$this->query = new class ($sql) {
|
|
public function __construct(public string $sql)
|
|
{
|
|
}
|
|
};
|
|
$this->executionTime = Duration::fromMilliseconds($executionTimeMs);
|
|
$this->startTimestamp = new class () {
|
|
public function __construct()
|
|
{
|
|
}
|
|
};
|
|
$this->endTimestamp = new class () {
|
|
public function __construct()
|
|
{
|
|
}
|
|
};
|
|
$this->memoryUsage = $memoryUsage;
|
|
}
|
|
|
|
public function getComplexityScore(): int
|
|
{
|
|
$sql = strtoupper($this->query->sql);
|
|
$complexity = 0;
|
|
$complexity += substr_count($sql, 'JOIN') * 2;
|
|
$complexity += substr_count($sql, 'SELECT') - 1;
|
|
$complexity += substr_count($sql, 'UNION') * 3;
|
|
$complexity += substr_count($sql, 'GROUP BY') * 2;
|
|
$complexity += substr_count($sql, 'ORDER BY');
|
|
|
|
return max(1, $complexity);
|
|
}
|
|
};
|
|
}
|
|
|
|
$batchAnalyses = $analyzer->batchAnalyze($profiles);
|
|
$summary = $analyzer->getOptimizationSummary($batchAnalyses);
|
|
|
|
echo " 📊 Batch Analysis Summary:\n";
|
|
echo " • Total queries analyzed: {$summary['total_queries_analyzed']}\n";
|
|
echo " • Average optimization score: {$summary['average_optimization_score']}/100\n";
|
|
echo " • Total issues: {$summary['total_issues']}\n";
|
|
echo " • Total suggestions: {$summary['total_suggestions']}\n";
|
|
echo " • Overall assessment: {$summary['overall_assessment']}\n\n";
|
|
|
|
echo " 🔥 Most Common Issues:\n";
|
|
foreach ($summary['most_common_issues'] as $issue => $count) {
|
|
echo " • {$issue} (occurred {$count} times)\n";
|
|
}
|
|
echo "\n";
|
|
|
|
echo " 💡 Most Common Suggestions:\n";
|
|
foreach ($summary['most_common_suggestions'] as $suggestion => $count) {
|
|
echo " • {$suggestion} (suggested {$count} times)\n";
|
|
}
|
|
echo "\n";
|
|
|
|
} catch (\Throwable $e) {
|
|
echo " ❌ Error in batch analysis: {$e->getMessage()}\n\n";
|
|
}
|
|
|
|
echo "4. Testing Individual Query Analysis Patterns:\n";
|
|
|
|
$specialTestCases = [
|
|
[
|
|
'name' => 'N+1 Pattern Detection',
|
|
'queries' => [
|
|
'SELECT * FROM users',
|
|
'SELECT * FROM profiles WHERE user_id = 1',
|
|
'SELECT * FROM profiles WHERE user_id = 2',
|
|
'SELECT * FROM profiles WHERE user_id = 3',
|
|
'SELECT * FROM profiles WHERE user_id = 4',
|
|
'SELECT * FROM profiles WHERE user_id = 5',
|
|
],
|
|
],
|
|
[
|
|
'name' => 'Index Opportunity Detection',
|
|
'queries' => [
|
|
'SELECT * FROM orders WHERE customer_id = 123',
|
|
'SELECT * FROM orders WHERE customer_id = 456',
|
|
'SELECT * FROM orders WHERE customer_id = 789',
|
|
'SELECT * FROM products WHERE category_id = 10',
|
|
'SELECT * FROM products WHERE category_id = 20',
|
|
],
|
|
],
|
|
];
|
|
|
|
foreach ($specialTestCases as $testCase) {
|
|
echo " 🔍 Testing: {$testCase['name']}\n";
|
|
|
|
$queryGroups = [];
|
|
foreach ($testCase['queries'] as $sql) {
|
|
// Normalize SQL for pattern detection
|
|
$normalized = preg_replace('/\b\d+\b/', '?', $sql);
|
|
$normalized = preg_replace('/\s+/', ' ', trim($normalized));
|
|
$queryGroups[$normalized][] = $sql;
|
|
}
|
|
|
|
foreach ($queryGroups as $pattern => $queries) {
|
|
if (count($queries) > 1) {
|
|
echo " • Pattern detected: " . substr($pattern, 0, 50) . "...\n";
|
|
echo " • Execution count: " . count($queries) . "\n";
|
|
echo " • Recommendation: ";
|
|
|
|
if (str_contains($pattern, 'profiles WHERE user_id')) {
|
|
echo "Use eager loading to avoid N+1 queries\n";
|
|
} elseif (str_contains($pattern, 'WHERE customer_id') || str_contains($pattern, 'WHERE category_id')) {
|
|
echo "Consider adding index on the WHERE clause column\n";
|
|
} else {
|
|
echo "Review for optimization opportunities\n";
|
|
}
|
|
}
|
|
}
|
|
echo "\n";
|
|
}
|
|
|
|
echo "5. Testing Performance Assessment:\n";
|
|
|
|
$performanceTests = [
|
|
['score' => 95, 'expected' => 'excellent'],
|
|
['score' => 80, 'expected' => 'good'],
|
|
['score' => 65, 'expected' => 'fair'],
|
|
['score' => 45, 'expected' => 'poor'],
|
|
['score' => 25, 'expected' => 'critical'],
|
|
];
|
|
|
|
foreach ($performanceTests as $test) {
|
|
$assessment = match (true) {
|
|
$test['score'] >= 90 => 'excellent',
|
|
$test['score'] >= 75 => 'good',
|
|
$test['score'] >= 60 => 'fair',
|
|
$test['score'] >= 40 => 'poor',
|
|
default => 'critical'
|
|
};
|
|
|
|
$status = $assessment === $test['expected'] ? '✅' : '❌';
|
|
echo " {$status} Score {$test['score']}: {$assessment} (expected: {$test['expected']})\n";
|
|
}
|
|
|
|
echo "\n=== Database Query Optimization Tools Test Completed ===\n";
|