or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

admin.mdadvanced.mdclient-server.mdcredentials-security.mderrors-status.mdhealth.mdindex.mdinterceptors.mdload-balancing.mdmetadata-context.mdname-resolution.mdobservability.mdreflection.mdstreaming.mdtesting.mdxds.md
tile.json

advanced.mddocs/

Advanced Features

This document covers advanced gRPC-Go features including compression, encoding, keepalive, backoff, connection management, and other advanced topics.

Compression

Built-in Compressors

import "google.golang.org/grpc/encoding/gzip"

// gzip compression is built-in and automatically registered
// No explicit import needed for basic usage

Using Compression

import "google.golang.org/grpc"

// Client: request compression
conn, err := grpc.NewClient("localhost:50051",
    grpc.WithDefaultCallOptions(grpc.UseCompressor("gzip")),
    grpc.WithTransportCredentials(creds))

// Server: enable compression
server := grpc.NewServer()

// Per-call compression
ctx := context.Background()
resp, err := client.MyMethod(ctx, req, grpc.UseCompressor("gzip"))

Server-Side Compression Control

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

// Set compressor for response
func (s *server) MyMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
    // Set send compressor
    grpc.SetSendCompressor(ctx, "gzip")

    // Check client-supported compressors
    compressors, err := grpc.ClientSupportedCompressors(ctx)
    if err == nil {
        log.Printf("Client supports: %v", compressors)
    }

    return &pb.Response{}, nil
}

Custom Compressor

import "google.golang.org/grpc/encoding"

type myCompressor struct{}

func (c *myCompressor) Compress(w io.Writer) (io.WriteCloser, error) {
    // Return compressed writer
    return nil, nil
}

func (c *myCompressor) Decompress(r io.Reader) (io.Reader, error) {
    // Return decompressed reader
    return nil, nil
}

func (c *myCompressor) Name() string {
    return "mycompressor"
}

// Register compressor
func init() {
    encoding.RegisterCompressor(&myCompressor{})
}

Encoding / Codec

Custom Codec

import "google.golang.org/grpc/encoding"

type myCodec struct{}

func (c *myCodec) Marshal(v any) ([]byte, error) {
    // Custom marshaling
    return nil, nil
}

func (c *myCodec) Unmarshal(data []byte, v any) error {
    // Custom unmarshaling
    return nil
}

func (c *myCodec) Name() string {
    return "mycodec"
}

// Register codec
func init() {
    encoding.RegisterCodec(&myCodec{})
}

// Use custom codec
conn, err := grpc.NewClient("localhost:50051",
    grpc.WithDefaultCallOptions(grpc.CallContentSubtype("mycodec")),
    grpc.WithTransportCredentials(creds))

Keepalive

Client Keepalive

import (
    "time"
    "google.golang.org/grpc"
    "google.golang.org/grpc/keepalive"
)

kacp := keepalive.ClientParameters{
    Time:                10 * time.Second, // Send pings every 10s if no activity
    Timeout:             3 * time.Second,  // Wait 3s for ping ack before considering connection dead
    PermitWithoutStream: true,             // Send pings even without active streams
}

conn, err := grpc.NewClient("localhost:50051",
    grpc.WithKeepaliveParams(kacp),
    grpc.WithTransportCredentials(creds))

Server Keepalive

import (
    "time"
    "google.golang.org/grpc"
    "google.golang.org/grpc/keepalive"
)

kaep := keepalive.EnforcementPolicy{
    MinTime:             5 * time.Second, // Min time client should wait between pings
    PermitWithoutStream: true,            // Allow pings even without active streams
}

kasp := keepalive.ServerParameters{
    Time:    10 * time.Second, // Ping client if no activity for 10s
    Timeout: 3 * time.Second,  // Wait 3s for ping ack
}

server := grpc.NewServer(
    grpc.KeepaliveEnforcementPolicy(kaep),
    grpc.KeepaliveParams(kasp))

Connection Backoff

Backoff Configuration

import (
    "time"
    "google.golang.org/grpc"
    "google.golang.org/grpc/backoff"
)

backoffConfig := backoff.Config{
    BaseDelay:  1.0 * time.Second, // Initial backoff
    Multiplier: 1.6,                // Backoff multiplier
    Jitter:     0.2,                // Jitter factor
    MaxDelay:   120 * time.Second,  // Max backoff
}

conn, err := grpc.NewClient("localhost:50051",
    grpc.WithConnectParams(grpc.ConnectParams{
        Backoff: backoffConfig,
    }),
    grpc.WithTransportCredentials(creds))

Min Connect Timeout

conn, err := grpc.NewClient("localhost:50051",
    grpc.WithConnectParams(grpc.ConnectParams{
        MinConnectTimeout: 5 * time.Second,
    }),
    grpc.WithTransportCredentials(creds))

Connection Management

Idle Timeout

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

// Experimental: Close idle connections after timeout
conn, err := grpc.NewClient("localhost:50051",
    grpc.WithIdleTimeout(5 * time.Minute),
    grpc.WithTransportCredentials(creds))

Manual Connection Control

// Get connection state
state := conn.GetState()

// Wait for state change
conn.WaitForStateChange(ctx, state)

// Trigger connection attempt (experimental)
conn.Connect()

// Reset connection backoff (experimental)
conn.ResetConnectBackoff()

Message Size Limits

Client-Side

import "google.golang.org/grpc"

conn, err := grpc.NewClient("localhost:50051",
    grpc.WithDefaultCallOptions(
        grpc.MaxCallRecvMsgSize(10 * 1024 * 1024), // 10 MB
        grpc.MaxCallSendMsgSize(10 * 1024 * 1024), // 10 MB
    ),
    grpc.WithTransportCredentials(creds))

Server-Side

import "google.golang.org/grpc"

server := grpc.NewServer(
    grpc.MaxRecvMsgSize(10 * 1024 * 1024), // 10 MB
    grpc.MaxSendMsgSize(10 * 1024 * 1024)) // 10 MB

Flow Control

Window Sizes

import "google.golang.org/grpc"

// Client
conn, err := grpc.NewClient("localhost:50051",
    grpc.WithInitialWindowSize(65535),
    grpc.WithInitialConnWindowSize(65535 * 16),
    grpc.WithTransportCredentials(creds))

// Server
server := grpc.NewServer(
    grpc.InitialWindowSize(65535),
    grpc.InitialConnWindowSize(65535 * 16))

Buffer Sizes

Read/Write Buffers

import "google.golang.org/grpc"

// Client
conn, err := grpc.NewClient("localhost:50051",
    grpc.WithReadBufferSize(32 * 1024),
    grpc.WithWriteBufferSize(32 * 1024),
    grpc.WithTransportCredentials(creds))

// Server
server := grpc.NewServer(
    grpc.ReadBufferSize(32 * 1024),
    grpc.WriteBufferSize(32 * 1024))

Connection Options

Max Concurrent Streams

import "google.golang.org/grpc"

server := grpc.NewServer(
    grpc.MaxConcurrentStreams(100))

Max Header List Size

import "google.golang.org/grpc"

// Client
conn, err := grpc.NewClient("localhost:50051",
    grpc.WithMaxHeaderListSize(16 * 1024),
    grpc.WithTransportCredentials(creds))

// Server
server := grpc.NewServer(
    grpc.MaxHeaderListSize(16 * 1024))

Custom Dialer

import (
    "context"
    "net"
    "time"
    "google.golang.org/grpc"
)

customDialer := func(ctx context.Context, addr string) (net.Conn, error) {
    // Custom dialing logic
    d := net.Dialer{Timeout: 5 * time.Second}
    return d.DialContext(ctx, "tcp", addr)
}

conn, err := grpc.NewClient("localhost:50051",
    grpc.WithContextDialer(customDialer),
    grpc.WithTransportCredentials(insecure.NewCredentials()))

Tap

The tap package allows intercepting connections before RPC processing.

import (
    "context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/tap"
)

tapHandle := func(ctx context.Context, info *tap.Info) (context.Context, error) {
    // Check request before creating stream
    // Can reject connection by returning error
    if !isAllowed(info.FullMethodName) {
        return nil, status.Error(codes.PermissionDenied, "method not allowed")
    }
    return ctx, nil
}

server := grpc.NewServer(
    grpc.InTapHandle(tapHandle))

Authority Override

import "google.golang.org/grpc"

// Override authority for specific call
resp, err := client.MyMethod(ctx, req,
    grpc.CallAuthority("custom-authority.example.com"))

// Set default authority for connection
conn, err := grpc.NewClient("localhost:50051",
    grpc.WithAuthority("custom-authority.example.com"),
    grpc.WithTransportCredentials(creds))

Wait for Ready

import "google.golang.org/grpc"

// Wait for connection to be ready before sending RPC
resp, err := client.MyMethod(ctx, req, grpc.WaitForReady(true))

// Fail immediately if not connected
resp, err := client.MyMethod(ctx, req, grpc.WaitForReady(false))

Best Practices

  1. Compression: Enable for large messages, consider CPU cost
  2. Keepalive: Configure to detect dead connections promptly
  3. Backoff: Use exponential backoff for reconnections
  4. Message limits: Set appropriate limits for your use case
  5. Flow control: Tune window sizes for high-throughput scenarios
  6. Idle timeout: Close unused connections to save resources
  7. Wait for ready: Use for retry logic, avoid for user-facing requests