|
| 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