Skip to content

Commit f4846d9

Browse files
committed
Improving backplane messaging. Reduced message size when sending multiple messages in bulks and improved de/serialization of the messages by 6-10x times
1 parent 7db3950 commit f4846d9

24 files changed

Lines changed: 1367 additions & 233 deletions

src/CacheManager.Core/Internal/BackplaneMessage.cs

Lines changed: 314 additions & 98 deletions
Large diffs are not rendered by default.

src/CacheManager.Core/Utility/ObjectPool.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public ObjectPool(IObjectPoolPolicy<T> policy, int? maxItems = null)
4545
throw new ArgumentNullException(nameof(policy));
4646
}
4747

48-
if (maxItems == null || maxItems == 0)
48+
if (maxItems == null || maxItems <= 0)
4949
{
5050
maxItems = Environment.ProcessorCount * 2;
5151
}

src/CacheManager.StackExchange.Redis/RedisCacheBackplane.cs

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Text;
45
using System.Threading;
56
using System.Threading.Tasks;
67
using CacheManager.Core;
@@ -27,13 +28,14 @@ namespace CacheManager.Redis
2728
public sealed class RedisCacheBackplane : CacheBackplane
2829
{
2930
private readonly string _channelName;
30-
private readonly string _identifier;
31+
private readonly byte[] _identifier;
3132
private readonly ILogger _logger;
3233
private readonly RedisConnectionManager _connection;
33-
private HashSet<string> _messages = new HashSet<string>();
34+
private HashSet<BackplaneMessage> _messages = new HashSet<BackplaneMessage>();
3435
private object _messageLock = new object();
3536
private int _skippedMessages = 0;
3637
private bool _sending = false;
38+
private CancellationTokenSource _source = new CancellationTokenSource();
3739

3840
/// <summary>
3941
/// Initializes a new instance of the <see cref="RedisCacheBackplane"/> class.
@@ -48,7 +50,7 @@ public RedisCacheBackplane(ICacheManagerConfiguration configuration, ILoggerFact
4850

4951
_logger = loggerFactory.CreateLogger(this);
5052
_channelName = configuration.BackplaneChannelName ?? "CacheManagerBackplane";
51-
_identifier = Guid.NewGuid().ToString();
53+
_identifier = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString());
5254

5355
var cfg = RedisConfigurations.GetConfiguration(ConfigurationKey);
5456
_connection = new RedisConnectionManager(
@@ -128,6 +130,7 @@ protected override void Dispose(bool managed)
128130
{
129131
try
130132
{
133+
_source.Cancel();
131134
_connection.Subscriber.Unsubscribe(_channelName);
132135
}
133136
catch
@@ -138,15 +141,13 @@ protected override void Dispose(bool managed)
138141
base.Dispose(managed);
139142
}
140143

141-
private void Publish(string message)
144+
private void Publish(byte[] message)
142145
{
143146
_connection.Subscriber.Publish(_channelName, message, CommandFlags.HighPriority);
144147
}
145148

146149
private void PublishMessage(BackplaneMessage message)
147150
{
148-
var msg = message.Serialize();
149-
150151
lock (_messageLock)
151152
{
152153
if (message.Action == BackplaneAction.Clear)
@@ -155,12 +156,12 @@ private void PublishMessage(BackplaneMessage message)
155156
_messages.Clear();
156157
}
157158

158-
if (!_messages.Add(msg))
159+
if (!_messages.Add(message))
159160
{
160161
Interlocked.Increment(ref _skippedMessages);
161162
if (_logger.IsEnabled(LogLevel.Trace))
162163
{
163-
_logger.LogTrace("Skipped duplicate message: {0}.", msg);
164+
_logger.LogTrace("Skipped duplicate message: {0}.", message);
164165
}
165166
}
166167

@@ -188,12 +189,12 @@ private void SendMessages()
188189
#if !NET40
189190
await Task.Delay(10).ConfigureAwait(false);
190191
#endif
191-
var msgs = string.Empty;
192+
byte[] msgs = null;
192193
lock (_messageLock)
193194
{
194195
if (_messages != null && _messages.Count > 0)
195196
{
196-
msgs = string.Join(",", _messages);
197+
msgs = BackplaneMessage.Serialize(_messages.ToArray());
197198

198199
if (_logger.IsEnabled(LogLevel.Debug))
199200
{
@@ -207,7 +208,7 @@ private void SendMessages()
207208

208209
try
209210
{
210-
if (msgs.Length > 0)
211+
if (msgs != null)
211212
{
212213
Publish(msgs);
213214
Interlocked.Increment(ref SentChunks);
@@ -223,14 +224,14 @@ private void SendMessages()
223224
#if NET40
224225
},
225226
this,
226-
CancellationToken.None,
227+
_source.Token,
227228
TaskCreationOptions.None,
228229
TaskScheduler.Default)
229230
.ConfigureAwait(false);
230231
#else
231232
},
232233
this,
233-
CancellationToken.None,
234+
_source.Token,
234235
TaskCreationOptions.DenyChildAttach,
235236
TaskScheduler.Default)
236237
.ConfigureAwait(false);
@@ -243,27 +244,25 @@ private void Subscribe()
243244
_channelName,
244245
(channel, msg) =>
245246
{
246-
var fullMessage = ((string)msg).Split(',')
247-
.Where(s => !s.StartsWith(_identifier, StringComparison.Ordinal))
248-
.ToArray();
247+
var messages = BackplaneMessage.Deserialize(msg, _identifier);
249248

250-
if (fullMessage.Length == 0)
249+
if (!messages.Any())
251250
{
252251
// no messages for this instance
253252
return;
254253
}
255254

256-
Interlocked.Add(ref MessagesReceived, fullMessage.Length);
255+
// now deserialize all of them (lazy enumerable)
256+
var fullMessages = messages.ToArray();
257+
Interlocked.Add(ref MessagesReceived, fullMessages.Length);
257258

258259
if (_logger.IsEnabled(LogLevel.Information))
259260
{
260-
_logger.LogInfo("Backplane got notified with {0} new messages.", fullMessage.Length);
261+
_logger.LogInfo("Backplane got notified with {0} new messages.", fullMessages.Length);
261262
}
262263

263-
foreach (var messageStr in fullMessage)
264+
foreach (var message in fullMessages)
264265
{
265-
var message = BackplaneMessage.Deserialize(messageStr);
266-
267266
switch (message.Action)
268267
{
269268
case BackplaneAction.Clear:
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using BenchmarkDotNet.Attributes;
6+
using CacheManager.Core.Internal;
7+
8+
namespace CacheManager.Benchmarks
9+
{
10+
public class BackplaneMessageBenchmarkMultiple
11+
{
12+
private static byte[] _ownderBytes = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString());
13+
private static BackplaneMessage[] _multiple;
14+
private byte[] _multipleSerialized = BackplaneMessage.Serialize(_multiple);
15+
16+
static BackplaneMessageBenchmarkMultiple()
17+
{
18+
var messages = new List<BackplaneMessage>();
19+
for (var i = 0; i < 10; i++)
20+
{
21+
messages.Add(BackplaneMessage.ForChanged(_ownderBytes, "somerandomkey" + i, CacheItemChangedEventAction.Update));
22+
messages.Add(BackplaneMessage.ForChanged(_ownderBytes, "somerandomkey" + i, "withregion", CacheItemChangedEventAction.Add));
23+
}
24+
for (var i = 0; i < 10; i++)
25+
{
26+
messages.Add(BackplaneMessage.ForClear(_ownderBytes));
27+
}
28+
for (var i = 0; i < 10; i++)
29+
{
30+
messages.Add(BackplaneMessage.ForClearRegion(_ownderBytes, "somerandomregion" + i));
31+
}
32+
for (var i = 0; i < 10; i++)
33+
{
34+
messages.Add(BackplaneMessage.ForRemoved(_ownderBytes, "somerandomkey" + i, "withregion"));
35+
}
36+
_multiple = messages.ToArray();
37+
}
38+
39+
[Benchmark]
40+
public void SerializeMany()
41+
{
42+
var bytes = BackplaneMessage.Serialize(_multiple);
43+
}
44+
45+
[Benchmark()]
46+
public void DeserializeMany()
47+
{
48+
var messages = BackplaneMessage.Deserialize(_multipleSerialized).ToArray();
49+
}
50+
}
51+
52+
public class BackplaneMessageBenchmark
53+
{
54+
private static byte[] _ownderBytes = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString());
55+
56+
private static BackplaneMessage _dataSingleChange = BackplaneMessage.ForChanged(_ownderBytes, "somerandomkey", CacheItemChangedEventAction.Update);
57+
private byte[] _rawSingleChange = BackplaneMessage.Serialize(_dataSingleChange);
58+
59+
private static BackplaneMessage _dataSingleChangeRegion = BackplaneMessage.ForChanged(_ownderBytes, "somerandomkey", "withregion", CacheItemChangedEventAction.Add);
60+
private byte[] _rawSingleChangeRegion = BackplaneMessage.Serialize(_dataSingleChangeRegion);
61+
62+
private static BackplaneMessage _dataSingleClear = BackplaneMessage.ForClear(_ownderBytes);
63+
private byte[] _rawSingleClear = BackplaneMessage.Serialize(_dataSingleClear);
64+
65+
private static BackplaneMessage _dataSingleClearRegion = BackplaneMessage.ForClearRegion(_ownderBytes, "somerandomregion");
66+
private byte[] _rawSingleClearRegion = BackplaneMessage.Serialize(_dataSingleClearRegion);
67+
68+
private static BackplaneMessage _dataSingleRemove = BackplaneMessage.ForRemoved(_ownderBytes, "somerandomkey", "withregion");
69+
private byte[] _rawSingleRemove = BackplaneMessage.Serialize(_dataSingleRemove);
70+
71+
[Benchmark(Baseline = true)]
72+
public void SerializeChange()
73+
{
74+
var fullMessage = BackplaneMessage.Serialize(_dataSingleChange);
75+
}
76+
77+
[Benchmark()]
78+
public void DeserializeChange()
79+
{
80+
var msg = BackplaneMessage.Deserialize(_rawSingleChange).ToArray();
81+
}
82+
83+
[Benchmark]
84+
public void SerializeChangeRegion()
85+
{
86+
var fullMessage = BackplaneMessage.Serialize(_dataSingleChangeRegion);
87+
}
88+
89+
[Benchmark()]
90+
public void DeserializeChangeRegion()
91+
{
92+
var msg = BackplaneMessage.Deserialize(_rawSingleChangeRegion).ToArray();
93+
}
94+
95+
[Benchmark]
96+
public void SerializeClear()
97+
{
98+
var fullMessage = BackplaneMessage.Serialize(_dataSingleClear);
99+
}
100+
101+
[Benchmark()]
102+
public void DeserializeClear()
103+
{
104+
var msg = BackplaneMessage.Deserialize(_rawSingleClear).ToArray();
105+
}
106+
107+
[Benchmark]
108+
public void SerializeClearRegion()
109+
{
110+
var fullMessage = BackplaneMessage.Serialize(_dataSingleClearRegion);
111+
}
112+
113+
[Benchmark()]
114+
public void DeserializeClearRegion()
115+
{
116+
var msg = BackplaneMessage.Deserialize(_rawSingleClearRegion).ToArray();
117+
}
118+
119+
[Benchmark]
120+
public void SerializeRemove()
121+
{
122+
var fullMessage = BackplaneMessage.Serialize(_dataSingleRemove);
123+
}
124+
125+
[Benchmark()]
126+
public void DeserializeRemove()
127+
{
128+
var msg = BackplaneMessage.Deserialize(_rawSingleRemove).ToArray();
129+
}
130+
}
131+
}

test/CacheManager.Benchmarks/BaseCacheManagerBenchmark.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
namespace CacheManager.Benchmarks
99
{
10-
[Config(typeof(CacheManagerBenchConfig))]
1110
public abstract class BaseCacheBenchmark
1211
{
1312
private static ICacheManagerConfiguration BaseConfig
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
``` ini
2+
3+
BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
4+
Processor=Intel Core i7-6700 CPU 3.40GHz (Skylake), ProcessorCount=8
5+
Frequency=3328125 Hz, Resolution=300.4695 ns, Timer=TSC
6+
[Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2098.0
7+
Job-VUBPRE : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2098.0
8+
9+
Runtime=Clr LaunchCount=1 TargetCount=10
10+
WarmupCount=4
11+
12+
```
13+
| Method | Mean | Error | StdDev | Median | Op/s | Scaled | ScaledSD | Rank | Gen 0 | Allocated |
14+
|------------------------ |----------:|----------:|---------:|----------:|-------------:|-------:|---------:|-----:|-------:|----------:|
15+
| SerializeChange | 194.57 ns | 11.309 ns | 7.480 ns | 192.86 ns | 5,139,629.5 | 1.00 | 0.00 | 4 | 0.1523 | 640 B |
16+
| DeserializeChange | 241.69 ns | 10.354 ns | 6.848 ns | 238.51 ns | 4,137,485.5 | 1.24 | 0.06 | 5 | 0.0875 | 368 B |
17+
| SerializeChangeRegion | 249.23 ns | 7.700 ns | 4.582 ns | 249.67 ns | 4,012,334.1 | 1.28 | 0.05 | 6 | 0.1826 | 768 B |
18+
| DeserializeChangeRegion | 305.05 ns | 8.953 ns | 5.922 ns | 306.25 ns | 3,278,156.0 | 1.57 | 0.06 | 7 | 0.0987 | 416 B |
19+
| SerializeClear | 96.21 ns | 4.459 ns | 2.949 ns | 95.74 ns | 10,393,877.9 | 0.50 | 0.02 | 1 | 0.1162 | 488 B |
20+
| DeserializeClear | 158.92 ns | 7.949 ns | 5.258 ns | 159.25 ns | 6,292,374.9 | 0.82 | 0.04 | 2 | 0.0741 | 312 B |
21+
| SerializeClearRegion | 169.66 ns | 7.262 ns | 3.798 ns | 169.60 ns | 5,894,312.1 | 0.87 | 0.04 | 3 | 0.1581 | 664 B |
22+
| DeserializeClearRegion | 240.22 ns | 8.661 ns | 5.154 ns | 240.20 ns | 4,162,849.7 | 1.24 | 0.05 | 5 | 0.0892 | 376 B |
23+
| SerializeRemove | 255.63 ns | 15.071 ns | 9.968 ns | 258.50 ns | 3,911,924.7 | 1.32 | 0.07 | 6 | 0.1826 | 768 B |
24+
| DeserializeRemove | 300.98 ns | 9.420 ns | 5.606 ns | 299.66 ns | 3,322,483.1 | 1.55 | 0.06 | 7 | 0.0987 | 416 B |
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Method;Job;AnalyzeLaunchVariance;EvaluateOverhead;MaxAbsoluteError;MaxRelativeError;MinInvokeCount;MinIterationTime;RemoveOutliers;Affinity;Jit;Platform;Runtime;AllowVeryLargeObjects;Concurrent;CpuGroups;Force;RetainVm;Server;Clock;EngineFactory;Toolchain;InvocationCount;IterationTime;LaunchCount;RunStrategy;TargetCount;UnrollFactor;WarmupCount;Mean;Error;StdDev;Median;Op/s;Scaled;ScaledSD;Rank;Gen 0;Allocated
2+
SerializeChange;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;194.57 ns;11.309 ns;7.480 ns;192.86 ns;"5,139,629.5";1.00;0.00;4;0.1523;640 B
3+
DeserializeChange;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;241.69 ns;10.354 ns;6.848 ns;238.51 ns;"4,137,485.5";1.24;0.06;5;0.0875;368 B
4+
SerializeChangeRegion;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;249.23 ns;7.700 ns;4.582 ns;249.67 ns;"4,012,334.1";1.28;0.05;6;0.1826;768 B
5+
DeserializeChangeRegion;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;305.05 ns;8.953 ns;5.922 ns;306.25 ns;"3,278,156.0";1.57;0.06;7;0.0987;416 B
6+
SerializeClear;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;96.21 ns;4.459 ns;2.949 ns;95.74 ns;"10,393,877.9";0.50;0.02;1;0.1162;488 B
7+
DeserializeClear;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;158.92 ns;7.949 ns;5.258 ns;159.25 ns;"6,292,374.9";0.82;0.04;2;0.0741;312 B
8+
SerializeClearRegion;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;169.66 ns;7.262 ns;3.798 ns;169.60 ns;"5,894,312.1";0.87;0.04;3;0.1581;664 B
9+
DeserializeClearRegion;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;240.22 ns;8.661 ns;5.154 ns;240.20 ns;"4,162,849.7";1.24;0.05;5;0.0892;376 B
10+
SerializeRemove;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;255.63 ns;15.071 ns;9.968 ns;258.50 ns;"3,911,924.7";1.32;0.07;6;0.1826;768 B
11+
DeserializeRemove;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;300.98 ns;9.420 ns;5.606 ns;299.66 ns;"3,322,483.1";1.55;0.06;7;0.0987;416 B
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!DOCTYPE html>
2+
<html lang='en'>
3+
<head>
4+
<meta charset='utf-8' />
5+
<title>BackplaneMessageBenchmark</title>
6+
7+
<style type="text/css">
8+
table { border-collapse: collapse; display: block; width: 100%; overflow: auto; }
9+
td, th { padding: 6px 13px; border: 1px solid #ddd; }
10+
tr { background-color: #fff; border-top: 1px solid #ccc; }
11+
tr:nth-child(even) { background: #f8f8f8; }
12+
</style>
13+
</head>
14+
<body>
15+
<pre><code>
16+
BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
17+
Processor=Intel Core i7-6700 CPU 3.40GHz (Skylake), ProcessorCount=8
18+
Frequency=3328125 Hz, Resolution=300.4695 ns, Timer=TSC
19+
[Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2098.0
20+
Job-VUBPRE : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2098.0
21+
</code></pre>
22+
<pre><code>Runtime=Clr LaunchCount=1 TargetCount=10
23+
WarmupCount=4
24+
</code></pre>
25+
26+
<table>
27+
<thead><tr><th> Method</th><th>Mean</th><th>Error</th><th>StdDev</th><th>Median</th><th> Op/s</th><th>Scaled</th><th>ScaledSD</th><th>Rank</th><th>Gen 0</th><th>Allocated</th>
28+
</tr>
29+
</thead><tbody><tr><td> SerializeChange</td><td>194.57 ns</td><td>11.309 ns</td><td>7.480 ns</td><td>192.86 ns</td><td>5,139,629.5</td><td>1.00</td><td>0.00</td><td>4</td><td>0.1523</td><td>640 B</td>
30+
</tr><tr><td>DeserializeChange</td><td>241.69 ns</td><td>10.354 ns</td><td>6.848 ns</td><td>238.51 ns</td><td>4,137,485.5</td><td>1.24</td><td>0.06</td><td>5</td><td>0.0875</td><td>368 B</td>
31+
</tr><tr><td>SerializeChangeRegion</td><td>249.23 ns</td><td>7.700 ns</td><td>4.582 ns</td><td>249.67 ns</td><td>4,012,334.1</td><td>1.28</td><td>0.05</td><td>6</td><td>0.1826</td><td>768 B</td>
32+
</tr><tr><td>DeserializeChangeRegion</td><td>305.05 ns</td><td>8.953 ns</td><td>5.922 ns</td><td>306.25 ns</td><td>3,278,156.0</td><td>1.57</td><td>0.06</td><td>7</td><td>0.0987</td><td>416 B</td>
33+
</tr><tr><td> SerializeClear</td><td>96.21 ns</td><td>4.459 ns</td><td>2.949 ns</td><td>95.74 ns</td><td>10,393,877.9</td><td>0.50</td><td>0.02</td><td>1</td><td>0.1162</td><td>488 B</td>
34+
</tr><tr><td> DeserializeClear</td><td>158.92 ns</td><td>7.949 ns</td><td>5.258 ns</td><td>159.25 ns</td><td>6,292,374.9</td><td>0.82</td><td>0.04</td><td>2</td><td>0.0741</td><td>312 B</td>
35+
</tr><tr><td>SerializeClearRegion</td><td>169.66 ns</td><td>7.262 ns</td><td>3.798 ns</td><td>169.60 ns</td><td>5,894,312.1</td><td>0.87</td><td>0.04</td><td>3</td><td>0.1581</td><td>664 B</td>
36+
</tr><tr><td>DeserializeClearRegion</td><td>240.22 ns</td><td>8.661 ns</td><td>5.154 ns</td><td>240.20 ns</td><td>4,162,849.7</td><td>1.24</td><td>0.05</td><td>5</td><td>0.0892</td><td>376 B</td>
37+
</tr><tr><td> SerializeRemove</td><td>255.63 ns</td><td>15.071 ns</td><td>9.968 ns</td><td>258.50 ns</td><td>3,911,924.7</td><td>1.32</td><td>0.07</td><td>6</td><td>0.1826</td><td>768 B</td>
38+
</tr><tr><td>DeserializeRemove</td><td>300.98 ns</td><td>9.420 ns</td><td>5.606 ns</td><td>299.66 ns</td><td>3,322,483.1</td><td>1.55</td><td>0.06</td><td>7</td><td>0.0987</td><td>416 B</td>
39+
</tr></tbody></table>
40+
</body>
41+
</html>

0 commit comments

Comments
 (0)