- 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.
1024 lines
25 KiB
Markdown
1024 lines
25 KiB
Markdown
# LiveComponents Performance Guide
|
|
|
|
Comprehensive guide to optimizing LiveComponent performance through caching, batching, fragment updates, and advanced patterns.
|
|
|
|
## Table of Contents
|
|
|
|
- [Overview](#overview)
|
|
- [Fragment-Based Rendering](#fragment-based-rendering)
|
|
- [Component Caching](#component-caching)
|
|
- [Advanced Caching: varyBy](#advanced-caching-varyby)
|
|
- [Stale-While-Revalidate (SWR)](#stale-while-revalidate-swr)
|
|
- [Request Batching](#request-batching)
|
|
- [Polling Optimization](#polling-optimization)
|
|
- [Performance Monitoring](#performance-monitoring)
|
|
- [Best Practices](#best-practices)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
LiveComponents provides multiple performance optimization strategies:
|
|
|
|
1. **Fragment Rendering** - Update only changed parts of the DOM (60-90% bandwidth reduction)
|
|
2. **Component Caching** - Cache rendered components with intelligent invalidation
|
|
3. **VaryBy Context** - Cache variations based on user, locale, feature flags
|
|
4. **Stale-While-Revalidate** - Serve stale data while refreshing in background
|
|
5. **Request Batching** - Combine multiple actions into single request
|
|
6. **Optimized Polling** - Intelligent intervals with backoff strategies
|
|
|
|
### Performance Impact
|
|
|
|
| Optimization | Bandwidth Reduction | Latency Reduction | User Experience |
|
|
|-------------|---------------------|-------------------|-----------------|
|
|
| Fragment Updates | 60-90% | 50-80% | Instant updates |
|
|
| Component Caching | 95-99% | 90-95% | Near-instant loads |
|
|
| VaryBy Context | +10-20% | Minimal | Personalized caching |
|
|
| SWR Pattern | Variable | 100% (perceived) | Zero waiting |
|
|
| Request Batching | 40-70% | 60-80% | Fewer requests |
|
|
|
|
---
|
|
|
|
## Fragment-Based Rendering
|
|
|
|
### What Are Fragments?
|
|
|
|
Fragments are **marked sections** of your component template that can be updated independently:
|
|
|
|
```html
|
|
<div data-lc-component="dashboard">
|
|
<!-- Fragment 1: Only updates when stats change -->
|
|
<div data-lc-fragment="user-stats">
|
|
<h3>Statistics</h3>
|
|
<p>Total: {stats.total}</p>
|
|
<p>Active: {stats.active}</p>
|
|
</div>
|
|
|
|
<!-- Fragment 2: Only updates when activities change -->
|
|
<div data-lc-fragment="recent-activity">
|
|
<h3>Recent Activity</h3>
|
|
<ul>
|
|
<for items="activities" as="activity">
|
|
<li>{activity.name} - {activity.time}</li>
|
|
</for>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Fragment 3: Updates independently -->
|
|
<div data-lc-fragment="notifications">
|
|
<span class="badge">{notification_count}</span>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### Using Fragments
|
|
|
|
**Automatic Fragment Updates**:
|
|
```html
|
|
<!-- Button updates only user-stats fragment -->
|
|
<button
|
|
data-lc-action="refreshStats"
|
|
data-lc-fragments="user-stats"
|
|
>
|
|
Refresh Stats Only
|
|
</button>
|
|
|
|
<!-- Button updates multiple fragments -->
|
|
<button
|
|
data-lc-action="refreshAll"
|
|
data-lc-fragments="user-stats,recent-activity"
|
|
>
|
|
Refresh Stats & Activity
|
|
</button>
|
|
```
|
|
|
|
**JavaScript Fragment Updates**:
|
|
```javascript
|
|
// Update specific fragments
|
|
await liveComponent.executeAction('refreshStats', {}, {
|
|
fragments: ['user-stats']
|
|
});
|
|
|
|
// Update multiple fragments
|
|
await liveComponent.executeAction('updateDashboard', {}, {
|
|
fragments: ['user-stats', 'recent-activity', 'notifications']
|
|
});
|
|
```
|
|
|
|
### Backend Fragment Support
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Rendering\FragmentRenderer;
|
|
|
|
#[LiveComponent('dashboard')]
|
|
final readonly class DashboardComponent implements LiveComponentContract
|
|
{
|
|
public function __construct(
|
|
public ComponentId $id,
|
|
public DashboardState $state,
|
|
private FragmentRenderer $fragmentRenderer
|
|
) {}
|
|
|
|
#[Action]
|
|
public function refreshStats(): DashboardState
|
|
{
|
|
// Update only stats in state
|
|
$newStats = $this->statsService->getLatest();
|
|
|
|
return $this->state->withStats($newStats);
|
|
}
|
|
|
|
// Framework automatically extracts requested fragments
|
|
// No manual fragment handling needed!
|
|
}
|
|
```
|
|
|
|
### Fragment Performance Characteristics
|
|
|
|
**Bandwidth Reduction**:
|
|
```
|
|
Full Render: 50 KB HTML
|
|
Fragment Update: 5 KB HTML (90% reduction)
|
|
|
|
Full Render: 20 KB HTML (dashboard)
|
|
Fragment Update: 2 KB HTML (stats only) (90% reduction)
|
|
|
|
Full Render: 100 KB HTML (product list)
|
|
Fragment Update: 8 KB HTML (grid only) (92% reduction)
|
|
```
|
|
|
|
**DOM Operations**:
|
|
```
|
|
Full Render: 500 nodes replaced
|
|
Fragment Update: 30 nodes replaced (94% reduction)
|
|
|
|
Full Render: 1000 nodes traversed
|
|
Fragment Update: 100 nodes traversed (90% reduction)
|
|
```
|
|
|
|
**Perceived Performance**:
|
|
- **Full Render**: 200-500ms (visible flash, scroll reset)
|
|
- **Fragment Update**: 50-100ms (smooth, no interruption)
|
|
|
|
### Fragment Best Practices
|
|
|
|
**1. Identify Independent Sections**
|
|
|
|
```html
|
|
<!-- ✅ Good - Independent fragments with clear boundaries -->
|
|
<div data-lc-fragment="sidebar"><!-- Sidebar content --></div>
|
|
<div data-lc-fragment="main-content"><!-- Main content --></div>
|
|
<div data-lc-fragment="footer"><!-- Footer --></div>
|
|
|
|
<!-- ❌ Bad - Overlapping fragments -->
|
|
<div data-lc-fragment="header">
|
|
<div data-lc-fragment="nav"><!-- Nested fragment causes confusion --></div>
|
|
</div>
|
|
```
|
|
|
|
**2. Use Fragments for High-Frequency Updates**
|
|
|
|
```html
|
|
<!-- ✅ Good - Frequently updated data as fragment -->
|
|
<div data-lc-fragment="stock-price">
|
|
${current_price} <span class="change">{change}%</span>
|
|
</div>
|
|
|
|
<!-- Poll only updates the fragment, not entire page -->
|
|
<button data-lc-action="poll" data-lc-fragments="stock-price">Refresh</button>
|
|
```
|
|
|
|
**3. Combine Related Updates**
|
|
|
|
```javascript
|
|
// ✅ Good - Update related fragments together
|
|
liveComponent.executeAction('updateDashboard', {}, {
|
|
fragments: ['stats', 'chart', 'summary']
|
|
});
|
|
|
|
// ❌ Bad - Multiple separate requests
|
|
liveComponent.executeAction('updateStats');
|
|
liveComponent.executeAction('updateChart');
|
|
liveComponent.executeAction('updateSummary');
|
|
```
|
|
|
|
---
|
|
|
|
## Component Caching
|
|
|
|
### Enable Caching
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Attributes\Action;
|
|
use App\Framework\LiveComponents\Contracts\Cacheable;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
|
|
#[LiveComponent('product-list')]
|
|
final readonly class ProductListComponent implements
|
|
LiveComponentContract,
|
|
Cacheable
|
|
{
|
|
// Implement Cacheable interface
|
|
public function getCacheTTL(): Duration
|
|
{
|
|
return Duration::fromMinutes(5);
|
|
}
|
|
|
|
public function getCacheKey(): string
|
|
{
|
|
return "products-{$this->state->categoryId}";
|
|
}
|
|
|
|
public function shouldCache(): bool
|
|
{
|
|
// Don't cache for admin users
|
|
return !$this->state->isAdmin;
|
|
}
|
|
|
|
#[Action]
|
|
public function loadProducts(): ProductListState
|
|
{
|
|
// Expensive database query
|
|
$products = $this->productRepository->findByCategory(
|
|
$this->state->categoryId
|
|
);
|
|
|
|
return $this->state->withProducts($products);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Cache Invalidation
|
|
|
|
**Manual Invalidation**:
|
|
```php
|
|
use App\Framework\Cache\Cache;
|
|
use App\Framework\Cache\CacheKey;
|
|
|
|
public function updateProduct(Product $product): void
|
|
{
|
|
$this->productRepository->save($product);
|
|
|
|
// Invalidate component cache
|
|
$this->cache->forget(
|
|
CacheKey::fromString("livecomponent:product-list:products-{$product->categoryId}")
|
|
);
|
|
}
|
|
```
|
|
|
|
**Event-Based Invalidation**:
|
|
```php
|
|
use App\Framework\Event\EventHandler;
|
|
|
|
#[EventHandler]
|
|
final readonly class InvalidateProductCacheHandler
|
|
{
|
|
public function handle(ProductUpdatedEvent $event): void
|
|
{
|
|
// Invalidate all product list caches for this category
|
|
$this->cache->invalidateTag("product-category-{$event->categoryId}");
|
|
}
|
|
}
|
|
```
|
|
|
|
### Cache Tags
|
|
|
|
```php
|
|
public function getCacheTags(): array
|
|
{
|
|
return [
|
|
"product-category-{$this->state->categoryId}",
|
|
"user-{$this->state->userId}",
|
|
'products'
|
|
];
|
|
}
|
|
|
|
// Invalidate all products
|
|
$this->cache->invalidateTag('products');
|
|
|
|
// Invalidate specific category
|
|
$this->cache->invalidateTag('product-category-123');
|
|
```
|
|
|
|
---
|
|
|
|
## Advanced Caching: varyBy
|
|
|
|
### What is varyBy?
|
|
|
|
**varyBy** allows you to cache different versions of the same component based on context:
|
|
|
|
- **User ID**: Different cache per user
|
|
- **Locale**: Different cache per language
|
|
- **Roles**: Different cache per permission level
|
|
- **Feature Flags**: Different cache when features enabled
|
|
- **Custom**: Any custom factor (theme, region, etc.)
|
|
|
|
### Basic varyBy Usage
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Contracts\Cacheable;
|
|
use App\Framework\LiveComponents\Caching\VaryBy;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
|
|
#[LiveComponent('user-dashboard')]
|
|
final readonly class UserDashboardComponent implements
|
|
LiveComponentContract,
|
|
Cacheable
|
|
{
|
|
public function getCacheTTL(): Duration
|
|
{
|
|
return Duration::fromMinutes(10);
|
|
}
|
|
|
|
public function getCacheKey(): string
|
|
{
|
|
return 'dashboard';
|
|
}
|
|
|
|
// Cache varies by user ID and locale
|
|
public function getVaryBy(): ?VaryBy
|
|
{
|
|
return VaryBy::userAndLocale();
|
|
}
|
|
|
|
// Results in cache keys like:
|
|
// livecomponent:user-dashboard:dashboard:u:123:l:en
|
|
// livecomponent:user-dashboard:dashboard:u:456:l:de
|
|
}
|
|
```
|
|
|
|
### VaryBy Factory Methods
|
|
|
|
```php
|
|
// No variation - global cache
|
|
VaryBy::none();
|
|
|
|
// Vary by user ID only
|
|
VaryBy::userId();
|
|
|
|
// Vary by locale only
|
|
VaryBy::locale();
|
|
|
|
// Vary by user and locale (most common)
|
|
VaryBy::userAndLocale();
|
|
|
|
// Vary by everything
|
|
VaryBy::all();
|
|
|
|
// Vary by feature flags
|
|
VaryBy::featureFlags(['new-ui', 'dark-mode']);
|
|
|
|
// Vary by custom factors
|
|
VaryBy::custom(['theme', 'region']);
|
|
```
|
|
|
|
### Fluent VaryBy Builder
|
|
|
|
```php
|
|
public function getVaryBy(): ?VaryBy
|
|
{
|
|
return VaryBy::none()
|
|
->withUserId() // Add user variation
|
|
->withLocale() // Add locale variation
|
|
->withFeatureFlags([ // Add feature flag variation
|
|
'new-ui',
|
|
'dark-mode',
|
|
'beta-features'
|
|
])
|
|
->withCustom(['theme']); // Add custom variation
|
|
}
|
|
|
|
// Results in:
|
|
// livecomponent:dashboard:main:u:123:l:en:f:new-ui,dark-mode:c:theme=dark
|
|
```
|
|
|
|
### VaryBy Examples
|
|
|
|
**User-Specific Dashboard**:
|
|
```php
|
|
#[LiveComponent('user-dashboard')]
|
|
final readonly class UserDashboardComponent implements Cacheable
|
|
{
|
|
public function getVaryBy(): ?VaryBy
|
|
{
|
|
return VaryBy::userId();
|
|
}
|
|
|
|
// Each user gets their own cached version
|
|
// User 123: livecomponent:user-dashboard:main:u:123
|
|
// User 456: livecomponent:user-dashboard:main:u:456
|
|
}
|
|
```
|
|
|
|
**Localized Content**:
|
|
```php
|
|
#[LiveComponent('product-catalog')]
|
|
final readonly class ProductCatalogComponent implements Cacheable
|
|
{
|
|
public function getVaryBy(): ?VaryBy
|
|
{
|
|
return VaryBy::locale();
|
|
}
|
|
|
|
// Each language gets its own cache
|
|
// English: livecomponent:product-catalog:products:l:en
|
|
// German: livecomponent:product-catalog:products:l:de
|
|
}
|
|
```
|
|
|
|
**Permission-Based Content**:
|
|
```php
|
|
#[LiveComponent('admin-panel')]
|
|
final readonly class AdminPanelComponent implements Cacheable
|
|
{
|
|
public function getVaryBy(): ?VaryBy
|
|
{
|
|
return VaryBy::roles();
|
|
}
|
|
|
|
// Each role combination gets its own cache
|
|
// Admin: livecomponent:admin-panel:main:r:admin
|
|
// Moderator: livecomponent:admin-panel:main:r:moderator
|
|
// Both: livecomponent:admin-panel:main:r:admin,moderator
|
|
}
|
|
```
|
|
|
|
**Feature Flag Variations**:
|
|
```php
|
|
#[LiveComponent('homepage')]
|
|
final readonly class HomepageComponent implements Cacheable
|
|
{
|
|
public function getVaryBy(): ?VaryBy
|
|
{
|
|
return VaryBy::featureFlags(['new-design', 'personalized-feed']);
|
|
}
|
|
|
|
// Different cache for each feature combination
|
|
// Old design: livecomponent:homepage:main:global
|
|
// New design only: livecomponent:homepage:main:f:new-design
|
|
// Both features: livecomponent:homepage:main:f:new-design,personalized-feed
|
|
}
|
|
```
|
|
|
|
**Complex Multi-Factor Caching**:
|
|
```php
|
|
#[LiveComponent('shopping-cart')]
|
|
final readonly class ShoppingCartComponent implements Cacheable
|
|
{
|
|
public function getVaryBy(): ?VaryBy
|
|
{
|
|
return VaryBy::none()
|
|
->withUserId() // Different per user
|
|
->withLocale() // Different per language
|
|
->withFeatureFlags(['express-checkout']) // Different if feature enabled
|
|
->withCustom(['currency', 'region']); // Different per currency/region
|
|
}
|
|
|
|
// Results in keys like:
|
|
// livecomponent:cart:main:u:123:l:en:f:express-checkout:c:currency=USD,region=US
|
|
// livecomponent:cart:main:u:123:l:de:c:currency=EUR,region=DE
|
|
}
|
|
```
|
|
|
|
### CacheContext and Runtime Context
|
|
|
|
The framework automatically provides `CacheContext` based on the current request:
|
|
|
|
```php
|
|
// Framework automatically creates CacheContext from:
|
|
$context = CacheContext::create(
|
|
userId: $currentUser?->id, // From auth session
|
|
locale: $request->getLocale(), // From request
|
|
roles: $currentUser?->roles ?? [], // From user object
|
|
featureFlags: $featureFlags->getActive(), // From feature service
|
|
custom: [
|
|
'theme' => $request->cookie->get('theme'),
|
|
'region' => $request->getRegion()
|
|
]
|
|
);
|
|
|
|
// VaryBy then applies this context to generate cache key suffix
|
|
$suffix = $varyBy->apply($context);
|
|
```
|
|
|
|
### VaryBy Performance Impact
|
|
|
|
**Cache Hit Rate**:
|
|
```
|
|
No varyBy (global): 95% hit rate, 1 cache entry
|
|
varyBy userId: 85% hit rate, 1000 cache entries
|
|
varyBy userId + locale: 75% hit rate, 3000 cache entries
|
|
varyBy userId + locale + flags: 60% hit rate, 10000 cache entries
|
|
```
|
|
|
|
**Trade-offs**:
|
|
- **More specific varyBy** = More cache entries = Lower hit rate = More storage
|
|
- **Less specific varyBy** = Fewer cache entries = Higher hit rate = Less personalization
|
|
|
|
**Recommendation**: Start with `VaryBy::userId()` or `VaryBy::userAndLocale()`, add more factors only if needed.
|
|
|
|
---
|
|
|
|
## Stale-While-Revalidate (SWR)
|
|
|
|
### What is SWR?
|
|
|
|
**Stale-While-Revalidate** serves cached content immediately (even if expired) while refreshing in the background:
|
|
|
|
```
|
|
Traditional:
|
|
Request → Cache Miss → Database → Response (500ms)
|
|
|
|
SWR:
|
|
Request → Stale Cache → Response (10ms) + Background Refresh → Updated Cache
|
|
```
|
|
|
|
### Enable SWR
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Contracts\Cacheable;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
|
|
#[LiveComponent('news-feed')]
|
|
final readonly class NewsFeedComponent implements Cacheable
|
|
{
|
|
public function getCacheTTL(): Duration
|
|
{
|
|
return Duration::fromMinutes(5); // Fresh for 5 minutes
|
|
}
|
|
|
|
public function getStaleWhileRevalidate(): ?Duration
|
|
{
|
|
return Duration::fromMinutes(30); // Serve stale up to 30 minutes
|
|
}
|
|
|
|
public function getCacheKey(): string
|
|
{
|
|
return 'feed-latest';
|
|
}
|
|
}
|
|
```
|
|
|
|
**How it works**:
|
|
1. **0-5 min**: Cache fresh → Serve from cache (instant)
|
|
2. **5-30 min**: Cache stale → Serve stale (instant) + refresh in background
|
|
3. **30+ min**: Cache expired → Fetch fresh → Update cache → Serve
|
|
|
|
### SWR with varyBy
|
|
|
|
```php
|
|
public function getVaryBy(): ?VaryBy
|
|
{
|
|
return VaryBy::userId();
|
|
}
|
|
|
|
public function getStaleWhileRevalidate(): ?Duration
|
|
{
|
|
return Duration::fromHours(1);
|
|
}
|
|
|
|
// Each user gets their own SWR cache:
|
|
// User 123: Fresh for 5min, stale-ok for 1h
|
|
// User 456: Fresh for 5min, stale-ok for 1h
|
|
```
|
|
|
|
### SWR Metrics
|
|
|
|
The framework provides metrics for SWR performance:
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Caching\CacheMetrics;
|
|
|
|
// After rendering component
|
|
$metrics = $component->getCacheMetrics();
|
|
|
|
if ($metrics->isStale) {
|
|
$this->logger->info('SWR: Served stale content', [
|
|
'age_seconds' => $metrics->ageSeconds,
|
|
'is_refreshing' => $metrics->isRefreshing,
|
|
'cache_key' => $metrics->cacheKey
|
|
]);
|
|
}
|
|
```
|
|
|
|
### SWR Use Cases
|
|
|
|
**✅ Perfect for SWR**:
|
|
- News feeds (5min fresh, 1h stale-ok)
|
|
- Product listings (10min fresh, 2h stale-ok)
|
|
- User profiles (15min fresh, 24h stale-ok)
|
|
- Dashboards (5min fresh, 30min stale-ok)
|
|
|
|
**❌ Not suitable for SWR**:
|
|
- Real-time stock prices
|
|
- Shopping cart totals
|
|
- Payment confirmations
|
|
- Authentication status
|
|
|
|
### SWR Response Headers
|
|
|
|
The framework automatically adds cache headers:
|
|
|
|
```http
|
|
Cache-Control: max-age=300, stale-while-revalidate=1800
|
|
Age: 450
|
|
X-Cache-Status: STALE
|
|
X-Cache-Refreshing: true
|
|
```
|
|
|
|
Client-side interpretation:
|
|
```javascript
|
|
const cacheStatus = response.headers.get('X-Cache-Status');
|
|
|
|
if (cacheStatus === 'STALE') {
|
|
// Show indicator: "Updating..."
|
|
showRefreshIndicator();
|
|
|
|
// Listen for updated data
|
|
liveComponent.on('cache:refreshed', (data) => {
|
|
hideRefreshIndicator();
|
|
// Data automatically updated
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Request Batching
|
|
|
|
### What is Batching?
|
|
|
|
Combine multiple component actions into a single HTTP request:
|
|
|
|
```
|
|
Without Batching:
|
|
Action 1 → HTTP Request → Response (100ms)
|
|
Action 2 → HTTP Request → Response (100ms)
|
|
Action 3 → HTTP Request → Response (100ms)
|
|
Total: 300ms + 3x overhead
|
|
|
|
With Batching:
|
|
Actions 1+2+3 → Single HTTP Request → Response (120ms)
|
|
Total: 120ms + 1x overhead
|
|
```
|
|
|
|
### Enable Batching
|
|
|
|
```javascript
|
|
import { LiveComponent } from './live-component.js';
|
|
|
|
const component = new LiveComponent('dashboard:main', {
|
|
batching: {
|
|
enabled: true,
|
|
maxBatchSize: 10, // Max actions per batch
|
|
batchWindow: 50 // Wait 50ms to collect actions
|
|
}
|
|
});
|
|
|
|
// Execute multiple actions
|
|
component.executeAction('updateStats');
|
|
component.executeAction('updateChart');
|
|
component.executeAction('updateNotifications');
|
|
|
|
// Framework automatically batches these into single request
|
|
```
|
|
|
|
### Batching Strategies
|
|
|
|
**Auto-Batching** (default):
|
|
```javascript
|
|
// Framework detects rapid actions and batches automatically
|
|
for (let i = 0; i < 5; i++) {
|
|
liveComponent.executeAction('increment');
|
|
}
|
|
// Sent as single batched request
|
|
```
|
|
|
|
**Manual Batching**:
|
|
```javascript
|
|
liveComponent.batch([
|
|
{ action: 'updateStats', params: {} },
|
|
{ action: 'updateChart', params: { period: '7d' } },
|
|
{ action: 'refreshNotifications', params: {} }
|
|
]);
|
|
```
|
|
|
|
**Conditional Batching**:
|
|
```javascript
|
|
// Only batch if multiple actions pending
|
|
const config = {
|
|
batching: {
|
|
enabled: true,
|
|
minBatchSize: 2 // Only batch if 2+ actions
|
|
}
|
|
};
|
|
```
|
|
|
|
### Backend Batch Processing
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Batching\BatchRequest;
|
|
use App\Framework\LiveComponents\Batching\BatchResponse;
|
|
|
|
// Framework automatically handles batched requests
|
|
// Your component actions work the same way
|
|
|
|
#[Action]
|
|
public function updateStats(): DashboardState
|
|
{
|
|
// Normal action implementation
|
|
// Framework handles batching transparently
|
|
}
|
|
```
|
|
|
|
### Batching Performance
|
|
|
|
**Bandwidth Reduction**:
|
|
```
|
|
3 separate requests: 3x overhead (headers, handshake) = ~2KB
|
|
1 batched request: 1x overhead = ~0.7KB (65% reduction)
|
|
```
|
|
|
|
**Latency Reduction**:
|
|
```
|
|
3 sequential requests: 300ms
|
|
3 parallel requests: 100ms (but 3x connections)
|
|
1 batched request: 120ms (60% faster than sequential)
|
|
```
|
|
|
|
---
|
|
|
|
## Polling Optimization
|
|
|
|
### Adaptive Polling Intervals
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Contracts\Pollable;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
|
|
#[LiveComponent('stock-ticker')]
|
|
final readonly class StockTickerComponent implements
|
|
LiveComponentContract,
|
|
Pollable
|
|
{
|
|
public function poll(): StockTickerState
|
|
{
|
|
$price = $this->stockService->getCurrentPrice($this->state->symbol);
|
|
|
|
return $this->state->withPrice($price);
|
|
}
|
|
|
|
public function getPollInterval(): int
|
|
{
|
|
// Adaptive interval based on market hours
|
|
$isMarketOpen = $this->timeService->isMarketOpen();
|
|
|
|
return $isMarketOpen
|
|
? 5000 // 5 seconds during market hours
|
|
: 60000; // 1 minute when market closed
|
|
}
|
|
}
|
|
```
|
|
|
|
### Backoff Strategies
|
|
|
|
```javascript
|
|
// Exponential backoff on errors
|
|
const component = new LiveComponent('notifications:main', {
|
|
polling: {
|
|
enabled: true,
|
|
interval: 10000, // Start with 10s
|
|
maxInterval: 300000, // Max 5 minutes
|
|
backoffMultiplier: 2, // Double on each error
|
|
backoffReset: 60000 // Reset after 1min success
|
|
}
|
|
});
|
|
|
|
// After error:
|
|
// Attempt 1: 10s
|
|
// Attempt 2: 20s (error)
|
|
// Attempt 3: 40s (error)
|
|
// Attempt 4: 80s (error)
|
|
// Attempt 5: 160s (error)
|
|
// Attempt 6: 300s (capped at max)
|
|
```
|
|
|
|
### Conditional Polling
|
|
|
|
```php
|
|
public function shouldPoll(): bool
|
|
{
|
|
// Don't poll if user inactive
|
|
if ($this->state->lastActivityAt < time() - 300) {
|
|
return false;
|
|
}
|
|
|
|
// Don't poll if data is fresh
|
|
if ($this->state->updatedAt > time() - 60) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Visibility-Based Polling
|
|
|
|
```javascript
|
|
// Stop polling when tab hidden, resume when visible
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.hidden) {
|
|
liveComponent.pausePolling();
|
|
} else {
|
|
liveComponent.resumePolling();
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Monitoring
|
|
|
|
### Cache Metrics
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Caching\CacheMetrics;
|
|
|
|
// Log cache performance
|
|
$this->logger->info('Component cached', [
|
|
'hit' => $metrics->hit,
|
|
'age_seconds' => $metrics->ageSeconds,
|
|
'is_stale' => $metrics->isStale,
|
|
'freshness_percent' => $metrics->getFreshnessPercent(),
|
|
'cache_key' => $metrics->cacheKey
|
|
]);
|
|
```
|
|
|
|
### Performance Tracking
|
|
|
|
```php
|
|
use App\Framework\Performance\PerformanceCollector;
|
|
|
|
#[Action]
|
|
public function loadData(): ComponentState
|
|
{
|
|
$start = microtime(true);
|
|
|
|
$data = $this->expensiveOperation();
|
|
|
|
$this->performance->track('component.action.load-data', [
|
|
'duration_ms' => (microtime(true) - $start) * 1000,
|
|
'cache_hit' => $this->wasCached(),
|
|
'fragment_count' => count($this->getFragments())
|
|
]);
|
|
|
|
return $this->state->withData($data);
|
|
}
|
|
```
|
|
|
|
### Client-Side Performance
|
|
|
|
```javascript
|
|
// Track render performance
|
|
liveComponent.on('render:complete', (metrics) => {
|
|
console.log('Render performance:', {
|
|
duration: metrics.renderDuration,
|
|
fragmentCount: metrics.fragmentsUpdated,
|
|
nodesPatched: metrics.nodesPatched,
|
|
wasCached: metrics.fromCache
|
|
});
|
|
|
|
// Send to analytics
|
|
analytics.track('livecomponent.render', metrics);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use Fragments for Large Components
|
|
|
|
```html
|
|
<!-- ✅ Good - Fragment-based dashboard -->
|
|
<div data-lc-component="dashboard">
|
|
<div data-lc-fragment="stats"><!-- 2KB --></div>
|
|
<div data-lc-fragment="chart"><!-- 15KB --></div>
|
|
<div data-lc-fragment="activity"><!-- 8KB --></div>
|
|
</div>
|
|
|
|
<!-- Update only stats fragment (2KB instead of 25KB) -->
|
|
<button data-lc-action="refreshStats" data-lc-fragments="stats">Refresh</button>
|
|
```
|
|
|
|
### 2. Cache Expensive Renders
|
|
|
|
```php
|
|
// ✅ Good - Cache expensive database queries
|
|
public function getCacheTTL(): Duration
|
|
{
|
|
return Duration::fromMinutes(5);
|
|
}
|
|
|
|
public function getVaryBy(): ?VaryBy
|
|
{
|
|
return VaryBy::userId();
|
|
}
|
|
```
|
|
|
|
### 3. Use SWR for Non-Critical Data
|
|
|
|
```php
|
|
// ✅ Good - News feed with SWR
|
|
public function getCacheTTL(): Duration
|
|
{
|
|
return Duration::fromMinutes(5);
|
|
}
|
|
|
|
public function getStaleWhileRevalidate(): ?Duration
|
|
{
|
|
return Duration::fromHours(1); // Serve stale up to 1 hour
|
|
}
|
|
```
|
|
|
|
### 4. Batch Related Actions
|
|
|
|
```javascript
|
|
// ✅ Good - Batch dashboard updates
|
|
component.batch([
|
|
{ action: 'updateStats' },
|
|
{ action: 'updateChart' },
|
|
{ action: 'refreshActivity' }
|
|
]);
|
|
|
|
// ❌ Bad - Separate requests
|
|
component.executeAction('updateStats');
|
|
component.executeAction('updateChart');
|
|
component.executeAction('refreshActivity');
|
|
```
|
|
|
|
### 5. Optimize Polling
|
|
|
|
```php
|
|
// ✅ Good - Adaptive polling interval
|
|
public function getPollInterval(): int
|
|
{
|
|
return $this->isHighPriority() ? 5000 : 30000;
|
|
}
|
|
|
|
// ❌ Bad - Aggressive fixed interval
|
|
public function getPollInterval(): int
|
|
{
|
|
return 1000; // Too frequent!
|
|
}
|
|
```
|
|
|
|
### 6. Monitor Performance
|
|
|
|
```php
|
|
// ✅ Good - Track and optimize
|
|
$this->performance->track('component.render', [
|
|
'duration_ms' => $duration,
|
|
'cache_hit' => $cacheHit,
|
|
'fragment_count' => $fragmentCount
|
|
]);
|
|
|
|
// Alert if performance degrades
|
|
if ($duration > 500) {
|
|
$this->alerting->send('Slow component render', $context);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Checklist
|
|
|
|
Before deploying to production:
|
|
|
|
- [ ] Identify large components and add fragments
|
|
- [ ] Enable caching for expensive renders
|
|
- [ ] Configure appropriate varyBy context
|
|
- [ ] Use SWR for non-critical data
|
|
- [ ] Batch related actions
|
|
- [ ] Optimize polling intervals
|
|
- [ ] Add performance monitoring
|
|
- [ ] Run performance benchmarks
|
|
- [ ] Test with realistic data volumes
|
|
- [ ] Monitor cache hit rates
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
LiveComponents performance optimizations:
|
|
|
|
✅ **Fragment Rendering** - 60-90% bandwidth reduction
|
|
✅ **Component Caching** - 95-99% faster repeated renders
|
|
✅ **VaryBy Context** - Intelligent cache variations
|
|
✅ **Stale-While-Revalidate** - Zero perceived latency
|
|
✅ **Request Batching** - 40-70% fewer requests
|
|
✅ **Adaptive Polling** - Optimized real-time updates
|
|
|
|
Apply these strategies based on your use case for optimal performance.
|