From 36601b5b131b0b44802ee04fb94ce94bbcb7ea3e Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Fri, 5 Apr 2024 16:42:29 -0300 Subject: [PATCH] fix: add graceful shutdown and hikari connection pool (#274) --- backend/openshift.deploy.yml | 6 ++-- backend/pom.xml | 6 ---- .../config/OracleGracefulShutdownConfig.java | 20 +++++++++++ .../oracle/config/OracleHikariConfig.java | 27 ++++++++++++++ .../config/OraclePersistenceConfig.java | 22 +++++++++++- .../PostgresGracefulShutdownConfig.java | 20 +++++++++++ .../postgres/config/PostgresHikariConfig.java | 27 ++++++++++++++ .../postgres/config/PostgresJpaConfig.java | 8 +++-- .../config/PostgresPersistenceConfig.java | 22 +++++++++++- .../src/main/resources/application.properties | 36 +++++++++---------- 10 files changed, 162 insertions(+), 32 deletions(-) create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/oracle/config/OracleGracefulShutdownConfig.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/oracle/config/OracleHikariConfig.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresGracefulShutdownConfig.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresHikariConfig.java diff --git a/backend/openshift.deploy.yml b/backend/openshift.deploy.yml index a37cb5ba..371b43ff 100644 --- a/backend/openshift.deploy.yml +++ b/backend/openshift.deploy.yml @@ -40,13 +40,13 @@ parameters: value: "90000" - name: DB_POOL_IDLE_TIMEOUT description: Maximum amount of milliseconds that a connection is allowed to sit idle in the pool. - value: "0" + value: "60000" - name: DB_POOL_MAX_LIFETIME description: Maximum lifetime of a connection in the pool. - value: "1800000" + value: "120000" - name: DB_POOL_MAX_SIZE description: Maximum number of connections per pod - value: "3" + value: "1" - name: DB_POOL_MIN_IDLE description: Minimum number of connections per pod value: "1" diff --git a/backend/pom.xml b/backend/pom.xml index d292a5f7..e8c50967 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -129,12 +129,6 @@ 1.18.30 true - - org.springframework.boot - spring-boot-devtools - runtime - true - diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/oracle/config/OracleGracefulShutdownConfig.java b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/config/OracleGracefulShutdownConfig.java new file mode 100644 index 00000000..06bf84de --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/config/OracleGracefulShutdownConfig.java @@ -0,0 +1,20 @@ +package ca.bc.gov.restapi.results.oracle.config; + +import jakarta.persistence.EntityManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; + +/** This class adds a listener for closing connection gracefully. */ +@Component +public class OracleGracefulShutdownConfig implements ApplicationListener { + + @Autowired private EntityManager oracleEntityManager; + + @Override + public void onApplicationEvent(@NonNull ContextClosedEvent event) { + oracleEntityManager.close(); + } +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/oracle/config/OracleHikariConfig.java b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/config/OracleHikariConfig.java new file mode 100644 index 00000000..bea36c55 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/config/OracleHikariConfig.java @@ -0,0 +1,27 @@ +package ca.bc.gov.restapi.results.oracle.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** This class holds configurations for the Oracle Hikari connection pool. */ +@Getter +@Setter +@Configuration +@ConfigurationProperties("spring.datasource.oracle") +public class OracleHikariConfig { + + private String driverClassName; + private String url; + private String username; + private String password; + private long connectionTimeout; + private long idleTimeout; + private long maxLifetime; + private long keepaliveTime; + private String poolName; + private int minimumIdle; + private int maximumPoolSize; + private long leakDetectionThreshold; +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/oracle/config/OraclePersistenceConfig.java b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/config/OraclePersistenceConfig.java index 65e9b848..39861e31 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/oracle/config/OraclePersistenceConfig.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/config/OraclePersistenceConfig.java @@ -1,6 +1,9 @@ package ca.bc.gov.restapi.results.oracle.config; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -11,15 +14,32 @@ @Configuration public class OraclePersistenceConfig { + @Autowired private OracleHikariConfig oracleHikariConfig; + @Bean @ConfigurationProperties("spring.datasource.oracle") public DataSourceProperties oracleDataSourceProperties() { return new DataSourceProperties(); } + /** Creates a Postgres Datasource with all Hikari connection pool configuration. */ @Bean @Primary public DataSource oracleDataSource() { - return oracleDataSourceProperties().initializeDataSourceBuilder().build(); + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(oracleHikariConfig.getUrl()); + config.setUsername(oracleHikariConfig.getUsername()); + config.setPassword(oracleHikariConfig.getPassword()); + config.setDriverClassName(oracleHikariConfig.getDriverClassName()); + config.setConnectionTimeout(oracleHikariConfig.getConnectionTimeout()); + config.setIdleTimeout(oracleHikariConfig.getIdleTimeout()); + config.setMaxLifetime(oracleHikariConfig.getMaxLifetime()); + config.setKeepaliveTime(oracleHikariConfig.getKeepaliveTime()); + config.setPoolName(oracleHikariConfig.getPoolName()); + config.setMinimumIdle(oracleHikariConfig.getMinimumIdle()); + config.setMaximumPoolSize(oracleHikariConfig.getMaximumPoolSize()); + config.setLeakDetectionThreshold(oracleHikariConfig.getLeakDetectionThreshold()); + + return new HikariDataSource(config); } } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresGracefulShutdownConfig.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresGracefulShutdownConfig.java new file mode 100644 index 00000000..f1f7e029 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresGracefulShutdownConfig.java @@ -0,0 +1,20 @@ +package ca.bc.gov.restapi.results.postgres.config; + +import jakarta.persistence.EntityManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; + +/** This class adds a listener for closing connection gracefully. */ +@Component +public class PostgresGracefulShutdownConfig implements ApplicationListener { + + @Autowired private EntityManager postgresEntityManager; + + @Override + public void onApplicationEvent(@NonNull ContextClosedEvent event) { + postgresEntityManager.close(); + } +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresHikariConfig.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresHikariConfig.java new file mode 100644 index 00000000..934ac253 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresHikariConfig.java @@ -0,0 +1,27 @@ +package ca.bc.gov.restapi.results.postgres.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** This class holds configurations for the Postgres Hikari connection pool. */ +@Getter +@Setter +@Configuration +@ConfigurationProperties("spring.datasource.postgres") +public class PostgresHikariConfig { + + private String driverClassName; + private String url; + private String username; + private String password; + private long connectionTimeout; + private long idleTimeout; + private long maxLifetime; + private long keepaliveTime; + private String poolName; + private int minimumIdle; + private int maximumPoolSize; + private long leakDetectionThreshold; +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresJpaConfig.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresJpaConfig.java index 97ed2300..1065f5fd 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresJpaConfig.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresJpaConfig.java @@ -45,10 +45,14 @@ public class PostgresJpaConfig { @Bean public LocalContainerEntityManagerFactoryBean postgresEntityManagerFactory( @Qualifier("postgresDataSource") DataSource dataSource, EntityManagerFactoryBuilder builder) { - LocalContainerEntityManagerFactoryBean build = - builder.dataSource(dataSource).packages(getPostgresEntities()).build(); Properties jpaProps = new Properties(); jpaProps.setProperty("hibernate.default_schema", "silva"); + jpaProps.setProperty("hibernate.ddl-auto", "update"); + jpaProps.setProperty("defer-datasource-initialization", "true"); + jpaProps.setProperty("sql.init.mode", "always"); + + LocalContainerEntityManagerFactoryBean build = + builder.dataSource(dataSource).packages(getPostgresEntities()).build(); build.setJpaProperties(jpaProps); return build; } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresPersistenceConfig.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresPersistenceConfig.java index ab855190..bdfcf5d9 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresPersistenceConfig.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresPersistenceConfig.java @@ -1,6 +1,9 @@ package ca.bc.gov.restapi.results.postgres.config; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -10,14 +13,31 @@ @Configuration public class PostgresPersistenceConfig { + @Autowired private PostgresHikariConfig postgresHikariConfig; + @Bean @ConfigurationProperties("spring.datasource.postgres") public DataSourceProperties postgresDataSourceProperties() { return new DataSourceProperties(); } + /** Creates a Postgres Datasource with all Hikari connection pool configuration. */ @Bean public DataSource postgresDataSource() { - return postgresDataSourceProperties().initializeDataSourceBuilder().build(); + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(postgresHikariConfig.getUrl()); + config.setUsername(postgresHikariConfig.getUsername()); + config.setPassword(postgresHikariConfig.getPassword()); + config.setDriverClassName(postgresHikariConfig.getDriverClassName()); + config.setConnectionTimeout(postgresHikariConfig.getConnectionTimeout()); + config.setIdleTimeout(postgresHikariConfig.getIdleTimeout()); + config.setMaxLifetime(postgresHikariConfig.getMaxLifetime()); + config.setKeepaliveTime(postgresHikariConfig.getKeepaliveTime()); + config.setPoolName(postgresHikariConfig.getPoolName()); + config.setMinimumIdle(postgresHikariConfig.getMinimumIdle()); + config.setMaximumPoolSize(postgresHikariConfig.getMaximumPoolSize()); + config.setLeakDetectionThreshold(postgresHikariConfig.getLeakDetectionThreshold()); + + return new HikariDataSource(config); } } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 1b0e5f75..89945b6e 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -3,6 +3,7 @@ logging.level.ca.bc.gov.restapi.results = ${LOGGING_LEVEL:INFO} spring.application.name = results-api server.error.include-message=always server.port = ${SERVER_PORT:8080} +server.shutdown = graceful # Actuator and ops management.endpoint.health.show-details = always @@ -23,31 +24,28 @@ spring.datasource.oracle.driver-class-name = oracle.jdbc.OracleDriver spring.datasource.oracle.url = jdbc:oracle:thin:@tcps://${DATABASE_HOST:nrcdb03.bcgov}:${DATABASE_PORT:1543}/${SERVICE_NAME:dbq01.nrs.bcgov}?javax.net.ssl.trustStore=${ca.bc.gov.nrs.oracle.keystore}&javax.net.ssl.trustStorePassword=${ca.bc.gov.nrs.oracle.secret}&javax.net.ssl.keyStore=${ca.bc.gov.nrs.oracle.keystore}&javax.net.ssl.keyStorePassword=${ca.bc.gov.nrs.oracle.secret}&oracle.net.ssl_certificate_alias=${ca.bc.gov.nrs.oracle.host}&oracle.net.ssl_server_dn_match=false spring.datasource.oracle.username = ${DATABASE_USER} spring.datasource.oracle.password = ${DATABASE_PASSWORD} -spring.datasource.oracle.hikari.connectionTimeout = ${DB_POOL_CONN_TIMEOUT:90000} -spring.datasource.oracle.hikari.idleTimeout = ${DB_POOL_IDLE_TIMEOUT:45000} -spring.datasource.oracle.hikari.maxLifetime = ${DB_POOL_MAX_LIFETIME:30000} -spring.datasource.oracle.hikari.keepaliveTime = 30000 -spring.datasource.oracle.hikari.poolName = SilvaOracleConnPool -spring.datasource.oracle.hikari.minimumIdle = ${DB_POOL_MIN_IDLE:1} -spring.datasource.oracle.hikari.maximumPoolSize = ${DB_POOL_MAX_SIZE:3} -#spring.jpa.database-platform = org.hibernate.dialect.OracleDialect +spring.datasource.oracle.connectionTimeout = ${DB_POOL_CONN_TIMEOUT:90000} +spring.datasource.oracle.idleTimeout = ${DB_POOL_IDLE_TIMEOUT:60000} +spring.datasource.oracle.maxLifetime = ${DB_POOL_MAX_LIFETIME:120000} +spring.datasource.oracle.keepaliveTime = 30000 +spring.datasource.oracle.poolName = SilvaOracleConnPool +spring.datasource.oracle.minimumIdle = ${DB_POOL_MIN_IDLE:1} +spring.datasource.oracle.maximumPoolSize = ${DB_POOL_MAX_SIZE:1} +spring.datasource.oracle.leakDetectionThreshold = 20000 # Database, and JPA - Postgres spring.datasource.postgres.driver-class-name = org.postgresql.Driver spring.datasource.postgres.url = jdbc:postgresql://${POSTGRES_HOST:localhost}:5432/${POSTGRES_DB:postgres} spring.datasource.postgres.username = ${POSTGRES_USER:postgres} spring.datasource.postgres.password = ${POSTGRES_PASSWORD:default} -spring.datasource.postgres.hikari.connectionTimeout = ${DB_POOL_CONN_TIMEOUT:90000} -spring.datasource.postgres.hikari.idleTimeout = ${DB_POOL_IDLE_TIMEOUT:45000} -spring.datasource.postgres.hikari.maxLifetime = ${DB_POOL_MAX_LIFETIME:30000} -spring.datasource.postgres.hikari.keepaliveTime = 30000 -spring.datasource.postgres.hikari.poolName = SilvaPostgresConnPool -spring.datasource.postgres.hikari.minimumIdle = ${DB_POOL_MIN_IDLE:1} -spring.datasource.postgres.hikari.maximumPoolSize = ${DB_POOL_MAX_SIZE:3} -#spring.jpa.database-platform = org.hibernate.dialect.PostgreSQLDialect -#spring.jpa.hibernate.ddl-auto = update -#spring.jpa.defer-datasource-initialization=true -#spring.sql.init.mode=always +spring.datasource.postgres.connectionTimeout = ${DB_POOL_CONN_TIMEOUT:90000} +spring.datasource.postgres.idleTimeout = ${DB_POOL_IDLE_TIMEOUT:60000} +spring.datasource.postgres.maxLifetime = ${DB_POOL_MAX_LIFETIME:120000} +spring.datasource.postgres.keepaliveTime = 30000 +spring.datasource.postgres.poolName = SilvaPostgresConnPool +spring.datasource.postgres.minimumIdle = ${DB_POOL_MIN_IDLE:1} +spring.datasource.postgres.maximumPoolSize = ${DB_POOL_MAX_SIZE:1} +spring.datasource.postgres.leakDetectionThreshold = 20000 # Common database settings spring.jpa.show-sql = true