C++ MongoDB 완벽 가이드 | mongocxx·CRUD·연결·문제 해결·성능 최적화 [#52-3]

C++ MongoDB 완벽 가이드 | mongocxx·CRUD·연결·문제 해결·성능 최적화 [#52-3]

이 글의 핵심

C++에서 MongoDB 연동: mongocxx 설치·연결, insert_one·find·update·delete 실전 코드. Connection timeout·bulk_write_exception 등 흔한 에러 해결, 성능 최적화, 프로덕션 패턴까지 900줄 분량으로 다룹니다.

들어가며: C++에서 MongoDB를 왜 쓰나요?

실제 겪는 문제 시나리오

시나리오 1: 로그 데이터 스키마가 자주 바뀜
시스템 로그를 저장할 때, 새 필드가 계속 추가됩니다. 관계형 DB는 ALTER TABLE이 필요하고 마이그레이션이 부담됩니다. MongoDB는 문서 단위로 유연하게 필드를 추가할 수 있습니다.

시나리오 2: 대용량 JSON 저장 시 직렬화 이중 작업
API에서 JSON을 받아 DB에 저장할 때, MySQL이면 JSON → 테이블 매핑 → SQL 파싱이 필요합니다. MongoDB는 BSON(바이너리 JSON)으로 문서를 그대로 저장해 직렬화 경로가 단순합니다.

시나리오 3: “Connection refused” 에러
mongocxx로 연결 시도 시 서버가 꺼져 있거나 URI가 잘못되면 연결 실패. 재시도·타임아웃 설정을 어떻게 해야 할지 막막합니다.

시나리오 4: Duplicate key 에러로 insert 실패
_id 중복 또는 유니크 인덱스 위반 시 bulk_write_exception이 발생합니다. 에러 코드를 파싱해 재시도·upsert로 처리하는 방법이 필요합니다.

시나리오 5: find() 결과가 너무 많아 메모리 폭증
커서 없이 전체 문서를 벡터에 담으면 수십만 건 시 OOM이 발생합니다. mongocxx::cursor로 스트리밍 조회하는 패턴이 필요합니다.

시나리오 6: 트랜잭션 필요 시
여러 컬렉션에 걸친 원자적 작업이 필요할 때, client_session과 트랜잭션 API 사용법을 알아야 합니다.

MongoDB C++ 드라이버(mongocxx)로 해결:

  • 공식 드라이버: MongoDB가 직접 유지보수, bsoncxx와 함께 BSON 문서 처리
  • 유연한 스키마: 문서 단위 저장, 필드 추가/삭제 자유로움
  • 커서 기반 조회: 대용량 결과를 메모리에 올리지 않고 스트리밍
  • 트랜잭션 지원: ACID 보장이 필요한 다중 문서 작업
flowchart LR
  subgraph rdb[관계형 DB]
    R1[JSON] --> R2[스키마 매핑]
    R2 --> R3[SQL 파싱]
    R3 --> R4[저장]
  end
  subgraph mongo[MongoDB]
    M1[BSON 문서] --> M2[직접 저장]
  end

MongoDB C++ 드라이버 아키텍처

flowchart TB
  subgraph App[C++ 애플리케이션]
    Main[main]
    Client[mongocxx::client]
    Coll[collection]
  end

  subgraph Driver[mongocxx / bsoncxx]
    Instance[mongocxx::instance]
    Uri[uri]
    Doc[bsoncxx::document]
  end

  subgraph Mongo[MongoDB 서버]
    Store[(컬렉션)]
  end

  Main --> Instance
  Main --> Client
  Client --> Uri
  Client --> Coll
  Coll --> Doc
  Client -->|BSON/TCP| Store

이 글에서 다루는 것:

  • mongocxx·bsoncxx 설치 및 환경 설정
  • 완전한 CRUD 예제 (insert, find, update, delete)
  • 자주 발생하는 에러와 해결법
  • 성능 최적화 팁
  • 프로덕션 배포 패턴

실무 적용 경험: 이 글은 대규모 C++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.

목차

  1. 환경 설정 및 설치
  2. 기본 연결 및 Hello World
  3. 완전한 CRUD 예제
  4. 자주 발생하는 에러와 해결법
  5. 성능 최적화 팁
  6. 프로덕션 패턴
  7. 구현 체크리스트

1. 환경 설정 및 설치

필수 의존성

항목버전비고
C++C++17 이상C++11 최소, C++17 권장
mongocxx4.xMongoDB C++ 드라이버
bsoncxx4.xBSON 문서 라이브러리
CMake3.15+find_package 지원
pkg-config-링크 플래그 확인용

MongoDB 서버 실행

# Docker로 MongoDB 실행 (권장)
docker run -d -p 27017:27017 mongo:7

# 또는 로컬 설치 후
mongod --dbpath /data/db

mongocxx 설치 (소스 빌드)

# 최신 릴리스 다운로드
curl -OL https://github.com/mongodb/mongo-cxx-driver/releases/download/r4.1.4/mongo-cxx-driver-r4.1.4.tar.gz
tar -xzf mongo-cxx-driver-r4.1.4.tar.gz
cd mongo-cxx-driver-r4.1.4/build

# 빌드 및 설치
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=17
cmake --build .
sudo cmake --build . --target install

vcpkg로 설치 (권장)

vcpkg install mongo-cxx-driver

CMakeLists.txt 기본 설정

cmake_minimum_required(VERSION 3.15)
project(mongodb_demo LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)

find_package(bsoncxx REQUIRED)
find_package(mongocxx REQUIRED)

add_executable(mongodb_demo main.cpp)
target_link_libraries(mongodb_demo PRIVATE
  mongo::mongocxx_shared
  mongo::bsoncxx_shared
)

pkg-config로 컴파일 (간단한 프로젝트)

c++ -std=c++17 main.cpp $(pkg-config --cflags --libs libmongocxx) -o mongodb_demo

macOS dyld 에러 시 (라이브러리 경로):

c++ -std=c++17 main.cpp -Wl,-rpath,/usr/local/lib \
  $(pkg-config --cflags --libs libmongocxx) -o mongodb_demo

2. 기본 연결 및 Hello World

mongocxx::instance (필수)

mongocxx::client를 사용하기 전에 반드시 mongocxx::instance를 한 번 생성해야 합니다. 전역 또는 main 초기에 생성합니다.

#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <mongocxx/uri.hpp>
#include <iostream>

int main() {
    // 1. 인스턴스 생성 (프로세스당 1회)
    mongocxx::instance inst{};

    // 2. URI로 클라이언트 생성
    mongocxx::uri uri("mongodb://localhost:27017");
    mongocxx::client client(uri);

    // 3. DB와 컬렉션 접근
    auto db = client[mydb];
    auto collection = db[test];

    // 4. Ping으로 연결 확인
    auto admin = client[admin];
    auto cmd = bsoncxx::builder::basic::make_document(
        bsoncxx::builder::basic::kvp("ping", 1));
    auto result = admin.run_command(cmd.view());
    std::cout << "Ping: " << bsoncxx::to_json(result) << std::endl;

    return 0;
}

코드 설명:

  • mongocxx::instance: 드라이버 초기화, 소멸 시 정리
  • mongocxx::uri: 연결 문자열 (mongodb://host:port, mongodb://user:pass@host/db?authSource=admin 등)
  • client[dbname]: 데이터베이스 핸들
  • db[collname]: 컬렉션 핸들

URI 옵션 예시

// 로컬 기본
mongocxx::uri uri("mongodb://localhost:27017");

// 인증 포함
mongocxx::uri uri("mongodb://user:password@localhost:27017/mydb?authSource=admin");

// 타임아웃 설정
mongocxx::uri uri("mongodb://localhost:27017/?connectTimeoutMS=5000&socketTimeoutMS=10000");

// 레플리카셋
mongocxx::uri uri("mongodb://host1:27017,host2:27017/?replicaSet=rs0");

Hello World: insert_one + find_one

#include <bsoncxx/builder/basic/document.hpp>
#include <bsoncxx/json.hpp>
#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <mongocxx/uri.hpp>
#include <iostream>

using bsoncxx::builder::basic::kvp;
using bsoncxx::builder::basic::make_document;

int main() {
    mongocxx::instance inst{};
    mongocxx::client client{mongocxx::uri{"mongodb://localhost:27017"}};
    auto collection = client[mydb][greetings];

    // insert_one
    auto doc = make_document(kvp("message", "Hello, MongoDB!"));
    auto insert_result = collection.insert_one(doc.view());
    if (insert_result) {
        std::cout << "Inserted id: " << insert_result->inserted_id().get_oid().value.to_string()
                  << std::endl;
    }

    // find_one
    auto filter = make_document(kvp("message", "Hello, MongoDB!"));
    auto result = collection.find_one(filter.view());
    if (result) {
        std::cout << "Found: " << bsoncxx::to_json(*result) << std::endl;
    } else {
        std::cout << "No document found" << std::endl;
    }

    return 0;
}

3. 완전한 CRUD 예제

3.1 Insert (insert_one, insert_many)

#include <bsoncxx/builder/basic/array.hpp>
#include <bsoncxx/builder/basic/document.hpp>
#include <bsoncxx/json.hpp>
#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <mongocxx/uri.hpp>
#include <iostream>
#include <vector>

using bsoncxx::builder::basic::kvp;
using bsoncxx::builder::basic::make_document;
using bsoncxx::builder::basic::make_array;

void insert_examples(mongocxx::collection& collection) {
    // insert_one: 단일 문서
    auto doc = make_document(
        kvp("name", "Mongo's Burgers"),
        kvp("cuisine", "American"),
        kvp("rating", 4.5));
    auto result = collection.insert_one(doc.view());
    if (result && result->inserted_id()) {
        std::cout << "Inserted: " << result->inserted_id().get_oid().value.to_string() << "\n";
    }

    // insert_many: 여러 문서
    std::vector<bsoncxx::document::value> docs;
    docs.push_back(make_document(kvp("name", "Mongo's Pizza")));
    docs.push_back(make_document(kvp("name", "Mongo's Tacos")));
    auto many_result = collection.insert_many(docs);
    if (many_result) {
        std::cout << "Inserted count: " << many_result->inserted_count() << "\n";
    }
}

주의: _id를 지정하지 않으면 드라이버가 자동으로 ObjectId를 생성합니다. 중복 _idbulk_write_exception을 발생시킵니다.

3.2 Find (find_one, find + cursor)

void find_examples(mongocxx::collection& collection) {
    using bsoncxx::builder::basic::kvp;
    using bsoncxx::builder::basic::make_document;

    // find_one: 단일 문서
    auto filter = make_document(kvp("name", "Mongo's Burgers"));
    auto result = collection.find_one(filter.view());
    if (result) {
        std::cout << "Found: " << bsoncxx::to_json(*result) << "\n";
    }

    // find: 여러 문서 (커서)
    auto cursor = collection.find(make_document(kvp("cuisine", "American")));
    for (auto&& doc : cursor) {
        std::cout << bsoncxx::to_json(doc) << "\n";
    }

    // find + limit, sort
    mongocxx::options::find opts{};
    opts.limit(10);
    opts.sort(make_document(kvp("rating", -1)).view());  // -1: 내림차순
    auto cursor2 = collection.find({}, opts);
    for (auto&& doc : cursor2) {
        std::cout << bsoncxx::to_json(doc) << "\n";
    }
}

커서 주의: mongocxx::cursor는 범위 기반 for로 순회합니다. 한 번 순회하면 끝이므로, 재사용하려면 find()를 다시 호출해야 합니다.

3.3 Update (update_one, update_many, upsert)

void update_examples(mongocxx::collection& collection) {
    using bsoncxx::builder::basic::kvp;
    using bsoncxx::builder::basic::make_document;

    // update_one: $set으로 필드 수정
    auto filter = make_document(kvp("name", "Mongo's Burgers"));
    auto update = make_document(kvp("$set", make_document(kvp("rating", 4.8))));
    auto result = collection.update_one(filter.view(), update.view());
    if (result) {
        std::cout << "Modified: " << result->modified_count() << "\n";
    }

    // update_many: 조건에 맞는 모든 문서 수정
    auto filter2 = make_document(kvp("cuisine", "American"));
    auto update2 = make_document(kvp("$set", make_document(kvp("updated", true))));
    auto result2 = collection.update_many(filter2.view(), update2.view());

    // upsert: 없으면 삽입
    mongocxx::options::update opts{};
    opts.upsert(true);
    auto filter3 = make_document(kvp("name", "New Restaurant"));
    auto update3 = make_document(kvp("$set", make_document(kvp("cuisine", "Korean"))));
    collection.update_one(filter3.view(), update3.view(), opts);
}

3.4 Delete (delete_one, delete_many)

void delete_examples(mongocxx::collection& collection) {
    using bsoncxx::builder::basic::kvp;
    using bsoncxx::builder::basic::make_document;

    // delete_one: 첫 번째 매칭 문서 삭제
    auto filter = make_document(kvp("name", "Mongo's Tacos"));
    auto result = collection.delete_one(filter.view());
    if (result) {
        std::cout << "Deleted: " << result->deleted_count() << "\n";
    }

    // delete_many: 조건에 맞는 모든 문서 삭제
    auto filter2 = make_document(kvp("rating", make_document(kvp("$lt", 3.0))));
    auto result2 = collection.delete_many(filter2.view());
}

3.5 실전 예제: 사용자 프로필 저장소

#include <bsoncxx/builder/basic/document.hpp>
#include <bsoncxx/json.hpp>
#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <mongocxx/uri.hpp>
#include <optional>
#include <string>

using bsoncxx::builder::basic::kvp;
using bsoncxx::builder::basic::make_document;

class UserProfileStore {
public:
    UserProfileStore(mongocxx::database db) : db_(std::move(db)) {
        collection_ = db_[users];
    }

    bool upsertProfile(const std::string& userId,
                       const std::string& name,
                       const std::string& email,
                       int age) {
        auto filter = make_document(kvp("_id", userId));
        auto update = make_document(
            kvp("$set", make_document(
                kvp("name", name),
                kvp("email", email),
                kvp("age", age),
                kvp("updatedAt", bsoncxx::types::b_date{std::chrono::system_clock::now()}))));

        mongocxx::options::update opts{};
        opts.upsert(true);

        auto result = collection_.update_one(filter.view(), update.view(), opts);
        return result && (result->modified_count() > 0 || result->upserted_id());
    }

    std::optional<bsoncxx::document::value> getProfile(const std::string& userId) {
        auto filter = make_document(kvp("_id", userId));
        return collection_.find_one(filter.view());
    }

    bool deleteProfile(const std::string& userId) {
        auto filter = make_document(kvp("_id", userId));
        auto result = collection_.delete_one(filter.view());
        return result && result->deleted_count() > 0;
    }

private:
    mongocxx::database db_;
    mongocxx::collection collection_;
};

// 사용 예
int main() {
    mongocxx::instance inst{};
    mongocxx::client client{mongocxx::uri{"mongodb://localhost:27017"}};
    UserProfileStore store(client[mydb]);

    store.upsertProfile("user123", "홍길동", "[email protected]", 30);
    auto profile = store.getProfile("user123");
    if (profile) {
        std::cout << bsoncxx::to_json(*profile) << "\n";
    }
    return 0;
}

3.6 트랜잭션 예제 (다중 문서 원자성)

void transaction_example(mongocxx::client& client) {
    auto db = client[mydb];
    auto accounts = db[accounts];

    mongocxx::client_session session = client.start_session();
    session.start_transaction();

    try {
        // 계좌 A에서 100 차감
        auto filter_a = make_document(kvp("_id", "account_a"));
        auto update_a = make_document(kvp("$inc", make_document(kvp("balance", -100))));
        accounts.update_one(session, filter_a.view(), update_a.view());

        // 계좌 B에 100 추가
        auto filter_b = make_document(kvp("_id", "account_b"));
        auto update_b = make_document(kvp("$inc", make_document(kvp("balance", 100))));
        accounts.update_one(session, filter_b.view(), update_b.view());

        session.commit_transaction();
        std::cout << "Transaction committed\n";
    } catch (const mongocxx::exception& e) {
        session.abort_transaction();
        std::cerr << "Transaction aborted: " << e.what() << "\n";
    }
}

주의: 트랜잭션은 MongoDB 4.0+ 레플리카셋 또는 샤드 클러스터에서 지원됩니다. 단일 인스턴스는 4.0+에서 지원합니다.


4. 자주 발생하는 에러와 해결법

에러 1: Connection timeout / Connection refused

증상: mongocxx::uri 생성 또는 client 생성 시 예외, 또는 첫 작업 시 “Connection refused”

원인:

  • MongoDB 서버가 실행 중이 아님
  • 잘못된 호스트/포트
  • 방화벽 차단
  • URI 문법 오류

해결법:

# MongoDB 서버 확인
mongosh --eval "db.adminCommand('ping')"
# 또는
docker ps | grep mongo
// ❌ 잘못된 URI
mongocxx::uri uri("mongodb://wronghost:27017");  // 연결 불가

// ✅ 타임아웃 포함 URI
mongocxx::uri uri("mongodb://localhost:27017/?connectTimeoutMS=5000&socketTimeoutMS=10000");

// ✅ 연결 확인 후 사용
try {
    mongocxx::client client(uri);
    auto admin = client[admin];
    auto cmd = bsoncxx::builder::basic::make_document(
        bsoncxx::builder::basic::kvp("ping", 1));
    admin.run_command(cmd.view());
    // 성공
} catch (const mongocxx::exception& e) {
    std::cerr << "MongoDB 연결 실패: " << e.what() << "\n";
}

에러 2: bulk_write_exception (Duplicate key)

증상: insert_one 또는 insert_manymongocxx::bulk_write_exception 발생

원인: _id 중복 또는 유니크 인덱스 위반

// ❌ 동일 _id로 두 번 insert
auto doc = make_document(kvp("_id", "fixed_id"), kvp("name", "test"));
collection.insert_one(doc.view());
collection.insert_one(doc.view());  // Duplicate key!

해결법:

// ✅ upsert 사용
auto filter = make_document(kvp("_id", "fixed_id"));
auto update = make_document(kvp("$set", make_document(kvp("name", "test"))));
mongocxx::options::update opts{};
opts.upsert(true);
collection.update_one(filter.view(), update.view(), opts);

// ✅ 에러 코드 확인 후 처리
try {
    collection.insert_one(doc.view());
} catch (const mongocxx::bulk_write_exception& e) {
    if (e.raw_server_error()) {
        auto code = e.raw_server_error()->code();
        if (code == 11000) {  // Duplicate key
            // upsert로 전환 또는 로깅
        }
    }
    throw;
}

에러 3: mongocxx::instance가 없음

증상: client 생성 시 크래시 또는 “instance not created” 유사 메시지

원인: mongocxx::instance를 생성하지 않고 mongocxx::client 사용

// ❌ 잘못된 코드
mongocxx::client client(mongocxx::uri{"mongodb://localhost:27017"});  // instance 없음!

해결법:

// ✅ main 또는 전역에서 instance 생성
mongocxx::instance inst{};  // 반드시 client보다 먼저
mongocxx::client client(mongocxx::uri{"mongodb://localhost:27017"});

에러 4: bsoncxx::document::view 수명 문제

증상: “Using a view of a deleted document” 또는 잘못된 데이터

원인: make_document()이 반환하는 document::value가 스코프를 벗어나 소멸한 뒤, 그 view()를 사용

// ❌ 위험한 코드
bsoncxx::document::view getFilter() {
    auto doc = make_document(kvp("name", "test"));
    return doc.view();  // doc는 반환 시 소멸 → view가 dangling!
}

해결법:

// ✅ value를 반환하거나, 호출자 스코프에서 value 유지
bsoncxx::document::value getFilter() {
    return make_document(kvp("name", "test"));
}
// 사용
auto filter = getFilter();
collection.find_one(filter.view());

에러 5: macOS dyld - Library not loaded

증상: dyld: Library not loaded: @rpath/libmongocxx._noabi.dylib

원인: 동적 라이브러리 경로가 링커에 전달되지 않음

해결법:

# 링크 시 rpath 추가
c++ -std=c++17 main.cpp -Wl,-rpath,/usr/local/lib \
  $(pkg-config --cflags --libs libmongocxx) -o app

# 또는 환경 변수
export DYLD_LIBRARY_PATH=/usr/local/lib
./app

에러 6: Authentication failed (authSource)

증상: (auth) Authentication failed

원인: authSource가 잘못되었거나, 사용자/비밀번호 오류

해결법:

// ✅ authSource 명시 (admin DB에 사용자 있을 때)
mongocxx::uri uri("mongodb://user:pass@localhost:27017/mydb?authSource=admin");

에러 7: cursor 순회 중 컬렉션 변경

증상: 커서 순회 중 예외 또는 불일치 결과

원인: 다른 스레드나 프로세스가 같은 컬렉션을 수정할 때, 커서가 스냅샷을 유지하지 않는 경우

해결법:

  • 읽기 전용 작업은 read_concern으로 스냅샷 사용 (MongoDB 4.0+)
  • 또는 순회 중 수정을 피하고, 필요한 경우 쿼리를 다시 실행

에러 8: WriteConcern 에러 (wtimeout)

증상: write_concern 관련 타임아웃

원인: 레플리카셋에서 w: "majority" 등으로 쓰기 시, 복제 지연으로 타임아웃

해결법:

mongocxx::write_concern wc;
wc.acknowledge_level(mongocxx::write_concern::level::k_majority);
wc.timeout(std::chrono::milliseconds{5000});

mongocxx::options::insert opts;
opts.write_concern(wc);
collection.insert_one(doc.view(), opts);

5. 성능 최적화 팁

팁 1: 클라이언트 재사용

mongocxx::client스레드 안전하며, 연결 풀을 내부적으로 관리합니다. 매 요청마다 새 client를 만들지 마세요.

// ❌ 나쁜 예
void handleRequest() {
    mongocxx::client client(mongocxx::uri{"mongodb://localhost:27017"});
    // ...
}

// ✅ 좋은 예: 전역 또는 스레드당 1개
mongocxx::instance inst{};
mongocxx::client g_client{mongocxx::uri{"mongodb://localhost:27017"}};

void handleRequest() {
    auto collection = g_client[mydb][items];
    // ...
}

팁 2: bulk_write로 대량 삽입

여러 문서를 삽입할 때 insert_many를 사용하면 한 번의 왕복으로 처리됩니다.

// ❌ N번 왕복
for (const auto& doc : documents) {
    collection.insert_one(doc.view());
}

// ✅ 1번 왕복
collection.insert_many(documents);

팁 3: 프로젝션으로 필드 제한

필요한 필드만 조회하면 네트워크와 직렬화 비용을 줄입니다.

mongocxx::options::find opts{};
opts.projection(make_document(kvp("name", 1), kvp("email", 1)).view());
auto cursor = collection.find(filter.view(), opts);

팁 4: 인덱스 활용

자주 조회하는 필드에 인덱스를 생성합니다. 인덱스 없이 대용량 컬렉션을 스캔하면 느립니다.

// 인덱스 생성 (한 번만 실행해도 됨)
auto index_spec = make_document(kvp("userId", 1), kvp("createdAt", -1));
collection.create_index(index_spec.view());

팁 5: 커서로 대용량 조회

수십만 건을 조회할 때, 전체를 vector에 담지 말고 커서로 스트리밍합니다.

// ❌ 메모리 폭증
std::vector<bsoncxx::document::value> all;
auto cursor = collection.find({});
for (auto&& doc : cursor) {
    all.push_back(bsoncxx::document::value(doc));  // 전체 보관
}

// ✅ 스트리밍 처리
auto cursor = collection.find({});
for (auto&& doc : cursor) {
    processDocument(doc);  // 하나씩 처리 후 버림
}

팁 6: connection pool 크기 조정

기본 풀 크기가 부족하면 URI 옵션으로 조정합니다.

// maxPoolSize 기본 100, 필요 시 조정
mongocxx::uri uri("mongodb://localhost:27017/?maxPoolSize=50");

성능 비교 (참고)

방식왕복 수메모리
insert_one N회N낮음
insert_many 1회1중간
find 전체 로드1높음 (위험)
find + cursor1낮음

6. 프로덕션 패턴

패턴 1: Health Check 및 재연결

bool ping(mongocxx::client& client) {
    try {
        auto admin = client[admin];
        auto cmd = bsoncxx::builder::basic::make_document(
            bsoncxx::builder::basic::kvp("ping", 1));
        admin.run_command(cmd.view());
        return true;
    } catch (...) {
        return false;
    }
}

void ensureHealthy(mongocxx::client& client) {
    if (!ping(client)) {
        throw std::runtime_error("MongoDB 연결 불가");
    }
}

패턴 2: 설정 외부화

struct MongoConfig {
    std::string uri = "mongodb://localhost:27017";
    int connectTimeoutMs = 5000;
    int socketTimeoutMs = 10000;
};

MongoConfig loadFromEnv() {
    MongoConfig c;
    if (const char* u = std::getenv("MONGODB_URI")) c.uri = u;
    if (const char* t = std::getenv("MONGODB_CONNECT_TIMEOUT_MS")) {
        c.connectTimeoutMs = std::stoi(t);
    }
    return c;
}

int main() {
    auto config = loadFromEnv();
    std::string uriStr = config.uri + "?connectTimeoutMS=" + std::to_string(config.connectTimeoutMs)
                        + "&socketTimeoutMS=" + std::to_string(config.socketTimeoutMs);
    mongocxx::uri uri(uriStr);
    mongocxx::client client(uri);
    // ...
}

패턴 3: 로깅·메트릭

template<typename Func>
auto withTiming(const char* op, Func&& f) -> decltype(f()) {
    auto start = std::chrono::steady_clock::now();
    auto result = f();
    auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(
        std::chrono::steady_clock::now() - start).count();
    LOG(INFO) << "MongoDB " << op << " took " << dur << "ms";
    return result;
}

// 사용
auto doc = withTiming("find_one", [&]() {
    return collection.find_one(filter.view());
});

패턴 4: 재시도 (지수 백오프)

template<typename Func>
auto withRetry(Func&& f, int maxRetries = 3) -> decltype(f()) {
    for (int i = 0; i < maxRetries; ++i) {
        try {
            return f();
        } catch (const mongocxx::exception& e) {
            if (i == maxRetries - 1) throw;
            auto delay = std::chrono::milliseconds(100 * (1 << i));
            std::this_thread::sleep_for(delay);
        }
    }
    throw std::runtime_error("Unreachable");
}

// 사용
auto result = withRetry([&]() {
    return collection.find_one(filter.view());
});

패턴 5: RAII 래퍼 클래스

class MongoClient {
public:
    MongoClient(const std::string& uriStr) {
        inst_ = std::make_unique<mongocxx::instance>();
        client_ = std::make_unique<mongocxx::client>(mongocxx::uri{uriStr});
    }

    mongocxx::database database(const std::string& name) {
        return (*client_)[name];
    }

private:
    std::unique_ptr<mongocxx::instance> inst_;
    std::unique_ptr<mongocxx::client> client_;
};

패턴 6: Graceful Shutdown

// 시그널 핸들러에서 client 정리
std::atomic<bool> g_running{true};

void signalHandler(int) {
    g_running = false;
}

int main() {
    std::signal(SIGINT, signalHandler);
    mongocxx::instance inst{};
    mongocxx::client client(mongocxx::uri{"mongodb://localhost:27017"});

    while (g_running) {
        // 작업 수행
    }
    // client, instance는 스코프 종료 시 자동 정리
    return 0;
}

7. 구현 체크리스트

환경 설정

  • MongoDB 서버 실행 확인 (mongosh 또는 docker ps)
  • mongocxx·bsoncxx 설치 (vcpkg 또는 소스)
  • CMake 또는 pkg-config 연동

연결

  • mongocxx::instance를 client보다 먼저 생성
  • URI에 타임아웃 설정 (connectTimeoutMS, socketTimeoutMS)
  • 인증 시 authSource 명시

CRUD

  • insert_one/insert_manyinserted_id 확인
  • find 결과는 커서로 스트리밍, 전체 로드 금지
  • update_one/update_many$set 등 연산자 사용
  • document::value 수명 관리 (view dangling 방지)

에러 처리

  • bulk_write_exception 시 에러 코드 11000 (Duplicate key) 처리
  • 연결 실패 시 재시도 또는 폴백
  • 트랜잭션 실패 시 abort_transaction 호출

성능

  • client 재사용 (매 요청마다 새 client 금지)
  • 대량 삽입 시 insert_many
  • 자주 조회하는 필드에 인덱스 생성
  • 프로젝션으로 불필요한 필드 제외

프로덕션

  • Health Check (ping) 주기적 수행
  • 설정 외부화 (환경 변수)
  • 로깅·메트릭 (지연 시간, 에러)
  • 재시도 정책 (지수 백오프)

문제 시나리오 해결 요약

문제MongoDB C++ 해결 방법
스키마 자주 변경문서 단위 유연한 필드, ALTER 불필요
JSON 저장 이중 직렬화BSON으로 문서 직접 저장
Connection refusedURI 타임아웃, 서버 확인, 재시도
Duplicate keyupsert, bulk_write_exception 코드 11000 처리
대용량 조회 OOMcursor 스트리밍, 프로젝션
다중 문서 원자성client_session + 트랜잭션

정리

항목요약
설치vcpkg 또는 소스 빌드, C++17 권장
연결instance → uri → client → database → collection
CRUDinsert_one/many, find_one/find, update_one/many, delete_one/many
커서find() 반환값을 범위 기반 for로 순회, 전체 로드 금지
에러bulk_write_exception(11000), view 수명, dyld rpath
성능client 재사용, insert_many, 프로젝션, 인덱스
프로덕션Health Check, 설정 외부화, 재시도, 로깅

핵심 원칙:

  1. mongocxx::instance를 반드시 먼저 생성
  2. document::value 수명에 주의, view dangling 방지
  3. client 재사용, 매 요청마다 새로 만들지 않기
  4. 대용량 조회는 커서로 스트리밍

자주 묻는 질문 (FAQ)

Q. MongoDB Atlas에 연결하려면?

A. Atlas 대시보드에서 연결 문자열을 복사해 mongocxx::uri에 넣습니다. mongodb+srv://... 형식도 지원합니다. IP 화이트리스트와 DB 사용자 설정을 확인하세요.

Q. bsoncxx와 mongocxx 차이는?

A. bsoncxx는 BSON 문서 생성·파싱 라이브러리입니다. mongocxx는 MongoDB 연결·CRUD를 담당하며 bsoncxx에 의존합니다.

Q. C++ 말고 다른 언어와 같은 DB를 쓸 수 있나요?

A. 네. MongoDB는 다국어 드라이버를 지원합니다. C++, Python, Node.js 등이 동일한 MongoDB 인스턴스에 접속해 같은 컬렉션을 읽고 쓸 수 있습니다.

Q. 트랜잭션은 언제 필요한가요?

A. 여러 컬렉션에 걸친 원자적 작업(예: 계좌 이체, 주문+재고 차감)이 필요할 때 사용합니다. 단일 문서 수정은 MongoDB가 원자적이므로 트랜잭션이 필요 없습니다.

한 줄 요약: mongocxx로 C++에서 MongoDB CRUD를 타입 안전하게 구현하고, 커서·재시도·프로덕션 패턴으로 안정적으로 운영할 수 있습니다.

다음 글: MongoDB 드라이버 고급: 집계·인덱싱·레플리카셋(#52-4)

이전 글: C++ Redis 클라이언트 완벽 가이드(#52-2)


참고 자료


관련 글

  • C++ MongoDB 드라이버 고급 | 집계 파이프라인·인덱싱·레플리카셋 완벽 가이드 [#52-4]
  • C++ MongoDB 실전 완벽 가이드 | mongocxx CRUD·집계·인덱싱·레플리카셋·프로덕션
  • C++ 데이터베이스 연동 완벽 가이드 | SQLite·PostgreSQL·연결 풀·트랜잭션 [#31-3]
  • C++ PostgreSQL 클라이언트 완벽 가이드 | libpq·libpqxx
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3