카테고리 : MySQL/기술노트

Undo tablespace와 truncate: undo 공간 증가를 관리하는 방법

InnoDB undo tablespace가 증가하는 원인과 truncate 동작, 장기 트랜잭션 관리, Aurora MySQL 운영 차이를 정리한다.

저자: MySQL 기술 노트 작성: 2026.06.10 약 10분 5,859자
다운로드

1. 운영자가 undo 공간을 별도로 보아야 하는 이유

InnoDB에서 undo log는 트랜잭션을 되돌리기 위한 보조 기록에 그치지 않는다. ROLLBACK을 수행할 때 변경 전 이미지를 복원하고, REPEATABLE READ 같은 격리 수준에서 일관된 읽기 consistent read를 제공하며, 장애 복구 과정에서 미완료 트랜잭션을 정리하는 데 사용된다. 따라서 undo 영역은 트랜잭션 처리량, 긴 읽기 쿼리, purge 지연, 디스크 사용량을 함께 반영하는 운영 지표다.

운영 현장에서 자주 만나는 문제는 “데이터는 삭제했는데 디스크가 줄지 않는다” 또는 “특정 기간 이후 MySQL 데이터 디렉터리가 계속 커진다”는 형태로 나타난다. 이때 원인이 테이블스페이스 단편화일 수도 있지만, 장기 트랜잭션 때문에 purge가 지연되어 undo tablespace가 커지는 경우도 많다. 특히 대량 배치, 온라인 스키마 변경, 느린 리포트 쿼리, 복제 지연, 백업 도구의 일관성 스냅샷이 겹치면 undo는 짧은 시간에 크게 증가할 수 있다.

이 글은 MySQL 8.0 이상을 기준으로 undo tablespace가 어떤 역할을 하며, truncate가 언제 공간을 회수하고, 어떤 상황에서는 기대처럼 줄지 않는지 운영 관점에서 정리한다. Aurora MySQL에서는 물리 파일 관리 방식이 Community MySQL과 다르므로, 같은 지표를 보더라도 해석을 분리해야 한다.

2. Undo log, rollback segment, undo tablespace의 관계

InnoDB는 데이터 변경 시 변경 전 이미지를 undo log record로 남긴다. 이 기록은 rollback segment에 연결되고, rollback segment는 undo tablespace 안에 저장된다. 과거 MySQL에서는 undo가 system tablespace에 강하게 묶여 있었지만, MySQL 8.0의 일반적인 구성에서는 별도 undo tablespace가 사용되며 자동 truncate 기능을 통해 불필요해진 공간을 회수할 수 있다.

flowchart TD
    A[사용자 트랜잭션] --> B[UPDATE/DELETE/INSERT]
    B --> C[Undo log record 생성]
    C --> D[Rollback segment]
    D --> E[Undo tablespace]
    B --> F[Redo log에 변경 기록]
    B --> G[Buffer Pool의 데이터 페이지 변경]
    H[Consistent read] --> C
    I[Rollback] --> C
    J[Purge thread] --> K[더 이상 필요 없는 undo 제거]
    K --> E
    E --> L[조건 충족 시 undo tablespace truncate]

세 계층을 구분하면 장애 대응이 쉬워진다.

  • undo log record: 개별 변경을 되돌리거나 이전 버전을 읽기 위한 논리적 기록이다.
  • rollback segment: 여러 undo log record를 관리하는 내부 구조다. 동시 트랜잭션을 분산 수용한다.
  • undo tablespace: rollback segment가 저장되는 물리적 공간 단위다. 운영자가 디스크 사용량과 truncate를 관찰하는 대상이다.

중요한 점은 undo tablespace 크기 증가가 곧바로 비정상은 아니라는 것이다. 대량 변경 트랜잭션이 많으면 undo가 늘어나는 것은 정상이다. 문제는 변경이 끝난 뒤에도 purge가 진행되지 못하거나, truncate 조건을 만족하지 못해 물리 공간이 회수되지 않는 상황이다.

3. Undo 공간 증가의 주요 원인

3.1 장기 트랜잭션과 read view 고정

MVCC에서 일관된 읽기는 read view를 기준으로 과거 버전을 읽는다. 어떤 세션이 오래된 read view를 붙잡고 있으면, 그 시점 이후 변경된 row version을 purge가 제거할 수 없다. 운영자는 대량 DELETE만 의심하기 쉽지만, 실제로는 오래 열린 SELECT나 애플리케이션 커넥션 풀의 미완료 트랜잭션이 purge를 막는 경우가 많다.

예를 들어 다음과 같은 패턴은 undo 증가를 유발한다.

  • autocommit=0 상태에서 조회 후 COMMIT 없이 커넥션을 반환한다.
  • 리포트 쿼리가 수십 분 이상 실행되며 많은 변경과 겹친다.
  • 백업이나 덤프가 일관성 스냅샷을 오래 유지한다.
  • 대량 배치가 작은 단위로 커밋하지 않고 하나의 큰 트랜잭션으로 실행된다.

3.2 Purge 처리량 부족

purge thread는 더 이상 어느 read view에서도 필요하지 않은 undo record를 제거한다. 변경량이 purge 처리량보다 크면 history list가 증가하고, undo tablespace도 커질 수 있다. 이때 CPU, I/O, buffer pool 압박, flush 지연, 대량 보조 인덱스 변경이 함께 나타날 수 있다.

3.3 대량 DML과 복제 지연

소스 서버에서 대량 변경이 발생하면 undo가 증가할 수 있고, replica에서는 SQL thread 또는 worker가 같은 변경을 적용하면서 별도의 undo를 생성한다. replica가 느리면 긴 트랜잭션 적용과 읽기 부하가 겹쳐 undo 정리가 더 늦어질 수 있다. 운영자는 소스만 볼 것이 아니라 읽기 전용 replica의 undo 증가도 함께 확인해야 한다.

4. Undo tablespace truncate의 동작 원리

Undo tablespace truncate는 “사용하지 않는 undo record를 지운다”와 “물리 파일 크기를 줄인다”를 분리해서 이해해야 한다. purge가 먼저 오래된 undo record를 제거해야 하며, 그 결과 특정 undo tablespace가 truncate 가능한 상태가 되어야 물리 공간 회수가 가능하다.

MySQL 8.0에서는 innodb_undo_log_truncate가 활성화된 경우 InnoDB가 undo tablespace를 주기적으로 검사하고, 사용량과 설정값 조건이 맞으면 truncate를 수행한다. 이 작업은 즉시 강제되는 단일 명령이라기보다 백그라운드 정리 절차에 가깝다. 즉, 운영자가 변수를 켰다고 해서 커진 파일이 바로 줄어드는 것은 아니다.

운영 관점에서 다음 순서가 중요하다.

  1. 장기 트랜잭션이 종료되어 오래된 read view가 해제된다.
  2. purge thread가 더 이상 필요 없는 undo record를 제거한다.
  3. undo tablespace가 truncate 가능한 상태로 전환된다.
  4. InnoDB가 설정된 임계값과 주기에 따라 tablespace를 줄인다.
  5. 파일 시스템 또는 스토리지 계층에서 회수된 공간이 관찰된다.

이 절차 중 하나라도 막히면 innodb_undo_log_truncate=ON이어도 디스크 사용량은 줄지 않을 수 있다.

5. 현재 환경에서 확인할 기본 진단 SQL

다음 예제는 MySQL 8.0 계열에서 undo 관련 변수와 tablespace 상태를 확인하는 최소 진단 쿼리다. 운영 환경에서는 결과를 시간 간격을 두고 반복 수집하여 증가 속도와 purge 지연을 함께 보아야 한다.

SELECT VERSION() AS mysql_version;

SHOW VARIABLES LIKE 'innodb_undo_log_truncate';
SHOW VARIABLES LIKE 'innodb_max_undo_log_size';
SHOW VARIABLES LIKE 'innodb_rollback_segments';

SELECT SPACE,
       NAME,
       SPACE_TYPE,
       FILE_SIZE,
       ALLOCATED_SIZE,
       STATE
FROM information_schema.INNODB_TABLESPACES
WHERE SPACE_TYPE = 'Undo'
ORDER BY SPACE;

실행 결과(MySQL 8.0.46):

mysql> SELECT VERSION() AS mysql_version;

+---------------+
| mysql_version |
+---------------+
| 8.0.46        |
+---------------+
1 row in set (0.00 sec)

mysql> SHOW VARIABLES LIKE 'innodb_undo_log_truncate';

+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_undo_log_truncate | ON    |
+--------------------------+-------+
1 row in set (0.01 sec)

mysql> SHOW VARIABLES LIKE 'innodb_max_undo_log_size';

+--------------------------+------------+
| Variable_name            | Value      |
+--------------------------+------------+
| innodb_max_undo_log_size | 1073741824 |
+--------------------------+------------+
1 row in set (0.00 sec)

mysql> SHOW VARIABLES LIKE 'innodb_rollback_segments';

+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_rollback_segments | 128   |
+--------------------------+-------+
1 row in set (0.00 sec)

mysql> SELECT SPACE,
    ->        NAME,
    ->        SPACE_TYPE,
    ->        FILE_SIZE,
    ->        ALLOCATED_SIZE,
    ->        STATE
    -> FROM information_schema.INNODB_TABLESPACES
    -> WHERE SPACE_TYPE = 'Undo'
    -> ORDER BY SPACE;

+------------+-----------------+------------+-----------+----------------+--------+
| SPACE      | NAME            | SPACE_TYPE | FILE_SIZE | ALLOCATED_SIZE | STATE  |
+------------+-----------------+------------+-----------+----------------+--------+
| 4294967278 | innodb_undo_002 | Undo       |  16777216 |       16777216 | active |
| 4294967279 | innodb_undo_001 | Undo       |  16777216 |       16777216 | active |
+------------+-----------------+------------+-----------+----------------+--------+
2 rows in set (0.00 sec)

위 결과에서 FILE_SIZEALLOCATED_SIZE는 undo tablespace가 운영 중 어느 정도 커졌는지 보는 단서다. 다만 이 값만으로 “누가 undo를 만들었는지”는 알 수 없다. 원인 분석에는 트랜잭션 목록, 긴 쿼리, history list, 애플리케이션 커밋 패턴을 함께 확인해야 한다.

장기 트랜잭션 후보는 다음처럼 확인할 수 있다. 테스트 컨테이너처럼 부하가 없는 환경에서는 결과가 비어 있을 수 있으며, 운영 환경에서는 실행 시간이 긴 트랜잭션과 현재 실행 SQL을 함께 보아야 한다.

SELECT trx_id,
       trx_state,
       trx_started,
       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS trx_age_seconds,
       trx_mysql_thread_id,
       LEFT(trx_query, 120) AS trx_query_sample
FROM information_schema.INNODB_TRX
ORDER BY trx_started
LIMIT 10;

실행 결과(MySQL 8.0.46):

mysql> SELECT trx_id,
    ->        trx_state,
    ->        trx_started,
    ->        TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS trx_age_seconds,
    ->        trx_mysql_thread_id,
    ->        LEFT(trx_query, 120) AS trx_query_sample
    -> FROM information_schema.INNODB_TRX
    -> ORDER BY trx_started
    -> LIMIT 10;

Empty set (0.00 sec)

트랜잭션이 오래 열려 있는데 trx_query가 비어 있다면 “현재 실행 중인 SQL이 없다”는 뜻일 수 있다. 이는 오히려 애플리케이션이 트랜잭션을 열어 둔 채 대기하고 있다는 신호일 수 있다. 이 경우 performance_schema.threads, events_statements_current, 애플리케이션 커넥션 풀 로그를 함께 확인해야 한다.

6. 대량 변경 작업에서 undo 증가를 줄이는 작성 패턴

Undo 공간을 완전히 만들지 않는 대량 변경은 없다. 그러나 트랜잭션 단위를 조절하면 purge가 따라갈 기회를 만들고, 장애 시 rollback 비용도 줄일 수 있다. 다음 예제는 작은 테이블에서 배치 삭제 패턴을 재현하는 구조다. 실제 운영 테이블에서는 PK 범위, 보조 인덱스, 외래 키, 복제 지연, 업무 피크 시간을 고려해 배치 크기를 조정해야 한다.

DROP TABLE IF EXISTS undo_batch_demo;

CREATE TABLE undo_batch_demo (
    id BIGINT PRIMARY KEY,
    status VARCHAR(20) NOT NULL,
    created_at DATETIME NOT NULL,
    KEY ix_status_created (status, created_at)
) ENGINE=InnoDB;

INSERT INTO undo_batch_demo (id, status, created_at) VALUES
(1, 'expired', '2026-01-01 00:00:00'),
(2, 'expired', '2026-01-02 00:00:00'),
(3, 'active',  '2026-01-03 00:00:00'),
(4, 'expired', '2026-01-04 00:00:00');

DELETE FROM undo_batch_demo
WHERE status = 'expired'
ORDER BY id
LIMIT 2;

SELECT id, status, created_at
FROM undo_batch_demo
ORDER BY id;

DROP TABLE undo_batch_demo;

실행 결과(MySQL 8.0.46):

mysql> DROP TABLE IF EXISTS undo_batch_demo;

Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE undo_batch_demo (
    ->     id BIGINT PRIMARY KEY,
    ->     status VARCHAR(20) NOT NULL,
    ->     created_at DATETIME NOT NULL,
    ->     KEY ix_status_created (status, created_at)
    -> ) ENGINE=InnoDB;

Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO undo_batch_demo (id, status, created_at) VALUES
    -> (1, 'expired', '2026-01-01 00:00:00'),
    -> (2, 'expired', '2026-01-02 00:00:00'),
    -> (3, 'active',  '2026-01-03 00:00:00'),
    -> (4, 'expired', '2026-01-04 00:00:00');

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

mysql> DELETE FROM undo_batch_demo
    -> WHERE status = 'expired'
    -> ORDER BY id
    -> LIMIT 2;

Query OK, 2 rows affected (0.00 sec)

mysql> SELECT id, status, created_at
    -> FROM undo_batch_demo
    -> ORDER BY id;

+----+---------+---------------------+
| id | status  | created_at          |
+----+---------+---------------------+
|  3 | active  | 2026-01-03 00:00:00 |
|  4 | expired | 2026-01-04 00:00:00 |
+----+---------+---------------------+
2 rows in set (0.00 sec)

mysql> DROP TABLE undo_batch_demo;

Query OK, 0 rows affected (0.01 sec)

운영 배치에서는 위 예제를 한 번 실행하고 끝내는 것이 아니라, 애플리케이션 또는 운영 스크립트에서 LIMIT 삭제를 반복하면서 각 반복마다 커밋한다. 단일 트랜잭션으로 수천만 건을 삭제하면 undo와 redo가 동시에 커지고, rollback이 필요할 때 복구 시간이 길어진다. 반대로 배치 크기를 너무 작게 잡으면 전체 작업 시간이 길어지고 인덱스 탐색 비용이 커질 수 있다. 따라서 다음 지표를 보면서 조정한다.

  • 배치 1회당 실행 시간
  • replica 지연 시간
  • undo tablespace 증가 속도
  • purge 지연 또는 history list 증가 추세
  • 버퍼 풀 dirty page와 checkpoint 압박
  • 업무 쿼리의 latency 변화

7. 설정값 해석과 변경 시 주의점

innodb_undo_log_truncate는 undo tablespace 자동 truncate를 켜는 핵심 변수다. innodb_max_undo_log_size는 truncate 판단에 사용하는 크기 기준으로 이해할 수 있다. 그러나 이 값들을 크게 또는 작게 바꾸는 것만으로 모든 문제가 해결되지는 않는다.

설정 변경 전에는 다음 질문에 답해야 한다.

  • undo 증가가 일시적인 대량 배치 때문인가, 지속적인 장기 트랜잭션 때문인가?
  • purge가 따라가지 못하는 원인이 CPU/I/O 부족인가, 오래된 read view인가?
  • 커진 공간이 실제로 truncate 가능한 상태인가?
  • replica 또는 읽기 전용 노드에서도 같은 문제가 발생하는가?
  • 백업, 온라인 DDL, 배치 작업 시간이 겹치지 않는가?

innodb_max_undo_log_size를 낮게 설정하면 truncate 기회가 더 자주 생길 수 있지만, 잦은 truncate가 항상 좋은 것은 아니다. 운영 부하가 큰 시간대에 공간 회수 작업이 겹칠 수 있고, 실제 병목이 장기 트랜잭션이라면 크기 기준을 낮춰도 효과가 제한된다. 설정값은 증상 완화 장치이지, 트랜잭션 설계 문제를 대신 해결하지 않는다.

8. Aurora MySQL에서의 운영 해석 차이

Aurora MySQL은 스토리지 계층이 Community MySQL의 로컬 파일 기반 배치와 다르다. 따라서 운영자가 EC2 로컬 디스크의 .ibu 파일 크기를 직접 보며 판단하는 방식은 그대로 적용되지 않는다. Aurora에서는 클러스터 볼륨, 스토리지 과금, purge 지연, reader 인스턴스의 read view, Performance Insights 지표를 함께 해석해야 한다.

Aurora에서 특히 주의할 점은 reader의 긴 쿼리도 purge와 버전 보존에 영향을 줄 수 있다는 점이다. 분석용 reader에서 오래 실행되는 리포트 쿼리가 있으면 writer의 변경 이후에도 오래된 버전을 보존해야 할 수 있다. 결과적으로 스토리지 사용량 증가, commit latency 변화, replica lag 또는 reader lag 형태로 운영 증상이 나타날 수 있다.

Aurora 운영에서는 다음 항목을 함께 본다.

  • Performance Insights에서 장기 실행 SQL과 wait event
  • CloudWatch의 VolumeBytesUsed, replica lag, DML 처리량
  • writer와 reader의 트랜잭션 지속 시간
  • 백업, export, 분석 쿼리의 시간대
  • 파라미터 그룹에서 undo 관련 변수 적용 가능 여부와 재시작 필요 여부

Community MySQL에서 가능한 파일 단위 확인이 Aurora에서는 제한될 수 있으므로, “파일이 줄었는가”보다 “버전 보존 압력이 해소되었는가”, “스토리지 증가 추세가 멈췄는가”, “장기 트랜잭션이 사라졌는가”를 중심으로 판단해야 한다.

9. 장애 및 오해 사례

9.1 DELETE 후 디스크가 바로 줄지 않는다고 장애로 판단

DELETE는 row를 논리적으로 제거하지만, undo와 purge, tablespace 재사용, 파일 truncate는 별도 단계다. 삭제 직후 디스크가 줄지 않는 것은 정상일 수 있다. 먼저 장기 트랜잭션과 purge 진행 여부를 확인해야 한다.

9.2 kill만 하면 undo 공간이 즉시 회수된다고 기대

장기 트랜잭션을 KILL하면 새 변경은 멈출 수 있지만, 이미 수행한 변경이 많다면 rollback 자체가 긴 작업이 된다. rollback 중에도 undo는 필요하며, rollback이 끝난 뒤 purge와 truncate 단계가 남는다.

9.3 배치 크기를 크게 잡아 전체 시간을 줄이려는 접근

대량 삭제를 하나의 큰 트랜잭션으로 묶으면 SQL 호출 횟수는 줄지만, undo와 redo 압력이 커지고 장애 시 복구 비용이 증가한다. 운영 시스템에서는 총 실행 시간보다 예측 가능한 부하와 복구 가능성이 더 중요할 때가 많다.

9.4 모니터링을 파일 크기 하나로 축소

Undo tablespace 크기는 결과 지표다. 원인 지표는 트랜잭션 지속 시간, purge 지연, 변경량, replica lag, I/O 지연, 애플리케이션 커밋 패턴에 있다. 파일 크기만 보면 이미 늦은 경우가 많다.

10. 운영 점검표

Undo 공간 증가를 발견했을 때는 다음 순서로 확인한다.

  • innodb_undo_log_truncate, innodb_max_undo_log_size, innodb_rollback_segments
  • information_schema.INNODB_TABLESPACES
  • information_schema.INNODB_TRX
  • 애플리케이션 커넥션 풀에서 autocommit=0

11. 결론

Undo tablespace 관리는 단순한 디스크 정리 작업이 아니라 트랜잭션 설계, MVCC, purge, 배치 운영, 스토리지 관찰을 함께 다루는 문제다. innodb_undo_log_truncate는 필요한 안전장치지만, 장기 트랜잭션과 과도한 대량 DML을 방치한 상태에서는 기대한 만큼 공간을 회수하지 못한다.

운영자는 undo tablespace 크기를 결과로 보고, 원인은 오래된 read view와 purge 처리량에서 찾아야 한다. Community MySQL에서는 파일과 tablespace 상태를 직접 관찰하고, Aurora MySQL에서는 클러스터 스토리지와 reader의 장기 쿼리까지 함께 보아야 한다. 다음 글에서는 undo와 밀접하게 연결되는 purge 동작, history list 증가, 그리고 장기 트랜잭션이 성능과 복구 시간에 미치는 영향을 더 깊게 다룰 수 있다.