kotlin

코루틴을 사용하면 스프링에서 비동기를 편하게 쓸 수 있을까?

DEV_GOLF 2023. 3. 13. 01:53
반응형

개인 프로젝트 도중 사용하게 되서 써본 글입니다. 앞으로 계속 공부를 이어나갈 예정입니다. 부족하지만 잘 부탁드립니다!

 

코루틴 소개

코루틴은 흔히 백엔드 개발자 사이에서 코틀린에서 제공하는 기능으로 착각하고 있는 분들이 많습니다. 하지만 Javascript, C#, Go 등 이미 여러 언어에서 지원하고 있는 개념이며 새로운 개념, 기술이 아닙니다.

코루틴 소개 전 CallBack 지옥에 대해서 알아보자

콜백 지옥은 함수형 패러다임을 적용한 언어에서 등장하는 형태입니다. 함수에서 최종적인 결과를 도출하기 위해 콜백 함수를 과도하게 호출하면 콜백 지옥이 생기게 되고 이러한 코드는 가독성을 떨어트리고 디버깅을 어렵게 만듭니다.

예를 들어 단순 주문을 하는 로직을 구현한다고 가정해 봅시다.

memberRepository.findByIdOrNull(memberId)
    .subscribe { buyer ->
        productRepository.findByIdIn(memberIds)
            .subscriber {prds ->
                orderItemService.save(Orderitems.create(prds))
                    .subscribe { items ->
                        orderRepository.createOrder(Order.create(buyer, items))
                    }
                    .whenComplete { order, _ -> emitter.success(order)
            }
    }

동기로 짰을 때 보다 보기도 좋고 한 함수에서 역할이 전부 몰려 있다 보니 유지보수 하기도 어려워집니다.

코루틴?

코루틴은 이런 콜백 지옥을 탈출하는 탈출구가 됩니다. 말고도 코루틴은 3가지 키워드로 접근해볼 수 있습니다.

  1. 협력형 멀티 태스킹
  2. 동시성 프로그래밍 지원
  3. 비동기 처리를 쉽게 도와줌

여기서 가장 눈여겨볼 개념은 협력형 멀티 태스킹입니다.

협력형 멀티태스킹

Cooperation에 CO 태스킹을 뜻하는 Routine을 합쳐 협력하는 함수 태스킹을 뜻합니다. 이 코루틴을 알기위에선 먼저 main routine과 sub routine을 알아봐야하는데

fun main(String[] args) {    // main routine
    // .. doSomething
    val a = 10
    val b - 10

    val result = plus(a, b)
}

fun plus(a: Int, b: Int) = a + b   // sub routine

위 코드를 보면 main 함수가 main routine이고, 여기서 호출 되는 plus 메소드가 sub routine이 됩니다. 흐름은 흔히 예상하는대로 입니다

sub routine은 우선 진입과 빠져나오는 지점이 명확합니다. 맨 처음 진입 부터 return문이나 닫는 괄호를 만나면 routine을 빠져나옵니다. 그리고 진입 시 쓰레드는 Block system call 일 경우 다른 작업을 하지 못합니다.

그렇다면 코루틴은 어떨까요?

코루틴은 진입점이 여러개이면서 함수를 빠져나갈 수 있는 탈출점도 여러가지를 갖고 있습니다. 즉, 코루틴은 자유롭게 함수를 드나든다라는 표현이 어울립니다.

코드로 보면 다음과 같습니다.

suspend fun createMember(member: Member): MemberResponseDto {

    checkEmailDuplication(member.email)
    checkNicknameDuplication(member.nickname)

    val savedMember = memberRepository.save(member)

    return MemberResponseDto(member)
}

suspend fun checkEmailDuplication(email: String) {
   // .. doSomething
}

suspend fun checkNicknameDuplication(nickname: String) {
   // .. doSomething
}

// ... implemented something

이 코드는 코루틴으로 동작하는 코드입니다. 그렇기 때문에 언데는 코드가 나가고 들어올 수 있습니다. 순서를 따라가 보면 다음과 같습니다.

 

1. thread가 createMember 메소드를 호출하면 코루틴이 만들어져 함수를 실행합니다.

 

2. thread가 checkEmailDuplication 함수 부분에서 더 이상 아래 코드를 실행하지 않고 잠시 createMember 메소드를 탈출합니다.

 

3. thread가 코루틴을 탈출했더라도 우리가 짜놓은 다른 코드를 실행할 수 있습니다. 그리고 checkEmailDuplication 함수는 계속 실행 중입니다. 이 함수는 개발자의 선택에 따라 동시성 프로그래밍으로 작동하거나 다른 thread에서 동작하고 있을 수도 있습니다.

 

4. 그러다가 checkEmailDuplication 메소드가 종료되면 다시 thread가 돌아와서 멈추어놓았던 아래 코드들을 실행하게 됩니다.

동시성 프로그래밍

함수를 중간에 빠져나왔다가, 다른 함수에 진입하고, 다시 원점으로 돌아와 멈추었던 부분부터 다시 시작하는 이 특성을 이용하여 동시성 프로그래밍을 할 수 있습니다.

 

예를 들자면, 각각 회원 가입 요청과 주문 생성 요청이 들어왔다고 가정해보면 회원 가입을 하는 도중에 잠시 탈출하여 주문 생성에 필요한 작업들을 수행하다가 회원 가입 프로세스로 돌아와서 작업하는 방식을 마치 동시에 하는것 처럼 매우 빠르게 작업한다면 이는 동시성 프로그램과 똑같은 동작입니다.

 

그러면 성능은 어떠할까요?

 

코루틴은 일반 thread를 이용한 동시성 프로그래밍과 차원이 다른 효율성을 보여줍니다. 하나의 CPU 에서 위 예시를 실행시킨다고 하면 두 개의 thread에서 스위칭을 하며 작업을 할 것이고 이러한 switching은 너무 많은 비용이 들어갑니다.

(Thread 에는 Register와 Heap, static, code, stack 정보가 저장되어 있고 이를 저장한 후에 다른 Thread의 Context를 불러와야하기 때문에 이 과정에서 비용이 발생합니다.)  

 

그렇지만 코루틴은 비동기로 contextswitching 없이 동작이 가능하기 때문에 훨씬 더 나은 성능을 보여줍니다. 

간결한 비동기 처리

마지막으로 코루틴의 꽃인 비동기 처리 시 얼마나 코드가 간결해지는지 보겠습니다.

suspend fun createOrder(memberId: Long, productIds: List<Long>) {
	
    try {
    	val buyer = memberRepository.findByIdOrNull(memberId)
        val products = productRepository.findByIdIn(productIds)
    
        val orderItems = orderItemRepository.saveAll(OrderItems.create(products))
    
        orderRepository.save(Order.create(buyer, orderItems))
    } catch (e: Exception) {
    	log.error("error") // 예시일 뿐 실제로 이렇게 처리하는 사람은 없길 바란다...
    }
}

맨 처음 callback 으로 지저분해진 코드가 너무 간결하게 변했습니다. 물론 더 자세히 살펴보면 repository에서의 코드도 변한게 있겠지만 크게 차이는 안납니다. 

 

이게 가능한 이유는 각각의 함수를 호출할 때 마다 잠시 createOrder 메서드를 탈출했다가 작업이 끝나면 다시 돌아와서 작업을 할 수 있기 때문입니다. 비동기는 유지한 채로 가독성을 높였다고 볼 수 있습니다.

주의 사항

이 글을 읽고 코루틴을 무조건 써야한다. 성능이 좋다에 대한 접근을 지양하길 바랍니다. 이 글은 비동기로 힘들어 하시는 분들을 위한 글이며 비동기를 안쓰는 데에도 불구하고 억지로 쓸 필요는 없습니다. 이 점 꼭 참고하시기 바랍니다. 

 

또한 CS의 기본기인 비동기 context switching 병렬성과 동시성에 대해서는 기본적으로 숙지하고 보길 바랍니다.

 

https://www.youtube.com/watch?v=eJF60hcz3EU&t=169s 

이건 당근 마켓 밋업에 올라온 태우님의 코루틴 소개 영상인데 꼭 보길 바랍니다... 내용 굿

Ref.

https://wooooooak.github.io/kotlin/2019/08/25/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0/

 

코틀린 코루틴(coroutine) 개념 익히기 · 쾌락코딩

코틀린 코루틴(coroutine) 개념 익히기 25 Aug 2019 | coroutine study 앞서 코루틴을 이해하기 위한 두 번의 발악이 있었지만, 이번에는 더 원론적인 코루틴에 대해서 알아보려 한다. 코루틴의 개념이 정확

wooooooak.github.io

https://kotlinlang.org/docs/coroutines-and-channels.html#blocking-requests

 

Coroutines and channels − tutorial | Kotlin

 

kotlinlang.org