대형 테이블 ALTER 전 체크리스트: 용량, 시간, 복제 지연, rollback 계획
대형 MySQL 테이블 ALTER의 공간·작업 시간·복제 지연을 사전 평가하고, 중단 기준과 완료 후 되돌리기 계획을 구분한다.
대형 테이블의 ALTER TABLE은 SQL 한 문장이지만, 운영에서는 저장 공간·I/O·메타데이터 잠금·복제 적용·애플리케이션 호환성을 함께 바꾸는 작업이다. 실행 계획이 없는 상태에서 “Online DDL이므로 서비스 중 실행해도 된다”고 판단하면, 끝부분의 잠금 대기나 replica의 지연 때문에 변경 자체보다 큰 장애를 만들 수 있다.
이 글은 MySQL 8.0 이상 InnoDB에서 실행해도 되는 조건과 중단해야 하는 조건을 미리 정하는 방법을 다룬다. 알고리즘의 세부 비교보다는 변경 승인에 필요한 증거와 복구 경계를 중심으로 설명한다. 아래 SQL은 폐기용 MySQL 8.0 인스턴스에서 실행한 축소 예제다. 대형 테이블의 소요 시간, 실제 복제 지연, 취소 소요 시간, Aurora 동작을 측정한 결과는 아니다.
1. 먼저 변경 명세를 고정한다
승인 대상은 “인덱스 추가”라는 작업명이 아니라, 정확한 SQL·서버 버전·테이블 정의·데이터 분포·동시 부하의 조합이다. 같은 컬럼 변경도 자료형, 문자셋, 파티션, 외래 키, 기존 인덱스, 행 형식에 따라 허용 알고리즘과 비용이 달라진다.
| 실행 경로 | 주요 작업 | 사전 확인의 중심 |
|---|---|---|
INSTANT |
지원되는 변경을 메타데이터 중심으로 처리 | 정확한 버전과 테이블 제약, MDL 획득 가능성 |
INPLACE, 재구축 없음 |
예를 들어 보조 인덱스 생성 시 기존 행을 읽고 새 인덱스 작성 | 스캔·정렬·인덱스 공간, 동시 변경 추적, 마지막 잠금 |
INPLACE, 재구축 있음 |
새 물리 구조를 만들고 기존 데이터를 다시 조직 | 원본과 재구축 중 구조의 공존, 임시 파일, I/O |
COPY |
새 테이블로 행을 복사하고 구조 전환 | 추가 테이블 공간, 쓰기 제한, 전환 시점의 잠금 |
INPLACE는 “테이블 재구축 없음”과 동의어가 아니다. LOCK=NONE도 “잠금 없음”이 아니라 해당 작업에서 동시 읽기·쓰기를 허용한다는 요구다. 시작과 완료 단계의 메타데이터 잠금(MDL)은 별도로 고려한다.
정확한 SQL에 지원되는 ALGORITHM과 LOCK을 명시하면 허용하지 않은 경로로 조용히 실행되는 것을 막을 수 있다. 다만 이것은 실행 시 제약이지 비용을 알려 주는 dry run이 아니다. 운영 테이블에 시험 삼아 실행하지 말고, 동일 정의의 복원본에서 먼저 확인한다. INSTANT에 LOCK=NONE을 기계적으로 덧붙이지 않고 해당 알고리즘의 문법과 제약을 따른다.
flowchart TD
A[변경 SQL과 대상 버전 고정] --> B[복원본에서 알고리즘과 호환성 검증]
B --> C{공간·시간·복제·복구 증거 충족}
C -->|아니오 또는 미확인| D[실행 보류와 계획 수정]
C -->|예| E[짧은 MDL 대기 한도로 실행]
E --> F{서비스와 자원 한도 유지}
F -->|아니오| G[중단 판단 후 정리 완료 관찰]
F -->|예| H[DDL 완료와 replica 적용 확인]
H --> I[애플리케이션 검증 후 변경 종료]
G --> J[기존 정의와 데이터 재확인]
2. 용량은 테이블 크기의 배수가 아니라 파일 위치별로 계산한다
information_schema.TABLES의 DATA_LENGTH + INDEX_LENGTH는 InnoDB 데이터와 인덱스의 대략적인 규모를 파악하는 출발점이다. 정확한 백업 크기나 ALTER의 최대 추가 공간을 의미하지 않는다. TABLE_ROWS도 추정치이며, DATA_FREE를 운영체제에서 즉시 쓸 수 있는 여유 공간으로 계산해서는 안 된다.
공간 예산은 다음 항목을 실제 파일이 놓이는 파일시스템별로 분해한다.
- 재구축 대상 공간: 원본을 유지한 채 새 테이블이나 새 인덱스가 만들어지는 구간의 추가 공간이다. 행 형식·압축·인덱스 구성이 달라지면 원본과 크기가 같지 않다.
- 정렬 임시 공간: 인덱스 생성과 재구축 과정의 임시 정렬 파일이다.
tmpdir,innodb_tmpdir설정과 작업 종류별 실제 배치를 확인한다.innodb_tmpdir을 바꾼다고 모든 중간 테이블 파일이 이동하는 것은 아니다. - 동시 변경 및 로그 공간: online alter log, 동시 DML의 undo·binlog 증가, replica의 relay log 적체 등이다. Native DDL의 binlog에 재구축된 모든 행이 행 이벤트로 기록된다고 계산하지 않는다. shadow table 도구의 복사 DML은 다른 용량 모델이다.
- 업무 증가분과 안전 여유: 작업뿐 아니라 취소·정리 중에도 업무 데이터가 늘 수 있다. 경보가 울린 뒤 종료 판단까지 걸리는 시간도 반영한다.
원본이 이미 디스크를 사용 중이라면, 현재 free space와 비교하는 것은 앞으로 추가로 필요한 공간이다. 원본을 두 번 더하는 오류와, 새 구조를 만들 공간을 누락하는 오류를 모두 피한다. 서로 다른 마운트의 여유 용량을 합쳐 하나의 합격 숫자로 만드는 것도 잘못이다. Community MySQL에서는 실제 datadir·임시 경로의 df와 사용량 추이를 확인하고, 모든 replica에도 같은 검사를 적용한다.
innodb_online_alter_log_max_size는 동시 변경을 추적하는 임시 로그의 상한이지 ALTER 전체 공간 상한이 아니다. 너무 작으면 작업 중 실패할 수 있고, 크게 올리면 완료 단계에서 반영할 변경량이 늘어 잠금 시간이 길어질 수 있다. 단순 증설보다 피크 쓰기 부하를 낮추거나 실행 구간을 바꾸는 선택을 먼저 비교한다.
3. 시간 예산에는 완료뿐 아니라 취소와 복제 회복을 포함한다
대표 데이터의 복원본에서 동일 SQL을 실행하고, 읽기·쓰기 부하를 재현한 상태의 시간과 자원 사용량을 기록한다. 작은 테이블에서의 성공은 문법과 기능 검증이지, 대형 테이블 완료 시간의 근거가 아니다. 테이블 바이트 수만으로 선형 환산하면 정렬 spill, 캐시 효과, 인덱스 수, 동시 DML 차이를 놓친다.
작업 시간은 적어도 다음처럼 분리한다.
- 시작 MDL 획득 대기.
- 스캔·정렬·새 구조 작성.
- 동시 변경 반영과 최종 전환 MDL 획득.
- replica 적용 및 backlog 해소.
- 정의·쿼리·업무 검증.
변경 창에는 정상 완료 예산과 별도로 취소·정리 및 복구 판단 여유가 필요하다. 남은 시간이 최소 정리 예산보다 짧아진 뒤에야 취소를 결정하는 방식은 늦다. lock_wait_timeout은 MDL 대기를 제한할 뿐 전체 ALTER 실행 시간을 제한하지 않는다. innodb_lock_wait_timeout은 InnoDB 행 잠금 대기와 관련되므로 대체 수단이 아니다. 클라이언트 timeout이나 연결 종료도 서버 작업 종료의 증거가 되지 않는다.
다음 예제는 동일 파일시스템을 사용하는 재구축 작업의 가정 입력으로 승인 조건을 계산한다. rebuild_gib·sort_gib·growth_gib·reserve_gib는 서로 중복되지 않도록 잡은 추가 공간이다. work_min은 리허설로 정한 작업 예산, finish_min은 복제 회복과 검증 예산, cancel_min은 취소·정리 여유다. 이들을 더하는 것은 보수적인 계획 규칙이며 MySQL의 예측 공식은 아니다.
WITH inputs AS (
SELECT 'ready' AS scenario, 1200 AS free_gib,
600 AS rebuild_gib, 300 AS sort_gib, 60 AS growth_gib,
140 AS reserve_gib, 180 AS window_min, 100 AS work_min,
30 AS finish_min, 20 AS cancel_min, 1 AS lag_s, 2 AS lag_limit_s
UNION ALL SELECT 'space_short', 1000, 600, 300, 60, 140, 180, 100, 30, 20, 1, 2
UNION ALL SELECT 'time_short', 1200, 600, 300, 60, 140, 120, 100, 30, 20, 1, 2
UNION ALL SELECT 'lag_high', 1200, 600, 300, 60, 140, 180, 100, 30, 20, 5, 2
UNION ALL SELECT 'lag_unknown', 1200, 600, 300, 60, 140, 180, 100, 30, 20, NULL, 2
)
SELECT scenario,
rebuild_gib + sort_gib + growth_gib + reserve_gib AS need_gib,
work_min + finish_min + cancel_min AS need_min,
CASE
WHEN lag_s IS NULL OR lag_s < 0 THEN 'HOLD_UNKNOWN'
WHEN free_gib < rebuild_gib + sort_gib + growth_gib + reserve_gib
THEN 'HOLD_SPACE'
WHEN window_min < work_min + finish_min + cancel_min THEN 'HOLD_TIME'
WHEN lag_s > lag_limit_s THEN 'HOLD_LAG'
ELSE 'PASS_NUMERIC'
END AS decision
FROM inputs
ORDER BY scenario;
실행 결과(MySQL 8.0.x):
가독성을 위해 반복되는 CTE 명령문만 줄였으며 결과 행과 열은 모두 표시했다.
mysql> WITH inputs AS (...) SELECT ... FROM inputs ORDER BY scenario;
+-------------+----------+----------+--------------+
| scenario | need_gib | need_min | decision |
+-------------+----------+----------+--------------+
| lag_high | 1100 | 150 | HOLD_LAG |
| lag_unknown | 1100 | 150 | HOLD_UNKNOWN |
| ready | 1100 | 150 | PASS_NUMERIC |
| space_short | 1100 | 150 | HOLD_SPACE |
| time_short | 1100 | 150 | HOLD_TIME |
+-------------+----------+----------+--------------+
5 rows in set (0.00 sec)
PASS_NUMERIC은 제시한 수치 조건만 통과했다는 뜻이다. 백업 복원 증거, MDL blocker, 알고리즘 지원 여부, 업무 승인을 확인하지 않은 상태의 실행 허가는 아니다. 실제 자동화에서는 모든 필수 입력의 누락·음수·단위 오류를 차단하고, 한 가지 사유만 표시하지 말고 위반 조건 전체를 기록한다. 지연 미확인을 0으로 바꿔 통과시키지 않는 것이 중요하다.
4. 복제 지연은 source 완료와 별개의 종료 조건이다
비동기 binlog 복제에서는 source의 ALTER 완료 후 replica가 같은 DDL을 적용하는 데 다시 시간이 걸릴 수 있다. replica의 디스크·CPU가 작거나 읽기 트랜잭션이 MDL을 오래 잡으면 source보다 오래 걸린다. 병렬 worker 수를 늘리는 것만으로 DDL 적용이나 의존성 장벽이 사라지지 않는다.
작업 전에 모든 채널의 수신·적용 상태, 마지막 오류, 기존 지연, 승격 후보 여부를 기록한다. 작업 중에는 SHOW REPLICA STATUS와 Performance Schema의 replication 상태 테이블로 수신과 적용을 구분하고, 업무 heartbeat의 가시성이나 적용 GTID 경계로 교차 확인한다. Seconds_Behind_Source의 0만으로 최신 업무 데이터와 새 정의가 모든 replica에 보인다고 결론 내리지 않는다. NULL이나 수집 실패는 정상값이 아니다.
운영 기준에는 다음을 명시한다.
- 어느 replica의 지연이 얼마 동안 지속되면 reader 트래픽을 우회할 것인가.
- DDL 적용 전후 스키마가 다른 기간에 신·구 애플리케이션이 모두 동작하는가.
- 적용이 뒤처진 replica를 장애 시 자동 승격 대상에서 어떻게 처리할 것인가.
- relay log와 source binlog 보관이 예상 catch-up 기간을 감당하는가.
- source의 DDL이 이미 커밋된 뒤 replica가 지연되면 누가 부하 조절·읽기 우회·복구를 결정하는가.
이 글의 테스트 인스턴스에는 복제 채널이 없다. 따라서 복제 지연 수치를 실제 관측 결과로 제시하지 않는다. 앞의 계산에 사용한 lag_s도 운영 데이터가 아닌 가정 입력이다.
5. 실행 직전에는 MDL과 장기 트랜잭션을 다시 확인한다
몇 시간 전 승인 시점에 blocker가 없었다고 실행 시점에도 없다는 보장은 없다. 장기 트랜잭션이 MDL을 잡으면 ALTER가 기다리고, 대기 중인 배타 MDL 요청 때문에 뒤따르는 업무 쿼리까지 줄을 설 수 있다. LOCK=NONE만 보고 안전하다고 판단하기 어려운 이유다.
다음은 현재 선택한 DB의 테이블 MDL과 전체 InnoDB 장기 트랜잭션 후보를 조회한다. 운영에서는 승인된 진단 계정 권한으로 실행하고, 필요한 스키마·세션 범위로 좁힌다. MDL instrumentation이 비활성화되어 있으면 결과가 비어 있어도 안전의 근거가 되지 않는다.
SELECT NAME, ENABLED, TIMED
FROM performance_schema.setup_instruments
WHERE NAME = 'wait/lock/metadata/sql/mdl';
SELECT m.OBJECT_NAME, m.LOCK_TYPE, m.LOCK_STATUS,
t.PROCESSLIST_ID AS connection_id
FROM performance_schema.metadata_locks AS m
LEFT JOIN performance_schema.threads AS t
ON t.THREAD_ID = m.OWNER_THREAD_ID
WHERE m.OBJECT_TYPE = 'TABLE'
AND m.OBJECT_SCHEMA = DATABASE()
ORDER BY m.OBJECT_NAME, m.LOCK_STATUS
LIMIT 10;
SELECT trx_mysql_thread_id AS connection_id,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS age_s,
trx_state, trx_rows_modified
FROM information_schema.INNODB_TRX
ORDER BY trx_started
LIMIT 10;
실행 결과(MySQL 8.0.x):
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 m.OBJECT_NAME, m.LOCK_TYPE, m.LOCK_STATUS,
-> t.PROCESSLIST_ID AS connection_id
-> FROM performance_schema.metadata_locks AS m
-> LEFT JOIN performance_schema.threads AS t
-> ON t.THREAD_ID = m.OWNER_THREAD_ID
-> WHERE m.OBJECT_TYPE = 'TABLE'
-> AND m.OBJECT_SCHEMA = DATABASE()
-> ORDER BY m.OBJECT_NAME, m.LOCK_STATUS
-> LIMIT 10;
Empty set (0.00 sec)
mysql> SELECT trx_mysql_thread_id AS connection_id,
-> TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS age_s,
-> trx_state, trx_rows_modified
-> FROM information_schema.INNODB_TRX
-> ORDER BY trx_started
-> LIMIT 10;
Empty set (0.00 sec)
검증 환경의 Empty set은 해당 시점에 조회 대상이 없었다는 뜻이다. 실제 MDL 대기나 취소를 재현한 결과가 아니다. 읽기 전용 트랜잭션도 MDL을 보유할 수 있으므로 trx_rows_modified = 0을 무해하다는 뜻으로 해석하지 않는다. 차단 세션이 발견되면 소유자와 종료 가능성을 먼저 확인하고, 일괄 KILL로 변경 창을 만드는 방식은 피한다.
6. 작은 실험으로 확인할 수 있는 것: 정의·실행·되돌리기
다음 예제는 선택한 테스트 DB에 일반 테이블을 만들고 작은 데이터를 넣는다. 운영 업무 테이블 대신 빈 실습 DB에서 실행한다. 이후 SQL 블록은 이 테이블을 순서대로 사용한다.
CREATE TABLE ddl_preflight_demo (
id BIGINT NOT NULL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
amount DECIMAL(12,2) NOT NULL
) ENGINE=InnoDB;
INSERT INTO ddl_preflight_demo VALUES (1,10,100.00),(2,10,200.00),(3,20,300.00);
SELECT VERSION() AS mysql_version;
SELECT TABLE_NAME, ENGINE, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ddl_preflight_demo';
실행 결과(MySQL 8.0.x):
mysql> CREATE TABLE ddl_preflight_demo (
-> id BIGINT NOT NULL PRIMARY KEY,
-> tenant_id BIGINT NOT NULL,
-> amount DECIMAL(12,2) NOT NULL
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO ddl_preflight_demo VALUES (1,10,100.00),(2,10,200.00),(3,20,300.00);
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> SELECT VERSION() AS mysql_version;
+---------------+
| mysql_version |
+---------------+
| 8.0.46 |
+---------------+
1 row in set (0.00 sec)
mysql> SELECT TABLE_NAME, ENGINE, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH
-> FROM information_schema.TABLES
-> WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ddl_preflight_demo';
+--------------------+--------+------------+-------------+--------------+
| TABLE_NAME | ENGINE | TABLE_ROWS | DATA_LENGTH | INDEX_LENGTH |
+--------------------+--------+------------+-------------+--------------+
| ddl_preflight_demo | InnoDB | 3 | 16384 | 0 |
+--------------------+--------+------------+-------------+--------------+
1 row in set (0.00 sec)
테이블 통계는 갱신 시점과 표본에 영향을 받는다. 여기의 작은 값으로 공간 증폭률을 계산하지 않는다. 실제 작업에서는 SHOW CREATE TABLE로 외래 키·파티션·문자셋·인덱스 정의까지 보관하고, 파일 사용량 및 대표 부하 리허설과 대조한다.
다음은 MDL 대기 한도를 세션에 설정하고 보조 인덱스를 실제로 추가한다. 성공 후 인덱스의 컬럼 순서와 원래 데이터가 그대로인지 확인한다.
SET SESSION lock_wait_timeout = 5;
ALTER TABLE ddl_preflight_demo
ADD INDEX idx_tenant_amount (tenant_id, amount),
ALGORITHM=INPLACE, LOCK=NONE;
SELECT INDEX_NAME, SEQ_IN_INDEX, COLUMN_NAME
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'ddl_preflight_demo'
AND INDEX_NAME = 'idx_tenant_amount'
ORDER BY SEQ_IN_INDEX;
SELECT id, tenant_id, amount FROM ddl_preflight_demo ORDER BY id;
실행 결과(MySQL 8.0.x):
mysql> SET SESSION lock_wait_timeout = 5;
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE ddl_preflight_demo
-> ADD INDEX idx_tenant_amount (tenant_id, amount),
-> ALGORITHM=INPLACE, LOCK=NONE;
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SELECT INDEX_NAME, SEQ_IN_INDEX, COLUMN_NAME
-> FROM information_schema.STATISTICS
-> WHERE TABLE_SCHEMA = DATABASE()
-> AND TABLE_NAME = 'ddl_preflight_demo'
-> AND INDEX_NAME = 'idx_tenant_amount'
-> ORDER BY SEQ_IN_INDEX;
+-------------------+--------------+-------------+
| INDEX_NAME | SEQ_IN_INDEX | COLUMN_NAME |
+-------------------+--------------+-------------+
| idx_tenant_amount | 1 | tenant_id |
| idx_tenant_amount | 2 | amount |
+-------------------+--------------+-------------+
2 rows in set (0.00 sec)
mysql> SELECT id, tenant_id, amount FROM ddl_preflight_demo ORDER BY id;
+----+-----------+--------+
| id | tenant_id | amount |
+----+-----------+--------+
| 1 | 10 | 100.00 |
| 2 | 10 | 200.00 |
| 3 | 20 | 300.00 |
+----+-----------+--------+
3 rows in set (0.01 sec)
5초는 이 실습의 MDL 대기 설정이지 권장 운영값이 아니다. 또한 작은 데이터에서 LOCK=NONE이 허용되었음을 확인했을 뿐, 피크 부하에서의 무중단이나 최대 실행 시간을 보장하지 않는다.
인덱스 추가가 완료된 뒤의 되돌리기는 다음처럼 별도의 역방향 DDL이다. 같은 세션에서 ROLLBACK을 보내는 것이 아니다. 인덱스가 애플리케이션 힌트나 제약에 사용되는지 확인한 뒤 제거하고, 변경한 정의와 업무 데이터 모두를 재검증한다.
ALTER TABLE ddl_preflight_demo
DROP INDEX idx_tenant_amount,
ALGORITHM=INPLACE, LOCK=NONE;
SELECT COUNT(*) AS remaining_index_parts
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'ddl_preflight_demo'
AND INDEX_NAME = 'idx_tenant_amount';
SELECT id, tenant_id, amount FROM ddl_preflight_demo ORDER BY id;
DROP TABLE ddl_preflight_demo;
실행 결과(MySQL 8.0.x):
mysql> ALTER TABLE ddl_preflight_demo
-> DROP INDEX idx_tenant_amount,
-> ALGORITHM=INPLACE, LOCK=NONE;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SELECT COUNT(*) AS remaining_index_parts
-> FROM information_schema.STATISTICS
-> WHERE TABLE_SCHEMA = DATABASE()
-> AND TABLE_NAME = 'ddl_preflight_demo'
-> AND INDEX_NAME = 'idx_tenant_amount';
+-----------------------+
| remaining_index_parts |
+-----------------------+
| 0 |
+-----------------------+
1 row in set (0.00 sec)
mysql> SELECT id, tenant_id, amount FROM ddl_preflight_demo ORDER BY id;
+----+-----------+--------+
| id | tenant_id | amount |
+----+-----------+--------+
| 1 | 10 | 100.00 |
| 2 | 10 | 200.00 |
| 3 | 20 | 300.00 |
+----+-----------+--------+
3 rows in set (0.00 sec)
mysql> DROP TABLE ddl_preflight_demo;
Query OK, 0 rows affected (0.01 sec)
위 결과는 인덱스 추가와 제거 전후에 동일한 세 행이 유지됨을 확인한다. 실제 대형 DDL을 도중에 취소하는 시험이나 삭제 컬럼 복원 시험은 아니다.
7. rollback 계획은 실행 상태에 따라 세 갈래로 나눈다
실행 전 또는 잠금 대기 중
아직 실제 변경에 들어가지 않았다면 승인된 대기 한도에서 실패시키고 원인을 재평가한다. 즉시 재시도를 반복하면 업무 쿼리를 계속 대기시킬 수 있다. 세션 ID, 시작 시각, 대상 SQL을 기록해 다른 작업을 취소하지 않도록 한다.
DDL 실행 중
중단은 KILL QUERY 등의 취소 요청과, 서버가 수행하는 rollback·임시 구조 정리 완료를 구분해야 한다. 취소가 즉시 끝난다는 보장은 없고 I/O와 잠금 부담이 이어질 수 있다. 계획서에는 중단 결정권자, 정확한 대상 세션 확인, 정리 완료 확인, 원래 정의와 접근 가능성 검증을 포함한다. 운영체제에서 임시 파일을 직접 지우거나 강제 재시작으로 완료를 앞당기려 하지 않는다.
Native ALTER에는 임의 지점에서 안전하게 멈췄다가 이어 실행하는 일반적인 pause/resume 기능이 없다. 지속 부하가 허용 한도를 넘었다면 계속 실행과 취소 후 정리 중 어느 쪽이 더 안전한지 판단해야 한다. “지연이 커지면 잠깐 멈춘다”는 계획은 도구가 실제로 그 동작을 지원할 때만 유효하다.
DDL 커밋 후
MySQL 8.0의 지원되는 InnoDB DDL은 atomic DDL의 보호를 받지만, 이는 데이터 딕셔너리·스토리지 변경·binlog 기록의 일관된 완료 또는 복구를 위한 성질이다. 사용자 트랜잭션의 ROLLBACK으로 성공한 ALTER를 되돌릴 수 있다는 의미가 아니다. DDL의 implicit commit 때문에 업무 트랜잭션과 섞어 실행하는 것도 피한다.
인덱스 추가는 제거로 되돌릴 수 있어도, 컬럼 삭제·자료형 축소·문자셋 변환은 잃은 값을 역방향 DDL만으로 되살릴 수 없다. 이런 변경은 구형 컬럼을 유지하는 단계적 배포, 새 구조 검증, 호환성 전환, 최종 제거를 분리한다. 백업 복원은 별도 인스턴스 복원·선별 데이터 복구·전환 중 어떤 방식인지 정하고, 백업 이후 정상 업무 변경의 보존 방법과 RTO/RPO까지 정해야 실행 가능한 대안이 된다.
8. Aurora에서는 클러스터 저장소와 로컬 공간을 분리한다
Aurora MySQL의 저장소 자동 확장을 이유로 모든 DDL 공간 위험이 사라진다고 해석해서는 안 된다. 클러스터 볼륨과 인스턴스 로컬 임시 공간은 다른 자원이다. FreeLocalStorage는 인스턴스의 로컬 가용 공간을 나타낸다. 클러스터 공간 한계는 VolumeBytesUsed만 최대 크기와 비교하기보다 AuroraVolumeBytesLeftTotal도 확인한다.
Aurora MySQL 3과 8.4의 Instant DDL을 Aurora MySQL 2의 Fast DDL과 혼동하지 않는다. 정확한 엔진 버전과 지원 조건으로 실행 경로를 검증한다. reader의 AuroraReplicaLag는 밀리초 단위의 Aurora 복제 지표이며, Community binlog replica의 Seconds_Behind_Source와 단위·경로가 다르다. 외부 binlog 복제를 함께 사용한다면 두 경로를 따로 관측한다.
관리형 환경에서도 schema 변경 뒤 writer와 reader에서 대표 쿼리를 확인하고, 새 정의에 의존하는 애플리케이션 배포 시점을 통제한다. 이 글의 로컬 MySQL 결과는 Aurora의 저장 공간, reader 가시성, 취소 시간 또는 failover를 검증한 결과가 아니다.
9. 실행 승인과 종료 판정 체크리스트
실행 승인
종료 또는 중단 후 확인
결론
대형 ALTER의 사전 점검은 “디스크가 넉넉한가”라는 질문으로 끝나지 않는다. 어느 자원에 언제 부담이 생기며, 실패한 시점에서 어떤 상태로 복귀할 수 있는가를 증거로 연결해야 한다. 명시적인 알고리즘 제약, 위치별 공간 예산, 복제 완료 경계, 상태별 rollback 계획이 준비되어야 실행 승인이 의미를 갖는다. 다음 단계에서는 Native DDL로 만족시키기 어려운 가용성 요구를 shadow table 기반 스키마 변경 도구가 어떻게 해결하고, 어떤 추가 비용을 만드는지 비교할 수 있다.