ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Springboot] web+jpa vs webflux+r2dbc 비교 (1)
    Server 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
    반응형

    댓글

Designed by Tistory.