MongoDB BSON 가이드(Go언어용)

beluga000·2025년 11월 7일

BSON 개요

BSON(Binary JSON)은 JSON(JavaScript Object Notation)을 이진(binary)형식으로 확장한 데이터 표현 방식

즉,

"JSON의 구조를 유지하면서도, 속도와 효율성을 위해 바이너리 형태로 표현한 포맷"

BSON 특징

  1. Binary 형태
  • 사람이 읽기 어렵지만, 기계가 읽고 쓰기 매우 효율적입니다.
  • 네트워크 전송 시 용량이 줄고 속도가 빨라집니다.
  1. Rich Data Types(확장된 자료형 지원)
타입설명예시
ObjectIdMongoDB의 고유 ID (12바이트)ObjectId("507f1f77bcf86cd799439011")
DateUTC 기준 날짜"ISODate("2025-11-07T00:00:00Z")"
Binary순수 이진 데이터파일, 이미지 등
Int32 / Int64정수 타입 구분32비트 / 64비트
Decimal128고정소수점금융 데이터에 사용
Regex정규표현식/pattern/i
Timestamp내부 타임스탬프oplog 등에서 사용
  1. 길이 정보 포함
  • 각 문서(document)와 필드에 길이 정보(length prefix)가 포함되어 있어, 빠르게 탐색이 가능하비낟.
  1. 트리 구조 유지
  • JON처럼 중첩 객체{}와 배열[]을 지원합니다.
  • 내부적으로 offset 기반으로 처리되어 랜덤 접근(random access)도 빠릅니다.

JSON VS BSON

비교 항목JSONBSON
형식텍스트 기반바이너리 기반
데이터 타입문자열, 숫자, 불리언, 배열, 객체 등JSON + 확장 타입 (날짜, ObjectId, 64비트 정수, 이진 데이터 등)
크기크다 (문자열로 표현되므로)작다 (바이너리 표현)
속도느림 (파싱 필요)빠름 (직렬화/역직렬화가 효율적)
가독성높음 (사람이 읽을 수 있음)낮음 (기계용 포맷)
MongoDB 저장 방식변환 후 저장기본 저장 포맷

내부 구조 예시

JSON 문서 예시

{
  "name": "Alice",
  "age": 25,
  "skills": ["Go", "MongoDB", "Python"]
}

위 JSON 문서를 BSON으로 표현하면(바이너리 형태)

\x31\x00\x00\x00\x02name\x00\x06\x00\x00\x00Alice\x00\x10age\x00\x19\x00\x00\x00\x04skills\x00...

Go에서 BSON 사용하기

필수 패키지 설치

go get go.mongodb.org/mongo-driver/mongo
go get go.mongodb.org/mongo-driver/bson

기본 import

import (
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
)

BSON 데이터 타입

기본 타입 매핑

Go 타입BSON 타입설명
stringStringUTF-8 문자열
int32Int3232비트 정수
int64Int6464비트 정수
float64Double배정도 부동소수점
boolBoolean불린값
[]byteBinary바이너리 데이터
time.TimeDateTime날짜/시간
primitive.ObjectIDObjectIdMongoDB ObjectID
primitive.Decimal128Decimal128고정소수점

특수 타입

// ObjectID 사용 예제
type User struct {
    ID       primitive.ObjectID `bson:"_id,omitempty"`
    Username string             `bson:"username"`
    Email    string             `bson:"email"`
}

// Decimal128 사용 예제 (금융 데이터)
type Product struct {
    ID    primitive.ObjectID   `bson:"_id,omitempty"`
    Name  string               `bson:"name"`
    Price primitive.Decimal128 `bson:"price"`
}

구조체 태그와 매핑

BSON 태그 옵션

type Employee struct {
    ID          primitive.ObjectID `bson:"_id,omitempty"`           // MongoDB _id 필드
    EmployeeNo  string             `bson:"employee_no"`             // 필드명 변경
    Name        string             `bson:"name"`                    // 기본 매핑
    Email       string             `bson:"email,omitempty"`         // 빈값 시 생략
    Password    string             `bson:"-"`                       // 저장하지 않음
    Salary      float64            `bson:"salary,truncate"`         // 소수점 자르기
    IsActive    bool               `bson:"is_active"`               // 스네이크 케이스
    CreatedAt   time.Time          `bson:"created_at"`              // 날짜 필드
    UpdatedAt   time.Time          `bson:"updated_at,omitempty"`    // 업데이트 시에만 저장
}

태그 옵션 설명

옵션설명예제
omitempty빈 값일 때 필드 생략bson:"field,omitempty"
minsize최소 크기로 저장bson:"field,minsize"
truncate소수점 자르기bson:"field,truncate"
-필드 무시bson:"-"

BSON 변환 예제

구조체 → BSON

// 구조체 정의
user := User{
    ID:       primitive.NewObjectID(),
    Username: "john_doe",
    Email:    "john@example.com",
}

// BSON으로 변환
bsonData, err := bson.Marshal(user)
if err != nil {
    log.Fatal(err)
}

// 또는 bson.D 사용
doc := bson.D{
    {Key: "_id", Value: primitive.NewObjectID()},
    {Key: "username", Value: "john_doe"},
    {Key: "email", Value: "john@example.com"},
}

BSON → 구조체

// BSON 데이터를 구조체로 변환
var user User
err := bson.Unmarshal(bsonData, &user)
if err != nil {
    log.Fatal(err)
}

// MongoDB 문서에서 직접 변환
err = collection.FindOne(context.TODO(), bson.M{"_id": objectID}).Decode(&user)
if err != nil {
    log.Fatal(err)
}

bson.Marshal() / bson.Unmarshal()

BSON 데이터의 직렬화(serialize) / 역직렬화(deserialize) 과정

  1. bson.Marshal()
    Go 데이터 -> BSON(Binary)

  2. bson.Unmarshal()
    BSON(Binary) -> Go 데이터

예시 1

package main

import (
    "fmt"
    "go.mongodb.org/mongo-driver/bson"
)

type User struct {
    Name string
    Age  int
}

func main() {
    // ✅ Go 구조체 → BSON 변환
    user := User{Name: "Alice", Age: 25}
    data, err := bson.Marshal(user)
    if err != nil {
        panic(err)
    }
    fmt.Printf("BSON bytes: %v\n", data)

    // ✅ BSON → Go 구조체 변환
    var decoded User
    err = bson.Unmarshal(data, &decoded)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Decoded struct: %+v\n", decoded)
}

출력 예시

BSON bytes: [29 0 0 0 2 78 97 109 101 0 6 0 0 0 65 108 105 99 101 0 16 65 103 101 0 25 0 0 0 0]
Decoded struct: {Name:Alice Age:25}

BSON 타입별 상세 설명 및 사용법

bson.M (Map)

특징

  • Go의 map[string]interfsce{}와 동일합니다.
  • 필드 순서가 보장되지 않습니다.
  • 가장 직관적이고 사용하기 쉬운 편입니다.
  • 일반적인 쿼리와 필터에 적합합니다.
// 기본 필터링
filter := bson.M{
    "status": "active",
    "age": bson.M{"$gte": 18},
}

// 복잡한 쿼리
complexFilter := bson.M{
    "$and": []bson.M{
        {"status": "active"},
        {"$or": []bson.M{
            {"role": "admin"},
            {"role": "manager"},
        }},
    },
}

// 업데이트 문서
update := bson.M{
    "$set": bson.M{
        "last_login": time.Now(),
        "status": "online",
    },
    "$inc": bson.M{"login_count": 1},
}

bson.D (Document)

특징

  • 순서가 보장되는 문서 타입입니다.
  • []bson.E (Element의 슬라이스)로 구현합니다.
  • 인덱스 생성, 집계 파이프라인에서 순서가 중요할 때 사용합니다.
  • MongoDB 명령어에서 필드 순서가 중요한 경우에는 필수입니다.
// 인덱스 생성 (순서 중요)
indexKeys := bson.D{
    {Key: "user_code", Value: 1},     // 오름차순
    {Key: "created_at", Value: -1},   // 내림차순
}

// 집계 파이프라인 (단계 순서 중요)
pipeline := bson.D{
    {Key: "$match", Value: bson.M{"status": "active"}},
    {Key: "$lookup", Value: bson.M{
        "from": "departments",
        "localField": "dept_id",
        "foreignField": "_id",
        "as": "department",
    }},
    {Key: "$unwind", Value: "$department"},
}

// 정렬 (여러 필드 순서 중요)
sort := bson.D{
    {Key: "priority", Value: -1},
    {Key: "created_at", Value: 1},
}

bson.A (Array)

특징

  • Go의 []interface{}와 동일합니다.
  • 배열 형태의 데이터로 표현합니다.
  • $in, $or 등의 연산자와 함께 사용합니다.
// $in 연산자와 함께
filter := bson.M{
    "status": bson.M{
        "$in": bson.A{"active", "pending", "approved"},
    },
}

// $or 연산자 배열
orConditions := bson.A{
    bson.M{"role": "admin"},
    bson.M{"role": "manager"},
    bson.M{"permissions": bson.M{"$in": bson.A{"write", "admin"}}},
}

// 집계 파이프라인 배열
pipeline := bson.A{
    bson.M{"$match": bson.M{"status": "active"}},
    bson.M{"$group": bson.M{
        "_id": "$department",
        "count": bson.M{"$sum": 1},
    }},
}

bson.E (Element)

특징

  • Key-Value 쌍을 나타내는 구조체입니다.
  • bson.D의 구성 요소입니다.
  • 명시적인 필드 순서 제어가 가능합니다.
// bson.E 직접 사용
element := bson.E{Key: "user_id", Value: "user123"}

// bson.D 구성
document := bson.D{
    bson.E{Key: "name", Value: "홍길동"},
    bson.E{Key: "age", Value: 30},
    bson.E{Key: "email", Value: "hong@example.com"},
}

타입 선택 가이드

사용 상황권장 타입이유
일반적인 필터/쿼리bson.M직관적이고 사용하기 쉬움
인덱스 생성bson.D필드 순서가 성능에 영향
집계 파이프라인bson.D단계 순서가 중요
정렬 조건bson.D정렬 우선순위 순서 보장
배열 데이터bson.A배열 형태 데이터 표현
MongoDB 명령어bson.D명령어 파라미터 순서 중요

실제 사용 예제 비교

// ERP 사용자 검색 - 다양한 방법
func SearchUsers() {
    // 1. bson.M 사용 (일반적인 경우)
    simpleFilter := bson.M{
        "is_active": true,
        "role": bson.M{"$in": bson.A{"admin", "manager"}},
    }
    
    // 2. bson.D 사용 (순서가 중요한 경우)
    orderedFilter := bson.D{
        {Key: "is_active", Value: true},
        {Key: "role", Value: bson.M{"$in": bson.A{"admin", "manager"}}},
    }
    
    // 3. 복합 인덱스 생성 (bson.D 필수)
    indexModel := mongo.IndexModel{
        Keys: bson.D{
            {Key: "company_id", Value: 1},    // 첫 번째 정렬 기준
            {Key: "department_id", Value: 1}, // 두 번째 정렬 기준
            {Key: "user_code", Value: 1},     // 세 번째 정렬 기준
        },
    }
    
    // 4. 집계 파이프라인 (bson.D로 순서 보장)
    pipeline := bson.D{
        {Key: "$match", Value: bson.M{"is_active": true}},
        {Key: "$lookup", Value: bson.M{
            "from": "departments",
            "localField": "department_id",
            "foreignField": "_id",
            "as": "dept_info",
        }},
        {Key: "$sort", Value: bson.D{
            {Key: "dept_info.name", Value: 1},
            {Key: "user_code", Value: 1},
        }},
    }
}
profile
Developer

0개의 댓글