Repository navigation
grpc-js: Backport performance and stability improvements to 1.14.x branch - #3107
Merged
murgatroid99 merged 12 commits intoOct 7, 2026
Merged
Conversation
…c#3092) Make ResolvingCall.deadlineTimer nullable and initialize it to null instead of allocating a throwaway setTimeout(() => {}, 0) on every RPC. - Initialize deadlineTimer to null and add null checks before clearTimeout. - Set deadlineTimer to null once cleared or when the deadline handler fires. - Add a test verifying that calls without deadlines do not schedule timers.
…c#3093) Read grpc-status and grpc-message directly from trailers using metadata.get() instead of calling metadata.getMap(). Calling metadata.getMap() on every completed RPC allocates a map object, iterates over all trailers, and clones binary buffers (such as grpc-status-details-bin) that are immediately thrown away. Reading the two needed headers directly avoids these allocations on the response hot path.
…rs (grpc#3099) Most gRPC metadata headers contain only a single value (such as authorization tokens, tracing headers, and request IDs). Previously, `toHttp2Headers` always called `.map()`, allocating a temporary array for every header key on every RPC. For single-value headers, we now assign the formatted value directly to the header object instead of wrapping it in an array. This eliminates unnecessary allocations and reduces garbage collection pressure on the RPC hot path.
The copy-protos script (run during `npm run prepare`) copies external proto definitions from grpc-js-xds into `packages/grpc-js/proto/protoc-gen-validate` and `packages/grpc-js/proto/xds`. Add both directories to .gitignore so these generated dependencies do not show up as untracked files or get accidentally committed.
…g paths (grpc#3090) Store timestamps as epoch milliseconds (Date.now()) instead of allocating Date objects on the request path and channelz counters: - Call layers (ResolvingCall, RetryingCall, LoadBalancingCall): store call start and resolution times as numbers; construct Date instances only when formatting deadline error messages or when trace logging is enabled. - Channelz call tracker and HTTP/2 transport: record last call started, message sent, and message received timestamps as numbers. - Channel activity tracking: record channel idle timestamp as a number. - Server timeout handling: calculate incoming call deadlines with Date.now() + timeout instead of allocating a Date object. - dateToProtoTimestamp: support numeric millisecond timestamps in addition to Date objects, and use Math.floor() to avoid 32-bit integer overflow beyond year 2038. - formatDateDifference: accept numeric timestamps as well as Date instances. - Update channelz and deadline tests for numeric timestamps.
Every Metadata instance previously allocated a Map for opaqueData in its constructor, even though opaqueData is only used in specific scenarios like caching parsed ORCA load reports on trailers. On high-throughput RPC paths where multiple Metadata instances are created per call, this eager allocation resulted in unnecessary heap allocation and GC pressure. Changes: - Make Metadata.prototype.opaqueData optional and lazily instantiate the Map on the first setOpaque() call. - Use optional chaining in getOpaque() when reading from opaqueData. - Preserve existing clone() and constructor behavior to maintain exact compatibility. - Add unit tests covering lazy allocation, key updates, value types, and cloned instance behavior.
Fix two test failures when running on Windows: - Increase the timeout for the nonexistent domain test on Windows to allow sequential DNS lookup failures to complete. - Skip the Unix Domain Socket (UDS) idle timer test on Windows where UDS file paths are not supported.
…3097) Cache the precomputed serviceUrl used by call credentials in a bounded two-level cache on InternalChannel instead of re-parsing the method path, splitting the host, and constructing a new template string on every RPC. - Add zero-allocation `extractServiceName` and `computeServiceUrl` helpers in `uri-parser.ts`. - Introduce a bounded two-level cache (`serviceUrlCache: Map<string, Map<string, string>>`) on `InternalChannel`, capped at 5 hosts and 100 methods per host to strictly bound memory while achieving zero-allocation lookups on cache hits. - Clear `serviceUrlCache` when the channel is closed. - Update `LoadBalancingCall` and `SingleSubchannelChannel` to use the shared URL computation and caching.
grpc#3098) On every RPC, the default config selector parsed the method name by splitting the string, filtering empty segments, and looping through method config match levels to find the appropriate configuration. In high-throughput clients, this performed repetitive string splitting, array allocations, and lookups on the hot path. This change: 1. Caches the resolved MethodConfig by method name in a Map within getDefaultConfigSelector, avoiding repeated string splitting and search loops on cache hits. 2. Bounds the cache to 100 method entries to keep memory usage fixed. 3. Clears the cache in unref() when channel resolution updates or the channel closes. 4. Returns fresh CallConfig containers for each RPC, preserving call isolation for pickInformation and dynamic filters.
…ial attempt (grpc#3101) RetryingCall.startNewAttempt() previously cloned this.initialMetadata on every attempt, even when previousAttempts was 0 and no grpc-previous-rpc-attempts header needed to be added. Because LoadBalancingCall never mutates this.metadata and always clones it in doPick(), the extra clone in RetryingCall on the initial attempt was redundant. Only clone this.initialMetadata in RetryingCall.startNewAttempt() when previousAttempts > 0.
…rpc#3104) In test-weighted-round-robin, test traffic was issued immediately against freshly created clients with aggressive 100ms per-call deadlines. On slower or heavily loaded CI environments (such as Windows Kokoro runners), cold-start connection establishment, name resolution, and picker readiness frequently exceeded 100ms, triggering intermittent DEADLINE_EXCEEDED ("Waiting for LB pick") failures. Additionally, blind initial traffic could reach only the first backend before the second finished connecting, leaving the second backend's metrics unrecorded. Furthermore, executing 50 sequential RPC round-trips combined with arbitrary sleep timers pushed total execution time beyond Mocha's default 2-second timeout on Windows VMs (often failing at ~2.005s). This change stabilizes the tests by: 1. Ensuring the channel reaches readiness and that both backends have responded to initial traffic before measurement begins, guaranteeing that connections and initial metric tracking are established across all endpoints. 2. Synchronizing deterministically against picker updates rather than relying on fixed sleep delays, and executing measurement RPCs concurrently over HTTP/2 so the suite runs in ~1s while preserving the expected weight distribution. 3. Increasing the test timeout to provide sufficient headroom against VM scheduling jitter on Windows CI.
… hot path (grpc#3100) Previously, every RPC executed through LoadBalancingCall composed per-call and subchannel CallCredentials and called generateMetadata(), even when both sides were EmptyCallCredentials. In addition: - Composing any CallCredentials with EmptyCallCredentials on the right-hand side wrapped them in a new ComposedCallCredentials instance. - InsecureChannelCredentialsImpl allocated a new EmptyCallCredentials instance on every getCallCredentials() call when no call credentials were provided. - EmptyCallCredentials.generateMetadata() allocated a new Metadata instance and Promise on every RPC only to merge an empty map. This change: - Treats EmptyCallCredentials as a two-sided identity in compose(), returning `this` directly when composing with an empty instance. - Evaluates the fallback CallCredentials.createEmpty() once per connector in InsecureChannelCredentialsImpl._createSecureConnector(). - Short-circuits metadata generation and merging in LoadBalancingCall when the composed CallCredentials is empty, reusing a pre-resolved Promise.
Member
|
Don't worry about the Windows failure. A different fix for that is on master but never got ported to this branch. |
murgatroid99
approved these changes
Oct 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backport of recent performance optimizations and supporting test/CI fixes from
masterto the@grpc/grpc-js@1.14.xbranch for the upcoming patch release.All 12 changes have already been reviewed and merged into
master:Performance Optimizations
Test and CI Fixes