Skip to content
isdnetworks
Go back

그 방식이면 멈추는 시간이 없었다

(수정됨)

수백만 건짜리 orders 에 컬럼을 추가해야 했고 그동안 서비스가 멈춘다고 보고했다. 예상 시간이 이십 분쯤이었으므로 점검 시간을 잡으려던 참이었다.

Table of contents

Open Table of contents

검증 — 잠기는지 확인

점검 안내를 내기 전에 정말 잠기는지부터 확인했는데 MySQL 8.0 참조 매뉴얼의 온라인 DDL 연산 표를 보니 컬럼 추가는 In Place 로 처리되고 동시 DML 을 허용해 잠기지 않았다.

ALTER TABLE orders ADD COLUMN memo TEXT, ALGORITHM=INPLACE, LOCK=NONE;

ALGORITHM 과 LOCK 을 명시하면 그 방식으로 못 할 때 오류가 나므로 조용히 잠기는 일이 없다. 시험 환경에서 먼저 걸어 보니 실제로 잠기지 않고 그대로 통과했다. 잠긴다는 것은 이 작업의 성질이 아니라 판과 변경 종류의 조합이 정하는 것이었다.

판단 기준 — 잠기는 변경과 안 잠기는 변경

이 참에 같은 표에서 무엇이 잠기는지 정리했다. 컬럼을 추가하는 것과 보조 인덱스를 추가하는 것과 컬럼 이름을 바꾸는 것은 동시 DML 을 허용하고, 컬럼 순서를 바꾸는 것은 테이블을 다시 만들지만 역시 허용하며, 컬럼 타입을 바꾸는 것과 기본 키를 지우는 것은 허용하지 않아 잠긴다.

잠기는 변경은 두 가지 방법이 있었는데 점검 시간을 잡고 ALTER 한 번으로 끝내는 것과 새 컬럼을 만들고 값을 나눠서 옮긴 뒤 읽는 쪽을 바꾸고 마지막에 옛 컬럼을 지우는 것이다. 뒤쪽을 골랐다. 이유는 점검 시간은 잡는 순간 그 시간 안에 끝나야 한다는 압박이 생겨서다. 대신 단계가 넷으로 늘고 그 사이 코드가 두 컬럼을 다 알아야 하는 것을 감수했다. 단계가 여럿이지만 각 단계가 짧아서 안 멈춘다.

값 채우기의 분할

컬럼을 추가한 뒤 값을 채우는 것도 한 번에 하면 오래 잠근다.

do {
    $n = DB::update("UPDATE orders SET memo = '' WHERE memo IS NULL LIMIT 5000");
    usleep(100000);
} while ($n > 0);

LIMIT 5000 으로 나누면 각각은 짧게 잠근다. 사이에 usleep() 을 넣은 것은 계속 돌면 다른 요청이 밀리기 때문이다. 부하가 높으면 더 쉬게 하고 정산 배치가 도는 시간에는 아예 안 돌게 했다.

진행 표시와 중단 지점

채워야 할 전체 건수를 COUNT(*) 로 미리 세어 두고 진행률을 화면에 찍게 했다. 이십 분 걸리는 것을 아무 표시 없이 기다리는 것과 몇 퍼센트인지 보면서 기다리는 것은 다르다.

if (file_exists('/tmp/stop-migration')) {
    echo "\n중단 요청. 여기까지 처리됨.\n";
    break;
}

/tmp/stop-migration 파일 하나로 멈출 수 있게도 했다. 중간에 멈춰도 다시 시작하면 남은 것부터 하므로 멈추는 비용이 낮아야 이상하다 싶을 때 실제로 멈춘다.

되돌릴 수 있는 구간

각 단계가 되돌릴 수 있는 것인지도 함께 따져 봤다. 컬럼을 추가한 것은 지우면 되지만 자료가 들어간 뒤에는 그 자료를 잃고, 값을 채운 것은 원래 비어 있었으므로 되돌릴 수 있다.

읽는 쪽을 바꾸고 나면 되돌리기 어려워지므로 그 단계 전에 확인을 충분히 했다. 어느 지점까지가 되돌릴 수 있는 구간인지 알고 있으면 그 앞에서 검증을 몰아서 한다.

사본에서 재 본 시간과 공간

시험 환경은 자료가 적어 시간이 안 맞았다. 수만 건에 십몇 초가 나왔는데 운영 자료를 복원한 사본에서는 수백만 건에 이십 분 가까이 걸렸고 예상과 비슷했다.

사본에서 재 보니 시간만 알게 된 것이 아니었다. 같은 매뉴얼의 온라인 DDL 공간 요구 항목대로 테이블을 다시 만드는 작업은 원본 테이블 크기만큼의 중간 파일 #sql-ib* 을 같은 디렉터리에 만들 수 있다는 것도 나왔다. 테이블이 십수 기가인데 df -h 로 본 남은 공간이 그보다 적었다. 미리 재 보지 않았으면 운영에서 중간에 실패했을 것이다. 공간을 늘리고 나서 실행했다.

작업하는 동안 curl 로 실제로 요청을 계속 보내면서 응답 시간도 함께 봤다. 한 번 느려진 구간이 있었지만 실패한 요청은 없었다. 안 멈춘다고 말하기 전에 요청을 보내 확인한 것이다.

정리


Share this post on:

Previous Post
규칙에 예외가 다섯 있었다
Next Post
부분 업데이트라고 믿은 PUT