[실전 DB 03] 인덱스 기초, 색인이 곧 속도이자 비용이다

측정으로 무거운 쿼리를 찾았으면, 이제 고칠 차례다. 가장 강력한 무기가 인덱스다. 인덱스 하나로 수천 행을 훑던 쿼리가 수백 행만 읽게 되고, 응답은 빨라지고 비용은 준다. 이번 편은 인덱스가 무엇이고 왜 그렇게 효과가 큰지, 그 기초 원리를 다룬다.


인덱스는 데이터베이스 최적화의 핵심이라, 이 원리를 확실히 이해하면 이후의 모든 최적화가 쉬워진다.

1. 인덱스는 책 뒤의 색인이다

인덱스를 이해하는 가장 좋은 비유가 책 뒤의 찾아보기, 즉 색인이다. 두꺼운 책에서 특정 단어를 찾는다고 하자. 색인이 없으면 첫 장부터 끝까지 넘기며 그 단어를 찾아야 한다. 하지만 책 뒤에 색인이 있으면, 그 단어가 몇 페이지에 있는지 바로 찾아 그 페이지로 간다.


데이터베이스 인덱스가 정확히 이 역할이다. 특정 컬럼에 대해 미리 정렬된 찾아보기 표를 만들어두는 것이다. 인덱스가 없으면 데이터베이스는 조건에 맞는 행을 찾으려고 전체를 훑는다. 이게 전체 스캔이다. 인덱스가 있으면 그 정렬된 표에서 바로 원하는 위치를 찾아간다. 전체를 안 훑어도 되는 것이다.


이 차이가 앞서 다룬 읽기량을 극적으로 바꾼다. 오천 행을 훑던 쿼리가, 인덱스로 바로 찾아가면 몇 행만 읽는다. 색인을 넘기는 것과 책 전체를 넘기는 것의 차이다. 자주 실행되는 쿼리라면 이 차이가 호출 횟수만큼 곱해져 엄청난 절감이 된다.


색인 비유가 인덱스의 본질을 정확히 담는다. 색인 없는 책에서 단어를 찾으려면 전체를 넘겨야 하듯, 인덱스 없는 테이블에서 조건에 맞는 행을 찾으려면 전체를 훑어야 한다. 색인이 있으면 바로 그 페이지로 가듯, 인덱스가 있으면 바로 그 행으로 간다. 이 단순한 비유가 인덱스가 왜 그렇게 빠른지를 설명한다.


조금 더 배경을 주면, 인덱스는 데이터베이스 성능의 팔 할을 좌우한다. 다른 최적화들도 중요하지만, 무거운 쿼리 대부분은 알맞은 인덱스 하나로 해결된다. 그래서 최적화를 배울 때 인덱스를 가장 먼저, 가장 깊이 이해해야 한다. 인덱스를 제대로 다루면 데이터베이스 문제의 상당수가 저절로 풀린다.


한 가지 더, 인덱스는 읽기 성능만이 아니라 정렬에도 결정적이다. 인덱스는 이미 정렬된 상태로 저장되니, 그 컬럼으로 정렬하는 쿼리는 별도의 정렬 작업 없이 인덱스 순서를 그대로 쓴다. 정렬을 위해 전체를 임시로 다시 정렬하는 무거운 작업이 사라지는 것이다. 목록을 시각순으로 보여주는 흔한 쿼리가 이 덕을 크게 본다.

2. 어떤 컬럼에 인덱스를 만드나

인덱스는 조회 조건이나 정렬에 자주 쓰이는 컬럼에 만든다. 쿼리에서 특정 컬럼으로 거르거나 그 컬럼으로 정렬한다면, 그 컬럼에 인덱스가 있으면 효율적이다. 반대로 조회 조건에 안 쓰이는 컬럼에는 인덱스가 필요 없다.


예를 들어 어떤 가상의 글 테이블 articles에서 작성 시각순으로 목록을 보여준다고 하자. 이 경우 작성 시각 컬럼으로 정렬하니, 그 컬럼에 인덱스를 만든다. 인덱스 생성은 대체로 CREATE INDEX idx_articles_created ON articles(created_at) 같은 형태다. 이러면 시각순 정렬이 전체를 훑지 않고 인덱스를 타게 된다.


어떤 쿼리가 자주 실행되고 무엇으로 거르는지를 알아야 인덱스를 제대로 만들 수 있다. 그래서 앞 편의 측정이 선행돼야 한다. 측정으로 주범 쿼리를 찾고, 그 쿼리가 어떤 조건과 정렬을 쓰는지 보고, 거기에 맞는 인덱스를 만드는 것이다. 쿼리 패턴을 모르면 인덱스도 감으로 만들게 된다.


어떤 컬럼에 인덱스를 만들지는 쿼리 패턴이 결정한다. 자주 거르거나 정렬하는 컬럼에 인덱스를 만드는 것이다. 그래서 인덱스 설계는 측정으로 주범 쿼리를 찾는 데서 이어진다. 그 쿼리가 무엇으로 거르고 무엇으로 정렬하는지 보고, 거기에 맞는 인덱스를 만든다. 쿼리를 모르고 인덱스만 만드는 건 순서가 틀렸다.

3. 인덱스가 실제로 쓰이는지 확인한다

인덱스를 만들었다고 데이터베이스가 항상 쓰는 건 아니다. 데이터베이스의 실행 계획 결정 로직이 이 쿼리엔 인덱스보다 전체 스캔이 낫다고 판단하면 인덱스를 안 쓸 수 있다. 그래서 인덱스를 만든 뒤엔 실제로 그게 쓰이는지 확인해야 한다.


확인은 앞 편에서 다룬 실행 계획으로 한다. 인덱스를 만든 쿼리의 실행 계획을 봐서, 인덱스를 타는 검색으로 바뀌었는지 확인한다. 실행 계획에 인덱스 이름이 나오면 성공이다. 여전히 전체 스캔이 나오면 인덱스가 안 쓰이는 것이고, 원인을 찾아야 한다.


인덱스를 만들었다는 것과 인덱스가 쓰인다는 것은 다르다. 만들고 방심하면 여전히 전체 스캔을 하고 있을 수 있다. 왜 안 쓰이는지는 통계 문제일 수도, 쿼리 구조 문제일 수도 있다. 이건 뒤에서 인덱스를 만들어도 안 쓰일 때 편에서 자세히 다룬다. 지금은 만든 뒤 반드시 확인한다는 원칙만 기억하자.


인덱스를 만들었다고 쓰이는 게 아니라는 점이 중요하다. 데이터베이스가 판단해서 안 쓸 수 있으니, 만든 뒤 실행 계획으로 실제 사용을 확인해야 한다. 실행 계획에 인덱스 이름이 나오면 성공이고, 여전히 전체 스캔이면 원인을 찾아야 한다. 만들었다와 쓰인다를 구분하는 이 습관이 최적화를 완성한다.


조금 더 실무적으로, 인덱스가 안 쓰이는 경우를 미리 알아두면 당황하지 않는다. 데이터베이스가 통계 정보가 없거나 낡으면 잘못된 판단을 하고, 쿼리 구조가 인덱스와 안 맞으면 못 쓴다. 그래서 만들고 확인하는 습관이 필수다. 이 안 쓰이는 경우들은 뒤 편에서 하나씩 다루니, 지금은 확인의 중요성만 기억하면 된다.

4. 인덱스의 효과 체감

인덱스의 효과는 겪어보면 극적이다. 내가 읽기 폭증을 잡을 때, 어떤 목록 쿼리가 호출당 수천 행을 읽고 있었다. 그 쿼리의 조건에 맞는 인덱스를 만들었더니, 호출당 읽는 행이 수백으로, 구할 넘게 줄었다. 같은 쿼리가 같은 결과를 내는데 읽기량만 몇 분의 일이 됐다.


이게 자주 실행되는 쿼리였기에 효과가 더 컸다. 봇들이 그 목록을 끊임없이 긁고 있었는데, 인덱스 하나로 그 모든 호출의 읽기량이 줄었다. 인덱스 하나가 전체 읽기량을 크게 떨어뜨린 것이다. 인덱스가 곧 비용 절감이라는 걸 이렇게 몸으로 배웠다.


그리고 이 절감은 응답 속도로도 이어진다. 적게 읽는 쿼리는 빠르게 응답한다. 인덱스로 목록 쿼리가 빨라지니 페이지도 빨라졌다. 비용이 주는 동시에 사용자 경험도 좋아진 것이다. 인덱스는 최적화에서 가장 가성비 높은 무기다.


인덱스의 효과를 체감하면 그 위력을 실감한다. 호출당 수천 행을 읽던 쿼리가 인덱스 하나로 수백 행만 읽게 되고, 그게 자주 실행되는 쿼리면 전체 읽기량이 크게 준다. 같은 결과를 내는데 비용만 몇 분의 일이 되는 것이다. 인덱스가 최적화에서 가장 가성비 높은 무기인 이유다.

5. 인덱스는 만능이 아니다

다만 인덱스가 만능은 아니다. 인덱스도 대가가 있다. 인덱스를 만들면 그만큼 저장 공간을 쓰고, 데이터를 쓸 때마다 인덱스도 갱신해야 해서 쓰기가 조금 느려진다. 그래서 아무 컬럼에나 인덱스를 다 걸면 오히려 낭비다.


인덱스는 읽기 이득과 쓰기 비용을 저울질해 선별적으로 만들어야 한다. 자주 조회 조건에 쓰이는 컬럼에는 인덱스로 크게 이득을 보지만, 쓰기만 많고 조회 조건에 안 쓰이는 컬럼에 인덱스를 걸면 쓰기 비용만 늘고 이득이 없다. 이 균형을 잡는 게 인덱스 설계의 요령이다. 이건 뒤에서 인덱스도 공짜가 아니다 편에서 깊이 다룬다.


정리하면 인덱스는 책 뒤의 색인처럼 전체를 훑지 않고 바로 찾아가게 해서 읽기량을 극적으로 줄이고, 자주 조회되는 컬럼에 선별해 만들되 실제로 쓰이는지 확인해야 한다. 다음 편에서는 이 인덱스가 실제로 쓰이는지 판단하는 도구, 실행 계획을 읽는 법을 본격적으로 다룬다. 스캔과 검색을 구분하는 게 핵심이다.


인덱스가 만능이 아니라는 것도 균형 있게 알아야 한다. 인덱스도 저장 공간을 쓰고 쓰기를 느리게 하니, 아무 데나 다 걸면 낭비다. 읽기 이득과 쓰기 비용을 저울질해 선별적으로 만들어야 한다. 이 균형 감각이 무작정 인덱스를 남발하는 것과 제대로 된 설계를 가른다.


인덱스의 균형을 잡는 감각이 초보와 숙련을 가른다. 초보는 인덱스가 좋다니 아무 데나 다 걸고, 숙련은 읽기 이득과 쓰기 비용을 저울질해 필요한 곳에만 건다. 자주 조회되는 컬럼에는 걸고, 쓰기만 많은 컬럼에는 안 거는 것이다. 이 선별이 인덱스를 무기로 만들지 짐으로 만들지를 결정한다.