gRPC messages are compressed by default, but you’re probably not getting the compression you think you are.

Let’s see it in action. We’ll set up a simple gRPC client and server using Python.

First, the Protobuf definition. This defines the structure of our messages.

syntax = "proto3";

package example;

message Message {
  string data = 1;
}

Now, let’s compile this into Python code.

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. message.proto

This gives us message_pb2.py and message_pb2_grpc.py.

Here’s a basic gRPC server:

import grpc
import time
import concurrent.futures
import message_pb2
import message_pb2_grpc

class GreeterServicer(message_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        return message_pb2.Message(data="Hello, " + request.data)

def serve():
    server = grpc.server(concurrent.futures.ThreadPoolExecutor(max_workers=10))
    message_pb2_grpc.add_GreeterServicer_to_server(GreeterServicer(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    print("Server started on port 50051")
    try:
        while True:
            time.sleep(86400)
    except KeyboardInterrupt:
        server.stop(0)

if __name__ == '__main__':
    serve()

And a client:

import grpc
import message_pb2
import message_pb2_grpc

def run():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = message_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(message_pb2.Message(data='World'))
        print("Received:", response.data)

if __name__ == '__main__':
    run()

If you run the client and server, you’ll see "Received: Hello, World". But what about compression? By default, gRPC uses zlib, which is essentially gzip. You can enable it by setting grpc.default_compression_algorithm on the server or grpc.enable_compression on the client.

On the server, you’d add this before server.start():

import grpc

# ... (rest of server code)

def serve():
    server = grpc.server(concurrent.futures.ThreadPoolExecutor(max_workers=10),
                         options=[('grpc.default_compression_algorithm', grpc.Compression.GZIP)])
    # ... (rest of serve function)

On the client, you’d pass this to grpc.insecure_channel:

import grpc

# ... (rest of client code)

def run():
    with grpc.insecure_channel('localhost:50051',
                                options=[('grpc.enable_compression', grpc.Compression.GZIP)]) as channel:
        # ... (rest of run function)

You can also use Snappy, which is often faster for smaller messages. Just replace grpc.Compression.GZIP with grpc.Compression.SNAPPY.

The critical lever you control is grpc.default_compression_algorithm on the server and grpc.enable_compression on the client. These are not just on/off switches; they accept specific grpc.Compression enum values like GZIP and SNAPPY. If the client and server don’t agree on a compression algorithm (or if one enables it and the other doesn’t), compression won’t happen, or worse, you’ll get errors if the client expects it and the server sends uncompressed data that looks like compressed data. The actual compression happens at the transport layer, transparently to your application logic. The gRPC library handles the encoding and decoding automatically based on the negotiated algorithm.

The one thing most people don’t realize is that the grpc.enable_compression option on the client is an opt-in. The server advertises its capabilities, and the client decides whether to use compression if the server supports it. If you only enable it on the server, the client might not compress its requests. For bidirectional compression, you need to configure it on both sides, and ensure they agree on the algorithm.

The next step is to explore how to dynamically negotiate compression based on message size.

Want structured learning?

Take the full Grpc course →