Skip to content

Commit dc7ef5e

Browse files
author
claudiamurialdo
committed
Replaced per-tenant RedisCache instances with a shared single ConnectionMultiplexer across all tenants, preventing key-not-found errors across replicas
1 parent 7fd90f6 commit dc7ef5e

2 files changed

Lines changed: 53 additions & 60 deletions

File tree

dotnet/src/dotnetcore/GxNetCoreStartup/SessionHelper.cs

Lines changed: 39 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
using System;
2-
using System.Collections.Concurrent;
31
using System.Linq;
42
using System.Threading;
53
using System.Threading.Tasks;
6-
using GeneXus.Services;
74
using Microsoft.AspNetCore.Http;
85
using Microsoft.Extensions.Caching.Distributed;
96
using Microsoft.Extensions.Caching.StackExchangeRedis;
7+
using StackExchange.Redis;
108

119
namespace GeneXus.Application
1210
{
@@ -15,90 +13,75 @@ public class TenantRedisCache : IDistributedCache
1513
private static readonly IGXLogger log = GXLoggerFactory.GetLogger<TenantRedisCache>();
1614

1715
private readonly IHttpContextAccessor _httpContextAccessor;
18-
private readonly IServiceProvider _serviceProvider;
19-
private readonly ConcurrentDictionary<string, RedisCache> _redisCaches = new();
16+
private readonly RedisCache _redis;
2017

21-
public TenantRedisCache(IHttpContextAccessor httpContextAccessor, IServiceProvider serviceProvider)
18+
public TenantRedisCache(IConnectionMultiplexer _redisMultiplexer, IHttpContextAccessor httpContextAccessor)
2219
{
2320
_httpContextAccessor = httpContextAccessor;
24-
_serviceProvider = serviceProvider;
25-
}
2621

27-
private IDistributedCache GetTenantCache()
28-
{
29-
string tenantId = _httpContextAccessor.HttpContext?.Items[TenantMiddleware.TENANT_ID]?.ToString() ?? "default";
30-
RedisCache cache;
31-
bool existed = _redisCaches.TryGetValue(tenantId, out cache);
32-
if (existed)
22+
var options = new RedisCacheOptions
3323
{
34-
log.LogDebug($"GetTenantCache: tenantId={tenantId}, cache reused");
35-
return cache;
36-
}
37-
else
38-
{
39-
log.LogDebug($"GetTenantCache: tenantId={tenantId}, cache created");
40-
cache = _redisCaches.GetOrAdd(tenantId, id =>
41-
{
42-
ISessionService sessionService = GXSessionServiceFactory.GetProvider();
43-
var options = new RedisCacheOptions
44-
{
45-
Configuration = sessionService.ConnectionString,
46-
InstanceName = $"{id}:"
47-
};
48-
return new RedisCache(options);
49-
});
50-
}
51-
return cache;
24+
ConnectionMultiplexerFactory = () => Task.FromResult(_redisMultiplexer),
25+
};
26+
_redis = new RedisCache(options);
27+
}
28+
private string GetTenantId()
29+
{
30+
return _httpContextAccessor.HttpContext?.Items[TenantMiddleware.TENANT_ID]?.ToString() ?? "default";
31+
}
32+
private string BuildKey(string key)
33+
{
34+
return $"{GetTenantId()}:{key}";
5235
}
5336
public byte[] Get(string key)
5437
{
55-
IDistributedCache cache = GetTenantCache();
56-
log.LogDebug($"CacheGet: key={key}");
57-
return cache.Get(key);
38+
string realKey = BuildKey(key);
39+
log.LogDebug($"CacheGet: key={realKey}");
40+
return _redis.Get(realKey);
5841
}
5942
public Task<byte[]> GetAsync(string key, CancellationToken token = default)
6043
{
61-
IDistributedCache cache = GetTenantCache();
62-
log.LogDebug($"CacheGetAsync: key={key}");
63-
return cache.GetAsync(key, token);
44+
string realKey = BuildKey(key);
45+
log.LogDebug($"CacheGetAsync: key={realKey}");
46+
return _redis.GetAsync(realKey, token);
6447
}
6548
public void Refresh(string key)
6649
{
67-
IDistributedCache cache = GetTenantCache();
68-
log.LogDebug($"CacheRefresh: key={key}");
69-
cache.Refresh(key);
50+
string realKey = BuildKey(key);
51+
log.LogDebug($"CacheRefresh: key={realKey}");
52+
_redis.Refresh(realKey);
7053
}
7154
public Task RefreshAsync(string key, CancellationToken token = default)
7255
{
73-
IDistributedCache cache = GetTenantCache();
74-
log.LogDebug($"CacheRefreshAsync: key={key}");
75-
return cache.RefreshAsync(key, token);
56+
string realKey = BuildKey(key);
57+
log.LogDebug($"CacheRefreshAsync: key={realKey}");
58+
return _redis.RefreshAsync(realKey, token);
7659
}
7760
public void Remove(string key)
7861
{
79-
IDistributedCache cache = GetTenantCache();
80-
log.LogDebug($"CacheRemove: key={key}");
81-
cache.Remove(key);
62+
string realKey = BuildKey(key);
63+
log.LogDebug($"CacheRemove: key={realKey}");
64+
_redis.Remove(realKey);
8265
}
8366
public Task RemoveAsync(string key, CancellationToken token = default)
8467
{
85-
IDistributedCache cache = GetTenantCache();
86-
log.LogDebug($"CacheRemoveAsync: key={key}");
87-
return cache.RemoveAsync(key, token);
68+
string realKey = BuildKey(key);
69+
log.LogDebug($"CacheRemoveAsync: key={realKey}");
70+
return _redis.RemoveAsync(realKey, token);
8871
}
8972
public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
9073
{
9174
string sliding = options?.SlidingExpiration?.ToString() ?? "none";
92-
IDistributedCache cache = GetTenantCache();
93-
log.LogDebug($"CacheSet: key={key}, slidingExpiration={sliding}");
94-
cache.Set(key, value, options);
75+
string realKey = BuildKey(key);
76+
log.LogDebug($"CacheSet: key={realKey}, slidingExpiration={sliding}");
77+
_redis.Set(realKey, value, options);
9578
}
9679
public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default)
9780
{
9881
string sliding = options?.SlidingExpiration?.ToString() ?? "none";
99-
IDistributedCache cache = GetTenantCache();
100-
log.LogDebug($"CacheSetAsync: key={key}, slidingExpiration={sliding}");
101-
return cache.SetAsync(key, value, options, token);
82+
string realKey = BuildKey(key);
83+
log.LogDebug($"CacheSetAsync: key={realKey}, slidingExpiration={sliding}");
84+
return _redis.SetAsync(realKey, value, options, token);
10285
}
10386
}
10487

dotnet/src/dotnetcore/GxNetCoreStartup/Startup.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -310,18 +310,28 @@ private void ConfigureSessionService(IServiceCollection services, ISessionServic
310310
{
311311
GXLogging.Info(log, $"Using multi-tenant Redis for Distributed session, ConnectionString:{sessionService.ConnectionString}, InstanceName: {sessionService.InstanceName}");
312312

313+
IConnectionMultiplexer redis = ConnectionMultiplexer.Connect(sessionService.ConnectionString);
314+
services.AddSingleton<IConnectionMultiplexer>(redis);
315+
313316
services.AddSingleton<IDistributedCache, TenantRedisCache>();
314-
services.AddDataProtection().PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(sessionService.ConnectionString), DATA_PROTECTION_KEYS).SetApplicationName("default");
317+
318+
string appDiscriminator = string.IsNullOrEmpty(sessionService.InstanceName)?string.Empty: sessionService.InstanceName.Trim('%');
319+
services.AddDataProtection()
320+
.PersistKeysToStackExchangeRedis(redis, DATA_PROTECTION_KEYS)
321+
.SetApplicationName(appDiscriminator);
315322
}
316323
else
317324
{
325+
GXLogging.Info(log, $"Using Redis for Distributed session, ConnectionString:{sessionService.ConnectionString}, InstanceName: {sessionService.InstanceName}");
326+
IConnectionMultiplexer redis = ConnectionMultiplexer.Connect(sessionService.ConnectionString);
327+
services.AddSingleton<IConnectionMultiplexer>(redis);
328+
318329
services.AddStackExchangeRedisCache(options =>
319330
{
320-
GXLogging.Info(log, $"Using Redis for Distributed session, ConnectionString:{sessionService.ConnectionString}, InstanceName: {sessionService.InstanceName}");
321-
options.Configuration = sessionService.ConnectionString;
331+
options.ConnectionMultiplexerFactory = () => Task.FromResult(redis);
322332
options.InstanceName = sessionService.InstanceName;
323333
});
324-
services.AddDataProtection().PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(sessionService.ConnectionString), DATA_PROTECTION_KEYS).SetApplicationName(sessionService.InstanceName);
334+
services.AddDataProtection().PersistKeysToStackExchangeRedis(redis, DATA_PROTECTION_KEYS).SetApplicationName(sessionService.InstanceName);
325335
}
326336
}
327337
else if (sessionService is GxDatabaseSession)

0 commit comments

Comments
 (0)