DB 통신 횟수를 줄이는 것이 성능 개선의 핵심이다

TPS 833013 화면에서 업로드 후 등록 처리가 40초 가까이 걸린다는 제보가 들어왔다.

원인을 찾아보니 트랜잭션 로그를 남기는 insertTrxnLogDocReceive 메서드에서 대부분의 시간이 소요되고 있었다. 구조를 파보니 문제는 단순했다. 엑셀 업로드로 들어온 데이터를 처리하면서 건마다 DB를 호출하고 있었다. 100건이면 100번, 1000건이면 1000번. 매번 커넥션을 열고, 쿼리를 실행하고, 결과를 받는 오버헤드가 수백 번 쌓이니 40초도 놀라운 게 아니었다.

// AS-IS: 건별 DB 호출
for (각 데이터) {
    insertTrxnLogDocReceive(mode, param, inParam, refno);
}

개선 방향은 명확했다. 각 건의 파라미터를 CoreMap에 적재한 뒤, 한 번의 DB 호출로 전체를 처리하는 배치 방식으로 전환한다. MyBatis의 foreach 구문을 활용하면 INSERT ALL로 한 번에 묶어서 처리할 수 있다.

// TO-BE: 파라미터 누적 후 일괄 처리
CoreMap batchParams = new CoreMap();
for (int i = 0; i < dataList.size(); i++) {
    batchParams.put(String.valueOf(i), dataList.get(i));
}
insertBatchTrxnLog(batchParams);
<!-- MyBatis foreach로 일괄 INSERT -->
<insert id="insertBatchLog">
  INSERT ALL
  <foreach collection="list" item="item">
    INTO TRX_LOG (...)
    VALUES (...)
  </foreach>
  SELECT * FROM DUAL
</insert>

기존 로직은 건드리지 않고 배치 전용 메서드를 오버로딩으로 추가했기 때문에 영향 범위도 최소화할 수 있었다. 기존 메서드를 수정하는 게 아니라 새 메서드를 추가하는 방식이라, 사이드 이펙트 걱정 없이 점진적으로 적용할 수 있었다.

성능 개선 결과는 극적이었다. 40초에 달하던 처리 시간이 몇 초 이내로 줄었다.

이 경험 이후로 성능 문제를 마주치면 습관적으로 “DB를 몇 번 호출하고 있지?”를 먼저 따지게 됐다. 네트워크를 타는 I/O는 생각보다 훨씬 비싸다. 루프 안에 DB 호출이 들어 있다면 거기서부터 의심해볼 만하다.

연결 (이유)

출처(참고문헌)