테마
디스크 I/O와 인덱스
인덱스가 왜 필요한가. 답은 한 줄이다. 디스크에서 필요한 페이지(page)만 골라 읽기 위해서다. 페이지는 DB가 디스크와 메모리 사이에서 데이터를 옮기는 최소 단위 블록이다. 이 답을 이해하려면 흩어진 곳을 건너뛰며 읽는 랜덤 I/O(random I/O)와 차례로 읽는 순차 I/O(sequential I/O)의 차이에서 출발해야 한다. I/O(Input/Output, 입출력)는 디스크와 데이터를 주고받는 일을 말한다.
랜덤 I/O vs 순차 I/O
- 순차 I/O: 디스크의 연속된 영역을 차례로 읽는다. 테이블의 모든 행을 처음부터 끝까지 읽는 풀 테이블 스캔(full table scan)은 페이지를 논리적 순서대로 훑는다. 게다가 곧 읽을 페이지를 요청 전에 미리 올려 두는 미리 읽기(read-ahead)의 도움을 받을 수 있어, 흩어진 행을 하나씩 찾는 랜덤 조회보다 효율적인 경우가 많다. 다만 논리적 순서대로 읽는다는 뜻이지, 실제 페이지가 디스크에 항상 물리적으로 연속해 있다는 뜻은 아니다.
- 랜덤 I/O: 디스크 곳곳에 흩어진 페이지를 건너뛰며 읽는다. HDD는 읽을 때마다 데이터가 있는 위치로 헤드(head, 디스크 위를 움직이며 읽는 장치)를 옮겨야 해서 느리다. SSD는 움직이는 헤드가 없어 순차와의 격차가 줄지만, 그래도 읽는 페이지 수 자체가 적을수록 빠르다.
인덱스가 하는 일은 테이블 전체를 순차로 스캔하는 대신 필요한 행이 있는 페이지만 랜덤으로 집어내는 것이다. 읽는 페이지 수가 100만 개에서 수십 개로 줄면, 랜덤 I/O 한 번의 비용(단가)이 순차보다 비싸도 전체 시간은 압도적으로 짧아진다.
SSD 시대에도 인덱스가 필요한 이유
"SSD는 랜덤 I/O가 빠르니 인덱스가 덜 중요하다"는 반만 맞다. SSD에서도 (1) 읽는 페이지 수가 적을수록 좋고, (2) 메모리 캐시인 버퍼 풀(buffer pool)에 올라가는 데이터가 적을수록 캐시 효율이 좋고, (3) 인덱스가 이미 정렬돼 있어 정렬·집계 작업을 피할 수 있다. 그래서 SSD 시대 인덱스의 가치는 I/O 한 번의 단가를 아끼는 데보다 DB가 해야 할 작업량 절감에 있다.
버퍼 풀과의 관계
InnoDB는 디스크에서 읽은 페이지를 버퍼 풀에 캐시해 두고, 같은 페이지가 다시 필요하면 디스크 대신 메모리에서 읽는다. 인덱스로 좁게 읽으면 자주 쓰는 페이지 묶음인 작업 집합(working set)이 작아진다. 작업 집합이 작을수록 한정된 버퍼 풀 안에 잘 들어가 캐시 효율이 좋아진다. 반대로 큰 풀 테이블 스캔은 한 번만 쓸 페이지를 대량으로 올려 기존 페이지를 밀어내는 캐시 압력(cache pressure)을 높인다. InnoDB는 올드 서브리스트(old sublist)1와 미리 읽기 같은 정책으로, 자주 쓰는 페이지가 한꺼번에 밀려나는 것을 완화한다.
면접 포인트
- "인덱스가 왜 빠른가요?" → 읽는 페이지 수를 줄여서. 랜덤/순차 I/O 단어로 설명.
- "그럼 인덱스를 많이 만들면?" → 쓰기 비용(INSERT·UPDATE마다 인덱스 갱신)과 공간. 읽기·쓰기 트레이드오프.
- "풀 테이블 스캔이 인덱스보다 빠른 경우?" → 테이블 대부분을 읽을 때. 옵티마이저(optimizer)가 예상 행 수(
rows) 비율로 판단한다(옵티마이저, 실행 계획 참고).
참고
버퍼 풀의 페이지 목록 중 오래된 쪽 구역. 새로 읽은 페이지는 여기에 먼저 들어가고 다시 쓰여야 자주 쓰는 구역으로 올라가므로, 한 번 읽고 마는 페이지가 자주 쓰는 페이지를 쉽게 밀어내지 못한다. ↩︎
