Performance Optimization - luckydeva03/desa_karangrejo GitHub Wiki

⚡ Performance Optimization

Panduan lengkap untuk mengoptimalkan performa Website Desa Karangrejo agar lebih cepat dan efisien.

🎯 Overview

Performance optimization adalah proses meningkatkan kecepatan dan efisiensi website. Panduan ini mencakup optimasi di semua level: aplikasi, database, server, dan network.

📊 Performance Metrics Target

Core Web Vitals

  • Largest Contentful Paint (LCP): < 2.5 detik
  • First Input Delay (FID): < 100 milidetik
  • Cumulative Layout Shift (CLS): < 0.1
  • Time to First Byte (TTFB): < 800 milidetik

Additional Metrics

  • Page Load Time: < 3 detik
  • Page Size: < 2MB
  • HTTP Requests: < 50 per halaman
  • Database Queries: < 20 per halaman

🚀 Frontend Optimization

1. Image Optimization

Automatic Image Processing

// app/Http/Controllers/Admin/MediaController.php
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;

class MediaController extends Controller
{
    public function uploadImage(Request $request)
    {
        $request->validate([
            'image' => 'required|image|max:5120' // 5MB max
        ]);

        $file = $request->file('image');
        $manager = new ImageManager(new Driver());
        
        // Create different sizes
        $sizes = [
            'original' => ['width' => 1920, 'quality' => 85],
            'large' => ['width' => 1200, 'quality' => 80],
            'medium' => ['width' => 800, 'quality' => 75],
            'small' => ['width' => 400, 'quality' => 70],
            'thumbnail' => ['width' => 150, 'quality' => 65]
        ];

        $filename = Str::random(40);
        $savedFiles = [];

        foreach ($sizes as $sizeName => $config) {
            $image = $manager->read($file->getPathname());
            
            // Resize maintaining aspect ratio
            $image->scaleDown(width: $config['width']);
            
            // Convert to WebP for better compression
            $webpFilename = "{$filename}_{$sizeName}.webp";
            $webpPath = storage_path("app/public/images/{$webpFilename}");
            
            $image->toWebp($config['quality'])->save($webpPath);
            
            // Fallback JPEG
            $jpegFilename = "{$filename}_{$sizeName}.jpg";
            $jpegPath = storage_path("app/public/images/{$jpegFilename}");
            
            $image->toJpeg($config['quality'])->save($jpegPath);
            
            $savedFiles[$sizeName] = [
                'webp' => "images/{$webpFilename}",
                'jpeg' => "images/{$jpegFilename}"
            ];
        }

        return response()->json([
            'success' => true,
            'files' => $savedFiles
        ]);
    }
}

Responsive Images in Blade

{{-- Responsive image component --}}
<picture class="block w-full">
    <source 
        srcset="{{ Storage::url($image['webp']) }}" 
        type="image/webp"
        media="(min-width: 1024px)"
    >
    <source 
        srcset="{{ Storage::url($imageMedium['webp']) }}" 
        type="image/webp"
        media="(min-width: 768px)"
    >
    <source 
        srcset="{{ Storage::url($imageSmall['webp']) }}" 
        type="image/webp"
        media="(max-width: 767px)"
    >
    
    {{-- Fallback for browsers that don't support WebP --}}
    <img 
        src="{{ Storage::url($imageMedium['jpeg']) }}"
        alt="{{ $alt }}"
        class="w-full h-auto"
        loading="lazy"
        decoding="async"
    >
</picture>

2. CSS & JavaScript Optimization

Vite Configuration (vite.config.js)

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js'
            ],
            refresh: true,
        }),
    ],
    build: {
        rollupOptions: {
            output: {
                manualChunks: {
                    vendor: ['alpinejs'],
                    utils: ['sweetalert2', 'chart.js']
                }
            }
        },
        minify: 'terser',
        terserOptions: {
            compress: {
                drop_console: true,
                drop_debugger: true
            }
        }
    },
    css: {
        postcss: {
            plugins: [
                require('tailwindcss'),
                require('autoprefixer'),
                require('cssnano')({
                    preset: 'default'
                })
            ]
        }
    }
});

Critical CSS Extraction

// app/View/Components/CriticalCss.php
class CriticalCss extends Component
{
    public function render()
    {
        $criticalCss = $this->extractCriticalCss();
        
        return view('components.critical-css', [
            'criticalCss' => $criticalCss
        ]);
    }
    
    private function extractCriticalCss()
    {
        // Extract critical CSS for above-the-fold content
        $criticalStyles = [
            // Header styles
            '.header { background: #1f2937; color: white; }',
            '.logo { height: 60px; }',
            // Navigation styles
            '.nav-link { color: white; padding: 1rem; }',
            // Hero section styles
            '.hero { background: linear-gradient(...); min-height: 50vh; }',
            // Layout styles
            '.container { max-width: 1200px; margin: 0 auto; }',
        ];
        
        return implode("\n", $criticalStyles);
    }
}
{{-- In layout head --}}
<style>
    {!! $criticalCss !!}
</style>

{{-- Defer non-critical CSS --}}
<link rel="preload" href="{{ Vite::asset('resources/css/app.css') }}" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="{{ Vite::asset('resources/css/app.css') }}"></noscript>

3. Lazy Loading Implementation

Images & Content

// resources/js/lazy-loading.js
class LazyLoader {
    constructor() {
        this.imageObserver = null;
        this.contentObserver = null;
        this.init();
    }
    
    init() {
        if ('IntersectionObserver' in window) {
            this.setupImageLazyLoading();
            this.setupContentLazyLoading();
        } else {
            this.loadAllImages();
        }
    }
    
    setupImageLazyLoading() {
        this.imageObserver = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const img = entry.target;
                    
                    if (img.dataset.src) {
                        img.src = img.dataset.src;
                        img.removeAttribute('data-src');
                    }
                    
                    if (img.dataset.srcset) {
                        img.srcset = img.dataset.srcset;
                        img.removeAttribute('data-srcset');
                    }
                    
                    img.classList.remove('lazy');
                    this.imageObserver.unobserve(img);
                }
            });
        }, {
            rootMargin: '50px 0px',
            threshold: 0.1
        });
        
        document.querySelectorAll('img[data-src]').forEach(img => {
            this.imageObserver.observe(img);
        });
    }
    
    setupContentLazyLoading() {
        this.contentObserver = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const element = entry.target;
                    const url = element.dataset.lazyUrl;
                    
                    if (url) {
                        this.loadContent(element, url);
                        this.contentObserver.unobserve(element);
                    }
                }
            });
        });
        
        document.querySelectorAll('[data-lazy-url]').forEach(el => {
            this.contentObserver.observe(el);
        });
    }
    
    async loadContent(element, url) {
        try {
            const response = await fetch(url);
            const html = await response.text();
            element.innerHTML = html;
            element.classList.add('loaded');
        } catch (error) {
            console.error('Failed to load content:', error);
            element.innerHTML = '<p>Failed to load content</p>';
        }
    }
    
    loadAllImages() {
        document.querySelectorAll('img[data-src]').forEach(img => {
            img.src = img.dataset.src;
            img.removeAttribute('data-src');
        });
    }
}

// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
    new LazyLoader();
});

🗄️ Database Optimization

1. Query Optimization

Eloquent Optimization

// ❌ Bad - N+1 queries
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->category->name; // N+1 query problem
    echo $post->user->name;
}

// ✅ Good - Eager loading
$posts = Post::with(['category', 'user'])->get();
foreach ($posts as $post) {
    echo $post->category->name;
    echo $post->user->name;
}

// ✅ Better - Selective eager loading
$posts = Post::with(['category:id,name', 'user:id,name'])
    ->select('id', 'title', 'category_id', 'user_id', 'created_at')
    ->get();

// ✅ Best - Chunking for large datasets
Post::with(['category', 'user'])
    ->chunk(100, function ($posts) {
        foreach ($posts as $post) {
            // Process post
        }
    });

Custom Database Queries

// High-performance post listing
class PostRepository
{
    public function getOptimizedPosts($page = 1, $perPage = 12)
    {
        return DB::table('posts as p')
            ->select([
                'p.id',
                'p.title',
                'p.slug',
                'p.excerpt',
                'p.featured_image',
                'p.published_at',
                'p.views',
                'c.name as category_name',
                'c.slug as category_slug',
                'u.name as author_name'
            ])
            ->join('categories as c', 'p.category_id', '=', 'c.id')
            ->join('users as u', 'p.user_id', '=', 'u.id')
            ->where('p.status', 'published')
            ->where('p.published_at', '<=', now())
            ->orderBy('p.published_at', 'desc')
            ->offset(($page - 1) * $perPage)
            ->limit($perPage)
            ->get();
    }
    
    public function getPopularPosts($limit = 5)
    {
        return Cache::remember('popular_posts', 3600, function () use ($limit) {
            return DB::table('posts')
                ->select('id', 'title', 'slug', 'views', 'featured_image')
                ->where('status', 'published')
                ->where('published_at', '>=', now()->subMonths(3))
                ->orderBy('views', 'desc')
                ->limit($limit)
                ->get();
        });
    }
}

2. Database Indexing

Strategic Index Creation

-- Posts table indexes for common queries
CREATE INDEX idx_posts_status_published ON posts(status, published_at DESC);
CREATE INDEX idx_posts_category_status ON posts(category_id, status, published_at DESC);
CREATE INDEX idx_posts_user_status ON posts(user_id, status, published_at DESC);
CREATE INDEX idx_posts_featured ON posts(is_featured, status, published_at DESC);
CREATE INDEX idx_posts_views ON posts(views DESC);

-- Full-text search indexes
CREATE FULLTEXT INDEX idx_posts_search ON posts(title, excerpt, content);
CREATE FULLTEXT INDEX idx_pages_search ON pages(title, content);

-- Gallery indexes
CREATE INDEX idx_galleries_category ON galleries(category_id, is_active);
CREATE INDEX idx_galleries_active_sort ON galleries(is_active, sort_order);

-- Activity log indexes for performance
CREATE INDEX idx_activity_log_subject ON activity_log(subject_type, subject_id);
CREATE INDEX idx_activity_log_causer ON activity_log(causer_type, causer_id);
CREATE INDEX idx_activity_log_created ON activity_log(created_at DESC);

-- User and authentication indexes
CREATE INDEX idx_users_email_active ON users(email, is_active);
CREATE INDEX idx_users_last_login ON users(last_login_at DESC);

-- Settings cache index
CREATE INDEX idx_settings_key_group ON settings(`key`, `group`);

3. Database Configuration Optimization

MySQL Configuration (my.cnf)

[mysqld]
# Basic Settings
default_storage_engine = InnoDB
default_table_type = InnoDB

# Connection Settings
max_connections = 200
max_connect_errors = 1000
thread_cache_size = 50
table_open_cache = 4000

# InnoDB Settings
innodb_buffer_pool_size = 2G          # 70-80% of available RAM
innodb_log_file_size = 256M
innodb_log_buffer_size = 64M
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 120
innodb_thread_concurrency = 0
innodb_file_per_table = 1

# Query Cache (use carefully with InnoDB)
query_cache_type = 1
query_cache_size = 256M
query_cache_limit = 2M

# Slow Query Log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
log_queries_not_using_indexes = 1

# Binary Logging
log_bin = /var/log/mysql/mysql-bin.log
expire_logs_days = 10
max_binlog_size = 100M

# Charset
character_set_server = utf8mb4
collation_server = utf8mb4_unicode_ci

# Security
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO

# Performance Schema
performance_schema = ON

💾 Caching Strategy

1. Application-Level Caching

Redis Configuration

// config/database.php - Redis optimization
'redis' => [
    'client' => env('REDIS_CLIENT', 'phpredis'),
    
    'options' => [
        'cluster' => env('REDIS_CLUSTER', 'redis'),
        'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
    ],
    
    'default' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_DB', '0'),
        'read_write_timeout' => 60,
        'context' => [
            'auth' => env('REDIS_PASSWORD'),
        ],
    ],
    
    'cache' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_CACHE_DB', '1'),
    ],
];

Smart Caching Implementation

// app/Services/CacheService.php
class CacheService
{
    const CACHE_TTL = [
        'posts' => 3600,        // 1 hour
        'pages' => 7200,        // 2 hours
        'categories' => 86400,  // 24 hours
        'settings' => 86400,    // 24 hours
        'gallery' => 1800,      // 30 minutes
        'stats' => 300,         // 5 minutes
    ];
    
    public function rememberPosts($key, $callback, $ttl = null)
    {
        $ttl = $ttl ?? self::CACHE_TTL['posts'];
        $cacheKey = "posts:{$key}";
        
        return Cache::remember($cacheKey, $ttl, $callback);
    }
    
    public function forgetPostsCache()
    {
        $pattern = "posts:*";
        $keys = Redis::keys($pattern);
        
        if (!empty($keys)) {
            Redis::del($keys);
        }
    }
    
    public function cacheHomepageData()
    {
        return Cache::remember('homepage_data', 1800, function () {
            return [
                'featured_posts' => Post::featured()->published()->limit(3)->get(),
                'latest_posts' => Post::published()->latest()->limit(6)->get(),
                'announcements' => Announcement::active()->limit(3)->get(),
                'village_stats' => VillageData::active()->orderBy('sort_order')->get(),
                'latest_gallery' => Gallery::active()->latest()->limit(8)->get(),
            ];
        });
    }
    
    public function warmupCache()
    {
        // Preload frequently accessed data
        $this->cacheHomepageData();
        
        Cache::remember('categories_menu', 86400, function () {
            return Category::active()->orderBy('sort_order')->get();
        });
        
        Cache::remember('site_settings', 86400, function () {
            return Setting::pluck('value', 'key')->toArray();
        });
    }
}

2. HTTP Caching

Nginx Caching Configuration

# /etc/nginx/conf.d/cache.conf

# Cache zones
proxy_cache_path /var/cache/nginx/static levels=1:2 keys_zone=static_cache:100m max_size=1g inactive=7d use_temp_path=off;
proxy_cache_path /var/cache/nginx/api levels=1:2 keys_zone=api_cache:50m max_size=500m inactive=1h use_temp_path=off;

# Static files caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    add_header Vary "Accept-Encoding";
    access_log off;
    
    # Enable Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/xml+rss
        application/json
        image/svg+xml;
}

# API caching
location /api/ {
    proxy_cache api_cache;
    proxy_cache_key $request_uri;
    proxy_cache_valid 200 5m;
    proxy_cache_valid 404 1m;
    add_header X-Cache-Status $upstream_cache_status;
    
    # Pass to PHP-FPM
    try_files $uri $uri/ /index.php?$query_string;
}

# Page caching for anonymous users
location / {
    set $skip_cache 0;
    
    # Skip cache for logged-in users
    if ($http_cookie ~* "laravel_session") {
        set $skip_cache 1;
    }
    
    # Skip cache for admin pages
    if ($request_uri ~* "/admin") {
        set $skip_cache 1;
    }
    
    proxy_cache static_cache;
    proxy_cache_bypass $skip_cache;
    proxy_no_cache $skip_cache;
    proxy_cache_key $request_uri;
    proxy_cache_valid 200 10m;
    add_header X-Cache-Status $upstream_cache_status;
    
    try_files $uri $uri/ /index.php?$query_string;
}

🔧 Server-Level Optimization

1. PHP-FPM Optimization

FPM Pool Configuration

; /etc/php/8.2/fpm/pool.d/desa-karangrejo.conf

[desa-karangrejo]
user = www-data
group = www-data

listen = /run/php/php8.2-fpm-desa.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Process management
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 1000

; Performance tuning
request_terminate_timeout = 300
request_slowlog_timeout = 30
slowlog = /var/log/php8.2-fpm-slow.log

; Monitoring
pm.status_path = /fpm-status
ping.path = /fpm-ping

; Security
security.limit_extensions = .php

; Environment variables
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp

2. OPcache Configuration

Optimal OPcache Settings

; /etc/php/8.2/fpm/conf.d/10-opcache.ini

zend_extension=opcache.so

; Enable OPcache
opcache.enable=1
opcache.enable_cli=0

; Memory settings
opcache.memory_consumption=512
opcache.interned_strings_buffer=64
opcache.max_accelerated_files=20000

; Performance settings
opcache.validate_timestamps=0  ; Disable in production
opcache.revalidate_freq=0
opcache.save_comments=1
opcache.fast_shutdown=1

; File cache (optional)
opcache.file_cache=/var/cache/opcache
opcache.file_cache_only=0

; Optimization
opcache.optimization_level=0x7FFFBFFF
opcache.enable_file_override=1
opcache.dups_fix=1
opcache.max_file_size=0

; Error handling
opcache.log_verbosity_level=2
opcache.preferred_memory_model=mmap

3. System-Level Optimization

Kernel Parameters

# /etc/sysctl.conf

# Network optimization
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 65536 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_congestion_control = bbr

# File system optimization
fs.file-max = 65535
fs.inotify.max_user_watches = 524288

# Memory management
vm.swappiness = 10
vm.dirty_ratio = 15
vm.dirty_background_ratio = 5

# Apply changes
sysctl -p

📊 Performance Monitoring

1. Application Performance Monitoring

Custom Performance Middleware

// app/Http/Middleware/PerformanceMonitor.php
class PerformanceMonitor
{
    public function handle($request, Closure $next)
    {
        $startTime = microtime(true);
        $startMemory = memory_get_usage(true);
        
        $response = $next($request);
        
        $endTime = microtime(true);
        $endMemory = memory_get_usage(true);
        
        $duration = round(($endTime - $startTime) * 1000, 2); // ms
        $memoryUsed = round(($endMemory - $startMemory) / 1024 / 1024, 2); // MB
        
        // Log slow requests
        if ($duration > 1000) { // > 1 second
            Log::warning('Slow request detected', [
                'url' => $request->fullUrl(),
                'method' => $request->method(),
                'duration' => $duration . 'ms',
                'memory' => $memoryUsed . 'MB',
                'user_id' => auth()->id(),
                'ip' => $request->ip()
            ]);
        }
        
        // Add performance headers (development only)
        if (config('app.debug')) {
            $response->headers->add([
                'X-Response-Time' => $duration . 'ms',
                'X-Memory-Usage' => $memoryUsed . 'MB',
                'X-DB-Queries' => DB::getQueryLog() ? count(DB::getQueryLog()) : 0
            ]);
        }
        
        return $response;
    }
}

2. Real User Monitoring (RUM)

JavaScript Performance Tracking

// resources/js/performance-monitor.js
class PerformanceMonitor {
    constructor() {
        this.metrics = {};
        this.init();
    }
    
    init() {
        // Wait for page load
        window.addEventListener('load', () => {
            this.collectMetrics();
            this.sendMetrics();
        });
        
        // Collect Core Web Vitals
        this.collectCoreWebVitals();
    }
    
    collectMetrics() {
        const navigation = performance.getEntriesByType('navigation')[0];
        
        this.metrics = {
            // Basic timing
            dns_lookup: navigation.domainLookupEnd - navigation.domainLookupStart,
            tcp_connect: navigation.connectEnd - navigation.connectStart,
            ssl_negotiation: navigation.connectEnd - navigation.secureConnectionStart,
            ttfb: navigation.responseStart - navigation.requestStart,
            download: navigation.responseEnd - navigation.responseStart,
            dom_ready: navigation.domContentLoadedEventEnd - navigation.navigationStart,
            page_load: navigation.loadEventEnd - navigation.navigationStart,
            
            // Resource counts
            total_resources: performance.getEntriesByType('resource').length,
            
            // Page info
            url: window.location.href,
            user_agent: navigator.userAgent,
            viewport: window.innerWidth + 'x' + window.innerHeight,
            timestamp: Date.now()
        };
        
        // Memory usage (if available)
        if (performance.memory) {
            this.metrics.memory_used = performance.memory.usedJSHeapSize;
            this.metrics.memory_total = performance.memory.totalJSHeapSize;
        }
    }
    
    collectCoreWebVitals() {
        // Largest Contentful Paint
        new PerformanceObserver((entryList) => {
            const entries = entryList.getEntries();
            const lastEntry = entries[entries.length - 1];
            this.metrics.lcp = lastEntry.startTime;
        }).observe({entryTypes: ['largest-contentful-paint']});
        
        // First Input Delay
        new PerformanceObserver((entryList) => {
            const firstInput = entryList.getEntries()[0];
            this.metrics.fid = firstInput.processingStart - firstInput.startTime;
        }).observe({entryTypes: ['first-input']});
        
        // Cumulative Layout Shift
        let clsScore = 0;
        new PerformanceObserver((entryList) => {
            for (const entry of entryList.getEntries()) {
                if (!entry.hadRecentInput) {
                    clsScore += entry.value;
                }
            }
            this.metrics.cls = clsScore;
        }).observe({entryTypes: ['layout-shift']});
    }
    
    sendMetrics() {
        // Send to analytics endpoint
        fetch('/api/analytics/performance', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
            },
            body: JSON.stringify(this.metrics)
        }).catch(error => {
            console.error('Failed to send performance metrics:', error);
        });
    }
}

// Initialize performance monitoring
document.addEventListener('DOMContentLoaded', () => {
    new PerformanceMonitor();
});

3. Performance Dashboard

Performance Analytics Controller

// app/Http/Controllers/Admin/PerformanceController.php
class PerformanceController extends Controller
{
    public function dashboard()
    {
        $metrics = $this->getPerformanceMetrics();
        return view('admin.performance.dashboard', compact('metrics'));
    }
    
    private function getPerformanceMetrics()
    {
        $lastWeek = now()->subWeek();
        
        return [
            'avg_response_time' => DB::table('performance_logs')
                ->where('created_at', '>', $lastWeek)
                ->avg('response_time'),
                
            'slow_queries' => DB::table('performance_logs')
                ->where('created_at', '>', $lastWeek)
                ->where('response_time', '>', 1000)
                ->count(),
                
            'error_rate' => DB::table('performance_logs')
                ->where('created_at', '>', $lastWeek)
                ->where('status_code', '>=', 400)
                ->count() / DB::table('performance_logs')->where('created_at', '>', $lastWeek)->count() * 100,
                
            'top_slow_pages' => DB::table('performance_logs')
                ->select('url', DB::raw('AVG(response_time) as avg_time'), DB::raw('COUNT(*) as requests'))
                ->where('created_at', '>', $lastWeek)
                ->groupBy('url')
                ->orderBy('avg_time', 'desc')
                ->limit(10)
                ->get(),
        ];
    }
}

Performance optimization adalah proses berkelanjutan yang memerlukan monitoring dan fine-tuning terus-menerus

⚠️ **GitHub.com Fallback** ⚠️