22using System . Collections . Generic ;
33using System . Linq ;
44using System . Text . Json ;
5- using System . Threading . Tasks ;
5+ #if NETCORE
6+ using GeneXus . Application ;
7+ using GxClasses . Helpers ;
8+ using Microsoft . Extensions . Caching . Memory ;
9+ #endif
610using GeneXus . Services ;
711using GeneXus . Utils ;
812using StackExchange . Redis ;
@@ -15,19 +19,24 @@ public sealed class Redis : ICacheService2
1519
1620 ConnectionMultiplexer _redisConnection ;
1721 IDatabase _redisDatabase ;
22+ #if NETCORE
23+ MemoryCache _localCache ;
24+ private const double DEFAULT_LOCAL_CACHE_FACTOR = 0.8 ;
25+ private static readonly TimeSpan LOCAL_CACHE_PERSISTENT_KEY_TTL = TimeSpan . FromMinutes ( 5 ) ;
26+
27+ #endif
1828 ConfigurationOptions _redisConnectionOptions ;
1929 private const int REDIS_DEFAULT_PORT = 6379 ;
2030 public int redisSessionTimeout ;
31+
2132 public Redis ( string connectionString )
2233 {
2334 _redisConnectionOptions = ConfigurationOptions . Parse ( connectionString ) ;
2435 _redisConnectionOptions . AllowAdmin = true ;
2536 }
2637
27- public Redis ( string connectionString , int sessionTimeout )
38+ public Redis ( string connectionString , int sessionTimeout ) : this ( connectionString )
2839 {
29- _redisConnectionOptions = ConfigurationOptions . Parse ( connectionString ) ;
30- _redisConnectionOptions . AllowAdmin = true ;
3140 redisSessionTimeout = sessionTimeout ;
3241 }
3342 public Redis ( )
@@ -56,8 +65,25 @@ public Redis()
5665 _redisConnectionOptions = ConfigurationOptions . Parse ( address ) ;
5766 }
5867 _redisConnectionOptions . AllowAdmin = true ;
68+ InitLocalCache ( providerService ) ;
5969 }
6070 }
71+ private void InitLocalCache ( GXService providerService )
72+ {
73+ #if NETCORE
74+ string localCache = providerService . Properties . Get ( "ENABLE_MEMORY_CACHE" ) ;
75+ if ( ! string . IsNullOrEmpty ( localCache ) && localCache . Equals ( bool . TrueString , StringComparison . OrdinalIgnoreCase ) )
76+ {
77+ GXLogging . Debug ( log , "Using Redis Hybrid mode with local memory cache." ) ;
78+ _localCache = new MemoryCache ( new MemoryCacheOptions ( ) ) ;
79+ }
80+ else
81+ {
82+ GXLogging . Debug ( log , "Using Redis only mode without local memory cache." ) ;
83+ }
84+ #endif
85+ }
86+
6187 IDatabase RedisDatabase
6288 {
6389 get
@@ -78,50 +104,73 @@ public void Clear(string cacheid, string key)
78104 public void ClearKey ( string key )
79105 {
80106 RedisDatabase . KeyDelete ( key ) ;
107+ ClearKeyLocal ( key ) ;
81108 }
82109
83110 public void ClearCache ( string cacheid )
84111 {
85112 Nullable < long > prefix = new Nullable < long > ( KeyPrefix ( cacheid ) . Value + 1 ) ;
86113 RedisDatabase . StringSet ( cacheid , prefix ) ;
114+ SetPersistentLocal ( cacheid , prefix ) ;
87115 }
88116
89117 public void ClearAllCaches ( )
90118 {
91- var endpoints = _redisConnection . GetEndPoints ( true ) ;
119+ IConnectionMultiplexer multiplexer = RedisDatabase . Multiplexer ;
120+ System . Net . EndPoint [ ] endpoints = multiplexer . GetEndPoints ( true ) ;
92121 foreach ( var endpoint in endpoints )
93122 {
94- var server = _redisConnection . GetServer ( endpoint ) ;
123+ var server = multiplexer . GetServer ( endpoint ) ;
95124 server . FlushAllDatabases ( ) ;
96125 }
126+ ClearAllCachesLocal ( ) ;
97127 }
98128
99129 public bool KeyExpire ( string cacheid , string key , TimeSpan expiry , CommandFlags flags = CommandFlags . None )
100130 {
101- Task < bool > t = RedisDatabase . KeyExpireAsync ( Key ( cacheid , key ) , expiry , flags ) ;
102- t . Wait ( ) ;
103- return t . Result ;
131+ string fullKey = Key ( cacheid , key ) ;
132+ bool expirationSaved = RedisDatabase . KeyExpire ( fullKey , expiry , flags ) ;
133+ if ( expirationSaved )
134+ KeyExpireLocal ( fullKey ) ;
135+ return expirationSaved ;
104136 }
105137
106138 public bool KeyExists ( string cacheid , string key )
107139 {
108- Task < bool > t = RedisDatabase . KeyExistsAsync ( Key ( cacheid , key ) ) ;
109- t . Wait ( ) ;
110- return t . Result ;
140+ string fullKey = Key ( cacheid , key ) ;
141+
142+ if ( KeyExistsLocal ( fullKey ) )
143+ {
144+ GXLogging . Debug ( log , $ "KeyExists hit local cache { fullKey } ") ;
145+ return true ;
146+ }
147+
148+ return RedisDatabase . KeyExists ( fullKey ) ;
111149 }
150+
112151 private bool Get < T > ( string key , out T value )
113152 {
153+ if ( GetLocal ( key , out value ) )
154+ {
155+ GXLogging . Debug ( log , $ "Get<T> hit local cache { key } ") ;
156+ return true ;
157+ }
158+
114159 if ( default ( T ) == null )
115160 {
116161 value = Deserialize < T > ( RedisDatabase . StringGet ( key ) ) ;
117- if ( value == null ) GXLogging . Debug ( log , "Get<T>, misses key '" + key + "'" ) ;
162+ if ( value == null )
163+ GXLogging . Debug ( log , "Get<T>, misses key '" + key + "'" ) ;
164+ else
165+ SetLocal ( key , value ) ;
118166 return value != null ;
119167 }
120168 else
121169 {
122170 if ( RedisDatabase . KeyExists ( key ) )
123171 {
124172 value = Deserialize < T > ( RedisDatabase . StringGet ( key ) ) ;
173+ SetLocal ( key , value ) ;
125174 return true ;
126175 }
127176 else
@@ -133,6 +182,81 @@ private bool Get<T>(string key, out T value)
133182 }
134183 }
135184
185+ #if NETCORE
186+ public IDictionary < string , T > GetAll < T > ( string cacheid , IEnumerable < string > keys )
187+ {
188+ if ( keys == null ) return null ;
189+
190+ var results = new Dictionary < string , T > ( ) ;
191+ var keysToFetch = new List < string > ( ) ;
192+
193+ foreach ( string k in keys )
194+ {
195+ string fullKey = Key ( cacheid , k ) ;
196+ if ( GetLocal < T > ( fullKey , out T value ) )
197+ {
198+ GXLogging . Debug ( log , $ "Get<T> hit local cache { fullKey } ") ;
199+ results [ k ] = value ;
200+ }
201+ else
202+ {
203+ keysToFetch . Add ( k ) ;
204+ }
205+ }
206+
207+ if ( keysToFetch . Count > 0 )
208+ {
209+ var prefixedKeys = Key ( cacheid , keysToFetch ) ;
210+ RedisValue [ ] values = RedisDatabase . StringGet ( prefixedKeys . ToArray ( ) ) ;
211+
212+ int i = 0 ;
213+ foreach ( string k in keysToFetch )
214+ {
215+ string fullKey = Key ( cacheid , k ) ;
216+ T value = Deserialize < T > ( values [ i ] ) ;
217+ results [ k ] = value ;
218+
219+ SetLocal ( fullKey , value ) ;
220+ i ++ ;
221+ }
222+ }
223+
224+ return results ;
225+ }
226+ public void SetAll < T > ( string cacheid , IEnumerable < string > keys , IEnumerable < T > values , int duration = 0 )
227+ {
228+ if ( keys == null || values == null || keys . Count ( ) != values . Count ( ) )
229+ return ;
230+
231+ IEnumerable < RedisKey > prefixedKeys = Key ( cacheid , keys ) ;
232+ IEnumerator < T > valuesEnumerator = values . GetEnumerator ( ) ;
233+ KeyValuePair < RedisKey , RedisValue > [ ] redisBatch = new KeyValuePair < RedisKey , RedisValue > [ prefixedKeys . Count ( ) ] ;
234+
235+ int i = 0 ;
236+ foreach ( RedisKey redisKey in prefixedKeys )
237+ {
238+ if ( valuesEnumerator . MoveNext ( ) )
239+ {
240+ T value = valuesEnumerator . Current ;
241+ redisBatch [ i ] = new KeyValuePair < RedisKey , RedisValue > ( redisKey , Serialize ( value ) ) ;
242+ SetLocal < T > ( redisKey . ToString ( ) , value , duration ) ;
243+ }
244+ i ++ ;
245+ }
246+ if ( redisBatch . Length > 0 )
247+ {
248+ if ( duration > 0 )
249+ {
250+ foreach ( var pair in redisBatch )
251+ RedisDatabase . StringSet ( pair . Key , pair . Value , TimeSpan . FromMinutes ( duration ) ) ;
252+ }
253+ else
254+ {
255+ RedisDatabase . StringSet ( redisBatch ) ;
256+ }
257+ }
258+ }
259+ #else
136260 public IDictionary < string , T > GetAll < T > ( string cacheid , IEnumerable < string > keys )
137261 {
138262 if ( keys != null )
@@ -170,19 +294,20 @@ public void SetAll<T>(string cacheid, IEnumerable<string> keys, IEnumerable<T> v
170294 RedisDatabase . StringSet ( dictionary ) ;
171295 }
172296 }
173-
297+ #endif
174298 private void Set < T > ( string key , T value , int duration )
175299 {
176300 GXLogging . Debug ( log , "Set<T> key:" + key + " value " + value + " valuetype:" + value . GetType ( ) ) ;
177301 if ( duration > 0 )
178302 RedisDatabase . StringSet ( key , Serialize ( value ) , TimeSpan . FromMinutes ( duration ) ) ;
179303 else
180304 RedisDatabase . StringSet ( key , Serialize ( value ) ) ;
305+ SetLocal ( key , value , duration ) ;
181306 }
182307
183308 private void Set < T > ( string key , T value )
184309 {
185- RedisDatabase . StringSet ( key , Serialize ( value ) ) ;
310+ Set < T > ( key , value , 0 ) ;
186311 }
187312
188313 public bool Get < T > ( string cacheid , string key , out T value )
@@ -245,5 +370,79 @@ static T Deserialize<T>(string value)
245370 opts . Converters . Add ( new ObjectToInferredTypesConverter ( ) ) ;
246371 return JsonSerializer . Deserialize < T > ( value , opts ) ;
247372 }
373+ #if NETCORE
374+ private TimeSpan LocalCacheTTL ( int durationMinutes )
375+ {
376+ return LocalCacheTTL ( durationMinutes > 0 ? TimeSpan . FromMinutes ( durationMinutes ) : ( TimeSpan ? ) null ) ;
377+ }
378+ private TimeSpan LocalCacheTTL ( TimeSpan ? ttl )
379+ {
380+ return ttl . HasValue ? TimeSpan . FromTicks ( ( long ) ( ttl . Value . Ticks * DEFAULT_LOCAL_CACHE_FACTOR ) ) : LOCAL_CACHE_PERSISTENT_KEY_TTL ;
381+ }
382+ #endif
383+ private void ClearKeyLocal ( string key )
384+ {
385+ #if NETCORE
386+ _localCache ? . Remove ( key ) ;
387+ #endif
388+ }
389+ void ClearAllCachesLocal ( )
390+ {
391+ #if NETCORE
392+ _localCache ? . Compact ( 1.0 ) ;
393+ #endif
394+ }
395+
396+ private void KeyExpireLocal ( string fullKey )
397+ {
398+ #if NETCORE
399+ _localCache ? . Remove ( fullKey ) ;
400+ #endif
401+ }
402+ private bool KeyExistsLocal ( string fullKey )
403+ {
404+ #if NETCORE
405+ return _localCache ? . TryGetValue ( fullKey , out _ ) ?? false ;
406+ #else
407+ return false ;
408+ #endif
409+ }
410+
411+ private void SetLocal < T > ( string key , T value )
412+ {
413+ #if NETCORE
414+ if ( _localCache != null )
415+ {
416+ TimeSpan ? redisTTL = RedisDatabase . KeyTimeToLive ( key ) ;
417+ _localCache . Set ( key , value , LocalCacheTTL ( redisTTL ) ) ;
418+ }
419+ #endif
420+ }
421+ private void SetPersistentLocal ( string cacheid , long ? prefix )
422+ {
423+ #if NETCORE
424+ _localCache ? . Set ( cacheid , prefix , LocalCacheTTL ( LOCAL_CACHE_PERSISTENT_KEY_TTL ) ) ;
425+ #endif
426+ }
427+ private void SetLocal < T > ( string key , T value , int duration )
428+ {
429+ #if NETCORE
430+ _localCache ? . Set ( key , value , LocalCacheTTL ( duration ) ) ;
431+ #endif
432+ }
433+ private bool GetLocal < T > ( string key , out T value )
434+ {
435+ #if NETCORE
436+ if ( _localCache == null )
437+ {
438+ value = default ( T ) ;
439+ return false ;
440+ }
441+ return _localCache . TryGetValue ( key , out value ) ;
442+ #else
443+ value = default ( T ) ;
444+ return false ;
445+ #endif
446+ }
248447 }
249448}
0 commit comments