반응형
Notice
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
Tags
- 빅데이터
- 데이터베이스
- 웹개발
- 자료구조
- 클라우드컴퓨팅
- 딥러닝
- 컴퓨터공학
- Yes
- 보안
- 프로그래밍
- 소프트웨어공학
- 인공지능
- 데이터과학
- 데이터분석
- 네트워크보안
- 디자인패턴
- springboot
- 데이터구조
- 컴퓨터과학
- I'm Sorry
- 사이버보안
- 자바스크립트
- 소프트웨어
- 프로그래밍언어
- 파이썬
- 머신러닝
- 버전관리
- 컴퓨터비전
- 네트워크
- 알고리즘
Archives
- Today
- Total
스택큐힙리스트
Kotlin 코루틴에서 트랜잭션 전파 이슈 잡기 본문
반응형
1. 코루틴과 트랜잭션이 충돌하는 이유
- Spring @Transactional ↔ ThreadLocal
JPA 트랜잭션은 현재 스레드에 커넥션·영속성 컨텍스트를 바인딩합니다. 하지만 suspend 함수는 언제든 다른 스레드로 재개(resume)될 수 있어 컨텍스트가 사라지면서 TransactionRequiredException 등이 터집니다. - 전파 옵션 무력화
REQUIRES_NEW처럼 새 트랜잭션을 강제해도, 같은 빈 내부·동일 코루틴 컨텍스트라면 프록시가 건너뛰어 한 트랜잭션으로 묶이는 사례가 잦습니다. - 동시성 확산
async {} · withContext(Dispatchers.Default) 등으로 트랜잭션을 복수 스레드에 퍼뜨리면 데드락·커넥션 고갈로 직행합니다.
2. Spring Boot 3.x 이후 지원 현황
- Suspend 함수에 @Transactional — 프록시가 인터셉트하지만 스레드 이동 시 안전을 보장하지 않습니다.
- 공식 권장: 블로킹 JDBC ⇨ 비동기화하려면 코루틴용 Reactive Stack(R2DBC) 또는 TransactionTemplate·TransactionalOperator를 사용한 프로그래밍 방식
3. 깨지는 패턴 Top 3 & 대응책
- withContext로 I/O 스레드 분리
- 해결: 트랜잭션 경계를 벗어나기 전까지 같은 Dispatcher 유지, 꼭 분리해야 한다면 TransactionTemplate로 감싸기.
- REQUIRES_NEW가 안 먹는 경우
- 해결: 새 트랜잭션이 필요한 메서드를 다른 빈으로 분리해 프록시를 강제로 타게 한다.
- 트랜잭션 안에서 async 병렬 호출
- 해결: 미리 데이터를 분할해 배치 API를 설계하거나, 테이블 Lock/Index를 재설계해 데드락을 예방.
4. 실전 코드 패턴 2가지
패턴 A – 트랜잭션을 non-suspend로 고정
@Service
class MessageService(
private val repo: MessageRepository
) {
@Transactional // 스레드 이동 없음
fun markReadTx(id: Long, userId: Long): Long {
repo.markMessageAsRead(id, userId)
return repo.countUnreadMessagesByUserId(userId)
}
suspend fun readMessage(id: Long, userId: Long): Long =
withContext(Dispatchers.IO) { markReadTx(id, userId) } // 코루틴에서 호출
}
- 장점: 전파(REQUIRED·REQUIRES_NEW) 규칙이 그대로 동작.
- 단점: I/O 스레드를 블로킹하므로 대량 호출엔 부적합. Velog
패턴 B – R2DBC + TransactionalOperator
@Service
class PayService(
private val operator: TransactionalOperator,
private val repo: PayRepository
) {
suspend fun pay(order: Order) = operator.executeAndAwait {
repo.save(order)
repo.logEvent(order.id)
}
}
- 완전 논블로킹; 트랜잭션 컨텍스트가 코루틴 Context로 전파돼 스레드 이동에도 안전.
5. 체크리스트
- 트랜잭션이 필요한 suspend 함수 내부에서 withContext/async 남발 금지
- 새 전파 옵션이 필요하면 별도 빈으로 분리해 프록시 타기
- JDBC + Coroutine으로 병렬 처리 시 커넥션 수와 데드락을 반드시 부하 테스트
- 고성능이 필수면 R2DBC Stack + TransactionalOperator로 전환 고려
- 코루틴·가상 스레드·블로킹 JDBC 조합의 장단점을 프로젝트 규모에 맞춰 선택
반응형
'개발' 카테고리의 다른 글
Spring Security Filter Chain 깊게 파보기 — 인증 vs 인가 (0) | 2025.07.19 |
---|---|
자바 스프링 개발 시작하기 - 12일차 로그인·회원가입·토큰 재발급 플로우 (1) | 2025.07.19 |
트랜잭션 전파·격리 수준 완전 정복 (0) | 2025.07.18 |
자바 스프링 개발 시작하기 - 11일차 Spring Data JPA로 데이터 접근 레이어 설계 (0) | 2025.07.18 |
Facade vs Adapter: 언제 퍼사드, 언제 어댑터? (0) | 2025.07.18 |
Comments