Search
Duplicate
๐ŸŒฅ๏ธ

DB ๋‹ค์ค‘ํ™”์™€ Replication(SpringBoot ํŽธ)

์ด ํฌ์ŠคํŒ…์€ ์ง€๋‚œ ํฌ์ŠคํŒ…์ธ DB ๋‹ค์ค‘ํ™”์™€ Replication(MySQL ํŽธ) ์— ์ด์–ด์„œ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
DB ์„œ๋ฒ„๋ฅผย Master - Slaveย ๋กœ ์ด์ค‘ํ™” ํ•˜์˜€์œผ๋ฏ€๋กœ,ย SpringBoot์—์„œ ์‚ฌ์šฉํ•˜๋Š”ย DataSource๋„ย Master - Slave๋ฅผ ๊ฐ๊ฐ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœย @Transactionalย ์• ๋…ธํ…Œ์ด์…˜์˜ ์†์„ฑ์„ ์ด์šฉํ•˜์—ฌย readOnly = false์ธ ํŠธ๋žœ์žญ์…˜์€ย Master DataSource๋ฅผ,ย readOnly = true์ธ ํŠธ๋žœ์žญ์…˜์€ย Slave DataSource๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •ํ•ด ์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋ฒ„๋ฅผ Source - Replica ๋กœ ์ด์ค‘ํ™” ํ•˜์˜€์œผ๋ฏ€๋กœ, ์Šคํ”„๋ง๋ถ€ํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” DataSource๋„ Source - Replica์— ๋งž๊ฒŒ 2๊ฐœ๋ฅผ ์จ์•ผํ•œ๋‹ค.ย readOnly = trueย ํŠธ๋žœ์žญ์…˜์€ Replica DataSource๋ฅผ,ย readOnly = falseย ์ธ ํŠธ๋žœ์žญ์…˜์€ Source DataSource๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ๋ถ„๊ธฐํ•ด์•ผํ•œ๋‹ค.

application.yml ์„ค์ •

์ €๋Š”ย Master 1,ย Slave 2ย ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœย Replication์„ ๊ตฌ์„ฑํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— 3๊ฐœ์˜ย DataSource์— ๋Œ€ํ•œ ์„ค์ •์„ ์ž‘์„ฑํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ํ•˜๋‚˜ ์œ ์˜ํ•ด์•ผ ํ•  ์ ์€ ์ผ๋ฐ˜์ ์ธย DataSourceย ๋“ฑ๋ก ๋ฐฉ๋ฒ•๊ณผ๋Š” ๊ณผ์ •์ด ์กฐ๊ธˆ ๋‹ฌ๋ผ์ง„๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.
ํ•˜๋‚˜์˜ย DataSource๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š”ย SpringBoot AutoConfiguration์„ ํ†ตํ•ด์„œ ์ž๋™์œผ๋กœ ๋นˆ์œผ๋กœ ๋งŒ๋“ค์–ด์ ธ ๊ด€๋ฆฌ๋˜์—ˆ์ง€๋งŒ, 2๊ฐœ ์ด์ƒ์˜ย DataSource๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ๋นˆ์„ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ผ๋‹จ 2๊ฐœ์˜ DataSource์— ๋Œ€ํ•œ ์„ค์ •์„ย application.ymlย ์— ์ ์–ด์ค„ ๊ฒƒ์ด๋‹ค. ํ•˜๋‚˜์˜ DataSource์— ๋Œ€ํ•ด์„œ๋Š” ์Šคํ”„๋ง๋ถ€ํŠธ๊ฐ€ ์ž๋™์œผ๋กœ ๋นˆ์œผ๋กœ ๋งŒ๋“ค์–ด ๊ด€๋ฆฌํ•ด์คฌ์ง€๋งŒ, 2๊ฐœ ์ด์ƒ์˜ DataSource๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์—๋Š” ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ๋นˆ์„ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค. ์ผ๋‹จ ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•˜์ž.
spring: datasource: source: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: "jdbc:mysql://localhost:13306/morak" username: root password: root replica1: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: "jdbc:mysql://localhost:13307/morak" username: root password: root replica2: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: "jdbc:mysql://localhost:13308/morak" username: root password: root jpa: show-sql: true
YAML
๋ณต์‚ฌ

yml ์ •๋ณด ๊ฐ์ฒด ๋ฐ”์ธ๋”ฉ

์•ž์„œ yml์— ์„ค์ •ํ•œ ์ •๋ณด๋Š” ์‚ฌ์šฉ์ž ์ž„์˜๋กœ ์ •์˜ํ•ด์ค€ ํ˜•์‹์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ์ •๋ณด๋ฅผ java ์ฝ”๋“œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋กย ConfigurationProperties๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ”์ธ๋”ฉํ•ด์ฃผ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์€ ๋‚ด๋ถ€์ ์œผ๋กœ getter, setter๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ํ•„์ˆ˜์ ์œผ๋กœ getter, setter ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.
@Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.source") public DataSource sourceDataSource() { return DataSourceBuilder.create() .build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.replica1") public DataSource replica1DataSource() { return DataSourceBuilder.create() .build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.replica2") public DataSource replica2DataSource() { return DataSourceBuilder.create() .build(); } }
Java
๋ณต์‚ฌ
์ž๋™์œผ๋กœ Datasource๋ฅผ ๋“ฑ๋กํ•ด์ฃผ์—ˆ๋˜ ์ด์ „๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ ๋“ฑ๋กํ•ด์ค˜์•ผํ•  Datasource๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ์ด๋ฏ€๋กœ ์ˆ˜๋™์œผ๋กœ ๋“ฑ๋กํ•ด์ฃผ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.
๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐ DB ์„œ๋ฒ„์— ๋Œ€์‘๋˜๋Š”ย DataSourceย ํƒ€์ž…์˜ ๋นˆ์„ ๋“ฑ๋กํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
์ถ”๊ฐ€์ ์œผ๋กœย @ConfigurationPropertiesย ****์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉดย application.yml์— ๋ช…์‹œํ•œ ์—ฌ๋Ÿฌ ์„ค์ • ์ค‘, ํŠน์ •ย prefix์— ํ•ด๋‹นํ•˜๋Š” ์„ค์ • ๊ฐ’์„ ์ž๋ฐ” ๋นˆ์— ๋งคํ•‘ํ•  ์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

Source Replica ๋ถ„๊ธฐ์ฒ˜๋ฆฌ

์Šคํ”„๋ง์€ย Multi DataSourceย ํ™˜๊ฒฝ์—์„œ ์—ฌ๋Ÿฌย DataSource๋ฅผ ๋ฌถ๊ณ  ๋ถ„๊ธฐํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œย AbstractRoutingDataSource๋ผ๋Š” ์ถ”์ƒ ํด๋ž˜์Šค๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
public class RoutingDataSource extends AbstractRoutingDataSource { private final RoutingReplicas<String> routingReplicas; public RoutingDataSource(List<String> routingReplicas) { this.routingReplicas = new RoutingReplicas<>(routingReplicas); } @Override protected Object determineCurrentLookupKey() { boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); if (isReadOnly) { return routingReplicas.get(); } else { return "source"; } } }
Java
๋ณต์‚ฌ
์‹ค์ œ ์ €ํฌ๊ฐ€ ์ด๋ฅผ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œย AbstractRoutingDataSourceย ๋ฅผ ์ƒ์†๋ฐ›๋Š” ๊ตฌ์ฒด ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ ์œ„์—์„œ ์–ธ๊ธ‰ํ•œย determineCurrentLookupKey()๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜์—ฌ ํŠธ๋žœ์žญ์…˜์˜ย readOnlyย ๊ฐ’์— ๋”ฐ๋ผ ๋‹ค๋ฅธย DataSourceย ์˜ย Key๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
@Transactional(readOnly=true)์ธ ๊ฒฝ์šฐ replica datasource๋กœ, ๋‚˜๋จธ์ง€๋Š” source datasource๋กœ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ธฐ์œ„ํ•œ ReplicationRoutingDataSource ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
determineCurrentLookupKey()๋ฉ”์„œ๋“œ๋Š” ํ˜„์žฌ ์š”์ฒญ์—์„œ ์‚ฌ์šฉํ•  datasource์˜ key๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์ค๋‹ˆ๋‹ค.
get์—์„œ ์–ด๋–ค replica๋ฅผ ์‚ฌ์šฉํ• ์ง€ ์ •ํ•ด์ง‘๋‹ˆ๋‹ค.
public class RoutingReplicas<T> { private final List<T> replicas; private final AtomicInteger index; public RoutingReplicas(List<T> replicas) { this.replicas = replicas; index = new AtomicInteger(0); } public T get() { return replicas.get(index.getAndIncrement() % replicas.size()); } }
Java
๋ณต์‚ฌ
๋‹ค์Œ๊ณผ ๊ฐ™์ดย RoutingCircularย ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ํ†ตํ•ด์„œย ์—ฌ๋Ÿฌ๊ฐœ์˜ย Slave DB์˜ย DataSource๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ๋กœ๋“œ๋ฐธ๋Ÿฐ์‹ฑย ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

DataSourceConfig ๊ตฌํ˜„

@Configuration public class DataSourceConfig { // data sources @Bean public DataSource routingDataSource( DataSource sourceDataSource, DataSource replica1DataSource, DataSource replica2DataSource ) { Map<Object, Object> dataSources = new HashMap<>(); dataSources.put("source", sourceDataSource); dataSources.put("replica1", replica1DataSource); dataSources.put("replica2", replica2DataSource); RoutingDataSource routingDataSource = new RoutingDataSource(List.of("replica1", "replica2")); routingDataSource.setDefaultTargetDataSource(dataSources.get("source")); routingDataSource.setTargetDataSources(dataSources); return routingDataSource; } @Primary @Bean public DataSource dataSource() { return new LazyConnectionDataSourceProxy(routingDataSource(sourceDataSource(), replica1DataSource(), replica2DataSource())); } }
Java
๋ณต์‚ฌ
์œ„์—์„œ ๋งŒ๋“ ย RoutingDataSourceย ์— ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•  Source DataSource์™€ Replica DataSource ์ •๋ณด๋ฅผ ๋“ฑ๋กํ•˜๊ณ , ์ด๋ฅผ ๋นˆ์œผ๋กœ ๋งŒ๋“ค๊ฒƒ์ด๋‹ค. ์•„๋ž˜ ์†Œ์Šค์ฝ”๋“œ๋Š”ย sourceDataSourceย ์™€ย replicaDataSourceย ๋นˆ์„ ์ •์˜ํ•œย DataSourceConfigurationย ํด๋ž˜์Šค์˜ ์†Œ์Šค์ฝ”๋“œ์— ์ด์–ด ์ž‘์„ฑํ•œ ๋‚ด์šฉ์ด๋‹ค.

AOP๋Š”?

SET AUTO COMMIT ์€?

LazyConnectionDataSourceProxy๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 
์ผ๋ฐ˜์ ์œผ๋กœ Spring์—์„œ๋Š” DataSource๋ฅผ ํ•˜๋‚˜๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹œ์ž‘์ „์— ์ •ํ•ด ๋†“์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ฆฌํ”Œ๋ ‰์…˜์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ DataSource๊ฐ€ ์•„์ง ์ •ํ•ด์ง€์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ Lazy ์ „๋žต์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ œ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰ ๋  ๋•Œ DataSource๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
์ด์ œ ์‹ค์ œ ์Šคํ”„๋ง๋ถ€ํŠธ ์ „๋ฐ˜์—์„œ ์‚ฌ์šฉ๋ ย DataSource dataSource()ย ๋นˆ์„ ๋งŒ๋“ค์–ด๋ณด์ž. ๊ทธ๋Ÿฐ๋ฐ, ์Šคํ”„๋ง์ดย DataSourceย ๋ฅผ ํ†ตํ•ดย Connectionย ์„ ํš๋“ํ•˜๋Š” ๊ณผ์ •์— ๋Œ€ํ•ด ์ดํ•ดํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.
์Šคํ”„๋ง์€ ํŠธ๋žœ์žญ์…˜์— ์ง„์ž…๋œ ์ˆœ๊ฐ„ ๋ฐ”๋กœย DataSourceย ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , ์ปค๋„ฅ์…˜์„ ํš๋“ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ๋‹ค์Œ์— ํŠธ๋žœ์žญ์…˜์˜ ํ˜„์žฌ ์ƒํƒœ๊ฐ€ ์ €์žฅ๋œ๋‹ค. ์ฆ‰, TransactionSynchronizationManager์— ํŠธ๋žœ์žญ์…˜ ์ •๋ณด๋ฅผ ๋™๊ธฐํ™” ํ•˜๋Š” ์ž‘์—…์€ DataSource๋กœ๋ถ€ํ„ฐ Connection์„ ์–ป์–ด์˜จ ์ดํ›„ ๋™์ž‘ํ•œ๋‹ค.
๋”ฐ๋ผ์„œย LazyConnectionDataSourceProxyย ๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠธ๋žœ์žญ์…˜์— ์ง„์ž…ํ•œ ์‹œ์ ์ด ์•„๋‹ˆ๋ผ, ์‹ค์ œ ์ฟผ๋ฆฌ๊ฐ€ ์‹œ์ž‘๋œ ์‹œ์ ์—ย DataSourceย ๊ฐ€ ์„ ํƒ๋˜๋„๋ก ์ง€์—ฐ(lazy) ์‹œํ‚ฌ ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

๊ฐœ์„ ์ 

โ€ข
์ปค๋„ฅ์…˜ ๋ฌธ์ œ
โ€ข
์ถ”๊ฐ€ ์ œ๊ฑฐ ๋ฌธ์ œ