사전 부분 배포
문제

부분 배포

gabury1고친 사람 github-actions[bot]

부분 배포는 새 버전을 여러 서버에 내보내다가 일부에만 깔린 채 멈춘 상태입니다. 한 서비스가 새 버전과 옛 버전으로 섞여 돕니다. 일부러 조금씩 나눠 내보내는 방식을 이 말로 부를 때도 있습니다. 이 편은 의도하지 않게 멈춘 쪽을 다룹니다.

쉽고 빠른 이해

서버 열 대에 새 버전을 두 대씩 차례로 올리던 중이었습니다. 넷째 차례에서 문제가 생겨 배포가 멈춥니다. 여섯 대는 새 버전입니다. 두 대는 새 버전을 올리다 멈춰 요청에서 빠져 있습니다. 나머지 두 대는 옛 버전입니다.

이게 곤란한 까닭은 두 버전이 같은 요청을 나눠 받기 때문입니다. 같은 사용자가 새로고침할 때마다 다른 버전을 만납니다. 새 버전이 저장한 데이터를 옛 버전이 못 읽는 일도 생깁니다.

어떻게 터지나:

  1. 서버를 몇 대씩 차례로 새 버전으로 바꿉니다
  2. 중간에 설치 실패나 접속 끊김으로 차례가 멈춥니다
  3. 끝까지 밀거나 되돌리는 사람도 장치도 없어 섞인 채로 남습니다

치르는 값은 이렇습니다. 버그가 어떤 때는 나고 어떤 때는 안 나서 원인을 찾기 어렵습니다. 사람들은 배포가 됐다고 믿거나 안 됐다고 믿습니다. 서버의 실제 상태는 둘 다 아닙니다.

상세

식당 테이블 열 개의 메뉴판을 새 가격표로 바꾸던 직원이 여섯 개째에서 불려 갑니다. 손님은 어느 테이블에 앉느냐에 따라 다른 가격을 봅니다. 주방은 어느 가격으로 계산할지 모릅니다.

배포는 새 버전의 코드나 설정을 운영 서버에 올려 실제 요청을 받게 하는 일입니다. 서버가 한 대면 배포는 된 것과 안 된 것 둘뿐입니다. 서버가 여러 대면 셋째 상태가 생깁니다. 일부만 바뀐 상태입니다. 이것을 부분 배포라고 부릅니다.

부분 배포는 누가 고른 결과가 아닙니다. 배포가 끝나기 전에 무언가가 끊기면 저절로 남습니다.

서버를 몇 대씩 나눠 바꾸는 이유

서버 여러 대가 한 서비스를 나눠 맡을 때 앞에는 로드 밸런서를 둡니다. 로드 밸런서는 들어온 요청을 뒤의 서버들에 골고루 나눠 주는 장비나 프로그램입니다. 서버 한 대가 빠져도 나머지가 요청을 받으므로 서비스는 계속 돕니다.

서버를 전부 한꺼번에 내리고 새 버전을 올리면 그동안 서비스가 멈춥니다. 그래서 흔히 몇 대씩 차례로 바꿉니다. 이 방식을 롤링 배포라고 부릅니다.

차례마다 서버를 로드 밸런서에 다시 넣기 전에 헬스 체크를 거칩니다. 헬스 체크는 서버가 요청을 받을 수 있는 상태인지 짧은 요청을 보내 확인하는 일입니다. 헬스 체크는 새 버전이 망가진 서버를 로드 밸런서에 다시 넣지 않으려고 둡니다.

한 차례는 이렇게 돕니다. 바꿀 서버를 로드 밸런서에서 뺍니다. 새 버전을 올립니다. 헬스 체크를 통과하면 로드 밸런서에 다시 넣습니다.

flowchart TD
    A["서버 두 대를 로드 밸런서에서 뺀다"] --> B["새 버전을 올린다"]
    B --> C{"헬스 체크를 통과하나"}
    C -->|통과| D["로드 밸런서에 다시 넣는다"]
    D --> E{"남은 서버가 있나"}
    E -->|있다| A
    E -->|없다| F["모든 서버가 새 버전"]
    C -->|실패| G["배포가 멈춘다 · 부분 배포"]

롤링 배포에는 두 버전이 함께 도는 시간이 원래 있습니다. 차례가 끝까지 가면 모든 서버가 한 버전으로 모입니다. 그러면 섞인 시간도 끝납니다. 부분 배포는 그 섞인 시간이 끝나지 않고 굳은 것입니다.

배포가 중간에 멈추는 길

차례가 끊기는 원인은 여럿입니다. 무엇이 끊기든 이미 바뀐 서버는 바뀐 채로 남습니다.

원인 무슨 일이 남나
새 버전이 헬스 체크를 통과 못 함 배포 도구가 다음 차례로 넘어가지 않는다
배포 도구나 그 도구가 도는 머신이 죽음 다음 차례를 진행할 주체가 사라진다
네트워크가 끊겨 일부 서버에 닿지 못함 닿은 서버만 바뀐다
일부 서버에서 디스크가 차거나 권한이 없어 설치 실패 그 서버만 옛 버전으로 남는다
사람이 이상을 보고 배포를 멈춤 되돌리지 않으면 멈춘 상태 그대로 남는다
서버마다 손으로 명령을 돌리다 몇 대를 빠뜨림 빠진 서버만 옛 버전으로 남는다

위 표의 마지막 두 줄은 사람이 손을 댄 경우입니다. 나머지 넷은 사람이 아무것도 안 해도 생깁니다.

멈춘 뒤 서버들의 상태

서버 열 대를 두 대씩 바꾸다가 넷째 차례, 곧 7~8번이 헬스 체크를 통과 못 했다고 해 봅시다. 앞 흐름도의 실패 갈래입니다. 서버들은 셋으로 갈립니다.

서버 도는 버전 요청을 받나
1~6번 새 버전 받는다
7~8번 새 버전을 올리다 멈춤 로드 밸런서에서 빠진 채라 안 받는다
9~10번 옛 버전 받는다

로드 밸런서는 이제 여덟 대에 요청을 나눕니다. 그중 여섯 대는 새 버전이고 두 대는 옛 버전입니다. 요청 넷 가운데 하나꼴로 옛 버전이 답합니다.

빠진 두 대 몫만큼 처리할 수 있는 양도 줄었습니다. 평소 열 대로 버티던 요청을 여덟 대가 받습니다.

두 버전이 섞여 돌 때 깨지는 것

첫째는 답이 요청마다 갈리는 것입니다. 같은 사용자가 새로고침할 때마다 새 화면과 옛 화면을 오갑니다. 버그 신고가 들어와도 개발자 쪽에서 다시 해 보면 안 납니다. 그 요청이 어느 서버에 닿았는지가 결과를 가르기 때문입니다.

둘째는 두 버전이 같은 데이터를 나눠 쓰는 것입니다. 서버들은 데이터베이스나 큐를 함께 씁니다. 새 버전이 데이터에 새 칸을 더해 쓰면, 옛 버전이 그 데이터를 읽게 됩니다.

sequenceDiagram
    participant 새 as 새 버전 서버
    participant 저장소
    participant 옛 as 옛 버전 서버
    새->>저장소: 배송 방식 칸을 더한 주문을 쓴다
    옛->>저장소: 주문을 읽는다
    저장소-->>옛: 모르는 칸이 든 주문
    Note over 옛: 모르는 칸을 버린다
    옛->>저장소: 주문을 고쳐 다시 쓴다
    Note over 저장소: 배송 방식 칸이 사라졌다

옛 버전이 모르는 칸을 만났을 때 무슨 일이 나는지는 그 코드가 데이터를 어떻게 읽도록 짜였느냐에 달렸습니다. 오류를 내고 멈추기도 합니다. 조용히 칸을 버리고 다시 쓰면 새 버전이 적은 값이 사라집니다.

방향이 반대인 경우도 있습니다. 새 버전이 옛 형식의 데이터를 못 읽으면 새 버전 쪽에서 오류가 납니다.

셋째는 사람이 믿는 것과 서버의 실제 상태가 갈리는 것입니다. 배포 기록에는 성공이나 실패 하나만 남는 일이 많습니다. 다음 배포는 이 섞인 상태를 모른 채 그 위에 얹힙니다.

적어 둔 상태와 실제 상태가 벌어지는 이 현상을 구성 드리프트라고 부릅니다. 부분 배포는 그 원인 가운데 하나입니다.

부분 배포가 생기는 조건

아래 셋이 모두 갖춰져야 생깁니다.

  1. 한 서비스가 서버 여러 대에서 돕니다. 서버가 한 대면 섞일 상대가 없습니다
  2. 새 버전을 서버마다 따로 올립니다. 모든 서버를 한 동작으로 바꾸는 방법이 없습니다
  3. 중간에 멈췄을 때 끝까지 밀거나 되돌리는 장치가 없습니다

첫째와 둘째는 규모가 커진 서비스라면 거의 늘 갖춰져 있습니다. 그래서 대책은 셋째에 몰립니다. 멈춘 상태를 누군가 알아채고 한 버전으로 모으게 해 두면 부분 배포는 잠깐 지나가는 상태로 끝납니다.

부분 배포를 막거나 줄이는 방법

대책은 두 갈래입니다. 섞인 상태를 짧게 만드는 쪽과, 섞여도 안 깨지게 만드는 쪽입니다.

방법 하는 일
선언적 설정과 조정 루프 원하는 버전을 파일에 적어 둔다. 도구가 서버마다 실제 버전을 계속 견주어 맞춘다. 멈춰도 다음에 돌 때 끝까지 간다
자동 롤백 헬스 체크가 실패하면 이미 바꾼 서버까지 옛 버전으로 되돌린다
블루-그린 배포 새 버전 서버 묶음을 따로 다 띄운 뒤 요청을 한 번에 옮긴다. 두 버전이 요청을 나눠 받는 시간이 거의 없다
불변 인프라 서버를 고치지 않고 새 버전으로 만든 서버로 갈아 끼운다. 반쯤 고쳐진 서버가 안 생긴다
배포 뒤 버전 모아 보기 서버마다 도는 버전을 모아 한 가지인지 확인한다
버전 사이 호환 유지 새 버전은 옛 형식을 읽게 짠다. 이것을 하위 호환이라 부른다. 옛 버전도 모르는 칸을 만나면 버리지 않고 남기게 미리 짜 둔다

앞의 다섯은 섞인 상태를 줄이거나 빨리 찾습니다. 마지막 하나는 섞인 상태가 와도 데이터가 안 깨지게 합니다. 롤링 배포는 정상일 때도 두 버전이 함께 도는 시간이 있습니다. 그래서 마지막 방법은 부분 배포가 없어도 필요합니다.

일부러 나눠 내보내는 배포와 가르는 선

카나리 배포는 새 버전을 먼저 서버 일부에만 올리고 지켜봅니다. 문제가 없으면 넓힙니다. 문제가 있으면 되돌립니다. 서버 상태만 보면 부분 배포와 똑같이 두 버전이 섞여 있습니다.

가르는 것은 섞인 상태를 누가 알고 있느냐입니다. 카나리 배포는 어느 서버가 어느 버전인지 압니다. 다음에 넓힐지 되돌릴지도 정해져 있습니다. 부분 배포는 둘 다 없습니다. 섞였다는 사실부터 모를 때가 많습니다.

두 버전이 함께 돈다는 점은 같습니다. 그래서 버전 사이 호환이 필요한 것도 같습니다.

관련 항목

부분 배포가 끼어드는 배포 방식

배포 · 롤링 배포 · 롤아웃 · 지속적 배포 · 배포 파이프라인 · 무중단 배포

두 버전이 섞여 있다는 점이 같은 의도된 배포

카나리 배포 · 블루-그린 배포 · 점진적 배포 · 기능 플래그

섞인 상태를 끝까지 밀거나 되돌리는 장치

선언적 설정 · 조정 루프 · 원하는 상태 · 실제 상태 · 자동 롤백 · 롤백 · 불변 인프라 · GitOps · 멱등성

배포 도중 서버를 넣고 빼는 장비와 확인

로드 밸런서 · 헬스 체크 · 트래픽 · 관측성

두 버전이 함께 돌 때 필요한 호환 규칙

하위 호환 · 상위 호환성 · 스키마 진화 · 직렬화 · 확장-축소 패턴

부분 배포와 함께 쌓이는 장애

구성 드리프트 · 눈송이 서버 · 배포 중 스키마 불일치 · 롤백 불가 마이그레이션 · 버전 불일치

부분 배포가 드러나는 배포 지표

변경 실패율 · 배포 실패 복구 시간 · 배포 빈도 · 배포 재작업률

다른 이름: partial deployment · 부분적 배포