- Customer has implemented a Redis cache with their DXA webapp. - The Redis cache works as expected for custom caches, but when trying to moving the DXA cache regions to the Redis distributed cache (ie PageModel, EntityModel, Navigation_Static, Navigation_Dynamic, BinaryPublishDate, LinkResolving, PublicationMapping, BrokerQuery), below timeout errors are seen. `` 2024-09-17 9:47:50 ERROR [Sdl.Web.Delivery.Caching.CacheHandlers.RedisCache.RedisCacheHandler`1.Get] Redis Timeout 'REDIS_CACHE:6379' due to System.TimeoutException: Timeout performing EVAL, inst: 56, queue: 31, qu: 0, qs: 31, qc: 0, wr: 0, wq: 0, in: 131072, ar: 0, clientName: HOSTNAME, IOCP: (Busy=1,Free=999,Min=8,Max=1000), WORKER: (Busy=106,Free=32661,Min=8,Max=32767), Local-CPU: unavailable (Please take a look at this article for some common client-side issues that can cause timeouts: https://github.com/StackExchange/StackExchange.Redis/tree/master/Docs/Timeouts.md) at StackExchange.Redis.ConnectionMultiplexer.ExecuteSyncImpl[T](Message message, ResultProcessor`1 processor, ServerEndPoint server) at StackExchange.Redis.RedisBase.ExecuteSync[T](Message message, ResultProcessor`1 processor, ServerEndPoint server) at StackExchange.Redis.RedisDatabase.ScriptEvaluate(String script, RedisKey[] keys, RedisValue[] values, CommandFlags flags) at Microsoft.Extensions.Caching.Redis.RedisExtensions.HashMemberGet(IDatabase cache, String key, String[] members) at Microsoft.Extensions.Caching.Redis.RedisCache.GetAndRefresh(String key, Boolean getData) at Sdl.Web.Delivery.Caching.CacheHandlers.RedisCache.RedisCacheHandler`1.<>c__DisplayClass13_0.<TryGet>b__0() at Sdl.Web.Delivery.Caching.CacheHandlers.RedisCache.RedisCacheHandler`1.RetryBlock[TResult](Func`1 block, TResult& returnedValue).- Developer notes that the DXA cache provider does not expose all Redis available configuration options such as syncTimeout. |
- Distributed caching through Redis is provided through the deprecated CIL library that DXA uses. Unfortunately the current implementation does not expose configuration options and would require CIL code changes to implement. - The DXA is a framework for which RWS provides options, and which customers can tailor as per their business requirements. The distributed Redis caching is an option to configure and implement. DXA support is offered via a PS engagement subscription, but DXA itself is not a core product offering which RWS Support can assist with. DXA custom cache implementation suggestion (A) 1. As the CIL library is deprecated, new functionality will not be provided for it. However a custom implementation of caching in DXA can be implemented; a developer would typically create an implementation of the ICacheProvider interface provided by the DXA framework. This interface requires an implementation of three basic methods. DXA provides two default implementations of this HERE. DXA can then be configured to use a custom implementation by modifying the Unity.config file and changing the section so that MyCustomCacheProvider can be the implementation of the ICacheProvider interface. <type type="ICacheProvider" mapTo="MyCustomCacheProvider"> <lifetime type="singleton" /> </type>2. The Redis libraries used by the CIL are - Microsoft.Extensions.Caching.Redis - Stackexchange.Redis.StrongName 3. Microsoft.Extensions.Caching.Redis library notes: - The .NET library used for caching with Redis is quite straight forward to use. Developer should be using the Microsoft.Extensions.Caching.Redis package from nuget.org. Once the developer has referenced that in the project a Redis cache can be constructed as below. var redisCache = new Microsoft.Extensions.Caching.Redis.RedisCache(new Microsoft.Extensions.Caching.Redis. RedisCacheOptions { Configuration = "<hostname>:<port>,user=####,password=#####=,ssl=True,abortConnect=False });See configuration options HERE. - Configuration in the above is basically the connection string built from various configuration options. Most likely, developer will want to read this from appSettings in the Web.config such as the below. WebConfigurationManager.AppSettings["redis_connection_string"];- Then items can be put in the cache like this var policy = new Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions(); // setup cache options policy here (sliding or absolute cache expiration time, etc). // Example, make it an absolute expiration of 300 seconds from now. Pull this value from web.config if you like: policy.AbsoluteExpiration = new DateTimeOffset(DateTime.UtcNow.AddSeconds(300)); // add 'data' to cache using 'cacheKey' with the policy defined above. data is a byte array redisCache.Set(cacheKey, data, policy);- An example of getting items out below. byte[] data = redisCache.Get(cacheKey);cacheKey would be a some unique key (string) to identify the value you wish to cache. Generally the developer would construct this key to be of the form cacheRegionName:key (or whatever desired; it isn't necessary to include a cacheRegionName, its just a string). - Take a copy of this entire class, name it something like RedisCachedGraphQLModelServiceProvider. In the constructor the developer could instantiate this 'redisCache' instance and policy as shown above. Then in the GetPageModelData method (and other methods needed), build a cacheKey by using the pageId and publication id/namespace from the localization object passed in. Then try and get from redisCache, if not null, deserialize to json else load json directly from graphQL client (as done by the current implementation) and set on redisCache. Then call the LoadModel function with the json. - Notice that when getting/setting values in Redis, byte arrays are being used. So the developer would take the json string and convert to a byte array (and from a byte array to string). Using GZipStream is recommended to reduce network traffic to compress the json as it may be quite large. 4. Miscellaneous notes:
DXA custom cache implementation suggestion (B) An alternative and possibly easier approach would be to implement a new IModelServiceProvider (and set it in your Unity.Config). This would give the developer access to the raw JSON from the content service and this could be sent to a Redis instance. The current implementation can be reviewed HERE and the developer could copy this entire class, name it RedisGraphQLModelServiceProvider, add Redis caching inside and just cache page models. - The implementation suggestions in this article are not Tridion Sites product version or DXA version specific, ie it can be implemented for previous and later DXA and Tridion Site product versions. - The implementation feedback in this article are outside the scope of RWS core product support; and the Support team and R&D developers are not able to assist with any questions For assistance, customer should engage RWS Professional Services. |