3636import com .clickhouse .client .api .serde .POJOFieldDeserializer ;
3737import com .clickhouse .client .api .serde .POJOFieldSerializer ;
3838import com .clickhouse .client .api .serde .POJOSerDe ;
39+ import com .clickhouse .client .api .transport .ClientNodeSelector ;
3940import com .clickhouse .client .api .transport .Endpoint ;
4041import com .clickhouse .client .api .transport .HttpEndpoint ;
4142import com .clickhouse .client .config .ClickHouseClientOption ;
6263import java .util .Collection ;
6364import java .util .Collections ;
6465import java .util .HashMap ;
65- import java .util .HashSet ;
6666import java .util .LinkedHashMap ;
67+ import java .util .LinkedHashSet ;
6768import java .util .List ;
6869import java .util .Map ;
6970import java .util .Set ;
@@ -140,9 +141,9 @@ public class Client implements AutoCloseable {
140141 private String dbUser ;
141142 private String serverVersion ;
142143 private final Object metricsRegistry ;
143- private final int retries ;
144144 private LZ4Factory lz4Factory = null ;
145145 private final Supplier <String > queryIdGenerator ;
146+ private final ClientNodeSelector nodeSelector ;
146147 private final CredentialsManager credentialsManager ;
147148
148149 private Client (Collection <Endpoint > endpoints , Map <String ,String > configuration ,
@@ -187,9 +188,8 @@ private Client(Collection<Endpoint> endpoints, Map<String,String> configuration,
187188 }
188189
189190 this .endpoints = tmpEndpoints .build ();
191+ this .nodeSelector = new ClientNodeSelector (this .endpoints );
190192
191- String retry = configuration .get (ClientConfigProperties .RETRY_ON_FAILURE .getKey ());
192- this .retries = retry == null ? 0 : Integer .parseInt (retry );
193193 boolean useNativeCompression = !MapUtils .getFlag (configuration , ClientConfigProperties .DISABLE_NATIVE_COMPRESSION .getKey (), false );
194194 if (useNativeCompression ) {
195195 this .lz4Factory = LZ4Factory .fastestInstance ();
@@ -272,7 +272,7 @@ public static class Builder {
272272 private Supplier <String > queryIdGenerator ;
273273
274274 public Builder () {
275- this .endpoints = new HashSet <>();
275+ this .endpoints = new LinkedHashSet <>();
276276 this .configuration = new HashMap <>();
277277
278278 for (ClientConfigProperties p : ClientConfigProperties .values ()) {
@@ -1364,8 +1364,8 @@ public CompletableFuture<InsertResponse> insert(String tableName, List<?> data,
13641364 }
13651365
13661366
1367- Integer retry = ( Integer ) configuration . get ( ClientConfigProperties .RETRY_ON_FAILURE .getKey ());
1368- final int maxRetries = retry == null ? 0 : retry ;
1367+ final int maxRetries = ClientConfigProperties .RETRY_ON_FAILURE .getOrDefault ( requestSettings . getAllSettings ());
1368+ final int maxAttempts = Math . max ( maxRetries , endpoints . size () - 1 ) ;
13691369
13701370 requestSettings .setOption (ClientConfigProperties .INPUT_OUTPUT_FORMAT .getKey (), format );
13711371 if (requestSettings .getQueryId () == null && queryIdGenerator != null ) {
@@ -1374,10 +1374,10 @@ public CompletableFuture<InsertResponse> insert(String tableName, List<?> data,
13741374 Supplier <InsertResponse > supplier = () -> {
13751375 long startTime = System .nanoTime ();
13761376 // Selecting some node
1377- Endpoint selectedEndpoint = getNextAliveNode ();
1377+ Endpoint selectedEndpoint = nodeSelector . getEndpoint ();
13781378
13791379 RuntimeException lastException = null ;
1380- for (int i = 0 ; i <= maxRetries ; i ++) {
1380+ for (int i = 0 ; i <= maxAttempts ; i ++) {
13811381 // Execute request
13821382 try (ClassicHttpResponse httpResponse =
13831383 httpClientHelper .executeRequest (selectedEndpoint , requestSettings .getAllSettings (),
@@ -1400,14 +1400,6 @@ public CompletableFuture<InsertResponse> insert(String tableName, List<?> data,
14001400 out .close ();
14011401 })) {
14021402
1403-
1404- // Check response
1405- if (httpResponse .getCode () == HttpStatus .SC_SERVICE_UNAVAILABLE ) {
1406- LOG .warn ("Failed to get response. Server returned {}. Retrying. (Duration: {})" , httpResponse .getCode (), durationSince (startTime ));
1407- selectedEndpoint = getNextAliveNode ();
1408- continue ;
1409- }
1410-
14111403 ClientStatisticsHolder clientStats = globalClientStats .remove (operationId );
14121404 OperationMetrics metrics = new OperationMetrics (clientStats );
14131405 String summary = HttpAPIClientHelper .getHeaderVal (httpResponse .getFirstHeader (ClickHouseHttpProto .HEADER_SRV_SUMMARY ), "{}" );
@@ -1420,15 +1412,19 @@ public CompletableFuture<InsertResponse> insert(String tableName, List<?> data,
14201412 String msg = requestExMsg ("Insert" , (i + 1 ), durationSince (startTime ).toMillis (), requestSettings .getQueryId ());
14211413 lastException = httpClientHelper .wrapException (msg , e , requestSettings .getQueryId ());
14221414 if (httpClientHelper .shouldRetry (e , requestSettings .getAllSettings ())) {
1423- LOG .warn ("Retrying." , e );
1424- selectedEndpoint = getNextAliveNode ();
1415+ if (i < maxAttempts ) {
1416+ LOG .warn ("Retrying." , e );
1417+ selectedEndpoint = nodeSelector .getNextAliveNode (selectedEndpoint );
1418+ } else {
1419+ nodeSelector .getNextAliveNode (selectedEndpoint );
1420+ }
14251421 } else {
14261422 throw lastException ;
14271423 }
14281424 }
14291425 }
14301426
1431- String errMsg = requestExMsg ("Insert" , retries , durationSince (startTime ).toMillis (), requestSettings .getQueryId ());
1427+ String errMsg = requestExMsg ("Insert" , maxAttempts + 1 , durationSince (startTime ).toMillis (), requestSettings .getQueryId ());
14321428 LOG .warn (errMsg );
14331429 throw (lastException == null ? new ClientException (errMsg ) : lastException ); };
14341430
@@ -1593,13 +1589,15 @@ public CompletableFuture<InsertResponse> insert(String tableName,
15931589 if (requestSettings .getQueryId () == null && queryIdGenerator != null ) {
15941590 requestSettings .setQueryId (queryIdGenerator .get ());
15951591 }
1592+ final int maxRetries = ClientConfigProperties .RETRY_ON_FAILURE .getOrDefault (requestSettings .getAllSettings ());
1593+ final int maxAttempts = Math .max (maxRetries , endpoints .size () - 1 );
15961594 responseSupplier = () -> {
15971595 long startTime = System .nanoTime ();
15981596 // Selecting some node
1599- Endpoint selectedEndpoint = getNextAliveNode ();
1597+ Endpoint selectedEndpoint = nodeSelector . getEndpoint ();
16001598
16011599 RuntimeException lastException = null ;
1602- for (int i = 0 ; i <= retries ; i ++) {
1600+ for (int i = 0 ; i <= maxAttempts ; i ++) {
16031601 // Execute request
16041602 try (ClassicHttpResponse httpResponse =
16051603 httpClientHelper .executeRequest (selectedEndpoint , requestSettings .getAllSettings (),
@@ -1608,14 +1606,6 @@ public CompletableFuture<InsertResponse> insert(String tableName,
16081606 out .close ();
16091607 })) {
16101608
1611-
1612- // Check response
1613- if (httpResponse .getCode () == HttpStatus .SC_SERVICE_UNAVAILABLE ) {
1614- LOG .warn ("Failed to get response. Server returned {}. Retrying. (Duration: {})" , httpResponse .getCode (), durationSince (startTime ));
1615- selectedEndpoint = getNextAliveNode ();
1616- continue ;
1617- }
1618-
16191609 OperationMetrics metrics = new OperationMetrics (finalClientStats );
16201610 String summary = HttpAPIClientHelper .getHeaderVal (httpResponse .getFirstHeader (ClickHouseHttpProto .HEADER_SRV_SUMMARY ), "{}" );
16211611 ProcessParser .parseSummary (summary , metrics );
@@ -1627,22 +1617,26 @@ public CompletableFuture<InsertResponse> insert(String tableName,
16271617 String msg = requestExMsg ("Insert" , (i + 1 ), durationSince (startTime ).toMillis (), requestSettings .getQueryId ());
16281618 lastException = httpClientHelper .wrapException (msg , e , requestSettings .getQueryId ());
16291619 if (httpClientHelper .shouldRetry (e , requestSettings .getAllSettings ())) {
1630- LOG .warn ("Retrying." , e );
1631- selectedEndpoint = getNextAliveNode ();
1620+ if (i < maxAttempts ) {
1621+ LOG .warn ("Retrying." , e );
1622+ selectedEndpoint = nodeSelector .getNextAliveNode (selectedEndpoint );
1623+ } else {
1624+ nodeSelector .getNextAliveNode (selectedEndpoint );
1625+ }
16321626 } else {
16331627 throw lastException ;
16341628 }
16351629 }
16361630
1637- if (i < retries ) {
1631+ if (i < maxAttempts ) {
16381632 try {
16391633 writer .onRetry ();
16401634 } catch (IOException ioe ) {
16411635 throw new ClientException ("Failed to reset stream before next attempt" , ioe );
16421636 }
16431637 }
16441638 }
1645- String errMsg = requestExMsg ("Insert" , retries , durationSince (startTime ).toMillis (), requestSettings .getQueryId ());
1639+ String errMsg = requestExMsg ("Insert" , maxAttempts + 1 , durationSince (startTime ).toMillis (), requestSettings .getQueryId ());
16461640 LOG .warn (errMsg );
16471641 throw (lastException == null ? new ClientException (errMsg ) : lastException );
16481642 };
@@ -1731,24 +1725,19 @@ public CompletableFuture<QueryResponse> query(String sqlQuery, Map<String, Objec
17311725 requestSettings .setQueryId (queryIdGenerator .get ());
17321726 }
17331727
1728+ final int maxRetries = ClientConfigProperties .RETRY_ON_FAILURE .getOrDefault (requestSettings .getAllSettings ());
1729+ final int maxAttempts = Math .max (maxRetries , endpoints .size () - 1 );
17341730 Supplier <QueryResponse > responseSupplier = () -> {
17351731 long startTime = System .nanoTime ();
17361732 // Selecting some node
1737- Endpoint selectedEndpoint = getNextAliveNode ();
1733+ Endpoint selectedEndpoint = nodeSelector . getEndpoint ();
17381734 RuntimeException lastException = null ;
1739- for (int i = 0 ; i <= retries ; i ++) {
1735+ for (int i = 0 ; i <= maxAttempts ; i ++) {
17401736 ClassicHttpResponse httpResponse = null ;
17411737 try {
17421738 httpResponse = httpClientHelper .executeRequest (selectedEndpoint ,
1743- requestSettings .getAllSettings (),
1744- sqlQuery );
1745- // Check response
1746- if (httpResponse .getCode () == HttpStatus .SC_SERVICE_UNAVAILABLE ) {
1747- LOG .warn ("Failed to get response. Server returned {}. Retrying. (Duration: {})" , httpResponse .getCode (), durationSince (startTime ));
1748- selectedEndpoint = getNextAliveNode ();
1749- HttpAPIClientHelper .closeQuietly (httpResponse );
1750- continue ;
1751- }
1739+ requestSettings .getAllSettings (),
1740+ sqlQuery );
17521741
17531742 OperationMetrics metrics = new OperationMetrics (clientStats );
17541743 String summary = HttpAPIClientHelper .getHeaderVal (httpResponse
@@ -1772,14 +1761,18 @@ public CompletableFuture<QueryResponse> query(String sqlQuery, Map<String, Objec
17721761 String msg = requestExMsg ("Query" , (i + 1 ), durationSince (startTime ).toMillis (), requestSettings .getQueryId ());
17731762 lastException = httpClientHelper .wrapException (msg , e , requestSettings .getQueryId ());
17741763 if (httpClientHelper .shouldRetry (e , requestSettings .getAllSettings ())) {
1775- LOG .warn ("Retrying." , e );
1776- selectedEndpoint = getNextAliveNode ();
1764+ if (i < maxAttempts ) {
1765+ LOG .warn ("Retrying." , e );
1766+ selectedEndpoint = nodeSelector .getNextAliveNode (selectedEndpoint );
1767+ } else {
1768+ nodeSelector .getNextAliveNode (selectedEndpoint );
1769+ }
17771770 } else {
17781771 throw lastException ;
17791772 }
17801773 }
17811774 }
1782- String errMsg = requestExMsg ("Query" , retries , durationSince (startTime ).toMillis (), requestSettings .getQueryId ());
1775+ String errMsg = requestExMsg ("Query" , maxAttempts + 1 , durationSince (startTime ).toMillis (), requestSettings .getQueryId ());
17831776 LOG .warn (errMsg );
17841777 throw (lastException == null ? new ClientException (errMsg ) : lastException );
17851778 };
@@ -2285,9 +2278,7 @@ public void updateAccessToken(String accessToken) {
22852278 this .credentialsManager .setAccessToken (accessToken );
22862279 }
22872280
2288- private Endpoint getNextAliveNode () {
2289- return endpoints .get (0 );
2290- }
2281+
22912282
22922283 public static final String VALUES_LIST_DELIMITER = "," ;
22932284
@@ -2313,7 +2304,7 @@ private Map<String, Object> buildRequestSettings(Map<String, Object> opSettings)
23132304 * <p>For {@link ClickHouseFormat#JSONEachRow}, callers may opt in to plain JSON numbers by setting
23142305 * {@link ClientConfigProperties#JSON_DISABLE_NUMBER_QUOTING}. Explicit server settings are otherwise
23152306 * left untouched.</p>
2316- * <ul>
2307+ * <ul>
23172308 * <li>{@code output_format_json_quote_64bit_integers}</li>
23182309 * <li>{@code output_format_json_quote_64bit_floats}</li>
23192310 * <li>{@code output_format_json_quote_decimals}</li>
0 commit comments