목차
데이터베이스는 커넥션을 매번 획득

커넥션 획득 과정:
- 애플리케이션은 DB 드라이버를 통해 커넥션을 요청합니다
- DB 드라이버는 데이터베이스와 TCP/IP 소켓 통신을 시작합니다 (3-way handshake)
- 연결된 소켓을 통해 ID, 비밀번호 등 인증 정보를 전달합니다
- 데이터베이스는 인증을 확인하고, 내부에 세션을 생성합니다
- 생성이 완료되었다는 응답을 보냅니다
- DB 드라이버는 이 정보를 바탕으로 커넥션 객체를 생성하여 반환합니다
이 모든 과정은 애플리케이션 서버와 데이터베이스 서버 양쪽 모두에 리소스를 소모하게 하고, 특히 시간이 많이 걸린다는 점에서 문제가 됩니다. 사용자가 요청을 보낼 때마다 SQL 실행 시간 외에도 '커넥션 생성 시간'이 추가된다면, 전체 응답 속도가 느려지고 결국 사용자 경험에 부정적인 영향을 줄 수 있습니다.
해결책: 커넥션 풀

이러한 비효율을 해소하기 위한 대표적인 방법이 바로 커넥션 풀입니다. 이름 그대로, 미리 일정 수의 커넥션을 생성해 '풀(pool)'에 보관해 두고, 필요할 때마다 꺼내어 사용한 뒤 다시 반납하는 방식입니다. 매번 새로 커넥션을 생성하는 부담을 줄일 수 있어 응답 속도를 향상시키고, 서버 리소스 사용도 보다 효율적으로 관리할 수 있습니다.
동작 방식:
- 애플리케이션 시작 시점에 미리 커넥션을 생성하여 풀에 채워 둡니다
- 애플리케이션은 커넥션이 필요할 때 풀에서 이미 생성된 커넥션을 빌려갑니다
- 사용이 끝나면 커넥션을 닫는 대신, 풀에 다시 반납하여 재사용할 수 있도록 합니다
커넥션 풀의 장점:
- 성능 향상: 커넥션 생성에 드는 시간을 제거하여 애플리케이션 응답 속도를 크게 개선합니다.
- 리소스 효율성: 한정된 수의 커넥션을 재사용하므로 서버 리소스를 효율적으로 관리할 수 있습니다.
- DB 보호: 서버당 최대 커넥션 수를 제한하는 효과가 있어, 트래픽이 폭주하더라도 데이터베이스에 무한정 연결이 생성되는 것을 막아 DB를 보호합니다.
커넥션 풀 크기 최적화
'어? 커넥션 풀이 많으면 많을수록 대규모 트래픽에 유리한 거 아닌가?'라는 의문이 들 수 있지만, 실제 성능 테스트는 종종 그 반대를 보여줍니다. 예컨대 커넥션 풀 크기를 2048개에서 96개로 줄였더니 응답 시간이 100 ms에서 2 ms까지 극적으로 단축된 사례 (https://engineerinsight.tistory.com/235) 가 있습니다. 이는 Time Sharing Theory가 설명하듯 과도하게 많은 프로세스가 동시에 경쟁하면 문맥 전환 비용이 커져 오히려 전체 처리 속도가 느려지기 때문입니다. 따라서 실무에서는 직접 커넥션 풀을 구현하기보다, 성능과 안정성이 검증된 오픈소스 대표적으로 HikariCP나 Commons-DBCP2 를 사용하는 것이 일반적이며 특히 스프링 부트 2.0부터 기본값으로 채택된 HikariCP가 사실상 표준 선택으로 자리 잡았습니다.
커넥션 생명주기 관리: OSIV
커넥션 풀을 사용하더라도, '언제 커넥션을 빌리고 언제 반납할 것인가' 라는 생명주기 관리 문제가 남습니다. 스프링에서는 이를 관리하는 전략으로 OSIV를 제공합니다.
OSIV를 끄면 트랜잭션이 종료될 때 영속성 컨텍스트가 닫히고, 데이터베이스 커넥션도 즉시 풀에 반환됩니다.
OSIV ON

OSIV ON 전략은 데이터베이스 커넥션과 영속성컨텍스트를 API요청 시작부터 응답이 끝날 때까지 유지하는 방식입니다.
장점: Controller나 View계층에서도 지연로딩을 사용할 수 있어 개발 편의성이 높습니다.
단점: 커넥션을 너무 오랜시간 점유합니다. 만약 컨트롤러에서 외부 API 호출하고 응답을 기다리는 시간이 길어진다면, 그 시간동안 DB 커넥션은 반환되지 못하고 대기하게 됩니다. 실시간 트래픽이 중요한 서비스에서 이는 커넥션 풀의 커넥션 부족으로 이어져 결국 전체 시스템 장애를 유발할 수 있습니다.
OSIV OFF


OSIV를 끄면 트랜잭션이 종료될 때 영속성 컨텍스트가 닫히고, 데이터베이스 커넥션도 즉시 풀에 반환됩니다.
장점: 커넥션을 필요한 만큼만 사용하고 즉시반납 하므로 리소스 낭비가 없고, 실시간 서비스에 적합합니다.
단점: 트랜잭션 범위 밖에서는 지연 로딩이 동작하지 않습니다. 따라서 컨트롤러나 뷰에서 지연 로딩을 사용하려면 서비스 계층의 트랜잭션 안에서 미리 데이터를 모두 조회해 두어야 합니다.
실무 권장사항:
- 요청이 많지 않은 간단한 서비스나 커넥션을 많이 사용하지 않은 곳에서는 OSIV ON
- 고객 서비스 실시간 API는 OSIV OFF (예: 채팅 기능)
CQRS
CQRS 패턴 - Azure Architecture Center | Microsoft Learn
CQRS 패턴 - Azure Architecture Center
CQRS(명령 쿼리 책임 분리) 패턴을 사용하여 데이터를 업데이트하는 작업에서 데이터를 읽는 작업을 분리하는 방법을 알아봅니다.
learn.microsoft.com
OSIV를 끈 상태에서 발생하는 복잡성을 관리하는 가장 좋은 방법 중 하나가 바로 명령(Command)과 조회(Query)의 책임을 분리하는 CQRS (Command Query Responsibility Segregation) 패턴입니다

왜 분리하는 것이 좋을까요?
관심사의 분리 (SoC):
- Command: 데이터의 상태를 변경(생성, 수정, 삭제)하는 로직에 집중합니다. 비즈니스 규칙과 데이터의 일관성이 중요합니다
- Query: 데이터를 조회하는 로직에 집중합니다. 화면(API 응답)에 맞춰 데이터를 얼마나 빠르고 효율적으로 가져오는지가 중요합니다
성능 최적화에 유리:
- QueryService에서는 @Transactional(readOnly = true)를 적극 사용하고, 화면에 최적화된 DTO를 반환하기 위해 fetch join이나 QueryDSL을 자유롭게 사용하여 성능을 극대화할 수 있습니다
- CommandService는 JPA의 영속성 컨텍스트가 제공하는 핵심 기능에 집중하여 간결한 비즈니스 로직을 유지할 수 있습니다
API 설계와 테스트 용이성:
- REST API의 POST, PUT, DELETE는 Command, GET은 Query로 자연스럽게 매핑됩니다
- 역할이 명확히 분리되므로 테스트 시나리오가 단순해지고 유지보수성이 향상됩니다