We are having speed issues while using the Azure Service Bus Relay with both netTcpRelayBinding
and basicHttpRelayBinding
. With small message sizes (10K), the relay operates with low latency (100ms), but as the message size increases (100K) we experience seemingly random response times (600ms-1000ms). We would like to improve the latency cost for larger messages.
Is using message compression (gzip, protobuf-net, etc.) supported through the Service Bus Relay? Has anyone had success with enabling both request/response compression through the relay? It's trivial to support response compression through IIS, but we'd like to support request compression for improving our latency costs. Since we can't profile the relay with Fiddler, how do we know the message is still compressed when it passes through the relay?
An interesting point we discovered is that introducing a delay between subsequent message relays (2s) we receive better performance (100K - 200ms). Could it be that larger messages are being automatically throttled? It would be nice to know the message size cutoff that triggers a throttling condition.
For our test - we just send a random message string to the service relay and echo the request string back from the server. We've tried this client/server from multiple geographic locations (to rule out firewall/web filter issues) and experienced the same latency behavior.
public class ServiceRelayProfiler : IServiceRelayProfiler
{
public string HelloProfiler(string name)
{
return string.Format("Hello {0}", name);
}
}
ChannelFactory<IServiceRelayProfiler> channelFactory = new ChannelFactory<IServiceRelayProfiler>("helloProfilerTcp");
IServiceRelayProfiler channel = channelFactory.CreateChannel();
string message = RandomString(100000); // 100K
for (int i = 0; i < 100; i++)
{
DateTime start = DateTime.Now;
string response = channel.HelloProfiler(message);
DateTime end = DateTime.Now;
TimeSpan duration = end - start;
Console.WriteLine("Response is: {0} at {1}\tDuration: {2}ms", response.Substring(0, 20) + "....", end, duration.Milliseconds);
//Thread.Sleep(2000); // delay makes response times more consistent
}
The Azure Relay service enables you to securely expose services that run in your corporate network to the public cloud. You can do so without opening a port on your firewall, or making intrusive changes to your corporate network infrastructure.
How Azure Service Bus Queue is used? Service Bus Queue primarily helps in load balancing of system level messages. It provides pull-based messaging services that temporary stores message in queue so that the destination / consuming system can process messages at its own pace / average processing.
It's not a full answer but on the server side, you can add this to the global.asax.cs
to allow request decompression :
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_BeginRequest(Object sender, EventArgs e)
{
//Activate request decompression
string contentEncoding = Request.Headers["Content-Encoding"];
if (contentEncoding != null && contentEncoding.Equals("gzip", StringComparison.CurrentCultureIgnoreCase))
{
Request.Filter = new GZipStream(Request.Filter, CompressionMode.Decompress, true);
}
}
}
One thing you could try is compress the messages yourselves via System.IO.Compression (see util below - you could just add as an extension).
But on the queueing/throttling question. Larger messages will always lag out a network more than smaller/optimized chunks. If you can break up data under 50k or less or use udp it may transmit faster with less queueing and validation than on tcp.
The absolute limitation on TCP packet size is 64K (65535 bytes), but in practicality this is far larger than the size of any packet you will see, because the lower layers (e.g. ethernet) have lower packet sizes.
The MTU (Maximum Transmission Unit) for Ethernet, for instance, is 1500 bytes. Some types of networks (like Token Ring) have larger MTUs, and some types have smaller MTUs, but the values are fixed for each physical technology.
from here: maximum packet size for a TCP connection
Things will go smoother when you break things up, multiplayer games have do to this and they also use udp more to limit extra validation (implementing reliable udp to validate individual messages and ordering when needed only).
CompressionUtil class, compress messages in and out before sending/after receive: https://gist.github.com/drawcode/8948293
public static class CompressUtil {
public static string ToCompressed(this string val) {
if (!IsStringCompressed(val)) {
return CompressString(val);
}
return val;
}
public static string ToDecompressed(this string val) {
if (IsStringCompressed(val)) {
return DecompressString(val);
}
return val;
}
public static string CompressString(string text) {
byte[] buffer = Encoding.UTF8.GetBytes(text);
var memoryStream = new MemoryStream();
using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) {
gZipStream.Write(buffer, 0, buffer.Length);
}
memoryStream.Position = 0;
var compressedData = new byte[memoryStream.Length];
memoryStream.Read(compressedData, 0, compressedData.Length);
var gZipBuffer = new byte[compressedData.Length + 4];
Buffer.BlockCopy(compressedData, 0, gZipBuffer, 4, compressedData.Length);
Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gZipBuffer, 0, 4);
return Convert.ToBase64String(gZipBuffer);
}
public static string DecompressString(string compressedText) {
byte[] gZipBuffer = Convert.FromBase64String(compressedText);
using (var memoryStream = new MemoryStream()) {
int dataLength = BitConverter.ToInt32(gZipBuffer, 0);
memoryStream.Write(gZipBuffer, 4, gZipBuffer.Length - 4);
var buffer = new byte[dataLength];
memoryStream.Position = 0;
using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) {
gZipStream.Read(buffer, 0, buffer.Length);
}
return Encoding.UTF8.GetString(buffer);
}
}
public static bool IsStringCompressed(string data) {
if (IsStringCompressedGZip(data) || IsStringCompressedPKZip(data)) {
return true;
}
return false;
}
public static bool IsStringCompressedGZip(string data) {
return CheckSignatureString(data, 3, "1F-8B-08");
}
public static bool IsStringCompressedPKZip(string data) {
return CheckSignatureString(data, 4, "50-4B-03-04");
}
public static bool CheckSignatureFile(string filepath, int signatureSize, string expectedSignature) {
if (String.IsNullOrEmpty(filepath))
throw new ArgumentException("Must specify a filepath");
if (String.IsNullOrEmpty(expectedSignature))
throw new ArgumentException("Must specify a value for the expected file signature");
using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
if (fs.Length < signatureSize)
return false;
byte[] signature = new byte[signatureSize];
int bytesRequired = signatureSize;
int index = 0;
while (bytesRequired > 0) {
int bytesRead = fs.Read(signature, index, bytesRequired);
bytesRequired -= bytesRead;
index += bytesRead;
}
string actualSignature = BitConverter.ToString(signature);
if (actualSignature == expectedSignature)
return true;
else
return false;
}
}
public static bool CheckSignatureString(string data, int signatureSize, string expectedSignature) {
byte[] datas = Encoding.ASCII.GetBytes(data);
using (MemoryStream ms = new MemoryStream(datas)) {
if (ms.Length < signatureSize)
return false;
byte[] signature = new byte[signatureSize];
int bytesRequired = signatureSize;
int index = 0;
while (bytesRequired > 0) {
int bytesRead = ms.Read(signature, index, bytesRequired);
bytesRequired -= bytesRead;
index += bytesRead;
}
string actualSignature = BitConverter.ToString(signature);
if (actualSignature == expectedSignature)
return true;
else
return false;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With