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. 환경 설정 및 설치
필수 의존성
| 항목 | 버전 | 비고 |
|---|---|---|
| C++ | C++17 이상 | C++11 최소, C++17 권장 |
| mongocxx | 4.x | MongoDB C++ 드라이버 |
| bsoncxx | 4.x | BSON 문서 라이브러리 |
| CMake | 3.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를 생성합니다. 중복 _id는 bulk_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_many 시 mongocxx::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 + cursor | 1 | 낮음 |
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_many후inserted_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 refused | URI 타임아웃, 서버 확인, 재시도 |
| Duplicate key | upsert, bulk_write_exception 코드 11000 처리 |
| 대용량 조회 OOM | cursor 스트리밍, 프로젝션 |
| 다중 문서 원자성 | client_session + 트랜잭션 |
정리
| 항목 | 요약 |
|---|---|
| 설치 | vcpkg 또는 소스 빌드, C++17 권장 |
| 연결 | instance → uri → client → database → collection |
| CRUD | insert_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, 설정 외부화, 재시도, 로깅 |
핵심 원칙:
- mongocxx::instance를 반드시 먼저 생성
- document::value 수명에 주의, view dangling 방지
- client 재사용, 매 요청마다 새로 만들지 않기
- 대용량 조회는 커서로 스트리밍
자주 묻는 질문 (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)
참고 자료
- MongoDB C++ Driver 공식 문서
- mongocxx API 레퍼런스
- mongo-cxx-driver GitHub
- MongoDB 드라이버 고급(#52-4) — 집계·인덱싱·레플리카셋
관련 글
- C++ MongoDB 드라이버 고급 | 집계 파이프라인·인덱싱·레플리카셋 완벽 가이드 [#52-4]
- C++ MongoDB 실전 완벽 가이드 | mongocxx CRUD·집계·인덱싱·레플리카셋·프로덕션
- C++ 데이터베이스 연동 완벽 가이드 | SQLite·PostgreSQL·연결 풀·트랜잭션 [#31-3]
- C++ PostgreSQL 클라이언트 완벽 가이드 | libpq·libpqxx