[Coroutine] 핫 데이터 소스와 콜드 데이터 소스

코틀린 코루틴을 공부하며 정리한 글입니다.
혼자 공부하고 정리한 내용이며, 틀린 부분은 지적해주시면 감사드리겠습니다 😀

핫 데이터 소스

핫 데이터 소스는 원소를 바로 생성(Eager)한다. 일반적으로 우리가 흔히 사용하는 List를 생각하면 편할 것이다. 리스트 원소를 지정한 뒤, 여러 중간 연산(메소드 체이닝)을 걸면, 해당 연산이 바로바로 진행된다.

fun main() {
    val list = listOf(1, 2, 3, 4, 5)
        .onEach { println("onEach $it") }
        .map { it * 10 }
    println("=======")
    println("Call List = $list")
}
onEach 1
onEach 2
onEach 3
onEach 4
onEach 5
=======
Call List = [10, 20, 30, 40, 50]

콜드 데이터 소스

콜드 데이터 소스는 요청이 있을 때만 원소를 생성(Lazy)한다. list와는 다르게, 중간 연산이 바로 진행되지 않고, 해당 변수에 접근을 하여, 종단 연산을 할 때, 결과가 실행되는 것을 알 수 있다.

fun main() {  
    val list = sequenceOf(1, 2, 3, 4, 5)
        .onEach { println("onEach $it") }
        .map { it * 10 }
    println("=======")
    println("Call List = ${list.toList()}")
}
=======
onEach 1
onEach 2
onEach 3
onEach 4
onEach 5
Call List = [10, 20, 30, 40, 50]

여기서 중요한 것은 바로 종단 연산이다. 만약 list.toList()가 아닌 list만 출력하게 된다면, 다음과 같은 결과가 나오게 된다.

=======
Call List = kotlin.sequences.TransformingSequence@f2a0b8e

핫 데이터 소스와 콜드 데이터 소스의 차이

핫 데이터 소스와 콜드 데이터 소스의 가장 큰 차이는 바로 중간 연산의 실행 시점이다.

fun main() {
    val list = listOf(1, 2, 3)
        .onEach { println("onEach $it") }
        .map {
            println("map $it")
            it * 10
        }
    println("=======")
    println("Call List = ${list}") 
}
onEach 1
onEach 2
onEach 3
map 1
map 2
map 3
=======
Call List = [10, 20, 30]

위 결과처럼, 핫 데이터 소스인 컬렉션의 리스트는 지정한 중간 연산마다 모든 원소를 처리하고, 다음 중간 연산을 처리하는 것을 알 수 있다. 이는, onEach의 구현을 보면 알 수 있다.

public inline fun <T, C : Iterable<T>> C.onEach(action: (T) -> Unit): C {  
    return apply { for (element in this) action(element) }  
}

return apply { ... }은 사실상 return this.apply{ ... }와 동일하다. 즉, 현재 컬렉션에 담겨있는 모든 원소를 for문을 통해 처리하고 다시 현재 컬렉션을 반환한다. 그렇다면, 콜드 데이터 소스인 sequence는 어떻게 동작할까?

fun main() {
    val list = sequenceOf(1, 2, 3)
        .onEach { println("onEach $it") }
        .map {
            println("map $it")
            it * 10
        }
    println("=======")
    println("Call List = ${list.toList()}")
}
=======
onEach 1
map 1
onEach 2
map 2
onEach 3
map 3
Call List = [10, 20, 30]

콜드 데이터 소스인 sequence는 각 원소를 꺼내올 때마다, 개발자가 지정한 중간 연산을 하나씩 거치는 것을 알 수 있다. 어떻게 이렇게 동작하는지 살펴보자.

public fun <T> Sequence<T>.onEach(action: (T) -> Unit): Sequence<T> {  
    return map {  
        action(it)  
        it  
    }  
}

컬렉션과 달리, sequence는 원소를 필요할 때마다 하나씩 지연 처리하여 가져오는 방식이다. 각 원소는 호출된 중간 연산(onEach, map)을 순서대로 거치며 최종적으로 연산 결과에 도달한다. 그렇다면 이 지연 처리는 어떻게 작동하는 것일까?

public fun <T> Sequence<T>.toList(): List<T> {  
    val it = iterator()  
    if (!it.hasNext())  
        return emptyList()  
    val element = it.next()  
    if (!it.hasNext())  
        return listOf(element)  
    val dst = ArrayList<T>()  
    dst.add(element)  
    while (it.hasNext()) dst.add(it.next())  
    return dst  
}

바로 toList()라는 종단 연산을 호출할 때 지연 처리가 시작된다. toList() 함수 내부에서는 it.next()를 통해 원소를 하나씩 가져오는데, 이때 각 원소는 sequence에 지정된 중간 연산(onEach, map)을 차례로 통과한다. 이를 통해 시퀀스가 각 원소를 하나씩 지연 평가하며, 최종적으로 리스트 형태로 반환된다.

 

또한, list는 중간 연산을 적용한 값을 계속해서 갖고 있지만, sequence는 종단 연산을 할 때마다, 계속해서 중간 연산을 처리해 값을 만들어낸다.

fun main() {
    val list = listOf(1, 2, 3)
        .onEach { println("onEach $it") }
        .map { it * 10 }
    println("=======")
    println("Call List = ${list}")
    println("Call List = ${list}")
}
onEach 1
onEach 2
onEach 3
=======
Call List = [10, 20, 30]
Call List = [10, 20, 30]
fun main() {
    val list = sequenceOf(1, 2, 3)
        .onEach { println("onEach $it") }
        .map { it * 10 }
    println("=======")
    println("Call List = ${list.toList()}")
    println("Call List = ${list.toList()}")
}
=======
onEach 1
onEach 2
onEach 3
Call List = [10, 20, 30]
onEach 1
onEach 2
onEach 3
Call List = [10, 20, 30]

이처럼 sequence는 공장과 같이 특정한 데이터를 만드는 과정을 정의한 레시피라고 보면 되고, 종단 연산을 실행하면, 그 때마다 데이터를 만들어내게 된다. 이는, 다음 게시물인 flow를 이해하는데, 큰 도움이 될 것이다.

정리

핫 데이터 소스는 모든 원소에 대한 중간 연산을 바로바로 처리해 결과를 내지만, 콜드 데이터는 종단 연산을 호출했을 때, 각 원소마다 개발자가 지정한 중간 연산을 처리하게 된다.

'Book Study > [Kotlin] 코틀린 코루틴' 카테고리의 다른 글

[Coroutine] 플로우  (0) 2024.12.15
[Coroutine] 코루틴 빌더(2)  (0) 2024.08.22
[Coroutine] 코루틴 빌더(1)  (0) 2024.08.14
[Coroutine] delay  (0) 2024.08.08
[Coroutine] 중단 함수  (0) 2024.08.06