미니 프로젝트 - 인덱싱 처리

Zyoon·2025년 7월 15일

미니프로젝트

목록 보기
22/36
post-thumbnail

📘데이터 조회시 인덱싱 처리하여 속도 향상


데이터 인덱싱 처리

  • 테이블의 특정 컬럼에 대해 색인을 만들어서 빠르게 찾을 수 있게 도와주는 작업
  • ex) 도서관의 책을 찾을 때, 그룹화 되어 묶어 두면 빠르게 찾을 수 있음(ㄱ,ㄴ,ㄷ…)
  • 데이터가 많아지면 많아질 수록 성능이 차이가 심해짐 (SELECT 속도 향상)
  • WHERE 절이나 ORDER BY, JOIN, GROUP BY 등에 자주 사용되는 컬럼에 효과적

인덱스 미적용시 실행 과정

  1. WHERE 조건에 맞는 레코드를 모두 풀 스캔

  2. 메모리에서 정렬 수행

  3. 거기서 LIMIT으로 자름

    데이터가 많을수록 속도 급격히 저하


인덱스 주의점

  1. 무조건 빠른 건 아님
    • 너무 많은 인덱스를 만들면 오히려 쓰기 성능(INSERT/UPDATE/DELETE)이 느려짐
  2. 인덱스는 "정렬된 복사본"
    • 디스크 공간을 더 차지하고, 매번 업데이트되기 때문에 처리 비용 증가
  3. 복합 인덱스는 순서 중요
    • @Index(columnList = "a,b")WHERE a=? AND b=?에 유리하지만 WHERE b=?에는 효과 없음

코드에서의 인덱싱 처리

인덱싱 처리 할 쿼리문

	/**
	 *삭제되지 않았으며, 주어진 날짜 이후에 열리는 콘서트들을 공연일과 ID 기준으로 오름차순 정렬하여 조회
	 * @param now 공연일 기준 필터 날짜 (이 날짜 이후의 공연만 조회됨)
	 * @param pageable 페이징 및 페이지 크기 설정
	 * @return 조건에 맞는 콘서트 목록을 포함하는 페이지 객체
	 */
	@Query("""
		    SELECT c FROM Concert c
		    WHERE c.isDeleted = false AND c.performanceDate >= :now
		    ORDER BY c.performanceDate ASC, c.id ASC
		""")
	Page<Concert> findUpcomingConcerts(@Param("now") LocalDate now, Pageable pageable);

엔티티에 index 추가

@Entity
@Table(name = "concerts", indexes = {
	@Index(
		name = "idx_isDeleted_performanceDate", 
		columnList = "isDeleted, performanceDate ASC, id ASC"
		)}
	)
@Getter
public class Concert extends BaseEntity
  • name = "idx_isDeleted_performanceDate" → 생성될 DB 인덱스 이름 (명시적으로 부여함, 가독성 및 관리 용이)
  • columnList = "isDeleted, performanceDate ASC, id ASC"인덱스를 구성할 컬럼들의 순서 및 정렬 방향 지정

인덱싱 처리의 이유

  1. isDeleted: Soft Delete 필터

    • 대부분의 조회 쿼리가 WHERE isDeleted = false 조건을 사용
    • 이 컬럼을 인덱스의 첫 번째 컬럼으로 설정하면 조건 필터링 성능 최적화
  2. performanceDate: 공연 날짜 기준 필터 및 정렬

    • 사용자가 공연을 볼 때 "공연 예정일 순"으로 보는 경우가 많음
    • WHERE performanceDate >= :now + ORDER BY performanceDate ASC
    • 조건 필터와 정렬 둘 다 고려하여 두 번째 컬럼으로 배치
  3. id: 정렬의 유일성 확보

    • performanceDate가 동일한 레코드가 많을 경우 ORDER BY id ASC로 안정적인 정렬 보장
    • 인덱스 정렬 기준에 id ASC를 포함하면 쿼리에서 정렬 최적화 가능

인덱싱 처리를 위한 테스트 조건

  1. isDeleted = false

    • 실무에서는 소프트 딜리트 처리 (isDeleted = true로 비활성화)
    • 따라서 대부분의 쿼리는 삭제되지 않은 것만 조회
    • 인덱스의 첫 번째 컬럼으로 설정 → WHERE 필터의 진입점
    • 테스트에서는 i % 5 != 0으로 80%는 false (= 조회 대상), 20%는 true
      • 적당한 비율을 주어 인덱스 효율을 높임
  2. performanceDate >= :now

    • 실무에서 가장 흔한 조건: "앞으로 열릴 공연만 보기"
    • 인덱스의 두 번째 컬럼>= 조건이기 때문에 Range Scan이 가능
    • 테스트에서는
      • i % 100 == 0일 때만 미래 날짜 (랜덤 0~29일 뒤)
      • 나머지는 과거 날짜 (1~364일 전)
    • 이 구조는 "절대 다수는 과거", "소수만 미래" → 성능 차이를 명확하게 유도
  3. 해당 구조의 의미

    • 총 100만건 중, 대략:
      • isDeleted = false인 건: 약 80만 건
      • 그 중 performanceDate >= :now인 건: 약 8,000건 정도 (0.8%)
    • 즉, 전체 테이블 중에서 실제 쿼리에 걸리는 건 극히 일부 → 이럴 때 인덱스를 타면 빠르게 원하는 데이터만 추출 가능

테스트 결과

  • 데이터 100만건 테스트
인덱스 적용 전인덱스 적용 후
595ms106ms
520ms116ms
512ms102ms
519ms103ms
548ms118ms
  • 인덱스 없이 평균 500 ~ 600ms
  • 인덱스 적용 후 110 ~ 130ms 수준으로 약 4,5배 성능 개선을 확인
profile
기어 올라가는 백엔드 개발

0개의 댓글