최근 많은 Spring 기반 프로젝트들이 JPA를 사용하면서, @Transactional 어노테이션의 올바른 사용이 더욱 중요해졌습니다.
이 포스팅에서는 @Transactional이 성능에 미치는 영향과 헥사고날 아키텍처 관점에서의 최적화 방안을 살펴보겠습니다.
- Peak Total QPS: 24K
- Select/Commit 쿼리: ~5K
- Update/Insert 쿼리: < 3K
- Set_option 쿼리: ~14K (!!)
특히 주목할 점은 set_option 쿼리가 전체 QPS의 58%를 차지한다는 것입니다.
이는 심각한 성능 저하를 암시하는 지표였습니다.
1. Set_option 쿼리 (14K, 58.3%)
SET autocommit=0/1
SET transaction_isolation='READ-COMMITTED'
SET sql_mode='...'
SET character_set_results=...
발생 원인
2. Select/Commit 쿼리 (5K, 20.8%)
Select 쿼리
Commit 쿼리
3. Update/Insert 쿼리 (3K, 12.5%)
Update 쿼리
Insert 쿼리
4. 기타 쿼리 (2K, 8.4%)
1. 높은 Set_option 비중
2. 리소스 낭비
3. 성능 영향
1. 트랜잭션 범위 최소화
2. 트랜잭션 전파 설정 최적화
@Transactional(propagation = Propagation.SUPPORTS)
3. 배치 처리 도입
헥사고날 아키텍처에서 Repository는 도메인과 인프라스트럭처 계층 사이의 포트 역할을 합니다. @Transactional의 과도한 사용은 이 경계에서 불필요한 오버헤드를 발생시키고 있었습니다.
@RestController
@RequestMapping("/test/read")
class TestController(
private val orderRepository: OrderRepository
) {
@GetMapping("/transaction/{transactionId}")
@Transactional(readOnly = true)
fun transactionTest(@PathVariable transactionId: String): String {
return orderRepository.findByTransactionId(transactionId)?.id?.toString() ?: "nohit"
}
@GetMapping("/{transactionId}")
fun nonTransactionTest(@PathVariable transactionId: String): String {
return orderRepository.findByTransactionId(transactionId)?.id?.toString() ?: "nohit"
}
}
@Service
class OrderService(private val orderRepository: OrderRepository) {
@ReadOnlyTransactional
fun findOrder(id: Long): Order? {
return orderRepository.findByIdWithoutTransaction(id)
}
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
annotation class ReadOnlyTransactional
@EntityGraph(attributePaths = ["items"])
@Query("SELECT o FROM Order o WHERE o.id = :id")
fun findByIdWithItems(@Param("id") id: Long): Order?
@BatchSize(size = 100)
@OneToMany(mappedBy = "order")
val items: List<OrderItem> = mutableListOf()
이번 정리를 통해 트랜잭션을 단순히 데이터 변경을 위한 도구로 사용하는 것이 아니라, DB 부하를 줄이고 성능을 최적화할 수 있는 전략적인 방법이 많다는 것을 다시 한번 확인할 수 있었습니다.
특히 이벤트 기반 트랜잭션 분리를 활용하면 트랜잭션 지속 시간을 최소화하면서 비즈니스 로직의 확장성을 높일 수 있다는 점이 인상적이었습니다.
앞으로 대규모 시스템 설계 시 이런 패턴들을 적극적으로 활용해야겠다고 생각했습니다. 🚀