Is it possible to control response message compression in gRPC when using C#?
This question received a comment but no answer. The comment directs to gRPC issue #10902 which says:
The functionality is there, but you need to unable [enable?] by setting appropriate channel option or by setting a metadata entry in your RPC.
The test class linked there shows a few possibilities:
new WriteOptions(WriteFlags.NoCompress)
but that has no effect for me: calls over a WAN take the same time regardless of this setting.
var compressionMetadata = new Metadata
{
{ new Metadata.Entry(Metadata.CompressionRequestAlgorithmMetadataKey, "gzip") }
};
Again, tests show no change in call time when this setting is changed. (The CompressionRequestAlgorithmMetadataKey
constant is internal, I decompiled to get the string value).
There is a tantalising CompressionLevel
enum in the C# github repo but I can't find any usage of it.
gRPC issue #4170 (Implement compression support in C#) is closed but says
It can be done in C#, but it's definitely not user friendly
Is anyone able to help me with an example on exactly how to do this? I don't mind if it's not user friendly, I'd just like to be able to do it! I'm sending some large (~8Mb) responses over the wire and I'd like to have some control over them. While I could just compress the data before sending it would be nice to allow the client to have some control over the method / level of compression used.
(I would post some code, but I'm just using test code from the C# sample code with a large hard-coded string)
tl;dr I can't work out how to control compression settings with C# gRPC. All likely-looking settings have no effect on the time taken to send a response over the wire.
Edit
After Jon Skeet's comment about looking at wire traffic I ran a new test where the response was the string The quick brown fox jumps over the lazy dog
repeated 5000 times. I turned on gRPC tracing via
Environment.SetEnvironmentVariable("GRPC_TRACE", "compression");
Environment.SetEnvironmentVariable("GRPC_VERBOSITY", "DEBUG");
GrpcEnvironment.SetLogger(new ConsoleLogger());
... on both client and server. I captured network traffic using Wireshark, decoding HTTP2 traffic on my gRPC server's port.
Test #1: no specific code to control compression
Test #2: server turns off compression
context.WriteOptions = new WriteOptions(WriteFlags.NoCompress);
on server side before returning response. Test #3: server enables compression
context.ResponseTrailers.Add(new Metadata.Entry("grpc-internal-encoding-request", "gzip"));
on server side before returning response. Test #4: client enables compression
var headers = new Metadata {{new Metadata.Entry("grpc-internal-encoding-request", "gzip")}};
D0228 16:53:21.566026 0 C:\jenkins\workspace\gRPC_build_artifacts\platform\windows\workspace_csharp_ext_windows_x64\src\core\ext\filters\http\message_compress\message_compress_filter.cc:262: Algorithm 'gzip' enabled but decided not to compress. Input size: 0
Edit #2
Jan Tattermusch got it spot on. Adding this code in my server-side method
var headers = new Metadata{new Metadata.Entry("grpc-internal-encoding-request", "gzip")};
await context.WriteResponseHeadersAsync(headers);
...did the trick! Wireshark showed that the response was compressed and the gRPC logs displayed
Compressed[gzip] 225002 bytes vs. 742 bytes (99.67% savings)
Brilliant! Sadly it led to my next problem: the overhead of compression led to this method call being slower than uncompressed data!
More googling around led me to find better options: when creating the Server
object you can pass a collection of ChannelOption
s. I set this as follows:
var options = new[] {new ChannelOption("grpc.default_compression_level", (int) CompressionLevel.Low)};
This sets compression to a much better level (for the data I am sending here) and has the side-effect of enabling gzip compression as the default for all calls. Now each method must explicitly turn off compression (via the WriteOptions
in my test #2).
Interestingly, attempting to override the default compression via the grpc-internal-encoding-request
is now exceedingly slow, I'm guessing due to exceptions being raised:
src\core\lib\surface\call.cc:1018: prepare_application_metadata: {"created":"@1520008670.738000000","description":"Unallowed duplicate metadata","file":"C:\jenkins\workspace\gRPC_build_artifacts\platform\windows\workspace_csharp_ext_windows_x64\src\core\lib\transport\metadata_batch.cc","file_line":111,"key":"grpc-internal-encoding-request","value":"gzip"}
It means I can't chop and change the compression algorithm or level on a per-call basis but given the performance benefits from just having compression turned on, I'm a happy man.
Test #1 and Test #2 seem to behave as expected.
Test #3:
Test #4:
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