1
1
using Enyim . Caching ;
2
2
using Enyim . Caching . Memcached ;
3
+ using Newtonsoft . Json . Linq ;
3
4
using System ;
4
5
using System . Collections . Generic ;
5
6
using System . Configuration ;
@@ -18,6 +19,11 @@ public static class MemCacheD
18
19
/// </summary>
19
20
internal static bool API_MEMCACHED_ENABLED = Convert . ToBoolean ( ConfigurationManager . AppSettings [ "API_MEMCACHED_ENABLED" ] ) ;
20
21
22
+ /// <summary>
23
+ /// Max size in MB before splitting a string record in sub-cache entries
24
+ /// </summary>
25
+ internal static uint API_MEMCACHED_MAX_SIZE = Convert . ToUInt32 ( ConfigurationManager . AppSettings [ "API_MEMCACHED_MAX_SIZE" ] ) ;
26
+
21
27
/// <summary>
22
28
/// Maximum validity in number of seconds that MemCacheD can handle (30 days = 2592000)
23
29
/// </summary>
@@ -38,6 +44,11 @@ public static class MemCacheD
38
44
/// </summary>
39
45
internal static MemcachedClient MemcachedClient = API_MEMCACHED_ENABLED ? new MemcachedClient ( ) : null ;
40
46
47
+ /// <summary>
48
+ /// SubKey prefix
49
+ /// </summary>
50
+ internal static String SubKeyPrefix = "subKey_" ;
51
+
41
52
#endregion
42
53
43
54
#region Methods
@@ -99,7 +110,7 @@ private static string GenerateKey_ADO(string nameSpace, string procedureName, Li
99
110
}
100
111
101
112
/// <summary>
102
- /// Generate the Key for ADO
113
+ /// Generate the Key for BSO
103
114
/// </summary>
104
115
/// <typeparam name="T"></typeparam>
105
116
/// <param name="nameSpace"></param>
@@ -142,6 +153,35 @@ private static string GenerateKey_BSO<T>(string nameSpace, string className, str
142
153
}
143
154
}
144
155
156
+ /// <summary>
157
+ /// Get a SubKey for a Key
158
+ /// </summary>
159
+ /// <param name="key"></param>
160
+ /// <returns></returns>
161
+ private static string GetSubKey ( string key )
162
+ {
163
+ return SubKeyPrefix + key ;
164
+ }
165
+
166
+ /// <summary>
167
+ /// Check if a subKey exists
168
+ /// </summary>
169
+ /// <param name="key"></param>
170
+ /// <returns></returns>
171
+ private static bool IsSubKey ( dynamic data , string key )
172
+ {
173
+ // A key must be either a String or a JValue returned from deserialisation
174
+ if ( data . GetType ( ) == typeof ( String ) || data . GetType ( ) == typeof ( JValue ) )
175
+ {
176
+ // Check the explicit casting to String against the subKey
177
+ if ( ( String ) data == GetSubKey ( key ) )
178
+ {
179
+ return true ;
180
+ }
181
+ }
182
+ return false ;
183
+ }
184
+
145
185
/// <summary>
146
186
/// Generate the Hash
147
187
/// </summary>
@@ -326,9 +366,33 @@ public static bool Store_BSO<T>(string nameSpace, string className, string metho
326
366
/// <param name="key"></param>
327
367
/// <param name="value"></param>
328
368
/// <param name="validFor"></param>
369
+ /// <param name="repository"></param>
329
370
/// <returns></returns>
330
371
private static bool Store ( string key , MemCachedD_Value value , TimeSpan validFor , string repository )
331
372
{
373
+ // Check if data is of type String or JValue
374
+ if ( value . data . GetType ( ) == typeof ( String ) || value . data . GetType ( ) == typeof ( JValue ) )
375
+ {
376
+ // Cast data to String and check if oversized
377
+ string sData = ( String ) value . data ;
378
+ if ( sData . Length * sizeof ( Char ) > API_MEMCACHED_MAX_SIZE * 1024 * 1024 )
379
+ {
380
+ // Get a subKey
381
+ string subKey = GetSubKey ( key ) ;
382
+
383
+ // SubStore the data by subKey
384
+ if ( SubStore ( subKey , sData , validFor , repository ) )
385
+ {
386
+ // Override data with the subKey to fish it out later
387
+ value . data = subKey ;
388
+ }
389
+ else
390
+ {
391
+ return false ;
392
+ }
393
+ }
394
+ }
395
+
332
396
try
333
397
{
334
398
// The value must be serialised
@@ -371,6 +435,57 @@ private static bool Store(string key, MemCachedD_Value value, TimeSpan validFor,
371
435
}
372
436
}
373
437
438
+ /// <summary>
439
+ /// SubStore the data string by subKey
440
+ /// </summary>
441
+ /// <param name="subKey"></param>
442
+ /// <param name="data"></param>
443
+ /// <param name="validFor"></param>
444
+ /// <param name="repository"></param>
445
+ /// <returns></returns>
446
+ private static bool SubStore ( string subKey , string data , TimeSpan validFor , string repository )
447
+ {
448
+ try
449
+ {
450
+ // The data is a string, no need to serialize
451
+ Log . Instance . Info ( "SubCache Size Uncompressed (Byte): " + data . Length * sizeof ( Char ) ) ;
452
+
453
+ // The data must be compressed
454
+ string subCacheCompressed = Utility . GZipCompress ( data ) ;
455
+ Log . Instance . Info ( "SubCache Size Compressed (Byte): " + subCacheCompressed . Length * sizeof ( Char ) ) ;
456
+
457
+ bool isStored = false ;
458
+
459
+ // Fix the MemCacheD issue with bad Windows' compiled version: use validFor instead of expiresAt
460
+ // validFor and expiresAt match each other
461
+ isStored = MemcachedClient . Store ( StoreMode . Set , subKey , subCacheCompressed , validFor ) ;
462
+
463
+ // Store Value by Key
464
+ if ( isStored )
465
+ {
466
+ Log . Instance . Info ( "SubStore succesfull: " + subKey ) ;
467
+
468
+ // Add the cached record into a Repository
469
+ if ( ! String . IsNullOrEmpty ( repository ) )
470
+ {
471
+ CasRepositoryStore ( subKey , repository ) ;
472
+ }
473
+
474
+ return true ;
475
+ }
476
+ else
477
+ {
478
+ Log . Instance . Fatal ( "SubStore failed: " + subKey ) ;
479
+ return false ;
480
+ }
481
+ }
482
+ catch ( Exception e )
483
+ {
484
+ Log . Instance . Fatal ( e ) ;
485
+ return false ;
486
+ }
487
+ }
488
+
374
489
/// <summary>
375
490
/// Add a Key into a Cas Repository
376
491
/// N.B. Cas records DO NOT exipire, see Enyim inline documentation
@@ -578,12 +693,10 @@ private static MemCachedD_Value Get(string key)
578
693
Log . Instance . Info ( "Cache Valid For (s): " + cacheValidFor . TotalSeconds . ToString ( ) ) ;
579
694
Log . Instance . Info ( "Cache Has Data: " + cache . hasData . ToString ( ) ) ;
580
695
581
- if ( ! Convert . ToBoolean ( cache . hasData ) )
582
- {
583
- Log . Instance . Info ( "Force removal of cache without data" ) ;
584
- // Remove the record with no data
585
- Remove ( key ) ;
586
- }
696
+ // Init subKey
697
+ string subKey = GetSubKey ( key ) ;
698
+ // Init isSubKey
699
+ bool isSubKey = IsSubKey ( cache . data , key ) ;
587
700
588
701
// double check the cache record is still valid if not cleared by the garbage collector
589
702
if ( cacheExpiresAt > DateTime . Now
@@ -594,22 +707,45 @@ private static MemCachedD_Value Get(string key)
594
707
value . expiresAt = cacheExpiresAt ;
595
708
value . validFor = cacheValidFor ;
596
709
value . hasData = Convert . ToBoolean ( cache . hasData ) ;
597
- value . data = cache . data ;
710
+ value . data = isSubKey ? null : cache . data ;
711
+
712
+ // Check for data in the subKey
713
+ if ( isSubKey )
714
+ {
715
+ // Get subCacheCompressed from the subKey
716
+ string subCacheCompressed = MemcachedClient . Get < string > ( subKey ) ;
717
+
718
+ if ( ! String . IsNullOrEmpty ( subCacheCompressed ) )
719
+ {
720
+ Log . Instance . Info ( "SubCache found: " + subKey ) ;
721
+ Log . Instance . Info ( "SubCache Size Compressed (Byte): " + subCacheCompressed . Length * sizeof ( Char ) ) ;
722
+
723
+ // Decompress the cache
724
+ string subCache = Utility . GZipDecompress ( subCacheCompressed ) ;
725
+ Log . Instance . Info ( "SubCache Size Decompressed (Byte): " + subCache . Length * sizeof ( Char ) ) ;
726
+
727
+ value . data = subCache ;
728
+ }
729
+ else
730
+ {
731
+ Log . Instance . Info ( "SubCache not found: " + key ) ;
732
+ }
733
+ }
598
734
599
735
return value ;
600
736
}
601
737
else
602
738
{
603
- Log . Instance . Info ( "Force removal of expired cache" ) ;
739
+ Log . Instance . Info ( "Forced removal of expired cache" ) ;
604
740
// Remove the expired record
605
741
Remove ( key ) ;
742
+
606
743
}
607
744
}
608
745
else
609
746
{
610
747
Log . Instance . Info ( "Cache not found: " + key ) ;
611
748
}
612
-
613
749
}
614
750
catch ( Exception e )
615
751
{
@@ -666,7 +802,15 @@ private static bool Remove(string key)
666
802
667
803
try
668
804
{
669
- // Remove the record by the Key
805
+ string subKey = GetSubKey ( key ) ;
806
+
807
+ // Remove the (optional) subKey
808
+ if ( MemcachedClient . Remove ( subKey ) )
809
+ {
810
+ Log . Instance . Info ( "SubRemoval succesfull: " + subKey ) ;
811
+ }
812
+
813
+ // Remove the Key
670
814
if ( MemcachedClient . Remove ( key ) )
671
815
{
672
816
Log . Instance . Info ( "Removal succesfull: " + key ) ;
0 commit comments