백엔드 개발자 블로그

Spring JDBC 성능 문제, 네트워크 분석으로 파악하기 본문

테크 블로그 리뷰

Spring JDBC 성능 문제, 네트워크 분석으로 파악하기

backend-dev 2024. 3. 13. 11:23

많은 양의 정산 데이터를 처리하기 위해 Spring Batch와 Spring JDBC(Oracle) 사용하는데, insert 과정에서 속도가 지연된다. 이를 해결하기 위해서 로그, TCP 패킷 확인, 디버깅 과정을 거쳐 원인을 찾아내고 해결하는 글을 보며 정리해본다.

https://toss.tech/article/engineering-note-7

 

Spring JDBC 성능 문제, 네트워크 분석으로 파악하기

토스페이먼츠 정산 플랫폼에서 많은 양의 정산 데이터 처리 과정에서 생긴 지연 이슈를 처리한 방법을 소개해요.

toss.tech

 

1. 상황

많은 양의 정산 데이터를 처리하기 위해 Spring Batch와 Spring JDBC(Oracle) 사용 중입니다.

여러 개의 데이터베이스 업데이트 명령을 한 번에 묶어서 처리하는 bulk insert를 사용하게 되는데, 5000개의 객체를 삽입할 때 무려 1분 이상이 걸리는 문제 발생했습니다.

 

2. 원인 찾기

1. 로그 레벨 변경해서 JDBC에서실행되는 모든 쿼리 기록 보기

insert 쿼리 외에 다른 쿼리 발견 안되서 pass

 

2. TCP 패킷 분석

단순한 write 있는 간단한 로직이므로 지연 문제가 발생한다면 데이터베이스와의 통신 중에 어떤 블로킹이 발생한 게 아닐까 추측했습니다.

WireShark라는 TCP 패킷 캡쳐 프로그램을 사용하여 로컬 데이터베이스 호스트에 연결을 한 뒤, 모든 TCP 패킷 확인하였고, bulk insert 쿼리가 실행되기 전에 해당 데이터베이스 테이블에 대한 select 쿼리가 다량으로, 계속해서 실행되는 문제를 발견할 수 있었습니다.

 

3. JDBC 디버깅

JDBC의 NamedJdbcTemplate 클래스의 batchUpdate 함수를 디버깅했습니다.

디버깅 결과, PreparedStatement에 넘긴 값이 null인 경우 문제가 발생한다는  것을 파악할 수 있었습니다.

PreparedStatement에 넘긴 값이 null인 경우 아래와 같이 작동합니다.

  1. setParameterValueInternal()에서 setNull() 함수 호출
  2. setNull()에서 Oracle 드라이버 구현체에 정의된 getParamterMetaData() 함수를 호출
  3. Oracle 드라이버의 getParamterMetaData() 함수는 DB의 메타데이터를 조회

즉, PreparedStatement에 넘긴 값이 null일 때마다 매번 DB 메타데이터를 조회하는 추가 과정 때문에 지연현상이 발생하는 것이였습니다.

 

3. 해결안

1. shouldIgnoreGetParameterType의 설정 변경하기

Spring 내에서 spring.jdbc.getParameterType.ignoretrue로 설정해서 적용하면 문제를 해결할 수 있습니다.

이를 설정하게 되면 추가 쿼리를 실행하는 대신, Spring JDBC 내부적으로 null 값을 어떻게 처리 설정할지 결정할 수 있습니다.

2. 파라미터 넘길 때 타입 명시하기

파라미터를 전달할 때 파라미터의 Sqltype 명시하면 문제를 해결 할 수 있습니다.

이렇게 하면 값이 null이어도 해당 파라미터에 대한 SQL 타입을 명확히 알고 있으므로 추가 조회가 이뤄지지 않습니다.

 

두 번째 해결안 사용

첫 번째 방법은 전체 시스템에 영향을 줄 수 있으로 사용하지 않았습니다.

특정 값이 null로 설정될 수 있는 경우가 제한적이고 예외적이라고 판단해서 타입 명시로 충분하다고 생각해서 두 번째 방법을 사용했습니다.

 

4. 결과 

100만 건 내외의 거래 데이터를 insert 하는 데 18분 소요되던 배치가 2분으로 줄어들었습니다.

 

 

5. 회고

다른 데이터베이스에서는 문제가 없을까?

Oracle 드라이버와 MySQL 드라이버를 getParamterMetaData() 함수를 비교해 봤습니다.

MySQL은 paraMeterData를 한 번 초기화한 후에는 객체를 계속 재사용해서 결과를 제공합니다.Oracle은 paraMeterData를 값을 조회하기 위해 매번 메타데이터를 조회합니다.즉, 메타데이터를 파악하기 위해서 MySQL은 한번만 조회가 일어나고, Oracle은 매번 조회가 일어납니다.

 

배운점

1. 성능 이슈를 진단하기 위해 네트워크 패킷 분석 접근 방식

TCP 패킷 분석으로 select 쿼리문이 실행된다느 것을 파악함

 

2. Spring JDBC와 DB 드라이버 구현체 상호 작용 방식

PreparedStatement 에 넘긴 인자값이 null인 경우 DB 드라이버의 getParamterMetaData() 함수를 사용하여 DB에서 메타데이터를 조회하여 가져옴