Skip to content

perf(go): pool the request-path wire payload#3442

Open
matanper wants to merge 8 commits into
apache:masterfrom
matanper:perf/iggy-tcp-request-alloc
Open

perf(go): pool the request-path wire payload#3442
matanper wants to merge 8 commits into
apache:masterfrom
matanper:perf/iggy-tcp-request-alloc

Conversation

@matanper

@matanper matanper commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR address?

Closes #3441

Rationale

Reduces per-RPC allocation pressure on the Go client's TCP poll path

What changed?

Path Before After
All commands: wire-frame assembly (length + code + body) createPayload allocates a fresh []byte per RPC Reused sync.Pool-backed buffer (0 allocs)
All commands: 8-byte response status header make([]byte, 8) per RPC Reused respHeader [8]byte field on IggyTcpClient
PollMessages: body encode MarshalBinary allocates the output slice AppendBinary writes into the pooled buffer (0 allocs)
PollMessages: Identifier wire bytes Re-marshaled per RPC × 4 identifiers (stream, topic, consumer, partition) Cached at NewIdentifier; appended from the cache
Other commands: body encode MarshalBinary allocates the output slice Unchanged — MarshalBinary is still called, output is appended into the pooled buffer
Other commands: Identifier wire bytes Re-marshaled per RPC via Identifier.MarshalBinary Unchanged — Identifier.MarshalBinary still allocates per call; the cache is only consumed via AppendBinary, which only PollMessages calls in this PR. Migrating other commands' MarshalBinary implementations to AppendBinary would unlock this for them too.

Local Execution

Passed

AI Usage

  1. Claude
  2. entire implementation
  3. run it locally on iggy server
  4. yes

Cache constant Identifier wire bytes in NewIdentifier; add MarshalledSize
and AppendBinary on Identifier and PollMessages so the TCP client can
encode the wire payload directly into a pooled buffer (requestBufPool)
instead of allocating per RPC. The 8-byte response status header now
reads into a stack-local buffer via readInto.

Identifier.Value is no longer aliased to the cached encoded bytes, so
external mutations to the exported Value field can't corrupt the wire
encoding. Coupling tests assert MarshalledSize == len(AppendBinary(nil))
for both types to catch future drift.

Benchmarks (foreign/go/benchmarks/poll_encode_benchmark_test.go):
  PollMessages encode  23.06 ns / 64 B / 1 allocs  ->  7.74 ns / 0 B / 0 allocs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

Thanks for the PR. It is labeled S-waiting-on-review and queued for review.

Slash commands (own line, regular comment) move it around the queue:

  • /ready - back to S-waiting-on-review after addressing feedback
  • /author - flip to S-waiting-on-author while you finish changes
  • /request-review @user-or-team - request a reviewer

See CONTRIBUTING.md for details.

@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Jun 8, 2026
@matanper matanper changed the title perf(foreign/go/tcp): pool the request-path wire payload perf(go): pool the request-path wire payload Jun 8, 2026
@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.35632% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.65%. Comparing base (7d30c09) to head (9d7ebb3).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
foreign/go/client/tcp/tcp_core.go 87.03% 4 Missing and 3 partials ⚠️
foreign/go/internal/command/message.go 76.47% 1 Missing and 3 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3442      +/-   ##
============================================
+ Coverage     74.30%   74.65%   +0.35%     
  Complexity      943      943              
============================================
  Files          1245     1228      -17     
  Lines        121653   120559    -1094     
  Branches      97959    97263     -696     
============================================
- Hits          90390    90004     -386     
+ Misses        28307    27619     -688     
+ Partials       2956     2936      -20     
Components Coverage Δ
Rust Core 75.85% <ø> (+0.46%) ⬆️
Java SDK 58.44% <ø> (ø)
C# SDK 69.87% <ø> (-0.06%) ⬇️
Python SDK 81.06% <ø> (ø)
PHP SDK ∅ <ø> (∅)
Node SDK 91.53% <ø> (+0.18%) ⬆️
Go SDK 40.46% <87.35%> (+0.26%) ⬆️
Files with missing lines Coverage Δ
foreign/go/contracts/identifier.go 77.77% <100.00%> (-1.47%) ⬇️
foreign/go/internal/command/message.go 72.38% <76.47%> (-2.84%) ⬇️
foreign/go/client/tcp/tcp_core.go 58.50% <87.03%> (+4.30%) ⬆️

... and 541 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

matanper and others added 3 commits June 9, 2026 01:13
…nd do()

Adds unit tests for the new request-path code paths:
 - encodeWireRequest appender fast path vs MarshalBinary fallback
   produce byte-identical output to the legacy createPayload pipeline.
 - encodeWireRequest grows an undersized buffer in both paths.
 - encodeWireRequest propagates MarshalBinary errors.
 - acquireRequestBuf / releaseRequestBuf round-trip; oversized buffers
   are dropped instead of poisoning the pool.
 - do() end-to-end via net.Pipe: the wire bytes the server reads match
   cmd.MarshalBinary() and the response is decoded correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renames the four multi-underscore test names to use the existing
Test<Symbol>_<Scenario> single-underscore pattern. Factors
serverRespond into serverRespondCapture (which returns the request
bytes) so the end-to-end Do test can assert on what the server saw
without inlining its own goroutine reader. Existing serverRespond
callers are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread foreign/go/client/tcp/tcp_core.go Outdated
Comment thread foreign/go/contracts/identifier.go Outdated
Comment thread foreign/go/benchmarks/poll_encode_benchmark_test.go Outdated
Comment thread foreign/go/client/tcp/tcp_core.go Outdated
Comment thread foreign/go/client/tcp/tcp_core.go Outdated
@github-actions github-actions Bot added S-waiting-on-author PR is waiting on author response and removed S-waiting-on-review PR is waiting on a reviewer labels Jun 9, 2026
@hubcio

hubcio commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

alright, from my side this change reasonable and it's merge-able. lets wait for @changhc for couple of days - he's our main golang guy, and nowadays he does most of go sdk work.

thanks for contribution @matanper

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author PR is waiting on author response

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(go-sdk): high per-RPC allocation pressure on TCP poll path

2 participants