Skip to content
hello tis
Go back

[26.10.21] 학습: 조회 패턴에 맞춰 복합 인덱스 설계하기

조회 패턴에 맞춰 복합 인덱스 키 설계하기

복합 인덱스의 컬럼 순서를 정하기 위해 ESR과 ERS의 읽기 및 정렬 비용을 비교했습니다. 조건에 맞는 행 수와 LIMIT에 따라 유리한 순서가 달랐습니다. (ESR/ERS 지침)

구성키 순서우선 검토할 상황추가 비용
ESREquality → Sort → Range조건에 맞는 최신 몇 건을 빨리 찾을 수 있어 별도 정렬을 줄이려는 경우조건에 맞지 않는 레코드도 읽을 수 있음
ERSEquality → Range → Sort범위 조건에 해당하는 행이 적어 읽을 후보부터 줄이려는 경우범위에 걸친 결과를 추가 정렬해야 할 수 있음

여러 쿼리에 활용할 동등 조건(E)의 선두 컬럼 정하기

여러 쿼리에서 재사용할 순서를 찾기 위해 선두 컬럼을 비교했습니다.

조회 조건검토할 인덱스활용 방식
feature_id와 user_id를 모두 동등 조건으로 조회두 컬럼 순서 모두 가능두 조건으로 탐색 범위 지정
기능별 전체 사용 이력(feature_id, user_id)feature_id만으로 탐색 범위 지정
사용자별 전체 사용 이력(user_id, feature_id)user_id만으로 탐색 범위 지정

읽는 행 수와 정렬 비용을 비교해 ESR과 ERS 선택하기

SELECT id, user_id, used_at
FROM feature_usage
WHERE feature_id = 'PDF'
  AND user_id BETWEEN 20 AND 40
ORDER BY used_at DESC
LIMIT 2;

정렬을 우선할지 범위를 먼저 줄일지 판단하기 위해, 한 기능의 10만 행에서 범위 일치 행 수와 LIMIT을 바꿨습니다. 캐시에 적재한 두 Covering Index를 비교한 결과는 다음과 같습니다.

하나의 LIMIT에서 선택한 순서를 그대로 적용하기보다, 반환량이 늘어나는 조회도 함께 검증해야 합니다. 그래프의 두 축은 로그 눈금입니다.

범위 일치 행 수별 LIMIT에 따른 ESR과 ERS의 평균 실행 시간.

EXPLAIN ANALYZE로 조기 종료 효과 확인하기

실행 시간 차이의 원인을 확인하려고 인덱스에서 읽은 행 수를 비교했습니다.

범위 일치 행 수LIMITESR이 읽은 행 수ERS가 읽은 행 수
10,025106810,025
10,0251,0009,89710,025
10,02510,00099,71010,025
1061,000100,000106

같은 결과를 얻는 두 경로에서도 비용이 발생하는 지점이 다릅니다.

요청한 수만큼 결과를 빨리 찾기 어렵다면, 정렬 회피보다 ERS로 후보를 먼저 줄이는 구성을 검토해야 합니다.

같은 레코드를 두 Leaf 페이지에 배치한 ESR과 ERS의 조회 경로.

행 데이터의 추가 조회가 많다면 반환 컬럼까지 포함하기

반환 컬럼을 넣을 때의 추가 비용을 비교하면, 보조 인덱스가 커지고 해당 컬럼을 변경할 때 갱신할 인덱스도 늘어납니다.

비교할 작업(feature_id, user_id)(feature_id, user_id, result)
보조 인덱스 저장탐색 컬럼과 기본 키 저장result를 포함해 인덱스 레코드 크기 증가
result 변경이 보조 인덱스의 키는 유지Clustered Index와 보조 인덱스 모두 갱신

같은 result를 조회하는 경로를 비교하면, Covering Index는 행 데이터의 추가 조회를 줄이는 대신 값을 보조 인덱스에도 저장합니다.

result 포함 여부에 따른 보조 인덱스와 Clustered Index 조회 경로.

컬럼을 추가하기 전에 실제 값 길이와 페이지 읽기 비교하기

보조 인덱스 페이지는 인덱스를 저장하고 읽는 기본 단위이며, 이번 비교의 페이지 크기는 16KiB입니다. 키 값 길이에 따른 조회 구조를 비교하면, 같은 행을 조회해도 필요한 페이지와 캐시 공간은 달라질 수 있습니다. 선언 길이만 줄이기보다 실제 값 길이와 자주 읽는 범위를 기준으로 포함할 컬럼을 정해야 합니다. (문자열 타입, InnoDB 페이지 구조)

키 값 길이에 따른 Leaf 배치와 Buffer Pool 및 저장소 접근 경로.

같은 100만 행에서 실제 키 값 길이에 따른 인덱스 용량을 비교했습니다. 컬럼 선언을 VARBINARY(256)으로 유지해도, 저장한 값이 길어질수록 페이지 수와 용량이 늘었습니다.

실제 키 값 길이보조 인덱스 페이지 수용량
16바이트2,34236.59MiB
64바이트5,75589.92MiB
256바이트19,776309.00MiB

Buffer Pool 64MiB에서 캐시 초기화 후 첫 조회와 같은 64개 쿼리를 반복한 경우를 비교했습니다. 초기 5회와 반복 15회의 산술평균에서, 가장 긴 키 값 조건은 반복 조회에서도 매번 추가 읽기가 남았습니다.

키 값 길이초기 평균(ms)반복 평균(ms)초기 평균 읽기(MiB)반복 평균 읽기(MiB)반복 읽기 발생
16바이트92.3059.1216.690.000/15회
64바이트151.6762.2840.281.055/15회
256바이트369.29315.72138.97105.6715/15회

반복 조회의 추가 읽기를 줄이기 위해 메모리와 저장소 접근을 비교했습니다. 같은 쿼리라도 필요한 페이지가 캐시에 남지 않으면 저장소 읽기가 반복됩니다.

Buffer Pool과 다른 메모리 사용 영역, 미적재 페이지의 저장소 읽기.

대표 쿼리의 실행 계획으로 인덱스 후보 검증하기

같은 쿼리도 조건값에 따라 읽는 양이 달라지므로, 다음 항목을 확인한 뒤 최종 구성을 정합니다.


Share this post:

Previous Post
[26.10.22] 사내 버셀에 정적 페이지 업로드 API 추가하기
Next Post
[26.10.20] ClickHouse로 300억 건 통계 통합하기