서비스 사용자가 늘어나면서 기존 레거시 코드가 가지고 있던 문제가 두드러지고 있다. 내가 입사하기 오래전부터 있던 문제인데, 오류가 발생할 때마다 수동으로 DB값을 업데이트하며 처리하고 있어 왔고, 나도 그렇게 하고 있다. 너무 비효율적인 방식이라 이를 개선해보고자, 요즘 DB 성능과 효율적인 코드에 대해 고민하고 있다.
이번주에는 특히 DB 인덱스와 관련해 살펴볼 일이 많았기 때문에 인덱스에 대해 간단히 정리해보려 한다. 자료구조와 관련된 내용도 나온다. 이 글에 같이 정리할까 고민했는데, 관련 글을 따로 작성해 링크 형식으로 첨부하는 것이 깔끔할 것 같아 자료구조에 관한 자세한 설명은 생략한다.
인덱스란?
인덱스의 특징
장점
- 인덱스는 쿼리 작업을 매우 효율적으로 만든다.
- 쿼리를 수행할 때 인덱스가 없다면 모든 로우를 일일이 조회해야 한다.
단점
- 인덱스를 만들면 새로운 로우를 생성하거나 제거하는 작업을 빈번하게 할 때 속도의 저하가 일어날 수 있다.
- 매번 인덱스를 업데이트 해야 하기 때문이다.
- 단순 인덱스를 만들면 해당 컬럼만 조회할 때 사용할 수 있다.
- 다수의 컬럼을 대상으로 조회를 할 때에는 복합 인덱스가 효율적이다.
다양한 인덱스 종류
B-Tree 인덱스
- 가장 기본적인 형태의 인덱스이다.
- PostgreSQl은 primary key를 생성하면 자동으로 B-Tree 방식의 인덱스를 생성한다.
- 프라이머리 키 값을 이용해서 정보를 찾는 일이 빈번하기 때문이다.
해시 인덱스
- B-Tree 구조와 해시화 함수를 이용해 인덱스를 하는 방식이다.
- 값을 직접 인덱싱하지 않고 값을 해시화 함수를 통해 더 작은 크기의 값으로 변형한 뒤 이 값을 기준으로 B-Tree 구조를 만든다.
장점
- 위와 같은 방식을 이용하면 B-Tree 구조만 사용했을 때보다 인덱스 크기면에서 훨씬 작아진다.
- 인덱스의 크기가 작아지면 인덱스를 메모리에 캐싱할 때도 이점이 있다
단점
- 해시화 함수를 거치면서 원래의 값을 변형하기 때문에 인덱스한 컬럼 값 사이의 크기 비교가 불가능해져 정렬과 비교 연산에 이 방식의 인덱스를 활용할 수 없다.
- 등호를 이용해서 값이 일치하는지 확인할 때만 사용할 수 있다
GIN
- Generalize Inverted Index
- Full Text Search를 주 목적으로 하는 인덱스
- 주로 긴 문자열이 들어가는 컬럼에 설정
컬럼 수에 따른 인덱스의 종류
단일 컬럼 인덱스
- 한 가지 종류의 인덱스 컬럼 값을 갖는 방식
- 대표적으로 primary key 컬럼을 갖는 인덱스
- 해당 컬럼에 어떤 값을 검색하거나, 값의 범위에 해당하는 로우를 불러올 때 사용할 수 있다
한계
- 두 가지 컬럼의 값을 동시에 일치하도록 검색하는 경우
- 한 가지 컬럼의 값을 먼저 찾은 후 다른 컬럼의 값은 하나한 대조해서 검색해야 한다
복합(Compound) 컬럼 인덱스
- 여러 개의 컬럼 값을 대상으로 하는 인덱스이다.
- 복합 인덱스는 순서가 중요하다.
- 값의 중복이 빈번한 (더 포괄적인) 컬럼을 우선적으로 설정해야 상대적으로 적은 횟수의 탐색으로 결과를 얻을 수 있다.
- a-b 복한 인덱스는 a 단순 인덱스와 같은 기능을 하기 때문에 대체할 수 있다.
실사용 데이터베이스 인덱스 설정하기
인덱스를 생성하려고 하는 테이블의 로우 수가 이미 많은 경우 인덱스를 생성하려고 하면 시간이 오래 걸린다. 프로덕션 환경에서 인덱스를 생성할 때 서비스에 에러가 발생하는 상황이 발생하기도 하는데, 그 이유는 인덱스를 생성하는 도중에는 해당 테이블이 읽기 전용으로 잠금이 걸리기 때문이다. 읽기 전용 잠금이 걸리면 해당 테이블에 새로운 정보를 추가하거나 수정할 수 없다.
위와 같은 문제를 해결하기 위해 CONCURRENTLY 옵션을 지원한다. 해당 옵션을 추가해서 인덱스를 생성하면 읽기 전용 잠금이 발생하지 않는다. 프로덕션 환경에서 새로운 인덱스를 추가하는데 유용하다.
대신 동시처리 기능을 사용하면 사용하지 않을 때보다 인덱스 생성 시간이 더 오래 걸린다는 단점이 있다. 이 옵션을 사용할 때 테이블에 대한 두 번의 스캔을 수행해야 하고, 잠재적으로 인덱스를 수정하거나 사용할 수 있는 기존의 모든 트랜잭션이 종료될 때까지 기다려야하기 때문이다. 또한, 인덱스 생성으로 인한 CPU 밑 I/O 부하가 생겨 다른 작업에 지연이 생길 수 있다.
partition한 테이블은 현재 해당 옵션을 지원하지 않고 있기 때문에 사용하기 전 주의해야 한다. partiton 대상이 되는 모든 테이블에 concurrently 옵션을 적용할 수는 없지만, 각각 partition된 테이블에는 적용가능하다.
Reference