이전 강의: 4강. 파이프라인과 아티팩트 이번 강의 키워드: Big-bang Release, Deploy vs Release, Rolling Update, Blue-Green, Expand and Contract, Canary, Progressive Delivery, Feature Flag, Knight Capital, Strangler Fig
들어가며
토요일 새벽 2시, "정기 배포 작업으로 서비스가 중단됩니다"라는 공지가 떠 있습니다. 여러 팀의 담당자가 모두 모여 있고, 체크리스트는 40단계입니다. 3개월 동안 쌓인 변경이 오늘 한꺼번에 나갑니다. 새벽 5시에 문제가 발견되지만 무엇이 원인인지 알 수 없고, 롤백하려니 DB 스키마가 이미 바뀌어 되돌릴 수 없습니다.
이것이 빅뱅 릴리스(Big-bang Release)입니다. 1강의 악순환이 가장 선명하게 나타나는 순간이기도 하죠. 3강과 4강에서 변경을 작게 만들고 안전하게 운반하는 방법을 봤다면, 이번 강의는 운영에 도착하는 순간의 위험을 작게 쪼개는 방법을 다룹니다.
가장 중요한 구분: 배포와 공개
이번 강의 전체를 관통하는 개념부터 짚겠습니다. 배포(deploy)와 공개(release)는 다른 행위입니다.
배포는 새 코드를 운영 환경에 올려 실행 가능한 상태로 만드는 기술적 행위입니다. 공개는 사용자가 새 기능을 실제로 쓰게 만드는 비즈니스 행위입니다. 빅뱅 릴리스에서는 이 둘이 한 순간에 묶여 있습니다. 코드가 올라가는 순간 모든 사용자가 모든 변경을 한꺼번에 겪게 되죠.
이 둘을 떼어 놓으면 선택지가 생깁니다. 코드를 미리 배포해 두고 공개는 나중에 할 수도 있고, 일부 사용자에게만 먼저 공개할 수도 있고, 문제가 생기면 배포는 그대로 둔 채 공개만 거둘 수도 있습니다. 이번 강의의 패턴들은 모두 이 분리를 서로 다른 층위에서 구현합니다.
Rolling Update: 가장 기본적인 방법
서버가 여러 대라면 가장 먼저 떠올릴 수 있는 방법은 한 대씩 차례로 교체하는 것입니다. 전체 10대 중 1~2대를 새 버전으로 바꾸고, 정상인지 확인하고, 다음 몇 대를 바꿉니다. Kubernetes의 Deployment가 기본으로 쓰는 방식이 이 Rolling Update입니다.
추가 자원이 거의 필요 없고 서비스 중단도 없다는 장점이 있습니다. 대신 교체가 진행되는 동안에는 구버전과 신버전이 동시에 요청을 처리합니다. 두 버전이 공존해도 문제가 없도록 API와 데이터 형식의 호환성을 설계해야 합니다. 이 조건은 이번 강의의 모든 패턴에 따라붙는 숙제이기도 합니다.
Blue-Green Deployment
Blue-Green은 ThoughtWorks의 Dan North와 Jez Humble이 2000년대 중반 사용한 기법으로, Martin Fowler가 2010년 소개하며 널리 알려졌습니다.
동일한 운영 환경을 두 벌(Blue, Green) 준비합니다. 현재 Blue가 트래픽을 받고 있다면, 새 버전은 쉬고 있는 Green에 배포합니다. Green에서 충분히 확인한 뒤 로드밸런서나 라우터에서 트래픽을 한 번에 Green으로 전환합니다. 문제가 생기면 다시 Blue로 돌리면 됩니다. Blue는 아직 이전 버전 그대로 살아 있으니까요.
┌──────────┐
사용자 → │ 라우터 │ ──(전환)──→ Green (v2) ← 새 버전
└──────────┘
╎
└ ─ ─ (대기) ─ ─→ Blue (v1) ← 즉시 롤백 대상Blue-Green의 가치는 배포 행위와 전환 행위의 분리, 그리고 즉각적인 롤백입니다. 배포는 사용자에게 영향 없이 미리 해두고, 전환은 스위치 하나로 합니다. 2강의 Immutable Infrastructure와도 잘 맞습니다. 기존 환경을 고치는 게 아니라 새 환경으로 갈아타는 것이니까요.
비용은 두 가지입니다. 전환 시점 전후로 자원이 두 배 필요합니다. 그리고 더 까다로운 문제가 있습니다. 두 환경이 데이터베이스를 공유한다는 점입니다.
데이터베이스라는 난제: Expand and Contract
애플리케이션 서버는 두 벌을 둘 수 있어도 데이터베이스는 보통 하나입니다. 그래서 "v2 배포와 함께 users.name 컬럼을 full_name으로 바꾼다"는 변경을 Blue-Green으로 내보내면 문제가 생깁니다. 스키마를 바꾸는 순간 Blue(v1)는 name 컬럼을 찾지 못해 망가지고, 롤백 대상이 사라집니다. 도입부에서 "스키마가 이미 바뀌어 롤백할 수 없었다"는 상황이 바로 이것입니다.
이 문제를 푸는 패턴이 Expand and Contract(Parallel Change라고도 부릅니다)입니다. 호환되지 않는 변경을 호환되는 여러 단계로 쪼갭니다.
1. Expand : full_name 컬럼을 추가한다 (name은 그대로 둔다)
2. 이중 쓰기 : 앱이 name과 full_name 양쪽에 쓰도록 배포한다
3. 이관 : 기존 데이터를 full_name으로 복사한다
4. 전환 : 앱이 full_name에서 읽도록 배포한다
5. Contract : name을 더 이상 쓰지 않음을 확인한 뒤 컬럼을 삭제한다모든 단계에서 바로 이전 버전의 애플리케이션이 여전히 동작합니다. 그래서 어느 단계에서든 롤백할 수 있습니다. 3강의 Branch by Abstraction이 코드에서 한 일을 스키마에서 하는 셈입니다. 한 번에 끝낼 일을 다섯 번에 나누니 번거롭지만, 이것이 무중단 배포의 실질적인 대가입니다.
여기서 나오는 안티패턴이 애플리케이션 변경과 파괴적 스키마 변경을 한 배포에 묶는 것입니다. 컬럼 삭제나 이름 변경 같은 파괴적 변경은 항상 그 컬럼을 쓰는 코드가 모두 사라진 다음 배포에서 해야 합니다.
Canary Release와 Progressive Delivery
탄광의 카나리아 이야기를 아시나요? 옛날 광부들은 유독가스에 민감한 카나리아를 갱도에 데려가, 새가 이상 증세를 보이면 대피했습니다. Canary Release는 이 발상을 배포에 적용합니다. 새 버전을 전체가 아니라 일부 트래픽에만 먼저 보내고, 문제가 없으면 점차 비율을 늘립니다.
v2 트래픽 비율: 1% → 5% → 25% → 50% → 100%
└ 각 단계마다 오류율·지연시간을 v1과 비교 ┘Blue-Green이 "한 번에 전환하되 되돌리기 쉽게" 만드는 전략이라면, Canary는 전환 자체를 작게 쪼개는 전략입니다. 1강의 두 번째 질문, "변경 단위가 작은가"를 영향 범위의 차원에서 적용한 것이죠. 문제가 있어도 전체 사용자의 1%만 겪습니다.
Canary가 제대로 동작하려면 관측이 필수입니다. 1%에 배포해놓고 그 1%에서 무슨 일이 일어나는지 볼 수 없다면 아무 의미가 없습니다. 신버전과 구버전의 오류율, 지연시간 같은 지표를 나란히 비교할 수 있어야 하고, 이 비교 결과에 따라 자동으로 다음 단계로 진행하거나 롤백하도록 만들 수도 있습니다. Kubernetes 생태계의 Argo Rollouts나 Flagger 같은 도구가 이런 자동화를 제공합니다. 이 관측 체계는 6강의 주제입니다.
RedMonk의 James Governor는 2018년 이처럼 영향 범위를 점진적으로 넓혀가는 배포 방식을 통틀어 Progressive Delivery라고 불렀습니다.
Feature Flag: 코드 안의 스위치
Blue-Green과 Canary가 인프라 층위에서 배포와 공개를 분리한다면, Feature Flag는 코드 층위에서 분리합니다. 3강에서 Trunk-Based Development의 조력자로 잠깐 등장했던 그 기법입니다.
if (flags.isEnabled("new-checkout", { userId })) {
return renderNewCheckout();
}
return renderLegacyCheckout();코드는 이미 배포돼 있지만, 플래그 설정에 따라 누가 새 기능을 보는지가 결정됩니다. 재배포 없이 특정 사용자 그룹, 특정 비율, 내부 직원에게만 기능을 켤 수 있습니다. 문제가 생기면 코드 롤백 대신 플래그만 끄면 됩니다.
Pete Hodgson은 2017년 Martin Fowler의 사이트에 실린 글에서 플래그를 용도별로 나눴습니다. 미완성 기능을 숨기는 릴리스 토글, A/B 테스트를 위한 실험 토글, 장애 시 부하가 큰 기능을 끄는 운영 토글, 요금제나 권한에 따라 기능을 여는 권한 토글입니다. 이 구분이 중요한 이유는 수명이 다르기 때문입니다. 릴리스 토글은 기능 공개가 끝나면 며칠이나 몇 주 안에 제거해야 하지만, 권한 토글은 사실상 영구적인 기능입니다.
Feature Flag의 그림자: Knight Capital 사건
Feature Flag는 강력한 만큼 그 자체로 안티패턴을 낳습니다. 가장 흔한 것은 플래그 부채(flag debt)입니다. 공개가 끝난 릴리스 토글을 제거하지 않고 방치하면, 코드에 분기가 쌓이고 조합 가능한 상태의 수가 기하급수적으로 늘어납니다. 아무도 어떤 조합이 테스트됐는지 모르게 됩니다.
이 위험이 극단적으로 드러난 사례가 2012년 8월의 Knight Capital 사건입니다. 미국의 대형 증권 거래 회사였던 Knight Capital은 새 거래 기능을 배포하면서, 오래전 사용이 중단된 옛 기능의 플래그를 재활용했습니다. 그런데 배포 과정에서 8대의 서버 중 1대에 새 코드가 설치되지 않았습니다. 플래그가 켜지자 7대는 새 기능을, 나머지 1대는 그 플래그에 연결된 옛 기능을 실행했습니다. 이 서버는 약 45분 동안 의도하지 않은 주문을 쏟아냈고, 회사는 약 4억 4천만 달러의 손실을 입었습니다.
이 사건에는 이번 시리즈의 안티패턴이 여러 개 겹쳐 있습니다. 제거되지 않은 옛 코드와 재사용된 플래그(플래그 부채), 수동 배포 과정에서 한 대가 누락된 것(2강의 서버 간 드리프트), 그리고 빠르게 이상을 감지하고 멈추는 장치의 부재(6강의 주제)입니다. 교훈은 명확합니다. 플래그는 수명과 담당자를 정해 관리하고, 쓰임이 끝나면 코드와 함께 제거하며, 절대 재활용하지 않는다.
Strangler Fig: 시스템 전체를 점진적으로 교체하기
지금까지는 한 번의 배포를 작게 쪼개는 패턴이었습니다. 마지막으로 더 큰 단위, 시스템 전체의 교체를 쪼개는 패턴을 보겠습니다.
오래된 레거시 시스템을 새로 만들고 싶을 때 가장 유혹적인 선택은 전면 재작성(big-bang rewrite)입니다. 새 시스템을 처음부터 따로 만들고, 완성되면 한 번에 갈아타는 것이죠. Joel Spolsky가 2000년 "절대 하지 말아야 할 일"로 꼽은 것이 바로 이것입니다. 그는 넷스케이프가 브라우저를 처음부터 다시 쓰느라 수년을 보내는 동안 시장을 잃은 사례를 들었습니다. 재작성 기간 동안 기존 시스템은 계속 변하고, 레거시 코드에 숨어 있던 수많은 예외 처리와 버그 수정은 새 시스템에서 다시 발견해야 합니다. 이것은 3강의 장수 브랜치가 시스템 규모로 커진 모습입니다.
Martin Fowler가 2004년 소개한 Strangler Fig 패턴은 이름을 호주의 교살무화과나무에서 따왔습니다. 이 덩굴은 숙주 나무를 타고 자라며 점점 감싸고, 결국 숙주가 사라진 자리에 스스로 선 나무가 됩니다.
┌─────────── 라우팅 계층 (프록시/게이트웨이) ───────────┐
요청 → │ /orders → 새 시스템 │
│ /payments → 새 시스템 │
│ 그 외 → 레거시 시스템 │
└────────────────────────────────────────────────────────┘레거시 시스템 앞에 라우팅 계층을 두고, 기능을 하나씩 새 시스템으로 옮기면서 해당 경로만 새 시스템으로 보냅니다. 옮겨진 기능이 늘어날수록 레거시가 담당하는 영역은 줄어들고, 마지막에는 레거시를 끌 수 있습니다. 매 단계가 독립적으로 배포되고 검증되며, 문제가 생기면 그 경로만 레거시로 되돌리면 됩니다.
롤백인가, 롤포워드인가
배포 후 문제가 생겼을 때 선택지는 두 가지입니다. 이전 버전으로 되돌리는 롤백(rollback), 수정 버전을 빠르게 다시 배포하는 롤포워드(roll-forward)입니다.
롤포워드는 파이프라인이 충분히 빠를 때만 현실적인 선택지입니다. 그렇지 않다면 기본값은 롤백이어야 하고, 이번 강의의 패턴들이 롤백을 쉽게 만드는 역할을 합니다. 여기서 주의할 안티패턴은 "롤백은 한 번도 해본 적 없는 절차"인 상태입니다. 롤백 절차가 문서에만 있고 실제로 실행해 본 적이 없다면, 가장 급한 순간에 처음 시도하게 됩니다. 2강의 Phoenix Server처럼, 롤백도 평소에 연습해 두어야 믿을 수 있습니다.
배포 전략 한눈에 보기
| 전략 | 분리하는 층위 | 롤백 속도 | 추가 비용 | 핵심 전제 |
|---|---|---|---|---|
| Rolling Update | 서버 단위 | 보통 (다시 순차 교체) | 거의 없음 | 신구 버전 공존 호환성 |
| Blue-Green | 환경 단위 | 매우 빠름 (스위치) | 자원 2배 | 공유 DB 호환성 |
| Canary | 트래픽 비율 | 빠름 (비율 0%) | 라우팅·관측 체계 | 버전별 지표 비교 |
| Feature Flag | 코드 분기 | 매우 빠름 (플래그 끄기) | 플래그 관리 | 플래그 수명 관리 |
| Strangler Fig | 기능·경로 단위 | 경로별로 빠름 | 라우팅 계층 | 기능 경계 분리 |
실무에서는 이 전략들을 조합해서 씁니다. 예를 들어 Canary로 새 버전을 배포하되, 새 기능 자체는 Feature Flag 뒤에 숨겨두는 식입니다.
정리
빅뱅 릴리스는 배포와 공개가 한 순간에 묶여, 모든 변경의 위험이 모든 사용자에게 동시에 떨어지는 구조입니다. 이번 강의의 패턴들은 이 결합을 서로 다른 층위에서 풉니다. Blue-Green은 환경을, Canary는 트래픽을, Feature Flag는 코드를, Strangler Fig는 시스템 경계를 기준으로 변경을 쪼갭니다. 그리고 공유 데이터베이스라는 제약은 Expand and Contract로 호환되는 여러 단계로 나눠 해결합니다. Knight Capital 사건은 이 도구들도 관리되지 않으면 그 자체로 위험이 된다는 것을 보여줍니다.
다음 6강에서는 배포가 끝난 뒤, 시스템이 실제로 어떻게 돌아가고 있는지 보는 방법과 장애가 났을 때 대응하는 방법을 다룹니다. Canary가 의존하는 관측 체계, 알림 피로, 그리고 사람을 탓하지 않는 장애 회고가 주제입니다.