Failover DB Cluster rds, read-only transaction, sprint boot

SQL [UPDATE tbl_]; cannot execute UPDATE in a read-only transaction

Una de las ventajas que tiene el cluster rds , la posibilidad de tener instancias en Multi-AZ, las cuales te permite tener alta disponibilidad de tu cluster de base de datos, cuando hay Failover en caso de fallos, RDS cambia automáticamente a la instancia diferente, pude suceder que el pool de conexión se quede en la instacia que no es de escritura

cannot execute UPDATE in a read-only transaction
Deploying Amazon RDS Multi-AZ and Read Replica, Simulate Failover | by  Meriem Terki | AWS Tip

En Spring Boot hay diferentes componentes que se utiliza para verificar la salud de una conexión con la base de datos,ConnectionFactory, Este indicador de salud forma parte de las capacidades de gestión de salud permitiendo monitorear el estado de las conexiones de manera efectiva, normal mente ConnectionFactoryHealthIndicator te habitaría que fallo la conexion, pero en algunos caso algunos pods o instancias de nuestra aplicacion no se conecta de forma correcta .

para poder identificar mejor como esta el pool y si esta usando el pool correcto vamos a customizar HealthCheck, que revise que este conectado de forma correcta.

  1. implementamos la dependencia
org.springframework.boot:spring-boot-starter-actuator
  1. Creamos la clase ConnectionPoolValidator, la cual se va encargar de consultar , que no la instancia no sea de lectura con el comando
SHOW transaction_read_only;
@Log
@Component
public class ConnectionPoolValidator {
    private final ConnectionPool connectionPool;

    public ConnectionPoolValidator( @Qualifier("postgresConnection") ConnectionPool connectionPool) {
        this.connectionPool = connectionPool;
    }

    public Flux<Boolean> validateConnection() {
        return connectionPool.create()
                .flatMapMany(connection -> connection
                        .createStatement("SHOW transaction_read_only;")
                        .execute())
                .flatMap(result -> result.map((row, rowMetadata) -> row.get(0, String.class)))
                .flatMap(value -> {
                    log.info("Connection not reader is: " + "off".equals(value) );
                    return Mono.just("off".equals(value));
                })
                .onErrorResume(e -> {
                            log.info("Connection validation error: " + e.getMessage());
                            return Mono.just(false);
                        }
                )
                .doFinally(signalType -> connectionPool.close());
    }
}
  1. Creamos nuestra clase HealthCheck
@Component("HealthCheckWriderRds")
@RequiredArgsConstructor
public class HealthCheck implements HealthIndicator {

    private final ConnectionPoolValidator connectionPoolValidator;

    @Override
    public Health health() {
        if (!Boolean.TRUE.equals(connectionPoolValidator.validateConnection().blockFirst())) {
            return Health.down()
                    .withDetail("Error Code", "down").build();
        }
        return Health.up().build();
    }
} 
  1. validamos que la siguiente url te responda , según sea tu caso

http://localhost:8080/actuator/health

  1. configuramos el livenessProbe, la cual va reiniciar cuando el health , no sea valido .
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: myimage
  name: liveness-http
spec:
  containers:
  - name: myimage 
    image: myimage:0.0.1
    args:
    - liveness
    livenessProbe:
      httpGet:
        path: actuator/health
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3

El tiempo puede variar según tu configuración y lo que demore el Failover, también se recomienda agregar -Dsun.net.inetaddr.ttl=0 se utiliza en aplicaciones Java para configurar el tiempo de vida (TTL, por sus siglas en inglés) de las entradas de caché del sistema de nombres de dominio (DNS).

happy code :).

Reference:

https://www.baeldung.com/spring-boot-health-indicators