아무리 최신 자동차라도 휘발유 대신 레모네이드를 넣으면 멈춰버리잖아. PostgreSQL의 인덱스도 마찬가지야. 진짜 강력한 도구지만, 제대로 써야 효과가 있어. 인덱스와 관련된 몇 가지 흔한 문제들을 같이 보자.
문제 1: 과도한 인덱싱
먼저, 예전 강의에서 다뤘던 내용을 잠깐 복습할게. 한 테이블에 인덱스가 너무 많으면, PostgreSQL은 그걸 다 관리하느라 힘들어져. 이건 INSERT, UPDATE, DELETE 같은 쓰기, 갱신, 삭제 작업에 직접적으로 영향을 줘. 인덱스마다 다 업데이트하고 동기화해야 하니까!
예를 들어, students라는 테이블이 있다고 해보자:
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(255) UNIQUE,
age INTEGER,
grade INTEGER
);
그리고 혹시 몰라서 모든 컬럼에 인덱스를 만든다고 해보자:
CREATE INDEX idx_students_name ON students(name);
CREATE INDEX idx_students_age ON students(age);
CREATE INDEX idx_students_grade ON students(grade);
이제 1만 개의 새 데이터를 넣는다고 상상해봐. PostgreSQL은 테이블에 데이터만 넣는 게 아니라, 저 세 개의 인덱스도 다 업데이트해야 해. 데이터가 많아질수록 쓰기 속도가 확 떨어지고, 시스템 성능도 나빠져.
이 문제를 어떻게 피할까? 인덱스를 만들기 전에 스스로 두 가지 질문을 해봐:
- 이 컬럼이 필터링(
WHERE), 정렬(ORDER BY), 그룹핑(GROUP BY)에 얼마나 자주 쓰이지? - 쿼리가 이 인덱스를 실제로 쓸까, 아니면 테이블 전체 스캔을 할까?
둘 다 "거의 안 써" 혹은 "전혀 안 써"라면, 인덱스는 필요 없어.
문제 2: 잘못된 컬럼에 인덱스 만들기
변별력 없는 데이터에 인덱스를 만드는 건, 뚜껑이 꽉 막힌 컵에 차를 따르려는 거랑 똑같아: 거의 쓸모가 없어. 컬럼에 고작 2~3개의 유니크 값만 있으면, PostgreSQL은 인덱스 대신 테이블 전체를 그냥 스캔할 확률이 높아.
예를 들어, courses라는 테이블이 있다고 하자:
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
level VARCHAR(10) -- 'Beginner', 'Intermediate', 'Advanced' 중 하나만 가능
);
그리고 level 컬럼에 인덱스를 만든다고 해보자:
CREATE INDEX idx_courses_level ON courses(level);
하지만 이런 쿼리:
SELECT * FROM courses WHERE level = 'Beginner';
는 인덱스를 안 쓸 수도 있어. PostgreSQL 입장에선 인덱스 보는 것보다 그냥 테이블 전체를 스캔하는 게 더 빠를 수 있거든. 특히 테이블이 작거나 값이 몇 개 안 되면 더 그래.
그래서 인덱스는 고유값이 많은 컬럼(카디널리티가 높은 컬럼)에만 의미가 있어. 값이 적은 컬럼엔 파티셔닝 같은 다른 최적화 방법을 쓰는 게 더 나아.
문제 3: 오래된 인덱스
가끔 인덱스를 만들어놓고, 나중에 안 쓰는데도 그냥 놔두는 경우가 있어. 이건 바탕화면에 파일 쌓이는 거랑 똑같아: 처음엔 두세 개뿐인데, 어느새 엄청 많아져서 필요한 거 찾느라 시간 다 보내는 거지... 공감하지?
예를 들어, 예전 기능 때문에 인덱스를 만들었는데, 쿼리 로직이 바뀌면서 새 인덱스를 추가했다고 해보자. 이제 옛날 인덱스는 아무도 안 쓰는데, 계속 공간 차지하고 쓰기 작업만 느려지게 만들어.
이런 걸 막으려면, 인덱스를 주기적으로 점검하고 분석해야 해. PostgreSQL에는 이런 걸 볼 수 있는 좋은 메트릭이 있어:
SELECT
relname AS table_name,
indexrelname AS index_name,
idx_scan AS total_scans
FROM
pg_stat_user_indexes
WHERE
idx_scan = 0;
여기서 idx_scan은 해당 인덱스를 쿼리가 몇 번 썼는지 보여줘. 값이 0이면, 인덱스가 안 쓰인다는 뜻이니까 삭제해도 돼:
DROP INDEX idx_courses_level;
문제 4: 자주 바뀌는 컬럼에 인덱스 만들기
자주 업데이트되는 컬럼에 인덱스가 있으면, PostgreSQL은 그때마다 인덱스를 다시 만들어야 해. 이게 성능을 꽤 많이 떨어뜨릴 수 있어.
예를 들어, 주문 정보를 담은 테이블이 있다고 해보자:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
status VARCHAR(20), -- 여러 번 바뀔 수 있음 (예: "새로 생성됨", "진행 중", "완료됨")
total NUMERIC(10, 2)
);
그리고 status 컬럼에 인덱스를 만들어서 상태별로 빠르게 찾으려고 한다고 해보자:
CREATE INDEX idx_orders_status ON orders(status);
근데 만약 status가 한 레코드마다 수십 번씩 바뀐다면, 인덱스가 오히려 성능을 망칠 수 있어.
이런 상황을 피하려면, 자주 바뀌는 컬럼엔 인덱스를 만들지 않는 게 좋아. 꼭 필요하다면, 부분 인덱스(partial index)를 고려해봐:
CREATE INDEX idx_orders_status_partial
ON orders(status)
WHERE status = '진행 중';
이렇게 하면, 특정 값에 대해서만 인덱스가 갱신돼.
문제 5: 필요 없는 컬럼에 UNIQUE 제약 걸기
유니크 인덱스(UNIQUE)는 데이터의 유일성을 보장하기 위해 자동으로 만들어져. 근데 유일성이 꼭 필요하지 않으면, 이런 인덱스는 쓸데없는 부담만 줘.
예를 들어, 로그 테이블을 만든다고 해보자:
CREATE TABLE logs (
id SERIAL PRIMARY KEY,
message TEXT,
created_at TIMESTAMP UNIQUE
);
매초마다 수천 개의 로그가 들어온다면, created_at의 유일성 체크 때문에 부하가 엄청 커져.
잘 쓰려면, UNIQUE 제약은 진짜 필요한 곳에만 남겨둬. 위 예시에서 created_at의 유일성이 필요 없다면, 그냥 일반 인덱스로 바꿔:
CREATE INDEX idx_logs_created_at ON logs(created_at);
문제 6: 잘못된 복합 인덱스 사용
복합 인덱스(multi-column indexes)는 쿼리가 여러 컬럼을 동시에 필터링하거나 정렬할 때 유용해. 근데 순서를 잘못 만들면, 인덱스가 아예 안 쓰일 수도 있어.
예를 들어, 이런 인덱스가 있다고 하자:
CREATE INDEX idx_students_name_grade ON students(name, grade);
이 인덱스는 아래처럼 두 컬럼을 같이 필터링하거나 정렬할 때 쓰여:
SELECT * FROM students WHERE name = 'Alice' AND grade = 90;
근데 이런 쿼리는:
SELECT * FROM students WHERE grade = 90;
이 인덱스를 안 써. 왜냐면 name이 먼저 나오기 때문이야.
이런 문제를 피하려면, 복합 인덱스는 쿼리에서 자주 쓰는 컬럼 순서대로 만들어야 해. 만약 한 컬럼만 필터링할 일이 많으면, 따로 인덱스를 만들어줘.
꿀팁 모음
인덱스 사용 현황을 꼭 모니터링해. PostgreSQL에는 pg_stat_user_indexes라는 시스템 뷰가 있어서, 어떤 인덱스가 쓰이고 있는지 바로 볼 수 있어.
인덱스만 신경 쓰지 말고 쿼리도 같이 최적화해. 구린 쿼리는 인덱스가 있어도 구려.
삭제도 잊지 마. 오래된 인덱스는 공간만 차지하고 쓰기 작업만 느리게 해.
여기까지야, 친구들! 인덱스는 진짜 강력한 무기지만, 큰 힘에는 큰 책임이 따르는 법. 인덱스를 똑똑하게 써서, 너희 데이터베이스가 SpaceX 로켓처럼 날아가게 만들어봐!
GO TO FULL VERSION