Zettelkasten

InnoDB free list는 페이지가 통째로 비거나 채움률 50% 미만일 때 생긴다

·수정 1

요약

  • InnoDB는 행을 PK 순서로 정렬된 클러스터드 인덱스(B+트리) 페이지에 저장하고, 빈 공간 재사용은 "PK 위치"가 아니라 "free list 단위"로 일어난다.
  • free page는 페이지가 100% 비거나 채움률이 MERGE_THRESHOLD(기본 50%) 미만으로 떨어질 때만 생긴다. 반쯤 빈 페이지는 free list로 안 돌아가 단편화로 남는다.
  • free list로 돌아와도 .ibd 파일은 OS로 공간을 반환하지 않는다. 실제 디스크 회수는 DROP PARTITION / TRUNCATE / OPTIMIZE뿐.

본문

auto_increment가 페이지를 채우는 방식

InnoDB 테이블은 PK로 정렬된 B+트리(클러스터드 인덱스)이고, 각 리프 페이지는 자기가 담당하는 PK 범위를 가진다. 새 행은 "빈 공간 있는 페이지"가 아니라 PK 값이 속하는 페이지로 들어간다 — 정렬을 유지해야 범위 스캔·이진 탐색이 되기 때문.

  • InnoDB는 페이지의 1/16을 미래 삽입·갱신용으로 비워둔다.
  • auto_increment처럼 순차(오름/내림차순) 삽입이면 삽입점이 항상 오른쪽 끝 1곳 → 페이지가 약 15/16(≈93.75%)까지 빽빽하게 채워진다.
  • 랜덤(UUID 등) 삽입이면 삽입점이 트리 전체에 분산 → 대상 페이지가 꽉 차면 페이지 분할(page split) 발생, 페이지 채움률이 1/2 ~ 15/16으로 헐거워지고 랜덤 I/O가 는다.

→ 흔한 오해와 반대로, 순차 삽입이 공간 효율·쓰기 성능 모두 더 좋다. 단편화는 "삽입 방식"이 아니라 "삭제로 페이지가 반쯤 비느냐"의 문제다.

free list(free page)가 생기는 조건

삭제·갱신으로 페이지 채움률이 줄 때:

  • 페이지가 100% 비면 → 그 페이지가 free list로 반환된다. 이후 INSERT가 오른쪽 끝에서 새 페이지를 필요로 할 때 이 free page를 꺼내 재활용한다(빈 껍데기에 새 키 범위 부여).
  • 채움률이 MERGE_THRESHOLD(기본 50%) 미만으로 떨어지면 → InnoDB가 인접 페이지와 병합해 트리를 축소하고 페이지 하나를 free list로 돌린다.
  • 반쯤(50% 이상) 찬 페이지 → free list로 안 감. auto_increment에선 그 빈 슬롯에 옛 키 범위의 행이 다시 안 들어오므로 갇힌 slack(단편화) 으로 남는다.

삭제 패턴이 결과를 가른다

  • 연속 범위 일괄 삭제(예: 오래된 시간/PK 앞쪽 통째) → 페이지가 통째로 비워짐 → free list로 대량 반환, 깔끔하게 재활용. 파일 크기는 어느 선에서 평평해진다.
  • 산발적 삭제(흩어진 행들) → 한 페이지에서 몇 행씩만 빠짐 → 반쯤 빈 페이지만 잔뜩 남음 → 돌아오는 페이지는 적고 단편화만 는다.

공통 함정: 파일은 OS로 안 돌아온다

free page가 아무리 많아도 .ibd 파일의 high-water mark는 유지된다. "이 테이블이 앞으로 INSERT 때 쓸 내부 여유분"일 뿐, OS의 가용 디스크(RDS FreeStorageSpace 등)로는 안 돌아온다.

실제 디스크 회수 방법:

  • DROP PARTITION / DROP TABLE / TRUNCATE TABLE (file-per-table) → 파일째 OS 반환. free list 단계를 건너뛴다.
  • OPTIMIZE TABLE → 테이블 재구축으로 단편화 압축, 새 파일로 교체.

이것이 retention 관리에서 DELETE보다 RANGE 파티션 + DROP PARTITION이 결정적인 이유다.

관련 노트

참고