LabHub

SI DB 운영 · DDL 과 변경관리 · 이론

되돌릴 수 있는 스키마 변경

LabHub 에서 이어서 보기

한 줄 요약

가역성은 코드의 속성이 아니라 코드와 데이터와 시간의 조합이다 — 같은 변경이라도 아무도 안 썼으면 되돌릴 수 있고, 백만 건이 쌓인 뒤에는 되돌릴 수 없다.

왜 이게 문제인가

"down 스크립트가 있으니 안전하다" 는 말이 위험한 이유가 여기 있다. DROP COLUMN 을 되돌리는 down 은 컬럼 구조만 되살릴 뿐 그 안에 있던 값은 못 되살린다. 스키마는 되돌아가고 데이터는 안 돌아온다.

그래서 롤백 계획을 세울 때 물어야 할 것은 "되돌리는 스크립트가 있는가" 가 아니라 "되돌린 뒤에 잃는 것이 무엇인가" 다. 이 질문을 배포 전에 하면 대개 확장 → 이관 → 축소로 나눠 가는 쪽을 고르게 된다.

가역성은 코드의 속성이 아니다

"이 변경은 롤백 가능한가?"라는 질문에 대한 정확한 답은 이렇다.

> 가역성은 코드와 데이터와 시간의 조합이다.
> 같은 코드 변경이라도 아직 아무도 쓰지 않았으면 가역이고,
> 백만 건이 쌓인 뒤에는 비가역이다.

컬럼을 하나 추가했다. 되돌릴 수 있는가? 어제 배포했고 아무도 안 썼다면 그렇다.
일주일 동안 백만 건이 그 컬럼에 값을 채웠다면, DROP 은 그 백만 건을 버리는 일이다.
스키마는 되돌아가지만 데이터는 안 돌아온다.

"down 스크립트가 있으니 안전하다"는 착각이 여기서 나온다.
DROP COLUMN 을 되돌리는 down 은 컬럼 구조만 되살릴 뿐 값은 못 되살린다.
그래서 이런 말이 있다 — 다운 마이그레이션은 대개 거짓말이다.

위험한 DDL 목록

| DDL | 왜 위험한가 |
| --- | --- |
| NOT NULL 추가 | 전체 행을 검사하느라 장시간 잠금. 기존 NULL 이 있으면 실패 |
| 컬럼 이름 변경 | 원자적으로 보이지만 사실상 삭제 + 추가. 구버전 코드가 즉시 깨진다 |
| 타입 변경 | DBMS 에 따라 테이블 재작성. 대용량이면 매우 오래 걸린다 |
| 인덱스 생성 | 대용량 테이블에서 잠금 또는 부하. 온라인 옵션 확인 필요 |
| 대량 백필 UPDATE | 거대한 트랜잭션 → 잠금·복제 지연. 읽기 복제본이 무너진다 |
| DROP COLUMN | 데이터 소멸. 되돌릴 수 없다 |

특히 대량 백필을 조심해야 한다. 500만 행을 한 트랜잭션으로 UPDATE 하면
트랜잭션 로그가 폭증하고 복제 지연이 생긴다. 조회 트래픽이 복제본으로
가는 구성이면, 이 순간 조회 서비스가 오래된 데이터를 보여 준다.
그래서 백필은 반드시 배치로 쪼갠다.

-- 나쁜 예UPDATE ORDERS SET DLVR_STS = '01' WHERE DLVR_STS IS NULL;-- 좋은 예: 1000건씩, 사이에 잠깐 쉬면서UPDATE ORDERS SET DLVR_STS = '01' WHERE ORD_NO IN (SELECT ORD_NO FROM ORDERS WHERE DLVR_STS IS NULL LIMIT 1000);-- 영향 행이 0이 될 때까지 반복

확장 → 이관 → 축소 (Expand / Migrate / Contract)

되돌릴 수 있는 스키마 변경의 표준 패턴이다.
핵심 규칙: 한 번의 배포에 두 단계를 함께 넣지 않는다.

[1차 배포 — 확장]  새 컬럼 추가 (NULL 허용, 기본값 있음)  애플리케이션: 새 컬럼과 옛 컬럼에 모두 쓰고, 읽기는 옛 컬럼  → 이 시점에서 롤백하면? 새 컬럼을 아무도 안 읽으니 안전[2차 — 이관]  기존 데이터 백필 (배치로 쪼개서)  검증: 두 컬럼 값이 일치하는가[3차 배포 — 전환]  애플리케이션: 읽기를 새 컬럼으로  → 롤백하면 옛 컬럼을 읽는데, 계속 써 왔으니 값이 있다. 안전[4차 배포 — 축소]  애플리케이션: 옛 컬럼 쓰기 중단  충분한 관찰 기간 후 옛 컬럼 DROP  → 이 시점에서야 비가역이 된다

느려 보이지만, 각 단계마다 되돌릴 수 있다는 것이 이 패턴의 전부다.
컬럼 이름을 한 번에 바꾸는 것은 30초 걸리고, 잘못되면 서비스가 멈춘다.
이 패턴은 2주 걸리고, 어느 시점에도 멈추지 않는다.

변경관리 대장

SI 프로젝트에서 DDL 은 개발자가 마음대로 날리는 것이 아니다.
변경관리 대장을 두고, 운영 반영은 승인을 거친다.

| 항목 | 왜 필요한가 |
| --- | --- |
| 변경 ID / 일자 | 추적 단위 |
| 대상 객체 | 영향 범위 파악의 출발점 |
| DDL 스크립트 파일 | 실제로 실행할 것 |
| 롤백 스크립트 파일 | 없으면 승인 안 남 |
| 예상 소요 시간 | 서비스 중단 시간 산정 |
| 영향 시스템 | 연동 상대에게 통보해야 할 대상 |
| 승인자 | 책임 |

롤백 스크립트를 요구하는 것이 이 대장의 핵심 가치다.
롤백을 쓰다 보면 "이건 되돌릴 수 없네"를 배포 전에 깨닫게 된다.
그 깨달음이 확장-이관-축소로 설계를 바꾸게 만든다.

스키마 변경과 애플리케이션 배포의 순서

이것도 실수가 잦다.

한 문장으로: 더하는 것은 DB 가 먼저, 빼는 것은 앱이 먼저.

그리고 롤링 배포 중에는 구버전과 신버전이 동시에 돈다.
그래서 스키마는 항상 양쪽 버전이 모두 동작하는 상태여야 한다.
이것이 확장-이관-축소가 필요한 근본 이유다.

블루그린은 스키마를 해결하지 않는다

무중단 배포 전략을 이야기할 때 반드시 짚어야 할 점.

> 컴퓨트는 복제하기 쉽지만 데이터베이스는 대개 공유한다.
> 그래서 블루그린은 애플리케이션 롤백을 초 단위로 만들어 주지만
> 스키마 문제는 전혀 해결하지 않는다.

블루와 그린이 같은 DB 를 본다면, 스키마는 양쪽 애플리케이션 버전과
모두 호환돼야 한다. 결국 같은 이야기로 돌아온다.

마이그레이션 검증

적용했으면 확인해야 한다. 4단계로 본다.

1. 스키마   컬럼·타입·제약·인덱스가 의도대로인가2. 데이터   행 수 · 합계 · NULL 개수 · 체크섬이 보존됐는가3. 성능     주요 쿼리의 실행계획과 응답시간이 나빠지지 않았는가4. 앱       핵심 기능 스모크 테스트

2번의 체크섬이 특히 유용하다. 행 수와 합계만으로는
"한 건이 빠지고 다른 한 건이 두 배가 된" 상황을 못 잡는다.

-- 이관 전 기준선을 만들어 둔다CREATE TABLE MIG_BASELINE ASSELECT COUNT(*) AS CNT, SUM(ORD_AMT) AS AMT,       COUNT(DISTINCT CUST_ID) AS CUSTS FROM ORDERS;-- 이관 후 같은 쿼리로 비교

작업 전에 기준선을 만들어 두는 것이 요령이다.
작업 후에 "원래 몇 건이었죠?"를 묻게 되면 이미 늦었다.

현장에서 만나는 모습

스키마 변경이 사고로 번지는 경로는 대개 둘이다.

첫째는 잠금이다. NOT NULL 을 추가하면 전체 행을 검사하느라 테이블이 잠기고, 그동안 그 테이블을 쓰는 모든 요청이 대기한다. 개발 DB 에서 0.2 초였던 것이 천만 건짜리 운영 테이블에서는 몇 분이 되고, 그 몇 분은 서비스 정지와 구분되지 않는다.

둘째는 순서다. 애플리케이션을 먼저 배포하면 아직 없는 컬럼을 읽다가 죽고, 스키마를 먼저 바꾸면 옛 애플리케이션이 새 제약을 위반한다. 그래서 "둘 다 동시에 배포" 는 계획이 아니다 — 어느 쪽이 먼저여도 견디도록 만들어 두는 것이 계획이다.

블루그린으로 이걸 해결하려는 시도도 자주 보는데, 두 색이 같은 DB 를 본다는 사실은 바뀌지 않는다. 애플리케이션은 무중단으로 바뀌어도 스키마는 그대로 한 벌이다.