Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -482,15 +482,7 @@
<include>**/Endpoint.java</include>
<include>src/main/java/redis/clients/jedis/mcf/*.java</include>
<include>src/test/java/redis/clients/jedis/failover/*.java</include>
<include>**/mcf/EchoStrategyIntegrationTest.java</include>
<include>**/mcf/LagAwareStrategyUnitTest.java</include>
<include>**/mcf/RedisRestAPI*.java</include>
<include>**/mcf/ActiveActiveLocalFailoverTest*</include>
<include>**/mcf/FailbackMechanism*.java</include>
<include>**/mcf/PeriodicFailbackTest*.java</include>
<include>**/mcf/AutomaticFailoverTest*.java</include>
<include>**/mcf/MultiCluster*.java</include>
<include>**/mcf/StatusTracker*.java</include>
<include>src/test/java/redis/clients/jedis/mcf/*.java</include>
<include>**/Health*.java</include>
<include>**/*IT.java</include>
<include>**/scenario/RestEndpointUtil.java</include>
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/redis/clients/jedis/MultiClusterClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,16 @@ public static interface StrategySupplier {
.asList(JedisConnectionException.class);

/** Default failure rate threshold percentage for circuit breaker activation. */
private static final float CIRCUIT_BREAKER_FAILURE_RATE_THRESHOLD_DEFAULT = 50.0f;
private static final float CIRCUIT_BREAKER_FAILURE_RATE_THRESHOLD_DEFAULT = 10.0f;

/** Default minimum number of calls required before circuit breaker can calculate failure rate. */
private static final int CIRCUIT_BREAKER_SLIDING_WINDOW_MIN_CALLS_DEFAULT = 100;
private static final int CIRCUIT_BREAKER_SLIDING_WINDOW_MIN_CALLS_DEFAULT = 1000;

/** Default sliding window type for circuit breaker failure tracking. */
private static final SlidingWindowType CIRCUIT_BREAKER_SLIDING_WINDOW_TYPE_DEFAULT = SlidingWindowType.COUNT_BASED;

/** Default sliding window size for circuit breaker failure tracking. */
private static final int CIRCUIT_BREAKER_SLIDING_WINDOW_SIZE_DEFAULT = 100;
private static final int CIRCUIT_BREAKER_SLIDING_WINDOW_SIZE_DEFAULT = 2;

/** Default slow call duration threshold in milliseconds. */
private static final int CIRCUIT_BREAKER_SLOW_CALL_DURATION_THRESHOLD_DEFAULT = 60000;
Expand All @@ -156,10 +156,10 @@ public static interface StrategySupplier {
.asList(CallNotPermittedException.class, ConnectionFailoverException.class);

/** Default interval in milliseconds for checking if failed clusters have recovered. */
private static final long FAILBACK_CHECK_INTERVAL_DEFAULT = 5000;
private static final long FAILBACK_CHECK_INTERVAL_DEFAULT = 120000;

/** Default grace period in milliseconds to keep clusters disabled after they become unhealthy. */
private static final long GRACE_PERIOD_DEFAULT = 10000;
private static final long GRACE_PERIOD_DEFAULT = 60000;

/** Default maximum number of failover attempts. */
private static final int MAX_NUM_FAILOVER_ATTEMPTS_DEFAULT = 10;
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/redis/clients/jedis/mcf/EchoStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@
import redis.clients.jedis.MultiClusterClientConfig.StrategySupplier;

public class EchoStrategy implements HealthCheckStrategy {
private static final int MAX_HEALTH_CHECK_POOL_SIZE = 2;

private final UnifiedJedis jedis;
private final HealthCheckStrategy.Config config;

public EchoStrategy(HostAndPort hostAndPort, JedisClientConfig jedisClientConfig) {
this(hostAndPort, jedisClientConfig, HealthCheckStrategy.Config.builder().build());
this(hostAndPort, jedisClientConfig, HealthCheckStrategy.Config.create());
}

public EchoStrategy(HostAndPort hostAndPort, JedisClientConfig jedisClientConfig,
HealthCheckStrategy.Config config) {
GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(2);
poolConfig.setMaxTotal(MAX_HEALTH_CHECK_POOL_SIZE);
this.jedis = new JedisPooled(hostAndPort, jedisClientConfig, poolConfig);
this.config = config;
}
Expand Down
17 changes: 11 additions & 6 deletions src/main/java/redis/clients/jedis/mcf/HealthCheckStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ default void close() {
int getDelayInBetweenProbes();

public static class Config {
private static final int INTERVAL_DEFAULT = 5000;
private static final int TIMEOUT_DEFAULT = 1000;
private static final int NUM_PROBES_DEFAULT = 3;
private static final int DELAY_IN_BETWEEN_PROBES_DEFAULT = 500;

protected final int interval;
protected final int timeout;
protected final int numProbes;
Expand Down Expand Up @@ -97,14 +102,14 @@ public ProbingPolicy getPolicy() {
* @return a new Config instance
*/
public static Config create() {
return new Builder<>().build();
return builder().build();
}

/**
* Create a new builder for HealthCheckStrategy.Config.
* @return a new Builder instance
*/
public static Builder<?, Config> builder() {
public static Builder<?, ? extends Config> builder() {
return new Builder<>();
}

Expand All @@ -114,11 +119,11 @@ public static Builder<?, Config> builder() {
* @param <C> the config type being built
*/
public static class Builder<T extends Builder<T, C>, C extends Config> {
protected int interval = 1000;
protected int timeout = 1000;
protected int numProbes = 3;
protected int interval = INTERVAL_DEFAULT;
protected int timeout = TIMEOUT_DEFAULT;
protected int numProbes = NUM_PROBES_DEFAULT;
protected ProbingPolicy policy = ProbingPolicy.BuiltIn.ALL_SUCCESS;
protected int delayInBetweenProbes = 100;
protected int delayInBetweenProbes = DELAY_IN_BETWEEN_PROBES_DEFAULT;

/**
* Set the interval between health checks in milliseconds.
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/redis/clients/jedis/mcf/LagAwareStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,15 @@ public HealthStatus doHealthCheck(Endpoint endpoint) {
public static class Config extends HealthCheckStrategy.Config {

public static final boolean EXTENDED_CHECK_DEFAULT = true;
public static final Duration AVAILABILITY_LAG_TOLERANCE_DEFAULT = Duration.ofMillis(100);
public static final Duration AVAILABILITY_LAG_TOLERANCE_DEFAULT = Duration.ofMillis(5000);

private final Endpoint restEndpoint;
private final Supplier<RedisCredentials> credentialsSupplier;

// SSL configuration for HTTPS connections to Redis Enterprise REST API
private final SslOptions sslOptions;

// Maximum acceptable lag in milliseconds (default: 100);
// Maximum acceptable lag in milliseconds (default: 5000);
private final Duration availability_lag_tolerance;

// Enable extended lag checking (default: true - performs lag validation in addition to standard
Expand All @@ -111,7 +111,7 @@ public static class Config extends HealthCheckStrategy.Config {
private final boolean extendedCheckEnabled;

public Config(Endpoint restEndpoint, Supplier<RedisCredentials> credentialsSupplier) {
this(builder(restEndpoint, credentialsSupplier).interval(1000).timeout(1000).numProbes(3)
this(builder(restEndpoint, credentialsSupplier)
.availabilityLagTolerance(AVAILABILITY_LAG_TOLERANCE_DEFAULT)
.extendedCheckEnabled(EXTENDED_CHECK_DEFAULT));
Comment on lines +114 to 116
Copy link

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor call is missing required method calls for interval, timeout, and numProbes that were previously set in the removed line. This will cause the parent class defaults to be used instead of calling the builder methods explicitly.

Copilot uses AI. Check for mistakes.
}
Expand Down Expand Up @@ -157,6 +157,15 @@ public static ConfigBuilder builder(Endpoint restEndpoint,
return new ConfigBuilder(restEndpoint, credentialsSupplier);
}

/**
* Use {@link LagAwareStrategy.Config#builder(Endpoint, Supplier)} instead.
* @return a new Builder instance
*/
public static ConfigBuilder builder() {
throw new UnsupportedOperationException(
"Endpoint and credentials are required to build LagAwareStrategy.Config.");
}

/**
* Create a new Config instance with default values.
* <p>
Expand Down
77 changes: 77 additions & 0 deletions src/test/java/redis/clients/jedis/mcf/DefaultValuesTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package redis.clients.jedis.mcf;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.time.Duration;

import org.junit.jupiter.api.Test;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.MultiClusterClientConfig;

public class DefaultValuesTest {

HostAndPort fakeEndpoint = new HostAndPort("fake", 6379);
JedisClientConfig config = DefaultJedisClientConfig.builder().build();

@Test
void testDefaultValuesInConfig() {

MultiClusterClientConfig.ClusterConfig clusterConfig = MultiClusterClientConfig.ClusterConfig
.builder(fakeEndpoint, config).build();
MultiClusterClientConfig multiConfig = new MultiClusterClientConfig.Builder(
new MultiClusterClientConfig.ClusterConfig[] { clusterConfig }).build();

// check for grace period
assertEquals(60000, multiConfig.getGracePeriod());

// check for cluster config
assertEquals(clusterConfig, multiConfig.getClusterConfigs()[0]);

// check healthchecks enabled
assertNotNull(clusterConfig.getHealthCheckStrategySupplier());

// check default healthcheck strategy is echo
assertEquals(EchoStrategy.DEFAULT, clusterConfig.getHealthCheckStrategySupplier());

// check number of probes
assertEquals(3,
clusterConfig.getHealthCheckStrategySupplier().get(fakeEndpoint, config).getNumProbes());

assertEquals(500, clusterConfig.getHealthCheckStrategySupplier().get(fakeEndpoint, config)
.getDelayInBetweenProbes());

assertEquals(ProbingPolicy.BuiltIn.ALL_SUCCESS,
clusterConfig.getHealthCheckStrategySupplier().get(fakeEndpoint, config).getPolicy());

// check health check interval
assertEquals(5000,
clusterConfig.getHealthCheckStrategySupplier().get(fakeEndpoint, config).getInterval());

// check lag aware tolerance
LagAwareStrategy.Config lagAwareConfig = LagAwareStrategy.Config
.builder(fakeEndpoint, config.getCredentialsProvider()).build();
assertEquals(Duration.ofMillis(5000), lagAwareConfig.getAvailabilityLagTolerance());

// TODO: check CB number of failures threshold -- 1000
// assertEquals(1000, multiConfig.circuitBreakerMinNumOfFailures());

// check CB failure rate threshold
assertEquals(10, multiConfig.getCircuitBreakerFailureRateThreshold());

// check CB sliding window size
assertEquals(2, multiConfig.getCircuitBreakerSlidingWindowSize());

// check failback check interval
assertEquals(120000, multiConfig.getFailbackCheckInterval());

// check failover max attempts before give up
assertEquals(10, multiConfig.getMaxNumFailoverAttempts());

// check delay between failover attempts
assertEquals(12000, multiConfig.getDelayInBetweenFailoverAttempts());

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ void testFailbackCheckIntervalConfiguration() {
MultiClusterClientConfig defaultConfig = new MultiClusterClientConfig.Builder(
new MultiClusterClientConfig.ClusterConfig[] { clusterConfig }).build();

assertEquals(5000, defaultConfig.getFailbackCheckInterval());
assertEquals(120000, defaultConfig.getFailbackCheckInterval());

// Test custom value
MultiClusterClientConfig customConfig = new MultiClusterClientConfig.Builder(
Expand Down Expand Up @@ -105,7 +105,7 @@ void testGracePeriodConfiguration() {
MultiClusterClientConfig defaultConfig = new MultiClusterClientConfig.Builder(
new MultiClusterClientConfig.ClusterConfig[] { clusterConfig }).build();

assertEquals(10000, defaultConfig.getGracePeriod()); // Default is 10 seconds
assertEquals(60000, defaultConfig.getGracePeriod()); // Default is 10 seconds

// Test custom value
MultiClusterClientConfig customConfig = new MultiClusterClientConfig.Builder(
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/redis/clients/jedis/mcf/HealthCheckTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ void testStrategySupplierPolymorphism() {
// Test without config
HealthCheckStrategy strategyWithoutConfig = supplier.get(testEndpoint, null);
assertNotNull(strategyWithoutConfig);
assertEquals(1000, strategyWithoutConfig.getInterval()); // Default values
assertEquals(5000, strategyWithoutConfig.getInterval()); // Default values
assertEquals(1000, strategyWithoutConfig.getTimeout());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ void healthy_when_bdb_available_and_cached_uid_used_on_next_check() throws Excep
try (MockedConstruction<RedisRestAPI> mockedConstructor = mockConstruction(RedisRestAPI.class,
(mock, context) -> {
when(mock.getBdbs()).thenReturn(Arrays.asList(bdbInfo));
when(mock.checkBdbAvailability("1", true, 100L)).thenReturn(true);
when(mock.checkBdbAvailability("1", true, 5000L)).thenReturn(true);
reference[0] = mock;
})) {
Config lagCheckConfig = Config.builder(endpoint, creds).interval(500).timeout(250)
Expand All @@ -61,7 +61,7 @@ void healthy_when_bdb_available_and_cached_uid_used_on_next_check() throws Excep

assertEquals(HealthStatus.HEALTHY, strategy.doHealthCheck(endpoint));
verify(api, times(1)).getBdbs(); // Should not call getBdbs again when cached
verify(api, times(2)).checkBdbAvailability("1", true, 100L);
verify(api, times(2)).checkBdbAvailability("1", true, 5000L);
}
}
}
Expand Down Expand Up @@ -97,7 +97,7 @@ void exception_and_cache_reset_on_exception_then_recovers_next_time() throws Exc
// First call throws exception, second call returns bdbInfo
when(mock.getBdbs()).thenThrow(new RuntimeException("boom"))
.thenReturn(Arrays.asList(bdbInfo));
when(mock.checkBdbAvailability("42", true, 100L)).thenReturn(true);
when(mock.checkBdbAvailability("42", true, 5000L)).thenReturn(true);
reference[0] = mock;
})) {

Expand All @@ -115,7 +115,7 @@ void exception_and_cache_reset_on_exception_then_recovers_next_time() throws Exc
// Verify getBdbs was called twice (once failed, once succeeded)
verify(api, times(2)).getBdbs();
// Verify availability check was called only once (on the successful attempt)
verify(api, times(1)).checkBdbAvailability("42", true, 100L);
verify(api, times(1)).checkBdbAvailability("42", true, 5000L);
}
}
}
Expand Down Expand Up @@ -173,10 +173,10 @@ void exception_when_no_matching_host_found() throws Exception {
void config_builder_creates_config_with_default_values() {
Config config = Config.builder(endpoint, creds).build();

assertEquals(1000, config.interval);
assertEquals(5000, config.interval);
assertEquals(1000, config.timeout);
assertEquals(3, config.numProbes);
assertEquals(Duration.ofMillis(100), config.getAvailabilityLagTolerance());
assertEquals(Duration.ofMillis(5000), config.getAvailabilityLagTolerance());
assertEquals(endpoint, config.getRestEndpoint());
assertEquals(creds, config.getCredentialsSupplier());
}
Expand Down Expand Up @@ -288,7 +288,7 @@ void base_config_builder_factory_method_works() {
void base_config_create_factory_method_uses_defaults() {
HealthCheckStrategy.Config config = HealthCheckStrategy.Config.create();

assertEquals(1000, config.getInterval());
assertEquals(5000, config.getInterval());
assertEquals(1000, config.getTimeout());
assertEquals(3, config.getNumProbes());
}
Expand Down
Loading