카테고리 : MySQL/기술노트

Purge thread와 history list length: 오래 열린 트랜잭션이 만드는 문제

InnoDB purge thread와 history list length의 관계를 통해 오래 열린 트랜잭션이 undo, 성능, 운영 안정성에 미치는 영향을 정리한다.

저자: MySQL 기술 노트 작성: 2026.06.14 약 12분 7,088자
다운로드

Purge thread와 history list length: 오래 열린 트랜잭션이 만드는 문제

InnoDB에서 UPDATEDELETE는 행을 즉시 완전히 지우는 동작이 아니다. MySQL은 동시성 제어를 위해 이전 버전을 undo log에 남기고, 다른 트랜잭션이 그 버전을 더 이상 볼 필요가 없어졌을 때 나중에 정리한다. 이 정리 작업이 purge이며, purge가 따라가지 못해 쌓인 과거 버전의 길이를 운영자가 관찰할 때 자주 보는 지표가 history list length다.

이 주제가 중요한 이유는 단순하다. 오래 열린 트랜잭션 하나가 시스템 전체의 쓰기 성능, 버퍼 풀 효율, 디스크 사용량, 백업·복구 시간, 장애 대응 시간을 동시에 악화시킬 수 있기 때문이다. 트랜잭션이 Sleep 상태로 보이거나 애플리케이션 커넥션 풀에 숨어 있더라도, 그 트랜잭션이 오래된 read view를 붙잡고 있으면 InnoDB는 해당 read view에서 보일 수 있는 과거 행 버전을 함부로 제거하지 못한다.

이 글은 purge thread와 history list length를 내부 동작 관점에서 설명하고, 운영자가 MySQL Community와 Aurora MySQL 환경에서 어떤 방식으로 관찰하고 대응해야 하는지 정리한다.

1. InnoDB MVCC와 purge의 역할

InnoDB는 MVCC(Multi-Version Concurrency Control)를 사용한다. 한 행이 수정되면 새 버전은 clustered index 페이지에 반영되고, 이전 버전으로 되돌아갈 수 있는 정보는 undo log에 기록된다. 읽기 트랜잭션은 자신이 시작한 시점의 read view를 기준으로 적절한 행 버전을 본다.

예를 들어 어떤 세션이 REPEATABLE READ 트랜잭션을 시작한 뒤 같은 테이블을 오래 조회하고 있다고 하자. 그 사이 다른 세션들이 같은 테이블을 계속 UPDATE하면 InnoDB는 새 값을 기록하면서도 오래된 트랜잭션이 볼 수 있는 과거 버전을 유지해야 한다. 오래된 트랜잭션이 종료되기 전까지는 관련 undo record를 제거할 수 없다.

purge thread는 더 이상 어떤 read view에서도 필요하지 않은 undo record와 delete-marked record를 정리한다. 이 작업은 백그라운드에서 진행되며, 실제로는 다음과 같은 의미를 갖는다.

  • undo log record 중 더 이상 참조되지 않는 항목을 제거한다.
  • delete-marked record가 더 이상 필요 없으면 인덱스에서 물리적으로 제거할 수 있게 한다.
  • 오래된 row version으로 인한 불필요한 탐색 비용을 줄인다.
  • undo tablespace와 내부 메타데이터가 계속 커지는 것을 완화한다.

그러나 purge는 임의로 앞질러 갈 수 없다. 가장 오래된 활성 read view가 아직 어떤 과거 버전을 필요로 하면, 그 이후에 생긴 undo chain을 일정 범위 이상 정리하지 못한다. 그래서 purge 병목은 단순히 백그라운드 스레드가 느린 문제가 아니라, 종종 애플리케이션 트랜잭션 경계가 불명확한 문제로 시작된다.

2. history list length가 의미하는 것

history list length는 InnoDB가 정리해야 할 undo history가 얼마나 쌓였는지를 보여주는 대표 지표다. 엄밀히 말하면 “남아 있는 undo record의 바이트 수”가 아니라 rollback segment history list에 연결된 undo log segment의 길이에 가까운 내부 지표다. 따라서 절대값 하나만 보고 즉시 장애로 단정하기보다는, 증가 속도와 지속 시간, 동시에 관찰되는 오래 열린 트랜잭션을 함께 봐야 한다.

운영 관점에서 history list length는 다음 질문에 답하기 위한 출발점이다.

  • purge가 쓰기 발생량을 따라잡고 있는가?
  • 오래 열린 트랜잭션 때문에 purge가 멈추거나 지연되고 있는가?
  • UPDATE/DELETE가 많은 배치 작업 이후 정리가 정상적으로 줄어드는가?
  • undo tablespace, buffer pool, secondary index 탐색 비용이 커질 위험이 있는가?

다음 흐름으로 이해하면 쉽다.

flowchart LR
    A[쓰기 트랜잭션<br/>UPDATE/DELETE] --> B[새 행 버전 생성]
    B --> C[undo log에 이전 버전 기록]
    C --> D{오래된 read view가<br/>이전 버전을 볼 수 있는가?}
    D -- 예 --> E[purge 대기<br/>history list length 증가]
    D -- 아니오 --> F[purge thread 정리 가능]
    E --> G[undo 증가·탐색 비용 증가·운영 리스크]
    F --> H[delete-marked record 제거<br/>undo history 축소]

여기서 중요한 점은 history list length가 “문제의 원인”이라기보다 “정리되지 못한 과거 버전이 쌓이고 있다는 증상”에 가깝다는 것이다. 원인은 purge 스레드 수 부족, 과도한 쓰기, I/O 병목일 수도 있지만, 가장 흔한 원인 중 하나는 오래 열린 트랜잭션이다.

3. 오래 열린 트랜잭션이 purge를 막는 방식

오래 열린 트랜잭션은 반드시 CPU를 많이 쓰거나 잠금을 잡고 있어야만 문제가 되는 것이 아니다. SELECT만 수행한 읽기 트랜잭션도 REPEATABLE READ에서 read view를 유지하면 purge의 하한선을 고정할 수 있다. 특히 다음 패턴은 운영 환경에서 자주 발견된다.

  • 애플리케이션이 명시적으로 BEGIN 후 예외 처리 경로에서 COMMIT/ROLLBACK을 누락한다.
  • ORM 또는 프레임워크가 request 범위보다 긴 트랜잭션을 만든다.
  • 커넥션 풀이 트랜잭션을 연 상태로 커넥션을 반환한다.
  • 대시보드, 배치, ETL 작업이 긴 SELECT를 하나의 트랜잭션으로 오래 유지한다.
  • 수동 점검 세션에서 START TRANSACTION WITH CONSISTENT SNAPSHOT 후 세션을 방치한다.

이때 InnoDB 내부에서는 다음과 같은 현상이 이어진다.

  1. 쓰기 트랜잭션이 계속 undo record를 만든다.
  2. purge thread는 가장 오래된 read view보다 최신인 undo chain 일부를 제거하지 못한다.
  3. history list length가 증가하거나 높은 값에서 내려오지 않는다.
  4. 인덱스 탐색 중 오래된 버전을 건너뛰는 비용이 늘고, buffer pool에 유지해야 할 페이지도 늘어난다.
  5. 트랜잭션 종료 후에도 한동안 purge가 밀린 작업을 따라잡아야 하므로 지연 효과가 남는다.

즉, 오래 열린 트랜잭션은 현재 실행 중인 쿼리 하나의 문제를 넘어 “과거 버전을 제거하지 못하게 만드는 전역적인 하한선”으로 작동한다.

4. 현재 환경에서 확인할 기본 지표

먼저 대상 MySQL의 버전과 격리 수준을 확인한다. 이 글의 SQL은 MySQL 8.0 계열을 기준으로 검증 가능한 형태로 작성했다.

SELECT VERSION() AS mysql_version,
       @@version_comment AS version_comment,
       @@transaction_isolation AS transaction_isolation;

실행 결과(MySQL 8.0.46):

mysql> SELECT VERSION() AS mysql_version,
    ->        @@version_comment AS version_comment,
    ->        @@transaction_isolation AS transaction_isolation;

+---------------+------------------------------+-----------------------+
| mysql_version | version_comment              | transaction_isolation |
+---------------+------------------------------+-----------------------+
| 8.0.46        | MySQL Community Server - GPL | REPEATABLE-READ       |
+---------------+------------------------------+-----------------------+
1 row in set (0.00 sec)

history list length 자체는 SHOW ENGINE INNODB STATUSTRANSACTIONS 섹션에서도 볼 수 있지만, 자동화된 수집과 비교에는 information_schema.INNODB_METRICS가 더 편리하다. 다음 쿼리는 해당 지표가 현재 서버에서 노출되는지 확인하고 값을 조회한다.

SELECT NAME,
       SUBSYSTEM,
       COUNT,
       STATUS,
       COMMENT
FROM information_schema.INNODB_METRICS
WHERE NAME IN ('trx_rseg_history_len')\G

실행 결과(MySQL 8.0.46):

mysql> SELECT NAME,
    ->        SUBSYSTEM,
    ->        COUNT,
    ->        STATUS,
    ->        COMMENT
    -> FROM information_schema.INNODB_METRICS
    -> WHERE NAME IN ('trx_rseg_history_len');

*************************** 1. row ***************************
     NAME: trx_rseg_history_len
SUBSYSTEM: transaction
    COUNT: 5
   STATUS: enabled
  COMMENT: Length of the TRX_RSEG_HISTORY list
1 row in set (0.00 sec)

운영 점검에서는 값을 한 번 보는 것으로 끝내지 말고, 1분 또는 5분 간격으로 추세를 기록해야 한다. 값이 높은데 감소하고 있다면 purge가 따라잡는 중일 수 있다. 반대로 쓰기량이 줄었는데도 계속 증가하거나 높은 값에서 정체된다면 오래 열린 트랜잭션을 의심해야 한다.

오래 열린 InnoDB 트랜잭션은 다음 쿼리로 확인할 수 있다. 실제 운영 환경에서는 trx_started, trx_state, trx_query, trx_mysql_thread_id를 중심으로 오래된 트랜잭션을 정렬해 본다.

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
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
    -> FROM information_schema.INNODB_TRX
    -> ORDER BY trx_started
    -> LIMIT 10;

Empty set (0.00 sec)

이 쿼리가 빈 결과를 반환한다고 해서 purge 문제가 절대 없다는 뜻은 아니다. 관찰 시점에 오래 열린 트랜잭션이 끝났을 수 있고, purge backlog가 뒤늦게 줄어드는 중일 수 있다. 따라서 history list length의 시계열, 쓰기 TPS, undo tablespace 증가, 애플리케이션 배포·배치 시간대를 함께 맞춰 봐야 한다.

5. 재현형 예제로 보는 undo와 정리 대상

단일 SQL 블록만으로 여러 세션의 장기 read view와 purge 지연을 완전하게 재현하기는 어렵다. 다만 undo가 필요한 변경이 어떻게 만들어지고, 트랜잭션 경계가 어떤 식으로 쓰기를 묶는지는 작은 테이블로 확인할 수 있다. 다음 예제는 임시 테이블을 만들고 여러 번 갱신한 뒤 현재 값을 확인한다.

DROP TABLE IF EXISTS purge_note_demo;
CREATE TABLE purge_note_demo (
  id BIGINT PRIMARY KEY,
  payload VARCHAR(100) NOT NULL,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
    ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB;

INSERT INTO purge_note_demo (id, payload) VALUES (1, 'v1');
START TRANSACTION;
UPDATE purge_note_demo SET payload = 'v2' WHERE id = 1;
UPDATE purge_note_demo SET payload = 'v3' WHERE id = 1;
SELECT id, payload FROM purge_note_demo WHERE id = 1;
COMMIT;
DROP TABLE purge_note_demo;

실행 결과(MySQL 8.0.46):

mysql> DROP TABLE IF EXISTS purge_note_demo;

Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE purge_note_demo (
    ->   id BIGINT PRIMARY KEY,
    ->   payload VARCHAR(100) NOT NULL,
    ->   updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
    ->     ON UPDATE CURRENT_TIMESTAMP
    -> ) ENGINE=InnoDB;

Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO purge_note_demo (id, payload) VALUES (1, 'v1');

Query OK, 1 row affected (0.00 sec)

mysql> START TRANSACTION;

Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE purge_note_demo SET payload = 'v2' WHERE id = 1;

Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> UPDATE purge_note_demo SET payload = 'v3' WHERE id = 1;

Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT id, payload FROM purge_note_demo WHERE id = 1;

+----+---------+
| id | payload |
+----+---------+
|  1 | v3      |
+----+---------+
1 row in set (0.01 sec)

mysql> COMMIT;

Query OK, 0 rows affected (0.00 sec)

mysql> DROP TABLE purge_note_demo;

Query OK, 0 rows affected (0.00 sec)

이 예제는 동일 세션 안에서 실행되므로 오래 열린 다른 read view가 purge를 막는 상황까지 만들지는 않는다. 운영 환경의 실제 문제는 보통 “한 세션은 오래된 snapshot을 유지하고, 다른 세션들이 같은 기간 대량 변경을 수행하는” 구조에서 발생한다. 그런 상황은 부하 테스트 또는 별도 세션을 사용하는 재현 환경에서 확인해야 하며, 운영 DB에서 무리하게 재현해서는 안 된다.

6. purge thread 관련 설정과 해석

MySQL 8.0에서 purge 관련 설정은 버전과 배포판에 따라 기본값이 다를 수 있으므로 실제 값을 확인해야 한다.

SHOW VARIABLES LIKE 'innodb_purge%';

실행 결과(MySQL 8.0.46):

mysql> SHOW VARIABLES LIKE 'innodb_purge%';

+--------------------------------------+-------+
| Variable_name                        | Value |
+--------------------------------------+-------+
| innodb_purge_batch_size              | 300   |
| innodb_purge_rseg_truncate_frequency | 128   |
| innodb_purge_threads                 | 4     |
+--------------------------------------+-------+
3 rows in set (0.00 sec)

대표적으로 innodb_purge_threads는 purge 작업을 수행하는 백그라운드 스레드 수와 관련된다. 쓰기량이 많은 시스템에서는 이 값을 늘리는 것이 도움이 될 수 있지만, 오래 열린 트랜잭션이 purge의 진행 지점을 막고 있다면 스레드 수를 늘려도 본질적인 해결이 되지 않는다. purge가 갈 수 있는 지점 자체가 고정되어 있기 때문이다.

운영 해석은 다음 순서가 안전하다.

  1. history list length가 증가하는지, 또는 높은 값에서 정체되는지 확인한다.
  2. 같은 시간대의 오래 열린 트랜잭션을 확인한다.
  3. 쓰기량, 배치, 대량 DELETE/UPDATE, 온라인 스키마 변경 작업을 대조한다.
  4. 오래 열린 트랜잭션이 있다면 먼저 종료 또는 애플리케이션 수정으로 read view를 해제한다.
  5. 그 뒤에도 purge가 따라잡지 못하면 purge thread, I/O, buffer pool, undo tablespace, 작업 분할 전략을 검토한다.

설정부터 바꾸는 접근은 위험하다. 특히 짧은 장애 대응 상황에서 innodb_purge_threads만 올리고 오래 열린 트랜잭션을 놓치면, 근본 원인은 남아 있고 장애 시간만 길어진다.

7. Aurora MySQL에서의 관찰 포인트

Aurora MySQL도 MySQL 호환 InnoDB 계층을 제공하므로 MVCC, undo, purge, 오래 열린 트랜잭션의 기본 원리는 유사하다. 다만 운영 해석에서는 몇 가지 차이가 있다.

첫째, Aurora는 분산 스토리지 계층을 사용하므로 로컬 파일 시스템의 undo tablespace 증가만으로 모든 영향을 설명하기 어렵다. 스토리지 사용량, redo/undo 관련 내부 작업, 복제 지연, reader 인스턴스의 쿼리 패턴을 함께 봐야 한다.

둘째, Aurora Reader에서 오래 실행되는 읽기 쿼리와 Writer의 쓰기 부하가 결합될 수 있다. Reader가 제공하는 일관성 모델과 엔진 버전에 따라 관찰 지표가 다르게 보일 수 있으므로, Writer와 Reader 각각의 Performance Insights, CloudWatch 지표, information_schema.INNODB_TRX 조회 결과를 분리해서 확인하는 편이 좋다.

셋째, Aurora에서는 장애 조치와 재시작 시간이 운영 목표에 직접 연결된다. history list가 과도하게 쌓인 상태에서 장애가 발생하면 복구와 내부 정리 작업이 길어질 수 있다. 따라서 “서비스가 아직 느리지 않다”는 이유로 높은 history list length를 오래 방치하면 안 된다.

Aurora 운영에서는 다음 관점을 추가한다.

  • Writer와 Reader 중 어느 인스턴스에서 오래 열린 트랜잭션이 관찰되는지 구분한다.
  • Performance Insights에서 wait/io, wait/lock, 긴 SQL, 세션 상태를 함께 확인한다.
  • 대량 변경 배치는 작은 단위로 나누고, 커밋 간격을 명확히 둔다.
  • 장애 조치 전후에 history list length와 긴 트랜잭션이 어떻게 변하는지 기록한다.

8. 장애 모드와 흔한 오해

8.1 Sleep 세션은 안전하다는 오해

SHOW PROCESSLIST에서 Sleep으로 보이는 세션도 열린 트랜잭션을 유지할 수 있다. 커넥션 풀에서 트랜잭션을 닫지 않은 채 반환한 경우가 대표적이다. 따라서 세션 상태만 보지 말고 information_schema.INNODB_TRX에서 실제 InnoDB 트랜잭션 시작 시각을 확인해야 한다.

8.2 history list length의 절대값만 보는 오해

어떤 값부터 장애인지는 시스템의 쓰기량, 데이터 크기, 쿼리 패턴, 하드웨어, Aurora 여부에 따라 다르다. 중요한 것은 기준선 대비 증가율, 높은 상태의 지속 시간, purge가 따라잡는 속도다. 같은 100만이라는 값도 몇 분 뒤 빠르게 줄어드는 시스템과 몇 시간 동안 증가하는 시스템의 위험도는 다르다.

8.3 대량 DELETE 후 바로 공간이 줄어들 것이라는 오해

DELETE는 행을 delete-mark하고 undo를 남긴다. purge가 지나가야 인덱스와 내부 구조에서 정리될 수 있으며, 테이블스페이스 파일 크기가 즉시 줄어드는 것도 아니다. 공간 회수까지 기대한다면 OPTIMIZE TABLE, 테이블 재구성, 파티션 drop 같은 별도 전략이 필요할 수 있다. Aurora에서는 스토리지 과금과 내부 재사용 방식도 함께 고려해야 한다.

8.4 purge thread를 늘리면 항상 해결된다는 오해

purge thread는 정리할 수 있는 대상을 더 병렬로 처리하게 도울 수 있지만, 오래된 read view가 정리 가능 지점을 막고 있으면 효과가 제한적이다. 원인이 애플리케이션 트랜잭션 누수라면 설정 튜닝이 아니라 트랜잭션 경계 수정이 우선이다.

9. 운영 대응 절차

history list length가 비정상적으로 증가할 때는 다음 순서로 대응한다.

  1. 지표 확인: trx_rseg_history_len, 쓰기 TPS, CPU, I/O, buffer pool dirty page, undo 관련 지표를 함께 확인한다.
  2. 오래 열린 트랜잭션 식별: information_schema.INNODB_TRX에서 가장 오래된 트랜잭션과 연결된 thread id를 찾는다.
  3. 세션 소유자 확인: performance_schema.threads, 애플리케이션 커넥션 속성, 접속 계정, 호스트, 쿼리 패턴을 대조한다.
  4. 업무 영향 평가: 단순 방치 세션인지, 중요한 배치인지, 롤백 비용이 큰 대량 변경 트랜잭션인지 확인한다.
  5. 안전한 종료 결정: 필요하면 애플리케이션 owner와 협의해 KILL QUERY 또는 KILL CONNECTION을 사용한다. 대량 변경 트랜잭션을 강제 종료하면 rollback 자체가 오래 걸릴 수 있다.
  6. purge 회복 관찰: 트랜잭션 종료 후 history list length가 감소하는지, I/O와 CPU가 purge 작업으로 증가하는지 관찰한다.
  7. 재발 방지: 트랜잭션 timeout, 커넥션 풀 반환 시 rollback, 배치 커밋 단위, 장기 리포트 쿼리 분리, 모니터링 알람을 정비한다.

다음은 오래 열린 트랜잭션과 MySQL thread를 연결해서 보는 기본 형태다. 환경에 따라 PROCESSLIST_INFO는 NULL일 수 있으므로, 애플리케이션 로그와 함께 해석해야 한다.

SELECT t.trx_id,
       t.trx_state,
       t.trx_started,
       TIMESTAMPDIFF(SECOND, t.trx_started, NOW()) AS trx_age_seconds,
       t.trx_mysql_thread_id,
       th.PROCESSLIST_USER,
       th.PROCESSLIST_HOST,
       LEFT(th.PROCESSLIST_INFO, 120) AS current_statement
FROM information_schema.INNODB_TRX AS t
LEFT JOIN performance_schema.threads AS th
  ON th.PROCESSLIST_ID = t.trx_mysql_thread_id
ORDER BY t.trx_started
LIMIT 10;

실행 결과(MySQL 8.0.46):

mysql> SELECT t.trx_id,
    ->        t.trx_state,
    ->        t.trx_started,
    ->        TIMESTAMPDIFF(SECOND, t.trx_started, NOW()) AS trx_age_seconds,
    ->        t.trx_mysql_thread_id,
    ->        th.PROCESSLIST_USER,
    ->        th.PROCESSLIST_HOST,
    ->        LEFT(th.PROCESSLIST_INFO, 120) AS current_statement
    -> FROM information_schema.INNODB_TRX AS t
    -> LEFT JOIN performance_schema.threads AS th
    ->   ON th.PROCESSLIST_ID = t.trx_mysql_thread_id
    -> ORDER BY t.trx_started
    -> LIMIT 10;

Empty set (0.00 sec)

10. 예방을 위한 설계 기준

오래 열린 트랜잭션 문제는 모니터링만으로는 충분하지 않다. 애플리케이션과 운영 절차의 기본 설계에 다음 기준을 넣어야 한다.

  • 웹 요청 하나보다 긴 트랜잭션을 만들지 않는다. 필요한 경우 명시적인 배치 작업으로 분리한다.
  • 읽기 전용 리포트도 트랜잭션 범위를 짧게 유지한다.
  • 커넥션 풀은 커넥션 반환 시 열린 트랜잭션을 rollback하도록 설정한다.
  • 대량 변경은 작은 chunk로 나누고 각 chunk마다 commit한다.
  • 배치 작업은 LIMIT 기반 반복, primary key 범위 분할, 작업 간 sleep을 사용해 purge와 복제 지연이 따라올 시간을 준다.
  • ORM의 lazy loading, streaming cursor, transaction propagation 설정을 점검한다.
  • history list length 증가율과 장기 트랜잭션 나이를 알람 기준에 포함한다.
  • 운영자가 수동으로 START TRANSACTION WITH CONSISTENT SNAPSHOT을 사용했다면 작업 종료 후 즉시 commit 또는 rollback한다.

11. 점검 체크리스트

  • trx_rseg_history_len 또는 SHOW ENGINE INNODB STATUS
  • Sleep
  • 대량 UPDATE/DELETE

12. 결론

Purge thread와 history list length는 InnoDB의 내부 정리 작업을 보여주는 단순 지표처럼 보이지만, 실제로는 애플리케이션 트랜잭션 설계와 운영 습관을 드러내는 신호다. 오래 열린 트랜잭션은 잠금을 잡고 있지 않아도 purge의 진행 지점을 막고, undo와 과거 행 버전을 쌓이게 하며, 시간이 지나 성능 저하와 복구 리스크로 이어질 수 있다.

운영자는 history list length를 단발성 숫자로 보지 말고, 쓰기량·장기 트랜잭션·purge 회복 속도와 함께 해석해야 한다. 다음 글들에서는 이러한 MVCC 기반 위에서 잠금 대기, deadlock, 대량 변경 작업, 백업 일관성 같은 주제를 더 구체적으로 연결해 살펴볼 수 있다.