시스템 디자인/데이터베이스

인덱싱(Indexing)은 DB를 어떻게 바꿀까?

땅다람쥐 2026. 6. 3. 06:25

"인덱싱(Indexing)은 DB 성능을 무조건 향상시킨다?" 이는 절반만 맞는 말입니다. 인덱스는 쓰기(Write) 작업 시 성능 저하를 야기하지만, 읽기(Read) 속도는 빠르게 만들어주기 때문입니다. 그렇다면 왜 이런 성능의 트레이드오프가 발생하는 걸까요? 이번 글에서는 인덱스의 작동 원리와 함께, 내 애플리케이션의 맞는 인덱싱 기법을 알아보겠습니다.

 

인덱싱(Indexing)


 

Reference: srimax.com

먼저 인덱싱은 왜 쓰는 걸까요? 인덱싱이 없는 DB에서 특정 데이터를 찾으려면 처음부터 끝까지 다 뒤져야 합니다. 이를 Full Table Scan이라고 하며, 보통 O(N)의 시간이 걸리죠. 인스타그램이나 구글처럼 데이터가 어마어마하게 많은 애플리케이션에서 매번 O(N)으로 데이터를 찾으려면 너무 느려질 겁니다.

반면, 인덱스가 없을 때 데이터를 쓰는(Write) 작업은 아주 단순합니다. 그냥 테이블 맨 뒤에 데이터를 툭 붙이면 끝이니 O(1)이면 충분하죠.

 

 

그럼 인덱싱은 어떤 방식으로 진행이 될까요? 인덱싱은 인덱싱 열(Indexing Column)이라는 새로운 열을 만듬으로서 읽는 속도를 좀 더 빠르게 만들게 됩니다. 예를들어, 해시 인덱싱(Hash Indexing)을 예로 들어보죠. 현재 가지고 있는 테이블의 한 열을 인덱싱화 시킴으로서, 필요한 Row를 찾을 때 마다, 그 Row와 관련된 index를 인덱싱 Column에서 찾고 그 Row를 읽어주는 역할을 합니다. 이렇게 함으로서, 읽기의 시간 복잡도를 O(1)으로 줄여줄 수 있죠.

 

하지만 세상에 공짜는 없듯, 읽기(Read) 성능 뒤에는 쓰기(Write)의 성능 저하가 뒤따릅니다. 데이터를 쓸 때마다 원본 테이블뿐만 아니라 별도로 만들어 둔 인덱스 열(Indexing Column)까지 함께 업데이트해야 하기 때문입니다. 원래 한 번만 데이터를 쓰면 되는 일을 두 번 해야 하게 되니 느려질 수밖에 없는 것이죠.

 

해시 인덱싱(Hash Indexing)


 

그럼 먼저 해시 인덱싱(Hash Indexing)에 대해서 알아볼까요?

 

해시 인덱싱은 저희가 알고 있는 해시 테이블의 구조와 같다고 생각하시면 됩니다. Key 값을 Hash Function을 통해 hash화 시킨 뒤, 그 hash 값을 Bucket과 이어주는 거죠. 이런 과정 때문에, 필요한 데이터를 찾을 때 O(1) 값으로 굉장히 빠르게 찾을 수 있게 됩니다.

 

하지만 Hash Table에도 충돌(Collision) 문제가 있듯이, 이러한 문제를 해결하기 위해서 링크드 리스트를 활용한 Chaining, 빈 Bucket을 찾아가는 Open addressing 기법을 사용할 수 있습니다.

 

그럼 해시 인덱싱의 단점은 뭘까요? 

  1. Disk Distribution: 해시 인덱스는 충돌(Collision)을 피하기 위해 데이터를 전 주소 공간에 균등하게 분산시킵니다. 이런 특성 때문에 공간 지역성(Spatial Locality)을 전혀 활용하지 못합니다. 결국 디스크 입장에서는 계속 데이터를 찾으러 다니는 부담을 가지게 됩니다.
  2. RAM: 앞서 말한 디스크 성능 저하 때문에, 해시 인덱스는 RAM에 사용하게 됩니다. 하지만 RAM은 디스크에 비해 훨씬 비싼 단점이 있습니다. 그리고 만약 Redis와 같은 in-memory 환경에서는, 재부팅 시 메모리가 통째로 날아가는 단점도 있습니다. 
  3. Range queries(범위 쿼리): 해시 인덱스는 Key-Value 매핑에만 특화되어 있습니다. 해시 함수는 값이 조금만 달라도 완전히 다른 주소를 주기 때문에, 데이터를 정렬된 상태로 보관하지 않습니다. 따라서 나이가 "20대인 유저들" 같은 범위 검색(Range Query)을 하려면 인덱스의 장점을 전혀 살리지 못하고, 결국 처음부터 끝까지 다 뒤지는 O(N)의 시간 복잡도가 생깁니다.

하지만 RAM의 휘발성이라는 약점 역시 WAL(Write-Ahead Log) 기법을 도입해 극복할 수 있습니다..

조회가 아닌 데이터의 변경(추가·수정·삭제) 작업이 발생할 때마다, 메모리(RAM)에 반영하기 직전 그 변경 이력을 WAL이라는 안전한 디스크 로그 파일에 순차적으로 먼저 기록(Write-Ahead)하는 방식입니다.

이렇게 하면 서버가 다운되어 RAM 속 인덱스 데이터가 통째로 날아가더라도 아무 걱정이 없습니다. 서버가 재부팅될 때 디스크에 고스란히 남아있는 WAL의 기록들을 순서대로 다시 실행하면 서버 다운 직전의 인덱스 상태를 똑같이 복구해낼 수 있기 때문입니다.

 

결론을 내리자면, 해시 인덱싱은 데이터를 읽고 쓰는데 효과적인지만, Range Queries를 처리할 수 없다는 단점이 명확합니다. 따라서 ID, 세션 토큰처럼 고유한 식별자를 기반으로 데이터 하나를 찾는 애플리케이션 환경에서 효과적으로 활용할 수 있습니다.

 

B-Tree 인덱싱 (B-tree Indexing)


 

이제 또 다른 인덱싱의 종류인 B-tree를 한 번 알아볼까요? 관계형 데이터베이스에서 인덱스를 생성하면 기본값으로 지정될 만큼, 가장 많이 쓰이고 사랑받는 인덱싱 기법입니다.

 

B-tree는 위 사진처럼 기준값에 따라 범위를 나누어 데이터를 찾아갑니다. 예를 들어 기준이 30과 50이라면, 30보다 작은 값은 왼쪽 Child, 30과 50 사이의 값은 가운데 Child, 50보다 큰 값은 가장 오른쪽 Child 노드로 길을 찾아 내려가는 식이죠. 그리고 마지막 Leaf Node에서 값들을 찾을 수 있습니다.

 

부모와 자식 노드가 1:N(일대다) 구조로 이루어져 있지만, B-tree는 Balanced Tree 구조를 유지합니다. 모든 Leaf Node들이 같은 Height에 위치하도록 설계되어 있죠. 덕분에 데이터가 많아도 Read의 속도를 O(log N)을 보장해 줍니다.

 

하지만 이런 B-tree도 단점이 있습니다. 트리 구조를 만들고 자식 노드들을 연결해야 해서 메모리 부담이 큽니다. 그리고 Tree를 타고 새로운 노드를 추가/변경/삭제를 할 때 트리 구조를 타고 가야하기 때문에 쓰기에 O(log N)이라는 시간이 걸립니다. 마지막으로 O(log N) 읽기도 빠르지만, 해시 인덱싱의 O(1)에 비해서는 더 느린편이죠.


하지만 범위 검색(Range Query)을 지원하기 때문에 정렬과 범위 조회가 필수인 '게시판' 같은 서비스에서는 해시보다 B-tree가 훨씬 더 빛을 발하게 됩니다.

 

 

 

Reference


https://www.srimax.com/index-part-1-choosing-the-right-column/

https://medium.com/@myscale/b-tree-indexing-vs-hash-indexing-vs-graph-indexing-which-is-right-for-your-database-8f4cac6d89ba

https://www.geeksforgeeks.org/dsa/introduction-of-b-tree-2/