MongoDB 커넥션 풀 모니터링과 알림 시스템 구축기
안녕하세요. 펫프렌즈 Pre-Order 팀 백엔드 개발자 제니입니다.
MongoDB 커넥션 풀 모니터링과 알림 시스템 구축기

안녕하세요. 펫프렌즈 Pre-Order 팀 백엔드 개발자 제니입니다.
트래픽이 급증하는 순간, “혹시 우리 데이터베이스 커넥션이 부족하진 않을까?” 걱정해보신 적 있으신가요? 저희 팀은 이런 불안을 해소하기 위해 DocumentDB 커넥션 풀 모니터링 시스템을 구축했습니다. 문제가 발생하기 전에 미리 알림을 받아 대응할 수 있게 된 과정을 공유합니다.
왜 만들게 되었나?
DocumentDB, 전시 영역의 핵심 데이터베이스가 되다
pre-order 팀은 상품 전시 영역을 담당하고 있습니다. 처음엔 RDS를 주로 사용했지만, CQRS 패턴을 도입 후 전시 데이터의 유연한 스키마 처리가 필요해짐에 따라, DocumentDB 사용이 확대되었습니다
(CQRS 파이프라인 도입기가 궁금하신 분들은 “MSA를 가기 위한 첫걸음 CQRS 파이프라인 도입기” 를 읽어보세요!)
상품 메타데이터, 카테고리 정보, 전시 레이아웃 설정 등 다양한 데이터가 DocumentDB로 옮겨갔고, 어느새 전시 영역에서 가장 중요한 데이터베이스가 되어 있었죠. 자연스럽게 DocumentDB의 안정성과 성능이 서비스 전체의 안정성에 직결되는 상황이 되었습니다.
성능 개선의 시작: 슬로우 쿼리 모니터링
DocumentDB의 중요성이 높아지면서, 단순히 잘 작동하는 것을 넘어 최적화가 필요했습니다. 가장 먼저 시작한 건 슬로우 쿼리 모니터링이었습니다.
이와 관련된 내용은 **DocumentDB 성능 최적화: 슬로우쿼리 수집부터 AI를 활용한 인덱스 추천까지 블로그를 참고해주세요!**
슬로우 쿼리로 집계된 개수가 30% 줄어드는 걸 보면서 모니터링의 중요성을 체감했죠.
다음 단계: 커넥션 풀 모니터링
슬로우 쿼리를 잡고 나니, 다음 병목이 보이기 시작했습니다. 바로 커넥션 풀이었습니다.
이벤트 발송으로 유저 인입이 급증할 때마다 “혹시 커넥션이 부족하진 않을까?” 걱정했지만, 실제로 현재 상태를 확인할 방법이 없었습니다. 슬로우 쿼리는 발견하고 고칠 수 있었지만, 커넥션 풀은 블랙박스나 다름없었거든요.
미리 풀 사이즈를 늘려두는 것도 한계가 있었습니다. 리소스 낭비일 수도 있고, 정확히 얼마나 필요한지 알 수 없으니까요.
“문제가 터지기 전에 미리 알 수는 없을까?”
슬로우 쿼리 모니터링처럼, 커넥션 풀도 모니터링하고 싶었습니다. 80% 도달 시점에 알림을 받는다면, 장애 전에 스케일 아웃이나 설정 조정으로 대응할 수 있을 것 같았습니다.
쿼리 성능 최적화에 이어, 이제는 리소스 사용량 최적화로 한 걸음 더 나아가는 시점이었습니다.
MongoDB 커넥션 풀, 왜 모니터링하기 어려울까?
MongoDB 드라이버는 내부적으로 커넥션 풀을 관리합니다. 요청이 들어오면 풀에서 커넥션을 빌려주고(Check-out), 작업이 끝나면 다시 반환받습니다(Check-in).
문제는 이 풀의 상태를 외부에서 볼 방법이 없다는 점입니다.
JMX로 메트릭을 볼 수 있지만 주기적 폴링이라 실시간 스파이크를 놓치기 쉽습니다. 게다가 임계치 초과를 자동으로 알려주지도 않죠.
그래서 ConnectionPoolListener를 직접 구현하기로 했습니다.
어떻게 구현했나?
1. ConnectionPoolListener 구현
MongoDB Java Driver가 제공하는 인터페이스를 구현하면 커넥션 풀의 모든 이벤트를 실시간으로 받을 수 있습니다.
@Slf4j
@Component
@RequiredArgsConstructor
public class MongoConnectionPoolListener implements ConnectionPoolListener {
private static final double WARNING_THRESHOLD = 0.8;
private final AtomicInteger checkedOutCount = new AtomicInteger(0);
private int maxPoolSize;
@Override
public void connectionCheckedOut(ConnectionCheckedOutEvent event) {
int current = checkedOutCount.incrementAndGet();
checkPoolUtilization(current);
}
@Override
public void connectionCheckedIn(ConnectionCheckedInEvent event) {
checkedOutCount.decrementAndGet();
}
@Override
public void connectionPoolCreated(ConnectionPoolCreatedEvent event) {
this.maxPoolSize = event.getSettings().getMaxSize();
log.info("MongoDB connection pool created with max size: {}", maxPoolSize);
}
private void checkPoolUtilization(int currentSize) {
double utilizationRate = (double) currentSize / maxPoolSize;
if (utilizationRate >= WARNING_THRESHOLD) {
log.warn("""
커넥션 풀 사용량 %.0f%% 초과.
현재 연결 수 : %d
최대 풀 사이즈: %d
사용률: %.2f%%""",
WARNING_THRESHOLD * 100, currentSize, maxPoolSize, utilizationRate * 100
);
}
}
}
이 코드의 핵심은 멀티스레드 환경에서 안전하게 동작하는 것이었습니다. 커넥션 체크아웃은 여러 스레드에서 동시에 일어나기 때문에 AtomicInteger를 사용해 thread-safe하게 카운팅했습니다. 그리고 커넥션이 체크아웃될 때마다 실시간으로 사용률을 체크해서, 80%에 도달하면 즉시 WARN 로그를 출력하도록 했습니다.
임계치를 80%로 설정한 이유는 간단합니다. 100% 도달 시점엔 이미 요청이 대기 중이라 늦거든요.
2. MongoDB 설정에 리스너 등록
@Configuration
public class MongodbConfig {
private final MongoConnectionPoolListener connectionPoolListener;
@Bean
public MongoClient mongoClient() {
MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(mongoUri))
.applyToConnectionPoolSettings(builder -> builder
.minSize(15)
.maxSize(30)
.maxWaitTime(5, TimeUnit.SECONDS)
.maxConnectionIdleTime(30, TimeUnit.MINUTES)
.addConnectionPoolListener(connectionPoolListener)
)
.build();
return MongoClients.create(settings);
}
}
커넥션 풀 사이즈는 운영 데이터를 바탕으로 설정했습니다. 평상시에 10~12개 정도 사용하고 있어서 minSize를 15로 여유 있게 잡았고, 피크 타임에 최대 25개까지 사용하는 걸 확인해서 maxSize를 30으로 설정했습니다. maxWaitTime은 5초로 설정했는데, 그 이상 대기하면 사용자 경험이 나빠질 수 있다고 생각했습니다.
3. OpenSearch Alerting + Slack 연동
로그만으로는 실시간 대응이 어려워서 OpenSearch의 Alerting 기능을 활용했습니다.
전체 알림 흐름은 다음과 같습니다.

각 단계를 자세히 살펴보겠습니다.
1단계: 커넥션 풀 사용률 80% 초과
애플리케이션에서 상품 조회 API가 호출되면, MongoDB 드라이버가 커넥션 풀에서 커넥션을 가져옵니다(Check-out). 이때 우리가 구현한 MongoConnectionPoolListener의 connectionCheckedOut 메서드가 호출됩니다.
@Override
public void connectionCheckedOut(ConnectionCheckedOutEvent event) {
int current = checkedOutCount.incrementAndGet(); // 24개 → 25개로 증가
checkPoolUtilization(current); // 사용률 체크
// 25/30 = 83.3% → 80% 초과!
}
2단계: MongoConnectionPoolListener가 WARN 로그 출력
임계치를 초과하면 즉시 경고 로그가 출력됩니다.
private void checkPoolUtilization(int currentSize) {
double utilizationRate = (double) currentSize / maxPoolSize; // 25/30 = 0.833
if (utilizationRate >= WARNING_THRESHOLD) { // 0.833 >= 0.8
log.warn("""
커넥션 풀 사용량 80% 초과.
현재 연결 수 : 25
최대 풀 사이즈: 30
사용률: 83.33%""");
}
}
실제로 출력되는 로그 예시
{
"@timestamp": "2025-12-09T00:00:00.000Z",
"level": "WARN",
"logger_name": "kr.co.petfriends.product.api.global.core.config.MongoConnectionPoolListener",
"message": "커넥션 풀 사용량 80% 초과.\n현재 연결 수 : 25\n최대 풀 사이즈: 30\n사용률: 83.33%",
"thread_name": "http-nio-8080-exec-12"
}
3단계: OpenSearch에 로그 적재
Logstash가 애플리케이션 로그를 수집해 OpenSearch로 전송합니다.
4단계: OpenSearch Alerting (5분마다 검색)
OpenSearch의 Alerting 기능이 주기적으로 쿼리를 실행해 조건을 확인합니다.
Monitor 설정:
{
"size": 0,
"query": {
"bool": {
"must": [
{"term": {"level.keyword": "WARN"}},
{"term": {"logger_name.keyword": "...MongoConnectionPoolListener"}},
{"match_phrase": {"message": "현재 연결 수"}}
],
"filter": [
{"range": {"@timestamp": {"from": "now-5m"}}}
]
}
},
"aggregations": {
"grouped_by_message": {
"terms": {
"field": "message.keyword",
"size": 10,
"order": [{"_count": "desc"}]
}
}
}
}
쿼리가 하는 일:
- 최근 5분간의 로그 중에서
level이 WARN이고logger_name이 MongoConnectionPoolListener이고message에 "현재 연결 수" 문구가 포함된 로그를 검색- 동일한 메시지끼리 그룹핑해서 카운트
검색 결과 예시:
{
"hits": {
"total": {
"value": 3 // 최근 5분간 3건 발생
}
},
"aggregations": {
"grouped_by_message": {
"buckets": [
{
"key": "커넥션 풀 사용량 80% 초과.\n현재 연결 수 : 25\n...",
"doc_count": 2
},
{
"key": "커넥션 풀 사용량 80% 초과.\n현재 연결 수 : 26\n...",
"doc_count": 1
}
]
}
}
}
5단계: Slack 메시지 발송
OpenSearch Trigger 조건을 충족하면 설정한 Slack 채널로 알림이 전송됩니다.
실제로 어땠나?
이벤트 발송 후 첫 알림
시스템을 구축하고 며칠 뒤, 평소와 다른 시간대에 Slack 알림이 도착했습니다.

무슨 일이 일어난 걸까?
OpenSearch에서 로그를 확인해보니, 오후 2시경 커넥션 사용량이 급증한 것을 확인할 수 있었습니다. 평소엔 10~15개 정도 사용하던 커넥션이 갑자기 25개까지 올라간 상황이었죠.
원인 분석
타이밍을 역추적해보니 마케팅팀에서 이벤트 알림톡을 발송한 후 였습니다.

다행히 maxSize 30개 설정 덕분에 장애로 이어지진 않았지만, 다음 대규모 이벤트를 대비해 아래와 같이 커넥션 풀 사이즈를 조정하여 조치했습니다.
커넥션 풀 사이즈 조정
// Before
.minSize(15)
.maxSize(30)
// After
.minSize(20) // 최소 커넥션 증가
.maxSize(40) // 최대 커넥션 여유 확보
마치며
ConnectionPoolListener 인터페이스 하나로 시작한 작은 개선이지만, 서비스 안정성에는 큰 도움이 되었습니다.
장애가 터진 후에 대응하는 것보다, 80% 시점에 미리 알고 대응하는 것이 훨씬 효과적이었습니다. 실제로 구현 후 6개월간 커넥션 풀 고갈로 인한 장애는 한 번도 발생하지 않았습니다. 이전에는 “혹시 문제가 생기면 그때 대응하자”라는 식이었는데, 막상 문제가 생기면 고객이 이미 타임아웃을 경험한 후거든요. 80% 알림 덕분에 문제가 커지기 전에 풀 사이즈를 조정하거나, 트래픽을 분산시킬 수 있는 시간적 여유가 생겼습니다.
단순히 “알림만 받는다”가 아니라, 알림을 계기로 시스템을 더 깊이 들여다보게 되었습니다. 처음 알림을 받고 OpenSearch에서 로그를 추적하며 어떤 API가 커넥션을 많이 사용하는지, 어떤 시간대에 트래픽이 집중되는지 분석하게 되었습니다. 특히 마케팅 이벤트 발송과 커넥션 사용량 사이의 연관성을 발견하면서, 이벤트 일정을 사전에 공유받는 프로세스까지 만들어졌죠. 알림 하나가 시스템 전체를 점검하는 계기가 된 셈입니다. 지금은 알림이 올 때마다 “왜 올랐을까?”를 추적하는 게 습관이 되었고, 그 과정에서 개선 포인트를 계속 찾고 있습니다.
특히 “문제가 생기기 전에 미리 안다”는 것이 얼마나 중요한지 체감할 수 있었습니다. 이벤트 발송 직후 알림을 받았을 때의 안도감은 이루 말할 수 없었죠. 알림이 없었다면 고객들이 타임아웃을 경험한 후에야 뒤늦게 알았을 테니까요.
여러분의 서비스에도 비슷한 불안 요소가 있다면, 작은 모니터링부터 시작해보시는 걸 추천드립니다!
참고 자료
메타데이터
- post_id
- 3ed3d4da2dfe
- slug
- mongodb-커넥션-풀-모니터링과-알림-시스템-구축기-3ed3d4da2dfe
- url
- https://techblog.pet-friends.co.kr/mongodb-%EC%BB%A4%EB%84%A5%EC%85%98-%ED%92%80-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81%EA%B3%BC-%EC%95%8C%EB%A6%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95%EA%B8%B0-3ed3d4da2dfe
- canonical_url
- https://techblog.pet-friends.co.kr/mongodb-%EC%BB%A4%EB%84%A5%EC%85%98-%ED%92%80-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81%EA%B3%BC-%EC%95%8C%EB%A6%BC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95%EA%B8%B0-3ed3d4da2dfe
- author_url
- https://medium.com/@jjaeun2_41190
- status
- ok
- fetched_at
- 2026-06-11 12:34:08