diff --git a/README.md b/README.md index ababf35..d1e6478 100644 --- a/README.md +++ b/README.md @@ -7,21 +7,13 @@ [](https://packagist.org/packages/iazaran/smart-cache) [](https://github.com/iazaran/smart-cache/actions) -A drop-in replacement for Laravel's `Cache` facade that automatically compresses, chunks, and optimizes cached data — with write deduplication, self-healing recovery, and cost-aware eviction built in. Implements `Illuminate\Contracts\Cache\Repository` and PSR-16 `SimpleCache`; your existing code works unchanged. +**Drop-in replacement for Laravel's `Cache` facade** that automatically compresses, chunks, and optimizes cached data — with write deduplication, self-healing recovery, and cost-aware eviction built in. -**PHP 8.1+ · Laravel 8–12 · All cache drivers** +Implements `Illuminate\Contracts\Cache\Repository` and PSR-16 `SimpleCache`. Your existing code works unchanged. -## Why SmartCache? +**PHP 8.1+ · Laravel 8–12 · Redis, File, Database, Memcached, Array** -| Concern | Without SmartCache | With SmartCache | -|---|---|---| -| Large payloads (100 KB+) | Stored as-is, slow reads | Auto-compressed & chunked | -| Redundant writes | Every `put()` hits the store | Skipped when content is unchanged (write deduplication) | -| Corrupted entries | Exception propagates to users | Auto-evicted and regenerated (self-healing) | -| Eviction strategy | LRU / random | Cost-aware scoring — keeps high-value keys | -| Cache stampede | Thundering herd on expiry | XFetch, jitter, and rate limiting | -| Conditional caching | Manual `if` checks around `put()` | `rememberIf()` — one-liner | -| Monitoring | DIY logging | Built-in dashboard, metrics, and health checks | +--- ## Installation @@ -29,18 +21,18 @@ A drop-in replacement for Laravel's `Cache` facade that automatically compresses composer require iazaran/smart-cache ``` -No configuration required. Works immediately with any cache driver (Redis, File, Database, Memcached, Array). +That's it. No configuration required — works immediately with your existing cache driver. ## Quick Start ```php use SmartCache\Facades\SmartCache; -// Same API as Laravel's Cache facade — with automatic optimization +// Same API you already know SmartCache::put('users', $users, 3600); $users = SmartCache::get('users'); -// Remember pattern +// Remember pattern — with automatic compression & cost tracking $users = SmartCache::remember('users', 3600, fn() => User::all()); // Helper function @@ -50,111 +42,114 @@ $products = smart_cache('products'); Large data is automatically compressed and chunked behind the scenes. No code changes needed. -### Multiple Cache Drivers - -```php -// Each store preserves all SmartCache optimizations -SmartCache::store('redis')->put('key', $value, 3600); -SmartCache::store('memcached')->remember('users', 3600, fn() => User::all()); - -// Bypass SmartCache when needed -SmartCache::repository('redis')->put('key', $value, 3600); -``` +## Why SmartCache? -SmartCache implements `Illuminate\Contracts\Cache\Repository`, so it works anywhere Laravel's Cache contract is expected. +| Problem | Without SmartCache | With SmartCache | +|---|---|---| +| Large payloads (100 KB+) | Stored as-is, slow reads | Auto-compressed & chunked | +| Redundant writes | Every `put()` hits the store | Skipped when unchanged (write deduplication) | +| Corrupted entries | Exception crashes the request | Auto-evicted and regenerated (self-healing) | +| Eviction decisions | LRU / random | Cost-aware scoring — keeps high-value keys | +| Cache stampede | Thundering herd on expiry | XFetch, jitter, and rate limiting | +| Conditional caching | Manual `if` around `put()` | `rememberIf()` — one-liner | +| Stale data serving | Not available | SWR, stale, refresh-ahead, async queue refresh | +| Observability | DIY logging | Built-in dashboard, metrics, and health checks | -## Automatic Optimization +### How Automatic Optimization Works -SmartCache selects the best strategy based on your data: +SmartCache selects the best strategy based on your data — zero configuration: -| Data Profile | Strategy | Effect | +| Data Profile | Strategy Applied | Effect | |---|---|---| | Arrays with 5 000+ items | Chunking | Lower memory, faster access | | Serialized data > 50 KB | Compression | Significant size reduction (gzip) | | API responses > 100 KB | Chunking + Compression | Best of both | | Data < 50 KB | None | Zero overhead | -All thresholds are configurable. See [Configuration](#configuration). +All thresholds are [configurable](#configuration). -## Advanced Features +## Features Every feature below is **opt-in** and backward-compatible. -### Atomic Locks +### Multiple Cache Drivers ```php -SmartCache::lock('expensive_operation', 10)->get(function () { - return regenerateExpensiveData(); -}); -``` - -### In-Request Memoization +// Each store preserves all SmartCache optimizations +SmartCache::store('redis')->put('key', $value, 3600); +SmartCache::store('memcached')->remember('users', 3600, fn() => User::all()); -```php -$memo = SmartCache::memo(); -$users = $memo->remember('users', 3600, fn() => User::all()); -$users = $memo->get('users'); // instant — served from memory +// Bypass SmartCache when needed +SmartCache::repository('redis')->put('key', $value, 3600); ``` -### Batch Operations +### SWR Patterns (Stale-While-Revalidate) ```php -$values = SmartCache::many(['key1', 'key2', 'key3']); -SmartCache::putMany(['key1' => $a, 'key2' => $b], 3600); -SmartCache::deleteMultiple(['key1', 'key2', 'key3']); -``` +// Serve stale data while refreshing in background +$data = SmartCache::swr('github_repos', fn() => Http::get('...')->json(), 300, 900); -### Adaptive Compression +// Extended stale serving (1 h fresh, 24 h stale) +$config = SmartCache::stale('site_config', fn() => Config::fromDatabase(), 3600, 86400); -```php -// Adjusts compression level per entry based on access frequency and compressibility -config(['smart-cache.strategies.compression.mode' => 'adaptive']); +// Proactive refresh before expiry +$analytics = SmartCache::refreshAhead('daily_analytics', fn() => Analytics::generateReport(), 1800, 300); + +// Queue-based background refresh — returns stale immediately +$data = SmartCache::asyncSwr('dashboard_stats', fn() => Stats::generate(), 300, 900, 'cache-refresh'); ``` -### Lazy Loading +### Stampede Protection ```php -config(['smart-cache.strategies.chunking.lazy_loading' => true]); +// XFetch algorithm — probabilistic early refresh +$data = SmartCache::rememberWithStampedeProtection('key', 3600, fn() => expensiveQuery()); -$dataset = SmartCache::get('100k_records'); // LazyChunkedCollection -foreach ($dataset as $record) { /* max 3 chunks in memory */ } +// Rate-limited regeneration +SmartCache::throttle('api_call', 10, 60, fn() => expensiveApiCall()); + +// TTL jitter — prevents thundering herd on expiry +SmartCache::withJitter(0.1)->put('popular_data', $data, 3600); +// Actual TTL: 3240–3960 s (±10 %) ``` -### Cache Events +### Write Deduplication (Cache DNA) -```php -config(['smart-cache.events.enabled' => true]); +Hashes every value before writing. Identical content → write skipped entirely. -Event::listen(CacheHit::class, fn($e) => Log::info("Hit: {$e->key}")); -Event::listen(CacheMissed::class, fn($e) => Log::warning("Miss: {$e->key}")); -Event::listen(OptimizationApplied::class, fn($e) => Log::info("Optimized: {$e->key}")); +```php +SmartCache::put('app_config', Config::all(), 3600); +SmartCache::put('app_config', Config::all(), 3600); // no I/O — data unchanged ``` -### Encryption at Rest +### Self-Healing Cache + +Corrupted entries are auto-evicted and regenerated on next read — zero downtime. ```php -// config/smart-cache.php -'encryption' => [ - 'enabled' => true, - 'keys' => ['user_*', 'payment_*'], -], +$report = SmartCache::remember('report', 3600, fn() => Analytics::generate()); ``` -### Namespacing +### Conditional Caching ```php -SmartCache::namespace('api_v2')->put('users', $users, 3600); -SmartCache::flushNamespace('api_v2'); +$data = SmartCache::rememberIf('external_api', 3600, + fn() => Http::get('https://api.example.com/data')->json(), + fn($value) => !empty($value) && isset($value['status']) +); ``` -### TTL Jitter +### Cost-Aware Eviction + +GreedyDual-Size–inspired scoring: `score = (cost × ln(1 + access_count) × decay) / size` ```php -SmartCache::withJitter(0.1)->put('popular_data', $data, 3600); -// Actual TTL: 3240–3960 s (±10 %) — prevents thundering herd +SmartCache::remember('analytics', 3600, fn() => AnalyticsService::generateReport()); +SmartCache::getCacheValueReport(); // all entries ranked by value +SmartCache::suggestEvictions(5); // lowest-value entries to remove ``` -### Circuit Breaker +### Circuit Breaker & Fallback ```php $data = SmartCache::withFallback( @@ -163,75 +158,42 @@ $data = SmartCache::withFallback( ); ``` -### Stampede Protection - -```php -// XFetch algorithm — probabilistic early refresh -$data = SmartCache::rememberWithStampedeProtection('key', 3600, fn() => expensiveQuery()); - -// Rate-limited regeneration -SmartCache::throttle('api_call', 10, 60, fn() => expensiveApiCall()); -``` - -### Cost-Aware Caching - -Implements a GreedyDual-Size–inspired scoring model. Every `remember()` call tracks regeneration cost, access frequency, and entry size to compute a value score: - -``` -score = (cost × ln(1 + access_count) × decay) / size -``` - -```php -// Works transparently — just use remember() -SmartCache::remember('analytics', 3600, fn() => AnalyticsService::generateReport()); - -// Inspect value scores -SmartCache::getCacheValueReport(); -SmartCache::cacheValue('analytics'); -SmartCache::suggestEvictions(5); // lowest-value entries to remove first -``` - -### Write Deduplication (Cache DNA) - -SmartCache hashes every value before writing. When the stored content is identical, the write is skipped entirely — eliminating redundant I/O for frequently refreshed but rarely changing data (configuration, feature flags, rate-limit counters). +### In-Request Memoization ```php -// Frequent cron refreshes? Only the first write hits the store. -SmartCache::put('app_config', Config::all(), 3600); -// Second call with the same data → no I/O, returns true immediately -SmartCache::put('app_config', Config::all(), 3600); +$memo = SmartCache::memo(); +$users = $memo->remember('users', 3600, fn() => User::all()); +$users = $memo->get('users'); // instant — served from memory ``` -Enabled by default. Disable per-environment: +### Atomic Locks ```php -'deduplication' => ['enabled' => false], +SmartCache::lock('expensive_operation', 10)->get(function () { + return regenerateExpensiveData(); +}); ``` -### Conditional Caching (`rememberIf`) - -Cache values only when a condition is met. The callback always executes, but the result is stored only if the condition returns `true` — useful for filtering out empty or invalid API responses. +### Namespacing ```php -$data = SmartCache::rememberIf('external_api', 3600, - fn() => Http::get('https://api.example.com/data')->json(), - fn($value) => !empty($value) && isset($value['status']) -); +SmartCache::namespace('api_v2')->put('users', $users, 3600); +SmartCache::flushNamespace('api_v2'); ``` -### Self-Healing Cache - -Corrupted or unrestorable cache entries are automatically evicted instead of propagating an exception. Combined with `remember()` or `rememberIf()`, the entry is transparently regenerated on the next read — zero downtime, zero manual intervention. +### Cache Invalidation ```php -// If 'report' is corrupted, SmartCache evicts it and the callback runs again -$report = SmartCache::remember('report', 3600, fn() => Analytics::generate()); -``` +// Pattern-based +SmartCache::flushPatterns(['user_*', 'api_v2_*', '/product_\d+/']); -Enabled by default. Disable per-environment: +// Dependency tracking +SmartCache::dependsOn('user_posts', 'user_profile'); +SmartCache::invalidate('user_profile'); // also clears user_posts -```php -'self_healing' => ['enabled' => false], +// Tag-based +SmartCache::tags(['users'])->put('user_1', $user, 3600); +SmartCache::flushTags(['users']); ``` ### Model Auto-Invalidation @@ -250,71 +212,66 @@ class User extends Model } ``` -### Cache Warming +### Encryption at Rest ```php -php artisan smart-cache:warm -php artisan smart-cache:warm --keys=products,categories +// config/smart-cache.php → strategies.encryption +'encryption' => [ + 'enabled' => true, + 'keys' => ['user_token_abc123'], // exact cache-key match + 'patterns' => ['/^user_token_/', '/^payment_/'], // regex match +], ``` -### Orphan Chunk Cleanup +### Adaptive Compression -```bash -php artisan smart-cache:cleanup-chunks +```php +config(['smart-cache.strategies.compression.mode' => 'adaptive']); +// Hot data → fast compression (level 3–4), cold data → high compression (level 7–9) ``` -### Statistics Dashboard +### Lazy Loading ```php -'dashboard' => ['enabled' => true, 'prefix' => 'smart-cache', 'middleware' => ['web', 'auth']], -// GET /smart-cache/dashboard | /smart-cache/stats | /smart-cache/health +config(['smart-cache.strategies.chunking.lazy_loading' => true]); +$dataset = SmartCache::get('100k_records'); // LazyChunkedCollection +foreach ($dataset as $record) { /* max 3 chunks in memory */ } ``` -## SWR Patterns (Laravel 12+) +### Batch Operations ```php -// Stale-While-Revalidate -$data = SmartCache::swr('github_repos', fn() => Http::get('...')->json(), 300, 900); - -// Extended stale serving -$config = SmartCache::stale('site_config', fn() => Config::fromDatabase(), 3600, 86400); - -// Refresh-ahead -$analytics = SmartCache::refreshAhead('daily_analytics', fn() => Analytics::generateReport(), 1800, 300); - -// Queue-based background refresh — returns stale data immediately, refreshes asynchronously -$data = SmartCache::asyncSwr('dashboard_stats', fn() => Stats::generate(), 300, 900, 'cache-refresh'); +$values = SmartCache::many(['key1', 'key2', 'key3']); +SmartCache::putMany(['key1' => $a, 'key2' => $b], 3600); +SmartCache::deleteMultiple(['key1', 'key2', 'key3']); ``` -## Invalidation +### Cache Events ```php -// Pattern-based -SmartCache::flushPatterns(['user_*', 'api_v2_*', '/product_\d+/']); - -// Dependency tracking -SmartCache::dependsOn('user_posts', 'user_profile'); -SmartCache::invalidate('user_profile'); // also clears user_posts - -// Tag-based -SmartCache::tags(['users'])->put('user_1', $user, 3600); -SmartCache::flushTags(['users']); +config(['smart-cache.events.enabled' => true]); +Event::listen(CacheHit::class, fn($e) => Log::info("Hit: {$e->key}")); +Event::listen(CacheMissed::class, fn($e) => Log::warning("Miss: {$e->key}")); ``` -## Monitoring +### Monitoring & Dashboard ```php SmartCache::getPerformanceMetrics(); // hit_ratio, compression_savings, timing -SmartCache::analyzePerformance(); // health score, recommendations +SmartCache::analyzePerformance(); // health score + recommendations +``` -SmartCache::executeCommand('status'); -SmartCache::executeCommand('clear', ['key' => 'api_data']); +```php +// Enable web dashboard +'dashboard' => ['enabled' => true, 'prefix' => 'smart-cache', 'middleware' => ['web', 'auth']], +// GET /smart-cache/dashboard | /smart-cache/stats | /smart-cache/health ``` ```bash php artisan smart-cache:status -php artisan smart-cache:status --force php artisan smart-cache:clear +php artisan smart-cache:warm --warmer=products --warmer=categories +php artisan smart-cache:cleanup-chunks ``` ## Configuration @@ -335,15 +292,16 @@ return [ 'strategies' => [ 'compression' => ['enabled' => true, 'mode' => 'fixed', 'level' => 6], 'chunking' => ['enabled' => true, 'chunk_size' => 1000], + 'encryption' => ['enabled' => false, 'keys' => []], ], 'monitoring' => ['enabled' => true, 'metrics_ttl' => 3600], - 'circuit_breaker' => ['enabled' => true, 'failure_threshold' => 5, 'timeout' => 30], - 'rate_limiter' => ['enabled' => true, 'default_limit' => 100, 'window' => 60], - 'encryption' => ['enabled' => false, 'keys' => []], + 'circuit_breaker' => ['enabled' => false, 'failure_threshold' => 5, 'recovery_timeout' => 30], + 'rate_limiter' => ['enabled' => true, 'window' => 60, 'max_attempts' => 10], 'jitter' => ['enabled' => false, 'percentage' => 0.1], 'deduplication' => ['enabled' => true], // Write deduplication (Cache DNA) 'self_healing' => ['enabled' => true], // Auto-evict corrupted entries 'dashboard' => ['enabled' => false, 'prefix' => 'smart-cache', 'middleware' => ['web']], + 'warmers' => [], // Cache warmer classes for smart-cache:warm ]; ``` @@ -361,7 +319,7 @@ $users = SmartCache::get('users'); ## Documentation -[Full documentation](https://iazaran.github.io/smart-cache/) — Installation, API reference, SWR patterns, and more. +[Full documentation →](https://iazaran.github.io/smart-cache/) — Installation, API reference, SWR patterns, and more. ## Testing diff --git a/composer.json b/composer.json index 78ed6bb..98f1d08 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,15 @@ "cache serialization", "cache strategies", "php caching", - "laravel optimization" + "laravel optimization", + "stale-while-revalidate", + "swr", + "cache stampede prevention", + "cost-aware eviction", + "self-healing cache", + "write deduplication", + "psr-16", + "laravel cache replacement" ], "homepage": "https://github.com/iazaran/smart-cache", "type": "library", diff --git a/config/smart-cache.php b/config/smart-cache.php index 2ea4585..6d0cfa6 100644 --- a/config/smart-cache.php +++ b/config/smart-cache.php @@ -55,6 +55,12 @@ 'patterns' => [], // Regex patterns for keys to encrypt (e.g., '/^user_token_/') 'encrypt_all' => false, // Encrypt all cached values ], + 'serialization' => [ + 'enabled' => false, // Disabled by default + 'preferred_method' => 'auto', // auto, json, igbinary, php + 'auto_detect' => true, // Auto-detect best method per value + 'size_threshold' => 1024, // Minimum size in bytes to apply optimization + ], ], /* @@ -66,6 +72,7 @@ | */ 'circuit_breaker' => [ + 'enabled' => false, // Disabled by default — enable with withCircuitBreaker() 'failure_threshold' => 5, // Number of failures before opening circuit 'recovery_timeout' => 30, // Seconds to wait before trying again 'success_threshold' => 3, // Successful calls needed to close circuit @@ -80,6 +87,7 @@ | */ 'rate_limiter' => [ + 'enabled' => true, // Enable rate limiting for stampede protection 'window' => 60, // Window in seconds 'max_attempts' => 10, // Maximum attempts per window ], @@ -239,4 +247,22 @@ 'prefix' => 'smart-cache', // URL prefix for dashboard routes 'middleware' => ['web'], // Middleware to apply to dashboard routes ], + + /* + |-------------------------------------------------------------------------- + | Cache Warmers + |-------------------------------------------------------------------------- + | + | Register cache warmer classes to pre-warm the cache on demand via + | the `smart-cache:warm` Artisan command. Each warmer must implement + | a `warm(): array` method returning ['keys' => int]. + | + | Example: + | 'users' => App\CacheWarmers\UserCacheWarmer::class, + | + */ + 'warmers' => [ + // 'users' => App\CacheWarmers\UserCacheWarmer::class, + // 'products' => App\CacheWarmers\ProductCacheWarmer::class, + ], ]; \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 42e2f8e..ef026a5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,6 +6,7 @@ + @@ -954,34 +955,6 @@
Auto-optimize compression levels based on data characteristics:
- -// Enable adaptive compression
-config(['smart-cache.strategies.compression.mode' => 'adaptive']);
-
-// Automatically selects optimal compression level:
-// - Hot data (frequently accessed) = level 3-4 (faster)
-// - Cold data (rarely accessed) = level 7-9 (smaller)
-// - Based on compressibility analysis
-
-SmartCache::put('hot_data', $frequentlyAccessed, 3600);
-SmartCache::put('cold_data', $rarelyAccessed, 3600);
-
- Load large datasets on-demand, keeping only the active chunk in memory:
- -// Enable lazy loading
-config(['smart-cache.strategies.chunking.lazy_loading' => true]);
-
-// Returns LazyChunkedCollection - loads chunks on-demand
-$largeDataset = SmartCache::get('100k_records');
-
-foreach ($largeDataset as $record) {
- processRecord($record);
- // Only 3 chunks in memory at once
-}
-
Auto-select best serialization method:
@@ -1454,11 +1427,11 @@Encrypt sensitive cached data automatically:
-// Configure encryption in config/smart-cache.php
+ // Configure encryption in config/smart-cache.php → strategies.encryption
'encryption' => [
'enabled' => true,
- 'keys' => ['user_*', 'payment_*'], // Keys to encrypt
- 'patterns' => ['/secret_.*/'], // Regex patterns
+ 'keys' => ['user_token_abc123'], // Exact cache-key match
+ 'patterns' => ['/^user_/', '/^payment_/'], // Regex patterns
],
// Data matching patterns is automatically encrypted
@@ -1516,8 +1489,8 @@ 🔌 Circuit Breaker
'circuit_breaker' => [
'enabled' => true,
'failure_threshold' => 5, // Open after 5 failures
- 'success_threshold' => 2, // Close after 2 successes
- 'timeout' => 30, // Try again after 30 seconds
+ 'recovery_timeout' => 30, // Try again after 30 seconds
+ 'success_threshold' => 3, // Close after 3 successes
],
// Check if cache is available
@@ -1556,8 +1529,8 @@ 🚦 Rate Limiting & Stampede Protection
// Configure rate limiter
'rate_limiter' => [
'enabled' => true,
- 'default_limit' => 100,
'window' => 60,
+ 'max_attempts' => 10,
],
🔥 Cache Warming
@@ -1568,26 +1541,35 @@ 🔥 Cache Warming
class ProductCacheWarmer implements CacheWarmer
{
- public function warm(): void
+ public function warm(): array
{
$products = Product::with('images')->get();
SmartCache::put('all_products', $products, 3600);
+
+ return ['keys' => 1];
+ }
+
+ public function getName(): string
+ {
+ return 'products';
}
- public function getKeys(): array
+ public function getDescription(): string
{
- return ['all_products'];
+ return 'Warms the product cache';
}
}
-// Register in service provider
-$this->app->tag([ProductCacheWarmer::class], 'cache.warmers');
+// Register in config/smart-cache.php
+'warmers' => [
+ 'products' => App\CacheWarmers\ProductCacheWarmer::class,
+],
// Run cache warming command
php artisan smart-cache:warm
-// Or warm specific keys
-php artisan smart-cache:warm --keys=all_products,categories
+// Or warm specific warmers
+php artisan smart-cache:warm --warmer=products
Automatically clean up orphaned chunks from expired data:
@@ -1742,23 +1724,27 @@Execute cache operation with automatic fallback on failure.
Pre-populate cache with frequently accessed data using registered warmers.
# Warm all registered cache warmers
php artisan smart-cache:warm
-# Warm specific keys only
-php artisan smart-cache:warm --keys=products,categories
+# Warm specific warmers only
+php artisan smart-cache:warm --warmer=products --warmer=categories
+
+# List available warmers
+php artisan smart-cache:warm --list