-
[Springboot] web+jpa vs webflux+r2dbc 비교 (1)Server 2022. 12. 4. 12:47728x90반응형
기존의 개발 방식으로 많이 사용하는 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반응형'Server' 카테고리의 다른 글
API Gateway의 역할과 장단점을 알아보자 (0) 2025.04.18 모놀리식 아키텍처 vs 마이크로 서비스 비교 (0) 2025.04.18 웹서버? WAS 서버? 에 대해 알아보자 (1) 2025.04.18 🔗 MSA 환경에서의 통신 방식 비교 (HTTP API vs gRPC vs 메시지 큐) (1) 2025.04.15 [Springboot] web+jpa vs webflux+r2dbc 비교 (2) (0) 2022.12.07