몽고디비는 대용량 데이터를 처리할 때 우수한 성능을 발휘하는 NoSQL 데이터베이스입니다.
그러나 트랜잭션 처리의 엄격성이 상대적으로 떨어지며, 네이티브 조인을 지원하지 않는 등 모든 용도에 적합하지 않을 수 있습니다.
이 글에서는 몽고디비의 장단점과 함께 자세한 예시로 확인해보도록 하겠습니다.
MongoDB
MongoDB는 고성능, 고가용성 및 쉬운 확장성을 제공하는 NoSQL, Document 지향 데이터베이스입니다.
데이터를 배열 및 중첩 Document와 같은 복잡한 데이터 유형을 효율적으로 저장할 수 있는 유연한 JSON과 유사한 형식인 BSON(Binary JSON)으로 저장합니다.
Database, Collection 및 Document
Document
MongoDB에서의 기본 데이터 단위로 관계형 데이터베이스의 행과 유사합니다.
document는 BSON 형식에 저장된 필드와 값 쌍으로 구성됩니다.
Collection
MongoDB의 컬렉션은 Document의 그룹이며 관계형 데이터베이스의 테이블과 유사합니다.
컬렉션은 단일 데이터베이스 내에 존재하며 스키마를 강제하지 않으므로 컬렉션 내의 Document는 다른 필드와 구조를 가질 수 있습니다.
Database
MongoDB 인스턴스는 여러 개의 데이터베이스를 호스트할 수 있으며 각각의 데이터베이스는 컬렉션의 컨테이너로 작용합니다.
데이터베이스는 디스크의 별도 파일에 데이터를 저장하며 각 데이터베이스에는 고유한 이름 공간이 있습니다.
BSON 데이터 형식
BSON은 MongoDB에서 사용되는 이진 직렬화 형식으로, 원격 프로시저 호출(RPC)과 같은 분산 시스템에서 데이터를 저장하고 전송하기 위해 만들어졌습니다.
이진 형식이기 때문에 텍스트 형식보다 적은 용량을 사용하며, 처리 속도가 빠릅니다.
또한, 길이 정보가 포함되어 있어 데이터 트래버스를 용이하게 합니다.
BSON은 문자열, 숫자(정수, 부동 소수점, 십진수), 날짜, 이진 데이터, ObjectId 등 다양한 데이터 유형을 지원하고, 이러한 데이터 유형을 효율적으로 저장할 수 있으며 이 덕분에 데이터를 빠르게 찾고 처리할 수 있습니다.
Bson에서 제공하는 타입에 대해서는 아래 링크에서 자세하게 확인이 가능합니다.
좀더 자세히 살펴 보자면
원격 프로시저 호출(RPC)은 한 시스템에서 다른 시스템의 프로시저(함수)를 호출할 수 있게 해주는 프로토콜입니다.
분산 시스템에서는 시스템 간의 상호 작용이 필요하기 때문에 RPC가 자주 사용됩니다.
예를 들어, 두 개의 서비스가 서로 통신해야 하는 상황을 생각해봅시다.
한 서비스는 상품 데이터를 저장하고 있는 MongoDB를 사용하고, 다른 서비스는 상품 검색 기능을 제공하는 Postgres를 사용합니다.
이 상황에서 두 서비스가 서로 통신하려면, 각 서비스의 데이터를 직렬화하고 이해할 수 있는 형식으로 전송해야 합니다.
이 때 BSON의 장점이 나타납니다.
- 이진 직렬화: BSON은 이진 형식으로 데이터를 직렬화하므로, 데이터의 크기가 작아집니다. 이로 인해 데이터 전송 시 소요되는 시간이 줄어들어 시스템 간의 통신이 빠르게 이루어질 수 있습니다. 이는 특히 대용량 데이터를 전송해야 하는 경우에 유리합니다.
- 처리 속도: BSON은 이진 형식으로 인해 처리 속도가 빠르다고 앞서 언급했습니다. 서로 다른 시스템 간의 통신에서 데이터를 받아들이고 처리하는 속도가 빠르다면 전체 응답 시간이 감소하게 됩니다.
- 다양한 데이터 유형 지원: BSON은 문자열, 숫자(정수, 부동 소수점, 십진수), 날짜, 이진 데이터, ObjectId 등 다양한 데이터 유형을 지원합니다. 이로 인해 서로 다른 시스템 간에 다양한 데이터 유형을 쉽게 전송하고 저장할 수 있습니다.
이진 직렬화 형식은 데이터를 이진 형태로 변환하여 저장하거나 전송하는 방식으로, 텍스트 형식에 비해 저장 공간을 효율적으로 사용하고 처리 속도가 빠릅니다.
이진 직렬화 형식은 트래버스, 즉 데이터 구조를 순회하거나 탐색하는 것이 쉽기 때문에 BSON 데이터를 빠르게 찾고 처리할 수 있습니다.
인코딩 및 디코딩 과정에서 BSON은 높은 성능을 보입니다.
이진 형식 덕분에 처리 속도가 빠르며, 길이 정보가 포함되어 있어 데이터의 시작과 끝을 쉽게 파악할 수 있습니다.
이러한 특징들로 인해 BSON은 MongoDB에서 뛰어난 성능을 발휘합니다.
Mongo Shell 에서 간단한 CRUD 예제
CREATE
insertOne() 또는 insertMany() 메서드를 사용하여 컬렉션에 Document를 삽입합니다.
db.users.insertOne({ "name": "John Doe", "email": "john.doe@example.com" })`
Read
find() 또는 findOne() 메서드를 사용하여 컬렉션에서 Document를 쿼리합니다.
$eq, $gt, $lt, $regex와 같은 쿼리 연산자를 사용하여 특정 기준에 따라 Document를 필터링할 수 있습니다.
db.users.find({"age": {"$gt": 30}})`
Update
Collection의 Document를 Update 하려면 updateOne(), updateMany() 또는 replaceOne() 메서드를 사용합니다.
$set, $inc, $push와 같은 업데이트 연산자를 적용하여 특정 필드를 수정할 수 있습니다.
db.users.updateOne({"_id": ObjectId("507f1f77bcf86cd799439011")}, {"$set": {"email": "new.email@example.com"}})
Delete
Collection에서 Document를 제거하려면 deleteOne() 또는 deleteMany() 메서드를 사용합니다.
db.users.deleteOne({"_id": ObjectId("507f1f77bcf86cd799439011")})
MongoDB의 장단점
장점
스키마-리스 디자인
RDBMS는 데이터 요구 사항이 변경될 때 미리 정의된 스키마를 필요로 하는 것과 다르게,
몽고디비의 유연한 스키마 디자인은 데이터 요구 사항이 변경될 때 매끄럽게 적응할 수 있도록 합니다.
이는 희소한 데이터 소스 또는 변경되는 데이터 모델을 갖는 애플리케이션에서 특히 유용합니다.
확장성
몽고디비는 read 기능에 대한 확장을 위한 레플리카 세트와 write 확장을 위한 샤딩을 제공하여 수평적 확장에서 뛰어납니다.
이 기능은 여러 대의 컴퓨터에 데이터를 분산하여 성능과 오류 허용성을 향상시키는 데 도움이 됩니다.
반면 RDBMS 솔루션은 비슷한 수준의 확장성을 달성하기 위해 비싼 하드웨어 업그레이드를 필요로 합니다.
확장성 샤딩을 통해 수평적 확장을 지원합니다.
높은 성능 인덱싱, 캐싱 및 메모리 내 저장을 지원하여 빠른 쿼리 처리가 가능합니다.
높은 가용성 replica sets을 지원함.
단점
트랜잭션 지원의 한계
MongoDB는 다중 document 트랜잭션을 지원하지만, 관계형 데이터베이스와 같이 엄격하지 않습니다.
MongoDB의 트랜잭션 처리는 ACID가 아닌 BASE 기반입니다.
이러한 차이로 인해, MongoDB의 트랜잭션 처리는 관계형 데이터베이스의 엄격한 ACID 트랜잭션 처리와 비교하여 덜 엄격하게 수행됩니다.
ACID(원자성, 일관성, 고립성, 지속성)는 데이터베이스 트랜잭션 처리의 전통적인 특성입니다.
ACID 트랜잭션은 데이터의 일관성을 보장하고, 복잡한 트랜잭션 처리를 안전하게 수행할 수 있도록 합니다.
관계형 데이터베이스는 ACID를 기반으로 하여 엄격한 트랜잭션 처리를 제공합니다.
반면, MongoDB는 BASE(Basically Available, Soft state, Eventual consistency)를 기반으로 합니다.
BASE 기반의 트랜잭션 처리는 ACID보다 느슨한 일관성 모델을 사용하며, 높은 가용성과 분산 시스템에 적합한 성능을 목표로 합니다.
MongoDB에서의 BASE 기반 트랜잭션 처리는 다음과 같은 특징을 갖습니다:
- 기본적으로 사용 가능(Basically Available): 시스템은 항상 응답을 제공하며, 일시적인 고장이나 네트워크 지연에도 높은 가용성을 유지합니다.
- 소프트 상태(Soft state): 시스템의 상태는 시간에 따라 변할 수 있으며, 일관성이 항상 보장되지 않습니다.
- 최종 일관성(Eventual consistency): 시스템은 일정 시간이 지난 후에야 최종적으로 일관된 상태를 달성합니다.
이러한 BASE 기반 트랜잭션 처리의 특성 때문에, MongoDB는 높은 성능과 확장성을 제공할 수 있지만, 관계형 데이터베이스의 ACID 트랜잭션 처리만큼 엄격한 트랜잭션 처리를 보장하지 않습니다.
왜??
웹 서비스의 발전에 따라 대량의 데이터를 처리하고, 빠른 성능과 수평 확장을 요구하는 데이터베이스 시스템이 필요해졌습니다.
이러한 요구 사항을 충족하기 위해, MongoDB는 데이터 무결성을 어느 정도 희생하면서 BASE를 기반으로 트랜잭션 처리를 수행합니다.
이러한 선택은 CAP 이론과 PACELC 이론에 근거합니다.
CAP 이론은 분산 시스템에서 일관성(Consistency), 가용성(Availability), 분할 허용성(Partition tolerance) 세 가지 속성 중 최대 두 가지만 동시에 보장할 수 있다고 주장합니다.
이 이론에 따라, 분산 데이터베이스 시스템은 이 세 가지 속성 중 두 가지를 선택해야 합니다.
MongoDB는 CAP 이론에서 가용성(Availability)과 분할 허용성(Partition tolerance)을 선택하여, 일관성(Consistency)을 어느 정도 희생하게 됩니다.
이 선택은 웹 서비스의 빠른 성능과 수평 확장성을 우선시하는 요구 사항을 충족하기 위한 것입니다.
PACELC 이론은 CAP 이론을 확장하여 분할이 발생하지 않은 상황에서도 일관성(Consistency)과 지연 시간(Latency) 사이의 균형을 고려해야 함을 주장합니다.
PACELC 이론은 데이터베이스 시스템이 분할 상황(Partition)에서 일관성(Consistency)과 가용성(Availability) 사이의 선택, 그리고 분할이 발생하지 않은 상황(Else)에서 일관성(Consistency)과 지연 시간(Latency) 사이의 선택을 고려해야 함을 강조합니다.
MongoDB는 PACELC 이론에 따라 pA / EC 시스템으로 분류됩니다.
즉, 분할 상황에서 가용성(Availability)을 선택하고, 분할이 발생하지 않은 상황에서는 일관성(Consistency)과 지연 시간(Latency) 사이의 균형을 맞춥니다.
이러한 선택은 MongoDB의 BASE 기반 트랜잭션 처리의 특성을 더욱 명확하게 보여주며, 웹 서비스의 빠른 성능과 수평 확장성을 달성하는 데 기여합니다.
대용량 데이터 저장
BSON 형식으로 인해 다른 데이터베이스에 비해 저장 공간이 더 많이 필요할 수 있습니다.
특히 대량의 작은 Document를 다룰 때 더 그렇습니다.
Join
MongoDB는 관계형 데이터베이스와 같이 네이티브 조인을 지원하지 않습니다.
그러나 집계 파이프라인에서 LeftOuterJoin을 수행하는 $lookup 연산자를 제공하며, 전통적인 조인에 비해 효율성이 떨어질 수 있습니다.
$lookup 예제
// orders 컬렉션
[
{ "_id": 1, "item": "abc", "price": 12, "quantity": 2 },
{ "_id": 2, "item": "jkl", "price": 20, "quantity": 1 },
{ "_id": 3, "item": "xyz", "price": 5, "quantity": 5 }
]
// inventory 컬렉션
[
{ "_id": 1, "sku": "abc", "description": "product 1", "instock": 120 },
{ "_id": 2, "sku": "def", "description": "product 2", "instock": 80 },
{ "_id": 3, "sku": "jkl", "description": "product 3", "instock": 60 }
]
leftOuterJoin을 수행하는 집계 파이프 라인
db.orders.aggregate([
{
$lookup: {
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
}
}
]);
결과값
[
{
"_id": 1,
"item": "abc",
"price": 12,
"quantity": 2,
"inventory_docs": [
{ "_id": 1, "sku": "abc", "description": "product 1", "instock": 120 }
]
},
{
"_id": 2,
"item": "jkl",
"price": 20,
"quantity": 1,
"inventory_docs": [
{ "_id": 3, "sku": "jkl", "description": "product 3", "instock": 60 }
]
},
{
"_id": 3,
"item": "xyz",
"price": 5,
"quantity": 5,
"inventory_docs": []
}
]
이 예제에서 orders 컬렉션의 각 문서는 item필드를 기준으로 inventory 컬렉션의 sku 필드와 왼쪽 외부 조인을 수행합니다.
이를 통해 orders 컬렉션의 각 문서에 대응하는 inventory컬렉션의 문서가 포함되고, 대응하는 문서가 없는 경우 빈 배열이 반환됩니다.
모든 용도에 적합하지 않음
MongoDB는 구조화되지 않은 데이터를 처리하는 데 우수하지만, 복잡한 트랜잭션, 엄격한 일관성 또는 전통적인 조인 연산이 필요한 애플리케이션에는 적합하지 않을 수 있습니다.
참조
performance - Pros and cons of MongoDB? - Stack Overflow
[MongoDB] 몽고DB에서 Join을 해보자 - $lookup으로 Join하기, MongoDB Join 예제 (tistory.com)
댓글