Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

gRPC compression in C#

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

  • Can see that traffic is not compressed.

Test #2: server turns off compression

  • Set context.WriteOptions = new WriteOptions(WriteFlags.NoCompress); on server side before returning response.
  • Can see that traffic is not compressed.

Test #3: server enables compression

  • Set context.ResponseTrailers.Add(new Metadata.Entry("grpc-internal-encoding-request", "gzip")); on server side before returning response.
  • Can see that traffic is not compressed.

Test #4: client enables compression

  • Set metadata headers when calling server: var headers = new Metadata {{new Metadata.Entry("grpc-internal-encoding-request", "gzip")}};
  • Client gRPC trace logs the following

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

  • I assume that this is attempting to compress the request not the response?
  • Can see that traffic is not compressed.




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 ChannelOptions. 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.

like image 509
Morgan Peat Avatar asked Feb 28 '18 14:02

Morgan Peat


1 Answers

Test #1 and Test #2 seem to behave as expected.

Test #3:

  • AFAIK grpc C# currently doesn't support enabling compression on the serverside
  • the header "grpc-internal-encoding-request" metadata header you are attempting to use on the server side should only be used on the client
  • you are adding the header to context.ResponseTrailers.Add() - response trailers are sent by the server AFTER all responses have been transmitted so this would have no chance of working anyway. If setting the compression was supported, you would've needed to add the header to context.ResponseHeaders().

Test #4:

  • I believe what you're doing (= setting "grpc-internal-encoding-request") is correct, it just seems that you're sending a request of size 0, so grpc decides that there's no point of compressing (there's a size threshold under which compression doesn't kick in).
like image 78
Jan Tattermusch Avatar answered Oct 10 '22 23:10

Jan Tattermusch