백엔드 애플리케이션과 DB 서버 사이에서 벌어지는 일
백엔드 애플리케이션은 클라이언트로부터 요청을 받으면 비즈니스 로직을 처리합니다. 그 과정에서 DB 서버에 쿼리를 요청한 뒤 결과를 받아 다시 클라이언트에게 전달하는데, 이때 두 서버는 대부분 서로 다른 컴퓨터이기 때문에 네트워크 통신을 하게 됩니다. 그리고 이 통신은 일반적으로 데이터 송수신의 신뢰성을 보장하는 TCP 기반으로 이루어집니다.
TCP는 매우 안정적인 프로토콜이지만 이러한 안정성은 공짜가 아닙니다. 연결 지향적 프로토콜이기 때문에 통신을 시작할 때 연결을 맺는 과정이 필요하고, 통신이 끝날 때는 연결을 종료하는 과정이 필요합니다. 문제는 이 연결과 해제 과정이 생각보다 많은 비용을 요구한다는 점입니다. 만약 백엔드 서버가 DB 서버에 쿼리를 보낼 때마다 TCP 연결을 새로 열고 닫는다면, 실제 쿼리 실행 시간보다 네트워크 연결 비용이 성능에 더 큰 영향을 미칠 수 있습니다. 즉, 서비스 성능 저하의 원인이 쿼리 자체가 아니라 연결 관리 방식일 수 있는 것입니다.
이러한 문제를 해결하기 위해 등장한 개념이 바로 DBCP(Database Connection Pool)입니다. 핵심 아이디어는 단순합니다. TCP가 연결 지향적이라면, 아예 미리 연결을 여러 개 맺어 두고 재사용하자는 것입니다. 백엔드 애플리케이션은 DB 서버와 일정 개수의 커넥션을 미리 생성해 커넥션 풀에 보관해 둡니다. 이후 요청이 들어오면 새로 연결을 생성하는 대신 풀에서 대기 중인 커넥션 하나를 꺼내 사용하고, 처리가 끝나면 다시 풀에 반납합니다. 이렇게 하면 매 요청마다 발생하던 연결 비용을 제거하여 전체 응답 시간을 안정적으로 유지할 수 있습니다.
안정적인 운영을 위한 DB 서버의 설정
하지만 커넥션을 무한정 생성할 수는 없기에 DB 서버 측의 설정을 먼저 이해해야 합니다. 대표적인 것이 DB 서버가 동시에 허용할 수 있는 최대 커넥션 수인 max_connections입니다. DB 서버 입장에서 백엔드 서버는 하나의 클라이언트이므로, 애플리케이션이 생성하는 모든 커넥션은 이 제한의 영향을 받습니다.

예를 들어 DB 서버의 max_connections가 4라고 가정해 보겠습니다.
이때 백엔드 서버 A가 이미 4개의 커넥션을 다 사용하고 있다면, DB 서버는 더 이상 새로운 연결을 받아들일 수 없습니다. 이 상태에서 트래픽 증가로 인해 백엔드 서버 B를 추가로 투입하더라도, 서버 B는 DB 서버와 커넥션을 맺는 것조차 불가능한 상황에 놓이게 됩니다. 이 때문에 커넥션 풀의 크기를 설정할 때는 단일 서버 기준이 아니라, 전체 서버 수를 고려한 전역적인 관점에서 설계해야 합니다.
또한 커넥션은 항상 정상적으로 종료되지 않습니다. 네트워크 단절이나 장애로 인해 비정상적인 '유령 커넥션'이 남을 수 있는데, 이를 해결하기 위해 DB 서버는 wait_timeout 설정을 제공합니다. 일정 시간 동안 아무 요청도 오지 않는 커넥션을 DB 서버가 스스로 종료하도록 하여 리소스 낭비를 방지하는 장치입니다.
성능 최적화를 위한 백엔드 애플리케이션 설정
minimumIdle과 maximumPoolSize로 커넥션 유지
DBCP에는 커넥션 수를 조절하는 minimumIdle과 maximumPoolSize라는 대표적인 설정이 있습니다. minimumIdle은 풀에 항상 유지되어야 하는 최소한의 대기 커넥션 수이고, maximumPoolSize는 사용 중인 것과 대기 중인 것을 포함한 전체 최대 커넥션 수입니다. 만약 minimumIdle이 2이고 maximumPoolSize가 4라면, 초기에는 2개가 생성되었다가 요청이 늘어남에 따라 최대 4개까지 증가하고, 트래픽이 줄면 다시 2개로 돌아가는 구조입니다.
그러나 실제 운영 환경인 HikariCP에서는 이 두 값을 동일하게 설정하여 풀 크기를 고정하는 것을 권장합니다. 트래픽이 몰리는 순간에 커넥션을 새로 생성하면 다시 TCP 연결 비용 문제가 발생하고, 이 과정이 병목이 되어 중요한 요청을 놓칠 수 있기 때문입니다. 따라서 피크 트래픽을 기준으로 필요한 양을 미리 확보해 두는 것이 더 안정적입니다.
maxLifetime connectionTimeout으로 시간 설정
그다음으로 중요한 것이 커넥션의 유지 시간을 결정하는 maxLifetime입니다. 이 설정값은 반드시 DB 서버의 wait_timeout보다 짧아야 합니다. 만약 두 값이 같거나 maxLifetime이 더 길다면, 애플리케이션이 커넥션을 사용하려는 찰나에 DB 서버가 먼저 연결을 종료해 버릴 수 있습니다. 이 경우 서버는 살아 있다고 믿고 쿼리를 보내지만, 이미 종료된 연결이라 예외가 발생하거나 데이터가 유실될 위험이 있습니다.
마지막으로 커넥션을 얻기 위해 대기하는 시간인 connectionTimeout은 사용자 경험을 고려해 설정해야 합니다. 사용자는 보통 몇 초 내에 응답이 없으면 요청을 중단합니다. 만약 이 시간을 30초로 길게 잡아두면, 이미 떠나버린 사용자를 위해 서버가 소중한 리소스를 붙잡고 기다리는 셈이 되어 오히려 손해를 보게 됩니다.
적절한 커넥션 수를 찾는 방법: 측정과 검증의 반복
이렇게 다양한 설정값들이 유기적으로 얽혀 있다 보니, 우리 서비스에 딱 맞는 '정답'인 커넥션 수를 단번에 찾아내기란 쉽지 않습니다. 적절한 커넥션 수는 결코 감으로 정할 수 없으며, 반드시 실제 환경과 유사한 조건에서의 측정과 검증을 통해 결정해야 합니다.
가장 먼저 해야 할 일은 서버 리소스와 DBCP의 상태를 실시간으로 관찰할 수 있는 모니터링 환경을 구축하는 것입니다. 준비가 되었다면 nGrinder와 같은 부하 테스트 도구를 활용해 시스템에 스트레스를 주며 초당 처리량(TPS)과 평균 응답 시간을 확인해야 합니다. 이 과정에서 단순히 응답이 느려진다는 결과에만 집중하기보다, 병목의 실체가 무엇인지를 파악하는 것이 중요합니다. 현재 대기 중인 스레드가 부족한 것인지, 커넥션 풀이 말라버린 것인지, 혹은 DB 서버의 CPU나 메모리가 먼저 한계에 도달했는지를 꼼꼼히 살펴야 합니다.
만약 문제가 발견된다면 스레드 풀, 커넥션 풀, DB 서버의 설정, 그리고 백엔드 서버의 대수를 단계적으로 조정해 나갑니다. 한 번의 조정으로 끝나는 것이 아니라, 설정을 바꿀 때마다 다시 부하 테스트를 수행하여 변화를 확인하는 과정이 필요합니다. 때로는 커넥션 풀을 늘리는 것보다 쿼리 자체를 최적화하거나 서버의 대수를 늘리는 것이 더 효과적일 때도 있습니다. 결국 이러한 반복적인 실험 속에서 우리 시스템의 한계를 파악하고, 가장 안정적인 지점을 찾아가는 과정이 DBCP 최적화의 본질이라 할 수 있습니다.
쉬운코드 : BJ.52 DBCP의 개념부터 설정 방법까지! hikariCP와 MySQL을 예제로 설명합니다! 이거 잘 모르면 힘들...
'CS > DB' 카테고리의 다른 글
| NoSQL은 뭐지? SQL을 안 쓰는 걸까 (0) | 2025.12.29 |
|---|---|
| DB 성능 문제, 구조를 나누는 것부터 시작하기 (0) | 2025.12.28 |
| 데이터베이스 성능을 저하시키는 N+1 쿼리 문제란? (0) | 2025.10.03 |
| Transaction 알아보기 (0) | 2025.09.15 |
| Enum VS VARCHAR (0) | 2025.09.15 |
