Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/go/cmd/http-relay-client/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ func DefaultClientConfig() ClientConfig {
NumPendingRequests: 1,
MaxIdleConnsPerHost: 100,

MaxChunkSize: 50 * 1024,
BlockSize: 10 * 1024,
MaxChunkSize: 1024 * 1024, // 1MB. High throughput (e.g. 100MB/s) would otherwise trigger ~2000 POST requests/s at 50KB chunks.
BlockSize: 64 * 1024, // 64KB. Controls the granularity of reads from the backend and internal channel pressure. Aligned with io.Copy.

DisableHttp2: false,
ForceHttp2: false,
Expand Down Expand Up @@ -481,7 +481,13 @@ func (c *Client) buildResponses(in <-chan []byte, resp *pb.HttpResponse, out cha
timer := time.NewTimer(c.config.BackendResponseTimeout)
timeouts := 0

// TODO(haukeheibel): Why are we not simply reading the entire body? Why the chunking?
// Chunking is required for two reasons:
// 1. Streaming: long-lived connections (gRPC, WebSockets, kubectl exec) don't send EOF.
// We must forward data as it arrives to maintain responsiveness.
// 2. Memory: large responses (e.g. 100MB+) would otherwise be buffered in memory,
// potentially causing OOMs when marshaled into a single Protobuf message.
// MaxChunkSize limits the buffer size for fast data, while BackendResponseTimeout
// ensures low latency for slow/interactive data.
for {
select {
case b, more := <-in:
Expand Down
17 changes: 15 additions & 2 deletions src/go/tests/relay/nok8s_relay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,15 @@ func TestDroppedBidiStreamFreesRelayChannel(t *testing.T) {
} else {
done <- err
}
return
}
if _, err := conn.Write(req); err != nil {
if err == io.EOF {
done <- nil
} else {
done <- err
}
return
}
}
}))
Expand Down Expand Up @@ -372,7 +374,12 @@ func mustStartRelayWithGrpcServer(t *testing.T, service testpb.TestServiceServer
if err != nil {
t.Fatalf("failed to listen: %v", err)
}
result.GrpcServer = grpc.NewServer()
// We increase the max message size to 10MB to accommodate the large payload
// used in TestGrpcRelayChunkingOfLargeResponseWorks (5 * MaxChunkSize = 5MB).
result.GrpcServer = grpc.NewServer(
grpc.MaxRecvMsgSize(10*1024*1024),
grpc.MaxSendMsgSize(10*1024*1024),
)
testpb.RegisterTestServiceServer(result.GrpcServer, service)
go result.GrpcServer.Serve(result.Listener)

Expand All @@ -386,7 +393,13 @@ func mustStartRelayWithGrpcServer(t *testing.T, service testpb.TestServiceServer

// Create gRPC client via relay.
result.Ctx = metadata.AppendToOutgoingContext(context.Background(), "x-server-name", "remote1")
result.Conn, err = grpc.DialContext(result.Ctx, relayAddress, grpc.WithInsecure())
result.Conn, err = grpc.DialContext(result.Ctx, relayAddress,
grpc.WithInsecure(),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(10*1024*1024),
grpc.MaxCallSendMsgSize(10*1024*1024),
),
)
if err != nil {
t.Fatalf("Failed to create client connection: %v", err)
}
Expand Down
Loading