
대부분의 개발자들은 insert, update, delete 쿼리가 발생하는 메서드에 습관적으로
@Transactional을 적용한다.
@Transactional 사용 패턴@Transactional
fun append(data: String) {
abcProcessor.append(data + "1")
}
이런 단순한 경우에는 문제가 없다. 하지만 외부 서비스 호출이 포함된 경우에는 상황이 달라진다.
@Transactional
fun append(data: String) {
smsSender.send(~~)
abcProcessor.append(data + "1")
slackNotifier.notify(~~)
}
이 코드의 문제점:
외부 SMS 서비스에 장애가 발생하여 30초 타임아웃이 발생한다고 가정하자:
fun append(data: String) {
smsSender.send(~~)
abcProcessor.append(data + "1")// 이 메서드 내부에서만 @Transactional 적용
slackNotifier.notify(~~)
}
Implementation Layer의 구현부에서만 트랜잭션을 적용한다:
class AbcProcessorImpl {
@Transactional
fun append(data: String) {
// 실제 데이터베이스 작업만 트랜잭션 내에서 수행
}
}
여러 프로세서의 작업이 하나의 트랜잭션으로 묶여야 하는 경우:
fun append(data: String) {
smsSender.send(~~)
abcProcessor.append(data + "1")// @Transactional 적용됨
bcaProcessor.appendNoTran(data + "2")// @Transactional 적용됨
slackNotifier.notify(~~)
}
이 두 프로세서의 작업이 반드시 같은 트랜잭션에서 실행되어야 한다면, Implementation Layer에서 하나의 구현체로 통합한다
class CombinedProcessorImpl {
@Transactional
fun appendBoth(data: String) {
// abcProcessor와 bcaProcessor의 로직을 하나의 트랜잭션에서 실행
// 결합도가 높다는 것을 인정하고 명시적으로 처리
}
}
SMS 발송 (30초) + DB 작업 (0.1초) + Slack 알림 (5초) = 35.1초 트랜잭션 유지
SMS 발송 (30초, 트랜잭션 없음) + DB 작업 (0.1초, 트랜잭션) + Slack 알림 (5초, 트랜잭션 없음)
실제 트랜잭션 시간: 0.1초
트랜잭션 유지 시간이 350배 단축되어 시스템 전체의 처리량이 대폭 향상된다.
@Transactional 어노테이션은 강력한 도구지만 신중하게 사용해야 한다. 무분별한 적용은 시스템 성능을 크게 저하시킬 수 있다.
실제 데이터베이스 작업이 필요한 최소한의 범위에만 적용하고, 외부 서비스 호출과는 분리하여 설계하는 것이 중요하다. 복합 트랜잭션이 필요한 경우에는 결합도 증가를 감수하더라도 명시적으로 처리하는 것이 바람직하다.