rastalion.dev
MONGODB

MongoDB의 데이터베이스, 컬렉션, 도큐먼트

teinam 2021-02-01updated 09-20 5 MIN

데이터베이스는 컬렉션과 인덱스의 모음이며, 동시에 네임스페이스입니다.

데이터베이스 구조는 데이터베이스 > 컬렉션 > 도큐먼트 형식으로 데이터베이스 안에 컬렉션, 컬렉션 안에 도큐먼트가 있습니다. 실질적인 데이터는 도큐먼트에 기록됩니다. 네임스페이스는 데이터베이스 이름과 컬렉션 이름을 마침표로 이은 <database>.<collection> 형태입니다.

아래 예제는 모두 mongosh 기준입니다. 레거시 mongo 셸은 MongoDB 6.0 에서 제거되었습니다.

데이터베이스

데이터베이스 생성

use testDB
db.users.insertOne( { x: 1 } )

use 는 어떤 데이터베이스를 사용할지 선택하는 명령입니다. 없는 데이터베이스도 선택할 수 있고, 선택만으로는 데이터베이스가 생성되지 않습니다. MongoDB 는 데이터를 처음 저장할 때 데이터베이스를 만듭니다. 위 예제에서는 insertOne()testDB 데이터베이스와 users 컬렉션을 함께 만듭니다.

이름 제약

  • 데이터베이스 이름은 빈 문자열일 수 없고 64바이트 미만이어야 합니다.
  • 데이터베이스 이름에 널 문자를 쓸 수 없습니다. 금지 문자는 플랫폼마다 다릅니다. Unix·Linux 에서는 /\. "$ 를 쓸 수 없고, Windows 에서는 여기에 *<>:|? 가 더해집니다.
  • 대소문자로 데이터베이스를 구분하지 않습니다. salesDataSalesData 를 함께 쓸 수 없습니다.
  • 네임스페이스 길이는 샤딩하지 않은 컬렉션과 뷰가 255바이트, 샤딩한 컬렉션이 235바이트까지입니다. 데이터베이스 이름과 마침표를 포함해 계산합니다.
  • 컬렉션 이름은 밑줄이나 문자로 시작하는 것이 좋습니다. $ 를 넣을 수 없고, 빈 문자열이나 널 문자를 쓸 수 없으며, system. 으로 시작하거나 .system. 을 포함할 수 없습니다.

시스템 데이터베이스

데이터베이스 역할
admin 사용자·역할 정보와 내부 메타데이터
local 복제용 데이터와 인스턴스별 데이터. 복제되지 않습니다
config 샤딩 메타데이터와 세션·트랜잭션 지원 컬렉션

admin 에는 system.users(인증 정보와 부여된 역할), system.roles(사용자 정의 역할), system.version(내부 메타데이터) 이 있습니다. config 는 샤딩하지 않은 배포에도 존재하며, 인과적 일관성을 가진 세션과 재시도 쓰기를 지원하는 컬렉션을 담습니다.

WARNINGconfig 데이터베이스의 스키마는 내부용이라 릴리스마다 바뀔 수 있습니다. 운영 중인 시스템에서 직접 수정하면 데이터가 어긋날 수 있습니다.

데이터베이스 조회

db
show dbs

db 는 현재 선택된 데이터베이스 이름을 돌려줍니다. show dbs 는 사용자가 볼 수 있는 데이터베이스 목록을 보여주는데, 데이터를 넣지 않은 새 배포에서는 admin·config·local 만 나옵니다.

db.stats( { freeStorage: 1, scale: 1024 } )
{
  db: 'test',
  collections: 2,
  views: 0,
  objects: 1689,                  // 데이터베이스 전체 도큐먼트 수
  avgObjSize: 52.56542332741267,  // dataSize 를 도큐먼트 수로 나눈 값. scale 이 적용되지 않습니다
  dataSize: 86.7021484375,        // 압축되지 않은 데이터의 전체 크기
  storageSize: 100,               // 도큐먼트 저장용으로 할당된 디스크 공간. 여유 공간을 포함합니다
  freeStorageSize: 32,
  indexes: 2,                     // 전체 컬렉션의 인덱스 개수
  indexSize: 116,                 // 인덱스에 할당된 디스크 공간. 여유 공간을 포함합니다
  indexFreeStorageSize: 36,
  totalSize: 216,                 // storageSize + indexSize
  totalFreeStorageSize: 68,
  scaleFactor: 1024,              // 명령에 사용된 scale 값
  fsUsedSize: 60155820,           // 데이터 경로가 있는 파일시스템에서 사용 중인 용량
  fsTotalSize: 61255492,          // 같은 파일시스템의 전체 용량
  ok: 1
}

scale 은 크기 값의 단위를 정합니다. 기본값 1 은 바이트, 1024 는 킬로바이트 단위로 보여줍니다. 여유 공간 필드는 freeStorage 를 1 로 주었을 때만 나옵니다. 압축을 켜면 dataSizestorageSize 보다 클 수 있고, 도큐먼트를 지우면 dataSize 는 줄어들지만 storageSize 는 줄어들지 않습니다. 현행 출력에는 예전 문서에 나오던 fileSize 필드가 없습니다.

데이터베이스 삭제

db.dropDatabase()

현재 선택된 데이터베이스와 그 안의 컬렉션을 삭제합니다.

컬렉션

컬렉션은 구조적으로 혹은 개념적으로 비슷한 도큐먼트를 담고 있는 컨테이너입니다. 별도의 명령 없이 도큐먼트를 네임스페이스에 삽입하는 것만으로도 컬렉션이 생성됩니다. 그러나 컬렉션에는 여러 타입이 있으므로 별도의 생성 명령도 있습니다.

컬렉션 생성

db.createCollection("users")

db.users.insertOne( { x: 1 } )
db.users.createIndex( { y: 1 } )

컬렉션이 없으면 insertOne()createIndex() 가 컬렉션을 함께 만듭니다. 캡드 컬렉션, 클러스터드 컬렉션, 시계열 컬렉션처럼 옵션이 필요한 경우나 검증 규칙을 붙이려는 경우에는 db.createCollection() 으로 명시적으로 만듭니다. 이미 만든 컬렉션의 옵션을 바꿀 때는 collMod 명령을 씁니다.

mongosh 에서 db 라는 변수를 통해 명령을 실행하는 것에 익숙해져야 합니다. MongoDB 의 많은 명령이 db 로 시작하고, 이 db 는 현재 선택된 데이터베이스를 가리킵니다. 다른 데이터베이스를 건드려야 하면 db.getSiblingDB("other") 로 접근합니다.

컬렉션 삭제

db.products.drop()

컬렉션 데이터만 삭제

db.products.deleteMany( {} )

큰 컬렉션의 도큐먼트를 전부 지울 때는 컬렉션을 드롭하고 다시 만드는 편이 빠를 수 있습니다. 다만 원래 컬렉션에 있던 인덱스를 다시 만들어야 하고, 샤딩된 컬렉션이었다면 다시 샤딩해야 합니다.

컬렉션 이름 변경

db.users.renameCollection("user_information")

캡드 컬렉션 (Capped Collection)

캡드 컬렉션은 삽입 순서를 기준으로 도큐먼트를 넣고 꺼내는 고정 크기 컬렉션입니다. 순환 버퍼처럼 동작해서 할당된 공간이 가득 차면 가장 오래된 도큐먼트를 지우고 자리를 만듭니다. size 로 바이트 크기를 정하고, max 로 도큐먼트 최대 개수를 함께 지정할 수 있습니다.

db.createCollection("users.action", {capped: true, size: 16384, max: 100})

캡드 컬렉션에는 제약이 있습니다. 샤딩할 수 없고, Stable API V1 에서 지원되지 않으며, 트랜잭션 안에서 쓸 수 없고, $out 집계 단계의 결과를 받을 수도 없습니다. 업데이트는 피하는 편이 좋습니다. 수정한 데이터가 할당된 공간을 넘어서면 예상하지 못한 동작이 생깁니다. 쓰기 연산을 직렬화하기 때문에 동시 삽입·수정·삭제 성능은 일반 컬렉션보다 떨어집니다.

가장 흔한 용도는 로그 저장입니다. tail -f 처럼 새로 삽입된 도큐먼트를 계속 읽어 가는 테일러블 커서(tailable cursor)도 캡드 컬렉션에서 쓸 수 있습니다. 다만 공식 문서는 TTL 인덱스가 캡드 컬렉션보다 성능과 유연성 면에서 낫다고 설명하면서, 캡드 컬렉션을 만들기 전에 TTL 인덱스로 해결할 수 있는지 먼저 확인하라고 권합니다.

TTL 컬렉션

MongoDB 는 특정 시간이 지난 도큐먼트를 만료시키는 기능을 제공합니다. TTL(Time to Live) 컬렉션이라고 부르지만 실제로는 TTL 인덱스로 구현합니다.

db.reviews.createIndex( { time_field: 1 }, { expireAfterSeconds: 3600 } )

time_field 값과 현재 시간의 차이가 expireAfterSeconds 설정 값보다 커지면 해당 도큐먼트가 자동으로 삭제됩니다. 삽입 시점의 시간을 넣어도 되고 다른 시점의 시간을 넣어도 되므로, 도큐먼트의 라이프사이클을 관리하는 데 쓸 수 있습니다. 대상 필드는 날짜 타입이거나 날짜 값을 담은 배열이어야 하며, 배열일 때는 가장 이른 날짜를 기준으로 만료를 판단합니다. 날짜가 아닌 값이 들어 있거나 필드가 아예 없으면 그 도큐먼트는 만료되지 않습니다.

TTL 인덱스에는 다음 제약이 있습니다.

  • 단일 필드 인덱스만 가능합니다. 복합 인덱스는 expireAfterSeconds 를 무시합니다.
  • _id 필드에는 쓸 수 없습니다.
  • 같은 필드에 TTL 이 아닌 단일 필드 인덱스가 이미 있으면 TTL 인덱스를 따로 만들 수 없습니다. collMod 명령으로 기존 인덱스를 바꿉니다.
  • createIndex() 로는 기존 인덱스의 expireAfterSeconds 를 바꿀 수 없습니다. 이때도 collMod 를 씁니다.
  • 삭제는 프라이머리에서만 도는 백그라운드 스레드가 60초 주기로 수행합니다. 세컨더리는 복제된 삭제 연산을 반영합니다. 그래서 만료된 데이터가 60초보다 오래 남아 있을 수 있습니다.

시스템 컬렉션

system. 으로 시작하는 네임스페이스는 MongoDB 내부용으로 예약되어 있습니다. 직접 만들지 않습니다.

컬렉션 용도
admin.system.users 사용자 인증 정보와 부여된 역할
admin.system.roles 관리자가 만든 사용자 정의 역할
<database>.system.views 데이터베이스에 정의된 뷰 정보
<database>.system.profile 데이터베이스 프로파일링 결과

복제를 사용하면 local.oplog.rs 라는 캡드 컬렉션이 oplog 역할을 합니다. 다른 캡드 컬렉션과 달리 oplog 는 majority commit point 를 지우지 않으려고 설정한 크기를 넘어 커질 수 있습니다. MongoDB 5.0 부터 oplog 에 직접 쓰는 작업은 제한됩니다.

컬렉션 목록과 인덱스 정의는 db.getCollectionInfos()db.collection.getIndexes() 로 조회합니다.

도큐먼트

도큐먼트 시리얼라이제이션

모든 도큐먼트는 MongoDB 에 저장하기 전에 BSON 으로 시리얼라이즈(serialize)되고 읽을 때 다시 디시리얼라이즈(deserialize)됩니다. 드라이버가 이 과정을 처리하면서 프로그래밍 언어의 적절한 데이터 타입으로 변환합니다. 에러 없이 시리얼라이즈하려면 필드 이름이 유효해야 하고, 값이 BSON 타입으로 변환될 수 있어야 합니다.

필드 이름에는 다음 규칙이 적용됩니다.

  • 널 문자를 넣을 수 없습니다.
  • MongoDB 5.0 부터 .$ 가 들어간 필드 이름을 저장할 수 있습니다. 다만 find()findAndModify() 의 프로젝션은 $ 로 시작하는 필드를 투영하지 못하고(DBRef 필드는 예외), CSV 로 내보내면 . 이 중첩 구조로 해석됩니다.
  • 한 도큐먼트 안에서 필드 이름은 유일해야 합니다. 이름이 중복된 도큐먼트를 저장하면 CRUD 연산이 예상과 다르게 동작할 수 있습니다.
  • _id 는 기본 키로 예약되어 있습니다. 하위 필드를 가질 경우 그 이름은 $ 로 시작할 수 없습니다.

필드 이름은 도큐먼트마다 저장되므로 이름 길이가 그대로 저장 용량에 반영됩니다. 약어를 쓰면 용량을 절약할 수 있습니다.

NOTE — BSON 타입 가운데 Undefined, DBPointer, Symbol, JavaScript with scope 는 deprecated 로 표시되어 있습니다. 새로 설계하는 스키마에서는 쓰지 않습니다.

_id 와 ObjectId

컬렉션의 모든 도큐먼트는 기본 키로 _id 필드를 가집니다. 값을 지정하지 않으면 드라이버가 ObjectId 를 만들어 채웁니다. _id 값은 컬렉션 안에서 유일해야 하고, 변경할 수 없으며, 배열과 정규식을 제외한 어떤 타입도 쓸 수 있습니다.

ObjectId 는 12바이트이고 다음 세 부분으로 구성됩니다.

  • 4바이트 타임스탬프 — 유닉스 에폭 이후 지나간 초 단위의 생성 시각
  • 5바이트 임의 값 — 클라이언트 프로세스마다 한 번 생성됩니다. 프로세스가 재시작하면 다시 만들어집니다.
  • 3바이트 증가 카운터 — 임의 값에서 시작하고 프로세스가 재시작하면 초기화됩니다.

타임스탬프와 카운터는 큰 바이트가 앞에 오는 빅엔디언으로 저장됩니다. 나머지 BSON 값은 리틀엔디언입니다. 생성 시각은 mongosh 에서 ObjectId.getTimestamp() 로 꺼낼 수 있습니다.

NOTE — ObjectId 값은 시간이 지나면서 커지는 경향이 있지만 단조 증가를 보장하지 않습니다. 시간 해상도가 1초뿐이라 같은 초에 만들어진 값들 사이에는 순서가 정해지지 않고, 값을 만드는 주체가 서로 시계가 다를 수 있는 클라이언트이기 때문입니다. _id 로 정렬하는 것은 생성 순서와 대략 비슷할 뿐 같지 않습니다.

문자열

BSON 문자열은 UTF-8 입니다. 드라이버가 언어의 문자열 형식을 UTF-8 로 변환해 주고받습니다. sort() 는 내부적으로 C++ strcmp API 를 쓰기 때문에 일부 문자의 정렬 순서가 기대와 다를 수 있습니다.

숫자

BSON 은 double, 32비트 int, 64비트 long, 128비트 Decimal128 의 네 가지 수 타입을 규정합니다. Decimal128 은 유효 숫자 34자리와 -6143 부터 +6144 까지의 지수 범위를 가지므로, 금액처럼 10진수 반올림이 중요한 값에 씁니다. 이진 부동소수점은 0.1 * 0.20.020000000000000004 로 계산하기 때문에 정산 금액을 double 로 다루면 오차가 누적됩니다.

mongosh 에서는 값이 32비트 정수로 변환될 수 있으면 Int32, 그렇지 않으면 Double 로 저장합니다. 타입을 고정하려면 생성자를 직접 씁니다.

db.types.insertOne( { _id: 1, value: Int32(1) } )
db.types.insertOne( { _id: 2, value: Long("9007199254740993") } )
db.types.insertOne( { _id: 3, value: Decimal128("9823.1297") } )

파이썬의 decimal.Decimal, 자바의 BigDecimal 처럼 언어마다 Decimal128 에 대응하는 타입이 다르므로 드라이버 문서를 확인해야 합니다.

날짜와 시간

BSON datetime 타입은 시간이나 날짜에 관련된 값을 저장하는 데 사용됩니다. 시간은 signed 64비트 정수를 사용해서 유닉스 에폭(Unix Epoch) 이후 지나간 밀리초(ms)로 표현합니다. 음수는 에폭 이전의 밀리초를 나타냅니다. 유닉스 에폭은 UTC 로 1970년 1월 1일 자정이고, 표현 범위는 에폭 기준으로 앞뒤 약 2억 9천만 년입니다.

BSON Timestamp 는 이름이 비슷하지만 다른 타입입니다. oplog 의 ts 필드처럼 MongoDB 내부에서 쓰는 값이므로 애플리케이션에서는 Date 를 씁니다.

타임존 저장

BSON datetime 은 타임존을 담지 않습니다. UTC 기준 밀리초 값만 저장하므로, 타임존 자체가 의미를 가지는 데이터라면 타임존을 별도 필드로 저장합니다.

{
  time_with_zone: {
    time: new Date(),
    zone: "EST"
  }
}

도큐먼트 크기에 대한 제약

도큐먼트의 최대 크기는 16MiB 입니다. 공식 문서는 이 제한이 하나의 도큐먼트가 지나치게 많은 RAM 을 쓰거나 전송 중에 지나치게 많은 대역폭을 쓰는 상황을 막아 준다고 설명합니다. 이 제한 덕분에 배열을 무한히 키우는 스키마를 설계 단계에서 걸러 낼 수 있기도 합니다. 16MiB 를 넘는 데이터는 도큐먼트를 나눠 저장하거나 GridFS API 를 씁니다.

BSON 도큐먼트의 중첩은 100단계까지 지원합니다. 객체나 배열 하나가 한 단계를 더합니다.

참고 자료

도서 : 맛있는 몽고DB

도서: Real MongoDB

도서: 오픈소스 몽고DB

도서: MongoDB in Action

MongoDB Manual: https://www.mongodb.com/docs/manual/

Advertisement