Skip to content

Latest commit

ย 

History

History
400 lines (317 loc) ยท 15.6 KB

2021-05-06-spring-transaction.md

File metadata and controls

400 lines (317 loc) ยท 15.6 KB

๋Œ€๋ถ€๋ถ„์˜ ์„œ๋น„์Šค์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ Master, Slave ๊ตฌ์กฐ๋กœ Master์—์„œ๋Š” Create, Update, Delete ์—…๋ฌด๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  Slave์—์„œ Read ์—…๋ฌด๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ๊ตฌ์กฐ๋กœ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค. Spring์˜ Master, Slave ํ™˜๊ฒฝ์—์„œ์˜ ํŠธ๋žœ์žญ์…˜์— ๋Œ€ํ•ด์„œ ํฌ์ŠคํŒ…ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ ˆํ”Œ๋ฆฌ์ผ€์ด์…˜

MySQL ๋ ˆํ”Œ๋ฆฌ์ผ€์ด์…˜

MySQL์€ ์œ„์™€ ๊ฐ™์€ ๊ตฌ์กฐ๋กœ ๋งˆ์Šคํ„ฐ - ์Šฌ๋ ˆ์ด๋ธŒ ๊ตฌ์กฐ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ„๋žตํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋งˆ์Šคํ„ฐ์˜ ๋ณ€๊ฒฝ์„ ๊ธฐ๋กํ•˜๊ธฐ ์œ„ํ•œ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋กœ๊ทธ
  • ์Šฌ๋ ˆ์ด๋ธŒ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๊ธฐ ์œ„ํ•œ ๋งˆ์Šคํ„ฐ ์Šค๋ ˆ๋“œ
  • ์Šฌ๋ ˆ์ด๋ธŒ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ๋ฆด๋ ˆ์ด ๋กœ๊ทธ์— ๊ธฐ๋กํ•˜๊ธฐ ์œ„ํ•œ I/O ์Šค๋ ˆ๋“œ
  • ๋ฆด๋ ˆ์ด ๋กœ๊ทธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด ์žฌ์ƒํ•˜๊ธฐ ์œ„ํ•œ ์Šฌ๋ ˆ์ด๋ธŒ SQL ์Šค๋ ˆ๋“œ

MySQL 5.7๋ถ€ํ„ฐ ACK๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์‹œ์ ์˜ ๋ณ€๊ฒฝ์ด ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด COMMIT์„ ์‹คํ–‰ํ•œ ๋‹ค์Œ์ด ์•„๋‹ˆ๋ผ COMMIT์„ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ACK๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋„๋ก ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ๋งˆ์Šคํ„ฐ์—์„œ COMMIT์ด ์™„๋ฃŒ๋œ ํŠธ๋žœ์žญ์…˜์€ ๋ชจ๋‘ ์Šฌ๋ ˆ์ด๋ธŒ์— ํ™•์‹คํžˆ ์ „๋‹ฌ๋˜๊ฒŒ ๋˜์–ด์„œ ๋ฌด์†์‹ค ๋ ˆํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ณด๋‹ค ์ž˜ ์ง€์›ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ MySQL 5.7 ์™„๋ฒฝ ๋ถ„์„์— ์ž˜ ์„ค๋ช…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

RoutingDataSource

ํŠธ๋žœ์žญ์…˜์—์„œ readOnly ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ false ๊ฒฝ์šฐ Master DataSource, true ๊ฒฝ์šฐ Slave DataSource๋ฅผ ๋ฐ”๋ผ๋ณด๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Master, Slave์˜ DataSource์˜ ์„ค์ •์€ Spring Boot์—์„œ์˜ ์ˆœ์ •์„ ์ƒํƒœ๋ฅผ ์ตœ๋Œ€ํ•œ ์ด์šฉํ•˜๋ ค ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ณธ ํฌ์ŠคํŒ…์€ DataSource ์ฝ”๋“œ ๊ตฌ์„ฑ์„ ๋‹ค๋ฃจ๋Š” ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๋‚ด์šฉ์€ ๋‹ค๋ฅธ ์ž๋ฃŒ๋ฅผ ๋ณด์‹œ๋Š” ๊ฒƒ์„ ๊ถŒ์žฅ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

# application.yml
spring:
    datasource:
        initialization-mode: never
        master:
            hikari:
                jdbc-url: jdbc:mysql://localhost:3306/study?useSSL=false&serverTimezone=UTC&autoReconnect=true&rewriteBatchedStatements=true&logger=Slf4JLogger&profileSQL=false&maxQuerySizeToLog=100000
                username: root
                password: root
                driver-class-name: com.mysql.cj.jdbc.Driver

        slave:
            hikari:
                jdbc-url: jdbc:mysql://localhost:3307/study?useSSL=false&serverTimezone=UTC&autoReconnect=true&rewriteBatchedStatements=true&logger=Slf4JLogger&profileSQL=false&maxQuerySizeToLog=100000
                username: root
                password: root
                driver-class-name: com.mysql.cj.jdbc.Driver
const val PROPERTIES = "spring.datasource.hikari"
const val MASTER_DATASOURCE = "masterDataSource"
const val SLAVE_DATASOURCE = "slaveDataSource"

@Configuration
class DataSourceConfiguration {

    @Bean(name = [MASTER_DATASOURCE])
    @ConfigurationProperties(prefix = "spring.datasource.master.hikari")
    fun masterDataSource() =
        DataSourceBuilder.create()
            .type(HikariDataSource::class.java)
            .build()

    @Bean(name = [SLAVE_DATASOURCE])
    @ConfigurationProperties("spring.datasource.slave.hikari")
    fun slaveDataSource() =
        DataSourceBuilder.create()
            .type(HikariDataSource::class.java)
            .build()
            .apply { this.isReadOnly = true }

    @Bean
    @DependsOn(MASTER_DATASOURCE, SLAVE_DATASOURCE)
    fun routingDataSource(
        @Qualifier(MASTER_DATASOURCE) masterDataSource: DataSource,
        @Qualifier(SLAVE_DATASOURCE) slaveDataSource: DataSource
    ): DataSource {
        val routingDataSource = RoutingDataSource()
        val dataSources = hashMapOf<Any, Any>()
        dataSources["master"] = masterDataSource
        dataSources["slave"] = slaveDataSource
        routingDataSource.setTargetDataSources(dataSources)
        routingDataSource.setDefaultTargetDataSource(masterDataSource)
        return routingDataSource
    }

    @Primary
    @Bean
    @DependsOn("routingDataSource")
    fun dataSource(routingDataSource: DataSource) =
        LazyConnectionDataSourceProxy(routingDataSource)
}

class RoutingDataSource : AbstractRoutingDataSource() {
    override fun determineCurrentLookupKey(): Any =
        when {
            TransactionSynchronizationManager.isCurrentTransactionReadOnly() -> "slave"
            else -> "master"
        }
}

masterDataSource, routingDataSource DataSource์˜ Bean์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ๊ฐ 3306(Master), 3307(Slave) ํฌํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ ํ•ด๋‹น ๋””๋น„๋Š” Master, Slave ์„ค์ •๊นŒ์ง€ ์™„๋ฃŒ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

routingDataSource์—์„œ๋Š” masterDataSource, routingDataSource ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ master, slave๋ฅผ HashMap์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. determineCurrentLookupKey ๋ฉ”์„œ๋“œ์—์„œ readOnly ์„ค์ • ์—ฌ๋ถ€์— ๋”ฐ๋ผ master, slave์˜ DataSource๋ฅผ ์„ ํƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ ๊ธฐ๋ณธ dataSource Bean์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ routingDataSource Bean์„ ์ด์šฉํ•ด์„œ LazyConnectionDataSourceProxy๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

RoutingDataSource ํ…Œ์ŠคํŠธ

@RestController
@RequestMapping("/api/book")
class BookApi(
    private val bookRepository: BookRepository
) {

    @GetMapping("/slave")
    @Transactional(readOnly = true)
    fun getSlave() = bookRepository.findAll()

    @GetMapping("/master")
    @Transactional(readOnly = false)
    fun getMaster() = bookRepository.findAll()
}

/api/book/slave๋Š” readOnly = true ์„ค์ •์œผ๋กœ Slave๋ฅผ ๋ฐ”๋ผ๋ณด๊ฒŒ ํ•˜๊ณ , ๊ทธ์™€ ๋ฐ˜๋Œ€๋กœ /api/book/master๋Š” readOnly = false์„ค์ •์œผ๋กœ Master๋ฅผ ๋ฐ”๋ผ๋ณด๊ฒŒ ์„ค์ •ํ•˜๊ณ  API ํ˜ธ์ถœ ์ดํ›„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

readOnly ์—ฌ๋ถ€์— ๋”ฐ๋ผ์„œ DataSource๊ฐ€ ์ ์ ˆํ•˜๊ฒŒ ๋ผ์šฐํŒ… ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Spring Transaction ํ…Œ์ŠคํŠธ

@Component
class AppSetup(
    private val bookRepository: BookRepository
) : ApplicationRunner {

    override fun run(args: ApplicationArguments) {
        bookRepository.saveAll(
            (1..5).map { Book(title = "INIT") }
                .toList()
        )
    }
}

Application์ด ์‹คํ–‰๋  ๋•Œ title์ด INIT์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ 5๊ฐœ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ๋ฉ”์„œ๋“œ๋งˆ๋‹ค readOnly ์„ค์ •์„ ๋‹ค๋ฃจ๊ฒŒ ๋‘๊ณ  ์—…๋ฐ์ดํŠธ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์กฐํšŒ ์ดํ›„ ์—…๋ฐ์ดํŠธ

@RestController
@RequestMapping("/api/book")
class BookApi(
    private val bookRepository: BookRepository,
    private val bookService: BookService
) {

    @GetMapping("/update/slave")
    fun startSlave() {
        bookService.updateSlave()
    }
}

@Service
class BookService(
    private val bookRepository: BookRepository
) {

    @Transactional(readOnly = true)
    fun updateSlave() {
        updateTitle("new title(slave)")
    }

    @Transactional(readOnly = false)
    fun updateMaster() {
        updateTitle("new title(master)")
    }

    @Transactional(readOnly = false)
    fun updateTitle(title: String) {
        val books = bookRepository.findAll()
        for (book in books) {
            book.title = title
        }
        bookRepository.saveAll(books)
    }
}
  • updateSlave() ๋ฉ”์„œ๋“œ๋Š” readOnly = true ์‹œ์ž‘ํ•˜๊ณ , updateTitle() ๋ฉ”์„œ๋“œ์—์„œ readOnly = false๋กœ ์ง„ํ–‰
  • updateMaster() ๋ฉ”์„œ๋“œ๋Š” readOnly = false ์‹œ์ž‘ํ•˜๊ณ , updateTitle() ๋ฉ”์„œ๋“œ์—์„œ readOnly = false๋กœ ์ง„ํ–‰

slave ์กฐํšŒ ์ดํ›„ ์—…๋ฐ์ดํŠธ

select query๊ฐ€ slave์—์„œ ์ง„ํ–‰๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์กฐํšŒ๋Š” readOnly = true ์„ค์ •์ด์ง€๋งŒ title๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๋•Œ๋Š” readOnly = false์ด๊ธฐ ๋•Œ๋ฌธ์— ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ flush๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด ํ•ด๋‹น ๋‚ด์šฉ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ˜์˜๋  ๊นŒ์š”? ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

GET http://localhost:8080/api/book/master

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 06 May 2021 13:53:28 GMT
Keep-Alive: timeout=60
Connection: keep-alive

[
  {
    "title": "INIT",
    "id": 1,
    "createdAt": "2021-05-06T22:42:33",
    "updatedAt": "2021-05-06T22:42:33"
  },
  {
    "title": "INIT",
    "id": 2,
    "createdAt": "2021-05-06T22:42:33",
    "updatedAt": "2021-05-06T22:42:33"
  },
  {
    "title": "INIT",
    "id": 3,
    "createdAt": "2021-05-06T22:42:33",
    "updatedAt": "2021-05-06T22:42:33"
  },
  {
    "title": "INIT",
    "id": 4,
    "createdAt": "2021-05-06T22:42:33",
    "updatedAt": "2021-05-06T22:42:33"
  },
  {
    "title": "INIT",
    "id": 5,
    "createdAt": "2021-05-06T22:42:33",
    "updatedAt": "2021-05-06T22:42:33"
  }
]

Response code: 200; Time: 31ms; Content length: 461 bytes

๊ฒฐ๊ณผ๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

master ์กฐํšŒ ์ดํ›„ ์—…๋ฐ์ดํŠธ

select, update query๊ฐ€ master์—์„œ ์ง„ํ–‰๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ API๋ฅผ ํ˜ธ์ถœํ•ด์„œ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

GET http://localhost:8080/api/book/master

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 06 May 2021 14:01:40 GMT
Keep-Alive: timeout=60
Connection: keep-alive

[
  {
    "title": "new title(master)",
    "id": 1,
    "createdAt": "2021-05-06T22:42:33",
    "updatedAt": "2021-05-06T22:59:06"
  },
  {
    "title": "new title(master)",
    "id": 2,
    "createdAt": "2021-05-06T22:42:33",
    "updatedAt": "2021-05-06T22:59:06"
  },
  {
    "title": "new title(master)",
    "id": 3,
    "createdAt": "2021-05-06T22:42:33",
    "updatedAt": "2021-05-06T22:59:06"
  },
  {
    "title": "new title(master)",
    "id": 4,
    "createdAt": "2021-05-06T22:42:33",
    "updatedAt": "2021-05-06T22:59:06"
  },
  {
    "title": "new title(master)",
    "id": 5,
    "createdAt": "2021-05-06T22:42:33",
    "updatedAt": "2021-05-06T22:59:06"
  }
]

Response code: 200; Time: 51ms; Content length: 526 bytes

์ •์ƒ์ ์œผ๋กœ title์ด ๋ณ€๊ฒฝ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์—์„œ Update

@Service
class BookService(
    private val bookRepository: BookRepository
) {

    @Transactional(readOnly = true)
    fun updateSlave() {
        println("updateSlave CurrentTransactionName: ${TransactionSynchronizationManager.getCurrentTransactionName()}")
        bookUpdateService.updateTitle("new title(slave)")
    }
}

@Service
class BookUpdateService(
    private val bookRepository: BookRepository
){
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    fun updateTitle(title: String) {
        println("updateTitle CurrentTransactionName: ${TransactionSynchronizationManager.getCurrentTransactionName()}")
        val books = bookRepository.findAll()
        for (book in books) {
            book.title = title
        }
        bookRepository.saveAll(books)
    }
}

BookUpdateService ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๊ณ  updateTitle ๋ฉ”์„œ๋“œ๋ฅผ ํ•ด๋‹น ํด๋ž˜์Šค์—์„œ propagation = Propagation.REQUIRES_NEW ์„ค์ •์„ ํ†ตํ•ด์„œ ์ƒˆ๋กœ์šด ํŠธ๋žœ์žญ์…˜์—์„œ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์ž‘์„ฑํ•˜๊ณ  /api/book/update/slave๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์กฐํšŒ API๋ฅผ ํ˜ธ์ถœํ•ด์„œ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ธฐ์กด ์„œ๋น„์Šค ์ฝ”๋“œ์—์„œ ๊ตฌํ˜„์„ ํ•˜์ง€ ์•Š๊ณ  BookUpdateService ์„œ๋น„์Šค ์ฝ”๋“œ๋ฅผ ๋งŒ๋“  ์ด์œ ๋Š” ๋™์ผํ•œ Bean์—์„œ @Transactional์˜ˆ์ƒํ•˜๋Š” ๊ฒƒ๊ณผ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ด์ „ ๋™์ผํ•œ Bean(Class)์—์„œ @Transactional ๋™์ž‘ ๋ฐฉ์‹์„ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”

GET http://localhost:8080/api/book/slave

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 08 May 2021 12:38:56 GMT
Keep-Alive: timeout=60
Connection: keep-alive

[
  {
    "title": "new title(slave)",
    "id": 1,
    "createdAt": "2021-05-08T21:37:43",
    "updatedAt": "2021-05-08T21:38:44"
  },
  ...
]

Response code: 200; Time: 107ms; Content length: 521 bytes

์ƒˆ๋กœ์šด ํŠธ๋žœ์žญ์…˜์˜ ๊ฒฝ์šฐ masterDataSource๋ฅผ ๋ฐ”๋ผ๋ณด๊ฒŒ ๋˜๋ฉฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ˜์˜๋˜๋Š”๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŠธ๋žœ์žญ์…˜ ์ด๋ฆ„์„ ๋ด๋„ ํ•ด๋‹น ํŠธ๋žœ์žญ์…˜์ด ๋‹ค๋ฅด๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰ com.example.springtransaction.BookService.updateSlave ํŠธ๋žœ์žญ์…˜์€ salveDataSoruce, com.example.springtransaction.BookUpdateService.updateTitle๋Š” masterDataSource๋ฅผ ๋ฐ”๋ผ๋ด…๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ์™œ ๊ทธ๋Ÿฐ๊ฒƒ์ผ๊นŒ?

์ฒซ ํŠธ๋žœ์žญ์…˜์˜ ์„ค์ •์˜ readOnly์— ๋”ฐ๋ผ salveDataSoruce, masterDataSource๊ฐ€ ๊ฒฐ์ •๋˜๋ฉฐ, ๋™์ผํ•œ ํŠธ๋žœ์žญ์…˜์—์„œ๋Š” ์ง€์ •๋œ readOnly ์†์„ฑ์€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด ํŠธ๋žœ์žญ์…˜๊ณผ ์ „ํ˜€ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์„ ๋งŒ๋‚˜๊ฒŒ ๋˜๋ฉด ํ•ด๋‹น ํŠธ๋žœ์žญ์…˜์˜ readOnly์„ค์ •์— ๋”ฐ๋ผ DataSource๊ฐ€ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์™œ ์ด๋ ‡๊ฒŒ ๋˜๋Š” ๊ฑธ๊นŒ์š”? ์ œ๊ฐ€ ํ•™์Šตํ–ˆ๋˜ ๋‚ด์šฉ์„ ํ† ๋Œ€๋กœ ์„ค๋ช…๋“œ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ํ‹€๋ฆฐ ๋ถ€๋ถ„์ด ์žˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์Šคํ”„๋ง์˜ ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™”

ํ† ๋น„์˜ ์Šคํ”„๋ง 3.1, 361 ํŽ˜์ด์ง€

์Šคํ”„๋ง์€ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™”๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ๋ฐฉ์‹์€ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“  Connection ์˜ค๋ธŒ์ ํŠธ๋ฅผ ํŠน๋ณ„ํ•œ ์ €์žฅ์†Œ์— ๋ณด๊ด€ํ•ด๋‘๊ณ , ์ดํ›„์— ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์„œ๋“œ์—์„œ ์ €์žฅ๋œ Connection์„ ๊ฐ€์ ธ๋‹ค๊ฐ€ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • (1) Userservice Connection์„ ์ƒ์„ฑ
  • (2) ์ƒ์„ฑ๋œ Connection์„ ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ์ €์žฅ์†Œ์— ์ €์žฅ, SetAutoCommit(false)๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘
  • (3) ์ฒซ ๋ฒˆ์งธ update() ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๊ณ  (4) ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ์ €์žฅ์†Œ์— ํ˜„์žฌ ์‹œ์ž‘๋œ ํŠธ๋žœ์žญ์…˜์„ ๊ฐ€์ง„ Connection์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ๋Š”(2)์—์„œ ์ƒ์„ฑํ•œ Connection์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  • (5) Connection์„ ์ด์šฉํ•ด PreparedsStatment์„ ๋งŒ๋“ค์–ด ํ•ด๋‹น SQL์„ ์‹คํ–‰ํ•˜๊ณ  JdbcTemplated๋Š” Connection ๋‹ซ์ง€ ์•Š์€ ์ƒํƒœ๋กœ ์ž‘์—…์„ ๋งˆ์นจ
  • ๋™์ผํ•œ ํ”Œ๋กœ์šฐ๋กœ (6), (7), (8) ์ˆ˜ํ–‰
  • ๋˜ ๋™์ผํ•œ ํ”Œ๋กœ์šฐ๋กœ (9), (10), (11) ์ˆ˜ํ–‰ํ•˜๋ฉฐ ํŠธ๋žœ์žญ์…˜ ๋‚ด์˜ ๋ชจ๋“  ์ž‘์—…์ด ์ •์ƒ์ ์œผ๋กœ ๋๋‚ฌ์œผ๋ฉด (12) Conntion commit์„ ํ˜ธ์ถœํ•ด ํŠธ๋žœ์žญ์…˜์„ ์™„๋ฃŒ
  • (13) ํŠธ๋žœ์žญ์…˜ ์ €์žฅ์†Œ๊ฐ€ ๋” ์ด์ƒ Connection ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š๋„๋ก ์ด๋ฅผ ์ œ๊ฑฐ

์œ„์™€ ๊ฐ™์€ ํ”Œ๋กœ์šฐ๋กœ ํŠธ๋žœ์žญ์…˜์ด ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.

updateSlave() ๋ฉ”์„œ๋“œ๋Š” readOnly = true ์‹œ์ž‘ํ•˜๊ณ , updateTitle() ๋ฉ”์„œ๋“œ์—์„œ readOnly = false๋กœ ์ง„ํ–‰ ๋˜๋Š” ๊ฒฝ์šฐ

์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” updateSlave()์—์„œ Connection(Slave)์„ ์ƒ์„ฑ ํ•˜๊ณ  updateTitle()์—์„œ ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ์ €์žฅ์†Œ์— ์ €์žฅ๋œ ํŠธ๋žœ์žญ์…˜์„ ๊ฐ€์ง„ Connection(Slave) ๊ธฐ๋ฐ˜์œผ๋กœ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๊ธฐ ๋•Œ๋ฌธ์— updateTitle() ๋ฉ”์„œ๋“œ์˜ ํŠธ๋žœ์žญ์…˜ readOnly = false ์„ค์ •์€ ๋™์ž‘ํ•˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ 

53a12ae724b5eb7b28a7ab70bd966a3cd19a1b23