Skip to content

Commit 96005da

Browse files
committed
JVMCBC-1696 Should not make bucketful KV connections to nodes that aren't hosting the bucket
Motivation ---------- During the migration to the new cluster topology classes, there was a regression that caused `core.reconfigureBuckets` to create bucket-scoped KV endpoints for nodes that aren't hosting the bucket. During rebalance, this causes noise in the logs due to fruitless connection attempts, and more importantly causes `Cluster.diagnostics()` to report the cluster is in a degraded state because the doomed endpoints are not connected. Modifications ------------- When reconfiguring a bucket, transform the nodes by removing the KV service from any node that is not hosting the bucket. This echoes the behavior of the old BucketConfig class. Note that we need to process the whole node list from the "global" part of the topology so we can remove existing KV endpoints that are no longer required by the updated topology. Change-Id: I8eaf79d39033d4a24cfdd5b1eb18864d296b931e Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/235828 Tested-by: Build Bot <[email protected]> Reviewed-by: Michael Reiche <[email protected]>
1 parent d3d0e30 commit 96005da

File tree

2 files changed

+26
-10
lines changed

2 files changed

+26
-10
lines changed

core-io/src/main/java/com/couchbase/client/core/Core.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
import com.couchbase.client.core.topology.ClusterIdentifierUtil;
9797
import com.couchbase.client.core.topology.ClusterTopology;
9898
import com.couchbase.client.core.topology.ClusterTopologyWithBucket;
99+
import com.couchbase.client.core.topology.HostAndServicePorts;
99100
import com.couchbase.client.core.topology.NodeIdentifier;
100101
import com.couchbase.client.core.transaction.cleanup.CoreTransactionsCleanup;
101102
import com.couchbase.client.core.transaction.context.CoreTransactionsContext;
@@ -104,8 +105,8 @@
104105
import com.couchbase.client.core.util.Deadline;
105106
import com.couchbase.client.core.util.LatestStateSubscription;
106107
import com.couchbase.client.core.util.NanoTimestamp;
107-
import org.slf4j.LoggerFactory;
108108
import org.slf4j.Logger;
109+
import org.slf4j.LoggerFactory;
109110
import reactor.core.Disposable;
110111
import reactor.core.publisher.Flux;
111112
import reactor.core.publisher.Mono;
@@ -135,6 +136,8 @@
135136
import static com.couchbase.client.core.util.ConnectionStringUtil.sanityCheckPorts;
136137
import static java.util.Collections.emptySet;
137138
import static java.util.Objects.requireNonNull;
139+
import static java.util.stream.Collectors.toList;
140+
import static java.util.stream.Collectors.toSet;
138141

139142
/**
140143
* The main entry point into the core layer.
@@ -823,18 +826,35 @@ private Mono<Void> reconfigureBuckets(final Flux<ClusterTopologyWithBucket> buck
823826
.then();
824827
}
825828

829+
/**
830+
* Returns the given topology's nodes, but with the KV service removed from nodes that aren't hosting the bucket.
831+
* <p>
832+
* This prevents the SDK from creating doomed KV endpoints that can never connect because
833+
* "select bucket" returns an error when the node isn't hosting the bucket.
834+
*/
835+
private static List<HostAndServicePorts> nodesWithoutIrrelevantKvServices(ClusterTopologyWithBucket topology) {
836+
Set<NodeIdentifier> nodeIdsServicingBucket =
837+
topology.bucket().nodes().stream()
838+
.map(HostAndServicePorts::id)
839+
.collect(toSet());
840+
841+
return topology.nodes().stream()
842+
.map(node -> nodeIdsServicingBucket.contains(node.id()) ? node : node.without(ServiceType.KV))
843+
.collect(toList());
844+
}
845+
826846
/**
827847
* @param bucketName pass non-null if using the topology to configure bucket-scoped services.
828-
*
829-
* @implNote Maybe in the future we can inspect the ClusterTopology to see if it has a BucketTopology,
830-
* and get the bucket name from there. However, let's make it explicit for now; this leaves the door open
831-
* to using a ClusterTopologyWithBucket to configure global services (by passing a null bucket name).
832848
*/
833849
private Mono<Void> reconfigureGlobalOrBucket(
834850
ClusterTopology topology,
835851
@Nullable String bucketName
836852
) {
837-
return Flux.fromIterable(topology.nodes())
853+
List<HostAndServicePorts> nodes = bucketName == null
854+
? topology.nodes()
855+
: nodesWithoutIrrelevantKvServices(topology.requireBucket());
856+
857+
return Flux.fromIterable(nodes)
838858
.flatMap(ni -> {
839859
Flux<Void> serviceRemoveFlux = Flux
840860
.fromArray(ServiceType.values())

core-io/src/main/java/com/couchbase/client/core/topology/BucketTopology.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package com.couchbase.client.core.topology;
1818

1919
import com.couchbase.client.core.annotation.Stability;
20-
import com.couchbase.client.core.config.BucketConfig;
2120
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.node.ObjectNode;
2221
import com.couchbase.client.core.node.MemcachedHashingStrategy;
2322
import org.jspecify.annotations.Nullable;
@@ -50,9 +49,6 @@ default boolean hasCapability(BucketCapability capability) {
5049
/**
5150
* Returns the subset of cluster nodes that are ready to service
5251
* requests for this bucket.
53-
* <p>
54-
* This method is a candidate for eventual removal, since it's used only by
55-
* unit tests and the code that converts a BucketTopology to a legacy {@link BucketConfig}.
5652
*/
5753
@Stability.Internal
5854
List<HostAndServicePorts> nodes();

0 commit comments

Comments
 (0)