Skip to content

Commit 4db3c64

Browse files
committed
chore(spanner): implement dynamic channel pooling and transaction affinity
Adds dynamic channel pooling to the Spanner client, replacing single-channel stubs with an elastic pool of gRPC connections. - Distributes requests across channels using the Power of Two Choices (P2C) algorithm to avoid hot connections. - Automatically spins up and warms new channels under heavy load, and quietly drains idle ones when traffic drops. - Pins multi-step transactions to the same channel so server state remains consistent. - Supports both static and dynamic configurations, and respects the `SPANNER_NUM_CHANNELS` environment variable. - Keeps the pool configuration internal (`pub(crate)`) for now while the API and behavior are finalized.
1 parent f02b42c commit 4db3c64

22 files changed

Lines changed: 2012 additions & 504 deletions

src/spanner/src/batch_read_only_transaction.rs

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ impl BatchReadOnlyTransaction {
168168
.partition_query(
169169
request,
170170
crate::RequestOptions::default(),
171-
self.inner.context.channel_hint,
171+
self.inner.context.affinity(),
172172
)
173173
.await?;
174174

@@ -231,7 +231,7 @@ impl BatchReadOnlyTransaction {
231231
.partition_read(
232232
request,
233233
crate::RequestOptions::default(),
234-
self.inner.context.channel_hint,
234+
self.inner.context.affinity(),
235235
)
236236
.await?;
237237

@@ -405,15 +405,11 @@ impl Partition {
405405
req: &ExecuteSqlRequest,
406406
gax_options: GaxRequestOptions,
407407
) -> crate::Result<ResultSet> {
408-
let channel_hint = client.next_channel_hint();
409-
let gax_options = client.attach_request_id(gax_options, channel_hint);
408+
let builder = client.execute_streaming_sql(req.clone(), gax_options, None);
409+
let actual_gax_options = builder.options().clone();
410410
let (stream, attempt_start_time) =
411-
Self::execute_partition_stream(client, "ExecuteStreamingSql", || {
412-
client
413-
.execute_streaming_sql(req.clone(), gax_options.clone(), channel_hint)
414-
.send()
415-
})
416-
.await?;
411+
Self::execute_partition_stream(client, "ExecuteStreamingSql", move || builder.send())
412+
.await?;
417413

418414
ResultSet::create(ResultSetParams {
419415
stream,
@@ -428,8 +424,7 @@ impl Partition {
428424
session_name: req.session.clone(),
429425
transaction_tag: None,
430426
operation: StreamOperation::Query(req.clone()),
431-
channel_hint,
432-
gax_options,
427+
gax_options: actual_gax_options,
433428
method_name: "ExecuteStreamingSql",
434429
attempt_start_time: Some(attempt_start_time),
435430
operation_start_time: Some(attempt_start_time),
@@ -443,15 +438,10 @@ impl Partition {
443438
req: &ReadRequest,
444439
gax_options: GaxRequestOptions,
445440
) -> crate::Result<ResultSet> {
446-
let channel_hint = client.next_channel_hint();
447-
let gax_options = client.attach_request_id(gax_options, channel_hint);
441+
let builder = client.streaming_read(req.clone(), gax_options, None);
442+
let actual_gax_options = builder.options().clone();
448443
let (stream, attempt_start_time) =
449-
Self::execute_partition_stream(client, "StreamingRead", || {
450-
client
451-
.streaming_read(req.clone(), gax_options.clone(), channel_hint)
452-
.send()
453-
})
454-
.await?;
444+
Self::execute_partition_stream(client, "StreamingRead", move || builder.send()).await?;
455445

456446
ResultSet::create(ResultSetParams {
457447
stream,
@@ -466,8 +456,7 @@ impl Partition {
466456
session_name: req.session.clone(),
467457
transaction_tag: None,
468458
operation: StreamOperation::Read(req.clone()),
469-
channel_hint,
470-
gax_options,
459+
gax_options: actual_gax_options,
471460
method_name: "StreamingRead",
472461
attempt_start_time: Some(attempt_start_time),
473462
operation_start_time: Some(attempt_start_time),

src/spanner/src/batch_write_transaction.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,10 @@ impl BatchWriteTransactionBuilder {
209209
/// ```
210210
pub fn build(self) -> BatchWriteTransaction {
211211
let session_name = self.client.session_name();
212-
let channel_hint = self.client.next_channel_hint();
213212
let gax_options = apply_defaults(self.gax_options);
214213
BatchWriteTransaction {
215214
session_name,
216215
client: self.client,
217-
channel_hint,
218216
transaction_tag: self.transaction_tag,
219217
priority: self.priority,
220218
exclude_txn_from_change_streams: self.exclude_txn_from_change_streams,
@@ -230,7 +228,6 @@ impl BatchWriteTransactionBuilder {
230228
pub struct BatchWriteTransaction {
231229
session_name: String,
232230
client: DatabaseClient,
233-
channel_hint: usize,
234231
transaction_tag: Option<String>,
235232
priority: Priority,
236233
exclude_txn_from_change_streams: bool,
@@ -291,7 +288,6 @@ impl BatchWriteTransaction {
291288
Ok(BatchWriteResponseStream {
292289
client: self.client,
293290
session_name: self.session_name,
294-
channel_hint: self.channel_hint,
295291
transaction_tag: self.transaction_tag,
296292
priority: self.priority,
297293
exclude_txn_from_change_streams: self.exclude_txn_from_change_streams,
@@ -317,7 +313,6 @@ impl BatchWriteTransaction {
317313
pub struct BatchWriteResponseStream {
318314
client: DatabaseClient,
319315
session_name: String,
320-
channel_hint: usize,
321316
transaction_tag: Option<String>,
322317
priority: Priority,
323318
exclude_txn_from_change_streams: bool,
@@ -436,7 +431,7 @@ impl BatchWriteResponseStream {
436431

437432
let stream_result = self
438433
.client
439-
.batch_write(request, self.gax_options.clone(), self.channel_hint)
434+
.batch_write(request, self.gax_options.clone(), None)
440435
.send()
441436
.await;
442437

@@ -540,8 +535,6 @@ impl BatchWriteResponseStream {
540535
match self.check_retry(error) {
541536
Ok(()) => {
542537
self.retry_count += 1;
543-
// Rotate channel hint only when a retry is confirmed to distribute load across healthy connections.
544-
self.channel_hint = self.client.next_channel_hint();
545538
if let Some(policy) = self.gax_options.backoff_policy() {
546539
let state = RetryState::new(true).set_attempt_count(self.retry_count as u32);
547540
let delay = policy.on_failure(&state);

0 commit comments

Comments
 (0)