Server

[Springboot] web+jpa vs webflux+r2dbc 비교 (1)

hoonylab 2022. 12. 4. 12:47
728x90
반응형

기존의 개발 방식으로 많이 사용하는 starter-web + starter-data-jpa 라이브러리를 사용하여 servlet stack 방식을 많이 사용하고 있을 것이다.

servlet stack

최근 webflux + r2dbc 라이브러리를 이용하여 비동기(async) & 논블로킹(non-blocking)으로 구현하는 reactive stack 알게 되어 구현해보고 비교해보려 한다.

reactive stack

 

각각의 stack방식의 코드 작성은 어떻게 할까?

필자는 mariadb와 연결해서 테스트를 위해 각각 blocking 방식 non-blocking 방식을 지원하는 db connection 라이브러리도 포함해서 진행한다.

 

servlet-stack 코드

기본적인 controller, service, repository, entity를 구성

controller

import com.minikode.web_demo.jpa.entity.BoardEntity
import com.minikode.web_demo.service.BoardService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/bk")
class BkController(
    private val boardService: BoardService,
) {
   
    @PostMapping("/")
    fun save(): ResponseEntity<BoardEntity> {
        return ResponseEntity.ok(boardService.saveBoard())
    }

    @GetMapping("/")
    fun find(): ResponseEntity<MutableList<BoardEntity>> {
        return ResponseEntity.ok(boardService.findBoards())
    }
}

service

import com.minikode.web_demo.jpa.entity.BoardEntity
import com.minikode.web_demo.jpa.repository.BoardRepository
import org.springframework.stereotype.Service

@Service
class BoardService(
    private val boardRepository: BoardRepository,
) {

    fun findBoards(): MutableList<BoardEntity> {
        return boardRepository.findAll()
    }

    fun saveBoard(): BoardEntity {
        val boardEntity = BoardEntity(
            title = "블로킹 제목1",
            description = "블로킹 설명11"
        )
        return boardRepository.save(boardEntity)
    }

}

repository

import com.minikode.web_demo.jpa.entity.BoardEntity
import org.springframework.data.jpa.repository.JpaRepository

interface BoardRepository : JpaRepository<BoardEntity, Long> {
}

Entity

import javax.persistence.*

@Entity
@Table(name = "board", catalog = "minikode", indexes = [])
class BoardEntity(
    title: String,
    description: String,
) {
    constructor() : this(title = "", description = "description") {
        this.title = title
        this.description = description
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var boardId: Long? = null
        private set

    @Column(name = "title", nullable = false)
    var title = title
        private set

    @Column(name = "description", nullable = true)
    var description = description
        private set


}

 

reactive-stack 코드

reactive-stack 에는 functional 방식과 controller 방식 두 가지가 있는데 여기서는 controller 방식을 사용했고, 기본적인 controller, service, repository, entity를 구성

contoller

import com.minikode.webflux_demo.r2dbc.entity.BoardEntity
import com.minikode.webflux_demo.service.BoardService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.util.*

@RestController
@RequestMapping("/rx")
class RxController(
    private val boardService: BoardService,
) {

    @PostMapping("/")
    fun save(): ResponseEntity<Mono<BoardEntity>> {
        return ResponseEntity.ok(boardService.saveBoard())
    }

    @GetMapping("/")
    fun find(): ResponseEntity<Flux<BoardEntity>> {
        return ResponseEntity.ok(boardService.findBoards())
    }
}

 

Service

import com.minikode.webflux_demo.r2dbc.entity.BoardEntity
import com.minikode.webflux_demo.r2dbc.repository.BoardRepository
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

@Service
class BoardService(
    private val boardRepository: BoardRepository,
) {

    fun saveBoard(): Mono<BoardEntity> {
        val boardEntity = BoardEntity(
            title = "논블로킹 제목1",
            description = "논블로킹 설명11"
        )
        return boardRepository.save(boardEntity)
    }

    fun findBoards(): Flux<BoardEntity> {
        return boardRepository.findAll()
    }
}

Repository

import com.minikode.webflux_demo.r2dbc.entity.BoardEntity
import org.springframework.data.r2dbc.repository.R2dbcRepository

interface BoardRepository : R2dbcRepository<BoardEntity, Long> {
}

Entity

import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.Column
import org.springframework.data.relational.core.mapping.Table
import java.util.*

@Table(
    name = "board",
    schema = "minikode"
)
class BoardEntity(
    title: String,
    description: String,
) {

    @Id
    @Column("board_id")
    var boardId: Long? = null
        private set

    @Column("title")
    var title = title
        private set

    @Column("description")
    var description = description
        private set


}

 

위 코드로 각각 servlet-stack, reative-stack 방식의 코드를 작성했고, jMeter를 이용해서 성능 비교를 해보았다.

각 controller의 save(), find()를 각각 동시 500명이 10번 반복하도록 설정했다.

-> save() 1000번 실행 -> find() 1000번 실행

 

결과는?

servlet-stack

Active Threads Over Time
Response Times Over Time
Transactions per Second
총 save 카운트

 

 

 

reactive-stack

Active Threads Over Time
Response Times Over Time
Transactions per Second
총 save 카운트

위처럼 결과가 나왔다.

servlet stack 은 총 29초 걸렸고, reactive stack 은 4분 35초가 걸렸다.

 

따라서 servlet 방식이 압도적으로 빠르게 처리했다.

 

테스트 전에 생각했던 거와 차이가 많이 나는 결과가 나왔다. 학습하면서 webflux를 이용해 netty 서버를 사용하면 많은 요청에 대해 이전 blocking 방식보다 빠르고 효율적으로 처리될 거라고 생각했지만 결과는 반대로 나와버렸다.

 

혹시 reactive 코드 중 하나라도 블로킹이 있으면 이런결과가 나올거 같은데, R2dbcRepository 가 문제인가? 싶지만 일단 이번 테스트 여기까지 해보고 다른 문제가 있지는 않은지 고민해봐야겠다.

 

혹시라도 코드중 문제가 있진 않았는지 좀 더 생각해보고, 문제가 있다면 다시 테스트해봐야겠다.

728x90
반응형