사전 백오프
개념

백오프

gabury1고친 사람 github-actions[bot]

백오프는 실패한 일을 다시 하기 전에 일부러 잠깐 기다리는 일입니다. 바로 다시 하면 같은 이유로 또 실패하기 쉽습니다. 기다리는 사이에 상대는 회복할 틈을 얻습니다. 같은 자원을 노리던 여럿은 서로 다른 때로 흩어집니다.

쉽고 빠른 이해

백오프는 다시 시도하기 전에 쉬는 시간을 두는 규칙입니다. 서버가 거절한 요청을 1초 쉬었다가 다시 보내는 것이 제일 단순한 백오프입니다.

곧바로 다시 보내면 서버는 아까와 같은 상태입니다. 같은 이유로 또 거절합니다. 여럿이 한꺼번에 그러면 서버는 회복할 틈이 없습니다.

어떻게 도는가:

  1. 실패하면 다음 시도까지 기다릴 시간을 정합니다
  2. 실패가 거듭되면 그 시간을 늘립니다. 여럿이 같은 순간에 몰리지 않게 무작위 값도 조금 섞습니다
  3. 정해 둔 횟수나 시간을 다 쓰면 포기합니다

대가가 있습니다. 기다리는 만큼 결과가 늦게 나옵니다. 상대가 이미 회복했어도 정한 시간이 지날 때까지 기다립니다.

기다려도 안 풀리는 실패에는 쓰지 않습니다. 권한이 없어 거절된 요청이 그렇습니다.

상세

친구에게 전화를 걸었더니 통화 중입니다. 당장 다시 걸면 또 통화 중일 겁니다. 그래서 몇 분 있다가 다시 겁니다. 그때도 통화 중이면 이번에는 조금 더 오래 기다렸다가 겁니다.

백오프는 이렇게 실패한 뒤 다음 시도까지 일부러 두는 기다림입니다. 영어 back off 는 한 발 물러선다는 뜻입니다. 부딪혔으면 곧바로 다시 밀고 들어가지 말고 물러서라는 이름입니다. 얼마나 기다릴지 정하는 규칙까지 합쳐서 백오프라고 부릅니다.

가장 흔히 만나는 곳은 재시도입니다. 다른 서비스를 부른 요청이 실패하면 1초 쉬었다가 다시 보냅니다. 또 실패하면 2초를 쉬고, 그다음에는 4초를 쉽니다.

백오프가 하는 일은 둘입니다. 하나는 상대에게 회복할 틈을 주는 것입니다. 다른 하나는 같은 자원을 두고 부딪힌 여럿을 서로 다른 때로 흩는 것입니다. 아래 두 소절이 하나씩 봅니다.

상대에게 회복할 틈을 주는 기다림

실패 가운데에는 금방 풀리는 것이 있습니다. 서버가 잠시 바빴거나 연결이 순간 끊긴 경우입니다. 이렇게 시간이 지나면 저절로 풀리는 실패를 일시적 장애라고 합니다. 조금 뒤에 다시 하면 대개 성공합니다.

문제는 그 「조금 뒤」가 없을 때입니다. 실패 직후에 다시 보내면 서버는 아직 같은 상태입니다. 버거워서 거절한 서버라면 두 번째 요청도 같은 이유로 거절합니다.

부르는 쪽이 여럿이면 문제가 커집니다. 각자 쉬지 않고 다시 보내면 서버로 들어오는 양이 줄지 않습니다. 재시도 폭풍은 이렇게 재시도가 부하를 더해 장애가 스스로 풀리지 않는 상태입니다.

flowchart TD
    A["서버가 버겁다"] --> B["요청을 거절한다"]
    B -->|백오프가 없으면| C["부르는 쪽이 곧바로 다시 보낸다"]
    C --> D["들어오는 양이 줄지 않는다"]
    D --> A
    B -->|백오프가 있으면| E["부르는 쪽이 기다린다"]
    E --> F["들어오는 양이 준다"]
    F --> G["서버가 밀린 일을 처리한다"]

백오프가 없는 쪽의 고리는 저절로 끊기지 않습니다. 끊는 것은 부르는 쪽의 기다림입니다. 부르는 쪽이 쉬는 동안 서버는 밀린 일을 처리할 틈을 얻습니다.

부딪힌 여럿을 흩는 무작위 기다림

선 하나를 나눠 쓰는 두 장비가 같은 순간에 신호를 보내 부딪혔다고 해 봅시다. 둘 다 1초를 쉬고 다시 보내면 1초 뒤에 또 부딪힙니다. 규칙이 같으니 몇 번을 되풀이해도 같은 때에 만납니다.

이 상태에서 둘은 멈추지 않고 계속 보냅니다. 그런데 어느 신호도 건너가지 못합니다. 이렇게 계속 움직이면서도 아무도 나아가지 못하는 상태가 라이브락입니다.

풀려면 쉬는 시간이 서로 달라야 합니다. 그래서 기다리는 시간에 무작위 값을 섞습니다. 이렇게 섞는 무작위 값을 지터라고 합니다.

두 장비를 장비 A 와 장비 B 라고 하고 순서대로 보겠습니다. 무작위로 뽑은 결과 A 는 0.3초, B 는 0.8초를 쉽니다.

sequenceDiagram
    participant A as 장비 A
    participant L as 선
    participant B as 장비 B
    A->>L: 신호를 보낸다
    B->>L: 같은 순간에 보낸다
    Note over A,B: 부딪혀 둘 다 물러선다
    A->>L: 0.3초 뒤 다시 보낸다
    Note over A,L: 선이 비어 있어 건너간다
    B->>L: 0.8초 뒤 다시 보낸다
    Note over L,B: A 가 끝난 뒤라 건너간다

먼저 깬 A 가 신호를 다 보냅니다. B 는 그 뒤에 와서 비어 있는 선에 보냅니다. 쉬는 시간이 달라 둘이 다시 부딪히지 않았습니다.

여럿이 한순간에 몰리는 것도 같은 모양입니다. 멈췄던 서버가 돌아오면 기다리던 부르는 쪽이 한꺼번에 덤빕니다. 썬더링 허드는 이렇게 한순간에 몰려드는 현상입니다. 무작위로 흩으면 한순간에 솟던 요청이 여러 순간으로 나뉩니다.

기다리는 시간을 정하는 규칙

얼마나 기다릴지 정하는 규칙은 몇 가지입니다. 넣는 값은 지금까지 실패한 횟수입니다. 나오는 값은 다음 시도까지 기다릴 초입니다.

Python
def fixed(n): return 1         # 1 1 1 1초
def linear(n): return n        # 1 2 3 4초
def expo(n): return 2**(n-1)   # 1 2 4 8초

오른쪽 주석은 첫 번째부터 네 번째 실패까지 나오는 값입니다. 간격이 늘어나는 모양이 규칙마다 다릅니다.

규칙 간격 성질
고정 간격 재시도 늘 같다 셈이 단순하다. 실패가 이어져도 보내는 양이 안 준다
선형 백오프 실패마다 같은 양씩 는다 간격이 천천히 벌어진다
지수 백오프 실패마다 곱절로 는다 몇 번만 실패해도 간격이 크게 벌어진다
무작위 섞기 위 값에 무작위 값을 더하거나 그 안에서 고른다 여럿이 같은 규칙을 써도 깨는 때가 갈린다

표의 마지막 줄은 앞의 셋 가운데 하나에 더해 씁니다. 실무에서 백오프라고 하면 대개 지수 백오프에 무작위 값을 섞은 것을 가리킵니다.

곱절로 늘리면 간격이 금세 커집니다. 위의 expo 로 셈하면 열한 번째 실패 뒤에는 1024초, 17분쯤을 기다립니다. 그래서 기다리는 시간에 상한을 둡니다. 상한에 닿으면 더 늘리지 않고 그 값으로 계속 기다립니다.

재시도 자체를 끝내는 조건도 함께 둡니다. 하나는 시도 횟수의 상한입니다. 다른 하나는 첫 요청부터 잰 전체 시간의 상한입니다. 둘 중 하나에 닿으면 재시도를 맡긴 코드에 마지막 실패를 돌려주고 그만둡니다.

서버가 다시 와도 될 때를 직접 알려 주기도 합니다. HTTP(HyperText Transfer Protocol) 응답의 Retry-After 헤더가 그 값을 싣습니다. 그 값이 오면 계산한 시간 대신 서버가 알려 준 시간을 따릅니다.

백오프가 들어 있는 곳

백오프는 서비스 호출에만 있는 것이 아닙니다. 여럿이 한 자원을 두고 부딪히는 곳이면 같은 생각이 들어가 있습니다. 아래 표는 무엇이 실패하고 백오프가 거기서 무엇을 하는지를 나란히 놓습니다.

쓰이는 곳 무엇이 실패하나 백오프가 하는 일
서비스 호출의 재시도 상대가 바쁘거나 잠깐 멈춰 요청이 실패한다 간격을 늘려 상대에게 회복할 틈을 준다
이더넷 충돌 선 하나를 나눠 쓰는 두 장비가 동시에 보내 신호가 부딪친다 각자 무작위로 기다린다. 부딪힘이 거듭될수록 고르는 범위를 넓힌다
TCP(Transmission Control Protocol) 재전송 보낸 데이터를 잘 받았다는 답이 제때 안 온다 다시 보낼 때마다 기다리는 시간을 늘린다
스핀락 경합 여러 스레드가 같은 잠금을 쥐려고 쉬지 않고 두드린다 못 잡으면 잠깐 쉬었다가 다시 본다. 두드리는 횟수가 줄어 시스템이 덜 붐빈다
끊긴 연결 다시 붙기 살아 있다는 신호를 주기적으로 주고받는 하트비트가 끊겨 서버에 다시 연결해야 한다 시도 사이를 벌려 서버가 되살아날 때 한꺼번에 몰리지 않게 한다
리더 선출 여러 노드가 동시에 리더가 되겠다고 나서 표가 갈린다 노드마다 무작위로 기다려 한 노드가 먼저 나서게 한다

여섯 곳은 부딪히는 대상이 다릅니다. 그래도 백오프가 하는 일은 앞에서 본 둘 가운데 하나입니다.

백오프로 안 풀리는 실패

백오프는 기다리면 풀리는 실패에만 듣습니다. 필수 값이 빠졌거나 권한이 없어 거절된 요청은 얼마를 기다려도 같은 답을 받습니다. 이런 실패에 백오프를 걸면 오류를 늦게 볼 뿐입니다.

이미 무너지는 중인 상대에게도 모자랍니다. 부르는 쪽이 간격을 벌려도 상대가 감당 못 할 양이 계속 오면 회복이 안 됩니다. 이때는 실패가 이어지면 호출을 아예 끊는 서킷 브레이커를 함께 둡니다.

기다림에는 값이 듭니다. 기다리는 만큼 결과가 늦게 나옵니다. 사람이 화면 앞에서 응답을 기다리는 요청이면 이 기다림이 곧 응답 지연입니다.

그래서 쓰는 곳에 따라 상한을 다르게 잡습니다. 사람이 기다리는 요청에는 상한을 짧게 잡아 실패를 일찍 보여 줍니다. 뒤에서 도는 배치 처리에는 상한을 길게 잡아 오래 걸려도 끝내 성공시킵니다.

백오프와 헷갈리는 이웃

백오프와 이름이 비슷하거나 늘 함께 나오는 것들이 있습니다. 가르는 기준은 둘입니다. 무엇과 무엇 사이를 기다리는가, 그리고 그 기다림을 누가 정하는가입니다. 표의 받는 쪽은 앞에서 상대·서버라고 부른 쪽입니다.

이름 무엇을 하나 누가 정하나
백오프 실패한 시도와 다음 시도 사이를 쉰다 부르는 쪽
타임아웃 한 시도 안에서 응답을 얼마나 기다릴지 정한다 부르는 쪽
속도 제한 정해 둔 양을 넘는 요청을 거절한다 받는 쪽
백프레셔 보내는 쪽에 속도를 늦추라고 알린다 받는 쪽

백오프와 타임아웃은 짝으로 돕니다. 타임아웃이 한 시도를 실패로 끊습니다. 백오프는 그 뒤 다음 시도까지 쉽니다. 속도 제한과 백프레셔는 받는 쪽이 먼저 신호를 줍니다. 백오프는 그런 신호가 없어도 실패만 보고 스스로 물러섭니다.

관련 항목

백오프가 기다림을 넣는 재시도 절차

재시도 · 자동 재시도 · 재시도 예산 · 재전송 · 재연결 · 폴링

백오프가 기다리는 시간을 정하는 규칙

지수 백오프 · 선형 백오프 · 고정 간격 재시도 · 적응형 백오프 · 지터 · Retry-After

백오프로 누그러뜨리는 장애

재시도 폭풍 · 썬더링 허드 · 라이브락 · 연쇄 장애 · 캐시 스탬피드 · 혼잡 붕괴

백오프를 품고 도는 프로토콜과 동기화 기법

이더넷 · TCP · 재전송 타임아웃 · 혼잡 제어 · 스핀락 · 리더 선출 · 하트비트

백오프와 함께 부하를 다스리는 장치

서킷 브레이커 · 속도 제한 · 스로틀링 · 백프레셔 · 부하 흘리기 · 벌크헤드

백오프를 쓸지 가르는 실패의 성질

일시적 장애 · 부분 실패 · 멱등성 · 네트워크 분단 · 권한

백오프와 맞세워지는 다른 기다림

타임아웃 · 데드라인 · 무작위 타임아웃 · 선거 시한

다른 이름: backoff · back-off