Available for day contractsFrom 21st September I have availability for day and half day contracts. Please contact for more information.

Contact →
mikepreston.org

gRPC

A high-performance, open-source universal RPC framework that uses Protocol Buffers for serialisation and HTTP/2 for transport.

gRPC Cheatsheet

A high-performance, open-source universal RPC framework that uses Protocol Buffers for serialisation and HTTP/2 for transport.

Overview

gRPC (gRPC Remote Procedure Calls) is a modern RPC framework developed by Google that enables efficient communication between distributed services. It supports multiple programming languages, bidirectional streaming, and provides built-in features for authentication, load balancing, and health checking.

gRPC ArchitectureGenerateGenerateHTTP/2Transport LayerHTTP/2 ProtocolTLS EncryptionProtocol BuffersSchema DefinitionClient StubServer StubgRPC ClientgRPC ServergRPC ArchitectureGenerateGenerateHTTP/2Transport LayerHTTP/2 ProtocolTLS EncryptionProtocol BuffersSchema DefinitionClient StubServer StubgRPC ClientgRPC Server

Protocol Buffers Schema Definition

Key Concepts

  • Protocol Buffers (Protobuf): Language-neutral, platform-neutral serialisation mechanism
  • Message Types: Define data structures with typed fields
  • Field Numbers: Unique identifiers for each field (1-15 use 1 byte, 16-2047 use 2 bytes)
  • Scalar Types: Basic types like int32, string, bool, bytes
  • Composite Types: Nested messages, enums, maps, repeated fields

Common Patterns

// Basic syntax declaration
syntax = "proto3";

// Package declaration
package myservice.v1;

// Import other proto files
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

// Options for code generation
option go_package = "github.com/myorg/myservice/v1";
option java_package = "com.myorg.myservice.v1";
option java_multiple_files = true;

// Enum definition
enum Status {
    STATUS_UNSPECIFIED = 0;  // Always have 0 as default
    STATUS_ACTIVE = 1;
    STATUS_INACTIVE = 2;
}

// Message definition
message User {
    // Scalar types
    string id = 1;
    string name = 2;
    int32 age = 3;
    bool is_verified = 4;

    // Enum field
    Status status = 5;

    // Repeated field (list/array)
    repeated string tags = 6;

    // Nested message
    Address address = 7;

    // Map field
    map<string, string> metadata = 8;

    // Timestamp (well-known type)
    google.protobuf.Timestamp created_at = 9;

    // Optional field (explicit presence)
    optional string nickname = 10;

    // Oneof (mutually exclusive fields)
    oneof contact {
        string email = 11;
        string phone = 12;
    }
}

// Nested message definition
message Address {
    string street = 1;
    string city = 2;
    string country = 3;
    string postcode = 4;
}

Examples

Compiling Protocol Buffers:

# Install protoc compiler
# Ubuntu/Debian
apt-get install -y protobuf-compiler

# macOS
brew install protobuf

# Generate Go code
protoc --go_out=. --go-grpc_out=. proto/*.proto

# Generate Python code
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. proto/*.proto

# Generate multiple languages
protoc --go_out=. --go-grpc_out=. \
       --java_out=. --grpc-java_out=. \
       --python_out=. --grpc_python_out=. \
       proto/*.proto

Service and Method Definitions

Key Concepts

  • Service: Collection of RPC methods
  • RPC Methods: Define request/response message types
  • Metadata: Key-value pairs sent with requests (like HTTP headers)
  • Deadlines: Time limits for RPC completion
  • Cancellation: Ability to terminate RPCs early

Common Patterns

syntax = "proto3";

package userservice.v1;

import "google/protobuf/empty.proto";

// Service definition
service UserService {
    // Unary RPC
    rpc GetUser(GetUserRequest) returns (GetUserResponse);

    // Server streaming RPC
    rpc ListUsers(ListUsersRequest) returns (stream User);

    // Client streaming RPC
    rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse);

    // Bidirectional streaming RPC
    rpc Chat(stream ChatMessage) returns (stream ChatMessage);

    // Empty request/response
    rpc Ping(google.protobuf.Empty) returns (google.protobuf.Empty);
}

// Request messages
message GetUserRequest {
    string user_id = 1;
}

message ListUsersRequest {
    int32 page_size = 1;
    string page_token = 2;
    string filter = 3;
}

message CreateUserRequest {
    string name = 1;
    string email = 2;
}

// Response messages
message GetUserResponse {
    User user = 1;
}

message CreateUsersResponse {
    int32 created_count = 1;
    repeated string user_ids = 2;
}

message ChatMessage {
    string user_id = 1;
    string content = 2;
    int64 timestamp = 3;
}

message User {
    string id = 1;
    string name = 2;
    string email = 3;
}

Unary vs Streaming RPCs

Key Concepts

Bidirectional StreamingStream Both WaysClientServerClient StreamingStream of RequestsSingle ResponseClientServerServer StreamingSingle RequestStream of ResponsesClientServerUnary RPCSingle RequestSingle ResponseClientServerBidirectional StreamingStream Both WaysClientServerClient StreamingStream of RequestsSingle ResponseClientServerServer StreamingSingle RequestStream of ResponsesClientServerUnary RPCSingle RequestSingle ResponseClientServer
Type Use Case Example
Unary Simple request/response Get user by ID
Server Streaming Large data sets, real-time updates List all users, stock prices
Client Streaming Uploading data, aggregation File upload, metrics collection
Bidirectional Real-time communication Chat, collaborative editing

Examples

Go Server Implementation:

package main

import (
    "context"
    "io"
    "log"

    pb "github.com/myorg/myservice/proto"
)

type server struct {
    pb.UnimplementedUserServiceServer
}

// Unary RPC
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
    // Check for deadline
    if deadline, ok := ctx.Deadline(); ok {
        log.Printf("Request deadline: %v", deadline)
    }

    user := &pb.User{
        Id:    req.UserId,
        Name:  "John Doe",
        Email: "john@example.com",
    }

    return &pb.GetUserResponse{User: user}, nil
}

// Server streaming RPC
func (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
    users := []pb.User{
        {Id: "1", Name: "Alice"},
        {Id: "2", Name: "Bob"},
        {Id: "3", Name: "Charlie"},
    }

    for _, user := range users {
        if err := stream.Send(&user); err != nil {
            return err
        }
    }

    return nil
}

// Client streaming RPC
func (s *server) CreateUsers(stream pb.UserService_CreateUsersServer) error {
    var userIds []string

    for {
        req, err := stream.Recv()
        if err == io.EOF {
            // Client finished sending
            return stream.SendAndClose(&pb.CreateUsersResponse{
                CreatedCount: int32(len(userIds)),
                UserIds:      userIds,
            })
        }
        if err != nil {
            return err
        }

        // Process each user
        userId := createUser(req)
        userIds = append(userIds, userId)
    }
}

// Bidirectional streaming RPC
func (s *server) Chat(stream pb.UserService_ChatServer) error {
    for {
        msg, err := stream.Recv()
        if err == io.EOF {
            return nil
        }
        if err != nil {
            return err
        }

        // Echo back with modification
        response := &pb.ChatMessage{
            UserId:    "server",
            Content:   "Received: " + msg.Content,
            Timestamp: time.Now().Unix(),
        }

        if err := stream.Send(response); err != nil {
            return err
        }
    }
}

Python Client Implementation:

import grpc
from proto import user_service_pb2 as pb
from proto import user_service_pb2_grpc as pb_grpc

def main():
    # Create channel with options
    channel = grpc.insecure_channel(
        'localhost:50051',
        options=[
            ('grpc.max_receive_message_length', 10 * 1024 * 1024),
        ]
    )

    stub = pb_grpc.UserServiceStub(channel)

    # Unary RPC with timeout
    try:
        response = stub.GetUser(
            pb.GetUserRequest(user_id="123"),
            timeout=5.0
        )
        print(f"User: {response.user.name}")
    except grpc.RpcError as e:
        print(f"RPC failed: {e.code()}: {e.details()}")

    # Server streaming RPC
    for user in stub.ListUsers(pb.ListUsersRequest(page_size=10)):
        print(f"User: {user.name}")

    # Client streaming RPC
    def user_generator():
        users = [
            pb.CreateUserRequest(name="Alice", email="alice@example.com"),
            pb.CreateUserRequest(name="Bob", email="bob@example.com"),
        ]
        for user in users:
            yield user

    response = stub.CreateUsers(user_generator())
    print(f"Created {response.created_count} users")

    # Bidirectional streaming RPC
    def message_generator():
        messages = ["Hello", "How are you?", "Goodbye"]
        for content in messages:
            yield pb.ChatMessage(user_id="client", content=content)

    for response in stub.Chat(message_generator()):
        print(f"Server: {response.content}")

if __name__ == '__main__':
    main()

Authentication and Security

Key Concepts

  • TLS/SSL: Transport layer encryption
  • Channel Credentials: Secure the connection (TLS certificates)
  • Call Credentials: Authenticate individual calls (tokens, JWT)
  • Interceptors: Middleware for adding auth to all calls
  • mTLS: Mutual TLS for client certificate authentication
AuthServiceServerClientAuthServiceServerClientTLS HandshakeToken AuthenticationAuthenticated RPCClientHelloServerHello + CertificateVerify CertificateGet JWT TokenJWT TokenRPC + JWT in MetadataValidate TokenResponseAuthServiceServerClientAuthServiceServerClientTLS HandshakeToken AuthenticationAuthenticated RPCClientHelloServerHello + CertificateVerify CertificateGet JWT TokenJWT TokenRPC + JWT in MetadataValidate TokenResponse

Examples

Server with TLS (Go):

package main

import (
    "crypto/tls"
    "crypto/x509"
    "io/ioutil"
    "log"
    "net"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

func main() {
    // Load server certificate and key
    cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
    if err != nil {
        log.Fatalf("Failed to load key pair: %v", err)
    }

    // Create TLS credentials
    creds := credentials.NewTLS(&tls.Config{
        Certificates: []tls.Certificate{cert},
        ClientAuth:   tls.NoClientCert, // or tls.RequireAndVerifyClientCert for mTLS
    })

    // Create server with TLS
    server := grpc.NewServer(grpc.Creds(creds))

    // Register services
    pb.RegisterUserServiceServer(server, &userServer{})

    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
    }

    log.Println("Server listening on :50051 with TLS")
    if err := server.Serve(lis); err != nil {
        log.Fatalf("Failed to serve: %v", err)
    }
}

// Server with mTLS (mutual TLS)
func createMTLSServer() *grpc.Server {
    // Load CA certificate
    caCert, _ := ioutil.ReadFile("ca.crt")
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)

    // Load server certificate
    cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")

    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        ClientAuth:   tls.RequireAndVerifyClientCert,
        ClientCAs:    caCertPool,
    }

    return grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConfig)))
}

Client with TLS and JWT (Go):

package main

import (
    "context"
    "log"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/metadata"
)

// JWT credentials
type jwtCredentials struct {
    token string
}

func (j jwtCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
    return map[string]string{
        "authorization": "Bearer " + j.token,
    }, nil
}

func (j jwtCredentials) RequireTransportSecurity() bool {
    return true // Require TLS
}

func main() {
    // Load TLS credentials
    creds, err := credentials.NewClientTLSFromFile("ca.crt", "")
    if err != nil {
        log.Fatalf("Failed to load credentials: %v", err)
    }

    // Create JWT credentials
    jwtCreds := jwtCredentials{token: "your-jwt-token"}

    // Connect with both TLS and JWT
    conn, err := grpc.Dial(
        "localhost:50051",
        grpc.WithTransportCredentials(creds),
        grpc.WithPerRPCCredentials(jwtCreds),
    )
    if err != nil {
        log.Fatalf("Failed to connect: %v", err)
    }
    defer conn.Close()

    // Alternatively, add metadata per-call
    ctx := metadata.AppendToOutgoingContext(
        context.Background(),
        "authorization", "Bearer your-token",
        "x-custom-header", "custom-value",
    )

    stub := pb.NewUserServiceClient(conn)
    response, err := stub.GetUser(ctx, &pb.GetUserRequest{UserId: "123"})
}

Server-side JWT Validation Interceptor (Go):

package main

import (
    "context"
    "strings"

    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/metadata"
    "google.golang.org/grpc/status"
)

func authInterceptor(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (interface{}, error) {
    // Skip auth for certain methods
    if info.FullMethod == "/health.v1.Health/Check" {
        return handler(ctx, req)
    }

    // Extract metadata
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, status.Error(codes.Unauthenticated, "missing metadata")
    }

    // Get authorization header
    authHeader := md.Get("authorization")
    if len(authHeader) == 0 {
        return nil, status.Error(codes.Unauthenticated, "missing authorization header")
    }

    // Extract and validate token
    token := strings.TrimPrefix(authHeader[0], "Bearer ")
    claims, err := validateJWT(token)
    if err != nil {
        return nil, status.Error(codes.Unauthenticated, "invalid token")
    }

    // Add claims to context
    newCtx := context.WithValue(ctx, "claims", claims)

    return handler(newCtx, req)
}

func main() {
    server := grpc.NewServer(
        grpc.UnaryInterceptor(authInterceptor),
        grpc.StreamInterceptor(streamAuthInterceptor),
    )
}

Load Balancing and Service Discovery

Key Concepts

  • Client-side Load Balancing: Client chooses which server to call
  • Proxy Load Balancing: External proxy (Envoy, nginx) distributes traffic
  • Look-aside Load Balancing: Separate load balancer service
  • Service Discovery: Dynamic server endpoint resolution
  • Health Checking: Monitor server availability
Service MeshClientSidecarServiceDiscoveryServer AServer BProxy LBClientProxyEnvoy/nginxServer AServer BServer CClient-side LBClientResolverLoad BalancerServer AServer BServer CService MeshClientSidecarServiceDiscoveryServer AServer BProxy LBClientProxyEnvoy/nginxServer AServer BServer CClient-side LBClientResolverLoad BalancerServer AServer BServer C

Common Patterns

Client-side Load Balancing (Go):

API note (grpc-go): grpc.Dial/grpc.DialContext and grpc.WithInsecure() are deprecated. Current code uses grpc.NewClient(target, opts...) with grpc.WithTransportCredentials(insecure.NewCredentials()) (from google.golang.org/grpc/credentials/insecure). NewClient defaults to the dns resolver and connects lazily. The older calls still compile throughout 1.x; treat the Dial/WithInsecure forms elsewhere in this sheet as legacy shorthand.

package main

import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
)

func main() {
    // DNS resolver with round-robin (one A record per backend)
    conn, err := grpc.NewClient(
        "dns:///myservice.example.com:50051",
        grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
        grpc.WithTransportCredentials(insecure.NewCredentials()),
    )

    // Multiple explicit addresses: grpc-go has no built-in "static" scheme
    // (only dns, passthrough, unix). Use a manual resolver and feed it the
    // address list, then dial its scheme:
    //   r := manual.NewBuilderWithScheme("static")
    //   r.InitialState(resolver.State{Addresses: []resolver.Address{
    //       {Addr: "localhost:50051"}, {Addr: "localhost:50052"},
    //   }})
    conn, err = grpc.NewClient(
        "static:///lb",
        grpc.WithResolvers(r),
        grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
        grpc.WithTransportCredentials(insecure.NewCredentials()),
    )
}

Custom Resolver for Service Discovery:

package main

import (
    "google.golang.org/grpc/resolver"
)

const scheme = "consul"

type consulResolver struct {
    target resolver.Target
    cc     resolver.ClientConn
}

func (r *consulResolver) ResolveNow(resolver.ResolveNowOptions) {
    // Query Consul for service endpoints
    endpoints := queryConsul(r.target.Endpoint())

    var addrs []resolver.Address
    for _, ep := range endpoints {
        addrs = append(addrs, resolver.Address{Addr: ep})
    }

    r.cc.UpdateState(resolver.State{Addresses: addrs})
}

func (r *consulResolver) Close() {}

type consulResolverBuilder struct{}

func (b *consulResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
    r := &consulResolver{target: target, cc: cc}
    r.ResolveNow(resolver.ResolveNowOptions{})
    return r, nil
}

func (b *consulResolverBuilder) Scheme() string {
    return scheme
}

func init() {
    resolver.Register(&consulResolverBuilder{})
}

// Usage
func main() {
    conn, _ := grpc.Dial(
        "consul:///myservice",
        grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
    )
}

Health Checking:

// Standard health checking protocol
syntax = "proto3";

package grpc.health.v1;

service Health {
    rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
    rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

message HealthCheckRequest {
    string service = 1;
}

message HealthCheckResponse {
    enum ServingStatus {
        UNKNOWN = 0;
        SERVING = 1;
        NOT_SERVING = 2;
        SERVICE_UNKNOWN = 3;
    }
    ServingStatus status = 1;
}

Implementing Health Check (Go):

package main

import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/health"
    healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

func main() {
    server := grpc.NewServer()

    // Create health server
    healthServer := health.NewServer()
    healthpb.RegisterHealthServer(server, healthServer)

    // Set service status
    healthServer.SetServingStatus("myservice", healthpb.HealthCheckResponse_SERVING)

    // Update status based on conditions
    go func() {
        if databaseConnected() {
            healthServer.SetServingStatus("myservice", healthpb.HealthCheckResponse_SERVING)
        } else {
            healthServer.SetServingStatus("myservice", healthpb.HealthCheckResponse_NOT_SERVING)
        }
    }()
}

Common Client and Server Libraries

Key Concepts

Language Server Library Client Library Package Manager
Go google.golang.org/grpc Same go mod
Python grpcio grpcio pip
Java io.grpc:grpc-netty io.grpc:grpc-stub Maven/Gradle
Node.js @grpc/grpc-js @grpc/grpc-js npm
C++ grpc++ grpc++ vcpkg/cmake
Rust tonic tonic cargo
C# Grpc.AspNetCore Grpc.Net.Client NuGet

Examples

Go Server Setup:

package main

import (
    "log"
    "net"

    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"

    pb "github.com/myorg/myservice/proto"
)

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
    }

    // Server options
    opts := []grpc.ServerOption{
        grpc.MaxRecvMsgSize(10 * 1024 * 1024), // 10MB
        grpc.MaxSendMsgSize(10 * 1024 * 1024),
    }

    server := grpc.NewServer(opts...)

    // Register services
    pb.RegisterUserServiceServer(server, &userServer{})

    // Enable reflection for debugging tools like grpcurl
    reflection.Register(server)

    log.Println("Server listening on :50051")
    if err := server.Serve(lis); err != nil {
        log.Fatalf("Failed to serve: %v", err)
    }
}

Python Server Setup:

import grpc
from concurrent import futures
from proto import user_service_pb2 as pb
from proto import user_service_pb2_grpc as pb_grpc

class UserServicer(pb_grpc.UserServiceServicer):
    def GetUser(self, request, context):
        # Implementation
        pass

def serve():
    server = grpc.server(
        futures.ThreadPoolExecutor(max_workers=10),
        options=[
            ('grpc.max_receive_message_length', 10 * 1024 * 1024),
        ]
    )

    pb_grpc.add_UserServiceServicer_to_server(UserServicer(), server)

    # Enable reflection
    from grpc_reflection.v1alpha import reflection
    SERVICE_NAMES = (
        pb.DESCRIPTOR.services_by_name['UserService'].full_name,
        reflection.SERVICE_NAME,
    )
    reflection.enable_server_reflection(SERVICE_NAMES, server)

    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()

if __name__ == '__main__':
    serve()

Node.js Client:

const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

// Load proto file
const packageDefinition = protoLoader.loadSync('proto/user_service.proto', {
    keepCase: true,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true
});

const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
const userService = protoDescriptor.userservice.v1;

// Create client
const client = new userService.UserService(
    'localhost:50051',
    grpc.credentials.createInsecure()
);

// Unary call
client.getUser({ user_id: '123' }, (err, response) => {
    if (err) {
        console.error('Error:', err);
        return;
    }
    console.log('User:', response.user);
});

// With async/await (using promisify)
const { promisify } = require('util');
const getUser = promisify(client.getUser).bind(client);

async function main() {
    try {
        const response = await getUser({ user_id: '123' });
        console.log('User:', response.user);
    } catch (err) {
        console.error('Error:', err);
    }
}

Java Client (with Spring Boot):

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import com.myorg.userservice.v1.UserServiceGrpc;
import com.myorg.userservice.v1.GetUserRequest;
import com.myorg.userservice.v1.GetUserResponse;

public class UserClient {
    private final ManagedChannel channel;
    private final UserServiceGrpc.UserServiceBlockingStub blockingStub;

    public UserClient(String host, int port) {
        this.channel = ManagedChannelBuilder.forAddress(host, port)
            .usePlaintext()
            .build();
        this.blockingStub = UserServiceGrpc.newBlockingStub(channel);
    }

    public GetUserResponse getUser(String userId) {
        GetUserRequest request = GetUserRequest.newBuilder()
            .setUserId(userId)
            .build();
        return blockingStub.getUser(request);
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }
}

Performance Optimisation Techniques

Key Concepts

  • Connection Pooling: Reuse HTTP/2 connections
  • Message Compression: Reduce bandwidth usage
  • Keepalive: Maintain connection health
  • Flow Control: Manage streaming backpressure
  • Deadline Propagation: Prevent cascading timeouts

Common Patterns

Connection and Channel Optimisation:

package main

import (
    "time"

    "google.golang.org/grpc"
    "google.golang.org/grpc/keepalive"
)

func createOptimisedClient() (*grpc.ClientConn, error) {
    return grpc.Dial(
        "localhost:50051",
        grpc.WithInsecure(),

        // Connection pooling (HTTP/2 multiplexing)
        grpc.WithInitialWindowSize(1 << 20),     // 1MB
        grpc.WithInitialConnWindowSize(1 << 20), // 1MB

        // Keepalive settings
        grpc.WithKeepaliveParams(keepalive.ClientParameters{
            Time:                10 * time.Second, // Ping server every 10s
            Timeout:             3 * time.Second,  // Wait 3s for ping ack
            PermitWithoutStream: true,             // Ping even without active streams
        }),

        // Message size limits
        grpc.WithDefaultCallOptions(
            grpc.MaxCallRecvMsgSize(10*1024*1024), // 10MB
            grpc.MaxCallSendMsgSize(10*1024*1024),
        ),

        // Compression
        grpc.WithDefaultCallOptions(
            grpc.UseCompressor("gzip"),
        ),
    )
}

func createOptimisedServer() *grpc.Server {
    return grpc.NewServer(
        // Keepalive enforcement
        grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
            MinTime:             5 * time.Second, // Minimum ping interval
            PermitWithoutStream: true,
        }),

        // Server keepalive
        grpc.KeepaliveParams(keepalive.ServerParameters{
            MaxConnectionIdle:     15 * time.Minute,
            MaxConnectionAge:      30 * time.Minute,
            MaxConnectionAgeGrace: 5 * time.Second,
            Time:                  5 * time.Second,
            Timeout:               1 * time.Second,
        }),

        // Concurrent streams per connection
        grpc.MaxConcurrentStreams(100),

        // Message sizes
        grpc.MaxRecvMsgSize(10 * 1024 * 1024),
        grpc.MaxSendMsgSize(10 * 1024 * 1024),
    )
}

Compression:

import (
    "google.golang.org/grpc"
    _ "google.golang.org/grpc/encoding/gzip" // Register gzip compressor
)

// Client-side compression
conn, _ := grpc.Dial(
    "localhost:50051",
    grpc.WithDefaultCallOptions(grpc.UseCompressor("gzip")),
)

// Per-call compression
response, err := client.GetUser(
    ctx,
    request,
    grpc.UseCompressor("gzip"),
)

// Server automatically decompresses and can respond compressed

Deadline Propagation:

import (
    "context"
    "time"
)

func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
    // Check remaining time
    deadline, ok := ctx.Deadline()
    if ok {
        remaining := time.Until(deadline)
        if remaining < 100*time.Millisecond {
            return nil, status.Error(codes.DeadlineExceeded, "insufficient time")
        }
    }

    // Propagate deadline to downstream calls
    user, err := s.userRepo.GetUser(ctx, req.UserId)
    if err != nil {
        return nil, err
    }

    return &pb.GetUserResponse{User: user}, nil
}

// Client setting deadline
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

response, err := client.GetUser(ctx, request)

Batching and Streaming for Performance:

// Instead of multiple unary calls
for _, id := range userIds {
    user, _ := client.GetUser(ctx, &pb.GetUserRequest{UserId: id})
}

// Use batch endpoint or streaming
response, err := client.GetUsers(ctx, &pb.GetUsersRequest{UserIds: userIds})

// Or use client streaming for uploads
stream, _ := client.CreateUsers(ctx)
for _, user := range users {
    stream.Send(&pb.CreateUserRequest{Name: user.Name})
}
response, _ := stream.CloseAndRecv()

Connection Reuse:

// BAD: Creating new connection per request
func getUser(userId string) (*pb.User, error) {
    conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
    defer conn.Close()

    client := pb.NewUserServiceClient(conn)
    return client.GetUser(ctx, &pb.GetUserRequest{UserId: userId})
}

// GOOD: Reuse connection
type UserClient struct {
    conn   *grpc.ClientConn
    client pb.UserServiceClient
}

func NewUserClient(addr string) (*UserClient, error) {
    conn, err := grpc.Dial(addr, grpc.WithInsecure())
    if err != nil {
        return nil, err
    }

    return &UserClient{
        conn:   conn,
        client: pb.NewUserServiceClient(conn),
    }, nil
}

func (c *UserClient) GetUser(ctx context.Context, userId string) (*pb.User, error) {
    resp, err := c.client.GetUser(ctx, &pb.GetUserRequest{UserId: userId})
    if err != nil {
        return nil, err
    }
    return resp.User, nil
}

Quick Reference

Category Command/Pattern Description
Proto Compilation protoc --go_out=. --go-grpc_out=. *.proto Generate Go gRPC code
Proto Compilation python -m grpc_tools.protoc --python_out=. --grpc_python_out=. Generate Python gRPC code
Testing grpcurl -plaintext localhost:50051 list List available services
Testing grpcurl -plaintext -d '{"user_id":"123"}' localhost:50051 userservice.v1.UserService/GetUser Call unary RPC
Testing grpc_health_probe -addr=localhost:50051 Check server health
Unary RPC rpc Method(Request) returns (Response) Single request/response
Server Stream rpc Method(Request) returns (stream Response) Server streams multiple responses
Client Stream rpc Method(stream Request) returns (Response) Client streams multiple requests
Bidi Stream rpc Method(stream Request) returns (stream Response) Bidirectional streaming
Metadata metadata.AppendToOutgoingContext(ctx, "key", "value") Add call metadata (Go)
Deadline context.WithTimeout(ctx, 5*time.Second) Set RPC deadline (Go)
Error Handling status.Error(codes.NotFound, "user not found") Return gRPC error
Compression grpc.UseCompressor("gzip") Enable gzip compression

Common Issues and Solutions

Connection Issues

Issue Cause Solution
connection refused Server not running or wrong port Verify server is running and port matches
context deadline exceeded Request timeout Increase deadline or optimise server
transport is closing Connection dropped Enable keepalive, check network
too many pings Client pinging too frequently Adjust keepalive parameters

Serialisation Issues

Issue Cause Solution
proto: wrong wireType Proto schema mismatch Regenerate code from latest proto
field number conflict Duplicate field numbers Ensure unique field numbers
unknown field Missing field in proto Add field or use proto3 defaults

Performance Issues

Issue Cause Solution
High latency New connections per request Reuse connections, implement pooling
Memory spikes Large messages Use streaming, increase limits carefully
Connection exhaustion Too many concurrent streams Limit concurrent streams, use backpressure

Authentication Issues

// Issue: "transport: authentication handshake failed"
// Solution: Ensure TLS certificates are valid and properly configured

// Check certificate validity
openssl x509 -in server.crt -text -noout

// Verify certificate chain
openssl verify -CAfile ca.crt server.crt

// Issue: "metadata not found"
// Solution: Check interceptor order and metadata key names

md, ok := metadata.FromIncomingContext(ctx)
if !ok {
    // Handle missing metadata
}
authHeaders := md.Get("authorization") // Note: lowercase keys

Debugging Tips

# Enable gRPC debug logging
export GRPC_VERBOSITY=DEBUG
export GRPC_TRACE=all

# Use grpcurl for testing
grpcurl -plaintext localhost:50051 list
grpcurl -plaintext localhost:50051 describe userservice.v1.UserService

# Check with grpc_cli
grpc_cli ls localhost:50051
grpc_cli call localhost:50051 userservice.v1.UserService.GetUser "user_id: '123'"

# Trace with OpenTelemetry
import (
    "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
)

conn, _ := grpc.Dial(
    addr,
    grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
    grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
)

Related Topics

  • Protocol Buffers: Deep dive into protobuf schema design, best practices, and evolution
  • Service Mesh (Istio/Linkerd): Advanced traffic management, observability, and security for gRPC services
  • HTTP/2 Protocol: Understanding the underlying transport protocol and its benefits
  • API Gateway Patterns: Kong, Envoy, and other gateways for gRPC traffic management
  • Distributed Tracing: OpenTelemetry, Jaeger, and Zipkin for tracing gRPC calls across services
  • Message Queues (Kafka/RabbitMQ): Complementary asynchronous messaging patterns for event-driven architectures