⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

MongoDB 문서와 BSON 구조 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 11. 27. · 0 Views

MongoDB 문서와 BSON 구조 완벽 가이드

MongoDB의 핵심 데이터 구조인 문서(Document)와 BSON 형식을 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. JSON과 BSON의 차이점부터 ObjectId, 내장 문서, 배열까지 실무에서 바로 활용할 수 있는 내용을 담았습니다.


목차

  1. JSON vs BSON 차이
  2. 문서(Document) 구조
  3. _id 필드와 ObjectId
  4. 내장 문서(Embedded Document)
  5. 배열 데이터 타입
  6. BSON 데이터 타입 종류

1. JSON vs BSON 차이

김개발 씨는 처음으로 MongoDB 프로젝트에 투입되었습니다. 문서를 읽다 보니 "BSON"이라는 단어가 자꾸 등장합니다.

"JSON은 아는데, BSON은 뭐지?" 선배에게 물어보니 의외로 간단한 대답이 돌아왔습니다.

BSON은 Binary JSON의 줄임말로, JSON을 이진 형식으로 인코딩한 것입니다. 마치 사람이 읽는 책을 컴퓨터가 더 빨리 읽을 수 있도록 바코드로 변환한 것과 같습니다.

MongoDB는 내부적으로 BSON을 사용하지만, 개발자는 JSON처럼 편하게 데이터를 다룰 수 있습니다.

다음 코드를 살펴봅시다.

// JSON 형식 - 사람이 읽기 편함
const jsonData = {
  "name": "김개발",
  "age": 28,
  "joinDate": "2024-01-15"
};

// MongoDB에 저장하면 BSON으로 변환됨
// BSON은 Date, ObjectId 등 추가 타입 지원
const bsonData = {
  name: "김개발",
  age: 28,  // BSON: int32 타입으로 저장
  joinDate: new Date("2024-01-15"),  // BSON: Date 타입
  _id: ObjectId("507f1f77bcf86cd799439011")  // BSON 전용 타입
};

김개발 씨는 입사 첫 주에 MongoDB를 처음 접했습니다. 이전 회사에서는 MySQL만 사용했기에 NoSQL이라는 개념 자체가 낯설었습니다.

그런데 문서를 읽다 보니 계속 등장하는 단어가 있었습니다. 바로 BSON이었습니다.

"JSON은 알겠는데, BSON은 대체 뭐지?" 김개발 씨가 중얼거리자, 옆자리 박시니어 씨가 웃으며 설명을 시작했습니다. "BSON은 생각보다 단순해요.

Binary JSON의 줄임말이에요." 그렇다면 BSON이란 정확히 무엇일까요? 쉽게 비유하자면, JSON은 사람이 읽는 일반 책이고 BSON은 그 책의 바코드 버전입니다.

도서관에서 책을 관리할 때 사서가 일일이 제목을 읽는 것보다 바코드를 스캔하는 게 훨씬 빠르죠. 컴퓨터도 마찬가지입니다.

이진 형식인 BSON을 처리하는 것이 텍스트 형식인 JSON을 파싱하는 것보다 훨씬 효율적입니다. JSON만으로는 부족했던 이유가 있습니다.

JSON은 문자열, 숫자, 불리언, 배열, 객체, null 정도의 데이터 타입만 지원합니다. 하지만 실무에서는 날짜, 이진 데이터, 정수와 실수 구분 등 더 다양한 타입이 필요합니다.

JSON으로 날짜를 저장하려면 문자열로 변환해야 하고, 나중에 다시 파싱해야 하는 번거로움이 있었습니다. BSON은 이런 한계를 극복하기 위해 만들어졌습니다.

BSON은 Date, ObjectId, Binary Data, Decimal128 등 다양한 데이터 타입을 네이티브로 지원합니다. 덕분에 날짜를 날짜 그대로, 숫자를 정확한 타입으로 저장할 수 있습니다.

또한 이진 형식이라 파싱 속도가 빠르고, 데이터 순회가 효율적입니다. 위의 코드를 살펴보겠습니다.

첫 번째 객체는 순수한 JSON 형식입니다. joinDate가 문자열로 저장되어 있어 날짜 계산을 하려면 추가 변환이 필요합니다.

반면 두 번째 객체는 MongoDB에서 사용하는 형태입니다. joinDate가 실제 Date 객체이고, _id는 BSON 전용 타입인 ObjectId입니다.

실제로 개발자가 BSON을 직접 다룰 일은 거의 없습니다. MongoDB 드라이버가 자동으로 JSON과 BSON 사이의 변환을 처리해주기 때문입니다.

개발자는 평소처럼 JavaScript 객체로 작업하면 됩니다. 드라이버가 알아서 BSON으로 변환해서 저장하고, 조회할 때는 다시 JavaScript 객체로 돌려줍니다.

하지만 BSON의 존재를 아는 것은 중요합니다. 데이터 타입 관련 에러가 발생했을 때, 왜 그런 에러가 나는지 이해할 수 있기 때문입니다.

예를 들어 JSON으로 날짜를 주고받을 때 ISOString 형식을 사용해야 하는 이유, 32비트 정수 범위를 넘는 숫자가 문제가 되는 이유 등을 파악할 수 있습니다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.

"아, BSON이 있어서 MongoDB가 다양한 데이터 타입을 효율적으로 처리할 수 있는 거군요!"

실전 팁

💡 - JSON으로 API 응답을 보낼 때 Date 객체는 자동으로 ISO 문자열로 변환됩니다

  • BSON 크기 제한은 문서당 16MB이므로 대용량 파일은 GridFS를 사용하세요

2. 문서(Document) 구조

"MongoDB는 테이블 대신 컬렉션을, 로우 대신 문서를 사용해요." 박시니어 씨의 설명에 김개발 씨는 고개를 갸웃했습니다. 관계형 데이터베이스만 사용하던 그에게 문서라는 개념은 아직 낯설었습니다.

MongoDB의 **문서(Document)**는 필드와 값의 쌍으로 이루어진 데이터 구조입니다. 관계형 데이터베이스의 행(row)과 비슷하지만, 훨씬 유연합니다.

마치 JSON 객체처럼 생겼으며, 같은 컬렉션 안에서도 문서마다 다른 필드를 가질 수 있습니다.

다음 코드를 살펴봅시다.

// MongoDB 문서의 기본 구조
const userDocument = {
  _id: ObjectId("507f1f77bcf86cd799439011"),
  username: "devkim",
  email: "kim@example.com",
  profile: {
    nickname: "김개발",
    bio: "주니어 개발자입니다"
  },
  skills: ["JavaScript", "Python", "MongoDB"],
  createdAt: new Date(),
  isActive: true
};

// 같은 컬렉션에 다른 구조의 문서도 가능
const anotherUser = {
  _id: ObjectId("507f1f77bcf86cd799439012"),
  username: "parkdev",
  phone: "010-1234-5678"  // 다른 필드 구조
};

김개발 씨는 MySQL에서 테이블을 설계하던 방식이 몸에 배어 있었습니다. 사용자 테이블을 만들려면 먼저 컬럼을 정의하고, 모든 행이 같은 구조를 따라야 했습니다.

그런데 MongoDB는 달랐습니다. 박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다.

"SQL에서 테이블이 엑셀 시트라면, MongoDB의 컬렉션은 서류 폴더 같은 거예요." **문서(Document)**란 무엇일까요? 엑셀 시트는 모든 행이 같은 열 구조를 따릅니다.

하지만 서류 폴더에 넣는 문서들은 각자 다른 양식일 수 있습니다. 어떤 문서는 이름과 전화번호만 있고, 다른 문서는 주소와 이메일까지 포함할 수 있습니다.

MongoDB의 컬렉션도 마찬가지입니다. 이런 유연성이 왜 필요할까요?

실제 비즈니스에서는 데이터 구조가 자주 바뀝니다. 새로운 기능이 추가되면 새로운 필드가 필요해지고, 기존 필드가 불필요해지기도 합니다.

관계형 데이터베이스에서는 ALTER TABLE 명령으로 스키마를 변경해야 하지만, MongoDB에서는 그냥 새 필드를 추가하면 됩니다. 위의 코드를 살펴보겠습니다.

userDocument는 여러 필드를 가진 전형적인 MongoDB 문서입니다. _id는 모든 문서에 필수인 고유 식별자입니다.

username, email은 문자열 필드이고, profile은 또 다른 객체를 담고 있습니다. skills는 배열이며, createdAt은 날짜, isActive는 불리언입니다.

같은 컬렉션에 있는 anotherUser를 보세요. 이 문서는 email이나 profile 필드가 없고, 대신 phone 필드가 있습니다.

관계형 데이터베이스였다면 에러가 났겠지만, MongoDB에서는 완전히 유효합니다. 이것이 바로 스키마리스(Schemaless) 또는 유연한 스키마라고 불리는 특성입니다.

하지만 무조건 자유로운 것은 아닙니다. 실무에서는 보통 Mongoose 같은 ODM을 사용해서 스키마를 정의합니다.

유연성은 유지하되, 필수 필드 검증이나 타입 체크 같은 안전장치를 추가하는 것입니다. 완전한 자유보다는 적절한 제약이 버그를 줄여줍니다.

문서의 최대 크기는 16MB입니다. 이 제한은 하나의 문서가 너무 커지는 것을 방지합니다.

만약 더 큰 데이터를 저장해야 한다면 GridFS를 사용하거나 데이터를 여러 문서로 분리해야 합니다. 대부분의 경우 16MB는 충분히 큰 용량입니다.

김개발 씨가 질문했습니다. "그럼 아무렇게나 데이터를 넣어도 되나요?" 박시니어 씨가 웃으며 답했습니다.

"기술적으로는 그래요. 하지만 좋은 설계는 여전히 중요해요.

유연하다고 해서 무질서해도 된다는 뜻은 아니니까요."

실전 팁

💡 - 문서 설계 시 자주 함께 조회되는 데이터는 같은 문서에 넣으세요

  • 스키마 검증이 필요하면 Mongoose나 MongoDB의 JSON Schema Validation을 활용하세요

3. id 필드와 ObjectId

김개발 씨가 첫 번째 데이터를 MongoDB에 저장했습니다. 그런데 분명히 넣지 않은 _id 필드가 자동으로 생겨 있었습니다.

"이건 뭐죠?" 이상한 문자열 조합을 보며 김개발 씨가 물었습니다.

_id는 MongoDB 문서의 기본 키(Primary Key)로, 컬렉션 내에서 각 문서를 고유하게 식별합니다. 직접 지정하지 않으면 MongoDB가 자동으로 ObjectId를 생성합니다.

ObjectId는 12바이트 값으로, 생성 시간 정보까지 담고 있어 별도의 타임스탬프 없이도 생성 순서를 알 수 있습니다.

다음 코드를 살펴봅시다.

// ObjectId 구조 분석
const id = ObjectId("507f1f77bcf86cd799439011");

// ObjectId는 12바이트로 구성됨
// 4바이트: 타임스탬프 (초 단위)
// 5바이트: 랜덤 값 (머신+프로세스 식별)
// 3바이트: 카운터 (같은 초 내 순차 증가)

// 생성 시간 추출
const timestamp = id.getTimestamp();
console.log(timestamp);  // 2012-10-17T20:46:22.000Z

// 커스텀 _id 사용도 가능
const customIdDoc = {
  _id: "user_12345",  // 문자열 _id
  name: "김개발"
};

김개발 씨가 처음 MongoDB에 데이터를 넣었을 때였습니다. insertOne 메서드로 간단한 객체를 저장했는데, 조회해보니 자신이 넣지 않은 필드가 하나 더 있었습니다.

바로 _id 필드였습니다. "507f1f77bcf86cd799439011...

이게 뭐예요?" 김개발 씨가 화면을 가리키며 물었습니다. 박시니어 씨가 설명을 시작했습니다.

_id는 MongoDB 문서의 주민등록번호와 같습니다. 대한민국 국민마다 고유한 주민등록번호가 있듯이, MongoDB의 모든 문서는 고유한 _id를 가집니다.

이 값은 컬렉션 내에서 절대 중복되지 않습니다. _id가 없으면 특정 문서를 정확히 찾아내거나 수정하기가 어려워집니다.

그렇다면 ObjectId는 무엇일까요? ObjectId는 MongoDB가 _id를 자동 생성할 때 사용하는 특별한 데이터 타입입니다.

24자리 16진수 문자열처럼 보이지만, 실제로는 12바이트의 이진 데이터입니다. 이 12바이트 안에는 꽤 많은 정보가 담겨 있습니다.

ObjectId의 구조를 살펴보면 흥미롭습니다. 처음 4바이트는 Unix 타임스탬프입니다.

문서가 언제 생성되었는지 알 수 있습니다. 다음 5바이트는 머신과 프로세스를 식별하는 랜덤 값입니다.

마지막 3바이트는 카운터로, 같은 초 안에 생성된 문서들을 구분합니다. 이 구조 덕분에 유용한 기능이 있습니다.

getTimestamp() 메서드를 호출하면 문서 생성 시간을 알 수 있습니다. 별도의 createdAt 필드를 만들지 않아도 되는 것입니다.

물론 정밀한 시간이 필요하거나 명시적인 필드가 필요하다면 createdAt을 따로 저장해도 됩니다. 꼭 ObjectId를 사용해야 하는 것은 아닙니다.

위 코드의 customIdDoc처럼 문자열이나 숫자를 _id로 사용할 수도 있습니다. 기존 시스템에서 마이그레이션하는 경우나, 의미 있는 ID가 이미 있는 경우에 유용합니다.

다만 고유성은 직접 보장해야 합니다. 주의할 점이 있습니다.

_id는 한 번 설정되면 변경할 수 없습니다. 수정하려면 문서를 삭제하고 새로 생성해야 합니다.

또한 _id에는 자동으로 인덱스가 생성되어 있어 조회가 매우 빠릅니다. 김개발 씨가 이해했다는 듯 말했습니다.

"아, MySQL의 AUTO_INCREMENT 같은 거군요. 근데 더 똑똑하네요.

시간 정보까지 들어 있다니." 박시니어 씨가 덧붙였습니다. "게다가 분산 환경에서도 충돌 없이 생성할 수 있어요.

여러 서버에서 동시에 문서를 만들어도 ID가 겹치지 않거든요."

실전 팁

💡 - ObjectId.getTimestamp()로 별도 필드 없이 생성 시간을 확인할 수 있습니다

  • 커스텀 _id 사용 시 고유성과 인덱싱 효율을 직접 고려해야 합니다

4. 내장 문서(Embedded Document)

"이 사용자의 주소 정보를 저장하려면 어떻게 해야 하죠?" 김개발 씨가 물었습니다. SQL이었다면 주소 테이블을 따로 만들고 외래 키로 연결했을 것입니다.

하지만 박시니어 씨는 다른 방법을 알려주었습니다.

**내장 문서(Embedded Document)**는 문서 안에 또 다른 문서를 포함시키는 것입니다. 마치 러시아 인형 마트료시카처럼, 문서 안에 문서가 중첩될 수 있습니다.

관련 데이터를 한 곳에 모아두기 때문에 조회 성능이 좋고, 데이터의 응집도가 높아집니다.

다음 코드를 살펴봅시다.

// 내장 문서를 활용한 사용자 정보
const userWithAddress = {
  _id: ObjectId("507f1f77bcf86cd799439011"),
  name: "김개발",
  email: "kim@example.com",
  // 주소 정보를 내장 문서로 저장
  address: {
    zipCode: "06234",
    city: "서울시",
    district: "강남구",
    street: "테헤란로 123",
    detail: "개발빌딩 5층"
  },
  // 여러 개의 내장 문서도 가능
  contacts: {
    phone: "010-1234-5678",
    emergency: "02-555-1234"
  }
};

// 내장 문서 필드 조회
db.users.findOne(
  { "address.city": "서울시" }  // 점 표기법 사용
);

김개발 씨는 회원 정보를 저장해야 했습니다. 이름, 이메일은 간단했지만, 주소 정보가 문제였습니다.

우편번호, 시, 구, 도로명, 상세주소... SQL에서는 주소 테이블을 따로 만들고 user_id로 연결했을 것입니다.

"MongoDB에서도 그렇게 해야 하나요?" 김개발 씨가 물었습니다. 박시니어 씨가 고개를 저었습니다.

"더 좋은 방법이 있어요. 내장 문서를 쓰면 돼요." 내장 문서는 러시아 인형 마트료시카와 같습니다.

큰 인형을 열면 그 안에 작은 인형이 있고, 그 안에 더 작은 인형이 있습니다. MongoDB 문서도 마찬가지입니다.

사용자 문서 안에 주소 문서를 넣고, 필요하다면 주소 문서 안에 또 다른 문서를 넣을 수 있습니다. 왜 테이블을 분리하지 않고 내장할까요?

관계형 데이터베이스에서 JOIN 연산은 비용이 큽니다. 사용자 정보와 주소 정보를 함께 조회하려면 두 테이블을 조인해야 합니다.

하지만 내장 문서를 사용하면 한 번의 조회로 모든 정보를 가져올 수 있습니다. 디스크 I/O가 줄어들고 성능이 좋아집니다.

위의 코드를 살펴보겠습니다. address 필드는 단순한 문자열이 아니라 여러 필드를 가진 객체입니다.

zipCode, city, district, street, detail 각각이 하위 필드입니다. contacts도 마찬가지로 phone과 emergency라는 하위 필드를 가집니다.

내장 문서의 필드에 접근하려면 **점 표기법(dot notation)**을 사용합니다. "address.city"처럼 점으로 경로를 표시합니다.

이 방식으로 쿼리도 할 수 있고, 인덱스도 생성할 수 있습니다. 마치 파일 시스템의 경로처럼 직관적입니다.

하지만 내장 문서가 항상 좋은 것은 아닙니다. 내장된 데이터가 자주 독립적으로 조회되거나, 너무 많이 중첩되면 오히려 비효율적입니다.

또한 내장 문서는 부모 문서와 함께 저장되므로, 내장 데이터만 따로 수정하려면 부모 문서 전체를 업데이트해야 합니다. 언제 내장하고 언제 분리할까요?

일대일 관계이거나, 부모 문서와 함께 조회되는 경우에는 내장이 좋습니다. 사용자의 프로필 정보, 주문의 배송 주소 같은 경우입니다.

반면 일대다 관계에서 "다" 쪽이 매우 많거나 독립적으로 관리되어야 한다면 별도 컬렉션을 고려하세요. 김개발 씨가 정리했습니다.

"함께 쓰이는 데이터는 함께 저장한다. 이게 MongoDB의 철학이군요." 박시니어 씨가 고개를 끄덕였습니다.

"맞아요. 데이터가 어떻게 사용되는지에 따라 구조를 설계하는 거예요."

실전 팁

💡 - 내장 문서 깊이는 보통 2-3단계가 적당합니다. 너무 깊으면 쿼리가 복잡해집니다

  • 내장 문서 필드에도 인덱스를 생성할 수 있습니다 (예: db.users.createIndex({"address.city": 1}))

5. 배열 데이터 타입

김개발 씨가 사용자의 관심 태그를 저장하려고 했습니다. "JavaScript, Python, MongoDB..." 여러 개의 값을 하나의 필드에 넣고 싶은데, SQL에서는 별도 테이블이 필요했습니다.

MongoDB에서는 어떻게 할까요?

MongoDB는 **배열(Array)**을 네이티브 데이터 타입으로 지원합니다. 하나의 필드에 여러 값을 순서대로 저장할 수 있습니다.

문자열 배열, 숫자 배열은 물론이고, 객체 배열도 가능합니다. 배열 요소를 대상으로 하는 강력한 쿼리와 업데이트 연산자도 제공됩니다.

다음 코드를 살펴봅시다.

// 다양한 배열 사용 예시
const developerProfile = {
  _id: ObjectId("507f1f77bcf86cd799439011"),
  name: "김개발",
  // 문자열 배열
  skills: ["JavaScript", "Python", "MongoDB"],
  // 숫자 배열
  scores: [85, 92, 78, 95],
  // 객체 배열 (내장 문서의 배열)
  projects: [
    { name: "쇼핑몰", year: 2023, role: "백엔드" },
    { name: "블로그", year: 2024, role: "풀스택" }
  ]
};

// 배열 쿼리 예시
db.developers.find({ skills: "MongoDB" });  // MongoDB 스킬 보유자
db.developers.find({ "skills.0": "JavaScript" });  // 첫번째 스킬이 JS인 사람
db.developers.find({ skills: { $size: 3 } });  // 스킬이 3개인 사람

김개발 씨에게 새로운 요구사항이 생겼습니다. 개발자 프로필에 보유 기술을 저장해야 했습니다.

한 사람이 여러 기술을 가질 수 있으니, 일대다 관계입니다. SQL에서였다면 user_skills라는 별도 테이블을 만들어 user_id와 skill_name을 저장했을 것입니다.

하지만 MongoDB에서는 훨씬 간단한 방법이 있었습니다. 배열은 하나의 서랍에 여러 물건을 담는 것과 같습니다.

서랍장을 생각해보세요. 양말 서랍에는 여러 켤레의 양말이 들어 있습니다.

굳이 양말 하나마다 서랍을 만들 필요가 없습니다. MongoDB의 배열도 마찬가지입니다.

skills 필드 하나에 "JavaScript", "Python", "MongoDB"를 모두 담을 수 있습니다. 배열의 장점은 직관성입니다.

데이터가 어떻게 생겼는지 한눈에 보입니다. 조인 없이 한 번의 조회로 모든 스킬을 가져올 수 있습니다.

또한 배열의 순서가 유지되므로, 첫 번째 스킬이 주력 스킬이라는 의미를 부여할 수도 있습니다. 위의 코드를 살펴보겠습니다.

skills는 문자열 배열, scores는 숫자 배열입니다. projects는 조금 특별합니다.

객체들의 배열인데, 이를 내장 문서 배열이라고 합니다. 각 프로젝트마다 이름, 연도, 역할 정보를 가지고 있습니다.

배열 쿼리는 매우 직관적입니다. { skills: "MongoDB" }라고 쓰면 skills 배열에 "MongoDB"가 포함된 모든 문서를 찾습니다.

배열 안에 해당 값이 있는지 자동으로 확인해줍니다. 특정 인덱스를 지정하려면 "skills.0"처럼 점 표기법을 사용합니다.

배열 업데이트를 위한 특별한 연산자들이 있습니다. $push는 배열에 요소를 추가하고, $pull은 제거합니다.

$addToSet은 중복 없이 추가하고, $pop은 첫 번째나 마지막 요소를 제거합니다. 배열 조작에 필요한 거의 모든 기능이 내장되어 있습니다.

하지만 배열이 너무 커지면 문제가 됩니다. 문서 하나의 크기 제한이 16MB이므로, 배열 요소가 수천, 수만 개가 되면 성능 문제가 생깁니다.

또한 배열 요소가 자주 추가되면 문서 크기가 계속 커져 재배치가 발생할 수 있습니다. 이런 경우에는 별도 컬렉션으로 분리하는 것이 좋습니다.

김개발 씨가 물었습니다. "배열 안의 특정 요소만 수정하는 것도 가능한가요?" 박시니어 씨가 답했습니다.

"물론이죠. **위치 연산자 $**를 사용하면 돼요.

{ 'projects.$': { ... } }처럼요."

실전 팁

💡 - 배열 요소가 1000개를 넘어간다면 별도 컬렉션으로 분리를 고려하세요

  • $elemMatch 연산자로 배열 안의 객체를 조건으로 검색할 수 있습니다

6. BSON 데이터 타입 종류

"이 필드에 어떤 타입을 써야 하죠?" 김개발 씨가 스키마를 설계하다 멈췄습니다. MongoDB가 지원하는 데이터 타입이 생각보다 많았기 때문입니다.

박시니어 씨가 주요 타입들을 하나씩 설명해주기 시작했습니다.

BSON은 JSON보다 훨씬 다양한 데이터 타입을 지원합니다. String, Number 같은 기본 타입부터 Date, ObjectId, Binary Data, Decimal128 같은 특수 타입까지 있습니다.

적절한 타입을 선택하면 저장 공간을 절약하고, 정확한 연산이 가능해집니다.

다음 코드를 살펴봅시다.

// BSON 주요 데이터 타입 예시
const dataTypesExample = {
  // 기본 타입
  stringField: "안녕하세요",           // String
  int32Field: NumberInt(42),           // 32비트 정수
  int64Field: NumberLong(9007199254740993), // 64비트 정수
  doubleField: 3.14159,                // 64비트 부동소수점
  booleanField: true,                  // Boolean
  nullField: null,                     // Null

  // 특수 타입
  objectIdField: ObjectId(),           // 12바이트 고유 식별자
  dateField: new Date(),               // UTC 날짜/시간
  decimal128Field: NumberDecimal("19.99"),  // 128비트 고정밀 소수
  binaryField: BinData(0, "base64data"),    // 이진 데이터

  // 구조 타입
  arrayField: [1, 2, 3],               // Array
  objectField: { nested: "value" },    // Embedded Document

  // 특수 목적 타입
  timestampField: Timestamp(),         // 내부용 타임스탬프
  regexField: /pattern/i               // 정규표현식
};

김개발 씨는 상품 정보를 저장해야 했습니다. 상품명은 당연히 문자열, 가격은 숫자...

그런데 가격을 어떤 숫자 타입으로 저장해야 할까요? 자바스크립트의 Number로 충분할까요?

박시니어 씨가 중요한 점을 짚었습니다. "가격 계산에서 부동소수점 오류가 생기면 큰일나요.

0.1 + 0.2가 0.30000000000000004가 되는 거 아시죠?" 숫자 타입부터 살펴보겠습니다. BSON은 여러 종류의 숫자 타입을 제공합니다.

Double은 64비트 부동소수점으로, JavaScript의 기본 Number 타입입니다. Int32는 32비트 정수로 약 21억까지, Int64는 64비트 정수로 훨씬 큰 수를 저장합니다.

금융 데이터처럼 정밀한 소수가 필요하면 Decimal128을 사용합니다. Date 타입은 시간을 다룹니다.

JavaScript의 Date 객체와 호환됩니다. 내부적으로는 1970년 1월 1일부터의 밀리초를 저장합니다.

문자열로 날짜를 저장하면 정렬이나 범위 쿼리가 제대로 동작하지 않으니, 반드시 Date 타입을 사용하세요. ObjectId는 앞서 설명한 12바이트 고유 식별자입니다.

_id 필드의 기본 타입이지만, 다른 필드에서도 참조용으로 사용할 수 있습니다. 예를 들어 댓글 문서에서 작성자를 userId: ObjectId("...")로 저장하는 식입니다.

Binary Data는 이진 데이터를 저장합니다. 이미지, 파일, 암호화된 데이터 등을 저장할 때 사용합니다.

단, 큰 파일은 GridFS를 사용하는 것이 좋습니다. Binary Data에는 서브타입이 있어서 데이터의 종류를 명시할 수 있습니다.

흔히 쓰이지 않지만 알아두면 좋은 타입들도 있습니다. Timestamp는 MongoDB 내부에서 복제와 샤딩에 사용하는 특수 타입입니다.

일반적인 시간 저장에는 Date를 쓰세요. Regular Expression은 정규표현식을 저장하여 쿼리에서 패턴 매칭에 활용합니다.

JavaScript Code는 서버 사이드 JavaScript를 저장하지만, 보안상 거의 사용하지 않습니다. 타입 선택은 왜 중요할까요?

잘못된 타입을 쓰면 예상치 못한 버그가 생깁니다. 문자열 "100"과 숫자 100은 정렬 결과가 다릅니다.

문자열 날짜 "2024-01-15"와 Date 객체는 범위 쿼리 성능이 다릅니다. Decimal128 대신 Double로 금액을 저장하면 1원 차이로 정산이 틀어질 수 있습니다.

김개발 씨가 메모했습니다. "가격은 Decimal128, 날짜는 Date, 수량은 Int32..." 박시니어 씨가 덧붙였습니다.

"Mongoose를 쓰면 스키마에서 타입을 명시할 수 있어요. 실수로 잘못된 타입이 들어가는 걸 방지해주죠."

실전 팁

💡 - 금액 계산에는 반드시 Decimal128을 사용하세요 (NumberDecimal("19.99"))

  • $type 연산자로 필드의 BSON 타입을 쿼리 조건에 활용할 수 있습니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#MongoDB#BSON#Document#ObjectId#NoSQL#MongoDB,Database,NoSQL

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.