
Kotlin Coroutines는 비동기 처리를 효율적으로 할 수 있지만, 트랜잭션 처리와 결합될 때는 여러 가지 고려사항이 필요합니다.
이번 글에서는 코루틴의 주요 이점과 함께 Spring 트랜잭션과 코루틴을 효과적으로 사용하는 방법을 포스팅합니다.
스레드 비용 절감: 코루틴은 기존 스레드보다 가볍기 때문에 수천 개의 코루틴을 동시에 실행할 수 있습니다.
이는 시스템 리소스를 효율적으로 사용하게 해줍니다.
메모리 최적화: 스레드 기반 처리가 아닌 코루틴을 사용하여 적은 메모리로 많은 작업을 처리할 수 있습니다.
repeat(1000) {
launch {
// 각 요청을 비동기적으로 처리
processRequest()
}
}
위 코드에서는 1000개의 요청을 코루틴을 통해 동시에 처리하며, 별도의 스레드 생성 없이 가볍게 실행할 수 있습니다.
구조화된 동시성: 부모-자식 관계로 코루틴을 묶어서 쉽게 관리할 수 있습니다. 트랜잭션이 필요할 때도 부모 코루틴 내에서 적절히 관리가 가능합니다.
에러 처리 용이: try-catch를 이용해 코루틴 내에서도 자연스럽게 에러를 처리할 수 있습니다.
coroutineScope {
val deferred1 = async { fetchData1() }
val deferred2 = async { fetchData2() }
// 두 작업의 결과를 동시에 기다림
val result = deferred1.await() + deferred2.await()
}
Non-blocking I/O: I/O 작업이 비동기적으로 처리되기 때문에, 스레드가 차단되지 않고 성능이 향상됩니다.
컨텍스트 스위칭 비용 감소: 스레드 간 전환보다 코루틴 간 전환이 훨씬 빠릅니다.
Kotlin Coroutines와 Spring 트랜잭션 관리를 결합하려면, 코루틴의 비동기 처리 이점과 Spring의 ThreadLocal 기반 트랜잭션 관리의 안정성을 모두 고려해야 합니다.
코루틴 내에서 JPA의 트랜잭션 처리를 안전하게 하려면, 트랜잭션 관련 로직은 Non-suspend 함수로 작성하고, 이를 suspend 함수로 래핑하는 방식이 좋습니다.
// 1. Non-suspend 트랜잭션 함수
@Transactional
fun doTransactionalWork(param: String): Result {
// 트랜잭션 범위 내에서 실행되는 작업
return repository.save(...)
}
// 2. Suspend 래퍼 함수
suspend fun doTransactionalWorkSuspend(param: String): Result {
return withContext(Dispatchers.IO) {
doTransactionalWork(param)
}
}
이 방식은 트랜잭션 안정성을 유지하면서도, 코루틴의 비동기적 특성을 활용할 수 있게 해줍니다.
트랜잭션 로직과 비동기 로직이 분리되어 유지보수성이 높아지며, 테스트 코드 작성도 훨씬 용이해집니다.
이제 실용적인 예시를 살펴보겠습니다.
메시지를 저장하는 과정에서 트랜잭션과 코루틴을 적절히 결합한 예입니다.
@Service
class MessageService {
@Transactional
fun saveMessage(message: Message): Message {
// 트랜잭션이 보장된 동기 처리
return repository.save(message)
}
suspend fun saveMessageAsync(message: Message): Message {
return withContext(Dispatchers.IO) {
// 비동기적으로 트랜잭션 처리를 수행
saveMessage(message)
}
}
suspend fun processBatchMessages(messages: List<Message>) {
coroutineScope {
messages.map { message ->
async {
saveMessageAsync(message)
}
}.awaitAll()
}
}
}
이 구조는 트랜잭션 안정성을 보장하고 대량 처리 시 성능을 향상시키며, 리소스를 효율적으로 사용할 수 있다는 장점이 있습니다. 그러나 몇 가지 주의할 사항도 있습니다.
과도한 코루틴이 커넥션 풀을 고갈시킬 수 있습니다.
코루틴 간에 트랜잭션이 전파되지 않으므로, 트랜잭션이 필요한 작업은 모두 Non-suspend 함수에서 처리해야 합니다.