Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,18 @@ private RoutingDecision routeFromRequest(ReadRequest.Builder reqBuilder) {
boolean routePreferLeader = transactionId.isEmpty() && preferLeader;
endpoint = finder.findServer(reqBuilder, routePreferLeader, excludedEndpoints);
preferLeader = routePreferLeader;
} else if (finder != null) {
// The request belongs to a read-write transaction and stays pinned to the transaction's
// affinity endpoint. Still populate the routing hint (operation UID, key and target range)
// so that:
// - the server returns key recipes for this statement shape, which the client otherwise
// never learns for statements that only run inside read-write transactions; and
// - the server can serve the request on the fastpath, or forward it through the fastpath
// when the key is not local to the transaction owner.
// The endpoint selected by the finder is ignored to preserve transaction affinity.
// preferLeader is intentionally left unchanged (true for read-write transactions): the
// hint must describe the leader tablet, as read-write requests are served by the leader.
finder.findServer(reqBuilder, preferLeader, excludedEndpoints);
}
Comment thread
samyakpjgoogle marked this conversation as resolved.
return new RoutingDecision(
finder, endpoint, databaseId, operationUid(reqBuilder.getRoutingHint()), preferLeader);
Expand All @@ -1186,6 +1198,18 @@ private RoutingDecision routeFromRequest(ExecuteSqlRequest.Builder reqBuilder) {
boolean routePreferLeader = transactionId.isEmpty() && preferLeader;
endpoint = finder.findServer(reqBuilder, routePreferLeader, excludedEndpoints);
preferLeader = routePreferLeader;
} else if (finder != null) {
// The request belongs to a read-write transaction and stays pinned to the transaction's
// affinity endpoint. Still populate the routing hint (operation UID, key and target range)
// so that:
// - the server returns key recipes for this statement shape, which the client otherwise
// never learns for statements that only run inside read-write transactions; and
// - the server can serve the request on the fastpath, or forward it through the fastpath
// when the key is not local to the transaction owner.
// The endpoint selected by the finder is ignored to preserve transaction affinity.
// preferLeader is intentionally left unchanged (true for read-write transactions): the
// hint must describe the leader tablet, as read-write requests are served by the leader.
finder.findServer(reqBuilder, preferLeader, excludedEndpoints);
}
Comment thread
samyakpjgoogle marked this conversation as resolved.
return new RoutingDecision(
finder, endpoint, databaseId, operationUid(reqBuilder.getRoutingHint()), preferLeader);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1314,6 +1314,78 @@ public void unaryReadWithTransactionIdRoutesToAffinityEndpoint() throws Exceptio
assertThat(harness.defaultManagedChannel.callCount()).isEqualTo(1);
}

@Test
public void readWriteTransactionReadAttachesRoutingHintWhilePinnedToAffinityEndpoint()
throws Exception {
TestHarness harness = createHarness();
ByteString transactionId = ByteString.copyFromUtf8("rw-read-routing-hint");
seedReadWriteTransactionAffinity(harness, transactionId);

// Key "n" is owned by server-b, but the transaction is pinned to server-a.
ClientCall<ReadRequest, PartialResultSet> call =
harness.channel.newCall(SpannerGrpc.getStreamingReadMethod(), CallOptions.DEFAULT);
call.start(new CapturingListener<PartialResultSet>(), new Metadata());
call.sendMessage(
ReadRequest.newBuilder()
.setSession(SESSION)
.setTable("T")
.setTransaction(TransactionSelector.newBuilder().setId(transactionId))
.setRoutingHint(RoutingHint.newBuilder().setKey(bytes("n")))
.build());

assertThat(harness.endpointCache.callCountForAddress("server-a:1234")).isEqualTo(2);
assertThat(harness.endpointCache.callCountForAddress("server-b:1234")).isEqualTo(0);
@SuppressWarnings("unchecked")
RecordingClientCall<ReadRequest, PartialResultSet> delegate =
(RecordingClientCall<ReadRequest, PartialResultSet>)
harness.endpointCache.latestCallForAddress("server-a:1234");
RoutingHint routingHint = delegate.lastMessage.getRoutingHint();
assertThat(routingHint.getOperationUid()).isNotEqualTo(0L);
assertThat(routingHint.getDatabaseId()).isEqualTo(7L);
// The hint describes the split covering key "n" ([m, z), group 2) and its leader tablet.
assertThat(routingHint.getKey()).isEqualTo(bytes("m"));
assertThat(routingHint.getLimitKey()).isEqualTo(bytes("z"));
assertThat(routingHint.getGroupUid()).isEqualTo(2L);
assertThat(routingHint.getSplitId()).isEqualTo(2L);
assertThat(routingHint.getTabletUid()).isEqualTo(2L);
}

@Test
public void readWriteTransactionQueryAttachesRoutingHintWhilePinnedToAffinityEndpoint()
throws Exception {
TestHarness harness = createHarness();
ByteString transactionId = ByteString.copyFromUtf8("rw-query-routing-hint");
seedReadWriteTransactionAffinity(harness, transactionId);

// Key "n" is owned by server-b, but the transaction is pinned to server-a.
ClientCall<ExecuteSqlRequest, PartialResultSet> call =
harness.channel.newCall(SpannerGrpc.getExecuteStreamingSqlMethod(), CallOptions.DEFAULT);
call.start(new CapturingListener<PartialResultSet>(), new Metadata());
call.sendMessage(
ExecuteSqlRequest.newBuilder()
.setSession(SESSION)
.setSql("SELECT * FROM T WHERE K = @k")
.setTransaction(TransactionSelector.newBuilder().setId(transactionId))
.setRoutingHint(RoutingHint.newBuilder().setKey(bytes("n")))
.build());

assertThat(harness.endpointCache.callCountForAddress("server-a:1234")).isEqualTo(2);
assertThat(harness.endpointCache.callCountForAddress("server-b:1234")).isEqualTo(0);
@SuppressWarnings("unchecked")
RecordingClientCall<ExecuteSqlRequest, PartialResultSet> delegate =
(RecordingClientCall<ExecuteSqlRequest, PartialResultSet>)
harness.endpointCache.latestCallForAddress("server-a:1234");
RoutingHint routingHint = delegate.lastMessage.getRoutingHint();
assertThat(routingHint.getOperationUid()).isNotEqualTo(0L);
assertThat(routingHint.getDatabaseId()).isEqualTo(7L);
// The hint describes the split covering key "n" ([m, z), group 2) and its leader tablet.
assertThat(routingHint.getKey()).isEqualTo(bytes("m"));
assertThat(routingHint.getLimitKey()).isEqualTo(bytes("z"));
assertThat(routingHint.getGroupUid()).isEqualTo(2L);
assertThat(routingHint.getSplitId()).isEqualTo(2L);
assertThat(routingHint.getTabletUid()).isEqualTo(2L);
}

@Test
public void genericUnimplementedReroutesOnceAndLearnsDefaultOnlyMethod() throws Exception {
TestHarness harness = createHarness();
Expand Down
Loading