부하 테스트 중 발생한 Connection is not available(timeout) 에러 해결

Ilhwanee·2022년 7월 2일
0

회고

목록 보기
3/9
post-thumbnail

Jmeter로 대량의 HTTP request를 보내는 부하 테스트를 진행하던 중, 10000개의 스레드가 동시 요청할 때 HikariCP connection-timeout 설정 값을 넘기게 되어 에러가 발생하였다.

처음에는 Connection의 개수가 부족해서 발생한 에러라고 생각하였고, HikariCP maximum-pool-size 설정 값을 높여 보았지만 해결되지 않았다. 또한 테스트에서 과도한 스트레스를 준 것도 아니었기 때문에, connection-timeout 설정 값은 문제가 없다고 생각했다.

무언가 이상하여 HTTP reponse 부분을 보니, 단 5개의 요청을 제외한 모든 요청에서 에러가 발생했다.

따라서 모든 스레드에서 Connection이 필요한데 모두 사용 중이고 아무도 반환하지 않는 데드락이 발생하였다고 판단했다.



원인은 GenerationType.AUTO

문제가 발생한 원인!

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

필자는 시퀀스 전략으로 GenerationType.AUTO를 사용하면 사용하는 데이터베이스의 환경에 알맞는 전략으로 변환한다고 알고 있었다.

그 중 우리 팀이 사용하는 데이터베이스는 MySQL 이었고, 이 경우 GenerationType.IDENTITY로 변환 된다고 알고 있었다.

하지만 이는 틀린 지식이었다.

Hibernate 5.2 버전 이상부터는 AUTO의 방식이 변경되었고, 사용한 Spring Boot의 버전인 2.7.0에서는 Hibernate 버전이 5.2 이상이었다.

이제 AUTO는 MySQL에서 IDENTITY가 아닌 TABLE을 시퀀스 전략으로 선택된다.

TABLE 전략은 MySQL에서 hibernate_sequence이라는 테이블에서 단일 row로 모든 테이블의 시퀀스를 관리하게 된다.



그렇다면 왜 TABLE 전략에서 데드락이 발생하였을까?

Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?

로그를 살펴보면, TABLE 전략에서 엔터티 저장 전 엔터티의 시퀀스를 생성하기 위해 hibernate_sequence 테이블에서 알맞은 시퀀스 값의 조회, 업데이트가 먼저 진행된다. MySQL의 for update 쿼리는 동일한 식별자 값을 막기 위하여 트랜잭션에 lock을 건다.

@Override
public <T> T delegateWork(WorkExecutorVisitable<T> work, boolean transacted) throws HibernateException {
	boolean wasAutoCommit = false;
	try {
		Connection connection = jdbcConnectionAccess().obtainConnection();

또한 이 과정은 또 하나의 Connection을 생성한다. "선후관계에서 모두 Connection이 필요한데, 이는 한정적이다." 여기서 데드락이 발생하는 것이다.

부하테스트 시작부터 데드락이 발생하는 과정을 살펴보면,

  1. 부하 테스트 시작 후, 수 많은 스레드(유저)의 요청으로 모든 스레드에서 엔터티 저장 트랜잭션 시작
  2. 엔터티 저장을 시작하기 위해 모든 Connection 사용 (남은 Conncetion 없음)
  3. 저장하려면 hibernate_sequence에서 시퀀스를 조회, 생성해야 하기 때문에 추가로 Connection이 필요함. 하지만 남은 Connection 없음.
  4. 모든 스레드에서 동시에 Connection이 반환되는 것을 기다림 (데드락 발생)

이렇게 데드락이 발생하고, connection-timeout 값만큼 시간이 경과하면 예외가 발생한다.

따라서 원하는 결과를 얻기 위하여 시퀀스 생성 전략을 AUTO에서 IDENTITY로 변경해주었고, 그 결과 문제 없이 부하 테스트를 진행할 수 있었다.



profile
블로그 이전 -> https://pppp0722.github.io

0개의 댓글