Foundry
데이터베이스
심화
핵심

무중단 스키마 변경과 대량 작업

운영 중인 테이블의 구조를 바꾸거나 대량 데이터를 넣을 때 서비스를 멈추지 않는 방법

무중단 스키마 변경과 대량 작업

운영 중인 테이블을 건드릴 때는 잠금 시간과 잠금 범위를 먼저 계산한다

무엇이 위험한가

작업위험이유
NOT NULL 컬럼 추가높다기본값을 채우며 전체 행을 다시 쓴다
컬럼 타입 변경높다전체 행을 다시 쓴다
인덱스 추가중간기본 문법은 쓰기를 막는다
NULL 허용 컬럼 추가낮다최신 버전은 메타데이터만 바꾼다
컬럼 삭제낮다표시만 하고 공간은 나중에 정리
컬럼 이름 변경낮지만 위험빠르지만 배포 중 옛 코드가 깨진다

핵심은 잠금을 얼마나 오래 쥐는가입니다. 전체 행을 다시 쓰는 작업은 행수에 비례해서, 1억 행이면 수십 분입니다. 그동안 그 테이블에 대한 읽기와 쓰기가 막힙니다.

NOT NULL 컬럼을 안전하게 추가하는 순서

순서작업
1NULL 허용으로 컬럼을 추가한다
2애플리케이션이 새 값을 함께 쓰게 배포한다
3기존 행의 값을 나눠서 채운다
4NOT NULL 제약을 건다

3번을 한 문장으로 하지 않는 것이 중요합니다. 1억 행을 한 번에 갱신하면 트랜잭션이 커지고 잠금이 길어집니다. 1만 건씩 나눠 돌리면서 사이에 짧은 휴식을 둡니다.

컬럼 이름을 바꿀 때

빠르지만 위험합니다. 배포 중에는 옛 코드와 새 코드가 함께 도는데, 옛 코드는 옛 이름을 찾습니다.

순서작업
1새 이름의 컬럼을 추가한다
2두 컬럼에 모두 쓰도록 배포한다
3기존 데이터를 복사한다
4읽기를 새 컬럼으로 바꿔 배포한다
5옛 컬럼 쓰기를 멈추고 배포한다
6옛 컬럼을 지운다

번거롭지만 각 단계에서 옛 코드와 새 코드가 함께 동작합니다.

대량 삽입

한 건씩 넣으면 왕복과 트랜잭션 비용이 건수만큼 듭니다.

방법100만 건 기준
한 건씩 삽입왕복 100만 번. 매우 느리다
묶음 삽입1,000건씩 묶으면 왕복 1,000번
대량 적재 명령파일에서 직접 읽어 가장 빠르다
인덱스 나중에 만들기적재 후 인덱스를 만들면 빠르다

주의할 점도 있습니다.

항목내용
트랜잭션 크기100만 건을 한 트랜잭션에 넣으면 로그가 커지고 되돌리기가 비싸다
잠금큰 삽입이 다른 쓰기를 방해할 수 있다
복제 지연대량 변경이 복제본에 전파되며 지연을 만든다
실패 처리중간에 실패하면 어디까지 됐는지 알 수 있게 한다

대량 삭제

DELETE 는 행을 하나씩 지우고 로그를 남깁니다. 전체를 지운다면 다른 방법이 낫습니다.

방법특징
DELETE조건부 삭제. 느리고 로그가 크다. 되돌릴 수 있다
TRUNCATE전체 삭제. 빠르지만 조건을 걸 수 없다
파티션 삭제오래된 파티션을 떼어낸다. 가장 빠르다
나눠서 삭제조건부로 조금씩. 운영 중에는 이 방법
면접에서 이렇게 나옵니다

Q.운영 중인 테이블에 NOT NULL 컬럼을 추가하려면 어떻게 하나요?

한 번에 하지 않고 NULL 허용으로 추가한 뒤 채우고 제약을 거는 순서로 나눕니다.

순서작업이유
1NULL 허용으로 컬럼 추가메타데이터만 바뀌어 빠르다
2새 값을 함께 쓰도록 배포이후 들어오는 행은 값이 있다
3기존 행을 나눠서 채운다한 번에 하면 잠금이 길다
4NOT NULL 제약을 건다이미 값이 다 있어 검사가 빠르다

한 번에 하면 무엇이 문제인지 보면 이렇습니다. 기본값이 있는 NOT NULL 컬럼을 추가하면 DB 가 모든 행에 그 값을 채워야 하고, 그동안 테이블 전체가 잠깁니다. 1억 행이면 수십 분이고 서비스가 멈춥니다.

3번을 나누는 방법입니다.

항목내용
묶음 크기1만 건 정도. 한 트랜잭션이 짧게 끝나도록
사이 휴식짧게 쉬어 다른 쿼리가 끼어들 틈을 준다
진행 추적마지막 처리 id 를 기록해 재시작할 수 있게

흔한 실수: 최신 DB 버전은 기본값 추가가 빠르다는 것만 알고 그대로 실행하는 것. 버전과 조건에 따라 다르고, 조건을 벗어나면 전체를 다시 씁니다. 실행 전에 그 버전의 동작을 확인해야 합니다.

Q.100만 건을 DB 에 넣어야 합니다. 어떻게 하나요?

한 건씩 넣지 않고 묶어서 보냅니다. 왕복 횟수가 그대로 시간이 됩니다.

방법왕복상대적 속도
한 건씩100만 번가장 느리다
1,000건씩 묶음1,000번크게 빨라진다
대량 적재 명령1번가장 빠르다

함께 볼 것들입니다.

항목판단
트랜잭션 크기전체를 한 트랜잭션으로 묶지 않는다. 로그가 커지고 실패 시 전체를 되돌린다
인덱스적재 후에 만드는 편이 빠르다. 운영 중이면 불가
잠금과 복제대량 변경이 다른 쓰기를 방해하고 복제 지연을 만든다
재시작어디까지 넣었는지 기록해 중단 후 이어갈 수 있게
유입 속도 조절운영 중이면 초당 건수에 상한을 둔다

운영 중과 초기 적재는 판단이 다릅니다. 초기 적재라면 인덱스를 나중에 만들고 최대 속도로 넣습니다. 운영 중이라면 속도를 낮추고 나눠서 넣는 편이 안전합니다.

흔한 실수: 반복문 안에서 커밋을 매번 하는 것. 트랜잭션 시작과 커밋이 100만 번 일어나 그 비용이 삽입보다 커집니다.

Q.컬럼 이름을 바꾸는 것이 왜 위험한가요?

DB 작업 자체는 빠르지만, 배포 중에 옛 코드와 새 코드가 함께 돌기 때문입니다.

시점옛 코드새 코드
이름 변경 직후옛 이름을 찾아 오류정상
배포 완료 후없음정상

배포는 순간이 아닙니다. 서버를 하나씩 교체하는 동안 두 버전이 공존하고, 그 사이 옛 버전의 요청이 전부 실패합니다. 되돌리기도 어렵습니다. 이전 버전으로 롤백하면 그것도 옛 이름을 찾습니다.

안전한 순서입니다.

순서작업
1새 이름의 컬럼을 추가한다
2두 곳에 모두 쓰도록 배포한다
3기존 데이터를 복사한다
4읽기를 새 컬럼으로 바꿔 배포한다
5옛 컬럼 쓰기를 멈추고 배포한다
6옛 컬럼을 지운다

번거롭지만 각 단계에서 두 버전이 함께 동작합니다. 이 원칙은 컬럼 삭제와 타입 변경에도 같이 적용됩니다.

흔한 실수: 배포 시간을 짧게 잡아 문제가 없다고 보는 것. 짧아도 그 사이 요청은 실패하고, 롤백 경로가 막히는 것은 시간과 무관합니다.

Q.대량 삭제를 어떻게 하나요?

목적에 따라 방법이 다릅니다. 전체인지 조건부인지, 운영 중인지가 갈림길입니다.

방법조건속도되돌리기
DELETE걸 수 있다느리다. 행마다 로그트랜잭션 안에서 가능
TRUNCATE없다. 전체매우 빠르다어렵다
파티션 떼어내기파티션 단위가장 빠르다파티션을 보관하면 가능
나눠서 DELETE걸 수 있다느리지만 안전가능

운영 중에는 네 번째를 씁니다. 1억 건을 한 문장으로 지우면 잠금이 길고 로그가 폭증하며 복제 지연이 커집니다. 1만 건씩 나눠 지우면서 사이에 쉬어야 합니다.

정기적으로 오래된 데이터를 지우는 요구라면 애초에 파티션으로 설계하는 것이 답입니다. 월별 파티션을 두면 지난달 데이터를 지우는 것이 파티션 하나를 떼는 일이 됩니다.

함께 볼 것내용
공간 회수DELETE 는 공간을 즉시 돌려주지 않는다. 정리 작업이 필요하다
인덱스삭제도 인덱스를 갱신하므로 인덱스가 많으면 느리다
외래 키참조하는 테이블이 있으면 검사 비용이 붙는다

흔한 실수: 조건부 삭제에 TRUNCATE 를 쓰려 하는 것. 조건을 걸 수 없어 전체가 사라집니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

더 깊이 공부하기

읽었으면 문제로 확인해보세요

데이터베이스 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.