perf(spanner): distribute single-use queries across gRPC channel pool - #9273
perf(spanner): distribute single-use queries across gRPC channel pool#9273olavloite wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a custom channel factory and call invocation transformer for Cloud Spanner to bypass session-to-channel affinity for single-use transactions, allowing them to be distributed across multiple gRPC channels in the pool. It also includes comprehensive unit and integration tests to verify this behavior. Feedback is provided regarding the use of Object.create to delegate the channel factory, which can lead to runtime errors with ES private fields or split state; using a Proxy is recommended to safely intercept the affinity configuration.
2326011 to
bc316ee
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a custom channel factory and call invocation transformer for Cloud Spanner to distribute single-use transactions (such as single queries) across multiple gRPC channels in the pool, while keeping multi-use and read-write transactions pinned to their respective channels. Feedback on the changes suggests optimizing the Proxy implementation in createChannelFactoryWithoutAffinity by caching bound methods to avoid per-request allocations on the hot path, and using the target as the receiver in Reflect.get to prevent potential errors with private fields.
bc316ee to
3c6564d
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a custom channel factory and call invocation transformer to optimize Spanner channel utilization by routing single-use transactions across multiple gRPC channels while preserving session affinity for multi-use and read-write transactions. The feedback suggests using a Proxy instead of Object.create to safely delegate channel factory properties without desynchronizing the this context, adding a defensive check for the channel factory's existence, and updating the unit tests accordingly.
3c6564d to
df1c424
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a custom channel factory and call invocation transformer for Spanner to distribute single-use transactions across multiple gRPC channels in the pool, while maintaining session-to-channel affinity for multi-use and read-write transactions. Comprehensive unit and integration tests are added to verify this behavior. The review feedback highlights a potential issue with using Object.create to delegate channel factory calls, suggesting a Proxy instead to safely handle private fields, WeakMaps, or strict this context checks.
|
(The lint failure is unrelated to this change) |
With multiplexed sessions enabled by default, grpc-gcp binds the multiplexed session name to Channel 0 during CreateSession. Because query and read methods specify BOUND affinity on session, all single-use read queries across the application were pinned to a single gRPC channel while the rest of the pool remained idle. Single-use read-only queries are stateless across RPCs and do not require session-to-channel affinity. This change introduces a call invocation transformer and a pre-allocated delegate on GcpChannelFactory that bypasses affinity lookup specifically for single-use transactions (singleUse / single_use). This enables grpc-gcp to distribute queries across all available channels in the pool using its native active-stream load balancer. Channel stickiness is strictly preserved for multi-statement read/write transactions and multi-use read-only snapshots (database.getSnapshot() / table.read()). - Pre-allocates a static delegate on GcpChannelFactory to avoid per-query allocations. - Encapsulates channel factory overrides in internal module src/channel-factory.ts without leaking internal methods into the public API. - Adds unit tests in test/channel-factory.ts and integration tests in test/spanner.ts verifying channel distribution for single-use queries/streams, channel stickiness for multi-statement transactions, and error propagation.
df1c424 to
05cb1a0
Compare
With multiplexed sessions enabled by default, grpc-gcp binds the multiplexed session name to Channel 0 during CreateSession. Because query and read methods specify BOUND affinity on session, all single-use read queries across the application were pinned to a single gRPC channel while the rest of the pool remained idle.
Single-use read-only queries are stateless across RPCs and do not require session-to-channel affinity. This change introduces a call invocation transformer and a pre-allocated delegate on GcpChannelFactory that bypasses affinity lookup specifically for single-use transactions (singleUse / single_use). This enables grpc-gcp to distribute queries across all available channels in the pool using its native active-stream load balancer.
Channel stickiness is strictly preserved for multi-statement read/write transactions and multi-use read-only snapshots (database.getSnapshot() / table.read()).
Benchmark Results
To evaluate the impact of distributing single-use transactions across the gRPC channel pool under traffic spikes, a 15-minute
channel-scaling-bursty-point-selectbenchmark was run on GCE (n2-standard-4, sidecar enabled) and compared against the 7-day nightly baseline for this benchmark.Latency Comparison
Takeaway
Distributing single-use transactions evenly across the channel pool eliminates single-channel saturation and queue contention during traffic bursts, reducing P99 tail latency by >70% (from 58.5 ms down to 17.4 ms) and mean latency by ~20.5%, with no regression on median latency (+0.08 ms).