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
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.
- implementamos la dependencia
org.springframework.boot:spring-boot-starter-actuator
- 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());
}
}
- 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();
}
}
- validamos que la siguiente url te responda , según sea tu caso
http://localhost:8080/actuator/health
- 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: