롤백 전략 수립 방법
배포 파이프라인에서 가장 중요한 것은 어떻게 잘 배포할 것인가가 아니라 터졌을때 얼마나 빨리 피흘리지 않고 되돌릴 수 있는가 이다.
App DB 분리 #
롤백 전략의 대전제인데 롤백 전략을 세울때는 먼저 깨달아야하는게 애플리케이션 코드는 1초면 롤백이 되지만 데이터베이스 롤백은 지옥이라는 것이다.
따라서 전략은 항상 상태가없는 stateless app 계층과 stateful db계층을 철저히 나누어 수립해야한다.
애플리케이션 계층의 롤백 전략 #
코드가 잘못되었을때 트래픽을 즉시 정상 버전으로 돌려놓는 전략들을 알아보자
블루/그린 Blue Green Deployment부터 알아보면 구버전 blue와 동일한 환경의 신버전 green 서버를 100% 띄워놓고 로드밸런서 nginx, aws alb등 타겟만 green으로 딸깍 변경하는 방식이다
- 롤백 방법은 신버전 green에서 에러가 터지만 다시 블루를 보게 하면 된다.
- 장점은 롤백 속도가 1초 미만으로 압도적으로 빠르다
- 단점은 서버 인프라 리소스가 평소의 2배로 필요하다.
**카나리는 신버전 서버의 소수(예: 전체의 5%)**만 띄워두고 트래픽을 아주 일부만 흘려보내서(라우팅) 간을 보는 방식이다
점진적으로 늘려나가며 에러율이나 cpu가 튀는지 모니터링한뒤에 100%까지 늘린다.
- 롤백 방법은 카나리 서버에서 에러가 감지되면, 해당 서버로 가는 트래픽 라우팅 룰을 삭제하고 즉시 구버전으로 트래픽을 100% 돌린다.
- 장점은 장애가 발생해도 전체 유저가 아닌 5% 유저만 영향을 준다
이건 배포 방식은 아니고 피쳐토글을 두는건데 인프라 건드리지 않고 코드 내부에 config 파일로 관리하거나 db에서 관리하거나 if (isNewFeatureEnabled) { newLogic() } else { oldLogic } 과 같은 방식으로 코드를 구성해 놓는것이다. 스위치를 두는곳
- 롤백 방법은 배포를 다시 할 필요없이 외부 설정 서버 redis, aws appconfig 등에서 스위치 값을 false로 변경하면 즉각적으로 기존 로직으로 동작한다.
- 장점은 가장 유연하게 특정 유저(사내 ip같은걸 예시로 들수있음)에게만 켜볼수도 있다.
데이터베이스 계층 롤백 전략 (전진 복구) #
여기가 가장 까다로운 부분인데, 컬럼 이름을 바꿨는데 app을 롤백해버리면 구버전 app이 바뀐 db 컬럼을 찾지 못해 전면 장애가 발생한다.
따라서 디비 마이그레이션 flyway, liquibase 시에는 롤백 drop, alter를 고려하는것이 아니라 어떤 버전의 app이 띄워져 있어도 동작하도록 하위 호완성 backward compatibility를 무조건 지키는것이 핵심이다.
이를 Expand & Contract(팽창과 수축) 패턴이라고 한다.
- DB 변경 Expand (추가만 허용): 기존 컬럼을 수정 삭제하지 않고 새로운 컬럼을 추가만 한다 구버전 app은 기존 컬럼을 쓰므로 정상 동작
- App 배포 (양방향 쓰기): 신버전 app을 배포하고 이 app은 기존 컬럼과 신규 컬럼에 데이터를 동시에쓰고 double write, 신규 컬럼에서 읽는다 롤백시 언제든 구버전 app으로 돌아갈 수 있다.
- 데이터 마이그레이션 (동기화 배치): 백그라운드 배치를 돌려 기존 컬럼의 과거 데이터를 신규 컬럼으로 복사한다
- DB 정리 (삭제 drop 배포): 며칠 후 시스템이 안정화 되었다면 비로소 안 쓰는 구버전 컬럼을 삭제한다.
명령어 및 설정 Example #
Nginx를 활용한 1초 블루/그린 롤백 스위칭
가장 원초적이지만 확실한 인프라 레벨의 롤백 방법이다 nginx.conf에서 바라보는 upstream만 변경하고 reload 한다.
# nginx.conf
http {
# Blue(구버전) 서버 그룹
upstream backend_blue {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
# Green(신버전) 서버 그룹
upstream backend_green {
server 10.0.0.3:8080;
server 10.0.0.4:8080;
}
server {
listen 80;
location / {
# 🚨 장애 발생 시 아래 주석만 교체하고 Nginx Reload!
# proxy_pass http://backend_green; # 신버전 터짐! 주석 처리
proxy_pass http://backend_blue; # 구버전으로 즉시 롤백!
}
}
}
nginx -t && nginx -s reload 로 바꾼후에 순단없이 트래픽 원복을 해주자
위에서 알아본 하위 호환성을 지키는 db 마이그레이션도 알아보자 flyway sql 예시로
user테이블의 name 컬럼을 first_name, last_name으로 분리하고 싶을때 절대 기존 name을 바로 지우면 안된다.
-- V2__add_new_name_columns.sql (배포 시 적용할 스크립트)
-- 1. 새로운 컬럼을 '추가'만 합니다. (절대 기존 name 컬럼을 DROP 하지 않음)
ALTER TABLE users ADD COLUMN first_name VARCHAR(50);
ALTER TABLE users ADD COLUMN last_name VARCHAR(50);
-- 이 상태에서는 App 배포가 실패해서 구버전으로 롤백되더라도,
-- 구버전 App은 여전히 기존 'name' 컬럼을 쳐다보므로 장애가 나지 않습니다.
온라인 ddl 같은걸로 테이블락을 안잡는 방식으로 db 스키마를 변경하는 방식들도 더 고려해볼 수있으나 오늘 주제랑은 벗어나니 생략