티스토리 뷰
백오프(Backoff) 전략이란?
백오프(Backoff) 전략은 네트워크 지연, 시스템 과부하, 락 경합 등의 상황에서 요청을 일정 시간 대기 후 재시도하는 기법입니다. 단순히 반복해서 요청하는 것보다, 점진적으로 대기 시간을 증가시켜 시스템 부하를 줄이고 자원의 효율적인 활용을 보장하는 것이 목표입니다.
특히 Redis 기반의 분산락을 사용할 때, 여러 프로세스가 동시에 같은 락을 획득하려고 하면 충돌(경합)이 발생할 수 있습니다. 이때 백오프 전략을 적용하면 락 획득 재시도 간격을 조정하여 효율적으로 락을 관리할 수 있습니다.
백오프 전략이 필요한 이유
- 락 경쟁(Concurrency Issue) 완화
- 여러 프로세스가 동시에 락을 획득하려고 할 때, 무작위로 빠른 재시도를 하면 서버 부하가 증가하고 성능이 저하됩니다.
- 락 획득 실패 시 일정 시간 대기 후 재시도하면 충돌을 줄일 수 있습니다.
- 서버 부하 감소
- 짧은 간격으로 지속적인 요청을 하면 Redis 서버에 높은 부하가 발생할 수 있습니다.
- 백오프 전략을 적용하면 재시도 간격을 늘려 서버의 부하를 완화할 수 있습니다.
- 효율적인 자원 사용
- CPU, 메모리, 네트워크 리소스를 효율적으로 사용하여 불필요한 요청을 줄일 수 있습니다.
백오프 전략의 종류
고정 백오프(Fixed Backoff)
- 락 획득 실패 시 항상 일정한 간격을 두고 재시도하는 방식입니다.
- 예를 들어, 100ms 간격으로 반복적으로 락을 요청하는 방식입니다.
- 단점: 경합이 심한 경우 불필요한 요청이 많아질 수 있습니다.
예제 (Java 코드)
while (true) {
if (acquireLock()) {
break;
}
Thread.sleep(100); // 100ms 후 재시도
}
지수 백오프(Exponential Backoff)
- 락 획득 실패 시 재시도 간격을 지수(2배) 단위로 증가시키는 방식입니다.
- 예를 들어, 100ms → 200ms → 400ms → 800ms 식으로 증가합니다.
- 경합이 높은 환경에서 유용하며, 불필요한 요청을 줄이는 데 효과적입니다.
- 단점: 재시도 간격이 길어지면서 응답 지연이 발생할 수 있습니다.
예제 (Java 코드)
int retryCount = 0;
while (true) {
if (acquireLock()) {
break;
}
long waitTime = (long) Math.pow(2, retryCount) * 100; // 100ms, 200ms, 400ms...
Thread.sleep(waitTime);
retryCount++;
}
제한된 지수 백오프(Bounded Exponential Backoff)
- 지수 백오프 방식의 단점을 보완하여, 최대 대기 시간을 설정하는 방식입니다.
- 예를 들어, 대기 시간이 5000ms(5초)를 초과하지 않도록 제한할 수 있습니다.
- 너무 긴 대기 시간으로 인해 락을 획득하지 못하는 문제를 방지할 수 있습니다.
예제 (Java 코드)
int retryCount = 0;
long maxWait = 5000; // 최대 5초 대기
while (true) {
if (acquireLock()) {
break;
}
long waitTime = Math.min((long) Math.pow(2, retryCount) * 100, maxWait);
Thread.sleep(waitTime);
retryCount++;
}
랜덤 백오프(Randomized Backoff)
- 랜덤한 시간 동안 대기 후 재시도하는 방식입니다.
- 동일한 락을 여러 프로세스가 동시에 요청할 경우, 모두 같은 타이밍에 재시도하면 충돌이 발생할 가능성이 큽니다.
- 이를 방지하기 위해 각 프로세스가 랜덤한 대기 시간을 가지도록 설정하는 방식입니다.
- 락 경합이 심한 환경에서 효율적입니다.
예제 (Java 코드)
Random random = new Random();
while (true) {
if (acquireLock()) {
break;
}
long waitTime = 100 + random.nextInt(400); // 100~500ms 사이 랜덤 대기
Thread.sleep(waitTime);
}
백오프 전략 적용 사례
Redis 분산락에서의 적용
- Redis에서 락을 획득하려는 여러 프로세스가 경쟁하는 경우, 바로 재시도하는 것이 아니라 백오프 전략을 적용하여 서버 부하를 줄일 수 있습니다.
- 락을 획득하지 못한 경우, 백오프 전략을 적용하여 점진적으로 재시도 간격을 늘리는 것이 효과적입니다.
예제 (Redisson 적용 코드)
RLock lock = redissonClient.getLock("myLock");
int retryCount = 0;
while (true) {
if (lock.tryLock()) {
try {
// 중요한 비즈니스 로직 실행
break;
} finally {
lock.unlock();
}
}
long waitTime = (long) Math.pow(2, retryCount) * 100; // 지수 백오프 적용
Thread.sleep(Math.min(waitTime, 5000)); // 최대 대기시간 5초 제한
retryCount++;
}
API 요청에서의 적용
- API 서버가 과부하 상태일 경우, 클라이언트가 즉시 재요청하는 것이 아니라 백오프 전략을 적용하여 일정 시간 후 재시도하도록 구현할 수 있습니다.
- 예를 들어, HTTP 429(Too Many Requests) 오류가 발생하면, 일정 시간 대기 후 다시 요청하도록 설정할 수 있습니다.
예제 (HTTP 요청에 적용한 지수 백오프)
int retryCount = 0;
while (true) {
HttpResponse response = sendHttpRequest();
if (response.getStatusCode() != 429) { // 429(Too Many Requests) 상태가 아닐 경우 정상 처리
break;
}
long waitTime = (long) Math.pow(2, retryCount) * 1000; // 1초, 2초, 4초...
Thread.sleep(Math.min(waitTime, 10000)); // 최대 10초 대기
retryCount++;
}
백오프 전략 선택 기준
전략 특징 장점 단점
고정 백오프 | 일정한 시간 후 재시도 | 구현이 간단 | 경합이 심한 경우 충돌 가능성 높음 |
지수 백오프 | 대기 시간을 지수 단위로 증가 | 재시도 횟수를 줄여 서버 부하 감소 | 응답 시간이 길어질 수 있음 |
제한된 지수 백오프 | 지수 백오프 + 최대 대기 시간 설정 | 응답 시간을 일정 수준에서 제한 가능 | 락 획득 지연 가능성 있음 |
랜덤 백오프 | 랜덤한 시간 후 재시도 | 락 충돌 확률 감소 | 재시도 시간이 예측 불가능 |
결론
백오프 전략은 분산락, API 요청, 데이터베이스 트랜잭션 충돌 해결 등 다양한 상황에서 사용됩니다.
특히 Redis 기반의 분산락 환경에서는 락 경합을 줄이고, 서버 부하를 완화하는 중요한 전략입니다.
시스템의 특성에 맞게 적절한 백오프 전략을 선택하여 적용하는 것이 중요합니다.