Skip to content
튜닝 연습파싱과 바인드 변수
PARSE-040

메인 화면의 「요즘 많이 본 상품」 칸 하나가 응답 시간의 절반을 차지한다

기초SELECT이커머스585 / 700

이 문제는 교재 18화 졸업 과제와 같은 이커머스 데이터베이스를 씁니다. 『튜너의 1년』을 순서대로 읽고 있다면 졸업 과제를 먼저 풀고 오세요. 아래 「테이블과 인덱스」 칸에 과제의 답 일부(제약 조건, 인덱스, 통계의 실제 상태)가 나옵니다.

상황 ​

이커머스 메인 화면에는 최근 30일 동안 많이 본 상품 10개가 뜬다. API 서버는 최근 30일의 조회 기록을 모두 받아 와 상품별로 세고, 상위 10개를 골라 상품명을 하나씩 읽는다. 메인 화면은 초당 20번쯤 열린다. 메인 화면 응답 0.21초 가운데 이 칸이 0.11초다. 개발자는 "조회 기록이 1,600건 남짓이라 자바에서 세는 게 DB를 덜 괴롭힌다"라고 한다.

테이블과 인덱스 ​

  • product_views: 100,000행 · 367블록 · view_id · product_id · customer_id · view_date · 인덱스는 PK뿐 · 최근 30일 조회 약 1,640건
  • products: 280행 · 8블록 · PK (product_id) · 상품명은 name
  • API 서버와 DB는 같은 IDC에 있고 왕복은 0.5ms다. JDBC fetch size는 기본값 10이다.

원본 SQL ​

sql
// PopularApi.java : 30일 치 조회 기록을 다 받아 와 자바에서 센다
List<Long> views = jdbc.queryForList(
    "SELECT product_id FROM product_views WHERE view_date >= SYSDATE - 30", Long.class);
Map<Long, Integer> cnt = new HashMap<>();
for (Long p : views) cnt.merge(p, 1, Integer::sum);
List<Long> top10 = cnt.entrySet().stream()
    .sorted(Map.Entry.<Long, Integer>comparingByValue().reversed())
    .limit(10).map(Map.Entry::getKey).collect(toList());
for (Long p : top10) {
    names.add(jdbc.queryForObject("SELECT name FROM products WHERE product_id = ?", String.class, p));
}

실행계획 학습용 예시 ​

xplan
[메인 화면 한 번 · 세션 통계 증가분]
  execute count                           11
  SQL*Net roundtrips to/from client      176
  session logical reads                  553
  bytes sent via SQL*Net to client      19 KB
  SQL*Net message from client           176번 · 0.09 초

[V$SQL · 조회 기록 문장]  EXECUTIONS 1 · FETCHES 165 · ROWS_PROCESSED 1,642 · BUFFER_GETS 532
---------------------------------------------------------------------------------------
| Id  | Operation         | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |               |      1 |        |   1642 |00:00:00.01 |     532 |
|*  1 |  TABLE ACCESS FULL| PRODUCT_VIEWS |      1 |   1644 |   1642 |00:00:00.01 |     532 |
---------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("VIEW_DATE">=SYSDATE@!-30)

[상품명 문장]  SELECT name FROM products WHERE product_id = :1 · 화면 한 번에 10번 · 한 번 2블록

문제 ​

  1. 화면에 쓰는 것은 10행인데 이 칸이 0.11초 걸리는 이유를 위 숫자로 설명하시오. 개발자의 말이 맞는지도 쓰시오.
  2. 같은 10개를 같은 순서로 돌려주도록 SQL 한 문장으로 고치고, 고친 뒤의 왕복 수를 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자