From fc90a11404118264e3a5ec874b2ce344a9c77216 Mon Sep 17 00:00:00 2001 From: "kshitij.suri" Date: Fri, 3 Apr 2026 19:49:02 +0000 Subject: [PATCH] Remove redundant CanonicalizeHeaderKey from metadata iteration paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: metadataToTransportRequest and getApplicationHeaders both call transport.CanonicalizeHeaderKey (strings.ToLower) on every key while iterating gRPC metadata.MD. These keys are already guaranteed to be lowercase, making the calls redundant CPU work on every inbound and response path. Fix: Remove the CanonicalizeHeaderKey call from both functions. Also normalize a test case in TestGetApplicationHeaders that used a mixed-case metadata key via a raw metadata.MD literal, which no longer reflects a realistic scenario. Safety — three independent guarantees that metadata keys are lowercase: 1. HTTP/2 spec (RFC 7540 s8.1.2): header field names MUST be converted to lowercase prior to their encoding in HTTP/2. https://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2 2. grpc-go PR #4416 (merged v1.39, June 2021): FromIncomingContext now explicitly lowercases all keys. https://github.com/grpc/grpc-go/pull/4416 3. yarpc-go uses grpc-go v1.67.3 (go.mod), well past v1.39. Call sites: - metadataToTransportRequest: called from handler.go via metadata.FromIncomingContext (server inbound path) - getApplicationHeaders: called from outbound.go with response trailer metadata (client response path) Impact: Eliminates one strings.ToLower scan per metadata entry on both the server inbound and client response paths. Benchmark (count=6, AMD EPYC 9B45): MetadataToTransportRequest: 984.8 ns/op → 907.7 ns/op (-7.83%, p=0.002) GetApplicationHeaders: 992.0 ns/op → 895.9 ns/op (-9.69%, p=0.002) Rebase note (2026-07-26): rebased onto current main; resolved a positional conflict in transport/grpc/headers_test.go where main's newly-added BenchmarkIsReserved and this PR's benchmarks were both appended at the same spot — kept both functions. Made-with: Cursor --- transport/grpc/headers.go | 5 ++-- transport/grpc/headers_test.go | 44 +++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/transport/grpc/headers.go b/transport/grpc/headers.go index 4b1436f819..da514de239 100644 --- a/transport/grpc/headers.go +++ b/transport/grpc/headers.go @@ -145,8 +145,7 @@ func metadataToTransportRequest(md metadata.MD) (*transport.Request, error) { default: return nil, yarpcerrors.InvalidArgumentErrorf("header has more than one value: %s:%v", header, values) } - header = transport.CanonicalizeHeaderKey(header) - // skip routing header + // gRPC metadata keys are already lowercase. if routingHeaders[header] { continue } @@ -221,7 +220,7 @@ func getApplicationHeaders(md metadata.MD) (transport.Headers, error) { } headers := transport.NewHeadersWithCapacity(md.Len()) for header, values := range md { - header = transport.CanonicalizeHeaderKey(header) + // gRPC metadata keys are already lowercase. if isReserved(header) { continue } diff --git a/transport/grpc/headers_test.go b/transport/grpc/headers_test.go index 9fb3c6e3a5..f244a305d9 100644 --- a/transport/grpc/headers_test.go +++ b/transport/grpc/headers_test.go @@ -253,7 +253,7 @@ func TestGetApplicationHeaders(t *testing.T) { "rpc-service": []string{"foo"}, // reserved header "test-header-empty": []string{}, // no value "test-header-valid-1": []string{"test-value-1"}, - "test-Header-Valid-2": []string{"test-value-2"}, + "test-header-valid-2": []string{"test-value-2"}, }, wantHeaders: map[string]string{ "test-header-valid-1": "test-value-1", @@ -404,3 +404,45 @@ func BenchmarkIsReserved(b *testing.B) { }) } } + +func BenchmarkMetadataToTransportRequest(b *testing.B) { + md := metadata.Pairs( + CallerHeader, "example-caller", + ServiceHeader, "example-service", + ShardKeyHeader, "example-shard-key", + RoutingKeyHeader, "example-routing-key", + RoutingDelegateHeader, "example-routing-delegate", + EncodingHeader, "raw", + CallerProcedureHeader, "example-caller-procedure", + "x-uber-source", "service-a", + "x-request-id", "abc-123", + "x-trace-id", "trace-456", + "x-custom-1", "val1", + "x-custom-2", "val2", + ) + + b.ResetTimer() + for range b.N { + _, _ = metadataToTransportRequest(md) + } +} + +func BenchmarkGetApplicationHeaders(b *testing.B) { + md := metadata.MD{ + "rpc-caller": []string{"example-caller"}, + "rpc-service": []string{"example-service"}, + "rpc-encoding": []string{"raw"}, + "x-uber-source": []string{"service-a"}, + "x-request-id": []string{"abc-123"}, + "x-trace-id": []string{"trace-456"}, + "x-custom-1": []string{"val1"}, + "x-custom-2": []string{"val2"}, + "x-custom-3": []string{"val3"}, + "x-custom-4": []string{"val4"}, + } + + b.ResetTimer() + for range b.N { + _, _ = getApplicationHeaders(md) + } +}