4343import com .google .cloud .ServiceRpc ;
4444import com .google .cloud .TransportOptions ;
4545import com .google .cloud .grpc .GcpManagedChannelOptions ;
46+ import com .google .cloud .grpc .GcpManagedChannelOptions .GcpChannelPoolOptions ;
4647import com .google .cloud .grpc .GrpcTransportOptions ;
4748import com .google .cloud .spanner .Options .DirectedReadOption ;
4849import com .google .cloud .spanner .Options .QueryOption ;
@@ -154,13 +155,6 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
154155 /** Default min number of channels for dynamic pool. */
155156 public static final int DEFAULT_DYNAMIC_POOL_MIN_CHANNELS = 2 ;
156157
157- // DCP bounds
158- static final int MAX_DYNAMIC_POOL_MAX_RPC = 100 ;
159- static final int MAX_DYNAMIC_POOL_MAX_CHANNELS = 20 ;
160- static final int MAX_DYNAMIC_POOL_INITIAL_SIZE = 256 ;
161- static final Duration MIN_DYNAMIC_POOL_SCALE_DOWN_INTERVAL = Duration .ofSeconds (30 );
162- static final Duration MAX_DYNAMIC_POOL_SCALE_DOWN_INTERVAL = Duration .ofHours (1 );
163-
164158 /**
165159 * Default affinity key lifetime for dynamic channel pool. This is how long to keep an affinity
166160 * key after its last use. Zero means keeping keys forever. Default is 10 minutes, which is
@@ -174,6 +168,39 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
174168 */
175169 public static final Duration DEFAULT_DYNAMIC_POOL_CLEANUP_INTERVAL = Duration .ofMinutes (1 );
176170
171+ /**
172+ * Creates a {@link GcpChannelPoolOptions} instance with Spanner-specific defaults for dynamic
173+ * channel pooling. These defaults are optimized for typical Spanner workloads.
174+ *
175+ * <p>Default values:
176+ *
177+ * <ul>
178+ * <li>Max size: {@value #DEFAULT_DYNAMIC_POOL_MAX_CHANNELS}
179+ * <li>Min size: {@value #DEFAULT_DYNAMIC_POOL_MIN_CHANNELS}
180+ * <li>Initial size: {@value #DEFAULT_DYNAMIC_POOL_INITIAL_SIZE}
181+ * <li>Max RPC per channel: {@value #DEFAULT_DYNAMIC_POOL_MAX_RPC}
182+ * <li>Min RPC per channel: {@value #DEFAULT_DYNAMIC_POOL_MIN_RPC}
183+ * <li>Scale down interval: 3 minutes
184+ * <li>Affinity key lifetime: 10 minutes
185+ * <li>Cleanup interval: 1 minute
186+ * </ul>
187+ *
188+ * @return a new {@link GcpChannelPoolOptions} instance with Spanner defaults
189+ */
190+ public static GcpChannelPoolOptions createDefaultDynamicChannelPoolOptions () {
191+ return GcpChannelPoolOptions .newBuilder ()
192+ .setMaxSize (DEFAULT_DYNAMIC_POOL_MAX_CHANNELS )
193+ .setMinSize (DEFAULT_DYNAMIC_POOL_MIN_CHANNELS )
194+ .setInitSize (DEFAULT_DYNAMIC_POOL_INITIAL_SIZE )
195+ .setDynamicScaling (
196+ DEFAULT_DYNAMIC_POOL_MIN_RPC ,
197+ DEFAULT_DYNAMIC_POOL_MAX_RPC ,
198+ DEFAULT_DYNAMIC_POOL_SCALE_DOWN_INTERVAL )
199+ .setAffinityKeyLifetime (DEFAULT_DYNAMIC_POOL_AFFINITY_KEY_LIFETIME )
200+ .setCleanupInterval (DEFAULT_DYNAMIC_POOL_CLEANUP_INTERVAL )
201+ .build ();
202+ }
203+
177204 private final TransportChannelProvider channelProvider ;
178205
179206 @ SuppressWarnings ("rawtypes" )
@@ -194,14 +221,7 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
194221 private final boolean grpcGcpExtensionEnabled ;
195222 private final GcpManagedChannelOptions grpcGcpOptions ;
196223 private final boolean dynamicChannelPoolEnabled ;
197- private final int dynamicPoolMaxRpc ;
198- private final int dynamicPoolMinRpc ;
199- private final Duration dynamicPoolScaleDownInterval ;
200- private final int dynamicPoolInitialSize ;
201- private final int dynamicPoolMaxChannels ;
202- private final int dynamicPoolMinChannels ;
203- private final Duration dynamicPoolAffinityKeyLifetime ;
204- private final Duration dynamicPoolCleanupInterval ;
224+ private final GcpChannelPoolOptions gcpChannelPoolOptions ;
205225 private final boolean autoThrottleAdministrativeRequests ;
206226 private final RetrySettings retryAdministrativeRequestsSettings ;
207227 private final boolean trackTransactionStarter ;
@@ -863,68 +883,11 @@ protected SpannerOptions(Builder builder) {
863883 dynamicChannelPoolEnabled = false ;
864884 }
865885
866- // Use defaults with proper bounds checking for DCP configuration
867- int effectiveMaxRpc =
868- builder .dynamicPoolMaxRpc != null
869- ? builder .dynamicPoolMaxRpc
870- : DEFAULT_DYNAMIC_POOL_MAX_RPC ;
871- dynamicPoolMaxRpc = effectiveMaxRpc ;
872-
873- int effectiveMinRpc =
874- builder .dynamicPoolMinRpc != null
875- ? builder .dynamicPoolMinRpc
876- : DEFAULT_DYNAMIC_POOL_MIN_RPC ;
877- // Ensure minRpc does not exceed maxRpc
878- dynamicPoolMinRpc = Math .min (effectiveMinRpc , effectiveMaxRpc );
879-
880- dynamicPoolScaleDownInterval =
881- builder .dynamicPoolScaleDownInterval != null
882- ? builder .dynamicPoolScaleDownInterval
883- : DEFAULT_DYNAMIC_POOL_SCALE_DOWN_INTERVAL ;
884-
885- int effectiveMaxChannels =
886- builder .dynamicPoolMaxChannels != null
887- ? builder .dynamicPoolMaxChannels
888- : DEFAULT_DYNAMIC_POOL_MAX_CHANNELS ;
889- dynamicPoolMaxChannels = effectiveMaxChannels ;
890-
891- int effectiveMinChannels =
892- builder .dynamicPoolMinChannels != null
893- ? builder .dynamicPoolMinChannels
894- : DEFAULT_DYNAMIC_POOL_MIN_CHANNELS ;
895- // Ensure minChannels does not exceed maxChannels
896- dynamicPoolMinChannels = Math .min (effectiveMinChannels , effectiveMaxChannels );
897-
898- int effectiveInitialSize =
899- builder .dynamicPoolInitialSize != null
900- ? builder .dynamicPoolInitialSize
901- : DEFAULT_DYNAMIC_POOL_INITIAL_SIZE ;
902- // Ensure initialSize is within [minChannels, maxChannels]
903- dynamicPoolInitialSize =
904- Math .max (dynamicPoolMinChannels , Math .min (effectiveInitialSize , dynamicPoolMaxChannels ));
905-
906- dynamicPoolAffinityKeyLifetime =
907- builder .dynamicPoolAffinityKeyLifetime != null
908- ? builder .dynamicPoolAffinityKeyLifetime
909- : DEFAULT_DYNAMIC_POOL_AFFINITY_KEY_LIFETIME ;
910-
911- // If cleanup interval is not set but affinity key lifetime is set, default to 1/10 of lifetime
912- if (builder .dynamicPoolCleanupInterval != null ) {
913- dynamicPoolCleanupInterval = builder .dynamicPoolCleanupInterval ;
914- } else if (!dynamicPoolAffinityKeyLifetime .isZero ()) {
915- dynamicPoolCleanupInterval = dynamicPoolAffinityKeyLifetime .dividedBy (10 );
916- } else {
917- dynamicPoolCleanupInterval = DEFAULT_DYNAMIC_POOL_CLEANUP_INTERVAL ;
918- }
919-
920- // Validate that if affinity key lifetime is set (non-zero), cleanup interval must be positive.
921- // A zero cleanup interval with a non-zero affinity key lifetime would disable cleanup of stale
922- // affinity keys, potentially leading to a memory leak.
923- Preconditions .checkArgument (
924- dynamicPoolAffinityKeyLifetime .isZero () || !dynamicPoolCleanupInterval .isZero (),
925- "Cleanup interval must be positive when affinity key lifetime is set, got cleanup interval:"
926- + " %s" ,
927- dynamicPoolCleanupInterval );
886+ // Use user-provided GcpChannelPoolOptions or create Spanner-specific defaults
887+ gcpChannelPoolOptions =
888+ builder .gcpChannelPoolOptions != null
889+ ? builder .gcpChannelPoolOptions
890+ : createDefaultDynamicChannelPoolOptions ();
928891
929892 autoThrottleAdministrativeRequests = builder .autoThrottleAdministrativeRequests ;
930893 retryAdministrativeRequestsSettings = builder .retryAdministrativeRequestsSettings ;
@@ -1155,14 +1118,7 @@ public static class Builder
11551118 private boolean grpcGcpExtensionEnabled = true ;
11561119 private GcpManagedChannelOptions grpcGcpOptions ;
11571120 private Boolean dynamicChannelPoolEnabled ;
1158- private Integer dynamicPoolMaxRpc ;
1159- private Integer dynamicPoolMinRpc ;
1160- private Duration dynamicPoolScaleDownInterval ;
1161- private Integer dynamicPoolInitialSize ;
1162- private Integer dynamicPoolMaxChannels ;
1163- private Integer dynamicPoolMinChannels ;
1164- private Duration dynamicPoolAffinityKeyLifetime ;
1165- private Duration dynamicPoolCleanupInterval ;
1121+ private GcpChannelPoolOptions gcpChannelPoolOptions ;
11661122 private RetrySettings retryAdministrativeRequestsSettings =
11671123 DEFAULT_ADMIN_REQUESTS_LIMIT_EXCEEDED_RETRY_SETTINGS ;
11681124 private boolean autoThrottleAdministrativeRequests = false ;
@@ -1236,14 +1192,7 @@ protected Builder() {
12361192 this .grpcGcpExtensionEnabled = options .grpcGcpExtensionEnabled ;
12371193 this .grpcGcpOptions = options .grpcGcpOptions ;
12381194 this .dynamicChannelPoolEnabled = options .dynamicChannelPoolEnabled ;
1239- this .dynamicPoolMaxRpc = options .dynamicPoolMaxRpc ;
1240- this .dynamicPoolMinRpc = options .dynamicPoolMinRpc ;
1241- this .dynamicPoolScaleDownInterval = options .dynamicPoolScaleDownInterval ;
1242- this .dynamicPoolInitialSize = options .dynamicPoolInitialSize ;
1243- this .dynamicPoolMaxChannels = options .dynamicPoolMaxChannels ;
1244- this .dynamicPoolMinChannels = options .dynamicPoolMinChannels ;
1245- this .dynamicPoolAffinityKeyLifetime = options .dynamicPoolAffinityKeyLifetime ;
1246- this .dynamicPoolCleanupInterval = options .dynamicPoolCleanupInterval ;
1195+ this .gcpChannelPoolOptions = options .gcpChannelPoolOptions ;
12471196 this .autoThrottleAdministrativeRequests = options .autoThrottleAdministrativeRequests ;
12481197 this .retryAdministrativeRequestsSettings = options .retryAdministrativeRequestsSettings ;
12491198 this .trackTransactionStarter = options .trackTransactionStarter ;
@@ -1750,128 +1699,33 @@ public Builder disableDynamicChannelPool() {
17501699 }
17511700
17521701 /**
1753- * Sets the maximum number of concurrent RPCs per channel before triggering a scale up of the
1754- * channel pool. Default is 25. Must be between 1 and 100.
1755- *
1756- * <p>This setting is only effective when dynamic channel pooling is enabled.
1757- */
1758- public Builder setDynamicPoolMaxRpc (int maxRpc ) {
1759- Preconditions .checkArgument (
1760- maxRpc >= 1 && maxRpc <= MAX_DYNAMIC_POOL_MAX_RPC ,
1761- "Dynamic pool max RPC must be between 1 and %s, got: %s" ,
1762- MAX_DYNAMIC_POOL_MAX_RPC ,
1763- maxRpc );
1764- this .dynamicPoolMaxRpc = maxRpc ;
1765- return this ;
1766- }
1767-
1768- /**
1769- * Sets the minimum number of concurrent RPCs per channel used for scale down checks. When the
1770- * average concurrent RPCs per channel falls below this value, the pool may scale down. Default
1771- * is 15. Must be between 1 and the configured max RPC value.
1772- *
1773- * <p>This setting is only effective when dynamic channel pooling is enabled.
1774- */
1775- public Builder setDynamicPoolMinRpc (int minRpc ) {
1776- Preconditions .checkArgument (
1777- minRpc >= 1 , "Dynamic pool min RPC must be at least 1, got: %s" , minRpc );
1778- this .dynamicPoolMinRpc = minRpc ;
1779- return this ;
1780- }
1781-
1782- /**
1783- * Sets the interval at which the channel pool checks whether it can scale down. Default is 3
1784- * minutes. Must be between 30 seconds and 1 hour.
1785- *
1786- * <p>This setting is only effective when dynamic channel pooling is enabled.
1787- */
1788- public Builder setDynamicPoolScaleDownInterval (Duration interval ) {
1789- Preconditions .checkNotNull (interval , "Scale down interval cannot be null" );
1790- Preconditions .checkArgument (
1791- interval .compareTo (MIN_DYNAMIC_POOL_SCALE_DOWN_INTERVAL ) >= 0
1792- && interval .compareTo (MAX_DYNAMIC_POOL_SCALE_DOWN_INTERVAL ) <= 0 ,
1793- "Scale down interval must be between %s and %s, got: %s" ,
1794- MIN_DYNAMIC_POOL_SCALE_DOWN_INTERVAL ,
1795- MAX_DYNAMIC_POOL_SCALE_DOWN_INTERVAL ,
1796- interval );
1797- this .dynamicPoolScaleDownInterval = interval ;
1798- return this ;
1799- }
1800-
1801- /**
1802- * Sets the initial number of channels to create for the dynamic channel pool. Default is 4.
1803- * Must be between 1 and 256.
1804- *
1805- * <p>This setting is only effective when dynamic channel pooling is enabled.
1806- */
1807- public Builder setDynamicPoolInitialSize (int initialSize ) {
1808- Preconditions .checkArgument (
1809- initialSize >= 1 && initialSize <= MAX_DYNAMIC_POOL_INITIAL_SIZE ,
1810- "Dynamic pool initial size must be between 1 and %s, got: %s" ,
1811- MAX_DYNAMIC_POOL_INITIAL_SIZE ,
1812- initialSize );
1813- this .dynamicPoolInitialSize = initialSize ;
1814- return this ;
1815- }
1816-
1817- /**
1818- * Sets the maximum number of channels for the dynamic channel pool. Default is 10. Must be
1819- * between 1 and 20.
1702+ * Sets the channel pool options for dynamic channel pooling. Use this to configure the dynamic
1703+ * channel pool behavior when {@link #enableDynamicChannelPool()} is enabled.
18201704 *
1821- * <p>This setting is only effective when dynamic channel pooling is enabled.
1822- */
1823- public Builder setDynamicPoolMaxChannels (int maxChannels ) {
1824- Preconditions .checkArgument (
1825- maxChannels >= 1 && maxChannels <= MAX_DYNAMIC_POOL_MAX_CHANNELS ,
1826- "Dynamic pool max channels must be between 1 and %s, got: %s" ,
1827- MAX_DYNAMIC_POOL_MAX_CHANNELS ,
1828- maxChannels );
1829- this .dynamicPoolMaxChannels = maxChannels ;
1830- return this ;
1831- }
1832-
1833- /**
1834- * Sets the minimum number of channels for the dynamic channel pool. The pool will not scale
1835- * down below this number. Default is 2. Must be between 1 and the configured max channels.
1705+ * <p>If not set, Spanner-specific defaults will be used (see {@link
1706+ * #createDefaultDynamicChannelPoolOptions()}).
18361707 *
1837- * <p>This setting is only effective when dynamic channel pooling is enabled.
1838- */
1839- public Builder setDynamicPoolMinChannels (int minChannels ) {
1840- Preconditions .checkArgument (
1841- minChannels >= 1 , "Dynamic pool min channels must be at least 1, got: %s" , minChannels );
1842- this .dynamicPoolMinChannels = minChannels ;
1843- return this ;
1844- }
1845-
1846- /**
1847- * Sets the affinity key lifetime for the dynamic channel pool. This determines how long to keep
1848- * an affinity key after its last use. Setting this to a non-zero value enables automatic
1849- * cleanup of stale affinity keys, which is important for long-running applications. Default is
1850- * 1 hour. Use {@link Duration#ZERO} to keep keys forever (not recommended for long-running
1851- * applications).
1708+ * <p>Example usage:
18521709 *
1853- * <p>This setting is only effective when dynamic channel pooling is enabled.
1854- */
1855- public Builder setDynamicPoolAffinityKeyLifetime (Duration lifetime ) {
1856- Preconditions .checkNotNull (lifetime , "Affinity key lifetime cannot be null" );
1857- Preconditions .checkArgument (
1858- !lifetime .isNegative (), "Affinity key lifetime must not be negative, got: %s" , lifetime );
1859- this .dynamicPoolAffinityKeyLifetime = lifetime ;
1860- return this ;
1861- }
1862-
1863- /**
1864- * Sets the cleanup interval for the dynamic channel pool affinity keys. This determines how
1865- * frequently the affinity key cleanup process runs. Default is 1 minute. Must be positive if
1866- * affinity key lifetime is set.
1710+ * <pre>{@code
1711+ * SpannerOptions options = SpannerOptions.newBuilder()
1712+ * .setProjectId("my-project")
1713+ * .enableDynamicChannelPool()
1714+ * .setGcpChannelPoolOptions(
1715+ * GcpChannelPoolOptions.newBuilder()
1716+ * .setMaxSize(15)
1717+ * .setMinSize(3)
1718+ * .setInitSize(5)
1719+ * .setDynamicScaling(10, 30, Duration.ofMinutes(5))
1720+ * .build())
1721+ * .build();
1722+ * }</pre>
18671723 *
1868- * <p>This setting is only effective when dynamic channel pooling is enabled.
1724+ * @param gcpChannelPoolOptions the channel pool options to use
1725+ * @return this builder for chaining
18691726 */
1870- public Builder setDynamicPoolCleanupInterval (Duration interval ) {
1871- Preconditions .checkNotNull (interval , "Cleanup interval cannot be null" );
1872- Preconditions .checkArgument (
1873- !interval .isNegative (), "Cleanup interval must not be negative, got: %s" , interval );
1874- this .dynamicPoolCleanupInterval = interval ;
1727+ public Builder setGcpChannelPoolOptions (GcpChannelPoolOptions gcpChannelPoolOptions ) {
1728+ this .gcpChannelPoolOptions = Preconditions .checkNotNull (gcpChannelPoolOptions );
18751729 return this ;
18761730 }
18771731
@@ -2298,52 +2152,13 @@ public boolean isDynamicChannelPoolEnabled() {
22982152 }
22992153
23002154 /**
2301- * Returns the maximum number of concurrent RPCs per channel before triggering a scale up. Default
2302- * is 25.
2303- */
2304- public int getDynamicPoolMaxRpc () {
2305- return dynamicPoolMaxRpc ;
2306- }
2307-
2308- /**
2309- * Returns the minimum number of concurrent RPCs per channel used for scale down checks. Default
2310- * is 15.
2311- */
2312- public int getDynamicPoolMinRpc () {
2313- return dynamicPoolMinRpc ;
2314- }
2315-
2316- /** Returns the scale down check interval. Default is 3 minutes. */
2317- public Duration getDynamicPoolScaleDownInterval () {
2318- return dynamicPoolScaleDownInterval ;
2319- }
2320-
2321- /** Returns the initial number of channels for the dynamic pool. Default is 4. */
2322- public int getDynamicPoolInitialSize () {
2323- return dynamicPoolInitialSize ;
2324- }
2325-
2326- /** Returns the maximum number of channels for the dynamic pool. Default is 10. */
2327- public int getDynamicPoolMaxChannels () {
2328- return dynamicPoolMaxChannels ;
2329- }
2330-
2331- /** Returns the minimum number of channels for the dynamic pool. Default is 2. */
2332- public int getDynamicPoolMinChannels () {
2333- return dynamicPoolMinChannels ;
2334- }
2335-
2336- /**
2337- * Returns the affinity key lifetime for the dynamic pool. This is how long to keep an affinity
2338- * key after its last use. Default is 10 minutes.
2155+ * Returns the channel pool options for dynamic channel pooling. If no options were explicitly
2156+ * set, returns the Spanner-specific defaults.
2157+ *
2158+ * @see #createDefaultDynamicChannelPoolOptions()
23392159 */
2340- public Duration getDynamicPoolAffinityKeyLifetime () {
2341- return dynamicPoolAffinityKeyLifetime ;
2342- }
2343-
2344- /** Returns the cleanup interval for dynamic pool affinity keys. Default is 1 minute. */
2345- public Duration getDynamicPoolCleanupInterval () {
2346- return dynamicPoolCleanupInterval ;
2160+ public GcpChannelPoolOptions getGcpChannelPoolOptions () {
2161+ return gcpChannelPoolOptions ;
23472162 }
23482163
23492164 public boolean isAutoThrottleAdministrativeRequests () {
0 commit comments