Kotlin Academy · 강의

복원력 있는 코루틴 아키텍처 설계

supervisorScope, 재시도 로직 및 처리기를 결합해 운영 환경에 적합한 동시성 코드를 만들어 보세요.

레슨 4/413개 단계

복원력 있는 코루틴 아키텍처 설계은(는) CoddyKit의 무료 Kotlin Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Kotlin Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Kotlin Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

코루틴을 회복력 있게 만드는 요소

회복력 있는 코루틴 아키텍처는 실패를 우아하게 처리합니다. 실패를 격리하고, 일시적인 오류를 재시도하며, 오래된 작업을 취소하고, 리소스를 결정적으로 정리합니다.

import kotlinx.coroutines.*
// Resilience pillars:
// 1. Failure isolation (SupervisorJob)
// 2. Retry with backoff (retry operator / loop)
// 3. Timeout guards (withTimeout)
// 4. Clean teardown (finally + NonCancellable)
// 5. Observability (CoroutineExceptionHandler)
fun main() = runBlocking { println("Design for failure from the start") }

앱 수준 범위 패턴

SupervisorJob + handler가 포함된 단일 앱 수준 CoroutineScope를 만듭니다. 모든 서비스가 동일한 수명 주기를 공유하도록 이 범위를 서비스에 주입합니다.

import kotlinx.coroutines.*
object AppCoroutineScope {
    private val handler = CoroutineExceptionHandler { _, e ->
        println("[AppScope] Uncaught: ${e.message}")
    }
    val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default + handler)
    fun cancel() = scope.cancel()
}

지수 백오프를 사용한 재시도

일시적인 실패를 우아하게 처리하려면 네트워크 호출을 지수 백오프를 적용하는 재시도 반복문으로 감쌉니다.

import kotlinx.coroutines.*
suspend fun <T> retryWithBackoff(
    times: Int = 3,
    initialDelay: Long = 100,
    block: suspend () -> T
): T {
    var delay = initialDelay
    repeat(times - 1) { attempt ->
        try { return block() }
        catch (e: Exception) {
            if (e is CancellationException) throw e
            println("Attempt ${attempt+1} failed, retrying in ${delay}ms")
            delay(delay)
            delay *= 2
        }
    }
    return block()
}
fun main() = runBlocking {
    var n = 0
    val result = retryWithBackoff {
        if (n++ < 2) throw RuntimeException("transient")
        "success"
    }
    println(result)
}

회로 차단기 패턴

회로 차단기는 서비스가 N번 실패하면 해당 서비스 호출을 중단하고, 쿨다운 시간이 지나면 다시 엽니다. 이를 통해 실패의 연쇄를 방지합니다.

import kotlinx.coroutines.*
class CircuitBreaker(val maxFailures: Int, val cooldownMs: Long) {
    private var failures = 0
    private var openUntil = 0L
    suspend fun <T> call(block: suspend () -> T): T {
        if (System.currentTimeMillis() < openUntil) throw RuntimeException("Circuit open")
        return try {
            val result = block()
            failures = 0
            result
        } catch (e: Exception) {
            if (e is CancellationException) throw e
            if (++failures >= maxFailures) openUntil = System.currentTimeMillis() + cooldownMs
            throw e
        }
    }
}

모든 외부 호출에 시간 제한 설정

외부 I/O는 항상 withTimeout 또는 withTimeoutOrNull로 감쌉니다. 멈춘 연결이 코루틴을 무기한 차단하게 두지 않습니다.

import kotlinx.coroutines.*
suspend fun fetchWithTimeout(url: String): String? = withTimeoutOrNull(3000) {
    // ktor: client.get(url).body()
    delay(100) // simulate
    "response from $url"
}
fun main() = runBlocking {
    val result = fetchWithTimeout("https://api.example.com")
    println(result ?: "Timed out")
}

기능별 구조화된 범위

기능이나 화면마다 자체 SupervisorJob이 있는 별도의 CoroutineScope를 만듭니다. 기능이 제거될 때 해당 범위를 취소합니다.

import kotlinx.coroutines.*
class FeatureController {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    fun start() {
        scope.launch { /* load data */ }
        scope.launch { /* subscribe to updates */ }
    }
    fun destroy() = scope.cancel()  // cancels all feature coroutines
}

use()를 사용한 리소스 안전성

AutoCloseable 리소스에는 use { }를 사용합니다. 코루틴 취소와 함께 사용하면 코루틴이 취소될 때도 리소스가 닫힙니다.

import kotlinx.coroutines.*
class Connection : AutoCloseable {
    override fun close() = println("Connection closed")
    suspend fun fetch(): String { delay(100); return "data" }
}
fun main() = runBlocking {
    val job = launch {
        Connection().use { conn ->
            println(conn.fetch())
        } // close() called even on cancellation
    }
    delay(50)
    job.cancelAndJoin()
}

우아한 종료

우아한 종료를 구현합니다. 새 작업 수락을 중지하고, 진행 중인 코루틴이 완료될 때까지 기다린 다음 범위를 취소합니다.

import kotlinx.coroutines.*
class WorkQueue {
    private val scope = CoroutineScope(SupervisorJob())
    private val jobs = mutableListOf<Job>()
    fun submit(block: suspend () -> Unit) {
        jobs += scope.launch { block() }
    }
    suspend fun shutdown() {
        jobs.forEach { it.join() }  // wait for all
        scope.cancel()               // then cancel scope
    }
}
fun main() = runBlocking {
    val q = WorkQueue()
    repeat(3) { i -> q.submit { delay(50); println("Task $i done") } }
    q.shutdown()
    println("Queue shut down cleanly")
}

관찰 가능성: CoroutineName

디버깅을 위해 코루틴에 CoroutineName을 지정합니다. 이름은 스택 추적에 표시되며 예외 처리기에서 구조화된 기록에 사용할 수 있습니다.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { ctx, e ->
        println("[${ctx[CoroutineName]?.name}] Failed: ${e.message}")
    }
    CoroutineScope(SupervisorJob() + handler).apply {
        launch(CoroutineName("UserLoader")) { throw RuntimeException("DB error") }
        launch(CoroutineName("Analytics")) { delay(100); println("Analytics ok") }
        delay(200); cancel()
    }
}

GlobalScope 피하기

프로덕션에서는 GlobalScope를 절대 사용하지 않습니다. 부모도, 수명 주기 관리도, 구조화된 취소도 없는 코루틴을 만들기 때문입니다. 대신 범위가 지정된 대안을 사용합니다.

import kotlinx.coroutines.*
// BAD — GlobalScope leaks coroutines:
// GlobalScope.launch { delay(Long.MAX_VALUE) }

// GOOD — scoped, cancellable:
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
scope.launch { delay(100); println("Scoped") }
fun main() = runBlocking { delay(200); scope.cancel() }

회복력 테스트

실제 지연 없이 단위 테스트에서 실패, 시간 제한, 재시도를 시뮬레이션하려면 runTest와 TestCoroutineScheduler를 사용합니다.

import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
fun testRetry() = runTest {
    var attempts = 0
    val result = retryWithBackoff(3, 100) {
        if (attempts++ < 2) throw RuntimeException("fail")
        "ok"
    }
    println(result) // "ok"
}
// In test, virtual time advances instantly through delays

빠른 확인

오래 유지되는 회복력 있는 코루틴 범위의 기반을 이루는 조합은 무엇입니까?

복습

회복력 있는 아키텍처는 격리를 위한 SupervisorJob, 일시적인 오류를 위한 retryWithBackoff, 멈춘 I/O를 위한 withTimeout, 정리를 위한 finally/NonCancellable, 관찰 가능성을 위한 CoroutineExceptionHandler를 조합합니다.

무료로 시작

AI 튜터와 함께 Kotlin을(를) 배우세요 — 무료

브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.

코스
51
레슨
203

자주 묻는 질문

“복원력 있는 코루틴 아키텍처 설계” 강의는 무료인가요?

네 — “복원력 있는 코루틴 아키텍처 설계” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Kotlin Academy 강의 전체를 잠금 해제할 수 있습니다. Kotlin Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“복원력 있는 코루틴 아키텍처 설계”에서 뭘 배우나요?

supervisorScope, 재시도 로직 및 처리기를 결합해 운영 환경에 적합한 동시성 코드를 만들어 보세요. 브라우저에서 직접 실행하는 실습 코드로 Kotlin Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Kotlin Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Kotlin Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“복원력 있는 코루틴 아키텍처 설계” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Kotlin Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Kotlin Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. SupervisorJob과 Job 비교: 실패 격리
  2. CoroutineExceptionHandler: 전역 미처리 예외 처리기
  3. async/await 예외 전파
  4. 복원력 있는 코루틴 아키텍처 설계
← Kotlin Academy(으)로 돌아가기