카테고리 : MySQL/기술노트

Online DDL 개요: ALGORITHM=COPY/INPLACE/INSTANT의 차이

MySQL Online DDL의 COPY·INPLACE·INSTANT 실행 방식과 재구축·동시성·메타데이터 잠금의 차이를 검증 예제와 운영 기준으로 정리한다.

저자: MySQL 기술 노트 작성: 2026.10.08 약 11분 6,143자
다운로드

Online DDL 개요: ALGORITHM=COPY/INPLACE/INSTANT의 차이

운영 중인 테이블에 컬럼이나 인덱스를 추가할 때 중요한 것은 문장이 짧은지가 아니다. 기존 데이터를 다시 쓰는지, 쓰기 요청이 계속 처리되는지, 마지막 정의 변경을 위해 얼마나 기다리는지가 서비스 영향을 결정한다. Online DDL은 이런 영향을 줄이는 기능의 묶음이지, 잠금과 부하가 없다는 보증이 아니다.

이 글은 InnoDB를 기준으로 세 알고리즘의 실행 경로를 구분하고, 명시적인 옵션으로 예상하지 못한 무거운 작업을 차단하는 방법을 설명한다. SQL은 폐기용 MySQL 8.0.46 인스턴스에서 실행했다. TOTAL_ROW_VERSIONS를 사용하는 관측 예제는 MySQL 8.0.29 이상을 대상으로 한다. 8.4 LTS에서도 작업별 지원표를 확인해야 하며, 아래 작은 데이터 실험을 대용량 성능이나 운영 중 동시 쓰기 처리량 시험으로 해석해서는 안 된다.

1. 알고리즘·재구축·동시성은 서로 다른 질문이다

DDL을 검토할 때 다음 세 질문을 분리한다.

  1. 어떤 경로로 수행하는가? ALGORITHM이 지정하는 실행 방식이다.
  2. 기존 테이블 데이터를 다시 구성하는가? 클러스터드 인덱스 재구축 여부다.
  3. 수행 중 다른 세션이 읽거나 쓸 수 있는가? 작업별 동시성 지원과 LOCK 옵션의 문제다.
알고리즘 주된 실행 방식 기존 테이블 재구축 동시 DML에 대한 해석
COPY 새 정의의 테이블로 기존 행을 복사한 뒤 교체 수행 일반적인 InnoDB COPY 작업은 쓰기를 차단한다. 읽기 허용 여부는 잠금 수준과 단계에 달려 있다.
INPLACE 서버 계층의 COPY 경로 대신 InnoDB가 변경 수행 작업에 따라 수행하거나 생략 일부 작업은 LOCK=NONE을 지원하지만 모든 INPLACE 작업이 그런 것은 아니다.
INSTANT 지원되는 정의 변경을 메타데이터 중심으로 처리 해당 변경을 위해 기존 행을 다시 쓰지 않음 동시 DML을 허용하더라도 정의 변경용 메타데이터 잠금은 필요하다.

INPLACE를 “원래 데이터 파일의 바이트 몇 개만 수정한다”로 읽으면 안 된다. 보조 인덱스 추가는 원본 클러스터드 인덱스를 재구축하지 않지만, 새 인덱스를 만들기 위해 데이터를 읽고 정렬하고 페이지를 작성한다. 반대로 FORCE나 일부 컬럼 변경은 INPLACE여도 테이블 전체를 재구축한다.

LOCK=NONE은 지원되는 작업의 주 실행 구간에 동시 읽기와 쓰기를 허용하라는 요구다. 메타데이터 잠금까지 없애라는 의미가 아니다. LOCK=SHARED는 읽기를 허용하고 쓰기를 차단하며, LOCK=EXCLUSIVE는 읽기와 쓰기를 모두 차단한다. LOCK=DEFAULT는 가능한 동시성을 선택하지만, 작업에 따라 그 결과가 달라진다.

2. 세 경로의 내부 작업과 비용

COPY: 행을 새 구조로 옮긴다

COPY 경로는 변경된 정의를 가진 테이블을 만들고 기존 행을 복사한다. 새 컬럼 정의에 맞는 변환과 인덱스 구성이 필요하며, 마지막에 새 구조로 교체한다. 따라서 데이터 크기, 인덱스 수, 저장장치 처리량과 여유 공간이 작업 시간에 영향을 준다.

원본과 새 구조가 공존하는 구간이 있으므로 “테이블 크기만큼 남았으니 충분하다”고 단정하면 안 된다. 인덱스와 임시 작업 공간까지 포함한 용량 계획이 필요하다. COPY에서 LOCK=NONE을 요구하는 것은 쓰기 차단을 제거하는 우회법이 아니며, 지원하지 않는 조합이면 실패한다.

INPLACE: 엔진이 수행하되, 작업량은 여전히 클 수 있다

일반 보조 인덱스를 온라인으로 생성할 때는 기존 행을 읽어 인덱스를 구축하고, 동시에 발생한 관련 변경을 온라인 변경 로그에 기록하여 새 구조에 반영한다. 이 로그는 binlog나 InnoDB redo log와 목적이 다르다. innodb_online_alter_log_max_size는 이런 온라인 DDL 변경 로그의 한도를 제어한다.

쓰기 유입이 많으면 로그가 커지고 변경 반영 부담도 증가한다. 한도 초과는 작업 실패로 이어질 수 있으며, 한도를 크게 잡는다고 처리량 부족이 해결되는 것은 아니다. 테이블 재구축을 수반하는 INPLACE 작업은 새 데이터 구조와 임시 공간도 요구한다. CPU·I/O·버퍼 풀 영향 및 종료 단계 대기를 함께 평가해야 한다.

INSTANT: 기존 행을 해석하는 정의를 바꾼다

지원되는 컬럼 추가는 기존 행을 모두 읽고 다시 저장하는 대신, 추가 컬럼과 기본값 등의 메타데이터를 기록한다. 기존 물리 행에 새 컬럼 값이 없더라도 서버가 새 정의에 따라 값을 해석하므로 조회에서는 기본값이 보일 수 있다. 이후 새로 저장되는 행과 이전 행이 같은 물리 형태를 가져야 하는 것은 아니다.

MySQL 8.0.12부터 INSTANT 컬럼 추가가 도입되었으며, 8.0.29에서 임의 위치 추가와 컬럼 삭제 지원 및 행 버전 관리가 확장되었다. 그렇다고 모든 테이블과 모든 컬럼 조합이 지원되는 것은 아니다. 압축 행 형식, FULLTEXT 인덱스, 행 크기와 컬럼 수, 다른 ALTER 절과의 조합 등을 확인해야 한다.

8.0.29 이후와 8.4에서 TOTAL_ROW_VERSIONS는 INSTANT 컬럼 추가·삭제로 누적된 행 버전을 확인하는 관측점이다. 이 계열의 한도는 64이며, 지원 한도를 넘으면 INSTANT 변경은 거부된다. 재구축은 이를 초기화하지만 그 자체가 무거운 작업이다. 이 값은 사용자 행 수나 트랜잭션 수가 아니다.

flowchart TD
    A[변경할 정의와 대상 버전 확인] --> B{해당 변경이 INSTANT 지원?}
    B -->|예| C[ALGORITHM=INSTANT 명시]
    B -->|아니요| D{INPLACE와 요구 동시성 지원?}
    C --> E[메타데이터 잠금 확보 후 정의 반영]
    D -->|예| F[재구축 여부와 로그·공간 검토]
    F --> G[ALGORITHM=INPLACE 및 LOCK 요구 명시]
    D -->|아니요| H[COPY 작업창 또는 다른 이행 방식 검토]
    E --> I[스키마·업무 동작·복제 상태 확인]
    G --> I
    H --> I

3. 옵션은 성능 힌트가 아니라 변경의 허용 조건이다

ALGORITHM을 생략하면 지원 가능한 방식 중에서 서버가 선택한다. 지원되는 경우 INSTANT를 사용하고, 그렇지 않으면 INPLACE나 COPY 경로가 선택될 수 있다. 운영자는 문장 성공만 확인하고 예상보다 큰 재구축이나 쓰기 중단을 뒤늦게 발견할 수 있다.

반면 ALGORITHM=INSTANT를 명시하면 그 알고리즘으로 수행할 수 없는 작업은 오류로 중단된다. ALGORITHM=INPLACE, LOCK=NONE 역시 그 실행 방식과 동시성을 함께 요구한다. 지원하지 않을 때 옵션을 자동 제거하고 재시도하는 배포 도구는 이 안전장치를 무력화한다.

INSTANT에는 LOCK=NONE을 덧붙이지 않는다. INSTANT에서 허용되는 LOCK 지정은 DEFAULT이므로, 이 글에서는 LOCK 절을 생략한다. 일반 보조 인덱스 추가에는 INSTANT를 사용할 수 없으며, 컬럼 추가와 인덱스 추가를 한 ALTER에 묶으면 전체 문장이 INSTANT 조건을 만족하지 못할 수 있다. 분리 실행 시에는 중간 스키마가 애플리케이션과 호환되는지도 검토한다.

별도 테스트 테이블에서 ADD INDEX에 ALGORITHM=INSTANT를 지정한 검증은 ERROR 1845 (0A000)으로 거부되었고, 요청한 인덱스가 생성되지 않았음도 확인했다. 오류 안내에 COPY/INPLACE가 제시되었다는 이유만으로 운영 배포가 이를 자동 선택하도록 만들지 않는다.

4. 실행 예제: 이름 대신 실제 변화를 확인한다

아래 네 예제는 각각 테스트 테이블을 만들고 변경 후 삭제한다. 운영 테이블 이름으로 바꾸어 그대로 실행하지 않는다. information_schema.INNODB_TABLES 조회에는 적절한 진단 권한이 필요하다. 각 예제의 TABLE_ID 비교는 이 실험에서 내부 테이블이 유지되었는지 확인하는 보조 증거이며, 모든 DDL의 구현을 판정하는 범용 API는 아니다.

실행 결과는 실제 출력에서 핵심 DDL 성공 메시지와 조회 결과를 발췌한다. 반복되는 준비·정리 출력은 생략하며, 작은 테이블의 실행 시간으로 알고리즘의 성능 차이를 비교하지 않는다.

4.1 INSTANT 컬럼 추가: 기존 행의 기본값과 행 버전

CREATE TABLE ddl_instant_demo (
  id INT PRIMARY KEY,
  amount INT NOT NULL
) ENGINE=InnoDB;
INSERT INTO ddl_instant_demo VALUES (1, 100), (2, 200);
SELECT TABLE_ID INTO @before_id
FROM information_schema.INNODB_TABLES
WHERE NAME = CONCAT(DATABASE(), '/ddl_instant_demo');
ALTER TABLE ddl_instant_demo
  ADD COLUMN state VARCHAR(8) NOT NULL DEFAULT 'new',
  ALGORITHM=INSTANT;
SELECT id, amount, state FROM ddl_instant_demo ORDER BY id;
SELECT TABLE_ID = @before_id AS same_table_id, TOTAL_ROW_VERSIONS
FROM information_schema.INNODB_TABLES
WHERE NAME = CONCAT(DATABASE(), '/ddl_instant_demo');
DROP TABLE ddl_instant_demo;

실행 결과(MySQL 8.0.x):

mysql> ALTER TABLE ddl_instant_demo
    ->   ADD COLUMN state VARCHAR(8) NOT NULL DEFAULT 'new',
    ->   ALGORITHM=INSTANT;

Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SELECT id, amount, state FROM ddl_instant_demo ORDER BY id;

+----+--------+-------+
| id | amount | state |
+----+--------+-------+
|  1 |    100 | new   |
|  2 |    200 | new   |
+----+--------+-------+
2 rows in set (0.00 sec)

mysql> SELECT TABLE_ID = @before_id AS same_table_id, TOTAL_ROW_VERSIONS
    -> FROM information_schema.INNODB_TABLES
    -> WHERE NAME = CONCAT(DATABASE(), '/ddl_instant_demo');

+---------------+--------------------+
| same_table_id | TOTAL_ROW_VERSIONS |
+---------------+--------------------+
|             1 |                  1 |
+---------------+--------------------+
1 row in set (0.00 sec)

기존 두 행에서 state='new'가 조회되고 내부 테이블 ID는 유지된다. 행 버전 증가는 이 ALTER의 메타데이터 변경을 보여준다. 이것을 기존 행마다 UPDATE를 실행했다는 증거로 해석해서는 안 된다.

4.2 INPLACE 보조 인덱스 추가: 재구축하지 않아도 실제 인덱스는 만든다

CREATE TABLE ddl_index_demo (
  id INT PRIMARY KEY,
  amount INT NOT NULL
) ENGINE=InnoDB;
INSERT INTO ddl_index_demo VALUES (1, 100), (2, 200);
SELECT TABLE_ID INTO @before_id
FROM information_schema.INNODB_TABLES
WHERE NAME = CONCAT(DATABASE(), '/ddl_index_demo');
ALTER TABLE ddl_index_demo
  ADD INDEX idx_amount (amount),
  ALGORITHM=INPLACE, LOCK=NONE;
SELECT TABLE_ID = @before_id AS same_table_id
FROM information_schema.INNODB_TABLES
WHERE NAME = CONCAT(DATABASE(), '/ddl_index_demo');
SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ddl_index_demo'
ORDER BY INDEX_NAME, SEQ_IN_INDEX;
DROP TABLE ddl_index_demo;

실행 결과(MySQL 8.0.x):

mysql> ALTER TABLE ddl_index_demo
    ->   ADD INDEX idx_amount (amount),
    ->   ALGORITHM=INPLACE, LOCK=NONE;

Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SELECT TABLE_ID = @before_id AS same_table_id
    -> FROM information_schema.INNODB_TABLES
    -> WHERE NAME = CONCAT(DATABASE(), '/ddl_index_demo');

+---------------+
| same_table_id |
+---------------+
|             1 |
+---------------+
1 row in set (0.00 sec)

mysql> SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE
    -> FROM information_schema.STATISTICS
    -> WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ddl_index_demo'
    -> ORDER BY INDEX_NAME, SEQ_IN_INDEX;

+------------+-------------+------------+
| INDEX_NAME | COLUMN_NAME | NON_UNIQUE |
+------------+-------------+------------+
| idx_amount | amount      |          1 |
| PRIMARY    | id          |          0 |
+------------+-------------+------------+
2 rows in set (0.00 sec)

idx_amount가 생성되고 same_table_id=1이지만, 메타데이터만 바뀐 작업은 아니다. 테이블이 작아서 빨랐다는 사실과 대형 테이블에서도 작업량이 작다는 주장은 전혀 다르다. 이 예제는 옵션 수용과 인덱스 생성을 검증하며, 다른 세션의 지속적인 쓰기 부하를 재현하지 않는다.

4.3 INPLACE 재구축: 같은 알고리즘 안에서도 비용이 다르다

CREATE TABLE ddl_rebuild_demo (
  id INT PRIMARY KEY,
  amount INT NOT NULL
) ENGINE=InnoDB;
INSERT INTO ddl_rebuild_demo VALUES (1, 100), (2, 200);
ALTER TABLE ddl_rebuild_demo
  ADD COLUMN state VARCHAR(8) NOT NULL DEFAULT 'new',
  ALGORITHM=INSTANT;
SELECT TABLE_ID, TOTAL_ROW_VERSIONS INTO @before_id, @before_versions
FROM information_schema.INNODB_TABLES
WHERE NAME = CONCAT(DATABASE(), '/ddl_rebuild_demo');
ALTER TABLE ddl_rebuild_demo FORCE, ALGORITHM=INPLACE, LOCK=NONE;
SELECT TABLE_ID = @before_id AS same_table_id,
       @before_versions AS versions_before,
       TOTAL_ROW_VERSIONS AS versions_after
FROM information_schema.INNODB_TABLES
WHERE NAME = CONCAT(DATABASE(), '/ddl_rebuild_demo');
SELECT id, amount, state FROM ddl_rebuild_demo ORDER BY id;
DROP TABLE ddl_rebuild_demo;

실행 결과(MySQL 8.0.x):

mysql> ALTER TABLE ddl_rebuild_demo FORCE, ALGORITHM=INPLACE, LOCK=NONE;

Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SELECT TABLE_ID = @before_id AS same_table_id,
    ->        @before_versions AS versions_before,
    ->        TOTAL_ROW_VERSIONS AS versions_after
    -> FROM information_schema.INNODB_TABLES
    -> WHERE NAME = CONCAT(DATABASE(), '/ddl_rebuild_demo');

+---------------+-----------------+----------------+
| same_table_id | versions_before | versions_after |
+---------------+-----------------+----------------+
|             0 |               1 |              0 |
+---------------+-----------------+----------------+
1 row in set (0.00 sec)

mysql> SELECT id, amount, state FROM ddl_rebuild_demo ORDER BY id;

+----+--------+-------+
| id | amount | state |
+----+--------+-------+
|  1 |    100 | new   |
|  2 |    200 | new   |
+----+--------+-------+
2 rows in set (0.00 sec)

여기서는 의도적으로 FORCE로 재구축한다. 앞선 INPLACE 인덱스 추가와 달리 내부 테이블 ID가 바뀌고, INSTANT로 누적된 행 버전이 초기화된다. 데이터 내용은 유지된다. FORCE를 일상적인 유지보수 처방으로 권장하는 예제가 아니라, INPLACE와 재구축 없음이 동의어가 아님을 확인하는 실험이다.

4.4 COPY: 알고리즘을 명시적으로 고정한 비교

CREATE TABLE ddl_copy_demo (
  id INT PRIMARY KEY,
  amount INT NOT NULL
) ENGINE=InnoDB;
INSERT INTO ddl_copy_demo VALUES (1, 100), (2, 200);
SELECT TABLE_ID INTO @before_id
FROM information_schema.INNODB_TABLES
WHERE NAME = CONCAT(DATABASE(), '/ddl_copy_demo');
ALTER TABLE ddl_copy_demo
  ADD COLUMN state VARCHAR(8) NOT NULL DEFAULT 'new',
  ALGORITHM=COPY, LOCK=SHARED;
SELECT TABLE_ID = @before_id AS same_table_id
FROM information_schema.INNODB_TABLES
WHERE NAME = CONCAT(DATABASE(), '/ddl_copy_demo');
SELECT id, amount, state FROM ddl_copy_demo ORDER BY id;
DROP TABLE ddl_copy_demo;

실행 결과(MySQL 8.0.x):

mysql> ALTER TABLE ddl_copy_demo
    ->   ADD COLUMN state VARCHAR(8) NOT NULL DEFAULT 'new',
    ->   ALGORITHM=COPY, LOCK=SHARED;

Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> SELECT TABLE_ID = @before_id AS same_table_id
    -> FROM information_schema.INNODB_TABLES
    -> WHERE NAME = CONCAT(DATABASE(), '/ddl_copy_demo');

+---------------+
| same_table_id |
+---------------+
|             0 |
+---------------+
1 row in set (0.00 sec)

mysql> SELECT id, amount, state FROM ddl_copy_demo ORDER BY id;

+----+--------+-------+
| id | amount | state |
+----+--------+-------+
|  1 |    100 | new   |
|  2 |    200 | new   |
+----+--------+-------+
2 rows in set (0.00 sec)

INSTANT로 처리할 수 있는 단순 컬럼 추가도 COPY를 명시하면 복사 경로로 수행한다. 결과 데이터가 같아도 작업 방식과 가용성은 같지 않다. Query OK의 affected rows는 출력 문맥에 따라 다르므로, 0이라고 내부에서 데이터를 전혀 읽거나 쓰지 않았다고 단정하지 않는다.

5. 짧은 DDL이 오래 기다리는 이유: 메타데이터 잠금

DDL은 기존 정의를 사용 중인 트랜잭션과 새 정의 사이의 경계를 조정해야 한다. 오래 열린 트랜잭션이 테이블을 읽고 커밋하지 않았다면, 현재 문장을 실행하지 않는 연결도 메타데이터 잠금을 보유할 수 있다. INSTANT여도 필요한 배타적 메타데이터 잠금을 얻기 전에는 기다린다.

온라인 INPLACE 작업은 준비·주 실행·완료 단계로 나누어 이해할 수 있다. 주 실행 단계에서 DML을 허용해도 준비 또는 완료 시점에는 더 강한 메타데이터 잠금이 필요할 수 있다. 대기 중인 DDL 뒤에 후속 쿼리가 줄을 서면 단일 배포 문장이 애플리케이션 연결 풀 고갈로 확대될 수 있다.

다음 조회는 메타데이터 잠금의 상태를 확인하는 출발점이다. 자기 세션을 제외하고 현재 스키마의 테이블 잠금을 좁혀 본다. 단일 연결 검증 환경에서는 Empty set이 정상이며, 운영 환경에서의 잠금 부재를 뜻하지 않는다.

SELECT m.OBJECT_NAME, m.LOCK_TYPE, m.LOCK_STATUS,
       t.PROCESSLIST_ID, t.PROCESSLIST_COMMAND
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()
  AND (t.PROCESSLIST_ID IS NULL OR t.PROCESSLIST_ID <> CONNECTION_ID())
ORDER BY m.OBJECT_NAME, m.LOCK_STATUS, t.PROCESSLIST_ID
LIMIT 20;

실행 결과(MySQL 8.0.x):

mysql> SELECT m.OBJECT_NAME, m.LOCK_TYPE, m.LOCK_STATUS,
    ->        t.PROCESSLIST_ID, t.PROCESSLIST_COMMAND
    -> 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()
    ->   AND (t.PROCESSLIST_ID IS NULL OR t.PROCESSLIST_ID <> CONNECTION_ID())
    -> ORDER BY m.OBJECT_NAME, m.LOCK_STATUS, t.PROCESSLIST_ID
    -> LIMIT 20;

Empty set (0.00 sec)

PENDING과 같은 객체의 GRANTED 보유자를 함께 확인하고, 트랜잭션 시작 시각과 실제 SQL 이력을 조사한다. 모든 GRANTED 행을 자동으로 blocker로 판정하거나 연결을 일괄 종료하지 않는다. 조회가 비어 있으면 대상 스키마, 계측 설정, 진단 권한과 조회 시점도 확인한다.

DDL 세션의 lock_wait_timeout은 메타데이터 잠금 대기 상한을 정하는 데 사용한다. InnoDB 행 잠금용 innodb_lock_wait_timeout과 혼동하지 않는다. 작은 값을 지정해도 DDL 전체 실행 시간 제한이 되는 것은 아니다. 변경을 시작하기 전에 대기 예산과 취소 조건을 정하고, 진행 중 작업을 취소하면 정리에 추가 시간이 들 수 있음을 고려한다.

별도의 두 연결 실험에서는 한 세션이 테스트 테이블을 읽고 트랜잭션을 열어 둔 동안, 다른 세션이 lock_wait_timeout=3으로 INSTANT 컬럼 추가를 시도했다. 관측된 잠금에는 SHARED_READ / GRANTED, SHARED_UPGRADABLE / GRANTED, EXCLUSIVE / PENDING이 함께 존재했다. ALTER는 ERROR 1205로 종료되었고 컬럼은 추가되지 않았다. 읽기 트랜잭션을 커밋한 뒤 같은 ALTER를 다시 실행하자 성공했다. 이는 메타데이터 대기를 실제로 재현한 결과이며, 대량 DML과 온라인 인덱스 생성의 부하 시험은 아니다.

6. 복제와 Aurora MySQL에서는 무엇이 달라지는가

Community MySQL의 binlog 복제에서는 원본에서 DDL이 성공한 뒤에도 복제본이 같은 스키마 변경을 적용하는 비용과 대기 시간이 남을 수 있다. 원본의 LOCK=NONE은 복제 지연을 없애는 옵션이 아니다. 원본과 복제본의 버전·정의가 다르면 명시한 알고리즘 지원 여부도 달라질 수 있으므로 전체 경로를 확인한다.

Aurora MySQL 버전 3과 8.4는 Instant DDL을 제공한다. Aurora MySQL 버전 2의 Fast DDL은 다른 구현이므로 이전 세대의 제한과 활성화 방법을 그대로 적용하지 않는다. Aurora에서도 정확한 엔진 버전과 변경 종류를 기준으로 지원 범위를 확인하고, 가능하면 명시적인 ALGORITHM으로 허용 범위를 좁힌다.

공유 스토리지와 관리형 운영은 애플리케이션의 긴 트랜잭션, DDL 메타데이터 잠금, 인덱스 생성 CPU·메모리 및 임시 공간 부담을 없애지 않는다. writer에서의 작업 종료뿐 아니라 reader 접근, 연결 오류, 지연과 업무 쿼리 계획도 확인한다. Aurora reader의 적용 경로를 Community binlog 복제 SQL thread와 동일하게 설명해서는 안 된다. 이 글의 실행 검증은 로컬 Community MySQL이며, 실제 Aurora 변경·failover·성능 시험을 수행한 결과는 아니다.

7. 배포 전후의 판단 기준

실행 전

  • 허용하는 ALGORITHM과 LOCK

실행 중과 완료 후

  • 복구 방법을 문서화한다. InnoDB의 atomic DDL은 장애 시 일관성을 위한 것이며, 성공한 ALTER를 사용자 ROLLBACK

자주 발생하는 오류는 “INPLACE니까 공간이 필요 없다”, “INSTANT니까 바로 끝난다”, “LOCK=NONE이니까 대기가 없다”라는 해석이다. 각각 재구축·자원 사용·메타데이터 잠금이라는 별도 축을 생략한 판단이다. 허용 조건을 만족하지 않는다면 COPY를 무조건 피하기보다 계획된 작업창, 단계적 스키마 이행 또는 별도 온라인 스키마 변경 도구를 검토한다. 외부 도구도 최종 교체 잠금, 복제 부하, 외래 키와 트리거 제약을 없애지는 않는다.

8. 정리

COPY는 새 구조로 행을 복사하고, INPLACE는 엔진의 변경 경로를 사용하며, INSTANT는 지원되는 작업의 기존 행 재작성을 피한다. 이 구분만으로 동시성이나 완료 시간을 보장할 수는 없다. 알고리즘, 재구축 여부, 동시성, 메타데이터 잠금을 각각 확인하고 실제 환경의 부하와 복제 경로를 검증해야 한다.

스키마 변경의 다음 단계는 컬럼·인덱스·기본 키 등 개별 작업의 지원 조건을 대조하고, 긴 트랜잭션이 있는 환경에서 메타데이터 잠금 대기와 배포 순서를 설계하는 것이다.

참고 문서