본문 바로가기
Log/Experiment

exp018_DB 인덱스 적용에 따른 래플 비딩 데이터 상세 조회 Latency 변화 관찰

by RIEM 2023. 3. 9.

Background Information

DALL·E 2023-03-09 20 31 50 - Pyramid that looks stable with sharp edge giving professional impression in dot art style | 400

<래플 어플리케이션 시스템 '피라미드'>

다수의 유저들이 소장 가치가 높은 패션 아이템들을 래플 방식으로 구매할 수 있도록 도와주는 웹 어플리케이션입니다. 신뢰성있고 신속한 대응으로 만족스러운 유저 경험을 제공하는 '피라미드' 시스템 구축을 위한 실험 과정을 기록한 내용입니다.

Framework : nest.js
DB : AWS RDS postgres

Title

exp018_DB 인덱스 적용에 따른 래플 비딩 데이터 상세 조회 Latency 변화 관찰

Research Question

  • 래플 비딩 데이터 상세 조회에 인덱싱을 적용하면 latency가 얼마나 개선될까?

Summary

bidSize = 270 조건 검색 쿼리의 성능 경우 인덱스 적용 시 API 요청 로직은 큰 변화가 없었다. 반면 bidSize > 250 and bidSize < 270 와 같은 범위 조건문의 경우 비즈니스 로직에 order by까지 포함되어 복잡도가 커서 인덱스의 성능 개선 효과 폭(61%)이 상대적으로 컸다.

Procedures

Data Recording & Analysis

특정 bidSize 조회

피라미드 시스템은 다양한 카테고리의 상품들을 지원할 수 있지만 VIP는 스니커즈 상품에 한정한다. bidSize는 유저가 입찰한 상품의 사이즈를 의미하는 프로퍼티다. 즉, 유저가 조던 검빨 신발 275을 입찰할 경우, 275가 바로 bidSize의 값에 해당하는 정보다.

bids.controller.ts

...
  @Get(':size')
  findBySize(@Param('size') size) {
    this.logger.log('size logger is triggered');
    return this.bidsService.findBySize(size);
  }
...
}

bids.service.ts

...
  async findBySize(size) {
    const result = await this.bidRepository
    .createQueryBuilder('bid')
    .select()
    .where('bid.bidSize = :size', { size: size})
    .getMany();

    return result;
  }
...

인덱스 적용 전

Screen Shot 2023-03-09 at 8 56 44 AM
테스트한 결과 404ms가 나왔다. 참고로 어플은 로컬, DB는 AWS RDS Postgres 연동했다.

-- DB SQL 스크립트 
-- 특정 bidsize 조회.
explain analyze select * from "Bid" b where "bidSize" = 275;

QUERY PLAN                                                                                                                |
--------------------------------------------------------------------------------------------------------------------------+
Gather  (cost=1000.00..20693.11 rows=19384 width=48) (actual time=0.229..109.378 rows=18967 loops=1)                      |
  Workers Planned: 2                                                                                                      |
  Workers Launched: 2                                                                                                     |
  ->  Parallel Seq Scan on "Bid" b  (cost=0.00..17754.71 rows=8077 width=48) (actual time=0.014..95.624 rows=6322 loops=3)|
        Filter: ("bidSize" = 275)                                                                                         |
        Rows Removed by Filter: 400059                                                                                    |
Planning Time: 0.054 ms                                                                                                   |
Execution Time: 110.848 ms                                                                                                |

DB SQL 스크립트에서 다이렉트로 요청 시 110ms가 소요된다.

인덱스 적용

인덱싱을 생성해보자.

-- bidsize 인덱스 생성
create index idx_bidsize on "Bid"("bidSize");

Screen Shot 2023-03-09 at 9 00 58 AM

인덱스 생성 전에는 약 404ms가 소요되었던 반면 인덱스 생성 이후에는 250ms가 소요되었다. 인덱스 생성 이후 Latency가 150ms 더 빨라졌다.

-- DB SQL 스크립트 
-- 특정 bidsize 조회.
explain analyze select * from "Bid" b where "bidSize" = 275;

QUERY PLAN                                                                                                                  |
----------------------------------------------------------------------------------------------------------------------------+
Bitmap Heap Scan on "Bid" b  (cost=218.65..12243.19 rows=19384 width=48) (actual time=2.433..13.686 rows=18967 loops=1)     |
  Recheck Cond: ("bidSize" = 275)                                                                                           |
  Heap Blocks: exact=9255                                                                                                   |
  ->  Bitmap Index Scan on idx_bidsize  (cost=0.00..213.81 rows=19384 width=0) (actual time=1.218..1.218 rows=18967 loops=1)|
        Index Cond: ("bidSize" = 275)                                                                                       |
Planning Time: 0.066 ms                                                                                                     |
Execution Time: 14.420 ms                                                                                                   |

DB 스크립트에서 직접 실행 시 약 14ms가 소요되는데 이는 인덱스 적용 전인 110ms의 약 10%에 해당하는 수준이다.

인덱스 적용 취소

혹시 인덱스를 다시 지우면 감소했던 만큼 동일하게 다시 Latency가 상승할까? 라는 의문이 들었다.

Screen Shot 2023-03-09 at 9 14 55 AM
API 요청 결과의 경우, 놀랍게도 큰 변화가 없었다. 265~400ms 사이로 측정된다. DB스크립트로 직접 DB요청할 경우에는 다시 이전 100ms 대 속도로 Latency가 증가한다. 정리하자면 이렇다.

bidSize = 270 조건 검색 쿼리에 인덱스 적용에 따른 Latency 변화

  • API 요청
    • 인덱스 미적용 : 404ms
    • 인덱스 적용 후 : 250ms(38% 감소)
    • 인덱스 삭제 후 : 265 ~ 400ms
  • DB 스크립트로 직접 요청
    • -> 인덱스 미적용 : 110ms
    • -> 인덱스 적용후 : 14ms (87% 감소)
    • -> 인덱스 삭제 후 : 104ms

DB에 직접 요청할 경우 인덱스의 효과를 관찰하기가 수월하다. 하지만 레이어가 더 추가된 API 요청의 경우 노이즈로 인해 인덱스 적용으로 인한 성능 개선 효과를 뚜렷하게 관찰하기가 상대적으로 어려웠다.

bidSize 범위로 조회

그렇다면 bidSize = 275 대신 260 ~ 270과 같이 range를 조건으로 주면 어떻게 될까?

인덱스 적용 전

bids.service.ts

...
  async findByRange(sizeFrom, sizeTo) {
    const result = await this.bidRepository
      .createQueryBuilder('bid')
      .select()
      .where('bid.bidSize > :sizeFrom', { sizeFrom: sizeFrom })
      .andWhere('bid.bidSize < :sizeTo', { sizeTo: sizeTo })
      .orderBy('bid.bidSize', 'ASC')
      .take(100)
      .getMany()

    return result;
  }
...

Screen Shot 2023-03-09 at 9 46 59 AM
218ms이면 양호한 듯 하다.

-- DB SQL 스크립트 
-- 특정 구간 사이즈 조회.
explain analyze select * from "Bid" b where "bidSize" > 250 and "bidSize" < 270;

QUERY PLAN                                                                                                      |
----------------------------------------------------------------------------------------------------------------+
Seq Scan on "Bid" b  (cost=0.00..29692.17 rows=354649 width=48) (actual time=0.011..131.516 rows=356067 loops=1)|
  Filter: (("bidSize" > 250) AND ("bidSize" < 270))                                                             |
  Rows Removed by Filter: 863078                                                                                |
Planning Time: 0.048 ms                                                                                         |
Execution Time: 144.738 ms                                                                                      |

스크립트 상으로는 약 144ms가 소요되었다.

인덱스 적용 후

-- DB SQL 스크립트 
-- bidsize 인덱스 생성
create index idx_bidsize on "Bid"("bidSize");

이전과 동일한 방식으로 bidSize에 인덱스를 생성해주었다.

Screen Shot 2023-03-09 at 9 49 56 AM
적용 전 218ms에서 84ms으로 감소했다. 몇번 더 클릭하니 30ms 대로 감소하기도 하는데, DB 내에서 캐싱이 되어서 그런게 아닐까 생각한다.

-- DB SQL 스크립트
-- 특정 구간 사이즈 조회.
explain analyze select * from "Bid" b where "bidSize" > 250 and "bidSize" < 270;


QUERY PLAN                                                                                                                       |
---------------------------------------------------------------------------------------------------------------------------------+
Bitmap Heap Scan on "Bid" b  (cost=4843.58..21568.31 rows=354649 width=48) (actual time=12.550..90.067 rows=356067 loops=1)      |
  Recheck Cond: (("bidSize" > 250) AND ("bidSize" < 270))                                                                        |
  Heap Blocks: exact=11405                                                                                                       |
  ->  Bitmap Index Scan on idx_bidsize  (cost=0.00..4754.92 rows=354649 width=0) (actual time=11.041..11.042 rows=356067 loops=1)|
        Index Cond: (("bidSize" > 250) AND ("bidSize" < 270))                                                                    |
Planning Time: 0.068 ms                                                                                                          |
Execution Time: 103.655 ms                                                                                                       |

스크립트 상으로도 144ms에서 103ms으로 감소했다.

정리하면 이렇다.

bidSize > 250 and bidSize < 270 조건 검색 쿼리에 인덱스 적용에 따른 Latency 변화

  • API 요청
    • 인덱스 미적용 : 218ms
    • 인덱스 적용 후 : 84ms(61% 감소)
  • DB 스크립트로 직접 요청
    • -> 인덱스 미적용 : 144ms
    • -> 인덱스 적용후 : 103ms (28% 감소)

Discussion

Conclusion

bidSize = 270 조건 검색 쿼리에 인덱스 적용에 따른 Latency 변화

  • API 요청
    • 인덱스 미적용 : 404ms
    • 인덱스 적용 후 : 250ms(38% 감소)
    • 인덱스 삭제 후 : 265 ~ 400ms
  • DB 스크립트로 직접 요청
    • -> 인덱스 미적용 : 110ms
    • -> 인덱스 적용후 : 14ms (87% 감소)
    • -> 인덱스 삭제 후 : 104ms

bidSize > 250 and bidSize < 270 조건 검색 쿼리에 인덱스 적용에 따른 Latency 변화

  • API 요청
    • 인덱스 미적용 : 218ms
    • 인덱스 적용 후 : 84ms(61% 감소)
  • DB 스크립트로 직접 요청
    • -> 인덱스 미적용 : 144ms
    • -> 인덱스 적용후 : 103ms (28% 감소)

위에서 정리한 데이터를 한 곳에 모았다.

bidSize = 270 조건 검색 쿼리의 성능 경우 인덱스 적용 시 API 요청 로직은 약 38% 개선된 반면 DB 스크립트 상에서는 87% 개선되었다. 다만, 인덱스를 삭제하고 재실험 시 API 요청은 큰 변동이 없다는 점에서 성능 개선 결과를 신뢰하기 어렵다.

bidSize > 250 and bidSize < 270 와 같이 범위 조건 검색 쿼리의 성능으 ㅣ경우, 인덱스 적용 시 API 요청은 약 61%로 개선 폭이 컸던 반면 DB 스크립트는 28% 감소했다. 범위 조건문의 경우 비즈니스 로직에 order by까지 포함되어 복잡도가 크기 때문에 인덱스의 성능 개선 효과가 더 커진 것이 아닐까 추측된다.

실제 서버에서 테스트

local환경에서 테스트해보았다면 이제 실제 서버에서 테스트해보자.

bidSize = 260 조건 테스트

Screen Shot 2023-03-09 at 10 58 12 PM
인덱스 생성 전 : 669ms

Screen Shot 2023-03-09 at 10 59 52 PM
인덱스 생성 후 : 470ms

  • 인덱스 생성 전 : 669ms
  • 인덱스 생성 후 : 470ms
  • 인덱스 제거 후 : 500ms

인덱스를 생성 후 latency가 669ms에서 470ms로 약 29% 감소했다. 하지만 인덱스를 제거해도 큰 변화가 없었다. 왜 인덱스를 적용할 때는 소폭의 감소가 있었는데, 인덱스를 제거한 뒤에도 큰 변화가 없다는 점이 조금 이상하다.

조건문이 너무 단순해서 그런 것일까? 데이터가 100만개 수준 밖에 되지 않아서일까?

복잡도가 좀 더 높은 로직을 테스트해보자.

bidSize > 250 and bidSize < 270 조건 테스트

Screen Shot 2023-03-09 at 11 17 53 PM
인덱스 적용 전 344ms

Screen Shot 2023-03-09 at 11 18 27 PM
인덱스 적용 후 24ms

bidSize > 250 and bidSize < 270  성능 변화

  • 인덱스 적용 전 : 344ms
  • 인덱스 적용 후 : 24ms
  • 인덱스 제거 후 : 365ms

빠르다. 93% 개선되었다. 혹시 몰라서 인덱스 제거를 하고 테스트해보니 다니 10배 수준으로 Latency가 증가했다. bidSize에 대한 조건들로 인한 복잡도가 증가하는 만큼 인덱스로 인한 성능의 개선 효과가 증가한다.

Reference

Appendix

댓글