한 줄 정의
디스크 I/O는 컴퓨터에서 가장 느린 작업이며, 데이터베이스 성능의 대부분은 디스크 헤드의 이동(랜덤 I/O)을 얼마나 줄이느냐 에 달려 있습니다.
쉽게 말하면
HDD를 LP 레코드 에 비유할 수 있습니다.
- 연속된 곡을 순서대로 듣는 것은 빠릅니다 (순차 I/O)
- 이 곡 조금, 저 곡 조금 건너뛰며 듣는 것은 바늘을 계속 옮겨야 하므로 느립니다 (랜덤 I/O)
SSD는 USB 메모리 에 비유할 수 있습니다.
- 기계적 움직임이 없으므로 어디를 읽든 거의 같은 속도입니다
- 하지만 여전히 “한 번에 한 블록씩 읽는 비용”은 존재합니다
결국 “디스크에 요청을 몇 번 하느냐”가 성능을 좌우하고, 인덱스의 목적은 이 요청 횟수를 줄이는 것 입니다.
왜 중요한가?
데이터베이스 성능 튜닝의 출발점은 “어디서 시간이 쓰이는가?” 를 아는 것입니다.
| 구성 요소 | 상대적 속도 |
|---|---|
| CPU 연산 | 매우 빠름 |
| 메모리 접근 | 빠름 |
| SSD I/O | 느림 (메모리의 1/1000 수준) |
| HDD 랜덤 I/O | 매우 느림 (SSD의 1/100 수준) |
| 네트워크 I/O | 상황에 따라 매우 느림 |
DB 쿼리에서 99%의 시간은 디스크 I/O에서 소비 됩니다. 따라서 CPU를 최적화하는 것보다 I/O를 줄이는 것 이 훨씬 효과적이며, 그 도구가 바로 인덱스입니다.
핵심 내용
HDD와 SSD의 차이
flowchart LR subgraph HDD["HDD (자기 디스크)"] direction TB Arm["디스크 헤드<br/>(물리적 이동)"] Platter["회전 플래터"] Arm --> Platter end subgraph SSD["SSD (플래시 메모리)"] direction TB Controller["컨트롤러"] Flash["플래시 셀<br/>(전자적 접근)"] Controller --> Flash end HDD -.->|기계 장치| 느림["랜덤 I/O가 치명적"] SSD -.->|전자 장치| 빠름["랜덤 I/O 페널티가 작음"]
HDD의 치명적 약점
HDD는 디스크 헤드를 물리적으로 이동 시켜야 데이터를 읽을 수 있습니다.
- 플래터가 분당 수천 번 회전합니다
- 원하는 트랙으로 헤드를 이동하는 탐색 시간(seek time) 이 발생합니다
- 원하는 섹터가 헤드 밑으로 올 때까지 기다리는 회전 지연(rotational latency) 이 발생합니다
데이터가 흩어져 있으면 이 두 지연이 매 읽기마다 발생합니다.
SSD의 강점과 한계
SSD는 기계적 이동이 없어 랜덤 I/O 페널티가 훨씬 작지만, 여전히 블록 단위로 읽어야 합니다. 또한 쓰기 시 페이지 지우기(erase) 비용 이 별도로 존재합니다.
SSD가 보편화됐다고 해서 인덱스가 덜 중요해진 것은 아닙니다. I/O 요청 횟수 자체를 줄이는 것 은 여전히 중요하며, 버퍼 풀 캐시 히트율도 인덱스 설계에 달려 있습니다.
랜덤 I/O vs 순차 I/O
| 구분 | 설명 | 특징 |
|---|---|---|
| 순차 I/O | 연속된 블록을 한 번에 읽기 | 디스크 헤드 이동 1회 |
| 랜덤 I/O | 흩어진 블록을 각각 읽기 | 블록마다 헤드 이동 |
쿼리 튜닝이 하는 일
쿼리 튜닝의 실체는 랜덤 I/O를 순차 I/O로 바꾸거나, 아예 I/O 횟수를 줄이는 것 입니다.
flowchart TD subgraph BAD["나쁜 실행 계획"] B1["랜덤 I/O 1000번"] end subgraph GOOD1["방법 1: 순차 I/O로 전환"] G1["범위 조건 + 인덱스 정렬<br/>→ 연속된 페이지 읽기"] end subgraph GOOD2["방법 2: I/O 자체 제거"] G2["커버링 인덱스<br/>→ 인덱스만으로 응답"] end BAD --> GOOD1 BAD --> GOOD2
- 방법 1: 인덱스 레인지 스캔으로 연속된 리프 페이지를 순차 읽기
- 방법 2: 커버링 인덱스로 테이블 접근 자체를 없애기
페이지/블록 단위 I/O
DB는 레코드 1건이 아니라 페이지(MySQL 기본 16KB) 단위로 읽고 씁니다.
- 레코드 1건만 필요해도 16KB를 통째로 디스크에서 읽습니다
- 따라서 한 페이지에 많은 레코드가 담길수록 I/O 효율이 좋아집니다
- 이것이 PK 크기와 레코드 크기가 성능에 영향을 주는 이유 입니다
페이지 크기가 16KB인 상황에서, 4KB 레코드는 한 페이지에 4건, 40바이트 레코드는 한 페이지에 400건 들어갑니다. 같은 양의 데이터를 읽어도 I/O 횟수가 100배 차이 납니다.
인덱스가 I/O를 줄이는 방식
인덱스가 도움이 되는 지점은 명확합니다.
- 풀 스캔보다 훨씬 적은 페이지만 읽음 — 리프 페이지의 필요 부분만 탐색
- 정렬된 상태로 저장 — 범위 검색 시 순차 I/O가 가능
- 리프 페이지 연결 리스트 — B-Tree의 리프는 좌우로 연결되어 있어 다음 페이지 이동이 쉬움
- 커버링 인덱스 — 데이터 파일을 아예 안 읽고 끝낼 수 있음
단, 인덱스 스캔 자체도 랜덤 I/O 입니다. 읽어야 할 레코드가 전체의 20~25%를 넘으면 옵티마이저가 풀 스캔(순차 I/O)을 선택하는 이유가 이것입니다.
내 생각
-
SSD 시대에도 “I/O를 줄여라”는 원칙은 여전히 유효합니다. 물리적 seek time은 사라졌지만, 버퍼 풀 캐시 효율 과 네트워크 왕복 시간 이 병목이 되기 때문입니다. 인덱스 설계 원칙은 스토리지 기술 변화와 무관하게 유효합니다.
-
“풀 스캔이 항상 나쁘다”는 오해도 조심해야 합니다. 읽어야 할 레코드가 테이블의 대부분이라면, 순차 I/O 풀 스캔이 랜덤 I/O 인덱스 스캔보다 훨씬 빠릅니다. 이것이 옵티마이저가 통계 정보로 판단하는 지점입니다.
-
실무에서 I/O 문제를 진단할 때는
SHOW ENGINE INNODB STATUS의 I/O 섹션과performance_schema의file_summary_by_event_name을 함께 봐야 합니다. 어떤 파일에 I/O가 집중되는지 알면 튜닝 방향이 명확해집니다.
관련 개념
- Ch04-2 InnoDB 스토리지 엔진 — 버퍼 풀, LRU 리스트, Read Ahead
- Ch08-3 B-Tree 인덱스 — 인덱스가 실제로 I/O를 줄이는 방식
- Ch08-8 클러스터링 인덱스 — PK 설계가 I/O 효율에 미치는 영향
출처
- Real MySQL 8.0 (1권), 8.1 디스크 읽기 방식