[spring] 대량 데이터 및 트래픽 처리 경험담... By starseat 2023-06-20 16:18:45 java/spring Post Tags 대량 트래픽 처리를 하게 되면서 경험한 내용을 정리해보고자 한다. `Spring Boot v2.7.10` 를 사용중이고 jdk 는 aws 의 `aws corretto 11` 을 사용하고 있다. # batch size 대량 데이터 처리를 위해 db 옵션 정보를 추가 했다. - url 뒤 `rewriteBatchedStatements=true` 옵션 추가 - jpa 에 `batch_size`, `order_inserts`, `order_updates` 옵션 추가 ```yaml datasource: url: {host}?rewriteBatchedStatements=true jpa: open-in-view: false show_sql: true generate_ddl: false properties: hibernate: dialect: storage_engine: innodb format_sql: true hbm2ddl.auto: none # must be none batch_size: 1000 order_inserts: true order_updates: true ``` # Async 설정 Async 설정은 예전에 간단히 정리했던 [[spring] Async 설정](https://starseat.net/blog/view/184) 로 하였다. # DB ReadTimeout 테스트 진행 중 아래와 같은 에러가 발생하였다. ```log org.springframework.dao.DataAccessResourceFailureException: could not execute statement; nested exception is org.hibernate.exception.JDBCConnectionException: could not execute statement at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:255) ~[spring-orm-5.3.26.jar!/:5.3.26] at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233) ~[spring-orm-5.3.26.jar!/:5.3.26] at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:566) ~[spring-orm-5.3.26.jar!/:5.3.26] ... Caused by: org.hibernate.exception.JDBCConnectionException: could not execute statement at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:48) ~[hibernate-core-5.6.15.Final.jar!/:5.6.15.Final] at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:37) ~[hibernate-core-5.6.15.Final.jar!/:5.6.15.Final] ... Caused by: java.sql.SQLInvalidAuthorizationSpecException: (conn=7726) Communications link failure with primary host {aws aurora db host}. Connection timed out at org.mariadb.jdbc.internal.util.exceptions.ExceptionFactory.createException(ExceptionFactory.java:66) ~[mariadb-java-client-2.7.9.jar!/:?] at org.mariadb.jdbc.internal.util.exceptions.ExceptionFactory.create(ExceptionFactory.java:158) ~[mariadb-java-client-2.7.9.jar!/:?] ... Caused by: java.sql.SQLNonTransientConnectionException: Read timed out at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.handleIoException(AbstractQueryProtocol.java:2089) ~[mariadb-java-client-2.7.9.jar!/:?] at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.readPacket(AbstractQueryProtocol.java:1539) ~[mariadb-java-client-2.7.9.jar!/:?] ... Caused by: java.net.SocketTimeoutException: Read timed out at java.net.SocketInputStream.socketRead0(Native Method) ~[?:?] at java.net.SocketInputStream.socketRead(SocketInputStream.java:115) ~[?:?] ``` 여기서 내가 눈여겨 본 부분은 `Read timed out` 이다. SQL 실행은 되었지만 조회 결과가 늦어 DB Connection Socket 의 Read timeout 이 발생한듯 하다. 이는 스택오버플로우의 [Instantaneous query timeout in Aurora MySQL (using jOOQ, MariaDB driver)](https://stackoverflow.com/questions/58062475/instantaneous-query-timeout-in-aurora-mysql-using-jooq-mariadb-driver) 를 참조하여 `jdbc-url` 에 `?socketTimeout=0` 를 추가하였다. # Duplicate entry 위 로그와 다른 로그도 발견 하였다. ```log - WARN [domain:xxx][SqlExceptionHelper][][TaskExecutor-2] SQL Error: 1062, SQLState: 23000 -- 1260454575474925751 7525979506970258262 - ERROR[domain:xxx][SqlExceptionHelper][][TaskExecutor-2] (conn=7726) Duplicate entry 'xxx' for key 'PRIMARY' -- 1260454575474925751 7525979506970258262 - INFO [domain:xxx][AbstractBatchImpl][][TaskExecutor-2] HHH000010: On release of batch it still contained JDBC statements -- 1260454575474925751 7525979506970258262 - ERROR[domain:xxx][SimpleAsyncUncaughtExceptionHandler][][TaskExecutor-PosSendBlock-2] Unexpected exception occurred invoking async method: public void asyncTest() -- 1260454575474925751 7352409539883517217 org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [PRIMARY]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:276) ~[spring-orm-5.3.26.jar!/:5.3.26] at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233) ~[spring-orm-5.3.26.jar!/:5.3.26] ``` 선배 개발자분의 말씀인데 insert 는 lock 을 못잡아서 동시 요청이 많은 경우 충분히 키 중복 에러가 발생할 수 있다고 한다. 그리고 주의해야될게 DB 가 읽기, 쓰기가 구분되어 있을때 데이터 save(insert) 후 바로 find(select) 를 하면 조회가 안된다고 한다. 이유는 복제 지연이 있기 때문이고 aws 문서에는 최대 100ms 라고 한다. 지금 DB 가 Master DB 가 있고 ReadOnly 용 DB가 별도로 있기에 나타난 상황인 것이다.. 지금 상황에서는 처리 상태 변경을 하는 경우이고, `jpa 의 save() 를 사용`하였다. update 시점에 select 를 하니 데이터 조회가 안되어 insert 를 하려던 찰나에 데이터가 있어서 `Duplicate entry` 에러가 발생한 것이다. jpa 와 QueryDSL 을 같이 사용하고 있어 이 부분은 `jpa 의 save()` 가 아닌 QueryDSL 에 update 문을 만들어 해결하였지만 다른 상황에서는 어떻게 해야될지 고민좀 해봐야 겠다. ## 참고내용 - [JPA에서 insert, update, delete 할 때 자동으로 select 하지 않게 하는...](https://yjh5369.tistory.com/entry/JPA%EC%97%90%EC%84%9C-insert-update-delete-%ED%95%A0-%EB%95%8C-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-select-%ED%95%98%EC%A7%80-%EC%95%8A%EA%B2%8C-%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95) - [[Spring Boot] DB Read-Write 분리(AWS RDS Aurora DB)](https://velog.io/@smkim104/Spring-Boot-DB-Read-Write-%EB%B6%84%EB%A6%AC) Previous Post [spring] Async 설정 Next Post [spring] 이벤트