| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
- aws cloudmap
- aws ecs networkmode
- aws ecs awsvpc
- Spring boot Transactional
- DB 유니크
- Kafka 용어 정리
- compileclasspath
- sqs visibility timeout
- Kafka SQS
- promethues wal
- gradle api implementation
- ECS Linux OOM Killer
- 가상면접 사례로 배우는 대규모 시스템 설계 기초2
- TransactionEventListener
- DB 제약 조건
- sqs ack
- sqs 분산환경
- linux OOM Killer
- runtimeclasspath
- prometheus head
- Spring boot TransactionEvenetListener
- compileonlyapi
- aws ecs bridge
- flyway 여러 db
- sqs listener
- AWS
- api implementation
- aws vpc endpoint
- aws ecs service discovery
- DB 유일성
- Today
- Total
최근하고있는개발은영
MongoDB에서 넓고 얕게 파헤쳐보기 본문
0. 들어가기 앞서
주로 관계형 데이터베이스를 다루고 있지만, 레거시 프로젝트를 담당하게 되면서 MongoDB를 자주 다루게 되었습니다. 내부적으로 레거시가 많이 있어 이를 보완하고 어떻게 전환해야 하는지에 대해 많은 학습들이 필요하다고 생각되어 기본이 되는 MongoDB의 기능이나 내부적인 동작 원리에 대해서 이해하고자 글을 작성하게 되었습니다.
1. MongoDB 특징 및 관련 개념
1.1 MongoDB의 특징
MongoDB는 관계형 DB가 아닌 NoSQL DB입니다. 관계형 데이터베이스는 데이터를 테이블의 행과 열로 표현하고 테이블 간의 관계를 나타내지만 MongoDB는 하나의 데이터를 BSON 문서 형태로 저장합니다. RDB인 경우에는 특정 테이블 기본키를 외래키로 사용하고 있는 경우에 존재하지 않는 기본키를 외래키로 사용하지 못하게끔 제한할 수 있습니다. 하지만 MongoDB 같은 경우에는 없는 ID를 사용한다고 해도 문제가 없습니다. 그래서 애플리케이션 레벨이나 트랜잭션으로 참조 무결성 문제를 풀어나갈 수 있습니다.
1.2 BSON이란?
BSON은 Binary JSON의 약자로, JSON과 유사한 문서 구조를 바이너리 형식으로 표현한 데이터 직렬화 형식입니다. MongoDB는 데이터를 BSON 문서로 표현하고 사용합니다. 예를 들어 MongoDB에서 다음과 같은 문서를 저장할 수 있습니다.
{
_id: ObjectId("68c138f0542b2fc809514dad"),
name: "홍길동",
age: 30,
balance: NumberDecimal("12500.50"),
createdAt: ISODate("2026-09-09T10:00:00Z"),
active: true
}
겉으로는 JSON처럼 보이지만 실제로는 각 필드의 이름과 값뿐 아니라 BSON 타입도 함께 표현됩니다.
1.3 JSON과 BSON의 차이
JSON은 사람이 읽고 작성하기 쉬운 텍스트 형식인 반면 BSON은 MongoDB가 다양한 데이터 타입을 명확하게 구분하고 처리할 수 있도록 만든 바이너리 형식입니다. 표준 JSON에서 사용할 수 있는 타입은 비교적 단순합니다. 예를 들어 String, number, boolean, array, object가 JSON에서 나타낼 수 있는 타입이지만, BSON은 조금 더 디테일하게 ObjectId, Date, Int32, Double과 같은 타입을 추가로 제공합니다.
필드 타입 필드 이름 필드 값
ObjectId _id 68c138...
String name 홍길동
Int32 age 30
Decimal128 balance 12500.50
Date createdAt 2026-09-09...
Boolean active true
1.4 MongoDB 언제 쓰는 게 장점인가?
대표적으로 MongoDB를 사용하는 경우는 비정형 데이터를 하나의 문서로 저장할 수 있다는 점에서 성능적인 이점과 확장성에서 장점이 있습니다. 예를 들어 애플리케이션에서 문서를 통째로 하나 읽어서 뷰에 보여주는 상황입니다.
아래에 상품, 상품 상세, 상품 이미지, 상품 옵션, 카테고리, 브랜드 정보 같은 것들을 하나의 화면에서 조회해야 한다고 가정해 보겠습니다.
product
product_details
product_images
product_options
categories
brands
기본적으로 상품 ID를 조건으로 사용해 관련 테이블의 데이터를 모두 Join 하거나 여러 번 쿼리를 호출해서 데이터를 가져올게 될 것입니다. 다만 테이블마다 상품에 관한 데이터가 많고 성능을 신경 써야 한다면 여러 테이블에 걸친 join이 부담될 수 있습니다. 이를 해결하기 위해서 MongoDB document에 관련 데이터를 저장하고 Document를 읽어 한번에 데이터를 모두 불러올 수 있습니다. 또한 비정형 데이터이다 보니 상품 조회 시 조건에 따라 별도의 처리를 해야 한다면 데이터를 불러와서 애플리케이션 레벨에서 이를 체크하기 쉬워질 것입니다.
다만 이런 기능이 PostgreSQL JSONB로도 어느 정도 해결이 가능하기 때문에 기존에 PostgreSQL를 잘 쓰고 있다면 이종 DB 도입에 대해서 그 정도의 가치가 있는지 고민해 볼 필요가 있습니다.
그래서 실무에서 사용할 때 판단은 여러 상황들이 아래에 작성했습니다. 물론 상황에 따라 매번 선택이 달라질 것이기 때문에 이런 조건들이 있을 것이다 정도로만 참고하시면 될 거 같습니다.
- 관계형 데이터가 중심이고 일부만 유연하다 → PostgreSQL + JSONB
- 핵심 데이터 자체가 독립적인 문서이며 문서 단위 접근이 대부분이다 → MongoDB 고려
- MongoDB를 선택하는 이유가 “JSON 저장 가능” 하나뿐이다 → PostgreSQL + JSONB
- 명확한 샤드 키로 요청과 데이터를 분산해야 한다 -> MongoDB
- 문서 경로에 맞춘 인덱스를 설계하고 조회해야 한다 -> MongoDB
2. MongoDB 인덱스
2.1 mongodb 인덱스의 종류
1. _id 인덱스
- MongoDB의 모든 문서는 문서를 고유하게 식별하는 _id 필드를 가져야 합니다.
- 일반 컬렉션을 만들면 _id 인덱스가 자동으로 생성되고 _id 인덱스는 기본적으로 유일성 제약이 적용됩니다.
2. 단일 필드 인덱스
- 하나의 필드를 기준으로 생성하는 인덱스입니다.
- 인덱스 정렬 순서를 정할 수 있고, 동등 조건 및 범위검색등에서 사용할 수 있습니다.
3. 복합 인덱스
- 문서의 여러 필드를 조합해서 인덱스를 생성할 수 있습니다.
- 복합 인덱스에서 필드 순서는 중요한데 인덱스로 지정된 필드 순서대로 정렬된 데이터를 저장하기 때문입니다.
- 저장된 순서가 어떠냐에 따라 데이터를 조회하는 방법이 달라지고 그에 따라 조회 성능에서 차이가 발생합니다.
- 예를 들어 재고 관리 시스템에서 재고가 부족한 품목을 파악하기 위해 이름과 수량으로 복합 인덱스를 생성할 수 있습니다.
4. 멀티키 인덱스
- 멀티키 인덱스는 배열에 들어 있는 각각의 값을 인덱싱 하기 위한 인덱스입니다.
5. 해시 인덱스
- 해시 인덱스는 필드의 원래 값을 정렬해서 저장하는 대신, 필드 값으로 계산한 해시값을 저장합니다.
- 동등 조건 검색에는 유리하나, 범위검색에서는 사용하기 어렵습니다. 보통 해시 인덱스는 Hashed 샤딩을 위해 사용합니다.
2.2 WiredTiger 스토리지 엔진
mongodb 인덱스 동작 원리에 대해서 이해하기 전에 wiredTiger라는 개념에 대해서 알아야 합니다. WiredTiger는 MySQL InnoDB처럼 MongDB의 기본 스토리지 엔진입니다. 4.2 이전에 다른 스토리지 엔진도 있었지만 제거됐다. WiredTiger가 담당하는 주요 기능은 다음과 같습니다.
- 컬렉션과 인덱스 데이터 저장
- 메모리 캐시 관리
- 데이터와 인덱스 압축
- B-tree 페이지 관리
- 문서 단위 동시성 제어
- MVCC와 스냅샷
- 체크포인트와 장애 복구
2.3 MongoDB 인덱스 검색/저장/갱신
일반적인 인덱스는 정렬된 B-tree 기반 구조로 관리됩니다. 인덱스에는 필드값과 해당 문서를 찾아가기 위한 식별 정보가 저장됩니다. 예를 들어 아래 같은 형식의 document를 여러 개 저장하고 이메일에 대한 단일 인덱스를 만들었다고 가정해 보겠습니다.
{
_id: ObjectId("68c63cc6ea575a5cae27482f"),
name: "홍길동",
email: "hong@example.com",
age: 30
}
그러면 이메일이 인덱스 키이고 키에 대한 값이 document의 위치를 나타냅니다. 그리고 그 위치는 RecordID라고 합니다. 그래서 실제로 Mongodb에서 인덱스를 통해서 검색하는 경우 아래와 같은 플로우로 검색하게 됩니다.
검색 조건의 값
↓
B-tree 인덱스 탐색
↓
RecordId 조회
↓
컬렉션 데이터에서 BSON 문서 조회
MongoDB가 인덱스로 조회할 때 MySQL과 차이점이 있다고 하면 ID가 클러스터링 인덱스 역할을 하지 않는다는 것입니다. 일반 컬렉션의 _id 인덱스는 MySQL InnoDB의 클러스터형 기본 키와 동일한 역할을 하지 않습니다. 단, clustered collection을 생성하면 문서 자체가 _id 값에 따라 저장되므로 구조가 달라질 수 있습니다.
또한 당연하게 MongoDB에서도 인덱스는 삽입/수정/삭제 비용을 희생하는 대신 조회 성능을 높이기 위한 자료구조입니다. 예를 들어 인덱스를 _id, email 두 필드에 대해 적용했다면 새 document 데이터를 저장할 때 다음 작업이 발생하게 됩니다.
1. BSON 문서를 컬렉션에 저장
2. 문서의 RecordId 결정
3. _id 인덱스에 (_id, RecordId) 추가
4. email 인덱스에 (email, RecordId) 추가
인덱스 데이터를 생성하는 과정, b-tree의 리프노드에 대한 재구성이 이루어지기 때문에 삽입/삭제/수정 성능이 떨어집니다.
3. MongoDB 트랜잭션
3.1 mongodb 트랜잭션 동작하려면?
MongoDB 트랜잭션을 사용하려면 조건이 필요합니다.
- Replica Set 또는 Sharded Cluster 환경에서만 가능
- WiredTiger 스토리지 엔진
Replica Set / Sharded Cluster에서만 트랜잭션을 쓸 수 있는 이유는 해당 구조에서만 사용할 수 있는 기능을 사용하기 때문입니다. MongoDB는 세션 ID와 트랜잭션 번호를 이용해 여러 요청을 하나의 트랜잭션으로 묶습니다. WiredTiger는 트랜잭션이 끝날 때까지 변경 내용을 다른 작업에서 볼 수 없도록 관리하고, 커밋된 변경은 oplog를 통해 Secondary에 복제합니다. 이러한 기능이 앞서 말했던 구조를 기반으로 구현되어 있기 때문에 Standalone에서는 트랜잭션을 사용할 수 없습니다.
3.2 MongoDB Snapshot Isolation
Snapshot Isolation은 트랜잭션이 실행되는 동안 특정 시점의 데이터 상태를 일관되게 읽도록 보장하는 격리 방식입니다. 즉 트랜잭션이 시작된 이후 다른 트랜잭션이 데이터를 변경하고 커밋하더라도, 현재 트랜잭션은 처음 찍은 스냅샷 데이터만 읽게 됩니다. 예시를 정리하면 다음과 같습니다.
1. 트랜잭션 A 시작
2. 트랜잭션 A가 balance 조회 → 10,000원
3. 트랜잭션 B를 시작하고 balance를 5,000원으로 변경
4. 트랜잭션 B 커밋
5. 트랜잭션 A가 balance를 다시 조회 -> 10,000원
3.3 MongoDB 트랜잭션 동시성 제어
MongoDB의 WiredTiger는 낙관적 동시성 제어를 사용합니다. 두 트랜잭션이 같은 문서를 동시에 수정하면 하나가 성공하고 다른 하나에서 write conflict가 발생할 수 있습니다.
트랜잭션 A: 문서 1 수정
트랜잭션 B: 문서 1 수정
트랜잭션 A 커밋
트랜잭션 B WriteConflict
다중 문서 트랜잭션에서 일시적인 충돌이 발생하면 TransientTransactionError가 반환될 수 있으며, 이 경우 트랜잭션 전체를 다시 실행해야 한다.
4. 글을 마치며
MongoDB가 확장성이나 편리함 때문에 많이 사용한다는 것을 알고 있었는데, 그 내부에서 어떻게 동작하는지, 기능은 무엇들이 있는지 알 수 있는 시간이었습니다. MySQL과 비교했을 때 MongoDB도 주의점이 비슷했고 결국 비슷한 철학들이 있지 않나라는 생각이 들었습니다. 이외에도 더 많은 기능들이 존재하기 때문에 추가로 정리해보려고 합니다. 이번에 정리한 내용을 바탕으로 더 나은 설계를 할 수 있기를 기대하며 글을 마무리하겠습니다.
'DB' 카테고리의 다른 글
| 멱등성 API 설계하기 (0) | 2025.09.06 |
|---|