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

주문 등록 API를 클라우드로 옮긴 뒤 한 건에 90ms가 걸리고 타임세일마다 커넥션 풀이 바닥난다

실전INSERT한빛유통589 / 700

상황 ​

한빛유통 쇼핑몰의 「주문 등록」 API는 지난달 API 서버를 클라우드로 옮긴 뒤 한 건에 평균 90ms가 걸린다(옮기기 전 8ms). 저녁 8시 타임세일에는 주문이 초당 140건, 순간적으로는 초당 450건까지 몰리면서 커넥션 풀(서버 한 대에 40개)이 바닥나 연결 대기 시간 초과가 난다. DB CPU는 20%도 쓰지 않는다. 개발팀은 세 가지 안을 냈다. ① 커넥션 풀을 150개로 늘린다. ② 지금 코드를 두고 SQL을 합치고 JDBC 배치와 RETURNING으로 호출 수를 줄인다. ③ 주문 등록 전체를 프로시저 하나로 옮겨 한 번에 부른다. 박팀장이 숫자로 견주어 하나를 고르고 고른 안의 대가도 적어 달라고 했다.

테이블과 인덱스 ​

  • T_ORD: 100,000행 · PK_T_ORD (ORD_SEQ) · 주문 번호는 시퀀스 SEQ_ORD(CACHE 1000)로 매긴다
  • T_ORD_DET: 200,000행 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) · 주문당 1 ~ 3줄(타임세일은 평균 2줄)
  • M_CUS: 2,000행 · PK_M_CUS · 등급 CUS_GD / M_ITM: 100행 · PK_M_ITM · 단가 UNT_PRC
  • 등급 할인 규칙(discount)은 API 서버의 자바 코드에 있고, 장바구니 화면의 예상 금액도 같은 코드로 계산한다.
  • API 서버와 DB의 왕복은 약 9ms다(옮기기 전 0.4ms). 주문 한 건이 한 트랜잭션이다.

원본 SQL ​

sql
// OrderRegisterService.java : 주문 한 건 (상품 2줄이면 DB 호출 9번)
String gd = jdbc.queryForObject("SELECT CUS_GD FROM M_CUS WHERE CUS_ID = ?", String.class, cusId);        // ①
long amt = 0;
for (CartLine c : cart) {                                                                                    // ② 줄마다
    c.prc = jdbc.queryForObject("SELECT UNT_PRC FROM M_ITM WHERE ITM_ID = ?", Long.class, c.itmId);
    amt += c.prc * c.qty;
}
long seq = jdbc.queryForObject("SELECT SEQ_ORD.NEXTVAL FROM DUAL", Long.class);                              // ③
jdbc.update("INSERT INTO T_ORD (ORD_SEQ, CUS_ID, ORD_DT, ORD_ST, ORD_AMT) VALUES (?, ?, SYSDATE, 'PENDING', ?)",
            seq, cusId, discount(gd, amt));                                                                  // ④
int no = 0;
for (CartLine c : cart) {                                                                                    // ⑤ 줄마다
    jdbc.update("INSERT INTO T_ORD_DET (ORD_SEQ, ORD_DET_NO, ITM_ID, ORD_QTY, UNT_PRC) VALUES (?, ?, ?, ?, ?)",
                seq, ++no, c.itmId, c.qty, c.prc);
}
Map<String, Object> saved = jdbc.queryForMap(
    "SELECT ORD_SEQ, ORD_DT, ORD_AMT FROM T_ORD WHERE ORD_SEQ = ?", seq);                                   // ⑥ 화면에 돌려줄 값
// ⑦ COMMIT (트랜잭션 끝)

실행계획 학습용 예시 ​

xplan
[주문 한 건 (상품 2줄) · APM 구간]
  호출     문장                                   DB 안       왕복 대기
  ①        SELECT CUS_GD FROM M_CUS ...           0.10 ms     9.0 ms
  ② × 2    SELECT UNT_PRC FROM M_ITM ...          0.10 ms     9.0 ms × 2
  ③        SELECT SEQ_ORD.NEXTVAL FROM DUAL       0.05 ms     9.0 ms
  ④        INSERT INTO T_ORD ...                  0.30 ms     9.0 ms
  ⑤ × 2    INSERT INTO T_ORD_DET ...              0.20 ms     9.0 ms × 2
  ⑥        SELECT ORD_SEQ, ORD_DT, ORD_AMT ...    0.10 ms     9.0 ms
  ⑦        COMMIT                                 0.60 ms     9.0 ms
  합계     9번                                     약 1.8 ms   81 ms     → 주문 한 건 약 90 ms (나머지는 API 서버 안)

[타임세일 20:00 ~ 20:10 · 인스턴스]
  주문 84,000건 · execute count 초당 1,120 · SQL*Net roundtrips 초당 1,270 · DB CPU 18%
  한 건이 연결을 90 ms 쥔다 → 초당 140건이면 동시에 12.6개, 순간 초당 450건이면 40.5개 (풀 40개)

문제 ​

  1. DB 안의 일은 2ms도 안 되는데 한 건에 90ms가 걸리고 풀이 바닥나는 이유를 위 숫자로 설명하시오.
  2. 세 안을 호출 수, 한 건의 시간, 동시에 쥐는 연결 수로 견주어 하나를 고르고, 고른 안을 코드로 쓰시오.
  3. 고른 안과 고르지 않은 안의 대가를 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자