관리자 행사 목록 조회 API 성능 최적화 및 실행 계획 분석

오형상·2025년 7월 22일
0

Ficket

목록 보기
31/31
post-thumbnail

기존 쿼리 및 실행 시간 (약 3초)

목적

하나의 이벤트에 대해 여러 공연 일정이 존재할 수 있으며, 같은 날에도 여러 시간대의 공연이 열릴 수 있습니다.
예를 들어, 다음과 같은 공연 일정을 가진다고 한다면

  • 2025년 7월 20일: 13시 공연, 20시 공연
  • 2025년 7월 21일: 13시 공연, 20시 공연

이 경우, 프론트엔드에서는 단순히 시간 단위가 아닌 날짜 단위(7.20 ~ 7.21)로 공연 기간을 표시하고자 합니다.
이를 위해 event_schedule 테이블의 event_dateDATE(event_date)로 잘라 날짜별로 그룹핑한 후, 해당 날짜 중 가장 이른 공연 시간(MIN(event_date))을 함께 조회하는 방식으로 데이터를 구성하였습니다.

SQL 쿼리

SELECT
    DISTINCT event.event_id,
    event.title,
    event_stage.stage_name,
    event.company_id,
    event.admin_id,
    MIN(event_schedule.event_date),
    event.created_at
FROM
    event
JOIN
    event_schedule
    ON event.event_id = event_schedule.event_id
    AND event_schedule.deleted_at IS NULL
JOIN
    event_stage
    ON event_stage.stage_id = event.stage_id
WHERE
    event.deleted_at IS NULL
GROUP BY
    event.event_id,
    event.title,
    event_stage.stage_name,
    event.company_id,
    event.admin_id,
    DATE(event_schedule.event_date);

실행 계획

idselect_typetabletyperowsExtra
1SIMPLEevent_scheduleALL199,644Using where; Using temporary
1SIMPLEeventeq_ref8Using where
1SIMPLEevent_stageALL3Using where; Using join buffer
  • event_schedule 테이블을 전체 스캔하고 임시 테이블을 사용하여 비효율적임이 나타남
  • 실행 시간이 약 3초로 매우 느림

1차 최적화: 서브쿼리 분리 및 인덱스 추가 (약 1.5초)

목적

기존 쿼리는 다량의 데이터에 JOIN, GROUP BY, DATE() 함수, MIN() 집계를 한 번에 처리하면서 성능 병목이 발생했습니다.
이를 개선하기 위해 event_schedule 테이블의 집계를 서브쿼리로 분리하고,
deleted_at, event_id, event_date에 복합 인덱스를 추가하여 성능을 향상시키고자 하였습니다.

SQL 쿼리

SELECT
    e.event_id,
    e.title,
    es.stage_name,
    e.company_id,
    e.admin_id,
    esc.event_day,
    esc.min_event_date
FROM
    event e
JOIN
    event_stage es ON e.stage_id = es.stage_id
JOIN
    (
        SELECT
            event_id,
            DATE(event_date) AS event_day,
            MIN(event_date) AS min_event_date
        FROM
            event_schedule
        WHERE
            deleted_at IS NULL
        GROUP BY
            event_id,
            DATE(event_date)
    ) esc ON e.event_id = esc.event_id
WHERE
    e.deleted_at IS NULL;

인덱스 추가

CREATE INDEX idx_event_schedule_deleted_event_eventdate
ON event_schedule (deleted_at, event_id, event_date);

실행 계획

idselect_typetabletyperowsExtra
1PRIMARYALL19,964
1PRIMARYeventeq_ref8Using where
1PRIMARYevent_stageALL3Using where; Using join buffer
2DERIVEDevent_scheduleALL199,644Using where; Using temporary
  • 서브쿼리를 분리해 처리했으나, 여전히 임시 테이블과 전체 스캔이 발생
  • 인덱스 추가로 일부 성능 향상
  • 실행 시간 약 1.5초로 절반가량 단축

2차 최적화: 서브쿼리에 페이징 적용 및 인덱스 활용 (약 0초)

목적

이전 쿼리에서는 GROUP BY DATE(event_date)를 사용했지만, 이는 event_date에 설정된 인덱스를 제대로 활용하지 못합니다.
즉, 함수가 컬럼에 직접 적용되면 MySQL은 해당 컬럼의 인덱스를 사용할 수 없기 때문에, 인덱스를 타지 못하고 전체 테이블 스캔이 발생합니다.
이를 해결하기 위해 날짜 단위로 그룹핑하지 않고, 최소값(MIN)과 최대값(MAX)을 사용해 전체 공연 기간을 파악하는 방식으로 구조를 변경하였습니다.

SQL 쿼리

SELECT
    e.event_id,
    e.title,
    es.stage_name,
    e.company_id,
    e.admin_id,
    esc.min_event_date,
    esc.max_event_date
FROM
    event e
JOIN
    event_stage es ON e.stage_id = es.stage_id
JOIN
    (
       SELECT
            event_id,
            MIN(event_date) AS min_event_date,
            MAX(event_date) AS max_event_date
       FROM
            event_schedule
       WHERE
            deleted_at IS NULL
       GROUP BY
            event_id
       LIMIT 0, 10
    ) esc ON e.event_id = esc.event_id
WHERE
    e.deleted_at IS NULL;

실행 계획

idselect_typetabletypekeyrowsExtra
1PRIMARYALLNULL11
1PRIMARYeventeq_refPRIMARY, idx_event_id, idx_event_filtering8Using where
1PRIMARYevent_stageeq_refPRIMARY3Using where
2DERIVEDevent_schedulerefevent_id, idx_event_schedule_deleted_event_eventdate9Using where; Using index
  • 서브쿼리 결과가 11건으로 제한되면서 조회 범위 축소
  • event_schedule 테이블에서 복합 인덱스를 활용해 빠른 조회가 가능
  • eventevent_stage는 기본키 조인으로 빠르게 연결
  • 실행 시간 거의 0초 수준으로 개선

마무리 및 느낀 점

이번 성능 개선 과정에서 실행 계획 분석과 쿼리 구조 변경이 얼마나 중요한지 체감했습니다.

  • 대용량 데이터를 다룰 때 무분별한 조인과 집계는 성능 저하의 주범임을 깨달았습니다.
  • 특히 서브쿼리에 페이징을 적용해 데이터 처리 범위를 줄이는 전략이 매우 효과적이었습니다.
  • 복합 인덱스 설계도 조회 성능 향상에 큰 역할을 하였습니다.
  • MySQL 실행 계획에서 Using temporary, Using filesort가 보일 때는 꼭 인덱스와 쿼리 구조를 점검해야 한다는 점을 배웠습니다.
  • 결과적으로 3초 걸리던 쿼리가 1.5초, 그리고 거의 즉시 실행되도록 개선되어 사용자 경험에 긍정적인 영향을 줄 수 있었습니다.

0개의 댓글