Skip to content

Commit 50be460

Browse files
committed
add doc for selective cache invalidation
1 parent dc229f5 commit 50be460

File tree

1 file changed

+292
-0
lines changed

1 file changed

+292
-0
lines changed
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
<!--
2+
{
3+
"title": "Selective Cache Invalidation",
4+
"id": "selective-cache-invalidation",
5+
"since": "7.1",
6+
"related": [
7+
"function-cachedwithinid",
8+
"function-cachedwithinflush",
9+
"function-cacheremove",
10+
"function-cachegetdefaultcachename",
11+
"tag-function",
12+
"tag-query"
13+
],
14+
"categories": [
15+
"cache",
16+
"performance"
17+
],
18+
"menuTitle": "Selective Cache Invalidation",
19+
"description": "How to flush specific cached queries, functions, and HTTP results in Lucee without clearing entire caches.",
20+
"keywords": [
21+
"Cache",
22+
"cachedWithin",
23+
"cachedWithinId",
24+
"cachedWithinFlush",
25+
"Query cache",
26+
"Function cache",
27+
"HTTP cache",
28+
"Cache invalidation",
29+
"Performance"
30+
]
31+
}
32+
-->
33+
34+
# Selective Cache Invalidation
35+
36+
Lucee 7.1 introduces the ability to selectively invalidate specific cached entries for queries, functions, and HTTP results. This allows you to cache results for extended periods while maintaining data freshness by flushing only the affected cache entries when underlying data changes.
37+
38+
## Overview
39+
40+
Traditional caching in Lucee requires either waiting for cache expiration or clearing entire caches, which can be inefficient. Selective cache invalidation solves this by allowing you to:
41+
42+
- Cache queries, functions, and HTTP results for long periods (hours or days)
43+
- Invalidate only specific cache entries when data changes
44+
- Maintain optimal performance without serving stale data
45+
- Target cache entries by their specific arguments or parameters
46+
47+
## Prerequisites
48+
49+
For selective cache invalidation to work, you must have **default caches configured** for the cache types you want to use (`query`, `function`, or `http`).
50+
51+
### Configuring Default Caches
52+
53+
Define default caches in `Application.cfc`:
54+
55+
```lucee
56+
// Define a cache connection
57+
this.cache.connections["myCache"] = {
58+
class: 'org.lucee.extension.cache.eh.EHCache',
59+
storage: false,
60+
custom: {
61+
"timeToIdleSeconds": "86400",
62+
"timeToLiveSeconds": "86400"
63+
}
64+
};
65+
66+
// Set as default for query, function, and http operations
67+
this.cache.query = "myCache";
68+
this.cache.function = "myCache";
69+
this.cache.http = "myCache";
70+
```
71+
72+
Without default caches configured, `cachedWithin` will not function, and selective invalidation will not work.
73+
74+
## Basic Usage
75+
76+
### Cached Queries
77+
78+
Cache a query result and flush it when the underlying data changes:
79+
80+
```lucee
81+
// Cache the query for 1 day
82+
query datasource="mydsn" name="qUserPlan" cachedwithin="#createTimeSpan(0,1,0,0)#" {
83+
echo("SELECT plan, features FROM users where id='#id#'");
84+
}
85+
86+
// Later, when user updates their plan, flush only that specific query cache
87+
cachedWithinFlush(qUserPlan);
88+
```
89+
90+
### Cached Functions
91+
92+
Cache function results based on arguments, and flush specific argument combinations:
93+
94+
```lucee
95+
function getUserPlan(userid) cachedwithin="#createTimeSpan(0,1,0,0)#" {
96+
return queryExecute(
97+
"SELECT plan, features FROM users WHERE id = :id",
98+
{id: userid}
99+
);
100+
}
101+
102+
// Cache is created per unique argument combination
103+
plan1 = getUserPlan(123); // Creates cache entry for userid=123
104+
plan2 = getUserPlan(456); // Creates separate cache entry for userid=456
105+
106+
// Flush cache only for userid=123
107+
cachedWithinFlush(getUserPlan, [123]);
108+
// or cachedWithinFlush(getUserPlan, {userid:123});
109+
110+
// userid=456 cache remains intact
111+
```
112+
113+
### Cached HTTP Requests
114+
115+
Cache HTTP responses and invalidate them when needed:
116+
117+
```lucee
118+
cfhttp(
119+
url="https://api.example.com/data",
120+
result="httpResult",
121+
cachedwithin="#createTimeSpan(0,0,10,0)#"
122+
);
123+
124+
// Flush the HTTP cache when you know the API data has changed
125+
cachedWithinFlush(httpResult);
126+
```
127+
128+
## Available Functions
129+
130+
### cachedWithinId()
131+
132+
Returns the cache identifier (cache key) for a cached object. This ID can be used with `cacheRemove()` for manual cache management.
133+
134+
**Syntax:**
135+
```lucee
136+
cacheId = cachedWithinId(cacheObject [, arguments])
137+
```
138+
139+
**Arguments:**
140+
- `cacheObject` (required) - The cached query, function, or HTTP result
141+
- `arguments` (optional) - For functions: array or struct of arguments that identify the specific cache entry
142+
143+
**Returns:** String - The cache identifier
144+
145+
**Examples:**
146+
147+
```lucee
148+
// Get cache ID for a query
149+
query datasource="mydsn" name="q" cachedwithin="#createTimeSpan(0,1,0,0)#" {
150+
writeOutput("SELECT * FROM users");
151+
}
152+
cacheId = cachedWithinId(q);
153+
dump(cacheId); // Outputs: unique cache identifier
154+
155+
// Get cache ID for a function with specific arguments
156+
function getUser(id) cachedwithin="#createTimeSpan(0,1,0,0)#" {
157+
return queryExecute("SELECT * FROM users WHERE id = :id", {id: id});
158+
}
159+
160+
// Positional arguments (array)
161+
cacheId = cachedWithinId(getUser, [123]);
162+
163+
// Named arguments (struct)
164+
cacheId = cachedWithinId(getUser, {id: 123});
165+
166+
// Get cache ID for HTTP result
167+
cfhttp(url="https://example.com", result="res", cachedwithin="#createTimeSpan(0,1,0,0)#");
168+
cacheId = cachedWithinId(res);
169+
```
170+
171+
### cachedWithinFlush()
172+
173+
Flushes a specific cached entry from the cache. This is the primary function for selective cache invalidation.
174+
175+
**Syntax:**
176+
```lucee
177+
success = cachedWithinFlush(cacheObject [, arguments])
178+
```
179+
180+
**Arguments:**
181+
- `cacheObject` (required) - The cached query, function, or HTTP result to flush
182+
- `arguments` (optional) - For functions: array or struct of arguments that identify the specific cache entry
183+
184+
**Returns:** Boolean - `true` if the cache entry was successfully removed, `false` otherwise
185+
186+
**Examples:**
187+
188+
```lucee
189+
// Flush a cached query
190+
query datasource="mydsn" name="qProducts" cachedwithin="#createTimeSpan(0,1,0,0)#" {
191+
writeOutput("SELECT * FROM products WHERE active = 1");
192+
}
193+
success = cachedWithinFlush(qProducts);
194+
dump(success); // true if flushed successfully
195+
196+
// Flush a function cache with specific arguments
197+
function calculatePrice(productId, quantity) cachedwithin="#createTimeSpan(0,1,0,0)#" {
198+
return queryExecute(
199+
"SELECT price FROM products WHERE id = :id",
200+
{id: productId}
201+
).price * quantity;
202+
}
203+
204+
// Flush cache for specific product
205+
cachedWithinFlush(calculatePrice, [101, 5]);
206+
207+
// Both argument formats work
208+
cachedWithinFlush(calculatePrice, {productId: 101, quantity: 5});
209+
210+
// Flush HTTP cache
211+
cfhttp(url="https://api.example.com/prices", result="priceData", cachedwithin="#createTimeSpan(0,0,30,0)#");
212+
cachedWithinFlush(priceData);
213+
```
214+
215+
## Advanced Usage
216+
217+
### Manual Cache Removal with cacheRemove()
218+
219+
For more control, combine `cachedWithinId()` with `cacheRemove()`:
220+
221+
```lucee
222+
// Get the cache ID
223+
query datasource="mydsn" name="q" cachedwithin="#createTimeSpan(0,1,0,0)#" {
224+
writeOutput("SELECT * FROM products");
225+
}
226+
cacheId = cachedWithinId(q);
227+
228+
// Manually remove from cache using the ID
229+
cacheRemove(
230+
ids: cacheId,
231+
cacheName: cacheGetDefaultCacheName("query")
232+
);
233+
```
234+
235+
This approach gives you the flexibility to store cache IDs and flush them later, or to use custom cache management logic.
236+
237+
### Working with Query Result Metadata
238+
239+
When using the `result` attribute in queries, the cache ID is automatically included in the result metadata:
240+
241+
```lucee
242+
query datasource="mydsn" name="q" cachedwithin="#createTimeSpan(0,1,0,0)#" result="r" {
243+
writeOutput("SELECT * FROM users");
244+
}
245+
246+
// Access cache ID from result metadata
247+
dump(r.cachedWithinId);
248+
249+
// Use it to remove from cache
250+
cacheRemove(
251+
ids: r.cachedWithinId,
252+
cacheName: cacheGetDefaultCacheName("query")
253+
);
254+
```
255+
256+
### Function Arguments: Array vs Struct
257+
258+
For cached functions, you can specify arguments as either an array (positional) or struct (named):
259+
260+
```lucee
261+
function searchProducts(category, minPrice, maxPrice) cachedwithin="#createTimeSpan(0,1,0,0)#" {
262+
return queryExecute(
263+
"SELECT * FROM products WHERE category = :cat AND price BETWEEN :min AND :max",
264+
{cat: category, min: minPrice, max: maxPrice}
265+
);
266+
}
267+
268+
// Positional arguments (array) - order matters
269+
cachedWithinFlush(searchProducts, ["Electronics", 100, 500]);
270+
271+
// Named arguments (struct) - order doesn't matter
272+
cachedWithinFlush(searchProducts, {
273+
category: "Electronics",
274+
minPrice: 100,
275+
maxPrice: 500
276+
});
277+
278+
// Both target the same cache entry
279+
```
280+
281+
282+
## Limitations and Considerations
283+
284+
- **Default Caches Required**: Selective invalidation only works when default caches are configured for the relevant cache type.
285+
286+
- **Function Arguments Must Match**: For functions, the arguments passed to `cachedWithinFlush()` must exactly match the arguments used when the cache entry was created.
287+
288+
- **No Wildcard Flushing**: You cannot flush all cache entries for a function at once; you must target specific argument combinations.
289+
290+
- **Cache Type Specificity**: Each cache type (`query`, `function`, `http`) uses its own default cache. Ensure the correct cache is configured.
291+
292+
- **Cross-Request Consistency**: Cache IDs are consistent across requests, so you can store them and use them later to flush caches.

0 commit comments

Comments
 (0)