
WorkManager 재시도를 이해하려면 실패를 한 종류로 보면 안 됩니다. 서버가 잠시 내려간 실패, 네트워크가 없는 실패, 입력 데이터가 잘못된 실패는 다시 실행해야 하는 기준이 다릅니다.
핵심은 재시도할 실패와 즉시 끝내야 할 실패를 Worker 안에서 분리하는 것입니다. 이 글은 WorkManager에서 Result.retry, backoff policy, constraints가 어떻게 함께 작동하는지 설명합니다.

WorkManager 재시도는 실패를 미루는 장치다
WorkManager는 앱이 종료되거나 기기가 재부팅되어도 실행되어야 하는 지연 가능한 작업에 자주 쓰입니다. 예를 들어 로그 업로드, 서버 동기화, 이미지 압축 후 업로드 같은 작업입니다.
하지만 모든 실패를 다시 시도하면 안 됩니다. 네트워크가 잠시 끊긴 실패는 재시도할 수 있지만, 요청 데이터 자체가 잘못된 실패는 반복해도 해결되지 않습니다.
Result.success, failure, retry의 의미
- Result.success: 작업이 끝났고 다음 단계로 넘어가도 되는 상태
- Result.failure: 다시 실행해도 의미 없는 실패 또는 더 이상 진행하면 안 되는 상태
- Result.retry: 지금은 실패했지만 나중에 다시 실행하면 성공할 가능성이 있는 상태
class SyncWorker(
context: Context,
params: WorkerParameters,
private val api: SyncApi
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
api.syncPendingItems()
Result.success()
} catch (e: IOException) {
Result.retry()
} catch (e: InvalidPayloadException) {
Result.failure()
}
}
}이 예시에서 네트워크 계열 실패는 retry 대상입니다. 반대로 payload가 잘못된 경우는 같은 요청을 반복해도 성공할 가능성이 낮으므로 failure로 끝내는 편이 맞습니다.
BackoffPolicy는 재시도 간격을 정한다
Worker가 Result.retry를 반환하면 WorkManager는 즉시 무한 반복하지 않습니다. backoff 정책에 따라 일정 시간 뒤 다시 실행합니다. 이 정책이 없으면 서버 장애나 네트워크 장애 때 앱과 서버 모두에 부담이 커질 수 있습니다.
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
30,
TimeUnit.SECONDS
)
.build()linear backoff는 간격이 일정하게 늘고, exponential backoff는 실패가 반복될수록 간격이 더 크게 늘어납니다. 서버 장애처럼 계속 실패할 가능성이 있는 작업은 exponential 방식이 더 자연스러운 경우가 많습니다.
Constraints는 실패 전에 실행 조건을 제한한다
재시도 정책이 실패 후의 처리라면, constraints는 실행 전 조건입니다. 네트워크가 필요한 작업이라면 네트워크 연결이 있을 때만 실행하도록 제한할 수 있습니다.
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(constraints)
.build()네트워크가 필요한 작업을 네트워크 없이 실행시킨 뒤 실패하고 retry하는 것보다, 애초에 네트워크가 있을 때 실행되도록 제한하는 편이 더 깔끔합니다.
unique work와 중복 실행
동기화 작업은 사용자가 여러 번 버튼을 누르거나 앱이 여러 이벤트를 받으면서 중복 예약될 수 있습니다. 이때 unique work를 쓰면 같은 이름의 작업을 어떻게 처리할지 정할 수 있습니다.
- KEEP: 이미 작업이 있으면 새 요청을 무시한다
- REPLACE: 기존 작업을 취소하고 새 요청으로 바꾼다
- APPEND: 기존 작업 뒤에 새 작업을 이어 붙인다
사용자 프로필 동기화처럼 최신 요청 하나만 의미 있다면 REPLACE가 자연스러울 수 있습니다. 반대로 전송해야 할 이벤트 큐처럼 순서가 중요하면 APPEND를 검토할 수 있습니다.
재시도하면 안 되는 실패
- 사용자 입력 검증 실패
- 권한이 영구적으로 없는 요청
- 서버가 400 계열로 잘못된 요청이라고 응답한 경우
- 파일이 이미 삭제되어 다시 업로드할 수 없는 경우
- 앱 버전과 서버 API 계약이 맞지 않는 경우
이런 실패는 retry를 반복하기보다 로컬 상태를 정리하거나 사용자에게 다시 행동을 요청해야 합니다.
디버깅할 때 보는 값
- WorkInfo 상태가 ENQUEUED, RUNNING, FAILED, SUCCEEDED 중 어디에 있는지 본다
- runAttemptCount가 계속 증가하는지 확인한다
- constraints가 충족되지 않아 실행 대기 중인지 확인한다
- Worker 내부 예외를 네트워크 실패와 영구 실패로 나누어 기록한다
- unique work 정책 때문에 새 작업이 무시되거나 대체되는지 확인한다
정리
WorkManager 재시도는 실패를 무조건 반복하는 기능이 아닙니다. 다시 시도할 가치가 있는 실패를 Result.retry로 표현하고, backoff와 constraints로 실행 부담을 조절하는 구조입니다.
Android 상태와 백그라운드 흐름은 collectAsStateWithLifecycle 사용 기준, Navigation Compose 전환 순서, Offline-first Android 앱 구조와 함께 보면 좋습니다. 외부 기준은 Android Developers – WorkManager basics, Android Developers – WorkManager retry and backoff policy, Android Developers – Work constraints를 확인했습니다.