본문 바로가기

SearchDeveloper/SpringBoot

[JPA] QueryDSL stream DB connection 에러 해결 과정 ①

버전

Spring Boot 2.7.4

QueryDSL 5.0.0

MySQL 5.7

 

DB 데이터를 가공해 카프카로 전송하는 애플리케이션을 만들면서 약 1주간 많은 난관에 봉착했고 많은 삽질을 했다. 어떤 에러가 있었고 어떻게 해결하였는지에 대해 공유하고자 한다.

stream() 으로 레코드 한 건씩 가져오는 건 줄 알았는데 왜 타임아웃이 나지!?

결론

.fetch().stream() 는 Java의 스트림, .stream() 은 DB의 스트리밍 방식이다!

에러 현상

쿼리 호출 대상인 tableA 테이블은 몇 십 만건의 데이터를 가지고 있다. 그래서 데이터를 한꺼번에 들고 오면 타임아웃이 나거나 메모리가 터질 수 있으므로 스트리밍 방식을 적용해 한 건씩 처리하기 위해 다음과 같이 구현을 하였다.

public Stream<TableA> bulk() {   
    return jpaQueryFactory
           .selectFrom(tableA)
           .fetch()
           .stream()
    ;
}

dev 환경에서 적은 양의 데이터로 쿼리 실행이 정상적으로 되길래 스트리밍 방식으로 잘 가져오는 줄 알았다. 근데 몇 십만 건의 데이터로 테스트 해보니 아래와 같은 에러가 나면서 터졌다.

HikariPool-7 - Connection com.mysql.cj.jdbc.ConnectionImpl@1235abb2 marked as broken because of SQLSTATE(08S01), ErrorCode(0)

com.mysql.cj.jdbc.exceptions.CommunicationsException: The last packet successfully received from the server was 35,235 milliseconds ago. The last packet sent successfully to the server was 59,935 milliseconds ago. is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem.

커넥션이 끊어지는 원인은 다양하지만 적은 데이터에서는 안 나던 에러가 많은 데이터일 때 났으니 스트리밍 방식이 제대로 동작하지 않아 한 번에 가져오다 타임아웃이 나는 건 아닌가 유추하였다.

원인 파악

그래서 fetch() 구현체를 살펴보니

AbstractJPAQuery.java
AbstractJPAQuery.java

 

fetch() 는 쿼리 결과 레코드를 모두 List 컬렉션에 담은 다음에 한꺼번에 반환하는 방식이었다. .fetch() 아래 달려있는 .stream() 은 Java Collection 클래스의 메소드로 단지 List 를 Stream 자료형으로 바꿔주는 역할에 불과했다.

해결

해결방법은

public Stream<TableA> bulk() {   
    return jpaQueryFactory
           .selectFrom(tableA)
           .stream()
    ;
}

.fetch() 를 제거해주는 것이다!

AbstractJPAQuery.java

그럼 JPAQueryFactory 클래스의 stream() 메소드를 통해 DB 스트리밍 방식으로 데이터를 전달하게 된다.

그리고 JPA에서 stream (Java 의 stream이 아닌 DB 의 steam!) 을 사용하려면 @Transactional 애노테이션을 붙여줘야한다.

@Transactional
public Stream<TableA> bulk() {   
    return jpaQueryFactory
           .selectFrom(tableA)
           .stream()
    ;
}

그렇게 두 번째 삽질이 시작되었다

다음글

https://elsboo.tistory.com/41

 

[JPA] QueryDSL stream DB connection 에러 해결 과정 ② - @Transactional

버전 Spring Boot 2.7.4 QueryDSL 5.0.0 MySQL 5.7 이전 글 https://elsboo.tistory.com/40 [JPA] QueryDSL stream DB connection 에러 해결 과정 ① 버전 Spring Boot 2.7.4 QueryDSL 5.0.0 MySQL 5.7 DB 데이터를 가공해 카프카로 전송하는

elsboo.tistory.com

[JPA] QueryDSL stream DB connection 에러 해결 과정 ①

글 읽어주셔서 언제나 감사합니다. 좋은 피드백, 개선 피드백 너무나도 환영합니다.