Skip to content

Conversation

@atakavci
Copy link
Collaborator

There is half baked weighted endpoint selection in current implementation. This PR introduces the proper health status check while improves the overall quality of the code.

  • Add HealthStatus Enum
    • New HealthStatus enum with states: UNKNOWN, HEALTHY, UNHEALTHY
    • Default status is HEALTHY for new databases
  • Simplify Configuration
    • Removed RedisDatabase.RedisDatabaseConfig inner class
    • RedisDatabase now accepts DatabaseConfig directly
  • Improve Failover Logic
    • Failover now filters by HealthStatus.HEALTHY instead of circuit breaker state
    • Avoid unnecessary exceptions with getNextHealthyDatabase

@atakavci atakavci self-assigned this Nov 19, 2025
@atakavci atakavci added the type: feature A new feature label Nov 19, 2025
@atakavci atakavci requested a review from Copilot November 19, 2025 08:43
Copilot finished reviewing on behalf of atakavci November 19, 2025 08:45
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces a HealthStatus enum and refactors the failover logic to use health-based endpoint selection instead of directly checking circuit breaker states. The changes simplify the configuration by removing the RedisDatabaseConfig inner class and having RedisDatabase accept DatabaseConfig directly.

Key Changes:

  • Added HealthStatus enum with UNKNOWN, HEALTHY, and UNHEALTHY states
  • Refactored RedisDatabase to use DatabaseConfig directly instead of inner RedisDatabaseConfig class
  • Updated failover logic in StatefulRedisMultiDbConnectionImpl to filter by health status instead of circuit breaker state

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
HealthStatus.java New enum defining health states (UNKNOWN, HEALTHY, UNHEALTHY) for database endpoints
RedisDatabase.java Removed inner RedisDatabaseConfig class, added healthStatus field with getter, updated constructor to accept DatabaseConfig directly
StatefulRedisMultiDbConnectionImpl.java Renamed getHealthyDatabase to getNextHealthyDatabase, changed filtering from circuit breaker state to health status, improved error handling with orElse(null)
MultiDbClientImpl.java Simplified database creation by passing DatabaseConfig directly instead of wrapping in RedisDatabaseConfig

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@atakavci atakavci changed the title [automatic failover] Implement weighted endpoint selection [automatic-failover] Implement weighted endpoint selection Nov 19, 2025
package io.lettuce.core.failover;

public enum HealthStatus {
UNKNOWN, HEALTHY, UNHEALTHY
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume UNKNOWN status is not used as of now, but is intended to be used once we have actual health checks as the initial state, till we get a result from the first health check?

return databases.values().stream().filter(db -> db != current)
.filter(db -> db.getCircuitBreaker().getCurrentState() == CircuitBreaker.State.CLOSED)
.max(Comparator.comparingDouble(RedisDatabase::getWeight)).get();
private RedisDatabase<C> getNextHealthyDatabase(RedisDatabase<C> current) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to confirm my understanding getHealthStatus() reflects the state of database only based on HealthChecks that will be introduced(not considering the state of CB for given DB)?

In that case where do we ensure that failoverFrom() is not failing over toward DB with CB already in OPEN_STATE?

.filter(db -> db.getCircuitBreaker().getCurrentState() == CircuitBreaker.State.CLOSED)
.max(Comparator.comparingDouble(RedisDatabase::getWeight)).get();
private RedisDatabase<C> getNextHealthyDatabase(RedisDatabase<C> current) {
return databases.values().stream().filter(db -> db.getHealthStatus() == HealthStatus.HEALTHY)
Copy link
Contributor

Choose a reason for hiding this comment

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

Improve readability,I suggest something like :

private RedisDatabase<C> getNextHealthyDatabase(RedisDatabase<C> current) {
        return databases.values().stream()
                .filter(isHealthy)
                .filter(isNotCurrent(current))
                .max(DatabaseComparators.byWeight).orElse(null);
    }
    
static class DatabaseComparators {
        public static final Comparator<RedisDatabase<?>> byWeight = Comparator.comparingDouble(RedisDatabase::getWeight);
    }

static class DatabasePredicates {
        public static final Predicate<RedisDatabase<?>> isHealthy = db -> db.getHealthStatus() == HealthStatus.HEALTHY;

        public static Predicate<RedisDatabase<?>> isNotCurrent( RedisDatabase<?> current) {
            return db -> !db.equals(current);
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants