티스토리 뷰
spring batch 를 이용할때 어떤 reader 를 이용할지 항상 고민되는 편이다. DB 접근을 JPA 로 하고 있다면 대표적으로 JpaCursorItemReader, JpaPagingItemReader 중에 고민하게 된다.
보통 이 둘을 고민하는 상황이 오면 성능적으론 JpaCursorItemReader 가 더 좋다는걸 알게 된다. 하지만 대량의 데이터를 조회한 후 커서를 이동시키는 이 방식은 타임아웃을 유발할 수 있기에 데이터가 많을땐 조심해야해서, 이런 문제에 대해 안전한 JpaPagingItemReader 를 선택하는 경우가 많다.
하지만 페이징 기반의 리더를 쓰게되면 몇가지 곤란한 부분이 있는데 대표적으로 특정 상태를 이용해 조회한 후 상태를 변경하는 방식의 배치 잡을 만들때다. 1페이지를 조회한 후 2페이지를 조회하기 위해 페이지는 변경되는데 이미 상태가 변경되어 건너뛰는 데이터들이 발생하는 문제는 배치를 만들때 흔히 겪는 문제다.
이를 페이징 기반 리더에서 해결하려면 조회 쿼리 조건에서 상태를 빼거나, 페이징을 쓰지만 페이지 수가 올라가지 않게 별도의 구현을 해야하는데 두 방법 다 속 시원한 해결법은 아니다.
이럴땐 그냥 커서 기반의 리더를 사용하면 되는데 그러자니 위에서 얘기한 타임아웃 문제가 우려된다. 근데 커서 기반 리더는 어떤 타임아웃을 조심해야하는걸까? 커넥션은 이미 맺고 쿼리를 실행하는 걸테니 커넥션 타임아웃은 아닐거고, 리드 타임아웃일까? 막연히 타임아웃을 조심해야한다 정도만 들었지 정확히 어떤 타임아웃을 조심해야하고, 어떤걸 설정해줘야 하는지는 설명하는 자료가 없다. 이번에는 이 부분에 대한 내용을 정리해보려한다.
# 공식문서
이걸 찾아보면서 알게된건데 정말 놀랍게도 spring 공식 문서에는 커서 기반 리더를 다루는 문서에서 타임아웃을 전혀 다루고 있지 않다. timeout 으로 검색시 JdbcCursorItemReader 의 queryTimeout 에 대해서만 간략하게 나오는데 다른 CursorItemReader 에서는 timeout 이라는 단어 자체가 안나온다. queryTime 에 대해서는 아래에서 좀 더 알아본다.
# Javadoc
커서 기반 리더의 구현체들의 javadoc 도 살펴보았으나 여기서도 커서 기반의 리더를 이용할때 타임아웃을 조심하라는 내용은 없다. 타임아웃을 정말 조심해야하는 경우라면 어떤 프로퍼티를 조절하고, 대체제로 페이징 기반 리더가 있다는 내용이 들어가 있을 법한데 그런 언급은 어떤 구현체에도 없다.
# 테스트
공식문서를 보고난 후에 사실 좀 허탈했다. 그 동안 우려해온 타임아웃은 존재하지 않는 위험이었던 것인가? 라는 생각이 들었기 때문이다. 그래서 몇가지 테스트를 직접 해보기로 했다.
jdbc:mysql://localhost:3306/batch_cursor?connectTimeout=10&socketTimeout=10
mysql 커넥터의 connectTimeout 과 socketTimeout 설정을 극단적으로 짧게 지정하고, 조회코드에서 스레드를 블럭킹했다. 커서를 한번 이동시키는데 200ms 씩 블럭킹을 걸어도 연결은 끊어지지 않았다. 곧 저 두 타임아웃은 커서가 오랫동안 유지되는 것과는 상관이 없다는 것이다. 참고로 socketTimeout 이 readTimeout 과 같다.
영향이 있으면 socketTimeout 일텐데 socketTimeout 이 영향을 주지 않는게 신기했다. 그래서 정말 오랜만에 JDBC 코드를 짜보기로 했다.
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/batch_cursor?socketTimeout=10&connectTimeout=10", "root", "batch");
preparedStatement = connection.prepareStatement("SELECT * FROM item");
resultSet = preparedStatement.executeQuery();
while(resultSet.next()) {
System.out.println(resultSet.getString(1) + ":" + resultSet.getString(2));
Thread.sleep(200);
}
} catch(Exception e) {
e.printStackTrace();
} finally {
try {
resultSet.close();
preparedStatement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
조회에 사용된 item 테이블엔 1000개의 데이터를 넣어놨었기에, 커서를 모두 돌아가려면 200초가 걸린다. socketTimeout 은 10ms 로 지정되어있음에도 해당 코드는 정상적으로 천개의 출력을 모두 해낸다.
preparedStatement.setQueryTimeout(1);
아까 JdbcCursorItemReader 에서 등장했던 queryTimeout 속성이다. JDBC 코드에서 직접 설정해보기로 했다. 참고로 파라미터 단위는 초이기 때문에 1초를 타임아웃으로 지정한 것과 같다. 이럼에도 모든 커서는 200초 동안 정상 동작했다.
# 왜? 그리고 결론
JDBC 연결을 설정할때 다루는 몇가지 타임아웃을 조절해가며 테스트해봤지만 문제가 되는 부분은 하나도 없었고, 커서는 잘 순회했다. 내가 테스트해보지 않은 다른 타임아웃이 영향을 줄지는 모르겠지만 일단 대표적으로 건드리는 설정값에 대해서는 아무런 영향이 없었다. 특히 socketTimeout 이 영향을 주지 않은게 가장 의외였는데, JDBC 는 커서를 이동할때마다 데이터를 하나씩 가져오게 되는데 socketTimeout 은 이때 의미가 있는 값이 된다. 즉 커서를 이동한 다음에 오래 걸리는건 영향을 주는 대목이 아니고, 실제 커서를 이동할때 시간이 socketTimeout 을 넘어가면 문제를 일으키는 것이다. 그러니 커서 기반 리더를 이용한다고 괜히 socketTimeout 을 늘릴 이유는 없다. 커서 기반 리더가 타임아웃에 그렇게 예민한 리더가 아니었기에 공식문서에서도 특별히 조심하라는 문구가 있는건 아니었는데 아마도 결과를 오랜 시간 하나씩 가져온다는 개념이 막연히 타임아웃에 대한 걱정을 하게 한거 아닐까 싶다. 이제 커서 기반 리더를 이용하는 것도 막연한 걱정을 할 필요는 없을 것 같아 정리해놓는다.
# 참고자료
https://docs.spring.io/spring-batch/reference/readers-and-writers/database.html
'Java > batch' 카테고리의 다른 글
JpaCursorItemReader 는 어떻게 동작할까 (0) | 2024.06.01 |
---|---|
spring batch 에서 사라진 JobBuilderFactory (0) | 2024.05.15 |
Spring batch 테스트하기 (1) | 2022.05.28 |
JdbcPagingItemReader 에서 column not found (0) | 2021.08.28 |
spring-batch 에서 TaskExecutor 사용시 jvm 종료 안 되는 현상 (1) | 2021.05.02 |
- Total
- Today
- Yesterday
- Git
- Kotlin
- frontcode
- JavaScript Core
- DesignPattern
- go-core
- TEST
- clean code
- MySQL
- mariadb
- code
- EffectiveJava
- Spring
- 정규표현식
- servlet
- Jackson
- toby
- java
- frontend개발환경
- http
- Design Pattern
- OOP
- db
- spring cloud
- java8
- JPA
- generics
- javascript
- programming
- backend개발환경
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |