Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[refactor] 인덱싱을 통해 출석 과정에서 학생이 강의 정보를 가져오는 시간 단축 #57 #58

Merged
merged 2 commits into from
Aug 14, 2023

Conversation

binary-ho
Copy link
Owner

What is this Pull Request about? 💬

결국 트래픽이 모이는 타이밍은, 출석 당시이다.
아주 짧은 시간 안에

로그인을 하고 (회원가입 할 수도 있다)
수강 신청 승인되었으며, 현재 OPEN 상태인 자신의 강의를 가져온 다음
출석 번호를 보내어 출석 신청을 한다.
전엔 3번 과정을 줄이기 위해 redis를 이용해 DB 접근을 한번 줄였다.
이번엔 2번 과정에서 시간을 줄이기 위해 강의 정보를 가져오는 과정을 인덱싱을 통해 줄이겠다.

자신의 강의이며
수강 신청이 승인 되었고
현재 OPEN 상태인 강의를 가져와야한다.
처음 복합 인덱스를 고려했으나, 다시 공부해 보니 바보 같은 생각이였다.
단일 인덱스로 변경하였고, 동시성 프레임워크를 사용해 직접 성능 차이와 인덱스 용량 테스트 했다.
결론적으로 단일 인덱스를 걸고, 쿼리를 수정했다.
쿼리를 수정하면서 outer join 하나가 줄었고, 사소하지만 객체 변환 과정도 줄어들었다.

2천명의 회원 4천개의 수강 신청 데이터가 있을 때 약 4배 정도의 성능 개선이 있었다.
RDS의 상태에 따라 충분히 결과의 차이가 있을 수 있는 측정이다.

1. 쿼리 변경

자바 동시성 컬렉션인 ThreadPoolExecutor와 CountDownLatch를 이용해 1000건의 동시 요청을 보냈다.
인덱스를 걸고, 쿼리를 아래와 같이 바꿨다.

성능을 테스트 하기 전에 쿼리를 바꾼 부분을 보이겠다.

Before

  • 기존 : 불필요한 member table join이 발생하고, 가져오고 싶은 정보는 결국 Lecture인데, Enrollment를 가져오고 있다.

image


발행된 쿼리

    select
        enrollment0_.enrollment_id as enrollme1_1_,
        enrollment0_.attendance_count as attendan2_1_,
        enrollment0_.enrollment_state as enrollme3_1_,
        enrollment0_.lecture_id as lecture_4_1_,
        enrollment0_.student_id as student_5_1_ 
    from
        enrollment_infos enrollment0_ 
    left outer join
        members member1_ 
            on enrollment0_.student_id=member1_.member_id 
    left outer join
        lectures lecture2_ 
            on enrollment0_.lecture_id=lecture2_.lecture_id 
    where
        member1_.member_id=? 
        and lecture2_.lecture_state=? 
        and enrollment0_.enrollment_state=?

After

  • 불필요한 Member Table join이 사라졌고, 실제로 사용할 정보인 Lecture를 가져오고 있다.

image

발행된 쿼리

    select
        lecture1_.lecture_id as lecture_1_2_,
        lecture1_.lecture_name as lecture_2_2_,
        lecture1_.lecture_state as lecture_3_2_,
        lecture1_.lecturer_name as lecturer4_2_,
        lecture1_.lecturer_id as lecturer5_2_ 
    from
        enrollment_infos enrollment0_ 
    inner join
        lectures lecture1_ 
            on enrollment0_.lecture_id=lecture1_.lecture_id 
    where
        enrollment0_.student_id=? 
        and enrollment0_.enrollment_state='APPROVAL' 
        and lecture1_.lecture_state='OPEN'

2. 성능 측정 테스트

실제 데이터를 dev용 RDS에 저장했다.

최대한 실제 상황과 유사하게 하고 싶었다.

당연히 RDS의 상태에 따라 성능 차이가 있을 것이다.

그리고 로컬 환경과 EC2 환경의 차이가 있을 것이다.

RDS와의 연결은 EC2가 같은 AZ라서 더 나을 수도 있을것 같다.

  • Execution Time : Postgresql 콘솔에서 직접 확인한 시간이다. 이 또한 RDS상황에 따라 다를 수 있겠다.
  • 소요 시간 : 서비스의 메서드를 호출하고 응답변환까지 마친 시간. 변환 시간을 포함한 이유는 쿼리를 개선하면서 응답 변환 시간도 줄일 수 있었기 때문었다. (정확하지 않다)
  • 인덱스 사이즈 : Postgresql 콘솔에서 직접 확인한 값이다.
SELECT
	indexname,
  	pg_relation_size(format('%I.%I', schemaname, indexname)) AS index_size
FROM
  pg_indexes 
WHERE
  tablename = 'enrollment_infos';

2.1 회원이 어느 정도 많은 상황

내 서비스에 4천개의 수강 신청 데이터가 있는 상황을 상상했다. 그러니까, 2천명의 회원이 가입했고, 각자 2개의 수업을 신청한 상황을 가정했다. 출석 상황에 1000명이 몰린 상황을 가정했다.

여러 수업들이 겹치면 1000명도 가능하지 않겠는가?
-> 2천명의 회원 4천개의 수강 신청 데이터가 있을 때, 1000명의 유저가 동시에 요청

2.1.1. 인덱스가 없을 때 : 11.1830초 소요 (1건 ExecutionTime 1.347ms)

2.1.2. memberId 단일 인덱스 : 2.9257초 소요, 인덱스 사이즈 114.688 KB (1건 Execution Time : 0.054ms)

2.1.3. enrollment_state 포함 복합 인덱스 : 2.9068초 소요, 인덱스 사이즈 196.608 KB (1건 ExecutionTime 0.050ms)

두 인덱스를 실제로 사용하는지 확인한 방법 + Execution Time 확인법

테스트 3 적은 데이터 복합 인덱스 실행 계획2

두 인덱스의 사이즈를 비교한 방법

  • 복합 인덱스 사이즈
    테스트 3 복합 인덱스 사이즈
  • 단일 인덱스 사이즈
    테스트 3 적은 데이터 단일 인덱스 사이즈

2.2 회원이 극단적으로 많은 상황

-> 1만명 회원 10만개의 수강 신청 데이터, 1000명의 유저가 동시 요청 (사실상 수강 신청 데이터 갯수가 중요함)

2.2.1. 인덱스가 없을 때 : 30.5735 초 소요 - 타임아웃, 668건 실패 (1건 ExecutionTime 91.522ms)

4 단일 인덱스 1000건 시간

2.2.2. memberId 단일 인덱스 : 6.4638 초 소요, 인덱스 사이즈 958.464 KB (1건 Execution Time : 0.052ms)

2.2.3. enrollment_state 포함 복합 인덱스 : 6.4872초 소요, 인덱스 사이즈 1458.176 KB (1건 ExecutionTime 0.046ms)

10만건에서의 인덱스 사이즈 비교

4 10만건 크기 비교

응답 시간 - 둘 다 6.4초대로 비슷했다

4 복합 인덱스 1000건 2

3. 결론

  1. 인덱싱을 통해 데이터가 많으면 많을 수록 큰 성능 개선을 이룰 수 있다.
  2. 복합 인덱싱과 단일 인덱싱의 용량 차이는 대략 1.5배 이상 차이났다.
  3. 처음 복합 인덱싱을 걸면 더욱 시간이 빨라질 것으로 생각했지만,
    다시 공부해보니 enrollment_state는 카디널리티가 매우 낮고, 그마저도 그 안에서 대부분의 값이 주로 찾아야 하는 상태이며, 어차피 한 사람당 수강 신청 갯수가 몇 개 안되기 때문에, 성능적으론 거의 차이가 없었다.
    따라서 용량 차이가 1.5배가 나는 것을 감수하고 복합 인덱스를 쓸 필요가 없다고 판단했다. 따라서, memberId에 단일 인덱스를 걸었다.
  4. 사실 더욱 빠른 응답 시간을 기대했다. 캐싱이나 다른 기법들을 고려해보겠다.

Key Changes 🔑

  • Enrollment Info 테이블에 인덱싱
  • 학생이 출석할 강의를 가져올 때 Lecture를 가져오도록 쿼리 변경
  • Lecture Service의 Student Open Lecture를 가져오는 메서드 수정

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant