ClickHouseResponse failover(ClickHouseRequest<?> sealedRequest, ClickHouseException exception, int times) {
            for (int i = 1; i <= times; i++) {
                log.debug("Failover %d of %d due to: %s", i, times, exception.getCause(), null);
                ClickHouseNode current = sealedRequest.getServer();
                ClickHouseNodeManager manager = current.manager.get();
                if (manager == null) {
                    log.debug("Cancel failover for unmanaged node: %s", current);
                    break;
                }
                ClickHouseNode next = manager.suggestNode(current, exception);
                if (next == current) {
                    log.debug("Cancel failover for same node returned from %s", manager.getPolicy());
                    break;
                }
                current.update(Status.FAULTY);
                if (sealedRequest.isTransactional()) {
                    log.debug("Cancel failover for transactional context: %s", sealedRequest.getTransaction());
                    break;
                } else if ((next = sealedRequest.changeServer(current, next)) == current) {
                    log.debug("Cancel failover for no alternative of %s", current);
                    break;
                }

                log.info("Switching node from %s to %s due to: %s", current, next, exception.getCause(), null);
                final ClickHouseProtocol protocol = next.getProtocol();
                final ClickHouseClient currentClient = client.get();
                if (!currentClient.accept(protocol)) {
                    ClickHouseClient newClient = null;
                    try {
                        newClient = ClickHouseClient.builder().agent(false)
                                .config(new ClickHouseConfig(currentClient.getConfig(), next.config))
                                .nodeSelector(ClickHouseNodeSelector.of(protocol)).build();
                    } catch (Exception e) {
                        exception = ClickHouseException.of(new ConnectException("No client available for " + next),
                                sealedRequest.getServer());
                    } finally {
                        if (newClient != null) {
                            boolean changed = changeClient(currentClient, newClient);
                            log.info("Switching client from %s to %s: %s", currentClient, newClient, changed);
                            if (changed) {
                                sealedRequest.resetCache();
                            }
                        }
                    }

                    if (newClient == null) {
                        continue;
                    }
                }

                try {
                    return sendOnce(sealedRequest);
                } catch (Exception exp) {
                    exception = ClickHouseException.of(exp.getCause() != null ? exp.getCause() : exp,
                            sealedRequest.getServer());
                }
            }

            throw new CompletionException(exception);
        }
Thread-safe wrapper of {@link ClickHouseClient} for collecting metrics and
fail-over.
clickhouse-client/src/main/java/com/clickhouse/client/ClickHouseClientBuilder.java