Instant ADD COLUMN의 조건과 한계: MySQL 8.0 DDL 최적화
MySQL Instant ADD COLUMN의 기본값 해석, 버전별 제약, 행 버전 한도와 MDL 대기를 실제 SQL로 확인하고 안전한 변경 절차를 정리한다.
컬럼 하나를 추가하는 작업도 대용량 테이블에서는 데이터 재작성, 추가 디스크 사용, 복제 지연을 유발할 수 있다. ALGORITHM=INSTANT는 지원되는 변경을 메타데이터 중심으로 처리하여 이 비용을 피한다. 그러나 데이터를 재작성하지 않는다는 사실과 즉시 응답한다는 보장은 다르다. 메타데이터 잠금 대기, 지원하지 않는 테이블 특성, 누적된 행 버전, 애플리케이션의 스키마 의존성이 여전히 변경의 성패를 결정한다.
이 글은 앞서 살펴본 Online DDL의 알고리즘과 잠금 구분에서 한 단계 더 들어가, Instant ADD COLUMN이 기존 행에 새 값을 어떻게 제공하는지와 언제 명시적으로 거부되는지를 설명한다. SQL 재현 기준은 Community MySQL 8.0.46의 InnoDB다. TOTAL_ROW_VERSIONS와 임의 위치 추가 예제는 8.0.29 이상을 전제로 한다. MySQL 8.4 및 Aurora에 관한 내용은 공식 문서상 조건이며, 여기서 해당 환경의 실행 성능을 측정한 것은 아니다.
1. 기존 행을 바꾸지 않고 새 컬럼을 읽는 원리
일반적인 테이블 재구축은 기존 레코드를 읽어 새 정의에 맞는 레코드로 작성한다. 반면 Instant ADD COLUMN은 기존 레코드마다 새 컬럼 값을 써 넣지 않고, 데이터 딕셔너리에 변경된 정의와 기존 행을 해석할 정보를 보관한다. 추가 당시 이미 존재하던 행을 읽을 때 InnoDB가 그 정보에 따라 누락된 컬럼의 값을 제공한다.
8.0.29 이후의 instant add/drop 구현은 행 버전과 컬럼 매핑을 이용해 서로 다른 레이아웃의 레코드를 하나의 현재 테이블 정의로 해석한다. 이 행 버전은 스키마 레이아웃 버전이다. MVCC의 트랜잭션 ID, Read View, undo 보존 기간과 같은 의미가 아니다.
flowchart TD
A[명시적 INSTANT 컬럼 추가] --> B{버전과 테이블 조건 충족}
B -->|아니오| C[오류 반환 - 재구축 자동 전환 차단]
B -->|예| D[필요한 메타데이터 잠금 획득]
D --> E[새 정의와 행 해석 정보 기록]
E --> F[기존 레코드는 일괄 재작성하지 않음]
F --> G[기존 행 읽기 - 추가 당시 기본값 해석]
E --> H[새 행 쓰기 - 현재 정의와 기본값 사용]
따라서 새 컬럼의 기본값을 나중에 변경해도 과거 행의 값을 소급해서 바꾸지는 않는다. 이후 INSERT가 해당 컬럼을 생략할 때 사용할 기본값만 달라진다. 기존 행의 업무 값을 바꾸려면 별도의 UPDATE가 필요하며, 그 작업은 undo·redo·행 잠금·binlog 비용을 발생시킨다. Instant DDL로 피한 테이블 재작성 비용이 대량 backfill에서 다시 나타날 수 있다.
2. 먼저 확인할 버전과 지원 조건
| 구분 | Instant ADD COLUMN 판단 |
|---|---|
| MySQL 8.0.12 이전 | 일반적인 InnoDB Instant ADD COLUMN 지원을 전제하지 않는다 |
| MySQL 8.0.12~8.0.28 | 지원 조건을 만족하면 마지막 위치에 추가할 수 있다. FIRST나 중간 AFTER는 같은 방식으로 처리할 수 없다 |
| MySQL 8.0.29 이상 | 임의 위치 추가 및 instant drop을 지원하며 TOTAL_ROW_VERSIONS로 누적 버전을 관측한다 |
| MySQL 8.4 | 해당 계열의 지원 조건을 적용한다. 행 버전 한도는 64이며 이후 메이저 버전의 한도를 가져오지 않는다 |
버전 숫자만으로 승인하지 않는다. 다음 조건도 함께 확인한다.
ROW_FORMAT=COMPRESSED테이블, FULLTEXT 인덱스가 있는 테이블, 데이터 딕셔너리 tablespace의 테이블은 instant 컬럼 추가 대상이 아니다.- 임시 테이블은 이 기능의 재현용 대체물이 아니다. 임시 테이블의 ALTER는
ALGORITHM=COPY만 지원한다. 시험에는 폐기 가능한 일반 InnoDB 테이블을 사용한다. - 컬럼 추가와 보조 인덱스 추가처럼 INSTANT를 지원하지 않는 작업을 한 ALTER에 묶으면 전체 문장을 INSTANT로 실행할 수 없다.
- AUTO_INCREMENT 컬럼 추가나 STORED generated column 추가를 일반 컬럼 추가와 같은 작업으로 취급하지 않는다. 작업별 Online DDL 지원표를 확인한다.
- 최대 행 크기, 내부 컬럼 수, 누적 행 버전 제한을 각각 점검한다. 현재 눈에 보이는 컬럼 개수만으로 모두 판단할 수 없다.
8.0.29 이상은 instant 컬럼 추가 시 최대 가능 행 크기를 검사한다. 이전 구현에서는 DDL이 통과해도 이후 INSERT·UPDATE에서 행 크기 문제를 만날 수 있었다. 또한 instant 추가 후 내부 표현의 컬럼 수가 1022를 초과할 수 없다는 제한은 일반 InnoDB 사용자 컬럼 한도를 대체하는 숫자가 아니다. instant drop 이력과 내부 표현을 고려한 별도 제약이다.
알고리즘을 생략하면 서버가 지원 가능한 다른 경로를 선택할 수 있다. 운영 변경서가 요구하는 것이 “재구축 없이 추가하거나 실패”라면 ALGORITHM=INSTANT를 명시해야 한다. 오류를 보고 무조건 알고리즘 절을 지워 재실행하는 것은 변경의 자원·잠금 계약을 바꾸는 행위다.
3. 재현: 과거 행의 값과 이후 INSERT 기본값
아래 예제는 빈 시험 스키마에서 실행한다. 첫 번째 행을 만든 다음 중간 위치에 컬럼을 추가하고, 기본값을 바꾼 뒤 두 번째 행을 입력한다. 각 SQL 블록은 자체 테이블을 생성하고 마지막에 정리한다. 아래 실행 결과는 실제 출력 중 준비·정리 부분을 줄인 발췌다.
CREATE TABLE instant_default_demo (
id BIGINT PRIMARY KEY,
amount INT NOT NULL
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
INSERT INTO instant_default_demo VALUES (1, 100);
ALTER TABLE instant_default_demo
ADD COLUMN state VARCHAR(12) NOT NULL DEFAULT 'new' AFTER id,
ALGORITHM=INSTANT;
ALTER TABLE instant_default_demo
ALTER COLUMN state SET DEFAULT 'queued', ALGORITHM=INSTANT;
INSERT INTO instant_default_demo (id, amount) VALUES (2, 200);
SELECT id, state, amount FROM instant_default_demo ORDER BY id;
SELECT COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'instant_default_demo'
ORDER BY ORDINAL_POSITION;
SELECT TOTAL_ROW_VERSIONS
FROM information_schema.INNODB_TABLES
WHERE NAME = CONCAT(DATABASE(), '/instant_default_demo');
DROP TABLE instant_default_demo;
실행 결과(MySQL 8.0.x):
mysql> ALTER TABLE instant_default_demo
-> ADD COLUMN state VARCHAR(12) NOT NULL DEFAULT 'new' AFTER id,
-> ALGORITHM=INSTANT;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE instant_default_demo
-> ALTER COLUMN state SET DEFAULT 'queued', ALGORITHM=INSTANT;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> INSERT INTO instant_default_demo (id, amount) VALUES (2, 200);
Query OK, 1 row affected (0.00 sec)
mysql> SELECT id, state, amount FROM instant_default_demo ORDER BY id;
+----+--------+--------+
| id | state | amount |
+----+--------+--------+
| 1 | new | 100 |
| 2 | queued | 200 |
+----+--------+--------+
2 rows in set (0.01 sec)
mysql> SELECT COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT
-> FROM information_schema.COLUMNS
-> WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'instant_default_demo'
-> ORDER BY ORDINAL_POSITION;
+-------------+------------------+----------------+
| COLUMN_NAME | ORDINAL_POSITION | COLUMN_DEFAULT |
+-------------+------------------+----------------+
| id | 1 | NULL |
| state | 2 | queued |
| amount | 3 | NULL |
+-------------+------------------+----------------+
3 rows in set (0.00 sec)
mysql> SELECT TOTAL_ROW_VERSIONS
-> FROM information_schema.INNODB_TABLES
-> WHERE NAME = CONCAT(DATABASE(), '/instant_default_demo');
+--------------------+
| TOTAL_ROW_VERSIONS |
+--------------------+
| 1 |
+--------------------+
1 row in set (0.00 sec)
해석할 핵심은 다음과 같다.
id=1의state는 추가 당시 기본값인new다. 현재 기본값을queued로 바꾸었다고 과거 행이 바뀌지 않는다.- 컬럼을 생략한 새로운
id=2행에는queued가 들어간다. 현재 기본값은information_schema.COLUMNS에서도 확인한다. AFTER id는 현재 논리적 컬럼 순서를 바꾼다. 기존 레코드 전체를 그 순서로 재작성했다는 뜻이 아니다.TOTAL_ROW_VERSIONS는 1이다. add/drop을 하지 않는 단순 기본값 변경이 add/drop 행 버전을 하나 더 소비하는 것은 아니다.
이 차이는 배포 호환성에도 중요하다. 컬럼 목록을 생략한 INSERT, SELECT * 결과를 위치로 해석하는 코드, 고정 필드 수를 기대하는 ETL은 서버의 DDL 성공과 무관하게 실패할 수 있다. 애플리케이션은 명시적 컬럼 목록을 사용하고, 구버전·신버전이 함께 동작하는 기간의 기본값과 NULL 의미를 먼저 합의해야 한다.
4. 누적 행 버전은 컬럼 수가 아니라 변경 이력이다
8.0.29 이상에서 컬럼을 추가하거나 제거하는 성공한 ALTER TABLE ... ALGORITHM=INSTANT 한 번은 새로운 행 버전을 만든다. 같은 문장에서 컬럼 여러 개를 추가해도 버전은 하나 증가한다. 반대로 add/drop을 작은 변경마다 반복하면 현재 컬럼 수가 적어도 버전 한도에 접근할 수 있다.
다음 예제는 한 문장에서 두 컬럼 추가 → 한 컬럼 제거 → 재구축 순서로 진행한다.
CREATE TABLE instant_versions_demo (
id INT PRIMARY KEY, amount INT NOT NULL
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
INSERT INTO instant_versions_demo VALUES (1, 100), (2, 200);
ALTER TABLE instant_versions_demo
ADD COLUMN state VARCHAR(8) NOT NULL DEFAULT 'new',
ADD COLUMN retry_count INT NOT NULL DEFAULT 0,
ALGORITHM=INSTANT;
SELECT 'add_two' AS phase, TOTAL_ROW_VERSIONS
FROM information_schema.INNODB_TABLES
WHERE NAME = CONCAT(DATABASE(), '/instant_versions_demo');
ALTER TABLE instant_versions_demo DROP COLUMN retry_count, ALGORITHM=INSTANT;
SELECT 'drop_one' AS phase, TOTAL_ROW_VERSIONS
FROM information_schema.INNODB_TABLES
WHERE NAME = CONCAT(DATABASE(), '/instant_versions_demo');
ALTER TABLE instant_versions_demo FORCE, ALGORITHM=INPLACE, LOCK=NONE;
SELECT 'rebuilt' AS phase, TOTAL_ROW_VERSIONS
FROM information_schema.INNODB_TABLES
WHERE NAME = CONCAT(DATABASE(), '/instant_versions_demo');
SELECT id, amount, state FROM instant_versions_demo ORDER BY id;
DROP TABLE instant_versions_demo;
실행 결과(MySQL 8.0.x):
mysql> ALTER TABLE instant_versions_demo
-> ADD COLUMN state VARCHAR(8) NOT NULL DEFAULT 'new',
-> ADD COLUMN retry_count INT NOT NULL DEFAULT 0,
-> ALGORITHM=INSTANT;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SELECT 'add_two' AS phase, TOTAL_ROW_VERSIONS
-> FROM information_schema.INNODB_TABLES
-> WHERE NAME = CONCAT(DATABASE(), '/instant_versions_demo');
+---------+--------------------+
| phase | TOTAL_ROW_VERSIONS |
+---------+--------------------+
| add_two | 1 |
+---------+--------------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE instant_versions_demo DROP COLUMN retry_count, ALGORITHM=INSTANT;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SELECT 'drop_one' AS phase, TOTAL_ROW_VERSIONS
-> FROM information_schema.INNODB_TABLES
-> WHERE NAME = CONCAT(DATABASE(), '/instant_versions_demo');
+----------+--------------------+
| phase | TOTAL_ROW_VERSIONS |
+----------+--------------------+
| drop_one | 2 |
+----------+--------------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE instant_versions_demo FORCE, ALGORITHM=INPLACE, LOCK=NONE;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SELECT 'rebuilt' AS phase, TOTAL_ROW_VERSIONS
-> FROM information_schema.INNODB_TABLES
-> WHERE NAME = CONCAT(DATABASE(), '/instant_versions_demo');
+---------+--------------------+
| phase | TOTAL_ROW_VERSIONS |
+---------+--------------------+
| rebuilt | 0 |
+---------+--------------------+
1 row in set (0.00 sec)
mysql> SELECT id, amount, state FROM instant_versions_demo ORDER BY id;
+----+--------+-------+
| id | amount | state |
+----+--------+-------+
| 1 | 100 | new |
| 2 | 200 | new |
+----+--------+-------+
2 rows in set (0.00 sec)
두 컬럼 추가 후 1, 한 컬럼 제거 후 2, 테이블 재구축 후 0이 되는지를 확인한다. 보조 인덱스만 추가하는 INPLACE 작업과 테이블을 재구축하는 INPLACE 작업을 구별해야 한다. 모든 INPLACE DDL이 행 버전을 초기화하는 것은 아니다.
8.0/8.4의 최대 64개 행 버전에 도달하면 다음 instant add/drop은 거부된다. 대응은 즉석에서 한도를 올리는 것이 아니라 재구축을 계획하거나 변경 방식을 재설계하는 것이다. 위의 FORCE는 작은 재현 테이블에서 원리를 확인하기 위한 실제 재구축이다. 운영 대형 테이블에서는 디스크 여유, I/O, online alter log, 복제 적용 시간, 종료 시 MDL 확보를 별도로 검토해야 한다. 버전 숫자가 높다는 이유만으로 즉시 실행할 유지보수 명령은 아니다.
서로 호환되는 컬럼 변경을 한 문장에 묶으면 버전 소비를 줄일 수 있다. 다만 인덱스 추가처럼 다른 알고리즘이 필요한 작업까지 묶으면 오히려 INSTANT 조건을 잃는다. “변경을 묶기”보다 “같은 알고리즘 계약을 지키는 변경만 묶기”가 정확한 기준이다.
5. 거부 조건을 실제로 확인한 결과
위의 정상 SQL과 별도로 동일한 폐기용 MySQL 8.0.46 서버에서 실패 경계를 시험했다. 예상 실패는 성공 예제와 분리하여 클라이언트 종료 코드와 변경 후 정의까지 확인하는 방식으로 검증한다.
| 시험 조건 | 실제 관측 결과와 의미 |
|---|---|
| 작은 테이블에서 instant ADD/DROP을 반복해 64개 행 버전 도달 | 다음 INSTANT ADD가 ERROR 4092로 거부되었다. 새 컬럼은 없고 기존 행은 보존되었다 |
ROW_FORMAT=COMPRESSED 테이블에 INSTANT ADD |
ERROR 1845로 거부되었고 새 컬럼이 생기지 않았다 |
| 컬럼 추가와 인덱스 추가를 한 ALTER에서 INSTANT로 요청 | ERROR 1845로 거부되었고 컬럼·인덱스 모두 생기지 않았다 |
| 다른 연결이 읽기 트랜잭션의 MDL을 유지하는 동안 INSTANT ADD | EXCLUSIVE / PENDING을 관측했다. lock_wait_timeout=3에서 ERROR 1205가 발생하고 새 컬럼은 없었다. 읽기 연결의 COMMIT 후 같은 ADD가 성공했으며 기존 행의 새 컬럼 값은 지정한 기본값 7이었다 |
이 시험은 조건별 동작을 확인하는 축소 실험이다. 대용량 테이블의 시간, 동시 처리량, replica 지연, Aurora의 동작을 측정한 결과로 확대하지 않는다. 특히 오류 번호만 같다고 원인이 같은 것은 아니다. 행 버전 한도와 최대 행 크기는 모두 오류 문구 전체 및 테이블 상태와 함께 판정한다.
6. INSTANT에도 잠금 대기와 서비스 영향은 남는다
Instant DDL도 정의를 안전하게 교체하기 위한 메타데이터 잠금이 필요하다. 다른 연결이 해당 테이블을 사용한 트랜잭션을 열어 두면, 현재 실행 중인 쿼리가 없어도 트랜잭션 종료까지 MDL이 유지될 수 있다. 이때 DDL의 작업 자체는 작아도 응답은 늦어진다. 대기 중인 DDL이 뒤따르는 쿼리의 진행에도 영향을 줄 수 있으므로 “기다리면 끝나겠지”라는 운영은 위험하다.
innodb_lock_wait_timeout은 InnoDB 행 잠금 대기의 설정이다. MDL 대기를 제한하려는 변경 세션에서는 lock_wait_timeout을 확인해야 한다. 이 값도 DDL 전체 실행 시간 제한이나 자동 롤백 시간 보장은 아니다. 아래는 진단 객체와 세션 설정을 확인하는 SQL이다. 연결을 종료하면 여기서 바꾼 세션 설정도 사라진다.
SET SESSION lock_wait_timeout = 5;
SELECT VERSION() AS mysql_version,
@@session.lock_wait_timeout AS mdl_wait_seconds,
@@session.innodb_lock_wait_timeout AS row_wait_seconds;
SELECT NAME, ENABLED, TIMED
FROM performance_schema.setup_instruments
WHERE NAME = 'wait/lock/metadata/sql/mdl';
SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS
FROM performance_schema.metadata_locks
WHERE OBJECT_TYPE = 'TABLE'
AND OBJECT_SCHEMA = DATABASE()
AND LOCK_STATUS = 'PENDING'
ORDER BY OBJECT_NAME, LOCK_TYPE
LIMIT 10;
실행 결과(MySQL 8.0.x):
mysql> SET SESSION lock_wait_timeout = 5;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT VERSION() AS mysql_version,
-> @@session.lock_wait_timeout AS mdl_wait_seconds,
-> @@session.innodb_lock_wait_timeout AS row_wait_seconds;
+---------------+------------------+------------------+
| mysql_version | mdl_wait_seconds | row_wait_seconds |
+---------------+------------------+------------------+
| 8.0.46 | 5 | 50 |
+---------------+------------------+------------------+
1 row in set (0.00 sec)
mysql> SELECT NAME, ENABLED, TIMED
-> FROM performance_schema.setup_instruments
-> WHERE NAME = 'wait/lock/metadata/sql/mdl';
+----------------------------+---------+-------+
| NAME | ENABLED | TIMED |
+----------------------------+---------+-------+
| wait/lock/metadata/sql/mdl | YES | YES |
+----------------------------+---------+-------+
1 row in set (0.00 sec)
mysql> SELECT OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS
-> FROM performance_schema.metadata_locks
-> WHERE OBJECT_TYPE = 'TABLE'
-> AND OBJECT_SCHEMA = DATABASE()
-> AND LOCK_STATUS = 'PENDING'
-> ORDER BY OBJECT_NAME, LOCK_TYPE
-> LIMIT 10;
Empty set (0.00 sec)
단일 연결의 정상 시험에서는 마지막 조회가 Empty set이어도 정상이다. 이것은 조회 시점에 조건에 맞는 대기가 없다는 뜻이지, 운영 서버에 잠금 위험이 없다는 증거가 아니다. 실제 대기가 있으면 metadata_locks.OWNER_THREAD_ID를 performance_schema.threads.THREAD_ID와 연결하고, 동일 객체의 GRANTED·PENDING 요청과 트랜잭션 수명을 함께 조사한다. 관측 권한과 instrumentation 활성화도 전제다.
LOCK=NONE을 INSTANT에 관성적으로 붙이지 않는다. INSTANT는 LOCK=DEFAULT만 허용하므로 보통 LOCK 절을 생략한다. 이미 대기 중인 DDL을 취소하는 것과 업무 트랜잭션을 강제 종료하는 것은 별도 판단이다. 후자는 업무 롤백과 재시도까지 고려해야 하며, Sleep 상태라는 이유만으로 연결을 종료하지 않는다.
7. Aurora MySQL과 복제 환경의 적용 판단
Aurora MySQL version 2의 Fast DDL과 version 3 및 8.4의 Instant DDL을 같은 구현으로 취급하지 않는다. AWS 공식 문서는 version 3/8.4의 instant 기능과 version 2의 별도 Fast DDL을 구분한다. Aurora에서는 클러스터의 정확한 엔진 버전과 해당 릴리스의 지원 조건을 먼저 확인하고, Community의 패치 버전 숫자만으로 기능을 단정하지 않는다.
스토리지가 관리형이라는 이유로 MDL이나 애플리케이션 호환성이 사라지지는 않는다. 대상과 같은 버전의 시험 클러스터에서 실제 ALTER, 장기 트랜잭션 동반 시 대기, writer·reader의 쿼리 호환성을 확인해야 한다. 이 글의 로컬 컨테이너 실험은 그 시험을 대신하지 않는다.
Community 비동기 복제에서도 source의 성공이 변경 완료의 전부는 아니다. DDL은 replica에서 적용되어야 하며, replica의 다른 테이블 상태·지원 조건·장기 조회로 지연 또는 오류가 생길 수 있다. source에서 행 버전을 정리했다고 모든 replica의 물리적 이력이 동일하다고 가정하지 말고 각 대상에서 확인한다. 컬럼을 읽는 새 애플리케이션 배포는 필요한 replica의 정의 반영까지 기다려야 한다.
8. 변경 승인과 중단 기준
실행 전
- 정확한 엔진·패치 버전과
TOTAL_ROW_VERSIONS
실행 중·완료 후
-
ALGORITHM=INSTANT
DDL은 일반 업무 트랜잭션의 ROLLBACK으로 되돌리는 작업이 아니다. 추가 컬럼을 이미 사용한 뒤 이를 제거하는 것은 새 DDL이자 데이터 손실 가능성이 있는 변경이다. 배포 롤백은 먼저 애플리케이션이 새 컬럼을 사용하지 않도록 되돌리는 절차와, 컬럼을 나중에 제거할지의 판단을 분리한다.
마무리
Instant ADD COLUMN의 이점은 데이터 크기에 비례하는 일괄 재작성 비용을 피하는 데 있다. 그 대가로 운영자는 버전별 지원 조건, 레코드 해석에 필요한 메타데이터 이력, 기본값의 시간적 의미를 이해해야 한다. 명시적 알고리즘, 제한된 MDL 대기, 실제 정의·값 검증을 하나의 변경 절차로 묶어야 “빠른 DDL”이 안전한 배포가 된다. 이후 대용량 스키마 변경 도구를 비교할 때도 같은 기준, 즉 데이터 이동 비용과 동기화 방식, 마지막 정의 전환의 잠금을 중심으로 판단할 수 있다.