Skip to content

Commit

Permalink
fix: Read replica database config not detected [DHIS2-15963] [2.40] (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
larshelge authored Dec 4, 2023
1 parent 5d00275 commit f3853d5
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 186 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
import org.hisp.dhis.common.CodeGenerator;
import org.hisp.dhis.commons.util.DebugUtils;
import org.hisp.dhis.datasource.DatabasePoolUtils;
import org.hisp.dhis.datasource.DefaultReadOnlyDataSourceManager;
import org.hisp.dhis.datasource.ReadOnlyDataSourceManager;
import org.hisp.dhis.external.conf.ConfigurationKey;
import org.hisp.dhis.external.conf.DhisConfigurationProvider;
import org.hisp.dhis.hibernate.HibernateConfigurationProvider;
Expand Down Expand Up @@ -93,7 +93,7 @@ public JdbcTemplate executionPlanJdbcTemplate(@Qualifier("dataSource") DataSourc
@Bean("readOnlyJdbcTemplate")
@DependsOn("dataSource")
public JdbcTemplate readOnlyJdbcTemplate(@Qualifier("dataSource") DataSource dataSource) {
DefaultReadOnlyDataSourceManager manager = new DefaultReadOnlyDataSourceManager(dhisConfig);
ReadOnlyDataSourceManager manager = new ReadOnlyDataSourceManager(dhisConfig);

JdbcTemplate jdbcTemplate =
new JdbcTemplate(MoreObjects.firstNonNull(manager.getReadOnlyDataSource(), dataSource));
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,134 @@
*/
package org.hisp.dhis.datasource;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.hisp.dhis.external.conf.ConfigurationKey.CONNECTION_PASSWORD;
import static org.hisp.dhis.external.conf.ConfigurationKey.CONNECTION_URL;
import static org.hisp.dhis.external.conf.ConfigurationKey.CONNECTION_USERNAME;

import java.beans.PropertyVetoException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hisp.dhis.commons.util.DebugUtils;
import org.hisp.dhis.external.conf.ConfigurationKey;
import org.hisp.dhis.external.conf.DhisConfigurationProvider;
import org.hisp.dhis.util.ObjectUtils;

/**
* @author Lars Helge Overland
*/
public interface ReadOnlyDataSourceManager {
/**
* Returns a data source which should be used for read only queries only. If read only replicas
* have been explicitly defined in the configuration, the data source implementation will be
* routing to potentially multiple underlying data sources. If not, the data source will point to
* the main data source.
*
* @return a DataSource instance.
*/
DataSource getReadOnlyDataSource();

/**
* Returns the number of explicitly defined read only database instances.
*
* @return the number of explicitly defined read only database instances.
*/
int getReadReplicaCount();
@Slf4j
public class ReadOnlyDataSourceManager {
private static final String FORMAT_READ_PREFIX = "read%d.";

private static final String FORMAT_CONNECTION_URL = FORMAT_READ_PREFIX + CONNECTION_URL.getKey();

private static final String FORMAT_CONNECTION_USERNAME =
FORMAT_READ_PREFIX + CONNECTION_USERNAME.getKey();

private static final String FORMAT_CONNECTION_PASSWORD =
FORMAT_READ_PREFIX + CONNECTION_PASSWORD.getKey();

private static final int VAL_ACQUIRE_INCREMENT = 6;

private static final int VAL_MAX_IDLE_TIME = 21600;

private static final int MAX_READ_REPLICAS = 5;

public ReadOnlyDataSourceManager(DhisConfigurationProvider config) {
checkNotNull(config);
init(config);
}

/** State holder for the resolved read only data source. */
private DataSource internalReadOnlyDataSource;

/** State holder for explicitly defined read only data sources. */
private List<DataSource> internalReadOnlyInstanceList;

public void init(DhisConfigurationProvider config) {
List<DataSource> ds = getReadOnlyDataSources(config);

this.internalReadOnlyInstanceList = ds;
this.internalReadOnlyDataSource = !ds.isEmpty() ? new CircularRoutingDataSource(ds) : null;
}

// -------------------------------------------------------------------------
// DataSourceManager implementation
// -------------------------------------------------------------------------

public DataSource getReadOnlyDataSource() {
return internalReadOnlyDataSource;
}

public int getReadReplicaCount() {
return internalReadOnlyInstanceList != null ? internalReadOnlyInstanceList.size() : 0;
}

// -------------------------------------------------------------------------
// Supportive methods
// -------------------------------------------------------------------------

private List<DataSource> getReadOnlyDataSources(DhisConfigurationProvider config) {
String mainUser = config.getProperty(ConfigurationKey.CONNECTION_USERNAME);
String mainPassword = config.getProperty(ConfigurationKey.CONNECTION_PASSWORD);
String driverClass = config.getProperty(ConfigurationKey.CONNECTION_DRIVER_CLASS);
String maxPoolSize = config.getProperty(ConfigurationKey.CONNECTION_POOL_MAX_SIZE);
String dbPoolType = config.getProperty(ConfigurationKey.DB_POOL_TYPE);

Properties props = config.getProperties();

List<DataSource> dataSources = new ArrayList<>();

for (int i = 1; i <= MAX_READ_REPLICAS; i++) {
String jdbcUrl = props.getProperty(String.format(FORMAT_CONNECTION_URL, i));
String username = props.getProperty(String.format(FORMAT_CONNECTION_USERNAME, i));
String password = props.getProperty(String.format(FORMAT_CONNECTION_PASSWORD, i));

username = StringUtils.defaultIfEmpty(username, mainUser);
password = StringUtils.defaultIfEmpty(password, mainPassword);

DatabasePoolUtils.PoolConfig.PoolConfigBuilder builder =
DatabasePoolUtils.PoolConfig.builder();
builder.dhisConfig(config);
builder.password(password);
builder.username(username);
builder.jdbcUrl(jdbcUrl);
builder.dbPoolType(dbPoolType);
builder.maxPoolSize(maxPoolSize);
builder.acquireIncrement(String.valueOf(VAL_ACQUIRE_INCREMENT));
builder.maxIdleTime(String.valueOf(VAL_MAX_IDLE_TIME));

if (ObjectUtils.allNonNull(jdbcUrl, username, password)) {
try {
dataSources.add(DatabasePoolUtils.createDbPool(builder.build()));
} catch (SQLException | PropertyVetoException e) {
String message =
String.format(
"Connection test failed for read replica database pool, "
+ "driver class: '%s', URL: '%s', user: '%s'",
driverClass, jdbcUrl, username);

log.error(message);
log.error(DebugUtils.getStackTrace(e));

throw new IllegalStateException(message, e);
}
}
}

log.info("Read only configuration initialized, read replicas found: " + dataSources.size());

config
.getProperties()
.setProperty(
ConfigurationKey.ACTIVE_READ_REPLICAS.getKey(), String.valueOf(dataSources.size()));

return dataSources;
}
}

0 comments on commit f3853d5

Please sign in to comment.