Skip to content
난이도 면접 빈출문제은행 3

온라인 DDL 2 ​

온라인 DDL에서는 DDL(Data Definition Language, 데이터 정의어)이 도는 동안에도 조회와 쓰기를 받는 온라인 DDL(online DDL)의 세 방식을 봤다. 그 가운데 INSTANT는 테이블 정의만 고쳐 즉시 끝나고, INPLACE는 복사 없이 제자리에서 바꾼다. COPY는 새 구조의 테이블을 따로 만들어 모든 행을 복사하는 방식이라 도는 동안 쓰기를 막는다. 이 문서는 8.4에서 칼럼 추가·삭제가 INSTANT로 끝나는 조건, 여러 변경을 한 문장으로 묶을 때 주의할 점, 그리고 온라인이어도 서비스를 멈출 수 있는 메타데이터 락(metadata lock) 대기를 다룬다.

INSTANT의 조건 (8.4) ​

칼럼 추가는 8.0.12부터 INSTANT가 기본이 됐지만 테이블 맨 뒤에만 붙일 수 있었다. 8.0.29부터 아무 위치에나 추가할 수 있고, 칼럼 삭제도 INSTANT로 된다. 8.4에서 INSTANT 추가·삭제에는 조건이 있다.

  • 같은 문장에 INSTANT를 지원하지 않는 다른 변경을 섞을 수 없다.
  • 압축 행 포맷(row format)인 ROW_FORMAT=COMPRESSED 테이블, FULLTEXT 인덱스(전문 검색용 인덱스)가 있는 테이블, 임시 테이블(temporary table)에서는 안 된다.
  • INSTANT로 칼럼을 추가·삭제할 때마다 테이블의 행 버전(row version)이 하나씩 늘어난다. 행 버전은 "이 행이 어느 시점의 칼럼 구성으로 저장됐는지"를 구분하는 번호다. 8.4에서 최대 64개이고, 다 차면 INSTANT 추가·삭제가 오류로 거부된다. 현재 값은 INFORMATION_SCHEMA.INNODB_TABLES의 TOTAL_ROW_VERSIONS로 보고, 모든 행을 새로 쓰는 테이블 재구성(rebuild)을 하면 0으로 돌아간다.

COPY로 돌았는지는 완료 메시지의 영향받은 행 수로 알 수 있다. 행 수가 찍히면 새 테이블로 모든 행을 복사한 것이고, 0 rows affected면 COPY가 아니었다는 뜻이다. 다만 INPLACE도 작업에 따라 테이블을 재구성하므로, 0이라고 해서 가벼운 작업이라는 보장은 없다. 재구성 여부는 온라인 DDL의 작업별 비용 표로 판단한다. 큰 테이블이라면 같은 구조의 작은 테스트 테이블에 먼저 돌려 확인한다.

여러 변경은 묶어서, INSTANT는 따로 ​

MySQL은 한 ALTER TABLE에 ADD·DROP·CHANGE·MODIFY를 쉼표로 여러 개 적을 수 있다. 재구성이 필요한 변경 세 개를 따로 돌리면 테이블을 세 번 다시 쓰지만, 한 문장으로 묶으면 한 번만 쓴다.

주의할 조합은 INSTANT로 끝날 칼럼 추가와, 재구성은 없지만 INSTANT도 아닌 인덱스 추가를 묶는 경우다. 칼럼 추가는 INSTANT가 아닌 변경과 한 문장에 섞일 수 없으므로 문장 전체가 INPLACE로 돈다. 그런데 INPLACE로 칼럼을 추가하면 테이블을 재구성한다. 따로 돌렸으면 재구성이 전혀 없었을 작업이 테이블 전체를 다시 쓰는 작업이 된다. 그래서 INSTANT 변경은 따로 실행하고, 재구성이 필요한 변경끼리 한 문장으로 묶는다.

메타데이터 락 대기 — 온라인이어도 멈춘다 ​

온라인 DDL도 테이블 구조를 보호하는 메타데이터 락(metadata lock, MDL)은 피할 수 없다. 시작할 때는 공유 MDL을 잡는다. 작업에 따라 준비 단계에서 잠깐 배타 MDL을 잡기도 하고, 새 정의를 확정하는 마지막 단계에서는 반드시 배타 MDL을 잡는다. 배타 MDL은 그 테이블을 쓰고 있는 다른 트랜잭션(transaction)이 모두 끝나야 얻을 수 있다.

SELECT도 그 테이블에 공유 MDL을 잡는데, 이 잠금은 문장이 끝나도 트랜잭션이 끝날 때까지 남는다. 그래서 SELECT 한 번 하고 커밋(commit)하지 않은 채 놀고 있는 트랜잭션 하나가 ALTER를 멈추게 한다. 더 큰 문제는 기다리는 ALTER의 배타 MDL 요청이 뒤에 오는 모든 쿼리를 막는다는 것이다. 기다리는 배타 MDL 요청은 뒤에 온 공유 MDL 요청보다 먼저 처리되도록 우선권을 가지므로, 그 뒤에 온 SELECT도 ALTER가 끝날 때까지 기다린다. 몇 초 만에 커넥션이 쌓여 장애가 된다. 이 연쇄는 MySQL 엔진의 잠금의 그림으로 본다.

  • 먼저 긴 트랜잭션을 확인한다. DDL 직전에 열린 트랜잭션과 그 테이블의 MDL 보유자를 본다(온라인 DDL 3·온라인 DDL 4).
  • 대기 시간을 짧게 건다. MDL을 기다리는 한도인 lock_wait_timeout은 기본 31536000초(1년)다. DDL 세션에서 SET SESSION lock_wait_timeout = 5처럼 줄여 두면, 막힐 때 뒤 쿼리를 오래 붙잡지 않고 실패하므로 다시 시도하면 된다.
  • 도중 쓰기량을 본다. INPLACE는 도중에 들어온 변경을 임시 로그에 모아 두는데, 이 로그가 innodb_online_alter_log_max_size(기본 128MB)를 넘으면 ALTER가 실패한다. 쓰기가 몰리는 시간을 피한다.
  • 늦어지는 시간을 계산한다. 복제(replication)에서 레플리카(replica)는 원본 서버(소스, source)에서 DDL이 끝난 뒤에야 같은 DDL을 시작하고, 그 사이 소스에 들어온 변경은 레플리카의 DDL이 끝난 뒤 적용된다. 오래 걸리는 DDL은 그만큼 레플리카를 늦춘다.

면접 포인트 ​

  • "8.4에서 칼럼 추가는?" → 기본 INSTANT, 8.0.29부터 아무 위치·삭제도 INSTANT. 행 버전 64개 한도.
  • "온라인 DDL인데 서비스가 멈췄어요" → 배타 MDL 대기. 긴 트랜잭션이 막고, 대기 중인 DDL이 뒤 쿼리를 막는다. 긴 트랜잭션 확인 + 짧은 lock_wait_timeout.
  • 재구성 변경은 한 문장으로 묶고, INSTANT 변경은 섞지 않는다.

관련 문서