Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat-be: DataSource Routing을 통한 DB Read/Write 요청 분산 #704

Merged
merged 12 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/be-cd_prod-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ jobs:
# DB Configuration secrets info from Github Secrets
DB_PORT=${{ secrets.DB_PORT }}
DB_IP_ADDRESS=${{ secrets.DB_IP_ADDRESS }}
DB_URL=${{ secrets.DB_URL }}
READ_DB_URL=${{ secrets.READ_DB_URL }}
WRITE_DB_URL=${{ secrets.WRITE_DB_URL }}
DB_USER=${{ secrets.DB_USER }}
DB_PASSWORD=${{ secrets.DB_PASSWORD }}
DDL_AUTO=${{ secrets.DDL_AUTO }}
Expand Down
59 changes: 59 additions & 0 deletions backend/src/main/java/com/cruru/config/DataSourceConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.cruru.config;

import com.zaxxer.hikari.HikariDataSource;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;

@Configuration
public class DataSourceConfig {

private static final String READ_DATASOURCE = "readDataSource";
private static final String WRITE_DATASOURCE = "writeDataSource";
private static final String ROUTE_DATASOURCE = "routeDataSource";

@Bean(name = READ_DATASOURCE)
@ConfigurationProperties(prefix = "spring.datasource.read")
public DataSource readDataSource() {
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.build();
}

@Bean(name = WRITE_DATASOURCE)
@ConfigurationProperties(prefix = "spring.datasource.write")
public DataSource writeDataSource() {
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.build();
}

@Bean(name = ROUTE_DATASOURCE)
@DependsOn({READ_DATASOURCE, WRITE_DATASOURCE})
public DataSourceRouter routeDataSource() {
DataSourceRouter dataSourceRouter = new DataSourceRouter();
DataSource writeDataSource = writeDataSource();
DataSource readDataSource = readDataSource();

Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DataSourceRouter.READ_DATASOURCE_KEY, readDataSource);
dataSourceMap.put(DataSourceRouter.WRITE_DATASOURCE_KEY, writeDataSource);
dataSourceRouter.setTargetDataSources(dataSourceMap);
dataSourceRouter.setDefaultTargetDataSource(writeDataSource());
return dataSourceRouter;
}

@Bean
@Primary
@DependsOn(ROUTE_DATASOURCE)
public DataSource defaultDataSource() {
return new LazyConnectionDataSourceProxy(routeDataSource());
Copy link
Contributor

Choose a reason for hiding this comment

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

LazyConnectionDataSourceProxy은 실제로 커넥션이 필요한경우가 아니라면 데이터베이스 풀에서 커넥션을 점유하지 않고 실제로 필요한 시점에만 커넥션을 점유하게 할 수 있게 합니다.

}
}
18 changes: 18 additions & 0 deletions backend/src/main/java/com/cruru/config/DataSourceRouter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.cruru.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.support.TransactionSynchronizationManager;

public class DataSourceRouter extends AbstractRoutingDataSource {

public static final String READ_DATASOURCE_KEY = "read";
public static final String WRITE_DATASOURCE_KEY = "write";

@Override
protected Object determineCurrentLookupKey() {
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
return READ_DATASOURCE_KEY;
}
Comment on lines +13 to +15
Copy link
Member

Choose a reason for hiding this comment

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

복제 구성은 주로 writer:read의 관계가 1:N입니다. 현재 구조에서 read db가 늘어나도 코드를 그대로 재사용할 수 있나요?

Copy link
Contributor

Choose a reason for hiding this comment

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

현재 구조에서는 read data가 늘어났을 때 코드를 재사용할 수 없죠.
read data가 늘어난다면 round-robin 형식으로 datasource를 지정하도록 변경할 수 있을 것 같습니다.
해당 사항은 나중에 반영해보도록 하겠습니다. 적용해보려 했는데 properties를 다루면서 에러를 많이 만날 것 같습니다..

return WRITE_DATASOURCE_KEY;
}
}
47 changes: 34 additions & 13 deletions backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ spring:
console:
enabled: true
datasource:
url: jdbc:h2:mem:database;MODE=MySQL;
read:
jdbc-url: jdbc:h2:mem:database;MODE=MySQL;
write:
jdbc-url: jdbc:h2:mem:database;MODE=MySQL;
flyway:
enabled: false
jpa:
Expand Down Expand Up @@ -68,10 +71,16 @@ spring:
activate:
on-profile: dev
datasource:
url: ${DB_URL}
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USER}
password: ${DB_PASSWORD}
read:
jdbc-url: ${DB_URL}
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USER}
password: ${DB_PASSWORD}
write:
jdbc-url: ${DB_URL}
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USER}
password: ${DB_PASSWORD}
flyway:
enabled: true
baseline-on-migrate: true
Expand Down Expand Up @@ -147,10 +156,16 @@ spring:
activate:
on-profile: test
datasource:
url: ${DB_URL}
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USER}
password: ${DB_PASSWORD}
read:
jdbc-url: ${DB_URL}
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USER}
password: ${DB_PASSWORD}
write:
jdbc-url: ${DB_URL}
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USER}
password: ${DB_PASSWORD}
flyway:
enabled: true
baseline-on-migrate: true
Expand Down Expand Up @@ -225,10 +240,16 @@ spring:
activate:
on-profile: prod
datasource:
url: ${DB_URL}
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USER}
password: ${DB_PASSWORD}
read:
jdbc-url: ${READ_DB_URL}
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USER}
password: ${DB_PASSWORD}
write:
jdbc-url: ${WRITE_DB_URL}
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USER}
password: ${DB_PASSWORD}
flyway:
enabled: true
baseline-on-migrate: true
Expand Down
37 changes: 37 additions & 0 deletions backend/src/test/java/com/cruru/config/DataSourceRouterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.cruru.config;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@DisplayName("Datasource Routing 테스트")
class DataSourceRouterTest {

private final DataSourceRouter dataSourceRouter = new DataSourceRouter();

@Test
void determineCurrentLookupKeyForReadOnlyTransaction() {
// given
TransactionSynchronizationManager.setCurrentTransactionReadOnly(true);

// when
Object lookupKey = dataSourceRouter.determineCurrentLookupKey();

// then
assertThat(lookupKey).isEqualTo(DataSourceRouter.READ_DATASOURCE_KEY);
}

@Test
void determineCurrentLookupKeyForReadWriteTransaction() {
// give
TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);

// when
Object lookupKey = dataSourceRouter.determineCurrentLookupKey();

// then
assertThat(lookupKey).isEqualTo(DataSourceRouter.WRITE_DATASOURCE_KEY);
}
}
5 changes: 4 additions & 1 deletion backend/src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ spring:
console:
enabled: true
datasource:
url: jdbc:h2:mem:database;MODE=MySQL;
read:
jdbc-url: jdbc:h2:mem:database;MODE=MySQL;
write:
jdbc-url: jdbc:h2:mem:database;MODE=MySQL;
flyway:
enabled: false
jpa:
Expand Down