Cache System

High-performance caching with multiple drivers and smart invalidation strategies.

MemoryFileRedisTagsStatistics

Quick Start

Basic Usage

dart
// Basic caching
await Khadem.cache.put('key', 'value', Duration(hours: 1));
final value = await Khadem.cache.get('key');

// Remember pattern
final data = await Khadem.cache.remember('user:123', Duration(hours: 1), () async {
  return await User().find(123);
});

💡 Note: Cache is automatically configured via service provider

⚡ Tip: Use descriptive keys like 'user:123' or 'posts:recent'

Cache Drivers

Memory

Fastest, process-specific

cache

File

Persistent, no external deps

cache.driver('file')

Redis

Distributed, high performance

cache.driver('redis')

Driver Selection Guide

Use CaseRecommendedWhy
DevelopmentMemoryFast, no setup
Production (single server)FilePersistent, simple
Distributed systemRedisShared across instances

Core Operations

dart
// Store with TTL
await Khadem.cache.put('temp_data', data, Duration(minutes: 30));

// Retrieve
final data = await Khadem.cache.get('my_key');

// Check existence
if (await Khadem.cache.has('my_key')) {
  print('Key exists');
}

// Remove
await Khadem.cache.forget('my_key');

// Clear all
await Khadem.cache.clear();

// Store permanently
await Khadem.cache.forever('app_config', config);
dart
// Get with default value
final settings = await Khadem.cache.get('settings') ?? defaultSettings;

// Remember pattern with complex computation
final result = await Khadem.cache.remember('complex_calc', Duration(hours: 2), () async {
  // Expensive computation here
  return await performComplexCalculation();
});

// Check multiple keys
final userExists = await Khadem.cache.has('user:123');
final profileExists = await Khadem.cache.has('user:123:profile');

// Batch operations (if supported by driver)
await Khadem.cache.put('key1', 'value1', Duration(hours: 1));
await Khadem.cache.put('key2', 'value2', Duration(hours: 1));

⚠️ Important Notes

  • • Cache is not a replacement for proper database design
  • • Always handle cache misses gracefully
  • • Use appropriate TTL values based on data freshness needs
  • • Monitor cache hit rates and adjust strategies as needed

Cache Tags

dart
// Tag items for group operations
await Khadem.cache.tag('user:123', ['users', 'active']);
await Khadem.cache.tag('user:456', ['users', 'active']);

// Clear by tag
await Khadem.cache.forgetByTag('users'); // Clears both users

// Multiple tags per key
await Khadem.cache.tag('post:789', ['posts', 'published', 'featured']);

// Clear specific tag group
await Khadem.cache.forgetByTag('posts'); // Clears all posts

When to Use Tags

  • • Clear related cache entries together
  • • Invalidate user-specific data on logout
  • • Clear category/product caches on updates
  • • Group cache by feature or module

Database Query Caching

dart
// Cache database queries
final posts = await Khadem.cache.remember('popular_posts', Duration(hours: 1), () async {
  return await Post().query()
    .with(['user', 'tags'])
    .where('published', true)
    .orderBy('views', 'desc')
    .limit(10)
    .get();
});

// Cache with automatic invalidation
Event.listen('post.created', (post) async {
  await Khadem.cache.forget('popular_posts');
});

Best Practices

  • • Cache results of complex JOINs and aggregations
  • • Use appropriate TTL based on data update frequency
  • • Include query parameters in cache key
  • • Invalidate cache when underlying data changes

Model Caching

dart
class User extends KhademModel<User> {
  static Future<User?> findCached(int id) async {
    return await Khadem.cache.remember('user:$id', Duration(hours: 1), () async {
      return await User().find(id);
    });
  }

  @override
  Future<bool> save() async {
    final result = await super.save();
    if (result) {
      await Khadem.cache.forget('user:${id}'); // Invalidate cache
    }
    return result;
  }
}

// Usage
final user = await User.findCached(123);
final users = await Khadem.cache.remember('users:active', Duration(hours: 2), () async {
  return await User().query().where('active', true).get();
});

Model Cache Patterns

  • • Cache frequently accessed model instances
  • • Cache relationships and computed properties
  • • Invalidate cache in model save/update methods
  • • Use tags for related model data

Response Caching

dart
class ApiController {
  static Future getPosts(Request req, Response res) async {
    final posts = await Khadem.cache.remember('api:posts', Duration(minutes: 5), () async {
      return await Post().query().with(['user']).paginate(1, 20);
    });

    res.header('Cache-Control', 'public, max-age=300');
    res.sendJson({'data': posts});
  }
}

HTTP Headers

Cache-Control: public

Cacheable by browsers/CDNs

ETag

Conditional requests (304)

Cache Statistics

dart
// Get cache statistics
final stats = Khadem.cache.stats;
print('Hit rate: ${stats.hitRate}%');
print('Total operations: ${stats.totalOperations}');
print('Hits: ${stats.hits}, Misses: ${stats.misses}');

// Monitor specific operations
await Khadem.cache.put('monitored_key', data, Duration(hours: 1));
// Stats are automatically updated

// Check cache health
if (stats.hitRate < 50.0) {
  print('Warning: Low cache hit rate detected');
}

// Get detailed metrics
print('Read operations: ${stats.readOperations}');
print('Write operations: ${stats.writeOperations}');
print('Cache sets: ${stats.sets}');
print('Cache deletions: ${stats.deletions}');

Key Metrics

  • • Hit rate percentage
  • • Total operations count
  • • Memory usage
  • • Cache key count
  • • Average response time

Best Practices

✅ Do's

  • • Use descriptive, namespaced cache keys
  • • Set appropriate TTL values
  • • Handle cache misses gracefully
  • • Monitor cache performance regularly
  • • Use tags for logical grouping
  • • Invalidate cache when data changes
  • • Cache expensive operations only

❌ Don'ts

  • • Don't cache sensitive user data
  • • Don't rely on cache for critical business logic
  • • Don't use cache as primary data store
  • • Don't forget to handle cache failures
  • • Don't use overly broad cache invalidation
  • • Don't cache frequently changing data
  • • Don't ignore memory limits

Common Patterns

Cache-Aside Pattern

Check cache first, then database

dart
// Cache-aside pattern
Future<User?> getUser(int id) async {
  final cached = await Khadem.cache.get('user:$id');
  if (cached != null) return User().fromJson(cached);

  final user = await User().find(id);
  if (user != null) {
    await Khadem.cache.put('user:$id', user.toJson(), Duration(hours: 1));
  }
  return user;
}

Write-Through Pattern

Update cache and database together

dart
// Write-through pattern
Future createPost(Map<String, dynamic> data) async {
  final post = await Post().create(data);
  await Khadem.cache.put('post:${post.id}', post.toJson(), Duration(hours: 2));
  return post;
}

Cache Warming

Pre-populate cache for better performance

dart
// Cache warming
class CacheWarmer {
  static Future warmPopularContent() async {
    await Khadem.cache.remember('posts:popular', Duration(hours: 1), () async {
      return await Post().query()
        .orderBy('views', 'desc')
        .limit(20)
        .get();
    });
  }
}

// Background refresh
Timer.periodic(Duration(minutes: 10), (_) async {
  await CacheWarmer.warmPopularContent();
});

On this page