B-Tree 인덱스 실패 원인과 Trigram 방식의 해결책


초기 상황과 기대했던 결과

CREATE INDEX idx_projects_name_btree ON projects(name); 

해당명령으로 B-Tree 인덱스를 생성했을 때, 당연히 기대했던 것은 검색 keyword 대상 필드에 대한 검색 성능이 극적으로 향상되는 것이었습니다. 인덱스라는 것은 마치 책의 색인과 같아서, 특정 내용을 찾을 때 모든 페이지를 뒤져보는 대신 색인을 통해 바로 해당 페이지로 갈 수 있게 해주는 역할을 하기 때문입니다.

특히 데이터베이스에서 인덱스 없이 검색을 하면 전체 테이블을 처음부터 끝까지 스캔해야 하는데, 이를 Full Table Scan 또는 Sequential Scan이라고 부릅니다. 데이터가 많아질수록 이런 방식은 점점 더 느려지게 됩니다. 따라서 인덱스를 추가하면 검색 시간이 몇 초에서 몇 밀리초로 줄어들 것이라고 기대하는 것은 지극히 당연한 일입니다.

예상과 다른 현실

하지만 실제로 성능을 측정해보니 응답 속도는 전혀 개선되지 않았습니다. 더 당황스러운 것은 EXPLAIN 명령으로 실행 계획을 확인해보니 여전히 "Seq Scan on projects"라고 나타났다는 점입니다. 이는 데이터베이스가 여러분이 정성스럽게 만든 인덱스를 전혀 사용하지 않고 있다는 의미였습니다.

이런 상황은 마치 빠른 고속도로를 건설해놓았는데도 모든 차량이 여전히 구불구불한 시골길로만 다니는 것과 같습니다. 분명히 더 빠른 길이 있는데 왜 사용하지 않는 걸까요?

image.png

B-Tree 인덱스가 실패한 핵심 이유

image.png

B-Tree 인덱스의 실패 원인을 이해하려면 먼저 B-Tree가 어떻게 데이터를 저장하고 검색하는지 알아야 합니다. B-Tree는 마치 사전과 같은 방식으로 작동합니다. 사전에서 "Spring"이라는 단어를 찾을 때는 'S' 섹션으로 가서 'Sp'로 시작하는 부분을 찾고, 그 다음 'Spr'로 시작하는 부분을 찾아나가죠.

B-Tree 인덱스도 동일한 원리로 문자열의 앞부분부터 순서대로 정렬하여 저장합니다. 따라서 name LIKE 'Spring%' 같은 검색에서는 'Spring'으로 시작하는 지점을 빠르게 찾아갈 수 있습니다. 트리 구조를 따라 내려가면서 정확한 위치에 도달할 수 있기 때문입니다.

하지만 name LIKE '%Spring%' 검색에서는 상황이 완전히 달라집니다. 'Spring'이 문자열의 어느 위치에 있을지 모르기 때문에, B-Tree의 정렬 구조를 활용할 수 없습니다. 예를 들어 "MySpringProject"나 "SpringBootApp"에서 모두 'Spring'을 찾아야 하는데, 이들은 B-Tree에서 완전히 다른 위치에 저장되어 있습니다.

결국 데이터베이스는 모든 인덱스 엔트리를 하나씩 확인해야 하고, 이는 인덱스를 사용하지 않는 것과 동일한 성능을 보이게 됩니다. 이것이 EXPLAIN 결과에서 "Seq Scan"이 나타난 이유입니다.

B-Tree가 잘 동작하는 경우

B-Tree가 실패하는 경우