Skip to content

Commit 50e7b24

Browse files
committed
Enhance Redis caching documentation: add sections on clearing the cache and updating the data schema
1 parent d92ce62 commit 50e7b24

File tree

1 file changed

+47
-23
lines changed

1 file changed

+47
-23
lines changed

conceptual/Caching/Backends/caching-redis.md

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ title: "Using Redis Cache"
44
product: "postsharp"
55
categories: "PostSharp;AOP;Metaprogramming"
66
summary: "The document provides instructions on how to use Redis Cache with PostSharp, including server configuration, setting up PostSharp for Redis caching, adding local in-memory cache, and using dependencies with the Redis caching backend."
7-
---
8-
# Using Redis Cache
7+
8+
# Using Redis cache
9+
10+
Redis is a high-performance, in-memory key-value store widely used for caching scenarios to improve application speed and scalability. This page explains how to integrate Redis as a caching backend in your applications. You’ll learn how to configure your Redis server and your application for optimal performance with PostSharp’s caching features.
911

1012
Our implementation uses the [StackExchange.Redis library](https://stackexchange.github.io/StackExchange.Redis/) internally. It is compatible with on-premises Redis Cache instances as well as with the [Azure Redis Cache](https://azure.microsoft.com/en-us/services/cache/) cloud service. We tested our adapter with single-node, master/replica, and sharded topologies.
1113

@@ -32,13 +34,13 @@ To prepare your Redis server for use with PostSharp caching:
3234
- `E` = Keyevent notifications,
3335
- `x` = Expired events,
3436
- `e` = Evicted events.
35-
37+
3638
See <https://redis.io/docs/latest/develop/interact/notifications/> for details.
3739

3840

3941
### Example
4042

41-
Here is en excert of your `redis.conf`configuration file:
43+
Here is an excerpt of your `redis.conf` configuration file:
4244

4345
```text
4446
maxmemory-policy volatile-lru
@@ -76,7 +78,7 @@ string connectionConfiguration = "localhost";
7678
ConnectionMultiplexer connection = ConnectionMultiplexer.Connect( connectionConfiguration );
7779
7880
// Set Redis as the default backend.
79-
RedisCachingBackendConfiguration redisCachingConfiguration =
81+
RedisCachingBackendConfiguration redisCachingConfiguration =
8082
new RedisCachingBackendConfiguration() { KeyPrefix = "MyApp-1.0.1" };
8183
CachingServices.DefaultBackend = RedisCachingBackend.Create( connection, redisCachingConfiguration );
8284
```
@@ -111,7 +113,7 @@ In-memory cache must be enabled globally for the whole back-end. It is not possi
111113
## Using dependencies with the Redis caching backend
112114

113115
Support for dependencies is disabled by default with the Redis caching backend because it has an important performance and deployment impact:
114-
- Performance impact: the dependency graphs need to be stored and maintained in Redis.
116+
- Performance impact: the dependency graphs need to be stored and maintained in Redis.
115117
- Deployment impact: the <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCacheDependencyGarbageCollector> component, which cleans up dependencies when cache items expire or are evicted, needs to run continuously.
116118

117119
To use dependencies with the Redis caching backend:
@@ -125,7 +127,7 @@ To use dependencies with the Redis caching backend:
125127
- It subscribes to `__keyevent@0__:expired` and `__keyevent@0__:evicted` notifications and cleans up the dependency graphs in real time. This process generally works well, but it is temporarily disabled when the system is overloaded.
126128
- It periodically scans the entire database and removes any unreachable data (garbage).
127129

128-
If you choose to enable dependencies with Redis, you need to ensure that at least one instance of the cache GC process is running. It is acceptable to have several instances running, but since all instances will compete to process the same messages, it’s better to a have a single instance running.
130+
If you choose to enable dependencies with Redis, you need to ensure that at least one instance of the cache GC process is running. It is acceptable to have several instances running, but since all instances will compete to process the same messages, it’s better to have a single instance running.
129131

130132
The dependencies feature of <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackend> is designed so that the cache is **observationally consistent** from the client’s point of view during normal operation. Read operations are fast and transactionless. Write operations are optimized for performance and read consistency, but may leave garbage behind in case of a race condition (later cleaned by the GC process).
131133

@@ -135,9 +137,15 @@ Garbage may therefore be due to three factors:
135137
- The <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCacheDependencyGarbageCollector> component was temporarily disabled because of high system load.
136138
- There was a race condition in setting a cache value (the version that loses becomes garbage).
137139

140+
### Limitations
141+
142+
Direct forward dependencies (i.e. the dependencies that a cache item directly depends on, at the first level) must be kept reasonably low because they are all loaded into memory as an array at the same time. The system can probably handle hundreds or thousands of forward dependencies, but not millions.
143+
144+
The number of items depending on a single dependency is only limited by your Redis instance. They are processed in a cursor-based manner and are never all simultaneously loaded in memory.
145+
138146
## Resilience
139147

140-
As with any part of a distributed system, <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackend> is a complex component that must be tuned and monitored with care.
148+
As with any part of a distributed system, <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackend> is a complex component that must be tuned and monitored with care.
141149

142150
### Exception handling
143151

@@ -154,12 +162,11 @@ With any level of load, it is recommended to enable cache key compression by ass
154162

155163
If you expect high load, it is recommended to tune the following <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration> properties:
156164

157-
- <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.BackgroundTasksMaxConcurrency> is critical to the <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCacheDependencyGarbageCollector> component. It should be high enough to utilize the maximum resources of your deployment, but small enough not to allow for a large operation backlog to form.
158-
- <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.BackgroundTasksOverloadedThreshold> is used only by the <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCacheDependencyGarbageCollector>. It will cause the component to stop processing eviction and expiration notifications in case the operation backlog is too large. In this case, GC would rely on periodic database scanning.
165+
- <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.BackgroundTasksMaxConcurrency> is critical for the <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCacheDependencyGarbageCollector> component. It should be high enough to utilize the maximum resources of your deployment, but small enough not to allow for a large operation backlog to form.
166+
- <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.BackgroundTasksOverloadedThreshold> is used only by the <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCacheDependencyGarbageCollector>. It will cause the component to stop processing eviction and expiration notifications if the operation backlog is too large. In this case, GC would rely on periodic database scanning.
159167

160168
The following <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCacheDependencyGarbageCollectorOptions> properties must also be properly tuned:
161-
162-
- The <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCacheDependencyGarbageCollectorOptions.CacheCleanupDelay> property is the delay between the initialization of the component and the first cleanup, then between two subsequent cache cleanups, and defaults to 4 hours. Cleaning up the database too frequently is useless performance overhead, but doing it too late degrades performance even more. If the database contains too much garbage, Redis will start evicting _useful_ data, affecting your application performance. However it will never evict garbage. That's why you should increase the cache cleanup frequency if you see high memory usage or high levels of evictions.
169+
- The <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCacheDependencyGarbageCollectorOptions.CacheCleanupDelay> property defines the delay between the component's initialization and the first cleanup, and then between subsequent cache cleanups; it defaults to 4 hours. Cleaning up the database too frequently is an unnecessary performance overhead, but doing it too late degrades performance even more. If the database contains too much garbage, Redis will start evicting useful data, affecting your application's performance. However, it will never evict garbage. That is why you should increase the cache cleanup frequency if you see high memory usage or high levels of evictions.
163170
- The <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCacheDependencyGarbageCollectorOptions.CacheCleanupOptions> property affects the cleanup process. It is important to keep the cleanup slow enough to avoid impacting your application's performance, but fast enough to finish before Redis runs out of memory. The <xref:PostSharp.Patterns.Caching.Implementation.CacheCleanupOptions.WaitDelay> is an artificial delay between processing each cache key, defaulting to 100 ms.
164171

165172
Note that you can run a manual cleanup by calling the <xref:PostSharp.Patterns.Caching.Implementation.CachingBackend.CleanUpAsync*?CachingBackend.CleanUpAsync> method. Do not run this method with the default options on a production database; these options are optimized for cleanup performance and may overload your server.
@@ -196,7 +203,7 @@ In this description, elements between angle brackets are placeholders and mean t
196203
- `<item-version>` is a randomly generated item version.
197204
- `<dependency-key>` is either a dependency key or a cache item key, when the cache item is itself a dependency (recursive dependencies), where `{`, `}` and `:` have been escaped.
198205

199-
Note the use of `{..}` brackets around `<item-key>`, marking `<item-key>` is a _hashtag_. It guarantees that all Redis keys related to the same logical cache item are stored on the same cluster slot and can be processed atomically in a transaction.
206+
Note the use of `{..}` brackets around `<item-key>`, marking `<item-key>` as a hashtag. It guarantees that all Redis keys related to the same logical cache item are stored on the same cluster slot and can be processed atomically in a transaction.
200207

201208
### Big O analysis
202209

@@ -226,23 +233,41 @@ Time complexity of operations is the following:
226233
- Invalidate: `O(Recursive_Items_Per_Dependency * Dependencies_Per_Item)`
227234
- Clean up: `O(Items * Dependencies_Per_Item + Dependencies * Items_Per_Dependency)`
228235

229-
Race conditions affecting performance can happen when several operations attempt to set the same key. In this case, Redis transactions are used to achieve consistency, and they might be repeated in case of race. Adding items that share the same dependencies do not cause races and do not affect performance.
236+
Race conditions affecting performance can occur when several operations attempt to set the same key. In this case, Redis transactions are used to achieve consistency, and they might be repeated in case of a race condition. Adding items that share the same dependencies does not cause race conditions and does not affect performance.
230237

231-
### Clearing the cache
238+
## Clearing the cache
232239

233240
To remove all cache keys, you can:
234241

235242
- Use the `FLUSHDB` Redis command to delete all keys in the selected database, even those that don’t match the prefix.
236243
- Use the `SCAN <prefix>:*` command to identify all cache keys, then use `UNLINK` (preferred) or `DEL` for each key.
237244
- Use the <xref:PostSharp.Patterns.Caching.Implementation.CachingBackend.ClearAsync*> method, which performs a `SCAN <prefix>:<schema-version>:*` then `UNLINK` for each key.
238245

246+
## Updating the data schema
247+
248+
When you update your application with a new data schema, it is important to think carefully about the deployment process.
249+
250+
There are two circumstances in which the data schema can change:
251+
252+
* When _your application_ has a new and compatible serialization schema for cached data, you must set the <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.KeyPrefix> property to a different value
253+
* When the <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackend> component is updated to a new schema, we will update the <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackend.SchemaVersion?text=RedisCachingBackend.SchemaVersion> constant.
254+
255+
The <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.KeyPrefix> and <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackend.SchemaVersion?text=RedisCachingBackend.SchemaVersion> properties are prepended to Redis keys. They ensure that the new version of your application can co-exist with the old version without conflicts.
256+
257+
However, after an update, the new <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCacheDependencyGarbageCollector> process will not clean up the data of the old version. You must do that manually after decommissioning the old version.
258+
259+
There are two ways to do it:
260+
261+
- Easily: by purging the whole database (`FLUSHDB`).
262+
- Selectively: by removing all keys that do not match the `<prefix>:<schema-version>:*` pattern. Starting from PostSharp 2025.1.8, you can use the <xref:PostSharp.Patterns.Caching.Implementation.CachingBackend.ClearAsync*> method, but remember to do that with the _old_ application version.
263+
239264
## Troubleshooting
240265

241266
### Observing the cache
242267

243268
- Make sure that `LoggingServices.DefaultBackend` has been properly configured so that the logging messages generated by <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackend> are routed to a service where you can monitor them.
244269

245-
- If necessary, increase the logging verbosity to `Info` (suitable for production in troubleshooting situations) or `Debug` (extremely verbose and never suitable for production). If you don't see any message in `Debug` verbosity it means that logging is not properly configured.
270+
- If necessary, increase the logging verbosity to `Info` (suitable for production in troubleshooting situations) or `Debug` (extremely verbose and never suitable for production). If you don't see any message in `Debug` verbosity, it means that logging is not properly configured.
246271

247272
```csharp
248273
LoggingServices.DefaultBackend.DefaultVerbosity.SetMinimalLevel( LogLevel.Info, LoggingRoles.Caching );
@@ -260,24 +285,23 @@ To remove all cache keys, you can:
260285

261286
**Remedy**: Check that you can connect to the Redis server, for instance using `connection.GetDatabase().Ping()`.
262287

263-
#### Problem: the collector component reports dozens of errors per minute.
288+
#### Problem: The collector component reports dozens of errors per minute.
264289

265-
**Cause**: the collector is overloaded because of excessive evictions and expirations.
290+
**Cause**: The collector is overloaded because of excessive evictions and expirations.
266291

267292
Remedies:
268293

269294
- If expirations are excessive, increase the cache item expiry delay.
270295
- If evictions are excessive, remove caching from methods with a high miss ratio.
271-
- If the problem is intermittent, tune the <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.BackgroundTasksMaxConcurrency> and <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.BackgroundTasksOverloadedThreshold>
296+
- If the problem is intermittent, tune the <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.BackgroundTasksMaxConcurrency> and <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.BackgroundTasksOverloadedThreshold>.
272297

273298
#### Problem: InvalidCacheItemException or InvalidCastException are logged after an application upgrade.
274299

275-
**Cause**: the serialization of new and old data classes is not compatible with each other, but the two versions of the application use the same <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.KeyPrefix> and <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.Database> properties.
276-
300+
**Cause**: The serialization of new and old data classes is not compatible with each other, but the two versions of the application use the same <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.KeyPrefix> and <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.Database> properties.
277301
**Remedies**:
278302

279-
- Use a different value of the <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.KeyPrefix> or <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.Database> property when you update cached classes.
280-
- Use a serializer that makes the data contract explicit, i.e. <xref:PostSharp.Patterns.Caching.Serializers.DataContractSerializer> or <xref:PostSharp.Patterns.Caching.Serializers.JsonCachingSerializer> instead of <xref:PostSharp.Patterns.Caching.Serializers.BinarySerializer> or <xref:PostSharp.Patterns.Caching.Serializers.PortableSerializer> -- and maintain serialization compatibility when you update the classes.
303+
- Use a different value for the <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.KeyPrefix> or <xref:PostSharp.Patterns.Caching.Backends.Redis.RedisCachingBackendConfiguration.Database> property when you update cached classes.
304+
- Use a serializer that makes the data contract explicit, i.e. <xref:PostSharp.Patterns.Caching.Serializers.DataContractSerializer> or <xref:PostSharp.Patterns.Caching.Serializers.JsonCachingSerializer> instead of <xref:PostSharp.Patterns.Caching.Serializers.BinarySerializer> or <xref:PostSharp.Patterns.Caching.Serializers.PortableSerializer> and maintain serialization compatibility when you update the classes.
281305

282306
## See also
283307

0 commit comments

Comments
 (0)