이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- 애플리케이션 rollback과 database rollback은 왜 다른 문제인가?
- DB migration이 포함된 배포에서 이전 버전 호환성을 어떻게 확보하는가?
- rollback보다 forward fix가 더 안전한 상황은 언제인가?
개요
Rollback은 새 애플리케이션 버전을 이전 artifact로 되돌리는 조치다.
하지만 DB schema나 데이터가 이미 바뀌었다면 애플리케이션만 되돌려도 이전 상태가 되지 않는다.
Spring 백엔드 운영에서 배포 전략은 항상 DB 변경 호환성과 함께 봐야 한다.
특히 rolling, blue-green, canary 모두 old/new version이 동시에 존재할 수 있으므로 schema 변경은 더 조심해야 한다.
애플리케이션과 DB의 차이
애플리케이션 artifact는 대체로 이전 version을 다시 실행하면 되돌릴 수 있다.
DB 변경은 다르다.
column 삭제, type 변경, 데이터 변환, unique constraint 추가, 대량 backfill은 되돌리기 어렵다.
이미 쓰인 데이터는 이전 코드가 읽지 못할 수 있다.
그래서 rollback 계획은 “이전 jar로 되돌린다”에서 끝나면 안 된다.
호환 가능한 변경
대체로 안전한 변경은 이전 버전도 계속 동작할 수 있는 변경이다.
예를 들면 nullable column 추가, 새 table 추가, 사용하지 않는 index 추가는 비교적 안전하다.
ALTER TABLE orders ADD COLUMN delivery_memo varchar(255) NULL;이전 버전은 새 column을 몰라도 기존 query가 동작할 수 있다.
새 버전은 해당 column이 없을 때도 처리할 수 있게 feature flag나 기본값을 둔다.
위험한 변경
위험한 변경은 이전 버전이 더 이상 동작하지 못하게 만든다.
ALTER TABLE orders DROP COLUMN address;
ALTER TABLE orders ALTER COLUMN status TYPE integer;
ALTER TABLE orders ADD CONSTRAINT orders_code_unique UNIQUE (code);column 삭제나 type 변경은 이전 애플리케이션 query를 즉시 깨뜨릴 수 있다.
constraint 추가는 기존 데이터와 쓰기 경로를 막을 수 있다.
이런 변경은 단일 배포에 묶지 말고 단계적으로 나눠야 한다.
Expand and Contract
DB 호환성을 지키는 대표 방식은 expand and contract다.
먼저 schema를 확장한다.
그 다음 애플리케이션이 새 schema를 쓰도록 배포한다.
충분히 안정화된 뒤 이전 column이나 code path를 제거한다.
1. nullable column 추가
2. 새 버전이 old/new column 모두 처리
3. backfill
4. read path 전환
5. 이전 버전 제거 확인
6. 오래된 column 삭제이 방식은 배포를 느리게 만들지만 rollback 가능성을 크게 높인다.
Rollback 판단
장애가 났을 때 항상 rollback이 정답은 아니다.
DB migration이 이전 버전을 깨지 않았다면 애플리케이션 rollback이 빠른 완화가 될 수 있다.
반대로 migration이 이미 이전 버전을 깨뜨렸다면 rollback은 2차 장애가 된다.
이때는 forward fix, feature flag off, read-only mode, traffic reduction이 더 안전할 수 있다.
판단에는 DBA나 인프라 담당자와의 협업이 필요하다.
운영 체크리스트
- migration이 backward compatible인지 검토했는가?
- 이전 version 애플리케이션이 새 schema에서 동작하는지 테스트했는가?
- destructive change가 별도 배포 단계로 분리되어 있는가?
- backfill 작업이 배포와 독립적으로 재시도 가능한가?
- rollback target artifact와 현재 DB version을 함께 기록했는가?
- feature flag로 새 write path를 끌 수 있는가?
- rollback, forward fix, restore의 판단 기준이 문서화되어 있는가?
장애 신호
- rollback 후 이전 버전에서
unknown column또는 type mismatch 오류가 난다. - migration 성공 직후 특정 write API가 unique constraint 오류를 낸다.
- backfill이 오래 걸려 배포 관측 창을 초과한다.
- old/new version이 같은 message를 다르게 해석한다.
- cache에 새 schema 기반 값이 남아 이전 버전이 읽지 못한다.
- rollback 문서에는 artifact만 있고 DB version 정보가 없다.
안전한 완화 조치
장애 중 rollback을 검토할 때 먼저 DB migration 목록을 확인한다.
./gradlew flywayInfo
curl -fsS https://api.example.com/actuator/info현재 애플리케이션 version과 DB schema version을 같이 봐야 한다.
호환 가능한 변경이면 이전 artifact로 traffic을 돌리는 것이 빠를 수 있다.
호환되지 않는 변경이면 새로운 patch version으로 forward fix하거나 문제 기능을 flag로 끄는 편이 안전할 수 있다.
데이터 손상 가능성이 있으면 restore는 마지막 수단으로 별도 판단한다.
인프라 담당자와 공유할 자료
증상: 배포 후 order update 500 증가
app version: 1.8.3-abc1234
previous version: 1.8.2-ff9012
DB migration: V52 add nullable column, V53 drop address
판단: V53 때문에 이전 version rollback 불가
완화 후보: 1.8.4 forward fix 또는 write path feature flag off
요청: V53 적용 시각과 영향 row 수 확인DB 변경이 포함된 장애 공유에는 app version과 migration version을 같이 적어야 한다.
실전 팁
- DB destructive change는 애플리케이션 배포와 분리한다.
- rollback 가능 여부는 배포 전에 검토한다.
- 이전 버전이 새 schema에서 동작하는 테스트를 둔다.
- backfill은 idempotent하고 재시도 가능하게 만든다.
- rollback과 restore를 같은 말로 쓰지 않는다.
위험 신호!
- migration 적용 후에도 이전 artifact rollback이 항상 가능하다고 가정한다.
- column 삭제와 코드 배포를 같은 release에 넣는다.
- rollback target은 있는데 DB version 기록이 없다.
- data backfill 실패를 애플리케이션 재배포로 해결하려 한다.
- restore가 필요한 상황을 일반 rollback으로 처리한다.
확인 질문
확인 질문
- 애플리케이션 rollback과 DB rollback이 다른 이유는 무엇인가?
- artifact는 이전 version으로 교체할 수 있지만 DB schema와 이미 변경된 데이터는 단순히 이전 상태로 돌아가지 않기 때문이다.
- Expand and Contract가 rollback에 유리한 이유는 무엇인가?
- old/new version이 함께 동작할 수 있게 schema를 먼저 확장하고 안정화 후 제거하므로 배포 중 되돌릴 여지를 남기기 때문이다.
- rollback보다 forward fix가 안전한 경우는 언제인가?
- DB 변경이 이미 이전 version을 깨는 상태라면 이전 artifact로 되돌리는 것이 더 큰 장애를 만들 수 있어 forward fix가 안전할 수 있다.