16
16
17
17
package com .google .cloud .spanner ;
18
18
19
+ import static com .google .cloud .spanner .SessionClient .optionMap ;
19
20
import static com .google .cloud .spanner .SpannerExceptionFactory .newSpannerException ;
20
21
import static com .google .common .base .Preconditions .checkArgument ;
21
22
import static com .google .common .base .Preconditions .checkNotNull ;
32
33
import com .google .cloud .spanner .AsyncResultSet .ReadyCallback ;
33
34
import com .google .cloud .spanner .Options .QueryOption ;
34
35
import com .google .cloud .spanner .Options .ReadOption ;
36
+ import com .google .cloud .spanner .SessionClient .SessionOption ;
35
37
import com .google .cloud .spanner .SessionImpl .SessionTransaction ;
36
38
import com .google .cloud .spanner .spi .v1 .SpannerRpc ;
37
39
import com .google .common .annotations .VisibleForTesting ;
52
54
import com .google .spanner .v1 .TransactionOptions ;
53
55
import com .google .spanner .v1 .TransactionSelector ;
54
56
import java .util .Map ;
57
+ import java .util .concurrent .ThreadLocalRandom ;
55
58
import java .util .concurrent .atomic .AtomicLong ;
56
59
import javax .annotation .Nullable ;
57
60
import javax .annotation .concurrent .GuardedBy ;
@@ -180,9 +183,15 @@ static Builder newBuilder() {
180
183
@ GuardedBy ("lock" )
181
184
private boolean used ;
182
185
186
+ private final Map <SpannerRpc .Option , ?> channelHint ;
187
+
183
188
private SingleReadContext (Builder builder ) {
184
189
super (builder );
185
190
this .bound = builder .bound ;
191
+ // single use transaction have a single RPC and hence there is no need
192
+ // of a channel hint. GAX will automatically choose a hint when used
193
+ // with a multiplexed session.
194
+ this .channelHint = getChannelHintOptions (session .getOptions (), null );
186
195
}
187
196
188
197
@ Override
@@ -209,6 +218,11 @@ TransactionSelector getTransactionSelector() {
209
218
.setSingleUse (TransactionOptions .newBuilder ().setReadOnly (bound .toProto ()))
210
219
.build ();
211
220
}
221
+
222
+ @ Override
223
+ Map <SpannerRpc .Option , ?> getTransactionChannelHint () {
224
+ return channelHint ;
225
+ }
212
226
}
213
227
214
228
private static void assertTimestampAvailable (boolean available ) {
@@ -217,6 +231,7 @@ private static void assertTimestampAvailable(boolean available) {
217
231
218
232
static class SingleUseReadOnlyTransaction extends SingleReadContext
219
233
implements ReadOnlyTransaction {
234
+
220
235
@ GuardedBy ("lock" )
221
236
private Timestamp timestamp ;
222
237
@@ -300,6 +315,8 @@ static Builder newBuilder() {
300
315
@ GuardedBy ("txnLock" )
301
316
private ByteString transactionId ;
302
317
318
+ private final Map <SpannerRpc .Option , ?> channelHint ;
319
+
303
320
MultiUseReadOnlyTransaction (Builder builder ) {
304
321
super (builder );
305
322
checkArgument (
@@ -318,6 +335,14 @@ static Builder newBuilder() {
318
335
this .timestamp = builder .timestamp ;
319
336
this .transactionId = builder .transactionId ;
320
337
}
338
+ this .channelHint =
339
+ getChannelHintOptions (
340
+ session .getOptions (), ThreadLocalRandom .current ().nextLong (Long .MAX_VALUE ));
341
+ }
342
+
343
+ @ Override
344
+ public Map <SpannerRpc .Option , ?> getTransactionChannelHint () {
345
+ return channelHint ;
321
346
}
322
347
323
348
@ Override
@@ -380,7 +405,7 @@ void initTransaction() {
380
405
.setOptions (options )
381
406
.build ();
382
407
Transaction transaction =
383
- rpc .beginTransaction (request , session . getOptions (), isRouteToLeader ());
408
+ rpc .beginTransaction (request , getTransactionChannelHint (), isRouteToLeader ());
384
409
if (!transaction .hasReadTimestamp ()) {
385
410
throw SpannerExceptionFactory .newSpannerException (
386
411
ErrorCode .INTERNAL , "Missing expected transaction.read_timestamp metadata field" );
@@ -727,7 +752,10 @@ CloseableIterator<PartialResultSet> startStream(@Nullable ByteString resumeToken
727
752
}
728
753
SpannerRpc .StreamingCall call =
729
754
rpc .executeQuery (
730
- request .build (), stream .consumer (), session .getOptions (), isRouteToLeader ());
755
+ request .build (),
756
+ stream .consumer (),
757
+ getTransactionChannelHint (),
758
+ isRouteToLeader ());
731
759
session .markUsed (clock .instant ());
732
760
call .request (prefetchChunks );
733
761
stream .setCall (call , request .getTransaction ().hasBegin ());
@@ -738,6 +766,16 @@ CloseableIterator<PartialResultSet> startStream(@Nullable ByteString resumeToken
738
766
stream , this , options .hasDecodeMode () ? options .decodeMode () : defaultDecodeMode );
739
767
}
740
768
769
+ Map <SpannerRpc .Option , ?> getChannelHintOptions (
770
+ Map <SpannerRpc .Option , ?> channelHintForSession , Long channelHintForTransaction ) {
771
+ if (channelHintForSession != null ) {
772
+ return channelHintForSession ;
773
+ } else if (channelHintForTransaction != null ) {
774
+ return optionMap (SessionOption .channelHint (channelHintForTransaction ));
775
+ }
776
+ return null ;
777
+ }
778
+
741
779
/**
742
780
* Called before any read or query is started to perform state checks and initializations.
743
781
* Subclasses should call {@code super.beforeReadOrQuery()} if overriding.
@@ -782,6 +820,12 @@ public void close() {
782
820
@ Nullable
783
821
abstract TransactionSelector getTransactionSelector ();
784
822
823
+ /**
824
+ * Channel hint to be used for a transaction. This enables soft-stickiness per transaction by
825
+ * ensuring all RPCs within a transaction land up on the same channel.
826
+ */
827
+ abstract Map <SpannerRpc .Option , ?> getTransactionChannelHint ();
828
+
785
829
/**
786
830
* Returns the transaction tag for this {@link AbstractReadContext} or <code>null</code> if this
787
831
* {@link AbstractReadContext} does not have a transaction tag.
@@ -872,7 +916,10 @@ CloseableIterator<PartialResultSet> startStream(@Nullable ByteString resumeToken
872
916
builder .setRequestOptions (buildRequestOptions (readOptions ));
873
917
SpannerRpc .StreamingCall call =
874
918
rpc .read (
875
- builder .build (), stream .consumer (), session .getOptions (), isRouteToLeader ());
919
+ builder .build (),
920
+ stream .consumer (),
921
+ getTransactionChannelHint (),
922
+ isRouteToLeader ());
876
923
session .markUsed (clock .instant ());
877
924
call .request (prefetchChunks );
878
925
stream .setCall (call , /* withBeginTransaction = */ builder .getTransaction ().hasBegin ());
0 commit comments