인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

Wonder님의 프로필 이미지

작성한 질문수

스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판

댓글 최대 2 depth - 목록 API 설계

댓글 목록 조회 - 튜플 비교 시 쿼리 성능 저하

해결된 질문

작성

·

242

·

수정됨

3

안녕하세요, 먼저 소중한 강의 만들어주셔서 너무 감사드립니다! 🙏🏻

 

댓글 목록 조회 쿼리에서 궁금한 점이 있어 질문드립니다.

( 댓글 최대 2 depth - 목록 API 설계 7:19 부분 )


 

질문

"튜플 비교 (a, b) > (x, y)를 사용하면 인덱스 풀 스캔이 발생하여 성능이 매우 떨어지는데, 명시적 조건 a > x OR (a = x AND b > y) 으로 분리하면 인덱스 레인지 스캔이 발생하여 쿼리 성능이 매우 빨라지는데 왜 그런 것일지 모르겠습니다.."

 


질문 상세


  • 테스트 환경: comment 에 약 8백만건의 테스트 데이터 삽입

  • mysql base image: mysql:8.0.38

  • comment table ddl

-- auto-generated definition
create table comment
(
    comment_id        bigint        not null
        primary key,
    content           varchar(3000) not null,
    article_id        bigint        not null,
    parent_comment_id bigint        not null,
    writer_id         bigint        not null,
    is_deleted        tinyint(1)    not null,
    created_at        datetime      not null
);

create index idx_article_id_parent_comment_id_comment_id
    on comment (article_id, parent_comment_id, comment_id);

 


문제가 되는 테스트 케이스 (1: slow, 2: fast)

case 1. tuple comparision (slow)

explain analyze
select comment.comment_id,
       comment.parent_comment_id,
       comment.content,
       comment.article_id,
       comment.writer_id,
       comment.content,
       comment.is_deleted,
       comment.created_at
from comment
where article_id = 1
  and (parent_comment_id, comment_id) > (142539921307124354, 142539921307124350)
order by parent_comment_id, comment_id
limit 30;

--
-> Limit: 30 row(s)  (cost=542979 rows=30) (actual time=8620..8620 rows=30 loops=1)
    -> Filter: ((`comment`.comment_id,`comment`.parent_comment_id) > (142539921307124354,142539921307124350))  (cost=542979 rows=4.01e+6) (actual time=8620..8620 rows=30 loops=1)
        -> Index lookup on comment using idx_article_id_parent_comment_id_comment_id (article_id=1)  (cost=542979 rows=4.01e+6) (actual time=1.83..8251 rows=8e+6 loops=1)

 

case 2. fast

explain analyze
select comment.comment_id,
       comment.parent_comment_id,
       comment.content,
       comment.article_id,
       comment.writer_id,
       comment.content,
       comment.is_deleted,
       comment.created_at
from comment
where article_id = 1
  and (
    parent_comment_id > 142539921307124354
        or
    (parent_comment_id = 142539921307124354 and comment_id > 142539921307124350)
    )
order by parent_comment_id, comment_id
limit 30;

--
-> Limit: 30 row(s)  (cost=416 rows=30) (actual time=0.252..0.727 rows=30 loops=1)
    -> Index range scan on comment using idx_article_id_parent_comment_id_comment_id over (article_id = 1 AND parent_comment_id = 142539921307124354 AND 142539921307124350 < comment_id) OR (article_id = 1 AND 142539921307124354 < parent_comment_id), with index condition: ((`comment`.article_id = 1) and ((`comment`.parent_comment_id > 142539921307124354) or ((`comment`.parent_comment_id = 142539921307124354) and (`comment`.comment_id > 142539921307124350))))  (cost=416 rows=358) (actual time=0.232..0.705 rows=30 loops=1)

 

튜플 비교를 사용한 1번 쿼리에서는 index full scan 이 발생하여 ( 8백만개의 row 를 모두 스캔 ) 8초의 안좋은 쿼리 성능이 나타난 것으로 판단했습니다.

 

반면 튜플 비교를 명시적 조건으로 분리한 2번 쿼리에서는,

  • (a > X OR (a = X AND b > Y)))

index range scan 을 통해 0.7초 이하의 빠른 쿼리 성능이 나타난 것 같아요.


요약

  • 튜플 비교 (a, b) > (x, y)를 사용한 1번 쿼리에서 MySQL 옵티마이저는 왜 풀 인덱스 스캔을 선택하는 것인지,

    튜플 비교가 인덱스 레인지 스캔으로 최적화되지 않는 이유가 무엇인지 원인을 찾고 있는데 잘 모르겠네요.. 힌트를 받을 수 있을까요?

 

새해복 많이 받으세요!

답변 2

2

쿠케님의 프로필 이미지
쿠케
지식공유자

Wonder님, 안녕하세요!

 

일단 죄송하다는 말씀 먼저 드립니다.

해당 부분은 강의에서도 오류가 있었네요.. ㅠㅠ

 

원래 튜플 비교가 아닌 그냥 조건으로 만들었다가,

튜플 비교는 저도 강의 준비하며 처음 알게 되어서 당연히 동일한 동작일 줄 알고 가독성 때문에 바꿔봤는데,

제가 테스트를 충분히 못해봤습니다..

무한 스크롤 쿼리는 인덱스를 통해 기준점을 로그 시간에 찾아야하는게 핵심인데,

튜플 비교는 인덱스를 제대로 못태우고 풀스캔으로 동작하며 WHERE 조건을 수행하고 있었군요..!?

그래서 뒷 페이지를 조회해야할 때, 조건을 모두 충족하는 limit을 채울 때까지 스캔해야하다 보니,

원하는 성능이 안 나오게 되는 것 같습니다.

겉으로 보면 인덱스를 타야할 것 같은데,

옵티마이저의 판단이라 명확한 원인은 찾아내기 어려울 수도 있을 것 같네요.

혹 명확한 원인을 찾게 된다면 추가 답변으로 달아두겠습니다.

이건 엄연히 테스트를 제대로 수행하지 못한 제 불찰입니다.. ㅠㅠ

 

일단 인덱스 동작에 대한 관점에서만 이해해주시고,

쿼리는 후자로 적용해주시면 감사하겠습니다..!

 

제보 정말 감사드리고, 위 부분은 강의에서도 수정해두겠습니다..!

강의 수강에 불편을 드려 정말 죄송합니다..!

쿠케님의 프로필 이미지
쿠케
지식공유자

Wonder님, 제 프로필에 메일 한통 보내주시겠어요?

Wonder님의 프로필 이미지
Wonder
질문자

알아보면서도 많은 공부가 되어서, 강의 수강에 불편함은 전혀 없었어요!

 

아직 명확한 원인은 잘 모르겠지만, 저도 공부하면서 더 알아가게 되면 공유하겠습니다!

 

동일한 현상과 관련된 MySQL 버그 리포트가 종종 있었던 것 같아서, 관련 히스토리부터 찾고 있습니다! (https://bugs.mysql.com/bug.php?id=111952)

쿠케님의 프로필 이미지
쿠케
지식공유자

너무 좋은 자세입니다!

저도 덕분에 배워가네요, 감사합니다!!

0

인프런 AI 인턴님의 프로필 이미지

안녕하세요, 인프런 AI 인턴입니다. Wonder님.

궁금하신 사항에 대해 아주 좋은 질문을 해주셨습니다. 말씀하신 튜플 비교 (a, b) > (x, y) 와 명시적 조건 a > x OR (a = x AND b > y) 이 SQL 최적화 과정에서 다른 결과를 보이고 있는 이유를 설명해드리겠습니다.

튜플 비교와 인덱스 스캔의 차이점

  1. 튜플 비교 (a, b) > (x, y):

    • 이 방식의 튜플 비교는 MySQL 옵티마이저가 복잡한 방식으로 평가하게 되며 최적화 과정에서 모든 가능한 조합을 탐색해야 하므로 인덱스의 효과적인 범위 스캔을 사용하지 않는 경우가 많습니다.
    • 기본적으로 여러 컬럼의 조건을 비교하는 성능 최적화가 다소 약할 수 있고, 인덱스를 이용한 범위 탐색을 적절히 수행하지 못할 때, 인덱스 풀 스캔(Full Index Scan)을 선택합니다.
  2. 명시적 조건 a > x OR (a = x AND b > y):

    • 이 방식의 쿼리는 각 조건을 명시적으로 나누어주어, MySQL 옵티마이저가 각 조건을 개별적으로 평가할 수 있다는 장점이 있습니다.
    • 조건이 명확하기 때문에, MySQL은 인덱스의 범위를 더욱 효과적으로 활용할 수 있습니다. 그래서 Index Range Scan을 진행해 빠른 성능을 낼 수 있습니다.

참고할 수 있는 추가 정보:

특정 조건이 명시적으로 정의됨에 따라, 복잡한 연산이나 튜플 비교 대신 각각의 조건이 독립적으로 최적화되고 적절하게 인덱스를 활용하게끔 유도됩니다. 더불어, MySQL의 인덱스 사용 및 쿼리 최적화에 대한 더 자세한 이해를 원하신다면 추가 학습에서 힌트를 얻을 수 있습니다.

질문에 도움이 될 만한 기존의 자료를 참고해보세요:
- MySQL 인덱스 최적화를 위한 이해 (게시글 제목)

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

Wonder님의 프로필 이미지

작성한 질문수

질문하기