diff --git a/autosharding/build.gradle b/autosharding/build.gradle
new file mode 100644
index 00000000000..26ffdacdd6d
--- /dev/null
+++ b/autosharding/build.gradle
@@ -0,0 +1,63 @@
+plugins {
+ id "java-library"
+ id "maven-publish"
+ id "com.google.protobuf"
+ id "jacoco"
+ id "ru.vyarus.animalsniffer"
+}
+
+description = "gRPC: Autosharding Loadbalancing plugin"
+
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.autosharding')
+ }
+}
+
+dependencies {
+ implementation project(':grpc-util'),
+ project(':grpc-core'),
+ project(':grpc-protobuf'),
+ project(':grpc-stub'),
+ libraries.auto.value.annotations,
+ libraries.guava
+ annotationProcessor libraries.auto.value
+ testImplementation libraries.truth,
+ project(':grpc-grpclb'),
+ project(':grpc-inprocess'),
+ project(':grpc-testing'),
+ project(':grpc-testing-proto'),
+ testFixtures(project(':grpc-api')),
+ testFixtures(project(':grpc-core'))
+ signature (libraries.signature.java) {
+ artifact {
+ extension = "signature"
+ }
+ }
+}
+
+tasks.named("compileJava").configure {
+ it.options.compilerArgs += [
+ "-Xlint:-processing",
+ ]
+ appendToProperty(
+ it.options.errorprone.excludedPaths,
+ ".*/build/generated/sources/annotationProcessor/java/.*",
+ "|")
+}
+
+tasks.named("javadoc").configure {
+ failOnError = false
+ exclude 'io/grpc/autosharding/v1/**'
+ exclude 'io/grpc/autosharding/*Provider.java'
+ exclude 'io/grpc/autosharding/internal/**'
+ exclude 'io/grpc/autosharding/Internal*'
+}
+
+tasks.named("jacocoTestReport").configure {
+ classDirectories.from = sourceSets.main.output.collect {
+ fileTree(dir: it, exclude: ['**/io/grpc/autosharding/v1/**'])
+ }
+}
+
+configureProtoCompilation()
diff --git a/autosharding/import.sh b/autosharding/import.sh
new file mode 100644
index 00000000000..545146aa077
--- /dev/null
+++ b/autosharding/import.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+# Copyright 2026 The gRPC Authors
+#
+# 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.
+
+# Update VERSION then execute this script
+
+set -e
+VERSION=3a21f69fe5516fe340af29ef68e327381473ecc7
+DOWNLOAD_URL="https://github.com/GoogleCloudPlatform/autosharding/archive/${VERSION}.tar.gz"
+DOWNLOAD_BASE_DIR="autosharding-${VERSION}"
+SOURCE_PROTO_BASE_DIR="${DOWNLOAD_BASE_DIR}/proto"
+TARGET_PROTO_BASE_DIR="src/main/proto"
+# Sorted alphabetically.
+FILES=(
+autosharding/v1/autosharding.proto
+)
+
+pushd "$(git rev-parse --show-toplevel)/autosharding" > /dev/null
+
+# put the repo in a tmp directory
+tmpdir="$(mktemp -d)"
+trap "rm -rf ${tmpdir}" EXIT
+curl -Ls "${DOWNLOAD_URL}" | tar xz -C "${tmpdir}"
+
+rm -rf "${TARGET_PROTO_BASE_DIR}"
+mkdir -p "${TARGET_PROTO_BASE_DIR}"
+pushd "${TARGET_PROTO_BASE_DIR}" > /dev/null
+
+# copy proto files to project directory
+TOTAL=${#FILES[@]}
+COPIED=0
+for file in "${FILES[@]}"
+do
+ mkdir -p "$(dirname "${file}")"
+ cp -p "${tmpdir}/${SOURCE_PROTO_BASE_DIR}/${file}" "${file}" && (( ++COPIED ))
+done
+popd > /dev/null
+
+popd > /dev/null
+
+echo "Imported ${COPIED} files."
+if (( COPIED != TOTAL )); then
+ echo "Failed importing $(( TOTAL - COPIED )) files." 1>&2
+ exit 1
+fi
diff --git a/autosharding/src/generated/main/grpc/com/google/cloud/autosharding/v1/AutoshardingServiceGrpc.java b/autosharding/src/generated/main/grpc/com/google/cloud/autosharding/v1/AutoshardingServiceGrpc.java
new file mode 100644
index 00000000000..908872d03da
--- /dev/null
+++ b/autosharding/src/generated/main/grpc/com/google/cloud/autosharding/v1/AutoshardingServiceGrpc.java
@@ -0,0 +1,473 @@
+package com.google.cloud.autosharding.v1;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+
+/**
+ *
+ * An auto-sharding service that assigns keys in an application's keyspace to
+ * abstract "endpoints", and which uses load information from clients to update
+ * that assignment over time. Assignments and load reports are scoped to an
+ * abstract "slicing target."
+ * Concrete examples of the concepts in this protocol:
+ * Endpoints:
+ * * Application servers
+ * * Pods in a Kubernetes cluster
+ * * Regions in a multi-regional service
+ * Keys:
+ * * User ids
+ * * Tenant ids in a multi-tenant system
+ * Load:
+ * * Request count
+ * * CPU cost of processing requests
+ *
+ */
+@io.grpc.stub.annotations.GrpcGenerated
+public final class AutoshardingServiceGrpc {
+
+ private AutoshardingServiceGrpc() {}
+
+ public static final java.lang.String SERVICE_NAME = "google.cloud.autosharding.v1.AutoshardingService";
+
+ // Static method descriptors that strictly reflect the proto.
+ private static volatile io.grpc.MethodDescriptor getWatchShardingAssignmentMethod;
+
+ @io.grpc.stub.annotations.RpcMethod(
+ fullMethodName = SERVICE_NAME + '/' + "WatchShardingAssignment",
+ requestType = com.google.cloud.autosharding.v1.WatchShardingAssignmentRequest.class,
+ responseType = com.google.cloud.autosharding.v1.WatchShardingAssignmentResponse.class,
+ methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+ public static io.grpc.MethodDescriptor getWatchShardingAssignmentMethod() {
+ io.grpc.MethodDescriptor getWatchShardingAssignmentMethod;
+ if ((getWatchShardingAssignmentMethod = AutoshardingServiceGrpc.getWatchShardingAssignmentMethod) == null) {
+ synchronized (AutoshardingServiceGrpc.class) {
+ if ((getWatchShardingAssignmentMethod = AutoshardingServiceGrpc.getWatchShardingAssignmentMethod) == null) {
+ AutoshardingServiceGrpc.getWatchShardingAssignmentMethod = getWatchShardingAssignmentMethod =
+ io.grpc.MethodDescriptor.newBuilder()
+ .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+ .setFullMethodName(generateFullMethodName(SERVICE_NAME, "WatchShardingAssignment"))
+ .setSampledToLocalTracing(true)
+ .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+ com.google.cloud.autosharding.v1.WatchShardingAssignmentRequest.getDefaultInstance()))
+ .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+ com.google.cloud.autosharding.v1.WatchShardingAssignmentResponse.getDefaultInstance()))
+ .setSchemaDescriptor(new AutoshardingServiceMethodDescriptorSupplier("WatchShardingAssignment"))
+ .build();
+ }
+ }
+ }
+ return getWatchShardingAssignmentMethod;
+ }
+
+ /**
+ * Creates a new async stub that supports all call types for the service
+ */
+ public static AutoshardingServiceStub newStub(io.grpc.Channel channel) {
+ io.grpc.stub.AbstractStub.StubFactory factory =
+ new io.grpc.stub.AbstractStub.StubFactory() {
+ @java.lang.Override
+ public AutoshardingServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+ return new AutoshardingServiceStub(channel, callOptions);
+ }
+ };
+ return AutoshardingServiceStub.newStub(factory, channel);
+ }
+
+ /**
+ * Creates a new blocking-style stub that supports all types of calls on the service
+ */
+ public static AutoshardingServiceBlockingV2Stub newBlockingV2Stub(
+ io.grpc.Channel channel) {
+ io.grpc.stub.AbstractStub.StubFactory factory =
+ new io.grpc.stub.AbstractStub.StubFactory() {
+ @java.lang.Override
+ public AutoshardingServiceBlockingV2Stub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+ return new AutoshardingServiceBlockingV2Stub(channel, callOptions);
+ }
+ };
+ return AutoshardingServiceBlockingV2Stub.newStub(factory, channel);
+ }
+
+ /**
+ * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+ */
+ public static AutoshardingServiceBlockingStub newBlockingStub(
+ io.grpc.Channel channel) {
+ io.grpc.stub.AbstractStub.StubFactory factory =
+ new io.grpc.stub.AbstractStub.StubFactory() {
+ @java.lang.Override
+ public AutoshardingServiceBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+ return new AutoshardingServiceBlockingStub(channel, callOptions);
+ }
+ };
+ return AutoshardingServiceBlockingStub.newStub(factory, channel);
+ }
+
+ /**
+ * Creates a new ListenableFuture-style stub that supports unary calls on the service
+ */
+ public static AutoshardingServiceFutureStub newFutureStub(
+ io.grpc.Channel channel) {
+ io.grpc.stub.AbstractStub.StubFactory factory =
+ new io.grpc.stub.AbstractStub.StubFactory() {
+ @java.lang.Override
+ public AutoshardingServiceFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+ return new AutoshardingServiceFutureStub(channel, callOptions);
+ }
+ };
+ return AutoshardingServiceFutureStub.newStub(factory, channel);
+ }
+
+ /**
+ *
+ * An auto-sharding service that assigns keys in an application's keyspace to
+ * abstract "endpoints", and which uses load information from clients to update
+ * that assignment over time. Assignments and load reports are scoped to an
+ * abstract "slicing target."
+ * Concrete examples of the concepts in this protocol:
+ * Endpoints:
+ * * Application servers
+ * * Pods in a Kubernetes cluster
+ * * Regions in a multi-regional service
+ * Keys:
+ * * User ids
+ * * Tenant ids in a multi-tenant system
+ * Load:
+ * * Request count
+ * * CPU cost of processing requests
+ *
+ */
+ public interface AsyncService {
+
+ /**
+ *
+ * Opens a stream over which clients report load and the server delivers
+ * sharding assignments.
+ * A given client may use this stream just to receive assignments or also to
+ * report load, and it may opt in or out of reporting load at any time. (An
+ * implementation of the Autosharding service may impose restrictions on
+ * which clients are allowed to report load, and it may respond to clients
+ * improperly reporting load by ignoring their reports or terminating their
+ * streams with an error.)
+ * The client should keep this stream open at all times and reopen the stream
+ * after it closes, with backoff if the stream closed without delivering any
+ * data.
+ *
+ */
+ default io.grpc.stub.StreamObserver watchShardingAssignment(
+ io.grpc.stub.StreamObserver responseObserver) {
+ return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getWatchShardingAssignmentMethod(), responseObserver);
+ }
+ }
+
+ /**
+ * Base class for the server implementation of the service AutoshardingService.
+ *
+ * An auto-sharding service that assigns keys in an application's keyspace to
+ * abstract "endpoints", and which uses load information from clients to update
+ * that assignment over time. Assignments and load reports are scoped to an
+ * abstract "slicing target."
+ * Concrete examples of the concepts in this protocol:
+ * Endpoints:
+ * * Application servers
+ * * Pods in a Kubernetes cluster
+ * * Regions in a multi-regional service
+ * Keys:
+ * * User ids
+ * * Tenant ids in a multi-tenant system
+ * Load:
+ * * Request count
+ * * CPU cost of processing requests
+ *
+ */
+ public static abstract class AutoshardingServiceImplBase
+ implements io.grpc.BindableService, AsyncService {
+
+ @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+ return AutoshardingServiceGrpc.bindService(this);
+ }
+ }
+
+ /**
+ * A stub to allow clients to do asynchronous rpc calls to service AutoshardingService.
+ *
+ * An auto-sharding service that assigns keys in an application's keyspace to
+ * abstract "endpoints", and which uses load information from clients to update
+ * that assignment over time. Assignments and load reports are scoped to an
+ * abstract "slicing target."
+ * Concrete examples of the concepts in this protocol:
+ * Endpoints:
+ * * Application servers
+ * * Pods in a Kubernetes cluster
+ * * Regions in a multi-regional service
+ * Keys:
+ * * User ids
+ * * Tenant ids in a multi-tenant system
+ * Load:
+ * * Request count
+ * * CPU cost of processing requests
+ *
+ */
+ public static final class AutoshardingServiceStub
+ extends io.grpc.stub.AbstractAsyncStub {
+ private AutoshardingServiceStub(
+ io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+ super(channel, callOptions);
+ }
+
+ @java.lang.Override
+ protected AutoshardingServiceStub build(
+ io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+ return new AutoshardingServiceStub(channel, callOptions);
+ }
+
+ /**
+ *
+ * Opens a stream over which clients report load and the server delivers
+ * sharding assignments.
+ * A given client may use this stream just to receive assignments or also to
+ * report load, and it may opt in or out of reporting load at any time. (An
+ * implementation of the Autosharding service may impose restrictions on
+ * which clients are allowed to report load, and it may respond to clients
+ * improperly reporting load by ignoring their reports or terminating their
+ * streams with an error.)
+ * The client should keep this stream open at all times and reopen the stream
+ * after it closes, with backoff if the stream closed without delivering any
+ * data.
+ *
+ */
+ public io.grpc.stub.StreamObserver watchShardingAssignment(
+ io.grpc.stub.StreamObserver responseObserver) {
+ return io.grpc.stub.ClientCalls.asyncBidiStreamingCall(
+ getChannel().newCall(getWatchShardingAssignmentMethod(), getCallOptions()), responseObserver);
+ }
+ }
+
+ /**
+ * A stub to allow clients to do synchronous rpc calls to service AutoshardingService.
+ *
+ * An auto-sharding service that assigns keys in an application's keyspace to
+ * abstract "endpoints", and which uses load information from clients to update
+ * that assignment over time. Assignments and load reports are scoped to an
+ * abstract "slicing target."
+ * Concrete examples of the concepts in this protocol:
+ * Endpoints:
+ * * Application servers
+ * * Pods in a Kubernetes cluster
+ * * Regions in a multi-regional service
+ * Keys:
+ * * User ids
+ * * Tenant ids in a multi-tenant system
+ * Load:
+ * * Request count
+ * * CPU cost of processing requests
+ *
+ */
+ public static final class AutoshardingServiceBlockingV2Stub
+ extends io.grpc.stub.AbstractBlockingStub {
+ private AutoshardingServiceBlockingV2Stub(
+ io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+ super(channel, callOptions);
+ }
+
+ @java.lang.Override
+ protected AutoshardingServiceBlockingV2Stub build(
+ io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+ return new AutoshardingServiceBlockingV2Stub(channel, callOptions);
+ }
+
+ /**
+ *
+ * Opens a stream over which clients report load and the server delivers
+ * sharding assignments.
+ * A given client may use this stream just to receive assignments or also to
+ * report load, and it may opt in or out of reporting load at any time. (An
+ * implementation of the Autosharding service may impose restrictions on
+ * which clients are allowed to report load, and it may respond to clients
+ * improperly reporting load by ignoring their reports or terminating their
+ * streams with an error.)
+ * The client should keep this stream open at all times and reopen the stream
+ * after it closes, with backoff if the stream closed without delivering any
+ * data.
+ *
+ */
+ @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918")
+ public io.grpc.stub.BlockingClientCall
+ watchShardingAssignment() {
+ return io.grpc.stub.ClientCalls.blockingBidiStreamingCall(
+ getChannel(), getWatchShardingAssignmentMethod(), getCallOptions());
+ }
+ }
+
+ /**
+ * A stub to allow clients to do limited synchronous rpc calls to service AutoshardingService.
+ *
+ * An auto-sharding service that assigns keys in an application's keyspace to
+ * abstract "endpoints", and which uses load information from clients to update
+ * that assignment over time. Assignments and load reports are scoped to an
+ * abstract "slicing target."
+ * Concrete examples of the concepts in this protocol:
+ * Endpoints:
+ * * Application servers
+ * * Pods in a Kubernetes cluster
+ * * Regions in a multi-regional service
+ * Keys:
+ * * User ids
+ * * Tenant ids in a multi-tenant system
+ * Load:
+ * * Request count
+ * * CPU cost of processing requests
+ *
+ */
+ public static final class AutoshardingServiceBlockingStub
+ extends io.grpc.stub.AbstractBlockingStub {
+ private AutoshardingServiceBlockingStub(
+ io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+ super(channel, callOptions);
+ }
+
+ @java.lang.Override
+ protected AutoshardingServiceBlockingStub build(
+ io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+ return new AutoshardingServiceBlockingStub(channel, callOptions);
+ }
+ }
+
+ /**
+ * A stub to allow clients to do ListenableFuture-style rpc calls to service AutoshardingService.
+ *
+ * An auto-sharding service that assigns keys in an application's keyspace to
+ * abstract "endpoints", and which uses load information from clients to update
+ * that assignment over time. Assignments and load reports are scoped to an
+ * abstract "slicing target."
+ * Concrete examples of the concepts in this protocol:
+ * Endpoints:
+ * * Application servers
+ * * Pods in a Kubernetes cluster
+ * * Regions in a multi-regional service
+ * Keys:
+ * * User ids
+ * * Tenant ids in a multi-tenant system
+ * Load:
+ * * Request count
+ * * CPU cost of processing requests
+ *
+ */
+ public static final class AutoshardingServiceFutureStub
+ extends io.grpc.stub.AbstractFutureStub {
+ private AutoshardingServiceFutureStub(
+ io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+ super(channel, callOptions);
+ }
+
+ @java.lang.Override
+ protected AutoshardingServiceFutureStub build(
+ io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+ return new AutoshardingServiceFutureStub(channel, callOptions);
+ }
+ }
+
+ private static final int METHODID_WATCH_SHARDING_ASSIGNMENT = 0;
+
+ private static final class MethodHandlers implements
+ io.grpc.stub.ServerCalls.UnaryMethod,
+ io.grpc.stub.ServerCalls.ServerStreamingMethod,
+ io.grpc.stub.ServerCalls.ClientStreamingMethod,
+ io.grpc.stub.ServerCalls.BidiStreamingMethod {
+ private final AsyncService serviceImpl;
+ private final int methodId;
+
+ MethodHandlers(AsyncService serviceImpl, int methodId) {
+ this.serviceImpl = serviceImpl;
+ this.methodId = methodId;
+ }
+
+ @java.lang.Override
+ @java.lang.SuppressWarnings("unchecked")
+ public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) {
+ switch (methodId) {
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ @java.lang.Override
+ @java.lang.SuppressWarnings("unchecked")
+ public io.grpc.stub.StreamObserver invoke(
+ io.grpc.stub.StreamObserver responseObserver) {
+ switch (methodId) {
+ case METHODID_WATCH_SHARDING_ASSIGNMENT:
+ return (io.grpc.stub.StreamObserver) serviceImpl.watchShardingAssignment(
+ (io.grpc.stub.StreamObserver) responseObserver);
+ default:
+ throw new AssertionError();
+ }
+ }
+ }
+
+ public static final io.grpc.ServerServiceDefinition bindService(AsyncService service) {
+ return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+ .addMethod(
+ getWatchShardingAssignmentMethod(),
+ io.grpc.stub.ServerCalls.asyncBidiStreamingCall(
+ new MethodHandlers<
+ com.google.cloud.autosharding.v1.WatchShardingAssignmentRequest,
+ com.google.cloud.autosharding.v1.WatchShardingAssignmentResponse>(
+ service, METHODID_WATCH_SHARDING_ASSIGNMENT)))
+ .build();
+ }
+
+ private static abstract class AutoshardingServiceBaseDescriptorSupplier
+ implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+ AutoshardingServiceBaseDescriptorSupplier() {}
+
+ @java.lang.Override
+ public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+ return com.google.cloud.autosharding.v1.AutoshardingProto.getDescriptor();
+ }
+
+ @java.lang.Override
+ public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+ return getFileDescriptor().findServiceByName("AutoshardingService");
+ }
+ }
+
+ private static final class AutoshardingServiceFileDescriptorSupplier
+ extends AutoshardingServiceBaseDescriptorSupplier {
+ AutoshardingServiceFileDescriptorSupplier() {}
+ }
+
+ private static final class AutoshardingServiceMethodDescriptorSupplier
+ extends AutoshardingServiceBaseDescriptorSupplier
+ implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+ private final java.lang.String methodName;
+
+ AutoshardingServiceMethodDescriptorSupplier(java.lang.String methodName) {
+ this.methodName = methodName;
+ }
+
+ @java.lang.Override
+ public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+ return getServiceDescriptor().findMethodByName(methodName);
+ }
+ }
+
+ private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+ public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+ io.grpc.ServiceDescriptor result = serviceDescriptor;
+ if (result == null) {
+ synchronized (AutoshardingServiceGrpc.class) {
+ result = serviceDescriptor;
+ if (result == null) {
+ serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+ .setSchemaDescriptor(new AutoshardingServiceFileDescriptorSupplier())
+ .addMethod(getWatchShardingAssignmentMethod())
+ .build();
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/autosharding/src/main/proto/autosharding/v1/autosharding.proto b/autosharding/src/main/proto/autosharding/v1/autosharding.proto
new file mode 100644
index 00000000000..c14a4b8921a
--- /dev/null
+++ b/autosharding/src/main/proto/autosharding/v1/autosharding.proto
@@ -0,0 +1,327 @@
+syntax = "proto3";
+
+package google.cloud.autosharding.v1;
+
+import "google/api/field_behavior.proto";
+import "google/protobuf/duration.proto";
+import "google/protobuf/timestamp.proto";
+
+option go_package = "github.com/GoogleCloudPlatform/autosharding/apiv1;autoshardingpb";
+option java_package = "com.google.cloud.autosharding.v1";
+option java_multiple_files = true;
+option java_outer_classname = "AutoshardingProto";
+
+// An auto-sharding service that assigns keys in an application's keyspace to
+// abstract "endpoints", and which uses load information from clients to update
+// that assignment over time. Assignments and load reports are scoped to an
+// abstract "slicing target."
+//
+// Concrete examples of the concepts in this protocol:
+//
+// Endpoints:
+// * Application servers
+// * Pods in a Kubernetes cluster
+// * Regions in a multi-regional service
+//
+// Keys:
+// * User ids
+// * Tenant ids in a multi-tenant system
+//
+// Load:
+// * Request count
+// * CPU cost of processing requests
+service AutoshardingService {
+ // Opens a stream over which clients report load and the server delivers
+ // sharding assignments.
+ //
+ // A given client may use this stream just to receive assignments or also to
+ // report load, and it may opt in or out of reporting load at any time. (An
+ // implementation of the Autosharding service may impose restrictions on
+ // which clients are allowed to report load, and it may respond to clients
+ // improperly reporting load by ignoring their reports or terminating their
+ // streams with an error.)
+ //
+ // The client should keep this stream open at all times and reopen the stream
+ // after it closes, with backoff if the stream closed without delivering any
+ // data.
+ rpc WatchShardingAssignment(stream WatchShardingAssignmentRequest)
+ returns (stream WatchShardingAssignmentResponse);
+}
+
+// In each instance of this message, the client MUST set exactly one field.
+message WatchShardingAssignmentRequest {
+ // Initial client-side configuration necessary to set up the stream. This
+ // field MUST be set in the first message sent, and it MUST NOT be set in
+ // subsequent messages.
+ InitialClientConfig initial_client_config = 1
+ [(google.api.field_behavior) = OPTIONAL];
+
+ // An aggregated snapshot of load data collected by this client.
+ //
+ // The client MUST include all load data it has collected since the last
+ // report in this report (irrespective of whether the last report was sent
+ // on this or a prior physical stream), and it MUST NOT include that data in
+ // any subsequent report.
+ //
+ // This field MUST NOT be set in the first message sent.
+ LoadReport load_report = 2 [(google.api.field_behavior) = OPTIONAL];
+
+ // The client's acknowledgment of the last received assignment.
+ //
+ // The client MUST respond with this message to every received
+ // `AssignmentMetadata` message from the server.
+ AssignmentAck assignment_ack = 3 [(google.api.field_behavior) = OPTIONAL];
+}
+
+// In each instance of this message, the server will set exactly one field.
+message WatchShardingAssignmentResponse {
+ // Configuration dictating how the client should aggregate and send load
+ // reports.
+ //
+ // The client MUST wait to send load reports until it has received the first
+ // version of this config from the server (on this physical stream).
+ LoadReportingConfig config = 1;
+
+ // A chunk of a logical assignment.
+ //
+ // The server delivers an assignment as a sequence of one or more
+ // `AssignmentChunk` messages followed by a single `AssignmentMetadata`
+ // message. Clients MUST wait for an `AssignmentMetadata` message and combine
+ // the prior chunks to form a logical assignment before using it.
+ AssignmentChunk chunk = 2;
+
+ // Metadata associated with the most recent chunked assignment. Acts as a
+ // trailer indicating that the server has finished delivering all chunks for
+ // that assignment.
+ AssignmentMetadata metadata = 3;
+}
+
+// Initial client-side configuration necessary to set up the stream.
+message InitialClientConfig {
+ // The slicing target to which this client's load reports and assignments
+ // apply.
+ //
+ // If this target is unknown to the server, the server will terminate the
+ // stream with a NOT_FOUND error.
+ //
+ // If this client does not have permission to connect to this target, the
+ // server will terminate the stream with a PERMISSION_DENIED error.
+ string target = 1 [(google.api.field_behavior) = REQUIRED];
+
+ // A UUID generated by this client upon instantiation. The client MUST reuse
+ // the same UUID across Autosharding stream reconnections. It is
+ // permissible but not expected for the client to use the same UUID across
+ // process restarts.
+ string client_uuid = 2 [(google.api.field_behavior) = REQUIRED];
+
+ // The latest assignment generation that this client has received. If set, the
+ // server will only send newer assignments. If unset (= zero), the server
+ // will send the latest assignment immediately.
+ int64 latest_generation = 3 [(google.api.field_behavior) = OPTIONAL];
+}
+
+// Server-specified configuration dictating how the client should report load.
+message LoadReportingConfig {
+ // The maximum fraction of total load in a `Load` message that appears in a
+ // single `RangeLoad` message, if the range is not a single-key range. When
+ // aggregating load data into a load report, the client MUST honor the latest
+ // received value of this field as of when it started aggregating. (See the
+ // `RangeLoad` documentation for a description of how the client should
+ // aggregate load data into load reports.)
+ //
+ // This field will always be in the range [0, 1].
+ double load_quantum_fraction = 1;
+
+ // The maximum length of slice keys in load reports. When aggregating load
+ // data into a load report, the client MUST honor the latest received value
+ // of this field as of when it started aggregating. (See the `RangeLoad`
+ // documentation for a description of how the client should aggregate load
+ // data into load reports.)
+ int32 max_load_balancing_resolution_bytes = 2;
+
+ // The interval at which this client should send `LoadReport` messages. After
+ // it sends a given load report to the server, the client MUST wait for
+ // roughly the interval specified by the latest received value of this field
+ // before it sends its next load report.
+ google.protobuf.Duration load_reporting_interval = 3;
+
+ // The lower bound on a load report's `start_time`, relative to the
+ // client's current time when it starts computing the load report. Clients
+ // should discard load data before this threshold.
+ google.protobuf.Duration load_report_start_threshold = 4;
+}
+
+// A load report consisting of per-metric load snapshots.
+message LoadReport {
+ // Snapshots of load in the keyspace associated with one or more metrics.
+ //
+ // A key in this map is a named load metric to which the load applies. This
+ // metric MUST be a "cost" metric, i.e., one where the total load for a given
+ // application key over a time interval is the sum of the individual load
+ // values recorded for that key over the time interval, and where the number
+ // of endpoints assigned a given application key does not affect the total
+ // load for that key.
+ map metric_loads = 1 [(google.api.field_behavior) = REQUIRED];
+
+ // The inclusive start timestamp of the time range to which this load report
+ // applies.
+ google.protobuf.Timestamp start_time = 2
+ [(google.api.field_behavior) = REQUIRED];
+
+ // The exclusive end timestamp of the time range to which this load report
+ // applies.
+ //
+ // This end time MUST be roughly equal to the client's current time, i.e.,
+ // the client must send this load report to the server immediately after
+ // computing it.
+ //
+ // To ensure good load-balancing behavior, the clock used to set these
+ // timestamps MUST be synchronized to within ~one second of the clocks of
+ // other clients and the Autosharding service. Implementations of the
+ // Autosharding service should have some mechanism for informing client
+ // owners when they detect significantly delayed client load reports (which
+ // suggests an actionable issue related to either network delays or clock
+ // skew).
+ google.protobuf.Timestamp end_time = 3
+ [(google.api.field_behavior) = REQUIRED];
+}
+
+// A per-key-range load snapshot.
+message Load {
+ // An ordered set of per-key-range load. Each range's inclusive start is
+ // implicitly the exclusive end of the previous range in the ordered set.
+ //
+ // Entries here MUST have lexicographically increasing start keys, and two
+ // entries MUST NOT have the same start key.
+ //
+ // Start keys in these entries MUST be at most
+ // `max_load_balancing_resolution_bytes` bytes (using the latest received
+ // value as of when the client started aggregating this load report).
+ //
+ // Clients MUST aggregate observed load values for a given key during this
+ // report's time interval into a single value for the key by summing the
+ // values, and clients MUST aggregate per-key load values into a single load
+ // value for a key range by summing the values.
+ //
+ // A single range MUST contain at most `load_quantum_fraction` of the total
+ // load across all ranges, unless it is a single-key range.
+ //
+ // A range is a single-key range if the next range's start key is the
+ // immediate lexicographic successor of this range's start key in the space of
+ // bytestrings of length `max_load_balancing_resolution_bytes` or if the start
+ // key is the largest allowed key.
+ //
+ // Subject to the above constraints, clients MUST aggregate this load data
+ // into as few key ranges as possible.
+ //
+ // Clients under memory pressure may report "inexact" load according to a
+ // strategy specified by the Autosharding service owner, e.g., by
+ // sampling observed load data rather than retaining each data point.
+ //
+ // Implementation sketch for aggregating load into a load report: maintain a
+ // hash map from key to total load observed since the last report. When it is
+ // time to send a new report, truncate the keys to
+ // `max_load_balancing_resolution_bytes`, then sort the load values
+ // lexicographically by key. Walk the keyspace starting from the min key,
+ // maintaining a current "open range" and its total load. If adding the
+ // current key to the current open range would push its total load over
+ // `load_quantum_fraction`, then "close" the range and start a new open range.
+ // Otherwise, add the current key to the current open range.
+ repeated RangeLoad range_loads = 1;
+}
+
+// Load associated with a key range (whose end is implicitly defined by the
+// next range's start).
+message RangeLoad {
+ // The key range's start (inclusive).
+ bytes start_key = 1;
+
+ // The aggregate load associated with this key range observed by this client
+ // during the time interval in this load report.
+ double load = 2;
+}
+
+// The client's acknowledgment of the last received assignment.
+message AssignmentAck {
+ // The generation of the assignment this acknowledgment applies to.
+ int64 generation = 1 [(google.api.field_behavior) = REQUIRED];
+
+ // Whether the client accepted the assignment.
+ //
+ // If the client did not accept the assignment, it MUST set this field to
+ // false and continue using the previous assignment, and it MUST set the
+ // `error_message` field to explain the reason for rejection.
+ bool accepted = 2 [(google.api.field_behavior) = REQUIRED];
+
+ // A textual error message from the client.
+ //
+ // This field MUST be set if and only if the client detected issues with the
+ // assignment, explaining those issues. The client may set this field even if
+ // it accepted the assignment, in which case the issues are not blocking from
+ // using the assignment.
+ //
+ // Service owners may use this information for fixing problems.
+ //
+ // The length of this field MUST NOT exceed 512 characters (Unicode code
+ // points, see https://google.aip.dev/210).
+ string error_message = 3 [
+ (google.api.field_behavior) = OPTIONAL
+ ];
+}
+
+// Metadata associated with an assignment.
+message AssignmentMetadata {
+ // The assignment's unique and monotonically-increasing version number.
+ int64 generation = 1;
+}
+
+// A portion of an assignment of slices to endpoints. The individual fields'
+// documentation describes how to reassemble a set of chunks into a complete
+// assignment.
+message AssignmentChunk {
+ // A chunk of the set of assignments for individual slices.
+ //
+ // Clients MUST reassemble the complete `slice_assignments` list by
+ // concatenating the contents of this field across chunks (in any order).
+ repeated SliceAssignment slice_assignments = 1;
+
+ // A chunk of the list of all endpoints present in this assignment.
+ //
+ // Clients MUST reassemble the complete `endpoints` list by concatenating the
+ // contents of this field across chunks, in chunk order.
+ repeated EndpointState endpoints = 2;
+}
+
+// An endpoint and any associated metadata.
+message EndpointState {
+ // The endpoint's name.
+ string endpoint = 1;
+}
+
+// The assignment for an individual slice.
+message SliceAssignment {
+ // The slice to which endpoints are assigned.
+ Slice slice = 1;
+
+ // The endpoints assigned to this slice.
+ repeated PerSliceEndpointState endpoints = 2;
+}
+
+// A single slice, i.e., a continuous range of keys.
+message Slice {
+ // The slice's start (inclusive). Will be at most 512 bytes.
+ bytes start_key = 1;
+
+ // The slice's end (exclusive). Will be at most 512 bytes. If this field is
+ // unset, this slice contains the largest allowed key (the 512-byte string
+ // 0xFFFF...).
+ optional bytes end_key = 2;
+}
+
+// An endpoint assigned to a slice, along with any associated metadata about
+// the endpoint in the context of that slice.
+message PerSliceEndpointState {
+ // An endpoint, specified as an index into the complete assignment's
+ // reassembled `endpoints` list.
+ int32 endpoint_index = 1;
+}
diff --git a/settings.gradle b/settings.gradle
index abd30a63956..0322a9a1fbd 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -95,6 +95,7 @@ include ":grpc-inprocess"
include ":grpc-util"
include ":grpc-opentelemetry"
include ":grpc-context-override-opentelemetry"
+include ":grpc-autosharding"
project(':grpc-api').projectDir = "$rootDir/api" as File
project(':grpc-core').projectDir = "$rootDir/core" as File
@@ -132,6 +133,7 @@ project(':grpc-inprocess').projectDir = "$rootDir/inprocess" as File
project(':grpc-util').projectDir = "$rootDir/util" as File
project(':grpc-opentelemetry').projectDir = "$rootDir/opentelemetry" as File
project(':grpc-context-override-opentelemetry').projectDir = "$rootDir/contextstorage" as File
+project(':grpc-autosharding').projectDir = "$rootDir/autosharding" as File
if (settings.hasProperty('skipCodegen') && skipCodegen.toBoolean()) {
println '*** Skipping the build of codegen and compilation of proto files because skipCodegen=true'