Advanced Cache link
What it is link
The funx performance package includes a small family of pluggable cache
backends and helpers that back memoize() and cacheAside(). You can choose
the eviction policy, add a weight limit, protect against cache stampedes, and
keep a set of keys warm in the background.
When to use it link
- You need more control than the default in-memory LRU cache.
- Cache entries have very different memory costs and you want a weight budget.
- Hot keys are requested concurrently and you want only one loader to run.
- A subset of keys should be refreshed proactively on a timer.
Components link
| Component | Purpose |
|---|
Cache<K, V> | Minimal cache interface (get, put, remove, clear). |
AdvancedCache<K, V> | Extends Cache with putEntry, getEntry, and keys. |
CacheEntry<V> | Value plus metadata: expiresAt, weight, accessCount. |
LruCache<K, V> | Evicts least-recently-used entries. |
LfuCache<K, V> | Evicts least-frequently-used entries. |
FifoCache<K, V> | Evicts oldest inserted entries. |
WeightedCache<K, V> | Wraps another cache and evicts by total weight. |
StampedeProtection<K, V> | Coalesces concurrent loads for the same key. |
CacheWarmer<K, V> | Periodically refreshes a set of keys. |
API reference link
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
| // api-reference
abstract class Cache<K, V> {
V? get(K key);
void put(K key, V value);
void remove(K key);
void clear();
}
abstract class AdvancedCache<K, V> implements Cache<K, V> {
void putEntry(K key, CacheEntry<V> entry);
CacheEntry<V>? getEntry(K key);
Iterable<K> get keys;
}
class CacheEntry<V> {
CacheEntry(
this.value, {
this.expiresAt,
this.weight = 1,
this.accessCount = 1,
});
final V value;
final DateTime? expiresAt;
final int weight;
int accessCount;
bool get isExpired;
}
class LruCache<K, V> implements AdvancedCache<K, V> {
LruCache({required int maxSize});
}
class LfuCache<K, V> implements AdvancedCache<K, V> {
LfuCache({required int maxSize});
}
class FifoCache<K, V> implements AdvancedCache<K, V> {
FifoCache({required int maxSize});
}
class WeightedCache<K, V> implements AdvancedCache<K, V> {
WeightedCache(
AdvancedCache<K, V> inner, {
required int maxWeight,
required int Function(V value) weigh,
});
int get totalWeight;
}
class StampedeProtection<K, V> {
Future<V> load(K key, Future<V> Function() loader);
}
class CacheWarmer<K, V> {
CacheWarmer({
required AdvancedCache<K, V> cache,
required Future<V> Function(K key) loader,
required Duration interval,
required Iterable<K> keys,
});
void start();
void stop();
bool get isRunning;
}
|
Examples link
Choose an eviction policy link
1
2
3
| final cache = LfuCache<String, int>(maxSize: 100);
cache.put('a', 1);
print(cache.get('a'));
|
Weighted cache link
1
2
3
4
5
6
7
| final cache = WeightedCache<String, String>(
LruCache<String, String>(maxSize: 1000),
maxWeight: 1024 * 1024, // 1 MB
weigh: (value) => value.length,
);
cache.put('a', 'hello');
print(cache.totalWeight);
|
Stampede protection link
1
2
3
4
5
6
| final protection = StampedeProtection<String, int>();
final value = await protection.load(
'key',
() async => 42,
);
print(value);
|
Warm a set of keys link
1
2
3
4
5
6
7
8
9
| final cache = LruCache<String, Product>(maxSize: 10);
final warmer = CacheWarmer<String, Product>(
cache: cache,
loader: (id) async => Product(),
interval: Duration(minutes: 5),
keys: ['featured', 'new-arrivals'],
)..start();
print(warmer.isRunning);
warmer.stop();
|
Best practices link
- Pick the eviction policy that matches your access pattern.
- Always provide both
maxWeight and weigh when using WeightedCache. - Keep
CacheWarmer intervals reasonable to avoid hammering the data source. - Stop warmers and clear caches when disposing long-lived objects.
Common pitfalls link
- Weight under-counting:
WeightedCache only tracks entries inserted
through itself; writes made directly to the inner cache are not weighed. - Heavy entries: A single entry heavier than
maxWeight is silently
rejected. - Timer leaks:
CacheWarmer holds a periodic timer; call stop() to avoid
leaks.