Loading the catalog…
Loading the catalog…
velog
예를들어, memberRepository.findById() 가 실행될 때, Spring 은 매번 DB 와 새로운 네트워크 연결을 만드는걸까 ? -> 보통은 그렇지않다, JPA 를 사용하는 경우 HikariCP 가 미리 관리하고 있는 DB Connection 을 빌려서 사용하고, 다시 반환한다. 보통 코드를 구현할때 아래와 같이 코드를 작성하고 끝낸다. @Transactional public void changeNickname(Long memberId, String nickname) { Member member = memberRepository.findById(memberId) .orElseThrow(); member.changeNickname(nickname); } 위에있는 코드가 서비스 코드에 있다고하면 위 코드를 실행한다면, 대략 아래와 같은 과정이 일어나게 된다. Service ↓ JPA / EntityManager ↓ Hibernate ↓ JDBC ↓ DataSource ↓ HikariCP ↓ Connection 대여 ↓ MySQL 오늘은 HikariCP ↕ JDBC Connection ↕ MySQL 위 과정을 중점적으러 보자. 일반적으로 DB Connection 을 새로 만드는것은 일반 JAVA 객체를 만드는것에 비해, 비용이 훨씬 많이든다. 새로운 DB Connection 을 생성하기 위해서는, Application ↓ 네트워크 연결 ↓ MySQL 접속 ↓ 인증 ↓ 세션 생성 ↓ Connection 사용 가능 위와같은 과정들이 필요하다. 이를, 매번 DB 요청마다 DB Connection 을 생성하고, 다 사용하면 없애는 식으로 하게되면 비용적으로 엄청난 손해이다. 이를 해결하기위해 ( 매번 새로운 DB Connection 을 생성 ) HikariCP의 Connection Pool 을 사용한다. HikariCP DB Connection Pool ┌───────────────┐ │ Connection 1 │ │ Connection 2 │ │ Connection 3 │ │ Connection 4 │ │ ... │ └───────────────┘ Spring 요청 → 빌림 → 사용 → 반환 HikariCP DB Connection Pool는, 이미 만들어놓은 DB Connection 을 보관하고 있다. 그래서, 요청이 들어오면 이미 만들어놓은 DB Connection 을 넘겨준다. 이를 사용하고나서, 작업들이 종료되게 되면 빌려준 DB Connection 을 받고 다시 Connection Pool 에 넣어놓는다. 흔히, DB 를 사용한다고 하면 설정정보 파일에 아래와 같은 정보들을 넣는다. spring: datasource: url: jdbc:... DB URL username: DB username password: DB password Spring Boot 가 해당 설정들을 읽는다. Spring Boot ↓ DataSource ↓ HikariDataSource ↓ HikariPool 이후, HikariCP는, DB Connection 들을 관리한다. 해당 요청이 들어왔다고 하자. @Transactional public void changeNickname(Long memberId, String nickname) { Member member = memberRepository.findById(memberId) .orElseThrow(); member.changeNickname(nickname); } DB작업이 필요해진다. memberRepository.findById(memberId); ( 영속성 컨텍스트에 해당 객체가 없다고 가정 ) Repository ↓ EntityManager ↓ Hibernate ↓ JDBC Hibernate 가 SQL 을 실행하려면, JDBC Connection 이 필요하다. 예를들어, SQL 이 다음과 같이 실행된다면 .. SELECT * FROM member WHWERE member_id = ? JDBC Driver 가, DB 에 이 SQL 을 보내야한다. ( DB -> MySQL ... ) 그럼, Connection 을 어디서 가져오는걸까? 여기서, HikariCP 가 등장한다. Connection 을 가지고 오기위해, Hibernate 가 DataSource 에게 Connection 을 요청을 한다. Connection connection = dataSource.getConnection(); dataSource 가, HikariDataSource 라면, dataSource.getConnection() ↓ HikariCP ↓ Pool에서 사용 가능한 Connection 검색 이 된다. 예를들어 혅재 Pool 이 Connection Pool C1 → 사용 중 C2 → 사용 가능 C3 → 사용 중 C4 → 사용 가능 이런식으로 있다면, 사용가능한 C2 / C4 중 1개를 빌려주게 된다. C2 를 빌려준경우 아래와 같은 과정이 된다. Application ↓ C2 ↓ DB ( MySQL ... ) 즉, 애플리케이션은 C2 ( DB Connection ) 을 통해, DB 에 SQL 을 보낼 수 있게된다. 여기에서 중요한것은, DB Connection 을 새로 만들어서 반환해주는게아니라, 미리 만들어놓은것중 사용가능한 DB Connection 을 반환한다는것이다. 이제, Hibernate 가 DB 에 SQL 을 실행한다. SELECT * FROM member WHWERE member_id = ? SQL 을 실행하고서, 결과가 있다면 결과를 반환해줄것이다. MySQL ↓ JDBC ResultSet (데이터베이스 쿼리 실행 결과를 저장하는 JAVA 인터페이스) ↓ Hibernate ↓ Member Entity ↓ Persistence Context 이후에는, 이전에 배웠던 JPA 내용과 연결된다. member.changeNickname(nickname); 현재 Member Entity 는 영속 상태이다. ( findById -> Member 를 찾아왔다고 하자. ) 따라서 아래와 같은 상황이 된다. Persistence Context Member nickname: old → Hoon 스냅샷을 통해서, 이전값과 현재값이 다른것을 확인했으므로 트랜잭션 종료 시점에 ( commit 전, flush ) Dirty Checking ↓ flush ↓ UPDATE SQL 생성 그리고, UPDATE SQL 도, 같은 트랜잭션안에서 사용하는 Connection 을 통해 실행이된다. ( 위 예제에서는 C2 를 사용했기에, 업데이트문도 C2 를 통해서 ... ) @Transactional 종료 ↓ Hibernate flush ↓ UPDATE ↓ Connection ↓ COMMIT Spring 의 TransactionManager 가 트랜잭션을 Commit 을 한다. JDBC 수준에서는, 현재 Connection 과 연결된 DB 트랜잭션이 commit 된다. Connection.close() 일반적으로, 서버에서 자원을 끌어다가 사용한 경우 반드시 해당 자원을 닫아줘야만 한다. Connection Pool 을 사용하지 않는 일반적인 Connection 이였다면, 반드시 connection.close() 를 호출해줘야 한다. 그래야, 실제 DB 연결 종료를 의미한다. 하지만, HikariCP 에서 빌린 Connection 의 close() 는 연결을 종료하는 의미가 아니다. HikariCP 에서 빌린 Connection close() -> 커넥션을 Connection Pool 에 반납. connection.close() ↓ HikariCP ↓ Connection Pool에 반환 즉, close -> 현재 예시에서는 C2 ( Connection 2 ) 를 Connection Pool 에 반납해서, 해당 Connection ( Connection 2 ) 는 사용가능하다라는 상태가 된다. C2 사용 중 ↓ close() ↓ 사용 가능 이후에, 또 다른 요청이 들어오면 C2 는 사용가능하기때문에 해당 요청에 Connection 을 넘겨줄 수 있다. 이전에 배웠던 내용들과 합쳐서, 전체적인 흐름은 다음과 같을것이다. HTTP Request ↓ Tomcat Thread ↓ Filter ... ( Spring Security ) ↓ DispatcherServlet ↓ Controller ↓ Service Proxy ↓ @Transactional ↓ JpaTransactionManager ↓ EntityManager ↓ Hibernate ↓ JDBC ↓ HikariDataSource ↓ HikariCP ↓ Connection 대여 ↓ MySQL ↓ SELECT / UPDATE ↓ COMMIT ↓ Connection 반환 Connection Pool 을 사용하면서 문제가 발생할 수 있다. Connection Pool 은 Connection 을 어느정도 미리 만들어놓고 사용하도록 반환해주기때문에, 사용중인 Connection 보다 새로운 요청이 더 많게되면, 새로운 요청은 Connection 을 얻지 못할수도있다. 예를들어, HikariCP Connection Pool 에 Connection 이 10개 있다고 하자. HikariCP C1 C2 C3 ... C10 동시에 DB 작업을 수행하는 요청들이 Connection 을 모두 잡았다고 하자. C1 사용 중 C2 사용 중 ... C10 사용 중 즉, 현재 Connection Pool 에 있는 Connection 들을 사용할 수 없다. 이러한 상황에서 11번째 요청이 들어왔다고 하자. 그렇다면, 해당 요청은 요청을 하고서 바로 Connection 을 얻을 수 없다. Request 11 ↓ getConnection() ↓ 사용 가능한 Connection 없음 ↓ 대기 . . . 이렇게 대기를 하게되는데 ... 일정시간동안 대기하고도 Connection 을 얻지못한다면 timeout 이 발생할 수 있다. 해당 문제는 주로, 다음과 같은 상황에서 발생한다. ( 트랜잭션 안에서 외부 API 를 호출하는 ) @Transactional public void process() { Member member = memberRepository.findById(1L) .orElseThrow(); externalApi.call(); ( 해당 작업이 5초가 걸린다고 가정. ) member.update(); } (1L 의 Member 가 현재, 영속성 컨텍스트에 없는 상황이라고 가정.) 위에 코드에서 Connection 을 확복한 상태라고하자. 그렇다면, 외부 API 를 호출하는 코드에서 5초동안 기다려야 하므로, DB Connection 을 마찬가지로 5초동안 가지고있는다. Connection 획득 ↓ SELECT ↓ 외부 API 호출 ████████ 5초 ████████ ↓ UPDATE ↓ COMMIT ↓ Connection 반환 만약, Connection Pool 에 Connection 이 10개만 존재하고, Connection 10개가 모두 해당 과정을 거친다고 하자. 그렇다면, 해당 작업들이 진행되고 있는동안에는 사용할 수 있는 Connection 이 존재하지 않기때문에, 이후 요청들은 모두 대기하는 상태가 된다. 그래서, 트랜잭션은 필요한 DB 작업 중심으로 짧게 유지한다. 라는것이 중요하다. 그렇다면, Connection Pool 안에, Connection 이 부족하지 않은 상황을 만들면 되는거아닌가? 라고 생각할 수 있다. 즉, Connection Pool 안에 Connection 들을 충분히 많이 만들어놓는것이다. spring: datasource: hikari: maximum-pool-size: 500 500개의 Connection 을 만들었다고하자. Connection 은 DB 입장에서도, HikariCP 입장에서도, 자원이다. 결국, 해당 Connection 들을 모두 관리해줘야 하기에 관리비용도 많이 들어갈것이다. 또한, DB 가 감당해야할 연결 수가 많아질것이고, 동시 작업도 커지게된다. 그렇기에, 적당한 Pool 크기를 잡아야한다. ( 이는 운영환경마다 다 다르니까 ... )
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
[Spring/DB/Java DAY5] - HikariCP 와 DB Connection Pool : Spring 은 요청마다 DB (MySQL) 에 새로 연결하는걸까?. 예를들어, memberRepository.findById() 가 실행될 때, Spring 은 매번 DB 와 새로운 네트워크 연결을 만드는걸까 ? -> 보통은 그렇지않다, JPA 를 사용하는 경우 HikariCP 가 미리 관리하고 있는 DB Connection 을 빌려서 사용하고, 다시 반환한다. 보통 코드를 구현할때 아래와 같이 코드를 작성하고 끝낸다. @Transactional public void changeNickname(Long memberId, String nickname) { Member member =…
Open source