Skip to content

Improve high-concurrency client throughput - #1347

Merged
quinnj merged 5 commits into
masterfrom
perf/high-concurrency-client-throughput
Aug 7, 2026
Merged

Improve high-concurrency client throughput#1347
quinnj merged 5 commits into
masterfrom
perf/high-concurrency-client-throughput

Conversation

@quinnj

@quinnj quinnj commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary

  • remember origins that negotiate HTTP/1.1 during automatic protocol selection
  • skip per-chunk write-deadline refresh when no write-idle timeout is active
  • increase the response copy buffer from 8 KiB to 64 KiB
  • replay buffered request bodies with an independent cursor over the same immutable backing storage

Root cause

Same-region Azure Blob profiles exposed four independent costs at high concurrency.

  1. An HTTP/1.1-only origin caused every automatic request to try and fail HTTP/2 negotiation. These attempts serialized on the client HTTP/2 lock and opened a new TLS connection before falling back.
  2. Each 16 KiB TLS write refreshed the descriptor deadline even when the request had no moving write-idle timeout.
  3. The 8 KiB response copy loop refreshed the configured read-idle deadline too often and increased timer-heap contention.
  4. Each buffered upload replay copied the complete request payload before the first attempt.

The HTTP/1.1 origin cache applies only to protocol = :auto. An explicit protocol = :h2 still performs HTTP/2 negotiation. Existing HTTP/2 origins keep the normal HTTP/2 path.

Benchmark evidence

The benchmark used one Julia process, 64 Julia threads, Julia 1.13.0-rc1, and an Azure Standard_E64ds_v6 VM with same-region Azure Blob storage. Protocol selection remained automatic.

  • caching the HTTP/1.1 negotiation result raised the 1 MiB download profile from 0.43 to 3.93 Gbps
  • the 64 KiB response buffer raised the same profiled case to 17.08 Gbps
  • a 256 KiB buffer was slower at 16.63 Gbps, so this change keeps 64 KiB
  • removing the buffered-body copy raised the 64 MiB upload case from about 21.3 to 23.7 Gbps
  • the final full-stack 16 GiB matrix reached 38.8 Gbps median download and 26.6 Gbps median upload

Profile and Profile.Allocs were used to identify and verify the hot paths.

Validation

  • full HTTP test suite passes on Julia 1.13.0-rc1 with 8 threads
  • trim-compile suite passes: 63 of 63
  • new automatic fallback integration test passes
  • new deadline and buffered replay regression tests pass

Co-authored by Codex

Cache origins that negotiate HTTP/1.1 so automatic protocol selection does not repeat failed HTTP/2 handshakes. Avoid redundant per-chunk write deadline work, use a 64 KiB response copy buffer, and replay buffered bodies without copying their backing storage.

Add regression coverage for protocol fallback, deadline refresh decisions, and independent replay cursors.
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.55%. Comparing base (34882ba) to head (af91469).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1347      +/-   ##
==========================================
+ Coverage   88.45%   88.55%   +0.09%     
==========================================
  Files          31       31              
  Lines       11930    11955      +25     
==========================================
+ Hits        10553    10587      +34     
+ Misses       1377     1368       -9     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 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.

@quinnj
quinnj marked this pull request as ready for review August 7, 2026 05:18
quinnj and others added 2 commits August 6, 2026 23:52
…buffers

Review fixes for the high-concurrency throughput changes:

- Guard `h1_origins` with its own lock. `_use_h2` previously took the
  client-global `h2_lock`, which is held across full TCP+TLS dials in
  `_acquire_h2_conn!`, so one slow HTTP/2 dial to any origin could stall
  every automatic-protocol HTTPS request on the client. The cache-hit
  fail-fast in `_acquire_h2_conn!` now also runs before `h2_lock`.
- Clear the cache in `close_idle_connections!` so long-lived clients can
  re-probe origins that enable HTTP/2 later; document the cache behavior
  on `Client` and `close_idle_connections!`.
- Make `allow_h1_alpn`/`auto_protocol` keyword arguments (the call site
  passed two identical adjacent positional booleans).
- Right-size response copy buffers: clamp preallocated-destination copies
  to the destination capacity so small responses stop paying a 64 KiB
  scratch allocation per request, use 64 KiB for the known-large (>1 MiB
  hint) accumulation path and the response_stream pump, and name the
  8 KiB small-loop constant with the sizing policy.
- Test that a transient connect failure does not populate the h1-origin
  cache (only a genuine ALPN h1 negotiation may), and that
  close_idle_connections! clears it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@quinnj
quinnj merged commit 4b6c835 into master Aug 7, 2026
13 of 14 checks passed
@quinnj
quinnj deleted the perf/high-concurrency-client-throughput branch August 7, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant