MongoDB란?
MongoDB는 NoSQL 데이터베이스로, JSON 형태의 데이터를 저장하는 도큐먼트 지향 데이터베이스입니다. 웹 애플리케이션과 인터넷 기반 서비스를 위해 설계된 데이터베이스입니다. 데이터 모델과 지속성 전략은 높은 읽기/쓰기 효율과 Failover를 통한 확장의 용이성을 고려해 만들어졌습니다.
MongoDB를 볼 때 확장성보다 먼저 눈에 들어오는 것은 직관적인 데이터 모델입니다. 데이터 값은 Row가 아닌 도큐먼트에 저장합니다. 그럼 도큐먼트는 무엇일까요?
MongoDB의 도큐먼트 형식은 임의의 데이터 구조를 담을 수 있는 JSON에 기반합니다. JSON은 JavaScript Object Notation의 약자이며, XML과 함께 현대 웹에서 데이터를 주고받는 기본 형식입니다. 숫자, 문자열, 부울 값 같은 기본 타입을 지원하고 배열과 해시도 지원합니다. 구조는 키(key)와 값(value)의 쌍으로 이루어집니다. 이렇게 계층을 갖는 데이터 모델이라 여러 테이블로 쪼개 조인하지 않고도 한 덩어리의 데이터를 그대로 표현할 수 있습니다.
MongoDB는 SQL 대신 자체 쿼리 API를 쓰고, 스키마가 유동적입니다. 다만 조인이라는 개념 자체가 없는 것은 아닙니다. 집계 파이프라인의 $lookup 단계는 같은 데이터베이스에 있는 다른 컬렉션에 left outer join을 수행해 일치하는 도큐먼트를 배열 필드로 붙여 줍니다. 5.1부터는 from 에 샤딩된 컬렉션도 지정할 수 있고, 8.0부터는 샤딩된 컬렉션을 대상으로 트랜잭션 안에서 $lookup 을 쓸 수 있습니다. 공식 문서는 $lookup 성능이 외래 필드의 인덱스 여부에 달려 있다고 적으며, 관련 데이터를 한 컬렉션에 임베딩하는 모델을 먼저 검토하라고 권합니다.
MongoDB에서 저장하는 데이터 단위는 도큐먼트이고, 이는 RDBMS의 행(row)에 대응합니다. 도큐먼트의 속성은 정형화되어 있지 않아 형태가 일정하지 않은 데이터를 담기에 적합합니다. 객체지향 언어로 프로그래밍할 때는 언어에서 정의한 객체가 거의 그대로 저장되므로 객체 맵퍼(mapper)의 복잡성이 줄어듭니다.
MongoDB는 BSON(Binary JSON)이라는 이진 인코딩 형식으로 문서를 저장합니다. BSON은 JSON 모델을 확장해 추가 데이터 타입과 필드 순서를 제공하고, 여러 언어에서 인코딩·디코딩하기 효율적입니다. 도큐먼트 하나의 최대 크기는 16MiB이고, 그보다 큰 데이터는 GridFS API로 저장합니다. JSON 자체에는 중첩 제한이 없지만 BSON은 중첩을 100단계까지만 허용하며, 객체나 배열이 하나 생길 때마다 한 단계가 올라갑니다.
| RDBMS | MongoDB |
|---|---|
| Database | Database |
| Table | Collection |
| Index | Index |
| Row | Document |
| Join | 임베딩·링킹 또는 $lookup |
관계형 모델에서는 테이블이라는 틀 때문에 새로운 테이블을 만들고 두 테이블 사이의 관계를 컬럼에 저장해서 데이터를 표시할 수밖에 없습니다. 하지만 MongoDB 모델에서는 값을 배열(array)로 바꾸는 작업만으로 구조를 바꿀 수 있습니다.
MongoDB의 가장 큰 장점은 복제(Replicate)와 샤딩(Sharding)을 기본적으로 제공한다는 점입니다. 복제는 장애 대비와 데이터 보존에, 샤딩은 데이터를 분산해 I/O를 늘리는 데 사용합니다.
기존의 RDBMS에 비해 스키마 변경이 자유롭기 때문에, 미래를 예측하기 어렵거나 모델이 아직 확정되지 않은 경우에 MongoDB 도입이 유용합니다. 분산 환경이 필요한 경우에도 좋은 선택이 될 수 있습니다. 단 Schemaless라는 말은 스키마 제약에서 비교적 자유롭다는 뜻이지 스키마가 없다는 뜻이 아닙니다.
MongoDB Community Server는 2018-10-16 이후에 나온 릴리스부터 SSPL(Server Side Public License) v1이 적용됩니다. 그 전에 나온 릴리스는 GNU AGPL v3 그대로이고, 오래된 버전의 패치라도 2018-10-16 이후에 배포된 것이라면 SSPL 대상입니다. SSPL은 OSI 승인을 받지 않았고, MongoDB가 지원하는 드라이버와 커넥터는 Apache License v2.0으로 배포됩니다.
MongoDB의 주요 특징
- 신뢰성 (Reliability)
- 확장성 (Scalability)
- 유연성 (Flexibility)
- 인덱스 지원 (Index Support)
신뢰성(Reliability)이란 하나의 서버에 장애가 발생한 경우에도 서비스는 계속 동작할 수 있는 고가용성 환경을 MongoDB에서 자체적으로 지원한다는 것입니다.
확장성(Scalability)은 데이터와 트래픽 증가에 따라 수평 확장이 가능하다는 뜻입니다. 샤딩을 기본으로 제공하고, 서비스 중에 노드를 추가하며 데이터를 재분배할 수 있습니다.
유연성(Flexibility)은 여러 가지 형태의 데이터를 저장할 수 있음을 나타내며 개발 과정 중에 스키마 변경이 필요한 경우 대처가 가능합니다.
MongoDB는 RDBMS처럼 인덱스 지원(Index Support)을 하기 때문에 다양한 조건으로 빠른 데이터 검색이 가능합니다. 텍스트 인덱스, TTL 인덱스, 공간 인덱스 등 여러 인덱스를 지원합니다.
유연한 스키마와 스키마 검증
데이터 구조를 데이터베이스가 아니라 애플리케이션이 정합니다. 구조가 자주 바뀌는 초기 개발에서는 이 방식이 시간을 벌어 주고, 가변적인 속성을 갖는 데이터도 그대로 표현할 수 있습니다.
다만 스키마가 없는 것과 스키마를 강제하지 않는 것은 다릅니다. 기본값은 한 컬렉션의 도큐먼트가 같은 필드나 같은 타입을 가질 필요가 없는 상태이고, 여기에 스키마 검증(schema validation) 규칙을 걸면 필드별로 허용 타입과 값 범위를 지정할 수 있습니다. 규칙은 $jsonSchema 연산자나 일반 쿼리 연산자로 작성합니다. 규칙을 걸고 나면 모든 insert가 규칙을 통과해야 하고, 이미 저장된 도큐먼트와 업데이트에 규칙을 어디까지 적용할지는 검증 수준이 결정합니다. 기본 동작은 유효하지 않은 도큐먼트를 만드는 insert·update를 거부하는 것이며, 대신 허용하고 경고만 남기도록 바꿀 수도 있습니다.
공식 문서는 스키마 검증이 데이터 구조가 정해진 애플리케이션에 가장 유용하다고 적습니다. 그래서 MongoDB에서도 Flexible 스키마에 대한 모델링이 필요하고, 운영자는 스키마 검증과 모델링 기술을 함께 갖춰야 합니다.
애드혹 쿼리 (ad hoc query)
여러 NoSQL 데이터베이스가 간단하고 확장성이 높은 모델을 채택한 대신 동적 질의를 제공하지 않습니다. 예를 들면 키-값 타입의 저장 시스템은 하나의 키-값으로만 질의가 가능합니다. RDBMS에서는 기본으로 제공하는 기능인데, MongoDB의 설계 목적 중 하나는 RDBMS에서 필수적인 풍부한 쿼리 기능을 NoSQL에서도 사용할 수 있게 하는 것입니다. Post와 Comments 예제로, 추천수가 10보다 크고 ‘politics’ 태그가 붙은 모든 포스트를 찾을 때 SQL과 MongoDB 쿼리의 차이를 아래와 같이 확인할 수 있습니다.
SQL
SELECT * FROM posts
INNER JOIN posts_tags ON posts.id = posts_tags.post_id
INNER JOIN tags ON posts_tags.tag_id = tags.id
WHERE tags.text = 'politics'
AND posts.vote_count > 10;
MongoDB
db.posts.find({ tags: 'politics', vote_count: { $gt: 10 } })
SQL 쿼리는 포스트와 태그를 서로 다른 테이블에 저장하는 엄격하게 정규화된 모델에 의존하는 반면, MongoDB의 쿼리는 태그가 각 도큐먼트에 포함되어 있다고 가정합니다. 하지만 두 쿼리 모두 여러 개의 속성을 임의로 조합해 질의하는 능력을 보여줍니다.
인덱스
MongoDB의 인덱스는 B-Tree 자료구조로 구현되어 있습니다. 인덱스 항목이 필드 값 순서로 정렬되어 저장되므로 동등 조건과 범위 조건, 정렬에 모두 쓸 수 있습니다. 다른 NoSQL 데이터베이스가 키-값 저장소로만 쓰이며 세컨더리 인덱스를 허용하지 않는 것과 달리, MongoDB에서는 세컨더리 인덱스로 더 넓은 범위의 쿼리를 최적화할 수 있습니다.
인덱스 종류로는 단일 필드, 복합(compound), 다중키(multikey), 와일드카드, 텍스트, 공간, 해시 인덱스가 있고, 유니크 같은 성질은 인덱스 속성으로 지정합니다. 한 컬렉션에 만들 수 있는 인덱스는 64개를 넘을 수 없고, 복합 인덱스에는 필드를 32개까지 넣을 수 있습니다. 컬렉션을 만들면 _id 필드에 유니크 인덱스가 기본으로 생성되며 이 인덱스는 삭제할 수 없습니다.
NOTE — 인덱스는 쓰기에 비용을 더합니다. insert마다 인덱스도 갱신되므로, 쓰기 비중이 높은 컬렉션에서는 인덱스 하나를 추가하는 결정도 측정해 보고 내리는 편이 좋습니다.
복제 세트 (replica set)
MongoDB는 자체적으로 복제 기능을 갖추고 있고, 장애에 대비해 데이터를 여러 대의 서버에 분산하며, 자동화된 failover 시스템을 제공합니다. 또한 복제 세트로 읽기 작업을 분산할 수도 있습니다.
속도와 내구성
많은 RDBMS가 메모리 캐시와 디스크를 조합해, 읽기와 쓰기를 우선 메모리에서 처리하고 디스크에는 뒤이어 반영합니다. MongoDB도 같은 구조를 따르며 내구성은 쓰기 시맨틱스(write concern)와 저널링(journaling)으로 조절합니다.
저장 엔진은 WiredTiger가 기본이며 도큐먼트 수준 동시성 제어, 체크포인트, 압축을 제공합니다. Enterprise에서는 데이터를 디스크가 아닌 메모리에 두어 지연을 예측 가능하게 만드는 인메모리 엔진과, WiredTiger의 저장 시 암호화를 함께 쓸 수 있습니다. 공식 문서가 소개하는 저장 엔진은 이 둘뿐입니다.
확장
MongoDB는 샤딩으로 확장을 지원합니다. 6.0.3부터는 자동 청크 분할(automatic chunk splitting)을 수행하지 않습니다.
트랜잭션
도큐먼트 하나를 넘어서는 트랜잭션은 한동안 NoSQL의 약점으로 꼽혔습니다. 지금 MongoDB는 ACID 트랜잭션을 지원하고, 공식 문서는 트랜잭션을 여러 연산, 여러 컬렉션, 여러 데이터베이스, 여러 도큐먼트, 여러 샤드에 걸쳐 사용할 수 있다고 적습니다. 다만 원자성·일관성·격리성·지속성은 설정한 read concern과 write concern에 따라 보장됩니다. 커밋을 w: 1 로 하면 failover가 일어날 때 트랜잭션이 롤백될 수 있고, 샤딩 클러스터에서 스냅샷 수준의 일관된 시점을 보려면 read concern을 "snapshot" 으로 지정해야 합니다.
기본 실행 시간 제한은 1분 미만이고, mongod 의 transactionLifetimeLimitSeconds 파라미터로 바꿉니다. 샤딩 클러스터에서는 모든 샤드 복제 세트 멤버에 같은 값을 적용해야 합니다. 제한을 넘긴 트랜잭션은 만료로 간주되어 주기적인 정리 프로세스가 중단시킵니다.
하나의 트랜잭션에서 많은 도큐먼트를 변경하는 작업은 여전히 권장하지 않습니다. 커밋되지 않은 트랜잭션이 WiredTiger 캐시를 지나치게 압박하면 쓰기 충돌 오류와 함께 중단되고, 캐시에 담을 수 없을 만큼 큰 트랜잭션은 TransactionTooLargeForCache 오류를 반환합니다. 대량 변경은 배치로 나누는 편이 안전합니다.
NOTE — 트랜잭션 안의 읽기는 read preference를
primary로 해야 하고,config·admin·local데이터베이스와 capped 컬렉션에는 쓸 수 없습니다.
트랜잭션은 따로 자세히 다루기로 하고, 오늘은 MongoDB도 트랜잭션을 지원한다는 정도만 확인하고 넘어가려고 합니다.
MongoDB를 사용하는 이유
MongoDB는 위에서 살펴본 것처럼 RDBMS와 NoSQL의 장점을 모아 설계되었습니다. 따라서 웹 애플리케이션, 분석과 로깅 애플리케이션, 캐시를 필요로 하는 애플리케이션에서 1차 저장 시스템으로 적합합니다. 또한 형태가 일정하지 않은 데이터를 담기 좋으므로 미리 구조를 알기 힘든 데이터를 저장하는 데 유용합니다.
다른 데이터베이스와 비교
| 분류 | 예 | 데이터 모델 | 용례 |
|---|---|---|---|
| 간단한 키-값 저장 시스템 | Memcached, Redis | 키-값 | 캐싱, 웹 ops |
| 정교한 키-값 저장 시스템 | 카산드라, Project Voldemort, Riak | 컬럼 또는 이진 blob | 액티비티 피드, 메시지 큐, 캐싱 |
| RDBMS | 오라클, MySQL, MariaDB, PostgreSQL | 테이블 | 트랜잭션이나 SQL이 필요한 시스템 |
정교한 키-값 저장 시스템
아마존의 다이나모(Dynamo)가 대표적입니다. 다이나모의 가장 큰 목표는 네트워크 장애나 데이터 센터 장애가 발생했을 때에도 서비스를 계속하는 데이터 저장 시스템이었습니다. 여러 개의 노드에 걸쳐 자동으로 복제되고 모든 노드에서 읽기와 쓰기를 할 수 있으며, 같은 데이터에 여러 노드가 쓸 때 생기는 충돌을 해결하는 방법을 제공합니다. 다수의 노드를 마스터 없는 동등한 하나의 시스템으로 이해하면 쉽습니다. 다이나모에서 처음 나온 아이디어지만 HBase, Riak, 카산드라 같은 NoSQL도 비슷한 방식으로 구성됩니다. 마스터가 없는 환경에서는 방금 읽은 데이터가 반드시 최신 데이터는 아닌, 다소 느슨한 일관성을 갖습니다.
RDBMS
오라클이 대표적이며 MySQL과 MariaDB, PostgreSQL까지 테이블 단위의 데이터베이스입니다. 일반적으로 우리가 흔히 알고 있는 데이터베이스이기 때문에 설명을 생략합니다.
Document DB
MongoDB와 CouchDB가 있습니다. CouchDB도 세컨더리 인덱스를 지원하지만, 인덱스를 맵리듀스 함수로 정의하는 뷰 형태로 만듭니다. CouchDB 클러스터는 데이터베이스를 자동으로 샤딩하며, 샤드 개수 q 의 기본값은 2, 각 샤드의 복제본 수 n 의 기본값은 3입니다. 즉 기본 설정에서 데이터베이스 하나가 샤드 복제본 6개로 나뉩니다.
참고 자료
도서 : 맛있는 몽고DB
도서: Real MongoDB
도서: 오픈소스 몽고DB
도서: MongoDB in Action
MongoDB Manual: https://www.mongodb.com/docs/manual/