From 408692a957d30f1af8bd674e132dc043713fcce0 Mon Sep 17 00:00:00 2001 From: yantian Date: Thu, 16 Jan 2025 11:32:41 +0800 Subject: [PATCH 1/8] add http config --- .../org/apache/paimon/rest/HttpClient.java | 10 +++++-- .../apache/paimon/rest/HttpClientOptions.java | 28 +++++++++++++++++-- .../paimon/rest/RESTCatalogOptions.java | 25 +++++++++++++++-- .../apache/paimon/rest/HttpClientTest.java | 8 +++++- 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java index 08d6c8a050a0..c27f1cf0008d 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java @@ -26,6 +26,7 @@ import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.core.JsonProcessingException; +import okhttp3.ConnectionPool; import okhttp3.Dispatcher; import okhttp3.Headers; import okhttp3.MediaType; @@ -40,6 +41,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; import static okhttp3.ConnectionSpec.CLEARTEXT; import static okhttp3.ConnectionSpec.COMPATIBLE_TLS; @@ -191,11 +193,15 @@ private static OkHttpClient createHttpClient(HttpClientOptions httpClientOptions BlockingQueue workQueue = new SynchronousQueue<>(); ExecutorService executorService = createCachedThreadPool(httpClientOptions.threadPoolSize(), THREAD_NAME, workQueue); - + ConnectionPool connectionPool = + new ConnectionPool(httpClientOptions.maxConnections(), 300, TimeUnit.MILLISECONDS); + Dispatcher dispatcher = new Dispatcher(executorService); + dispatcher.setMaxRequestsPerHost(httpClientOptions.maxRetries()); OkHttpClient.Builder builder = new OkHttpClient.Builder() - .dispatcher(new Dispatcher(executorService)) + .dispatcher(dispatcher) .retryOnConnectionFailure(true) + .connectionPool(connectionPool) .connectionSpecs(Arrays.asList(MODERN_TLS, COMPATIBLE_TLS, CLEARTEXT)); httpClientOptions.connectTimeout().ifPresent(builder::connectTimeout); httpClientOptions.readTimeout().ifPresent(builder::readTimeout); diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClientOptions.java b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClientOptions.java index 00ae1a529e89..35f7de1b0897 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClientOptions.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClientOptions.java @@ -32,16 +32,25 @@ public class HttpClientOptions { @Nullable private final Duration connectTimeout; @Nullable private final Duration readTimeout; private final int threadPoolSize; + private final int maxConnections; + private final int maxConnectionsPerRoute; + private final int maxRetries; public HttpClientOptions( String uri, @Nullable Duration connectTimeout, @Nullable Duration readTimeout, - int threadPoolSize) { + int threadPoolSize, + int maxConnections, + int maxConnectionsPerRoute, + int maxRetries) { this.uri = uri; this.connectTimeout = connectTimeout; this.readTimeout = readTimeout; this.threadPoolSize = threadPoolSize; + this.maxConnections = maxConnections; + this.maxConnectionsPerRoute = maxConnectionsPerRoute; + this.maxRetries = maxRetries; } public static HttpClientOptions create(Options options) { @@ -49,7 +58,10 @@ public static HttpClientOptions create(Options options) { options.get(RESTCatalogOptions.URI), options.get(RESTCatalogOptions.CONNECTION_TIMEOUT), options.get(RESTCatalogOptions.READ_TIMEOUT), - options.get(RESTCatalogOptions.THREAD_POOL_SIZE)); + options.get(RESTCatalogOptions.THREAD_POOL_SIZE), + options.get(RESTCatalogOptions.MAX_CONNECTIONS), + options.get(RESTCatalogOptions.MAX_CONNECTIONS_PER_ROUTE), + options.get(RESTCatalogOptions.MAX_RETIES)); } public String uri() { @@ -67,4 +79,16 @@ public Optional readTimeout() { public int threadPoolSize() { return threadPoolSize; } + + public int maxConnections() { + return maxConnections; + } + + public int maxConnectionsPerRoute() { + return maxConnectionsPerRoute; + } + + public int maxRetries() { + return maxRetries; + } } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java index 1af64def4f71..12c39565d0e8 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java @@ -32,18 +32,39 @@ public class RESTCatalogOptions { .noDefaultValue() .withDescription("REST Catalog server's uri."); + // default same with iceberg 3min public static final ConfigOption CONNECTION_TIMEOUT = ConfigOptions.key("rest.client.connection-timeout") .durationType() - .noDefaultValue() + .defaultValue(Duration.ofSeconds(180)) .withDescription("REST Catalog http client connect timeout."); + // as read timeout need less than connect timeout default 170s public static final ConfigOption READ_TIMEOUT = ConfigOptions.key("rest.client.read-timeout") .durationType() - .noDefaultValue() + .defaultValue(Duration.ofSeconds(170)) .withDescription("REST Catalog http client read timeout."); + public static final ConfigOption MAX_CONNECTIONS = + ConfigOptions.key("rest.client..max-connections") + .intType() + .defaultValue(100) + .withDescription("REST Catalog http client's max connections."); + + // need less than max connections default 100 + public static final ConfigOption MAX_CONNECTIONS_PER_ROUTE = + ConfigOptions.key("connections-per-route") + .intType() + .defaultValue(100) + .withDescription("REST Catalog http client's max connections per route."); + + public static final ConfigOption MAX_RETIES = + ConfigOptions.key("rest.client..max-retries") + .intType() + .defaultValue(5) + .withDescription("REST Catalog http client's max retry times."); + public static final ConfigOption THREAD_POOL_SIZE = ConfigOptions.key("rest.client.num-threads") .intType() diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java index 161dbaf3bb50..70db2ab0aa53 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java @@ -57,7 +57,13 @@ public void setUp() throws Exception { errorHandler = DefaultErrorHandler.getInstance(); HttpClientOptions httpClientOptions = new HttpClientOptions( - server.getBaseUrl(), Duration.ofSeconds(3), Duration.ofSeconds(3), 1); + server.getBaseUrl(), + Duration.ofSeconds(3), + Duration.ofSeconds(3), + 1, + 10, + 10, + 2); mockResponseData = new MockRESTData(MOCK_PATH); mockResponseDataStr = server.createResponseBody(mockResponseData); errorResponseStr = From 01fc6970a524a1b106df734cf98d4d43d7970083 Mon Sep 17 00:00:00 2001 From: yantian Date: Thu, 16 Jan 2025 16:20:53 +0800 Subject: [PATCH 2/8] add ExponentialHttpRetryInterceptor to handle http retry in RESTCatalog --- .../rest/ExponentialHttpRetryInterceptor.java | 175 ++++++++++++++++++ .../org/apache/paimon/rest/HttpClient.java | 5 +- .../ExponentialHttpRetryInterceptorTest.java | 126 +++++++++++++ .../apache/paimon/rest/HttpClientTest.java | 17 ++ 4 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java create mode 100644 paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java b/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java new file mode 100644 index 000000000000..2648baa16568 --- /dev/null +++ b/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.paimon.rest; + +import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableSet; +import org.apache.paimon.shade.guava30.com.google.common.net.HttpHeaders; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +import javax.net.ssl.SSLException; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ConnectException; +import java.net.NoRouteToHostException; +import java.net.UnknownHostException; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Defines an exponential HTTP request retry interceptor. + * + *
    + *
  • InterruptedIOException + *
  • UnknownHostException + *
  • ConnectException + *
  • NoRouteToHostException + *
  • SSLException + *
+ * + * The following retrievable HTTP status codes are defined: + * + *
    + *
  • TOO_MANY_REQUESTS (429) + *
  • BAD_GATEWAY (502) + *
  • SERVICE_UNAVAILABLE (503) + *
  • GATEWAY_TIMEOUT (504) + *
+ * + * The following retrievable HTTP method which is idempotent are defined: + * + *
    + *
  • GET + *
  • HEAD + *
  • PUT + *
  • DELETE + *
  • TRACE + *
  • OPTIONS + *
+ */ +public class ExponentialHttpRetryInterceptor implements Interceptor { + private final int maxRetries; + private final Set> nonRetriableExceptions; + private final Set retrievableCodes; + private final Set retrievableMethods; + + public ExponentialHttpRetryInterceptor(int maxRetries) { + this.maxRetries = maxRetries; + this.retrievableMethods = + ImmutableSet.of("GET", "HEAD", "PUT", "DELETE", "TRACE", "OPTIONS"); + this.retrievableCodes = ImmutableSet.of(429, 503, 502, 504); + this.nonRetriableExceptions = + ImmutableSet.of( + InterruptedIOException.class, + UnknownHostException.class, + ConnectException.class, + NoRouteToHostException.class, + SSLException.class); + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + Response response = null; + + for (int retryCount = 1; ; retryCount++) { + try { + response = chain.proceed(request); + } catch (IOException e) { + if (needRetry(request.method(), e, retryCount)) { + wait(response, retryCount); + continue; + } + } + if (needRetry(response, retryCount)) { + if (response != null) { + response.close(); + } + wait(response, retryCount); + } else { + return response; + } + } + } + + public boolean needRetry(Response response, int execCount) { + if (execCount > maxRetries) { + return false; + } + if (response != null + && (response.isSuccessful() || !retrievableCodes.contains(response.code()))) { + return false; + } + return true; + } + + public boolean needRetry(String Method, IOException e, int execCount) { + if (execCount > maxRetries) { + return false; + } + if (!retrievableMethods.contains(Method)) { + return false; + } + if (nonRetriableExceptions.contains(e.getClass())) { + return false; + } else { + for (Class rejectException : nonRetriableExceptions) { + if (rejectException.isInstance(e)) { + return false; + } + } + } + return true; + } + + public long getRetryIntervalInMilliseconds(Response response, int execCount) { + // a server may send a 429 / 503 with a Retry-After header + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After + String retryAfterStrInSecond = + response == null ? null : response.header(HttpHeaders.RETRY_AFTER); + Long retryAfter = null; + if (retryAfterStrInSecond != null) { + try { + retryAfter = Long.parseLong(retryAfterStrInSecond) * 1000; + } catch (Throwable ignore) { + } + + if (retryAfter != null && retryAfter > 0) { + return retryAfter; + } + } + + int delayMillis = 1000 * (int) Math.min(Math.pow(2.0, (long) execCount - 1.0), 64.0); + int jitter = ThreadLocalRandom.current().nextInt(Math.max(1, (int) (delayMillis * 0.1))); + + return delayMillis + jitter; + } + + private void wait(Response response, int retryCount) throws InterruptedIOException { + try { + Thread.sleep(getRetryIntervalInMilliseconds(response, retryCount)); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedIOException(); + } + } +} diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java index c27f1cf0008d..6537dfb7e60e 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java @@ -202,7 +202,10 @@ private static OkHttpClient createHttpClient(HttpClientOptions httpClientOptions .dispatcher(dispatcher) .retryOnConnectionFailure(true) .connectionPool(connectionPool) - .connectionSpecs(Arrays.asList(MODERN_TLS, COMPATIBLE_TLS, CLEARTEXT)); + .connectionSpecs(Arrays.asList(MODERN_TLS, COMPATIBLE_TLS, CLEARTEXT)) + .addInterceptor( + new ExponentialHttpRetryInterceptor( + httpClientOptions.maxRetries())); httpClientOptions.connectTimeout().ifPresent(builder::connectTimeout); httpClientOptions.readTimeout().ifPresent(builder::readTimeout); diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java new file mode 100644 index 000000000000..ae999d9e5e9f --- /dev/null +++ b/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.paimon.rest; + +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.hc.core5.http.HttpHeaders; +import org.junit.jupiter.api.Test; + +import javax.net.ssl.SSLException; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ConnectException; +import java.net.NoRouteToHostException; +import java.net.UnknownHostException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** Test for {@link ExponentialHttpRetryInterceptor}. */ +class ExponentialHttpRetryInterceptorTest { + private final int maxRetries = 5; + private ExponentialHttpRetryInterceptor interceptor = + new ExponentialHttpRetryInterceptor(maxRetries); + + @Test + void testNeedRetryByMethod() { + assertThat(interceptor.needRetry("GET", new IOException(), 1)).isTrue(); + assertThat(interceptor.needRetry("HEAD", new IOException(), 1)).isTrue(); + assertThat(interceptor.needRetry("PUT", new IOException(), 1)).isTrue(); + assertThat(interceptor.needRetry("DELETE", new IOException(), 1)).isTrue(); + assertThat(interceptor.needRetry("TRACE", new IOException(), 1)).isTrue(); + assertThat(interceptor.needRetry("OPTIONS", new IOException(), 1)).isTrue(); + assertThat(interceptor.needRetry("POST", new IOException(), 1)).isFalse(); + assertThat(interceptor.needRetry("PATCH", new IOException(), 1)).isFalse(); + assertThat(interceptor.needRetry("CONNECT", new IOException(), 1)).isFalse(); + assertThat(interceptor.needRetry("GET", new IOException(), maxRetries + 1)).isFalse(); + } + + @Test + void testNeedRetryByException() { + assertThat(interceptor.needRetry("GET", new InterruptedIOException(), 1)).isFalse(); + assertThat(interceptor.needRetry("GET", new UnknownHostException(), 1)).isFalse(); + assertThat(interceptor.needRetry("GET", new ConnectException(), 1)).isFalse(); + assertThat(interceptor.needRetry("GET", new NoRouteToHostException(), 1)).isFalse(); + assertThat(interceptor.needRetry("GET", new SSLException("error"), 1)).isFalse(); + } + + @Test + void testRetryByResponse() { + assertThat(interceptor.needRetry(createResponse(429), 1)).isTrue(); + assertThat(interceptor.needRetry(createResponse(503), 1)).isTrue(); + assertThat(interceptor.needRetry(createResponse(502), 1)).isTrue(); + assertThat(interceptor.needRetry(createResponse(504), 1)).isTrue(); + assertThat(interceptor.needRetry(createResponse(500), 1)).isFalse(); + assertThat(interceptor.needRetry(createResponse(404), 1)).isFalse(); + assertThat(interceptor.needRetry(createResponse(200), 1)).isFalse(); + } + + @Test + public void invalidRetryAfterHeader() { + Response response = createResponse(429, "Stuff"); + + assertThat(interceptor.getRetryIntervalInMilliseconds(response, 3)).isBetween(4000L, 5000L); + } + + @Test + public void validRetryAfterHeader() { + long retryAfter = 3; + Response response = createResponse(429, retryAfter + ""); + assertThat(interceptor.getRetryIntervalInMilliseconds(response, 3)) + .isEqualTo(retryAfter * 1000); + } + + @Test + public void exponentialRetry() { + ExponentialHttpRetryInterceptor interceptor = new ExponentialHttpRetryInterceptor(10); + Response response = createResponse(429, "Stuff"); + + // note that the upper limit includes ~10% variability + assertThat(interceptor.getRetryIntervalInMilliseconds(response, 0)).isEqualTo(0); + assertThat(interceptor.getRetryIntervalInMilliseconds(response, 1)).isBetween(1000L, 2000L); + assertThat(interceptor.getRetryIntervalInMilliseconds(response, 2)).isBetween(2000L, 3000L); + assertThat(interceptor.getRetryIntervalInMilliseconds(response, 3)).isBetween(4000L, 5000L); + assertThat(interceptor.getRetryIntervalInMilliseconds(response, 4)).isBetween(8000L, 9000L); + assertThat(interceptor.getRetryIntervalInMilliseconds(response, 5)) + .isBetween(16000L, 18000L); + assertThat(interceptor.getRetryIntervalInMilliseconds(response, 6)) + .isBetween(32000L, 36000L); + assertThat(interceptor.getRetryIntervalInMilliseconds(response, 7)) + .isBetween(64000L, 72000L); + assertThat(interceptor.getRetryIntervalInMilliseconds(response, 10)) + .isBetween(64000L, 72000L); + } + + public static Response createResponse(int httpCode) { + return createResponse(httpCode, ""); + } + + public static Response createResponse(int httpCode, String retryAfter) { + return new Response.Builder() + .code(httpCode) + .message("message") + .protocol(Protocol.HTTP_1_1) + .request(new Request.Builder().url("http://example.com").build()) + .addHeader(HttpHeaders.RETRY_AFTER, retryAfter) + .build(); + } +} diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java index 70db2ab0aa53..8eb433e33936 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java @@ -122,4 +122,21 @@ public void testDeleteFail() { server.enqueueResponse(errorResponseStr, 400); assertThrows(BadRequestException.class, () -> httpClient.delete(MOCK_PATH, headers)); } + + @Test + public void testRetry() { + HttpClient httpClient = + new HttpClient( + new HttpClientOptions( + server.getBaseUrl(), + Duration.ofSeconds(30), + Duration.ofSeconds(30), + 1, + 10, + 10, + 2)); + server.enqueueResponse(mockResponseDataStr, 429); + server.enqueueResponse(mockResponseDataStr, 200); + assertDoesNotThrow(() -> httpClient.get(MOCK_PATH, MockRESTData.class, headers)); + } } From 7524cc7fb4e3c8ffa4ae5783d4308fcfb5d28bfc Mon Sep 17 00:00:00 2001 From: yantian Date: Thu, 16 Jan 2025 16:53:46 +0800 Subject: [PATCH 3/8] remove read timeout config and format --- .../rest/ExponentialHttpRetryInterceptor.java | 16 +++++++--------- .../java/org/apache/paimon/rest/HttpClient.java | 17 +++++++++++++---- .../apache/paimon/rest/HttpClientOptions.java | 12 ++---------- .../apache/paimon/rest/RESTCatalogOptions.java | 9 +-------- .../ExponentialHttpRetryInterceptorTest.java | 2 +- .../org/apache/paimon/rest/HttpClientTest.java | 17 ++--------------- 6 files changed, 26 insertions(+), 47 deletions(-) diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java b/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java index 2648baa16568..43256563194f 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java @@ -36,7 +36,8 @@ import java.util.concurrent.ThreadLocalRandom; /** - * Defines an exponential HTTP request retry interceptor. + * Defines an exponential HTTP request retry interceptor. The following retrievable IOException + * status codes are defined: * *
    *
  • InterruptedIOException @@ -76,7 +77,7 @@ public ExponentialHttpRetryInterceptor(int maxRetries) { this.maxRetries = maxRetries; this.retrievableMethods = ImmutableSet.of("GET", "HEAD", "PUT", "DELETE", "TRACE", "OPTIONS"); - this.retrievableCodes = ImmutableSet.of(429, 503, 502, 504); + this.retrievableCodes = ImmutableSet.of(429, 502, 503, 504); this.nonRetriableExceptions = ImmutableSet.of( InterruptedIOException.class, @@ -115,18 +116,15 @@ public boolean needRetry(Response response, int execCount) { if (execCount > maxRetries) { return false; } - if (response != null - && (response.isSuccessful() || !retrievableCodes.contains(response.code()))) { - return false; - } - return true; + return response == null + || (!response.isSuccessful() && retrievableCodes.contains(response.code())); } - public boolean needRetry(String Method, IOException e, int execCount) { + public boolean needRetry(String method, IOException e, int execCount) { if (execCount > maxRetries) { return false; } - if (!retrievableMethods.contains(Method)) { + if (!retrievableMethods.contains(method)) { return false; } if (nonRetriableExceptions.contains(e.getClass())) { diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java index 6537dfb7e60e..2d04f58062a3 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java @@ -54,6 +54,7 @@ public class HttpClient implements RESTClient { private static final String THREAD_NAME = "REST-CATALOG-HTTP-CLIENT-THREAD-POOL"; private static final MediaType MEDIA_TYPE = MediaType.parse("application/json"); + private static final int CONNECTION_KEEP_ALIVE_DURATION_IN_MS = 300_000; private final OkHttpClient okHttpClient; private final String uri; @@ -194,9 +195,12 @@ private static OkHttpClient createHttpClient(HttpClientOptions httpClientOptions ExecutorService executorService = createCachedThreadPool(httpClientOptions.threadPoolSize(), THREAD_NAME, workQueue); ConnectionPool connectionPool = - new ConnectionPool(httpClientOptions.maxConnections(), 300, TimeUnit.MILLISECONDS); + new ConnectionPool( + httpClientOptions.maxConnections(), + CONNECTION_KEEP_ALIVE_DURATION_IN_MS, + TimeUnit.MILLISECONDS); Dispatcher dispatcher = new Dispatcher(executorService); - dispatcher.setMaxRequestsPerHost(httpClientOptions.maxRetries()); + dispatcher.setMaxRequestsPerHost(httpClientOptions.maxConnectionsPerRoute()); OkHttpClient.Builder builder = new OkHttpClient.Builder() .dispatcher(dispatcher) @@ -206,8 +210,13 @@ private static OkHttpClient createHttpClient(HttpClientOptions httpClientOptions .addInterceptor( new ExponentialHttpRetryInterceptor( httpClientOptions.maxRetries())); - httpClientOptions.connectTimeout().ifPresent(builder::connectTimeout); - httpClientOptions.readTimeout().ifPresent(builder::readTimeout); + httpClientOptions + .connectTimeout() + .ifPresent( + timeoutDuration -> { + builder.connectTimeout(timeoutDuration); + builder.readTimeout(timeoutDuration); + }); return builder.build(); } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClientOptions.java b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClientOptions.java index 35f7de1b0897..3e945733b782 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClientOptions.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClientOptions.java @@ -30,7 +30,6 @@ public class HttpClientOptions { private final String uri; @Nullable private final Duration connectTimeout; - @Nullable private final Duration readTimeout; private final int threadPoolSize; private final int maxConnections; private final int maxConnectionsPerRoute; @@ -39,14 +38,12 @@ public class HttpClientOptions { public HttpClientOptions( String uri, @Nullable Duration connectTimeout, - @Nullable Duration readTimeout, int threadPoolSize, int maxConnections, int maxConnectionsPerRoute, int maxRetries) { this.uri = uri; this.connectTimeout = connectTimeout; - this.readTimeout = readTimeout; this.threadPoolSize = threadPoolSize; this.maxConnections = maxConnections; this.maxConnectionsPerRoute = maxConnectionsPerRoute; @@ -57,7 +54,6 @@ public static HttpClientOptions create(Options options) { return new HttpClientOptions( options.get(RESTCatalogOptions.URI), options.get(RESTCatalogOptions.CONNECTION_TIMEOUT), - options.get(RESTCatalogOptions.READ_TIMEOUT), options.get(RESTCatalogOptions.THREAD_POOL_SIZE), options.get(RESTCatalogOptions.MAX_CONNECTIONS), options.get(RESTCatalogOptions.MAX_CONNECTIONS_PER_ROUTE), @@ -72,10 +68,6 @@ public Optional connectTimeout() { return Optional.ofNullable(connectTimeout); } - public Optional readTimeout() { - return Optional.ofNullable(readTimeout); - } - public int threadPoolSize() { return threadPoolSize; } @@ -85,10 +77,10 @@ public int maxConnections() { } public int maxConnectionsPerRoute() { - return maxConnectionsPerRoute; + return Math.min(maxConnections, maxConnectionsPerRoute); } public int maxRetries() { - return maxRetries; + return Math.max(maxRetries, 0); } } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java index 12c39565d0e8..42458e4cb7f4 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java @@ -32,20 +32,13 @@ public class RESTCatalogOptions { .noDefaultValue() .withDescription("REST Catalog server's uri."); - // default same with iceberg 3min + // default same with iceberg 3min public static final ConfigOption CONNECTION_TIMEOUT = ConfigOptions.key("rest.client.connection-timeout") .durationType() .defaultValue(Duration.ofSeconds(180)) .withDescription("REST Catalog http client connect timeout."); - // as read timeout need less than connect timeout default 170s - public static final ConfigOption READ_TIMEOUT = - ConfigOptions.key("rest.client.read-timeout") - .durationType() - .defaultValue(Duration.ofSeconds(170)) - .withDescription("REST Catalog http client read timeout."); - public static final ConfigOption MAX_CONNECTIONS = ConfigOptions.key("rest.client..max-connections") .intType() diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java index ae999d9e5e9f..986fddb0515e 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java @@ -37,7 +37,7 @@ /** Test for {@link ExponentialHttpRetryInterceptor}. */ class ExponentialHttpRetryInterceptorTest { private final int maxRetries = 5; - private ExponentialHttpRetryInterceptor interceptor = + private final ExponentialHttpRetryInterceptor interceptor = new ExponentialHttpRetryInterceptor(maxRetries); @Test diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java index 8eb433e33936..3f742f0ce033 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java @@ -56,14 +56,7 @@ public void setUp() throws Exception { server.start(); errorHandler = DefaultErrorHandler.getInstance(); HttpClientOptions httpClientOptions = - new HttpClientOptions( - server.getBaseUrl(), - Duration.ofSeconds(3), - Duration.ofSeconds(3), - 1, - 10, - 10, - 2); + new HttpClientOptions(server.getBaseUrl(), Duration.ofSeconds(3), 1, 10, 10, 2); mockResponseData = new MockRESTData(MOCK_PATH); mockResponseDataStr = server.createResponseBody(mockResponseData); errorResponseStr = @@ -128,13 +121,7 @@ public void testRetry() { HttpClient httpClient = new HttpClient( new HttpClientOptions( - server.getBaseUrl(), - Duration.ofSeconds(30), - Duration.ofSeconds(30), - 1, - 10, - 10, - 2)); + server.getBaseUrl(), Duration.ofSeconds(30), 1, 10, 10, 2)); server.enqueueResponse(mockResponseDataStr, 429); server.enqueueResponse(mockResponseDataStr, 200); assertDoesNotThrow(() -> httpClient.get(MOCK_PATH, MockRESTData.class, headers)); From 17b2ea83117a4e6c6c569c26580971476d464d6c Mon Sep 17 00:00:00 2001 From: yantian Date: Thu, 16 Jan 2025 17:06:07 +0800 Subject: [PATCH 4/8] delete no need conf --- .../main/java/org/apache/paimon/rest/HttpClient.java | 3 ++- .../org/apache/paimon/rest/HttpClientOptions.java | 8 -------- .../org/apache/paimon/rest/RESTCatalogOptions.java | 11 ++--------- .../java/org/apache/paimon/rest/HttpClientTest.java | 4 ++-- 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java index 2d04f58062a3..58d7888ae35d 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java @@ -200,7 +200,8 @@ private static OkHttpClient createHttpClient(HttpClientOptions httpClientOptions CONNECTION_KEEP_ALIVE_DURATION_IN_MS, TimeUnit.MILLISECONDS); Dispatcher dispatcher = new Dispatcher(executorService); - dispatcher.setMaxRequestsPerHost(httpClientOptions.maxConnectionsPerRoute()); + // set max requests per host use max connections + dispatcher.setMaxRequestsPerHost(httpClientOptions.maxConnections()); OkHttpClient.Builder builder = new OkHttpClient.Builder() .dispatcher(dispatcher) diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClientOptions.java b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClientOptions.java index 3e945733b782..548a98956821 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClientOptions.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClientOptions.java @@ -32,7 +32,6 @@ public class HttpClientOptions { @Nullable private final Duration connectTimeout; private final int threadPoolSize; private final int maxConnections; - private final int maxConnectionsPerRoute; private final int maxRetries; public HttpClientOptions( @@ -40,13 +39,11 @@ public HttpClientOptions( @Nullable Duration connectTimeout, int threadPoolSize, int maxConnections, - int maxConnectionsPerRoute, int maxRetries) { this.uri = uri; this.connectTimeout = connectTimeout; this.threadPoolSize = threadPoolSize; this.maxConnections = maxConnections; - this.maxConnectionsPerRoute = maxConnectionsPerRoute; this.maxRetries = maxRetries; } @@ -56,7 +53,6 @@ public static HttpClientOptions create(Options options) { options.get(RESTCatalogOptions.CONNECTION_TIMEOUT), options.get(RESTCatalogOptions.THREAD_POOL_SIZE), options.get(RESTCatalogOptions.MAX_CONNECTIONS), - options.get(RESTCatalogOptions.MAX_CONNECTIONS_PER_ROUTE), options.get(RESTCatalogOptions.MAX_RETIES)); } @@ -76,10 +72,6 @@ public int maxConnections() { return maxConnections; } - public int maxConnectionsPerRoute() { - return Math.min(maxConnections, maxConnectionsPerRoute); - } - public int maxRetries() { return Math.max(maxRetries, 0); } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java index 42458e4cb7f4..8a11bdd25ff9 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java @@ -40,20 +40,13 @@ public class RESTCatalogOptions { .withDescription("REST Catalog http client connect timeout."); public static final ConfigOption MAX_CONNECTIONS = - ConfigOptions.key("rest.client..max-connections") + ConfigOptions.key("rest.client.max-connections") .intType() .defaultValue(100) .withDescription("REST Catalog http client's max connections."); - // need less than max connections default 100 - public static final ConfigOption MAX_CONNECTIONS_PER_ROUTE = - ConfigOptions.key("connections-per-route") - .intType() - .defaultValue(100) - .withDescription("REST Catalog http client's max connections per route."); - public static final ConfigOption MAX_RETIES = - ConfigOptions.key("rest.client..max-retries") + ConfigOptions.key("rest.client.max-retries") .intType() .defaultValue(5) .withDescription("REST Catalog http client's max retry times."); diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java index 3f742f0ce033..05078cf805f7 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java @@ -56,7 +56,7 @@ public void setUp() throws Exception { server.start(); errorHandler = DefaultErrorHandler.getInstance(); HttpClientOptions httpClientOptions = - new HttpClientOptions(server.getBaseUrl(), Duration.ofSeconds(3), 1, 10, 10, 2); + new HttpClientOptions(server.getBaseUrl(), Duration.ofSeconds(3), 1, 10, 2); mockResponseData = new MockRESTData(MOCK_PATH); mockResponseDataStr = server.createResponseBody(mockResponseData); errorResponseStr = @@ -121,7 +121,7 @@ public void testRetry() { HttpClient httpClient = new HttpClient( new HttpClientOptions( - server.getBaseUrl(), Duration.ofSeconds(30), 1, 10, 10, 2)); + server.getBaseUrl(), Duration.ofSeconds(30), 1, 10, 2)); server.enqueueResponse(mockResponseDataStr, 429); server.enqueueResponse(mockResponseDataStr, 200); assertDoesNotThrow(() -> httpClient.get(MOCK_PATH, MockRESTData.class, headers)); From d5aef7d5fef0b1a98f78c63c7a53d72af515f448 Mon Sep 17 00:00:00 2001 From: yantian Date: Thu, 16 Jan 2025 17:31:35 +0800 Subject: [PATCH 5/8] fix checkstyle --- .../paimon/rest/ExponentialHttpRetryInterceptor.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java b/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java index 43256563194f..417d539beecf 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java @@ -36,8 +36,9 @@ import java.util.concurrent.ThreadLocalRandom; /** - * Defines an exponential HTTP request retry interceptor. The following retrievable IOException - * status codes are defined: + * Defines exponential HTTP request retry interceptor. + * + *

    The following retrievable IOException * *

      *
    • InterruptedIOException @@ -47,7 +48,7 @@ *
    • SSLException *
    * - * The following retrievable HTTP status codes are defined: + *

    The following retrievable HTTP status codes are defined: * *

      *
    • TOO_MANY_REQUESTS (429) @@ -56,7 +57,7 @@ *
    • GATEWAY_TIMEOUT (504) *
    * - * The following retrievable HTTP method which is idempotent are defined: + *

    The following retrievable HTTP method which is idempotent are defined: * *

      *
    • GET From bf3d851d60b7c6012e47602bdf06cdfa999673ee Mon Sep 17 00:00:00 2001 From: yantian Date: Thu, 16 Jan 2025 17:37:25 +0800 Subject: [PATCH 6/8] format --- .../org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java | 1 + .../apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java | 1 + 2 files changed, 2 insertions(+) diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java b/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java index 417d539beecf..dd16e47fc580 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptor.java @@ -69,6 +69,7 @@ *
    */ public class ExponentialHttpRetryInterceptor implements Interceptor { + private final int maxRetries; private final Set> nonRetriableExceptions; private final Set retrievableCodes; diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java index 986fddb0515e..85904bc8a043 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java @@ -36,6 +36,7 @@ /** Test for {@link ExponentialHttpRetryInterceptor}. */ class ExponentialHttpRetryInterceptorTest { + private final int maxRetries = 5; private final ExponentialHttpRetryInterceptor interceptor = new ExponentialHttpRetryInterceptor(maxRetries); From e11051e9b6d4fb81f0d214d895b0b1231c19ab64 Mon Sep 17 00:00:00 2001 From: yantian Date: Thu, 16 Jan 2025 18:07:59 +0800 Subject: [PATCH 7/8] format and add test --- .../org/apache/paimon/rest/HttpClient.java | 4 ++-- .../ExponentialHttpRetryInterceptorTest.java | 21 +++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java index 58d7888ae35d..5a13a51ef7fa 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java @@ -54,7 +54,7 @@ public class HttpClient implements RESTClient { private static final String THREAD_NAME = "REST-CATALOG-HTTP-CLIENT-THREAD-POOL"; private static final MediaType MEDIA_TYPE = MediaType.parse("application/json"); - private static final int CONNECTION_KEEP_ALIVE_DURATION_IN_MS = 300_000; + private static final int CONNECTION_KEEP_ALIVE_DURATION_MS = 300_000; private final OkHttpClient okHttpClient; private final String uri; @@ -197,7 +197,7 @@ private static OkHttpClient createHttpClient(HttpClientOptions httpClientOptions ConnectionPool connectionPool = new ConnectionPool( httpClientOptions.maxConnections(), - CONNECTION_KEEP_ALIVE_DURATION_IN_MS, + CONNECTION_KEEP_ALIVE_DURATION_MS, TimeUnit.MILLISECONDS); Dispatcher dispatcher = new Dispatcher(executorService); // set max requests per host use max connections diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java index 85904bc8a043..6510371f2d27 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/ExponentialHttpRetryInterceptorTest.java @@ -43,12 +43,14 @@ class ExponentialHttpRetryInterceptorTest { @Test void testNeedRetryByMethod() { + assertThat(interceptor.needRetry("GET", new IOException(), 1)).isTrue(); assertThat(interceptor.needRetry("HEAD", new IOException(), 1)).isTrue(); assertThat(interceptor.needRetry("PUT", new IOException(), 1)).isTrue(); assertThat(interceptor.needRetry("DELETE", new IOException(), 1)).isTrue(); assertThat(interceptor.needRetry("TRACE", new IOException(), 1)).isTrue(); assertThat(interceptor.needRetry("OPTIONS", new IOException(), 1)).isTrue(); + assertThat(interceptor.needRetry("POST", new IOException(), 1)).isFalse(); assertThat(interceptor.needRetry("PATCH", new IOException(), 1)).isFalse(); assertThat(interceptor.needRetry("CONNECT", new IOException(), 1)).isFalse(); @@ -57,33 +59,40 @@ void testNeedRetryByMethod() { @Test void testNeedRetryByException() { + assertThat(interceptor.needRetry("GET", new InterruptedIOException(), 1)).isFalse(); assertThat(interceptor.needRetry("GET", new UnknownHostException(), 1)).isFalse(); assertThat(interceptor.needRetry("GET", new ConnectException(), 1)).isFalse(); assertThat(interceptor.needRetry("GET", new NoRouteToHostException(), 1)).isFalse(); assertThat(interceptor.needRetry("GET", new SSLException("error"), 1)).isFalse(); + + assertThat(interceptor.needRetry("GET", new IOException("error"), 1)).isTrue(); + assertThat(interceptor.needRetry("GET", new IOException("error"), maxRetries + 1)) + .isFalse(); } @Test void testRetryByResponse() { + assertThat(interceptor.needRetry(createResponse(429), 1)).isTrue(); assertThat(interceptor.needRetry(createResponse(503), 1)).isTrue(); assertThat(interceptor.needRetry(createResponse(502), 1)).isTrue(); assertThat(interceptor.needRetry(createResponse(504), 1)).isTrue(); + assertThat(interceptor.needRetry(createResponse(500), 1)).isFalse(); assertThat(interceptor.needRetry(createResponse(404), 1)).isFalse(); assertThat(interceptor.needRetry(createResponse(200), 1)).isFalse(); } @Test - public void invalidRetryAfterHeader() { + void invalidRetryAfterHeader() { Response response = createResponse(429, "Stuff"); assertThat(interceptor.getRetryIntervalInMilliseconds(response, 3)).isBetween(4000L, 5000L); } @Test - public void validRetryAfterHeader() { + void validRetryAfterHeader() { long retryAfter = 3; Response response = createResponse(429, retryAfter + ""); assertThat(interceptor.getRetryIntervalInMilliseconds(response, 3)) @@ -91,7 +100,7 @@ public void validRetryAfterHeader() { } @Test - public void exponentialRetry() { + void exponentialRetry() { ExponentialHttpRetryInterceptor interceptor = new ExponentialHttpRetryInterceptor(10); Response response = createResponse(429, "Stuff"); @@ -111,16 +120,16 @@ public void exponentialRetry() { .isBetween(64000L, 72000L); } - public static Response createResponse(int httpCode) { + private static Response createResponse(int httpCode) { return createResponse(httpCode, ""); } - public static Response createResponse(int httpCode, String retryAfter) { + private static Response createResponse(int httpCode, String retryAfter) { return new Response.Builder() .code(httpCode) .message("message") .protocol(Protocol.HTTP_1_1) - .request(new Request.Builder().url("http://example.com").build()) + .request(new Request.Builder().url("http://localhost").build()) .addHeader(HttpHeaders.RETRY_AFTER, retryAfter) .build(); } From 17c434e44f5e7f443def06a440e27efcb21ec86f Mon Sep 17 00:00:00 2001 From: yantian Date: Fri, 17 Jan 2025 11:10:43 +0800 Subject: [PATCH 8/8] delete no need comment --- .../src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java | 1 - 1 file changed, 1 deletion(-) diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java index 8a11bdd25ff9..843228fa0707 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java @@ -32,7 +32,6 @@ public class RESTCatalogOptions { .noDefaultValue() .withDescription("REST Catalog server's uri."); - // default same with iceberg 3min public static final ConfigOption CONNECTION_TIMEOUT = ConfigOptions.key("rest.client.connection-timeout") .durationType()