From 2c9da0da95d2cfc02e0cc30b817385284fa70af7 Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Thu, 8 Jan 2026 08:45:46 +0530 Subject: [PATCH] feat: add ChannelFinder server interfaces This commit adds the server abstraction interfaces for location-aware routing: - ChannelFinderServer: Interface representing a Spanner server endpoint with address, health check, and channel access - ChannelFinderServerFactory: Factory interface for creating and caching server connections - GrpcChannelFinderServerFactory: gRPC implementation that creates and manages gRPC channels for different server endpoints These interfaces enable the client to maintain connections to multiple Spanner servers and route requests directly to the appropriate server based on key location information. This is part of the experimental location-aware routing for improved latency. --- .../spanner/spi/v1/ChannelFinderServer.java | 28 ++++++ .../spi/v1/ChannelFinderServerFactory.java | 24 +++++ .../v1/GrpcChannelFinderServerFactory.java | 98 +++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/ChannelFinderServer.java create mode 100644 google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/ChannelFinderServerFactory.java create mode 100644 google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GrpcChannelFinderServerFactory.java diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/ChannelFinderServer.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/ChannelFinderServer.java new file mode 100644 index 0000000000..27a0b5d31a --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/ChannelFinderServer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.spi.v1; + +import io.grpc.ManagedChannel; + +/** Represents a Spanner server endpoint for location-aware routing. */ +public interface ChannelFinderServer { + String getAddress(); + + boolean isHealthy(); + + ManagedChannel getChannel(); // Added to get the underlying channel for RPC calls +} diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/ChannelFinderServerFactory.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/ChannelFinderServerFactory.java new file mode 100644 index 0000000000..c81cf82c0d --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/ChannelFinderServerFactory.java @@ -0,0 +1,24 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.spi.v1; + +/** Factory for creating and caching server connections for location-aware routing. */ +public interface ChannelFinderServerFactory { + ChannelFinderServer defaultServer(); + + ChannelFinderServer create(String address); +} diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GrpcChannelFinderServerFactory.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GrpcChannelFinderServerFactory.java new file mode 100644 index 0000000000..8c120f0773 --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GrpcChannelFinderServerFactory.java @@ -0,0 +1,98 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.spi.v1; + +import com.google.api.gax.grpc.GrpcTransportChannel; +import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; +import io.grpc.ManagedChannel; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +class GrpcChannelFinderServerFactory implements ChannelFinderServerFactory { + private final InstantiatingGrpcChannelProvider.Builder channelBuilder; + private final Map servers = new ConcurrentHashMap<>(); + private final GrpcChannelFinderServer defaultServer; + + public GrpcChannelFinderServerFactory(InstantiatingGrpcChannelProvider.Builder channelBuilder) + throws IOException { + this.channelBuilder = channelBuilder; + // The "default" server will use the original endpoint from the builder. + this.defaultServer = + new GrpcChannelFinderServer(this.channelBuilder.getEndpoint(), channelBuilder.build()); + this.servers.put(this.defaultServer.getAddress(), this.defaultServer); + } + + @Override + public ChannelFinderServer defaultServer() { + return defaultServer; + } + + @Override + public ChannelFinderServer create(String address) { + return servers.computeIfAbsent( + address, + addr -> { + try { + // Modify the builder to use the new address + synchronized (channelBuilder) { + InstantiatingGrpcChannelProvider.Builder newBuilder = + channelBuilder.setEndpoint(addr); + return new GrpcChannelFinderServer(addr, newBuilder.build()); + } + } catch (IOException e) { + throw new RuntimeException("Failed to create channel for address: " + addr, e); + } + }); + } + + static class GrpcChannelFinderServer implements ChannelFinderServer { + private final String address; + private final ManagedChannel channel; + + public GrpcChannelFinderServer(String address, InstantiatingGrpcChannelProvider provider) + throws IOException { + this.address = address; + // It's assumed that getTransportChannel() returns a ManagedChannel or can be cast to one. + // For this example, GrpcTransportChannel is used as in KeyAwareChannel. + GrpcTransportChannel transportChannel = (GrpcTransportChannel) provider.getTransportChannel(); + this.channel = (ManagedChannel) transportChannel.getChannel(); + } + + // Constructor for the default server that already has a channel + public GrpcChannelFinderServer(String address, ManagedChannel channel) { + this.address = address; + this.channel = channel; + } + + @Override + public String getAddress() { + return address; + } + + @Override + public boolean isHealthy() { + // A simple health check. In a real scenario, this might involve a ping or other checks. + return !channel.isShutdown() && !channel.isTerminated(); + } + + @Override + public ManagedChannel getChannel() { + return channel; + } + } +}