김현우
  • 데이터베이스 인덱스 기본 개념 (DB Index Concept)
    2025년 01월 16일 16시 28분 01초에 업로드 된 글입니다.
    작성자: kugorang

    TL;DR — 3줄 핵심 요약

    • 문제: 직무 테스트에서 데이터베이스 인덱스를 설명하는 문항을 다 쓰지 못했다. 백엔드와 모바일 게임 서버에서 인덱스 설계는 읽기 성능을 좌우한다.

    • 기술/원인: 인덱스는 컬럼 값과 레코드 위치를 정렬해 둔 별도 자료구조다. 조회는 그 구조에서 위치를 먼저 찾아 전체 스캔을 피하지만, 삽입·수정·삭제 때는 인덱스도 함께 갱신해야 한다.

    • 해결/인사이트: WHERE, JOIN, GROUP BY, ORDER BY에 자주 쓰이는 컬럼에 선택도가 높은 것부터 건다. 만든 뒤에는 EXPLAIN으로 실제 사용 여부를 확인해야 한다.

    들어가며: 직무 테스트에서 놓친 주제

    며칠 전 한 기업의 직무 테스트를 봤다. CS 기초 필기였는데, 데이터베이스 인덱스 문항을 끝까지 쓰지 못했다. 인덱스는 초당 트랜잭션 수(TPS)와 응답 속도를 좌우해서 면접과 실무에서 자주 나오는 주제다. 지원한 곳이 모바일 게임 서버였기 때문에 더 중요했다. 이 글은 그때 정리하지 못한 내용을 다시 공부한 기록이다.

    인덱스란 무엇인가

    테이블의 특정 컬럼 값과 그 값이 있는 레코드의 위치를 미리 정렬해 둔 별도 자료구조다. 조회할 때 테이블의 모든 행을 읽지 않고, 이 구조에서 위치를 먼저 찾은 뒤 그 위치만 읽는다.

    예를 들어 유저 ID 1003을 찾을 때, 인덱스는 "1003은 테이블의 57번 레코드"라는 정보를 바로 준다. 인덱스가 없으면 첫 행부터 끝까지 읽으며 1003을 찾아야 한다.

    대신 인덱스를 만들면 INSERT, UPDATE, DELETE 때 인덱스 구조도 같이 고쳐야 한다. 읽기를 빠르게 하는 대신 쓰기에 비용이 붙는 구조다.

    인덱스의 자료구조

    B-Tree와 B+Tree

    가장 많이 쓰이는 구조다. 키를 정렬된 트리로 유지해서 같은 값을 찾는 일과 범위를 찾는 일 모두에 쓸 수 있다. 트리 높이가 균형을 이루므로 탐색 시간을 예측하기 쉽다.

    • B-Tree: 내부 노드와 리프 노드 모두에 키와 데이터 위치를 둘 수 있다.
    • B+Tree: 실제 데이터 위치는 리프 노드에만 두고, 내부 노드는 키와 자식 포인터만 가진다. 리프 노드끼리 연결 리스트로 이어져 있어 범위 검색이 빠르다.

    MySQL InnoDB와 PostgreSQL은 실제로 B+Tree를 쓴다. 문서에서 "B-Tree 인덱스"라고 부르는 것은 B+Tree를 포함해 통칭하는 표현이다.

    Hash 인덱스

    값을 해시로 바꿔 저장하므로 같은 값을 찾는 데는 가장 빠르다. 값의 크기 순서를 유지하지 않으므로 범위 검색에는 쓸 수 없다. MySQL에서는 주로 Memory 엔진에서 쓴다.

    특수 인덱스

    • Full-Text: 글 내용에서 단어를 찾는 검색에 쓴다.
    • R-Tree: 좌표와 영역을 다루는 공간 검색에 쓴다.
    • GiST, GIN: PostgreSQL에서 텍스트와 범위 타입 등에 쓰는 범용 구조다.

    동작 원리

    조회

    CREATE INDEX idx_user_name ON user_table (name);
    SELECT * FROM user_table WHERE name = 'Alice';

    엔진은 name에 인덱스가 있는지 확인하고, 인덱스에서 'Alice'의 위치를 찾은 뒤 그 레코드만 읽는다. 테이블 전체를 읽는 풀 테이블 스캔을 피한다.

    범위 검색

    SELECT * FROM score_table WHERE score BETWEEN 100 AND 300;

    B+Tree는 리프 노드가 정렬되어 이어져 있어 100 지점을 찾은 뒤 300까지 순서대로 읽으면 된다. 전체를 읽지 않는다.

    쓰기

    INSERT INTO user_table (name, score) VALUES ('Bob', 250);

    테이블에 행이 추가되면, name 인덱스에는 'Bob'의 위치가, score 인덱스에는 250의 위치가 추가된다. 인덱스가 여러 개면 그 수만큼 갱신이 늘어난다. 트리의 균형을 유지하려고 노드를 나누거나 합치는 작업도 일어나 CPU와 디스크 입출력이 더 든다.

    장점과 단점

    • 장점: 조회 속도와 응답 시간이 좋아지고, 조건 검색과 범위 검색이 빨라진다.
    • 단점: INSERT, UPDATE, DELETE가 느려지고, 인덱스를 저장할 디스크 공간이 더 필요하다.

    데이터베이스는 인덱스와 실제 데이터가 항상 일치해야 한다. 그래서 테이블이 바뀔 때마다 인덱스도 함께 고쳐야 하고, 이 비용이 쓰기 성능을 떨어뜨린다. 쓰기가 많은 테이블은 인덱스를 적게, 읽기가 많은 테이블은 필요한 만큼 두는 것이 기본 원칙이다.

    설계할 때 볼 것

    자주 쓰는 쿼리의 컬럼

    • WHERE: 조건에 쓰이는 컬럼에 인덱스가 있으면 전체 스캔을 피한다.
    • JOIN: 연결 기준 컬럼에 인덱스가 없으면 양쪽 테이블을 모두 읽어 맞춰야 한다.
    • GROUP BY, ORDER BY: 인덱스가 이미 정렬되어 있으면 추가 정렬을 줄일 수 있다.

    그래서 WHERE, JOIN, GROUP BY, ORDER BY에 반복해서 등장하는 컬럼부터 인덱스를 만든다.

    선택도와 쓰기 부담

    선택도는 컬럼 값이 얼마나 다양하게 나뉘는지를 뜻한다. 성별처럼 값이 두 가지뿐인 컬럼은 인덱스를 만들어도 읽을 행이 절반이라 효과가 작다. 반대로 유저 ID처럼 값이 거의 겹치지 않는 컬럼은 효과가 크다. 매분 갱신되는 컬럼이라면 인덱스 수를 줄이거나 캐시를 함께 쓰는 방법을 검토한다.

    실행 계획 확인

    EXPLAIN SELECT * FROM user_table WHERE name = 'Alice';            -- MySQL, PostgreSQL
    EXPLAIN ANALYZE SELECT * FROM user_table WHERE name = 'Alice';    -- 실제 실행 시간 포함

    EXPLAIN은 어떤 순서로 어떤 인덱스를 쓰는지 계획을 보여준다. EXPLAIN ANALYZE는 쿼리를 실제로 실행하고 걸린 시간까지 보여준다. 인덱스를 만들었는데도 풀 테이블 스캔이 나오면 조건 순서, 컬럼 순서, 형 변환을 확인해야 한다. 인덱스를 만든 것과 실제로 쓰이는 것은 다른 문제이므로 계획을 봐야 한다.

    모바일 게임 서버에서 중요한 이유

    • 동시 접속이 많다. 로그인, 매치메이킹, 인벤토리 조회처럼 읽기 쿼리가 계속 들어온다.
    • 유저 프로필, 보유 아이템, 랭킹 점수는 짧은 간격으로 반복 조회된다.
    • 이벤트나 패치 직후 접속이 몰릴 때 인덱스가 없으면 응답이 밀린다.

    인덱스는 이런 읽기 부하에서 병목을 줄이는 가장 직접적인 수단이다. 다만 아이템 획득처럼 쓰기가 잦은 구간은 인덱스를 늘리는 대신 다른 방법을 함께 봐야 한다.

    정리

    • 인덱스는 컬럼 값과 레코드 위치를 정렬해 둔 별도 구조다.
    • B+Tree가 가장 흔하고 범위 검색에 강하다. Hash는 같은 값 찾기에만 강하다.
    • 읽기를 빠르게 하고 쓰기를 느리게 한다. 디스크도 더 쓴다.
    • 설계는 쿼리 패턴, 선택도, 쓰기 부담을 함께 보고, EXPLAIN으로 확인한다.

    참고 자료

    • MySQL Reference Manual, InnoDB 인덱스 파트
    • PostgreSQL Documentation, Indexes
    • High Performance MySQL (O'Reilly)

    자주 묻는 질문 (FAQ)

    Q1. 인덱스를 많이 만들수록 좋은가요?

    A. 아니다. 조회는 빨라지지만 INSERT, UPDATE, DELETE마다 모든 인덱스를 갱신해야 해서 쓰기가 느려지고 디스크도 더 쓴다. 실제 쿼리에서 쓰이는 인덱스만 남기는 것이 좋다.

    Q2. B-Tree와 B+Tree는 무엇이 다른가요?

    A. B-Tree는 내부 노드에도 데이터 위치를 둘 수 있고, B+Tree는 리프 노드에만 두고 리프끼리 연결한다. 그래서 B+Tree가 범위 검색에 유리하고, 실제 DB 엔진은 대부분 B+Tree를 쓴다.

    Q3. 인덱스를 만들었는데도 느린 이유는 무엇인가요?

    A. 대표적으로 네 가지다. 컬럼에 함수나 형 변환을 적용해 인덱스를 쓰지 못하는 경우, 앞부분 조건이 빠진 복합 인덱스, 선택도가 낮은 컬럼, 읽는 행이 전체의 상당 부분이라 옵티마이저가 전체 스캔을 고르는 경우다. EXPLAIN으로 계획을 확인해야 한다.

    728x90
    728x90
    댓글