-
Notifications
You must be signed in to change notification settings - Fork 139
chore: add routing hints and cache updates for begin/commit #4392
Changes from 2 commits
82c3244
3c99d2a
e836a5d
0f39595
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,8 +19,10 @@ | |
| import com.google.api.core.InternalApi; | ||
| import com.google.spanner.v1.BeginTransactionRequest; | ||
| import com.google.spanner.v1.CacheUpdate; | ||
| import com.google.spanner.v1.CommitRequest; | ||
| import com.google.spanner.v1.DirectedReadOptions; | ||
| import com.google.spanner.v1.ExecuteSqlRequest; | ||
| import com.google.spanner.v1.Mutation; | ||
| import com.google.spanner.v1.ReadRequest; | ||
| import com.google.spanner.v1.RoutingHint; | ||
| import com.google.spanner.v1.TransactionOptions; | ||
|
|
@@ -92,23 +94,31 @@ public ChannelEndpoint findServer(ExecuteSqlRequest.Builder reqBuilder, boolean | |
| } | ||
|
|
||
| public ChannelEndpoint findServer(BeginTransactionRequest.Builder reqBuilder) { | ||
| if (!reqBuilder.hasMutationKey()) { | ||
| if (!reqBuilder.hasMutationKey() | ||
| || !recipeCache.computeKeys( | ||
| reqBuilder.getMutationKey(), reqBuilder.getRoutingHintBuilder())) { | ||
| return null; | ||
| } | ||
| TargetRange target = recipeCache.mutationToTargetRange(reqBuilder.getMutationKey()); | ||
| if (target == null) { | ||
| return null; | ||
| } | ||
| RoutingHint.Builder hintBuilder = RoutingHint.newBuilder(); | ||
| hintBuilder.setKey(target.start); | ||
| if (!target.limit.isEmpty()) { | ||
| hintBuilder.setLimitKey(target.limit); | ||
| } | ||
| return fillRoutingHint( | ||
| preferLeader(reqBuilder.getOptions()), | ||
| KeyRangeCache.RangeMode.COVERING_SPLIT, | ||
| DirectedReadOptions.getDefaultInstance(), | ||
| hintBuilder); | ||
| reqBuilder.getRoutingHintBuilder()); | ||
| } | ||
|
|
||
| public void fillRoutingHint(CommitRequest.Builder reqBuilder) { | ||
| if (reqBuilder.getMutationsCount() == 0) { | ||
| return; | ||
| } | ||
| Mutation mutation = reqBuilder.getMutations(0); | ||
| if (!recipeCache.computeKeys(mutation, reqBuilder.getRoutingHintBuilder())) { | ||
| return; | ||
| } | ||
| fillRoutingHint( | ||
| /* preferLeader= */ true, | ||
| KeyRangeCache.RangeMode.COVERING_SPLIT, | ||
| DirectedReadOptions.getDefaultInstance(), | ||
| reqBuilder.getRoutingHintBuilder()); | ||
| } | ||
|
|
||
| private ChannelEndpoint fillRoutingHint( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe not directly related to this change, but I noticed that this method is unused. Can we remove it? |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| import com.google.protobuf.ByteString; | ||
| import com.google.spanner.v1.BeginTransactionRequest; | ||
| import com.google.spanner.v1.CommitRequest; | ||
| import com.google.spanner.v1.CommitResponse; | ||
| import com.google.spanner.v1.ExecuteSqlRequest; | ||
| import com.google.spanner.v1.PartialResultSet; | ||
| import com.google.spanner.v1.ReadRequest; | ||
|
|
@@ -47,9 +48,10 @@ | |
| /** | ||
| * ManagedChannel that routes eligible requests using location-aware routing hints. | ||
| * | ||
| * <p>Routing hints are applied to streaming read/query and unary ExecuteSql. Commit/Rollback use | ||
| * transaction affinity when available. BeginTransaction is routed only when a mutation key is | ||
| * provided. | ||
| * <p>Routing hints are applied to streaming read/query and unary ExecuteSql. Mutation-based | ||
| * BeginTransaction and Commit requests also carry routing hints when recipes are available. | ||
| * Commit/Rollback use transaction affinity when available. BeginTransaction is routed only when a | ||
| * mutation key is provided. | ||
| */ | ||
| @InternalApi | ||
| final class KeyAwareChannel extends ManagedChannel { | ||
|
|
@@ -355,8 +357,10 @@ public void sendMessage(RequestT message) { | |
| BeginTransactionRequest.Builder reqBuilder = | ||
| ((BeginTransactionRequest) message).toBuilder(); | ||
| String databaseId = parentChannel.extractDatabaseIdFromSession(reqBuilder.getSession()); | ||
| if (databaseId != null && reqBuilder.hasMutationKey()) { | ||
| if (databaseId != null) { | ||
| finder = parentChannel.getOrCreateChannelFinder(databaseId); | ||
| } | ||
| if (finder != null && reqBuilder.hasMutationKey()) { | ||
| endpoint = finder.findServer(reqBuilder); | ||
| } | ||
| if (reqBuilder.hasOptions() && reqBuilder.getOptions().hasReadOnly()) { | ||
|
|
@@ -367,11 +371,19 @@ public void sendMessage(RequestT message) { | |
| } | ||
| message = (RequestT) reqBuilder.build(); | ||
| } else if (message instanceof CommitRequest) { | ||
| CommitRequest request = (CommitRequest) message; | ||
| if (!request.getTransactionId().isEmpty()) { | ||
| endpoint = parentChannel.affinityEndpoint(request.getTransactionId()); | ||
| transactionIdToClear = request.getTransactionId(); | ||
| CommitRequest.Builder reqBuilder = ((CommitRequest) message).toBuilder(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could potentially be a very heavy operation, as a This is also true for the other types of requests here, but those are less likely to be as heavy as a |
||
| String databaseId = parentChannel.extractDatabaseIdFromSession(reqBuilder.getSession()); | ||
| if (databaseId != null) { | ||
| finder = parentChannel.getOrCreateChannelFinder(databaseId); | ||
| } | ||
| if (finder != null) { | ||
| finder.fillRoutingHint(reqBuilder); | ||
| } | ||
|
Comment on lines
+376
to
+392
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| if (!reqBuilder.getTransactionId().isEmpty()) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TransactionId is empty if the |
||
| endpoint = parentChannel.affinityEndpoint(reqBuilder.getTransactionId()); | ||
| transactionIdToClear = reqBuilder.getTransactionId(); | ||
| } | ||
| message = (RequestT) reqBuilder.build(); | ||
| } else if (message instanceof RollbackRequest) { | ||
| RollbackRequest request = (RollbackRequest) message; | ||
| if (!request.getTransactionId().isEmpty()) { | ||
|
|
@@ -610,7 +622,15 @@ public void onMessage(ResponseT message) { | |
| transactionId = transactionIdFromMetadata(response); | ||
| } else if (message instanceof Transaction) { | ||
| Transaction response = (Transaction) message; | ||
| if (response.hasCacheUpdate() && call.channelFinder != null) { | ||
| call.channelFinder.update(response.getCacheUpdate()); | ||
| } | ||
| transactionId = transactionIdFromTransaction(response); | ||
| } else if (message instanceof CommitResponse) { | ||
| CommitResponse response = (CommitResponse) message; | ||
| if (response.hasCacheUpdate() && call.channelFinder != null) { | ||
| call.channelFinder.update(response.getCacheUpdate()); | ||
| } | ||
| } | ||
| if (transactionId != null) { | ||
| if (call.isReadOnlyBegin) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -230,6 +230,23 @@ public void computeKeys(ExecuteSqlRequest.Builder reqBuilder) { | |
| } | ||
| } | ||
|
|
||
| boolean computeKeys(Mutation mutation, RoutingHint.Builder hintBuilder) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method in general is quite hard to read, as it both returns a value that indicates whether the method did something or not, and has a potential side-effect of (maybe) populating the hintBuilder. Is there a better way to do this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed that helper from the mutation routing flow. |
||
| if (!schemaGeneration.isEmpty()) { | ||
| hintBuilder.setSchemaGeneration(schemaGeneration); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This updates the hint builder, but the method might still return false if the |
||
| } | ||
|
|
||
| TargetRange target = mutationToTargetRange(mutation); | ||
| if (target == null) { | ||
| return false; | ||
| } | ||
|
|
||
| hintBuilder.setKey(target.start); | ||
| if (!target.limit.isEmpty()) { | ||
| hintBuilder.setLimitKey(target.limit); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| public TargetRange mutationToTargetRange(Mutation mutation) { | ||
| if (mutation == null) { | ||
| return null; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find some of this code very hard to read. E.g. the fact that
computeKeys(..)returns a boolean and has the side-effect that it modifies theRoutingHintBuilderthat it gets (but only if it returns true, but also sometimes when it returns false, see also below) makes this very hard to understand. I think it would be good if we would take a 'readability sweep' of both this method, but also the code in general.