High-Throughput Cloud Spanner with Custom Fast Codec & Memory Pooling

This sample demonstrates how to build a specialized, zero-reflection custom gRPC codec and memory pool tailored specifically for Google Cloud Spanner streaming reads without requiring any third-party compiler plugins or code-generation tools.


Why a Spanner-Specific Codec?

In high-throughput Cloud Spanner workloads (such as analytical streaming queries or large ETL scans via ExecuteStreamingSql), the vast majority of network payload bytes consist of repeated google.protobuf.Value items inside PartialResultSet.values.

The standard Go Protobuf runtime (google.golang.org/protobuf) uses table-driven reflection to parse each message. By writing a lightweight, specialized decoder for PartialResultSet and Value:

  1. Zero Reflection on Hot Paths: *sppb.PartialResultSet and *structpb.Value are parsed directly from wire bytes using inline varint and fixed64 bit-shifts.
  2. Deep Memory Recycling: PartialResultSet chunk containers and inner Value objects (strings, numbers, booleans, nulls) are recycled using sync.Pool.
  3. Zero External Tooling: Works out of the box in pure standard Go code—no protoc plugins or code generation scripts required.
  4. Safe Fallback: All non-streaming RPC messages (sessions, commit requests, metadata) automatically fall back to standard proto.Unmarshal.

How It Works

1. The Custom gRPC Codec (SpannerFastCodec)

Implements google.golang.org/grpc/encoding.Codec:

type SpannerFastCodec struct{}

func (SpannerFastCodec) Name() string { return "proto" }

func (SpannerFastCodec) Marshal(value any) ([]byte, error) {
    return proto.Marshal(value.(proto.Message))
}

func (SpannerFastCodec) Unmarshal(data []byte, value any) error {
    if partialResultSet, ok := value.(*sppb.PartialResultSet); ok {
        return FastUnmarshalPartialResultSet(data, partialResultSet)
    }
    return proto.Unmarshal(data, value.(proto.Message))
}

2. Memory Pooling (CustomPartialResultSetPool)

Implements spanner.PartialResultSetPool:

type CustomPartialResultSetPool struct{}

func (p *CustomPartialResultSetPool) Get() *sppb.PartialResultSet {
    return partialResultSetPool.Get().(*sppb.PartialResultSet)
}

func (p *CustomPartialResultSetPool) Put(partialResultSet *sppb.PartialResultSet) {
    // Recycles nested *structpb.Value objects and resets the chunk slice
    ...
}

3. Creating the Client

func NewCustomOptimizedSpannerClient(ctx context.Context, database string, opts ...option.ClientOption) (*spanner.Client, error) {
    codecOption := option.WithGRPCDialOption(
        grpc.WithDefaultCallOptions(grpc.ForceCodec(SpannerFastCodec{})),
    )
    allOpts := append([]option.ClientOption{codecOption}, opts...)
    return spanner.NewClientWithConfig(ctx, database,
        spanner.ClientConfig{
            SessionPoolConfig:    spanner.DefaultSessionPoolConfig,
            PartialResultSetPool: &CustomPartialResultSetPool{},
        },
        allOpts...,
    )
}

Running Tests and Benchmarks

Run Automated Tests

go test -v ./...

Run End-to-End Client Benchmarks

go test -bench=BenchmarkSpannerClient -benchmem -benchtime=2s

Benchmark Results (1,000 rows / 4,000 columns per query):

BenchmarkSpannerClient/Standard_Protobuf-8           385   6203632 ns/op   5892991 B/op   115213 allocs/op
BenchmarkSpannerClient/CustomCodec_With_Pooling-8    415   5777385 ns/op   5571052 B/op   103241 allocs/op