롤백 불가 마이그레이션
고친 사람 github-actions[bot]
롤백 불가 마이그레이션은 데이터베이스 구조를 바꾼 뒤에 되돌리면 데이터를 잃게 되는 변경입니다. 옛 데이터를 지워 버리는 변경이 대표입니다. 실행한 뒤에는 그 전 모습으로 돌아갈 길이 사라집니다. 코드는 옛 버전으로 되돌릴 수 있습니다. 지운 데이터는 돌아오지 않습니다.
쉽고 빠른 이해
회원 테이블에서 별명 열을 지우는 변경이 대표입니다. 되돌리려고 열을 다시 만들면 칸은 생깁니다. 그 안에 있던 별명은 전부 비어 있습니다.
배포를 되돌리는 일은 이전 상태가 남아 있어야 성립합니다. 그래서 곤란합니다. 새 버전에서 버그가 나면 보통 옛 버전으로 물러섭니다. 옛 코드는 지워진 별명 열을 찾다가 실패합니다.
어떻게 터지나:
- 배포와 함께 데이터를 지우는 변경이 운영 데이터베이스에서 실행됩니다
- 새 버전에서 문제가 나서 옛 버전으로 되돌립니다
- 옛 코드가 기대하는 데이터가 이미 없어 되돌린 쪽도 제대로 동작하지 못합니다
치르는 값은 이렇습니다. 뒤로 물러서는 길이 막혀 앞으로 고쳐 나가는 수밖에 없습니다. 백업으로 되살리면 백업 뒤에 들어온 데이터를 잃습니다.
상세
종이 서류를 스캔하고 원본은 파쇄기에 넣었다고 해 봅시다. 나중에 스캔본 몇 장이 흐려서 못 읽는다는 것을 알게 됩니다. 원본은 이미 조각이 나서 다시 스캔할 수 없습니다.
두 열을 합친 새 열만 남기고 옛 열을 지우는 마이그레이션이 이와 같습니다. 합친 결과가 잘못된 줄 나중에 알아도 옛 열은 이미 없습니다.
이 절은 데이터베이스 변경이 언제 되돌릴 수 없게 되는지를 다룹니다. 먼저 열 하나를 지우는 명령 몇 줄로 그 모습을 보여 드립니다. 그다음 그 변경이 배포를 되돌리는 순서와 부딪히는 과정을 따라갑니다.
마이그레이션이 하는 일
데이터베이스에는 스키마가 있습니다. 스키마는 어떤 테이블이 있고 테이블마다 어떤 열이 어떤 타입으로 있는지를 적은 정의입니다. 데이터는 이 모양에 맞춰 저장됩니다.
코드가 바뀌면 스키마도 따라 바뀌어야 합니다. 새 기능이 회원의 별명을 다루려면 회원 테이블에 별명 열이 있어야 합니다. 개발자 노트북과 테스트 서버와 운영 서버가 같은 변경을 같은 순서로 받아야 코드가 어디서나 동작합니다.
마이그레이션은 이 스키마 변경을 파일로 적어 차례대로 실행하는 방식입니다. 파일에 번호나 시각을 붙여 순서를 정합니다. 마이그레이션 도구는 어느 파일까지 실행했는지를 데이터베이스 안에 기록합니다. 다음 실행 때는 남은 파일만 실행합니다.
마이그레이션이라는 말은 서버나 데이터를 다른 곳으로 옮기는 일에도 씁니다. 이 편에서 마이그레이션은 스키마를 바꾸는 파일 쪽을 가리킵니다.
올리기와 되돌리기
마이그레이션 파일에는 흔히 두 방향을 같이 적습니다. 변경을 적용하는 올리기(up)와 그 변경을 취소하는 되돌리기(down)입니다. 되돌리기는 배포한 버전에 문제가 생겨 스키마를 이전 모습으로 돌려야 할 때 씁니다.
예로 쓸 명령은 ALTER TABLE 입니다. SQL(Structured Query Language, 데이터베이스에 질의하는
언어)에서 테이블 구조를 바꾸는 명령입니다. ADD COLUMN 은 열을 더합니다. DROP COLUMN 은 열을
지웁니다.
아래는 회원 테이블 users 에 자기소개 열 bio 를 더하는 마이그레이션입니다. TEXT 는 문자열
타입입니다.
-- 올리기
ALTER TABLE users ADD COLUMN bio TEXT;
-- 되돌리기
ALTER TABLE users DROP COLUMN bio;
올리기는 빈 bio 열을 만듭니다. 되돌리기는 그 열을 지웁니다. 올리기 전부터 있던 데이터는 어느
쪽도 건드리지 않습니다. 그래서 되돌리고 나면 스키마가 올리기 전 모습으로 돌아옵니다.
정보를 버리는 변경
이번에는 방향을 뒤집어 별명 열 nick 을 지웁니다. 되돌리기는 그 열을 다시 만드는 명령입니다.
SELECT 는 값을 읽는 명령입니다. 첫 줄과 마지막 줄은 id 가 1 인 회원의 별명을 읽습니다.
오른쪽 주석은 그 줄이 돌려주는 값입니다.
SELECT nick FROM users WHERE id=1; -- yuumi
-- 올리기
ALTER TABLE users DROP COLUMN nick;
-- 되돌리기
ALTER TABLE users ADD COLUMN nick TEXT;
SELECT nick FROM users WHERE id=1; -- NULL
되돌리기를 돌리면 nick 열은 다시 생깁니다. 값은 모두 NULL 입니다. NULL 은 칸에 값이 없다는
표시입니다. 스키마의 모양은 돌아왔습니다. 그 안의 내용은 돌아오지 않았습니다.
이것이 롤백 불가 마이그레이션입니다. 되돌리기 파일을 쓸 수는 있습니다. 그러나 되돌리기에는 올리기가 지운 값을 다시 만들 재료가 없습니다. 바꾼 뒤의 데이터만 보고 바꾸기 전 데이터를 다시 만들 수 없으면 그 변경은 되돌릴 수 없습니다.
마이그레이션 도구 중에는 이런 변경에 되돌리기를 비워 두고 「되돌릴 수 없음」으로 표시하게 하는 것이 있습니다. 표시된 마이그레이션을 되돌리려 하면 도구가 거기서 멈춥니다. 빈 열을 만들어 놓고 되돌렸다고 믿는 일을 막으려는 장치입니다.
되돌릴 수 없게 만드는 변경들
열 삭제만 그런 것이 아닙니다. 아래 변경은 모두 바꾸기 전 값의 일부를 버립니다.
| 변경 | 사라지는 정보 |
|---|---|
| 열이나 테이블 삭제 | 그 안의 값 전부 |
| 행 삭제 (예: 오래된 기록 정리) | 지운 행 |
| 타입 좁히기 (예: 긴 문자열을 짧게, 소수를 정수로) | 잘리거나 반올림된 부분 |
| 두 열을 하나로 합치기 (예: 성과 이름을 전체 이름으로) | 어디서 갈라야 하는지 |
| 여러 값을 하나로 덮어쓰기 (예: 상태 셋을 둘로 합침) | 원래 어느 값이었는지 |
표의 다섯 줄은 공통점이 하나입니다. 바꾼 결과에서 바꾸기 전 값으로 가는 길이 하나로 정해지지 않습니다. 「남궁민수」를 성과 이름으로 다시 가르려면 「남 · 궁민수」인지 「남궁 · 민수」인지 알아야 합니다. 그 답은 두 열을 합칠 때 버렸습니다.
배포 롤백과 부딪히는 순서
배포는 새 버전의 코드를 운영 서버에 올려 실제 요청을 받게 하는 일입니다. 새 버전에서 버그가 나면 흔히 옛 버전의 코드로 되돌립니다. 이것을 배포 롤백이라고 부릅니다. 옛 버전의 빌드 결과물이 남아 있으므로 코드를 되돌리는 일은 대개 금방 끝납니다.
문제는 데이터베이스가 코드와 함께 되돌아가지 않는다는 것입니다. 마이그레이션은 배포와 함께 운영 데이터베이스에서 이미 실행됐습니다. 옛 코드는 옛 스키마를 기대합니다.
아래는 앞의 nick 열 삭제가 배포 롤백과 만나는 순서입니다.
sequenceDiagram
participant 운영자
participant 앱
participant 데이터베이스
운영자->>데이터베이스: 마이그레이션 · nick 열 삭제
운영자->>앱: 새 코드 배포
Note over 앱: 새 코드에서 버그 발견
운영자->>앱: 옛 코드로 롤백
앱->>데이터베이스: nick 열 읽기
데이터베이스-->>앱: 그런 열이 없다는 오류
운영자->>데이터베이스: 되돌리기 · nick 열 다시 만들기
앱->>데이터베이스: nick 열 읽기
데이터베이스-->>앱: 열은 있음 · 값은 전부 NULL
옛 코드로 돌아가자 앱은 nick 열을 찾다가 오류를 냅니다. 되돌리기를 돌리면 오류는 멈춥니다.
대신 모든 회원의 별명이 빈 채로 화면에 나옵니다. 어느 쪽도 배포 전 상태가 아닙니다.
남는 길은 앞으로 고치는 것입니다. 옛 버전으로 물러서는 대신 버그를 고친 새 버전을 한 번 더 배포합니다. 이것을 롤 포워드(roll forward)라고 부릅니다. 고친 버전이 나갈 때까지 서비스는 버그를 안은 채 돌아갑니다.
되돌리기가 있어도 잃는 데이터
데이터를 지우지 않는 마이그레이션도 시간이 지나면 같은 문제가 됩니다. 되돌리면 데이터를
잃게 되기 때문입니다. 앞의 bio 열을 다시
봅니다. 올리기 직후에 되돌리면 잃는 것이 없습니다.
배포 뒤 하루 동안 회원들이 새 기능으로 자기소개를 썼다고 해 봅시다. 이제 되돌리기는 그 하루치 자기소개를 지웁니다. 되돌리기 파일은 처음과 같습니다. 바뀐 것은 그 사이 새 스키마에 쌓인 데이터입니다.
데이터를 새 테이블로 옮기는 마이그레이션에서도 같은 일이 생깁니다. 새 코드가 새 테이블에만 쓰기 시작하면 옛 테이블은 그 시점에서 멈춥니다. 되돌리기가 옛 테이블로 돌아가기만 하면 그 사이 쓴 데이터는 새 테이블에 남은 채 버려집니다.
발생 조건
롤백 불가 마이그레이션이 장애가 되려면 두 조건이 함께 있어야 합니다.
- 마이그레이션을 되돌리면 잃는 데이터가 생깁니다. 앞의 표에 있는 변경이 그렇습니다. 새 스키마에만 데이터가 쌓인 경우도 그렇습니다
- 그 마이그레이션이 운영 데이터베이스에서 실행된 뒤에 배포를 되돌려야 할 일이 생깁니다
flowchart TD
A["마이그레이션이 운영 데이터베이스에서 실행된다"] --> B{"되돌리면 잃는 데이터가 있나"}
B -->|없다| C["되돌리기로 이전 스키마에 돌아간다"]
B -->|있다| D{"배포를 되돌려야 하나"}
D -->|아니다| E["정리 작업으로 끝난다"]
D -->|그렇다| F["물러설 수 없다 · 롤 포워드만 남는다"]
조건 1 만 있으면 장애가 아닙니다. 쓰지 않는 열을 지우는 정리는 원래 정보를 버리려고 하는 일입니다. 조건 2 만 있으면 되돌리기 마이그레이션이 스키마와 데이터를 함께 이전 모습으로 돌려놓습니다.
백업으로 대신할 수 없는 까닭
지운 데이터는 백업에 남아 있을 수 있습니다. 백업은 어느 시점의 데이터베이스 전체를 복사해 둔 것입니다.
백업에서 복구하면 nick 열과 값이 돌아옵니다. 대신 백업을 뜬 시점 뒤에 들어온 데이터가 전부
사라집니다. 그 사이의 가입과 주문과 결제도 함께 사라집니다. 복구하는 동안 서비스도 멈춰야 합니다.
지운 열의 값만 필요하면 백업을 다른 곳에 따로 복구해 그 열만 꺼내 옮깁니다. 손이 많이 갑니다. 그 사이 바뀌거나 지워진 행과 하나씩 맞춰야 합니다.
미리 막는 방법
터진 뒤에는 고칠 길이 좁습니다. 그래서 정보를 버리는 단계를 배포와 떼어 놓는 방법을 씁니다.
| 방법 | 하는 일 |
|---|---|
| 확장-축소 패턴 | 새 열을 먼저 더하고 코드를 옮긴 뒤, 옛 열 삭제는 맨 나중에 따로 배포한다 |
| 지우기 전에 떼어 보관 | 지울 열이나 행을 보관용 테이블로 복사한 뒤 지운다 |
| 되돌릴 수 없음 표시 | 되돌리기를 비워 두고 표시해, 되돌리기를 실행할 때 도구가 멈추고 사람이 판단하게 한다 |
| 기능 플래그 | 코드를 되돌리는 대신 설정으로 새 기능만 끈다 |
확장-축소 패턴은 지우는 단계를 맨 끝으로 미룹니다. 그 단계를 배포할 때는 바로 앞 버전의 코드도 옛 열을 읽지 않습니다. 그래서 한 버전 되돌려도 옛 코드가 지워진 열을 찾지 않습니다.
관련 항목
롤백 불가 마이그레이션이 깨뜨리는 배포 절차
배포 · 롤백 · 자동 롤백 · 부분 배포 · 롤링 업데이트 · 블루-그린 배포 · 카나리 배포
롤백 불가 마이그레이션을 피하거나 수습하는 방법
롤 포워드 · 확장-축소 패턴 · 기능 플래그 · 백업과 복구 · 시점 복구 · 소프트 삭제
롤백 불가 마이그레이션이 바꾸는 데이터베이스 구조
데이터베이스 · 스키마 · 테이블 · 열 · 행 · NULL · SQL
스키마를 바꾸는 작업과 그 규칙
데이터베이스 마이그레이션 · 스키마 변경 · 스키마 진화 · 하위 호환 · 데이터베이스 변경 관리 · 멱등성
롤백 불가 마이그레이션과 함께 터지는 장애
배포 중 스키마 불일치 · 버전 불일치 · 데이터 손실 · 구성 드리프트
마이그레이션을 실행하는 도구
다른 이름: irreversible migration · 되돌릴 수 없는 마이그레이션 · 비가역 마이그레이션