스키마 변경
고친 사람 github-actions[bot]
스키마 변경은 데이터의 모양을 바꾸는 일입니다. 테이블에 열을 더하거나 필드 이름을 고치는 일이 모두 여기에 듭니다. 바꾸는 쪽은 한 곳이지만 그 데이터를 읽는 쪽은 여럿입니다. 읽는 쪽이 모르는 채로 모양이 바뀌면 멀리 떨어진 작업이 깨집니다.
쉽고 빠른 이해
무슨 일을 하는 물건인가 — 데이터가 담기는 틀을 고치는 일입니다. 주문 테이블의 price 열 이름을 amount 로 바꾸는 것이 한 예입니다.
왜 이렇게 하나 — 서비스가 자라면 담아야 할 정보도 바뀝니다. 그런데 그 데이터를 매일 밤 가져가 집계하는 작업은 옛 이름을 믿고 있습니다. 그래서 바꾸는 일보다 바꾼 뒤 누가 깨지는지가 더 큰 문제입니다.
어떻게 도나
- 바꾸려는 것이 열을 더하는 것인지, 빼거나 바꾸는 것인지 가립니다
- 더하는 변경은 대개 읽는 쪽을 안 깨뜨립니다. 빼거나 바꾸는 변경은 읽는 쪽에 먼저 알립니다
- 깨뜨리는 변경은 여러 번의 배포로 쪼개서 옛 열과 새 열이 한동안 함께 있게 합니다
대가 — 한 줄로 끝날 변경이 여러 단계로 늘어납니다. 읽는 쪽이 많을수록 바꾸기 전에 확인할 곳도 늘어납니다.
상세
회사가 내선 번호 체계를 새로 짰다고 해 봅시다. 총무팀은 하루 만에 사무실 전화기 번호를 다 바꿉니다. 그런데 거래처에 돌린 명함에도, 다른 층 벽에 붙은 비상 연락표에도 옛 번호가 적혀 있습니다. 누군가 그 번호로 전화를 걸기 전까지는 아무도 문제를 모릅니다.
스키마는 데이터에 어떤 항목이 있고 각 항목에 어떤 값이 오는지 정한 설계도입니다. 데이터베이스라면 테이블마다 열의 이름과 타입을 정한 것이 스키마입니다. 「주문 번호는 정수, 금액은 소수」 같은 약속이 그 예입니다.
스키마 변경은 이 설계도를 고치는 일입니다. 열을 더하는 것, 열을 지우는 것, 열 이름이나 타입을 바꾸는 것이 모두 스키마 변경입니다. JSON(JavaScript Object Notation) 메시지처럼 테이블이 아닌 데이터에서는 필드가 열과 같은 구실을 합니다.
데이터베이스에서 스키마를 바꾸는 명령은 따로 묶어 DDL(Data Definition Language, 데이터 정의 언어)이라고 부릅니다. 아래는 SQL(Structured Query Language)의 ALTER TABLE 문으로 열 이름을 바꾸는 한 줄입니다.
ALTER TABLE orders RENAME COLUMN price TO amount;
명령은 한 줄입니다. 금방 끝납니다. 이 편이 다루는 것은 그 한 줄 뒤에 벌어지는 일입니다.
더하는 변경과 깨는 변경
스키마 변경은 읽는 쪽에 미치는 영향으로 크게 갈립니다. 아래 표는 흔한 변경 넷을 그 영향과 함께 놓았습니다.
| 변경 | 그 데이터를 읽던 쪽에 생기는 일 |
|---|---|
| 열을 더한다 | 대개 아무 일도 없다. 모르는 열은 안 읽으면 그만이다 |
| 열을 지운다 | 그 열을 읽던 쪽이 깨진다 |
| 열 이름을 바꾼다 | 옛 이름으로 읽던 쪽이 깨진다 |
| 열 타입을 바꾼다 | 값을 해석하던 쪽이 깨지거나 틀린 값을 쓴다 |
읽는 쪽을 깨뜨리는 변경을 브레이킹 체인지라고 부릅니다. 표의 아래 세 줄이 여기에 듭니다.
열을 더하는 변경도 늘 안전하지는 않습니다. 열을 이름이 아니라 순서로 받는 코드는 열이 하나 늘면 값이 한 칸씩 밀립니다. SELECT * 로 가져와 몇 번째 값인지로 꺼내는 코드가 그렇습니다.
변경이 이어지는 동안 지켜야 할 방향은 둘입니다. 새 프로그램이 옛 데이터를 읽는 것과 옛 프로그램이 새 데이터를 읽는 것입니다. 이 두 방향을 어떻게 지키는지는 스키마 진화 편이 자세히 다룹니다. 여기서는 변경 하나가 어디까지 번지는지를 봅니다.
바꾸는 쪽과 읽는 쪽
스키마를 바꾸는 쪽은 대개 그 데이터를 만드는 서비스입니다. 주문 테이블이라면 주문을 받는 서비스와 그 팀입니다. 그 팀은 자기 코드에서 옛 이름을 찾아 고치고 함께 배포합니다. 자기 서비스 안에서는 아무것도 안 깨집니다.
그런데 같은 데이터를 읽는 쪽은 그 서비스 밖에도 있습니다. 매일 밤 주문 테이블을 복사해 가는 복사 작업, 그 복사본으로 매출을 더해 매출 테이블에 쌓는 집계 쿼리, 매출 테이블을 그리는 대시보드가 그렇습니다. 이들은 다른 팀이 만들었고 주문 서비스의 코드 저장소에 들어 있지도 않습니다. 앞의 내선 번호 이야기로 치면 주문 서비스 팀이 총무팀이고, 이 작업들이 명함과 비상 연락표입니다.
데이터를 원천에서 가져와 다듬고 쌓는 이런 작업의 사슬을 데이터 파이프라인이라고 부릅니다. 복사 작업, 집계 쿼리, 대시보드가 그 사슬의 칸 하나하나입니다.
파이프라인에서 데이터가 흘러오는 쪽을 상류, 흘러가는 쪽을 하류라고 합니다. 스키마 변경은 상류 한 곳에서 일어납니다. 그 영향은 하류 여러 곳에서 나타납니다.
데이터 엔지니어링에서 스키마 변경이 흔한 장애 원인이 되는 까닭이 이것입니다. 바꾼 팀은 하류에 누가 있는지 모르기 쉽습니다. 하류 팀은 상류가 언제 바뀌는지 모르기 쉽습니다.
하류로 번지는 길
열 이름 하나가 바뀌었을 때 하류가 어떻게 영향을 받는지 따라가 보겠습니다. 아래 그림은 주문 테이블의 price 가 amount 로 바뀐 뒤의 흐름입니다.
flowchart TD
A["주문 테이블 · price 를 amount 로 바꿈"] --> B["복사 작업 · price 를 찾다 멈춘다"]
B --> C["집계 쿼리 · 고쳐도 price 를 찾다 다시 멈춘다"]
C --> D["매출 테이블 · 갱신이 멈춘다"]
D --> E["대시보드 · 숫자가 안 바뀐다"]
복사 작업이 price 를 이름으로 찾도록 짜여 있으면 거기서 바로 멈춥니다. 복사 작업을 고쳐도 집계 쿼리가 다시 같은 이름을 찾다 멈춥니다. 고칠 곳이 사슬의 칸마다 하나씩 있는 셈입니다.
소리 내며 깨지는 경우와 조용히 깨지는 경우
깨지는 모습은 둘로 갈립니다. 하나는 작업이 오류를 내고 멈추는 경우입니다. 아래 쿼리는 이름이 바뀐 뒤의 주문 테이블에서 옛 이름을 찾습니다.
SELECT price FROM orders; -- 오류: 열 없음
이쪽은 그나마 알아차리기 쉽습니다. 작업이 실패했다는 알림이 옵니다. 누군가 원인을 찾아 고칩니다. 대가는 고치는 동안 하류의 숫자가 갱신되지 않는다는 것입니다.
다른 하나는 오류 없이 틀린 값이 흘러가는 경우입니다. 둘을 가르는 것은 없는 이름을 찾았을 때 오류를 내느냐, 빈 값을 돌려주느냐입니다. JSON 데이터를 다루는 코드는 아래처럼 없는 필드를 꺼내면 빈 값을 돌려주는 방식을 자주 씁니다.
row.get("price") # None
이 빈 값은 값이 없음을 뜻하는 널(null)이 되어 매출 테이블에 쌓입니다. 작업은 성공으로 끝났으니 알림도 오지 않습니다. 대시보드의 매출이 비어 보인다고 누군가 말할 때까지 아무도 모릅니다.
타입을 바꿀 때도 조용히 깨지기 쉽습니다. 정수였던 금액이 문자열로 바뀌면 읽는 쪽이 숫자로 못 바꾼 값을 버리거나 비워 둘 수 있습니다. 이렇게 틀린 값이 섞여 드는 문제는 데이터 품질로 이어집니다.
변경 데이터 캡처가 받는 스키마 변경
변경 데이터 캡처(Change Data Capture, CDC)는 데이터베이스에서 일어난 변경을 하나하나 이벤트로 뽑아 하류에 흘려보내는 방식입니다. 행 하나가 바뀔 때마다 「이 행이 이렇게 바뀌었다」는 이벤트가 생깁니다. 하류는 이 이벤트를 받아 자기 사본을 원천과 같게 맞춥니다.
이 방식에서는 스키마 변경이 이벤트 흐름 한가운데서 일어납니다. 변경 앞의 이벤트는 옛 모양입니다. 뒤의 이벤트는 새 모양입니다. 하류는 한 흐름 안에서 두 모양을 다 읽어야 합니다.
하류가 그 경계를 알아차리려면 이벤트마다 어느 스키마로 적혔는지가 함께 와야 합니다. 스키마를 버전마다 번호를 매겨 모아 두는 저장소를 스키마 레지스트리라고 합니다. 이벤트에는 스키마 버전 번호만 붙이고, 하류는 그 번호로 레지스트리에서 스키마 본문을 찾습니다.
바꾼 코드와 되돌린 코드
스키마 변경은 배포와도 얽힙니다. 서버 여러 대를 한 대씩 새 버전으로 바꾸는 롤링 업데이트 동안에는 옛 코드와 새 코드가 같은 테이블을 함께 씁니다. 그 사이에 열 이름을 바꾸면 옛 코드가 도는 서버의 조회가 오류를 냅니다.
되돌릴 때도 같은 문제가 생깁니다. 새 버전에 문제가 생겨 직전 버전으로 롤백하면 되돌아간 옛 코드는 이미 바뀐 스키마를 만납니다. 코드는 이전 버전을 다시 올리면 됩니다. 바뀐 스키마와 그사이 쌓인 데이터는 그렇게 쉽게 안 돌아갑니다.
데이터베이스 안의 비용
스키마 변경은 데이터베이스 자신에게도 일거리입니다. 데이터베이스는 테이블마다 어떤 열이 있고 각 열이 어떤 타입인지를 행 데이터와 따로 적어 둡니다. 어떤 변경은 이 기록만 고치고 금방 끝납니다.
어떤 변경은 테이블의 모든 행을 새 모양으로 다시 써야 합니다. 열 타입을 바꾸는 변경이 대개 이쪽입니다.
행을 다시 쓰는 동안 데이터베이스가 그 테이블에 쓰기를 막는 경우가 있습니다. 행이 수억 개인 테이블이면 그 시간이 길어지고 그동안 서비스가 주문을 못 받습니다. 어느 변경이 어느 쪽인지는 데이터베이스 제품과 버전마다 다릅니다.
쓰기를 막지 않고 바꾸는 방법도 있습니다. 새 모양의 테이블을 따로 만들어 행을 조금씩 옮겨 담습니다. 다 옮기면 두 테이블을 바꿔치기합니다. 이 방법을 온라인 스키마 변경이라고 부릅니다.
깨지지 않게 바꾸는 방법
이 소절은 하류를 깨뜨리지 않고 스키마를 바꾸는 방법을 견줍니다. 방법마다 막는 것이 달라서 여럿을 함께 씁니다.
| 방법 | 무엇을 하나 | 막는 것 |
|---|---|---|
| 더하는 변경만 하기 | 새 열은 없어도 되게 두고 기존 열은 건드리지 않는다 | 읽는 쪽이 깨지는 일 대부분 |
| 확장과 축소 | 새 열을 더하고, 두 열에 함께 쓰고, 읽는 쪽을 다 옮긴 뒤 옛 열을 지운다 | 옛 코드와 새 코드가 함께 도는 동안의 오류 |
| 데이터 계약 | 상류와 하류가 스키마를 약속으로 적어 두고 바꿀 때 미리 알린다 | 하류가 모르는 채로 바뀌는 일 |
| 등록 때 호환성 검사 | 새 스키마를 레지스트리에 올릴 때 옛 스키마와 두 방향으로 함께 읽히는지 보고 깨는 변경을 막는다 | 깨는 변경이 하류로 흘러나가는 일 |
| 적재 전 스키마 검사 | 들어온 데이터의 모양을 기대한 모양과 견주고 다르면 멈춘다 | 조용히 깨지는 일 |
확장과 축소는 이름 바꾸기 한 번을 여러 번의 배포로 쪼갭니다. 옛 열과 새 열이 한동안 함께 있으므로 옛 코드도 새 코드도 멀쩡히 돕니다. 대가는 한 줄로 끝날 변경이 여러 단계로 늘어난다는 것입니다.
적재 전 스키마 검사는 조용히 깨지는 경우를 소리 내며 깨지는 경우로 바꿉니다. 틀린 값을 쌓느니 작업을 멈추고 알림을 보내는 쪽을 고르는 것입니다.
이 검사가 찾아내는 어긋남에는 이름이 있습니다. 원천의 모양이 하류가 기대한 모양과 어긋난 상태를 스키마 드리프트라고 합니다. 하류에 알리지 않은 스키마 변경이 그 대표적인 원인입니다.
스키마 진화·스키마 마이그레이션과 가르기
이름이 비슷한 셋은 보는 범위가 다릅니다. 변경 하나인가, 그 변경을 적용하는 절차인가, 여러 변경에 걸친 일인가로 갈립니다. 아래 표가 셋을 가릅니다.
| 이름 | 가리키는 것 |
|---|---|
| 스키마 변경 | 스키마를 고치는 일 그 자체 |
| 스키마 마이그레이션 | 그 변경을 파일로 적어 두고 차례대로 적용하는 절차 |
| 스키마 진화 | 변경이 이어지는 동안 옛 데이터와 새 데이터, 옛 프로그램과 새 프로그램이 함께 돌게 하는 일 |
스키마 마이그레이션 파일 하나에는 스키마 변경이 하나 이상 들어 있습니다. 스키마 진화는 그런 변경이 여러 번 쌓여도 아무것도 깨지지 않게 하는 일입니다.
관련 항목
스키마 변경이 고치는 데이터 구성 요소
스키마 · 테이블 · 열 · 행 · 필드 · 타입 · 기본값 · 널
스키마 변경을 적용하는 명령과 절차
DDL · ALTER TABLE · SQL · 스키마 마이그레이션 · 데이터베이스 마이그레이션 · 온라인 스키마 변경 · 확장과 축소
스키마 변경이 하류를 깨지 않게 막는 장치
스키마 진화 · 하위 호환 · 상위 호환성 · 브레이킹 체인지 · 데이터 계약 · 스키마 레지스트리 · 호환성 검사
스키마 변경의 영향을 받는 하류 작업
데이터 파이프라인 · ETL · 변환 · 적재 · 변경 데이터 캡처 · 데이터 웨어하우스 · 대시보드
스키마 변경에서 자주 나는 장애와 뒤처리
스키마 드리프트 · 데이터 품질 · 태스크 실패 · 백필 · 재처리
스키마 변경과 얽히는 배포 방식
롤링 업데이트 · 롤백 · 무중단 배포 · 블루-그린 배포 · 지속적 전달
스키마 변경이 속하는 상위 분야
다른 이름: schema change · 스키마 수정