테마
버거 본사 운영팀의 「주문 조회」 화면은 기간(최대 31일)을 넣으면 최근 주문부터 한 쪽에 20건씩 보여 주고, 목록 위에 「총 n건」을 띄운다. 2016년에 만든 화면으로, 쪽 번호를 누를 때마다 DAO가 다시 불린다. 지난달 운영팀이 40명으로 늘자 쪽을 넘길 때마다 2~3초가 걸리고 화면 서버(WAS)가 Full GC로 멈칫한다는 신고가 잦아졌다. DBA는 "그 SQL은 인덱스를 잘 타고 실행당 DB 시간이 0.2초예요. WAS 쪽 문제 아닌가요?"라고 한다.
ORD 주문: 1,000,000행 · 22,422블록 · ORD_NO · ORD_DT VARCHAR2(8) · ORD_HMS VARCHAR2(6) · SHOP_NO · 날짜순 적재 · 2013년 11월 41,666건(테이블 약 934블록)ORD_PK (ORD_NO) · ORD_X01 (ORD_DT, ORD_HMS): BLEVEL 2 · 리프 약 3,570블록(11월은 약 149블록) · CF 22,223 · 통계 최신// OrderSearchDao.java (2016) : 쪽 번호를 받아 그 쪽의 20건과 총건수를 함께 돌려준다
public Page<OrderRow> search(String from, String to, int page) throws SQLException {
String sql = "SELECT ORD_NO, ORD_DT, ORD_HMS, SHOP_NO FROM ORD "
+ "WHERE ORD_DT BETWEEN ? AND ? "
+ "ORDER BY ORD_DT DESC, ORD_HMS DESC";
try (PreparedStatement ps = conn.prepareStatement(sql,
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) {
ps.setString(1, from);
ps.setString(2, to);
ResultSet rs = ps.executeQuery();
rs.last(); // 맨 끝 행으로 가서
int total = rs.getRow(); // 그 행 번호가 총건수
List<OrderRow> rows = new ArrayList<>();
if (rs.absolute((page - 1) * 20 + 1)) { // 그 쪽의 첫 행으로 돌아와 20건
do { rows.add(map(rs)); } while (rows.size() < 20 && rs.next());
}
return new Page<>(rows, total);
}
}
[1쪽 한 번 · 2013-11-01 ~ 2013-11-30 · 세션 통계 증가분 (V$SESSTAT)]
화면 응답 (APM) 2.71 초
execute count 1
SQL*Net roundtrips to/from client 4,169
bytes sent via SQL*Net to client 2.9 MB
session logical reads 9,425
DB time 0.21 초
SQL*Net message from client 4,168번 · 1.88 초 (평균 0.45 ms)
[V$SQL · 같은 실행] EXECUTIONS 1 · FETCHES 4,167 · ROWS_PROCESSED 41,666 · BUFFER_GETS 9,419
[이어서 37쪽을 누른 실행] 위와 같다 (ROWS_PROCESSED 41,666 · roundtrips 4,169 · 2.69 초)
[WAS · 지난달 대비 · 운영팀 40명]
힙 사용량 1.2 GB → 3.4 GB · Full GC 시간당 2번 → 31번 (한 번에 1~2초 멈춤)
힙 덤프 상위 : JDBC 드라이버가 ResultSet마다 쌓아 둔 행 캐시 (요청 하나에 41,666행, 약 9 MB)
[DISPLAY_CURSOR · 같은 실행 (모든 페치를 합친 값)]
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 41666 |00:00:00.19 | 9419 |
|* 1 | FILTER | | 1 | | 41666 |00:00:00.19 | 9419 |
| 2 | TABLE ACCESS BY INDEX ROWID | ORD | 1 | 41100 | 41666 |00:00:00.18 | 9419 |
|* 3 | INDEX RANGE SCAN DESCENDING | ORD_X01 | 1 | 41100 | 41666 |00:00:00.03 | 4318 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(:1<=:2)
3 - access("ORD_DT">=:1 AND "ORD_DT"<=:2)