여러 웹 서비스를 하나의 MongoDB Atlas 클러스터에서 운영하다 보면 트래픽이 증가할 때 서비스별 클러스터 분리나 멀티 리전 구성을 고민하게 됩니다.
특히 해외 사용자의 콘텐츠 조회가 늘어나면 데이터베이스를 사용자와 가까운 국가에 추가해야 하는지 판단하기 어려울 수 있습니다. 하지만 단순한 콘텐츠 조회 서비스라면 클러스터를 늘리기 전에 캐시 구조부터 점검하는 것이 중요합니다.
현재 운영 구조 예시
다음과 같이 하나의 MongoDB Atlas 클러스터에서 여러 서비스의 데이터베이스를 운영한다고 가정하겠습니다.
MongoDB Atlas 단일 클러스터
└─ 한국 리전
├─ content_service
├─ travel_service
├─ game_service
├─ community_service
└─ auth_service
각 서비스는 데이터베이스 이름으로 논리적으로 분리되어 있지만, 실제 MongoDB 서버 자원은 하나의 클러스터를 공유합니다.
MongoDB 지표를 비용으로 오해하면 안 되는 이유
Atlas의 Namespace Insights와 Query Profiler에서는 다음과 같은 정보를 확인할 수 있습니다.
| 예시 컬렉션 |
읽기 횟수 |
평균 처리시간 |
누적 처리시간 |
content_service.posts |
251 |
139ms |
34.93초 |
content_service.products |
59 |
224ms |
13.21초 |
travel_service.articles |
24 |
406ms |
9.74초 |
travel_service.places |
5 |
643ms |
3.22초 |
여기에서 Sum은 청구 금액이나 데이터 전송량이 아닙니다. 해당 기간 MongoDB 작업에 소요된 누적 실행시간입니다.
따라서 실행시간이 높다고 해서 해외 트래픽 때문에 데이터 전송 요금이 발생했다고 바로 판단할 수는 없습니다.
실제 비용은 Atlas 청구서에서 다음 항목을 따로 확인해야 합니다.
- 클러스터 사용료
- 저장 공간
- 백업 및 스냅샷
- 데이터 전송
- 추가 기능 및 네트워크 서비스
서비스별 MongoDB 클러스터를 분리해야 할까?
비용 절감이 목적이라면 처음부터 서비스별 클러스터를 분리하는 것은 추천하지 않습니다.
클러스터를 분리하면 서비스마다 다음 비용이 중복될 수 있습니다.
- 클러스터 기본 사용료
- 백업 비용
- 최소 저장 공간
- 모니터링 비용
- 유휴 CPU와 메모리
- 연결 및 보안 관리 비용
트래픽이 적은 서비스까지 별도 클러스터로 분리하면 사용하지 않는 자원에도 비용을 지불하게 됩니다.
현재 단계에서 권장하는 구조
MongoDB Atlas 단일 클러스터
├─ 서비스별 데이터베이스 분리
├─ 서비스별 전용 DB 계정 발급
├─ 컬렉션별 인덱스 최적화
└─ 애플리케이션 캐시 적용
데이터베이스 이름과 접근 계정은 서비스별로 분리하면서 물리적인 클러스터는 공유하는 방식입니다.
클러스터 분리를 검토해야 하는 시점
다음 조건이 나타나면 트래픽이 많은 서비스부터 별도 클러스터로 분리할 수 있습니다.
- CPU 사용률이 장시간 70~80% 이상 유지
- 메모리 부족이나 디스크 IOPS 병목 발생
- 특정 서비스 때문에 다른 서비스까지 느려짐
- 서비스마다 독립적으로 DB 사양을 조절해야 함
- 보안이나 접근 권한을 물리적으로 격리해야 함
- 서비스별 장애 격리가 사업상 중요해짐
- 백업 및 데이터 보존 정책이 서비스마다 다름
단순히 특정 컬렉션의 읽기 횟수가 많다는 이유만으로 클러스터를 분리할 필요는 없습니다.
MongoDB 멀티 리전은 비용 절감에 도움이 될까?
멀티 리전은 일반적으로 비용 절감보다 가용성과 글로벌 응답속도를 위한 구성입니다.
멀티 리전을 적용하면 다음 비용이 추가될 수 있습니다.
- 리전별 MongoDB 노드
- 리전 간 데이터 복제
- 네트워크 전송
- 추가 백업 및 스토리지
- 읽기 라우팅과 장애 대응 관리
따라서 해외 사용자가 많다는 이유만으로 멀티 리전을 구성하면 오히려 전체 비용이 증가할 가능성이 큽니다.
멀티 리전이 필요한 경우
다음과 같은 서비스라면 멀티 리전을 검토할 수 있습니다.
- 여러 국가에서 로그인과 검색 요청이 지속적으로 발생
- 실시간 개인화 데이터를 해외에서 조회
- 글로벌 결제나 예약 기능 운영
- 특정 국가의 리전 장애에도 서비스를 유지해야 함
- 캐시할 수 없는 동적 API 요청이 대부분
- 응답속도보다 높은 가용성이 더 중요함
반대로 기사, 상품 소개, 여행 정보, 게임 공략처럼 읽기 중심의 콘텐츠는 멀티 리전 DB보다 CDN 캐시가 효과적입니다.
해외 사용자는 CDN으로 처리하는 것이 유리하다
읽기 중심 서비스에서는 해외 사용자가 MongoDB까지 접근하지 않도록 구성해야 합니다.
해외 사용자
→ 가까운 CDN
→ 캐시된 콘텐츠 반환
→ MongoDB 조회 없음
콘텐츠가 없거나 캐시를 갱신해야 할 때만 애플리케이션 서버가 MongoDB를 조회합니다.
해외 사용자
→ CDN 캐시 미스
→ 애플리케이션 서버
→ 동일 리전 MongoDB
→ CDN 캐시 저장
여기에서 중요한 것은 사용자와 MongoDB의 거리가 아니라 애플리케이션 서버와 MongoDB의 거리입니다.
애플리케이션 서버와 MongoDB를 같은 리전에 배치하고, 사용자에게는 CDN으로 콘텐츠를 제공하는 구성이 일반적으로 가장 효율적입니다.
Next.js에서 MongoDB 조회를 줄이는 방법
페이지에 ISR 적용하기
변경 빈도가 낮은 게시물은 일정 시간 동안 정적 페이지로 캐시할 수 있습니다.
export const revalidate = 3600;
export default async function ArticlePage() {
const article = await getArticle();
return <Article article={article} />;
}
첫 요청이나 재검증 시점에만 MongoDB를 조회하고 이후 요청은 캐시된 페이지로 처리합니다.
MongoDB 조회 결과 캐시하기
MongoDB 드라이버를 직접 사용하는 함수는 별도로 캐시해야 합니다.
import { unstable_cache } from 'next/cache';
export const getArticleBySlug = unstable_cache(
async (slug: string, locale: string) => {
const db = await getDatabase();
return db.collection('posts').findOne(
{
slug,
locale,
},
{
projection: {
title: 1,
content: 1,
thumbnail: 1,
updatedAt: 1,
},
},
);
},
['article-by-slug'],
{
revalidate: 3600,
tags: ['articles'],
},
);
게시물을 등록하거나 수정한 경우에만 관련 캐시를 갱신합니다.
import { revalidateTag } from 'next/cache';
revalidateTag('articles');
여러 컬렉션을 반복 조회하는 구조 점검하기
하나의 페이지를 출력하기 위해 여러 컬렉션을 각각 조회하면 방문자 한 명당 많은 DB 요청이 발생할 수 있습니다.
const restaurants = await getRestaurants();
const cafes = await getCafes();
const shops = await getShops();
const events = await getEvents();
서로 의존하지 않는 요청이라면 최소한 병렬로 처리해야 합니다.
const [restaurants, cafes, shops, events] = await Promise.all([
getRestaurants(),
getCafes(),
getShops(),
getEvents(),
]);
데이터 구조가 유사하다면 하나의 컬렉션으로 통합하는 방법도 있습니다.
{
"type": "restaurant",
"locale": "en",
"region": "seoul",
"name": "Sample Place",
"slug": "sample-place"
}
const places = await db
.collection('places')
.find({
locale: 'en',
type: {
$in: ['restaurant', 'cafe', 'shop'],
},
})
.project({
name: 1,
slug: 1,
thumbnail: 1,
type: 1,
})
.limit(30)
.toArray();
단, 기존 컬렉션을 바로 통합하기보다는 캐시, 인덱스, 병렬 조회를 먼저 적용한 후 결과를 측정하는 것이 안전합니다.
비용 절감 적용 순서
MongoDB 비용이 높아졌다면 다음 순서로 점검하는 것이 좋습니다.
- Atlas 청구서에서 클러스터·백업·전송 비용 구분
- 애플리케이션 서버와 MongoDB 리전 일치 여부 확인
- 게시물과 상품 페이지에 ISR 적용
- MongoDB 조회 함수에 서버 캐시 적용
- 목록 조회 시 필요한 필드만 반환
- 페이지당 조회 개수 제한
- 검색 및 정렬 조건에 맞는 인덱스 생성
- 여러 컬렉션을 순차 조회하는 코드 제거
- 이미지와 정적 파일을 CDN에서 제공
- 실제 자원 병목이 확인된 서비스만 클러스터 분리
최종 권장안
| 구분 |
권장 결정 |
| MongoDB 클러스터 |
하나로 유지 |
| MongoDB 리전 |
애플리케이션 서버와 동일한 단일 리전 |
| 서비스 데이터 |
데이터베이스 이름으로 논리 분리 |
| 해외 콘텐츠 제공 |
글로벌 CDN 사용 |
| 게시물·상품 페이지 |
ISR 적용 |
| DB 조회 결과 |
서버 캐시 적용 |
| 서비스별 클러스터 분리 |
자원 병목 확인 후 적용 |
| 멀티 리전 |
고가용성 또는 글로벌 동적 서비스에만 적용 |
해외 사용자가 증가했다고 해서 바로 MongoDB를 해외 리전에 추가하거나 서비스별로 클러스터를 분리할 필요는 없습니다.
읽기 중심의 콘텐츠 서비스에서는 다음 구성이 비용과 성능의 균형이 가장 좋습니다.
단일 리전 MongoDB
+ 서비스별 논리 DB 분리
+ 애플리케이션 서버 동일 리전 배치
+ ISR 및 DB 캐시
+ 글로벌 CDN
클러스터 분리와 멀티 리전은 비용 절감 수단이 아니라 성능 격리, 장애 대응, 글로벌 동적 요청 처리를 위한 확장 전략으로 접근해야 합니다.