5. CRUD 연산 및 기본 쿼리
이번 장에서는 MongoDB Shell을 사용한 데이터베이스 관리 방법을 배웁니다.
Ruby 및 Python 드라이버를 활용하여 기본적인 생성(Create), 조회(Read), 수정(Update), 삭제(Delete) CRUD 연산을 수행하면서 MongoDB와 다양한 프로그래밍 환경 간의 상호 작용 방식을 익히게 됩니다.
또한, MongoDB Stable API(이전의 Versioned API)가 개발자에게 어떤 의미를 가지는지와 제공하는 기능들을 살펴볼 것입니다.
마지막으로, MongoDB Community Edition 및 유료 버전인 Enterprise Edition에서 인증(Authentication) 및 권한 관리(Authorization) 방법을 다룹니다.
이번 장에서 다룰 내용
- MongoDB CRUD 연산 이해
- Ruby 및 Python 드라이버를 활용한 CRUD 연산 수행
- mongosh를 사용한 배치(Batch) 연산 실행
- MongoDB에서 인증 및 권한 관리 구현
기술 요구 사항
이 장의 코드를 따라 하려면 로컬에서 MongoDB를 설치하거나 MongoDB Atlas 데이터베이스에 연결해야 합니다.
MongoDB Community Edition은 mongodb.com에서 다운로드할 수 있으며, MongoDB Atlas는 무료 티어와 최신 버전으로 원활하게 업그레이드할 수 있는 완전 관리형 데이터베이스 서비스(DBaaS)를 제공합니다.
대부분의 예제는 새로운 mongosh 셸과 호환되지만, 기존 mongo 셸을 사용하거나 mongosh 호환 모드를 활성화할 수도 있습니다.
또한, Ruby 또는 Python의 공식 드라이버를 다운로드해야 합니다: MongoDB 공식 드라이버.
이 장을 진행하기 위해 필요한 것
- mongosh 셸
- MongoDB Atlas 데이터베이스 연결
- Ruby 또는 Python 공식 드라이버
MongoDB CRUD 연산
MongoDB 서버를 사용하는 목적이 무엇이든, CRUD(Create, Read, Update, Delete) 연산을 수행해야 합니다.
이러한 기본 연산은 MongoDB와 상호작용하는 핵심 방법이며, MongoDB 문서를 수정하는 절차는 다음과 같습니다.
- 서버에 연결
- 관련 문서 검색(쿼리 실행)
- 지정된 속성 수정
- 변경된 데이터를 데이터베이스에 반영(업데이트 수행)
각 CRUD 연산은 다음과 같은 역할을 합니다.
- Create → 새 문서를 데이터베이스에 삽입
- Read → 데이터베이스에서 문서를 검색
- Update → 기존 문서를 수정
- Delete → 데이터베이스에서 문서를 삭제
mongosh를 사용한 CRUD 연산
mongosh는 관계형 데이터베이스의 관리 콘솔과 유사한 역할을 합니다.
(* 역자주 - 독자의 이해를 돋기 위해 원문의 6번은 4번 뒤에 들어갈 지문으로 바꾸고, Delete를 9번으로 번역함)
1. mongosh 연결
다음 명령어를 사용해 MongoDB Atlas에 연결합니다.
mongosh "mongodb+srv://mycluster.packt.mongodb.net/myDatabase" --apiVersion 1 --username <username> --password <password>
2. 사용 가능한 데이터베이스 확인
mongosh "mongodb+srv://mycluster.packt.mongodb.net/myDatabase" --apiVersion 1 --username <username> --password <password>
3. 특정 데이터베이스 선택
use <db_name>
4. 문서 삽입 (Create)
db.books.insertOne({ title: 'Mastering MongoDB 7.0', isbn: '101' });
결과:
{
acknowledged: true,
insertedId: ObjectId("652024e7ab44f3bf77788a3d")
}
5. 문서 조회 (Read)
db.books.find({ isbn: '101' });
결과:
{
acknowledged: true,
insertedId: ObjectId("652024e7ab44f3bf77788a3d")
}
6. 문서 수정 (Update)
db.books.updateOne({ isbn: '101' }, { $set: { price: 30 } });
결과:
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
7. 수정된 문서 확인
db.books.find({ isbn: '101' });
결과:
[
{
_id: ObjectId("652024e7ab44f3bf77788a3d"),
title: 'Mastering MongoDB 7.0',
isbn: '101',
price: 30
}
]
8. 문서 삭제 (Delete)
db.books.deleteOne({ isbn: '101' });
결과:
{
acknowledged: true,
deletedCount: 1
}
9. 삭제 확인
db.books.find({ isbn: '101' });
결과:
// 아무것도 출력되지 않음 (데이터가 삭제됨)
>
여기에서 몇 가지 사항을 확인할 수 있습니다:
- updateOne() 메서드의 JSON 문서 { isbn: '101' }은 특정 문서를 식별하기 위한 쿼리 필터 역할을 합니다.
- matchedCount 객체는 쿼리에 일치하는 문서가 1개 있음을 나타냅니다.
- modifiedCount 객체는 쿼리에 의해 수정된 문서가 1개 있음을 나타냅니다.
- $set 연산자는 특정 필드 값을 설정하는 데 사용됩니다:
db.books.updateOne({ isbn: '101' }, { $set: { price: 30 } });
결과:
{
"acknowledged": true,
"insertedId": null,
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
여러 문서가 필터 기준을 충족하는 경우, updateOne() 메서드는 첫 번째로 일치하는 문서만 수정*합니다.
이제 find() 메서드를 사용하여 문서가 예상대로 변경되었는지 확인할 수 있습니다:
db.books.deleteOne({ isbn: '101' });
[
{
"_id": ObjectId("652024e7ab44f3bf77788a3d"),
"title": "Mastering MongoDB 7.0",
"isbn": "101",
"price": 30
}
]
(* 역자주 - updateOne(), deleteOne(), findOne()은 필터 기준을 충족하는 첫 번째 문서만 처리합니다. 그런데 다른 조건이 없을 때 여기서 첫번째라는 것은 저장 순서, 즉 _id의 순서를 의미합니다. MongoDB는 _id(ObjectId) 생성 순서에 따라 문서를 저장하므로, 삽입된 순서가 중요합니다. 만약 특정 필드 기준으로 정렬된 상태에서 첫 번째 문서를 선택하려면 sort()를 같이 사용해야 합니다. 예를 들어 db.books.findOne({ price: { $gte: 30 } }, { sort: { price: 1 } }); 을 사용하면, 가격이 30 이상인 문서 중 가장 저렴한 문서 한 개를 반환합니다.)
문서 삭제하기
문서를 삭제하는 방법은 여러 가지가 있습니다. 가장 간단한 방법은 문서의 고유한 _id 필드를 기준으로 필터링하는 것입니다.
예를 들어, isbn: 101인 문서를 삭제하려면 다음과 같이 실행합니다:
db.books.deleteOne({ isbn: '101' });
결과:
{
"acknowledged": true,
"deletedCount": 1
}
이제 find()를 사용하여 해당 문서를 조회해 보면 아무것도 반환되지 않습니다:
db.books.find({ isbn: '101' });
결과:
// 아무것도 출력되지 않음 (데이터가 삭제됨)
>
이때, mongosh는 검색 결과가 없을 경우 쉘 프롬프트(> 기호)만 표시합니다.
MongoDB 7.0에서의 삭제 연산 제한
MongoDB 7.0에서는 삭제 명령(delete)과 관련된 대부분의 시계열 데이터 제한(time series limitations)을 해결했습니다.
그러나 다음과 같은 삭제 명령에는 여전히 시계열 컬렉션(time series collection) 관련 제한이 하나 있습니다:
- delete
- deleteOne()
- deleteMany()
- Bulk.find.delete()
- Bulk.find.deleteOne()
참고: 이러한 명령어는 다중 문서 트랜잭션(multi-document transactions)에서 사용할 수 없습니다.*
(* 역자주 - MongoDB에서 일반 컬렉션(Regular Colleciton)와 다른 방식으로 데이터를 처리하는 시계열 컬렉션(time series collection)은 일반적으로 대량의 데이터를 효율적으로 처리하기 위해 설계되었습니다. 다중 문서 트랜잭션은 복잡한 연산과 높은 자원 소모를 요구하므로, MongoDB는 성능과 안정성을 위해 이러한 제한을 유지하고 있습니다. 또한 시계열 컬렉션을 사용할 때는 이러한 제한을 고려하여 설계를 진행해야 합니다.)
mongosh 스크립팅
내장 명령어를 사용하여 데이터베이스를 관리하는 것은 유용하지만, 셸을 사용하는 주요 이유는 아닙니다.
mongosh의 진정한 강점은 JavaScript REPL 환경을 제공한다는 점입니다.
즉, 여러 개의 명령어를 하나로 묶어 복잡한 관리 작업을 실행할 수 있습니다.
변수 선언 및 할당
let title = 'MongoDB in a nutshell';
print(title);
db.books.insertOne({ title: title, isbn: 102 });
결과:
{
acknowledged: true,
insertedId: ObjectId("65205285f7f90f88bce3909d")
}
변수 기반 문서 검색
db.books.find({ title: title, isbn: 102 });
결과:
[
{
_id: ObjectId("65205285f7f90f88bce3909d"),
title: 'MongoDB in a nutshell',
isbn: 102
}
]
위 예제에서는 title 변수를 선언하고, 이를 사용하여 books 컬렉션에 새 문서를 삽입한 후 해당 문서를 조회합니다.
JavaScript 함수를 활용한 복잡한 쿼리 실행
mongosh는 JavaScript 기반이므로, 함수와 스크립트를 사용하여 데이터베이스에서 복잡한 결과를 생성할 수 있습니다.
queryBooksByIsbn = function (isbn) {
return db.books.find({ isbn: isbn });
}
결과:
[Function: queryBooksByIsbn]
위 함수는 isbn을 기준으로 books 컬렉션에서 문서를 검색하는 기능을 수행합니다.
이 한 줄 명령은 isbn 값을 인자로 받는 queryBooksByIsbn이라는 새로운 함수를 생성합니다.
컬렉션에 있는 데이터를 사용하여 이 새로운 함수를 통해 isbn으로 책을 가져올 수 있습니다.
다음은 코드 예시입니다:
queryBooksByIsbn(102)
[
{
_id: ObjectId("65205285f7f90f88bce3909d"),
title: 'MongoDB in a nutshell',
isbn: 102
}
]
mongosh를 사용하여 이러한 스크립트를 작성하고 테스트할 수 있습니다.
만약 만족하면, 이를 .js 파일에 저장하고 커맨드 라인에서 직접 실행할 수 있습니다. 예를 들어:
'mongosh <script_name.js>'
다음은 이러한 스크립트의 기본 동작과 관련된 몇 가지 유용한 참고 사항입니다:
- 쓰기 작업(write operations)은 기본적으로 쓰기 컨선(write concern) w: "majority" 를 사용합니다. 여기서 다수(majority)는 모든 노드의 계산된 다수와 데이터 저장 노드(data-bearing nodes) 수 중 최소값으로 결정됩니다. 즉, 데이터를 저장하지 않는 중재자(arbiter) 노드는 다수 계산에서 제외됩니다.
예를 들어, 세 개의 서버로 구성된 복제 세트(replica set)에서 한 개가 중재자인 경우, 다수 계산은 다음과 같이 이루어집니다:- 모든 노드의 계산된 다수: 전체 노드 수(3)의 50%를 초과하는 가장 가까운 수 → 2
- 데이터 저장 노드 수: 2 (조정자를 제외한 노드)
- 결과적으로 최소값 2가 다수로 사용됩니다.
- 스크립트에서 실행 결과를 표준 출력(stdout)으로 가져오려면 JavaScript의 print() 함수를 사용해야 합니다.
다음 섹션에서는 mongosh에서 스크립트를 사용하는 것과 직접 사용하는 것의 차이를 살펴보겠습니다.
mongosh 스크립팅 vs 직접 명령어 사용
mongosh에서 스크립트를 작성할 때는 셸 헬퍼(Shell Helpers)를 사용할 수 없습니다.
예를 들어, use <database_name>, show collections 등의 명령어는 셸에 내장되어 있으므로 JavaScript 실행 컨텍스트에서는 접근할 수 없습니다. 하지만 다행히도 JavaScript 실행 컨텍스트에서 사용할 수 있는 대체 명령어가 존재합니다.
아래 Table 5.1에서 이를 정리하였습니다.
| 셸 헬퍼 | JavaScript 대체 명령어 |
| show dbs, show databases | db.adminCommand('listDatabases') |
| use <database_name> | db = db.getSiblingDB('<database_name>') |
| show collections | db.getCollectionNames() |
| show users | db.getUsers() |
| show roles | db.getRoles({ showBuiltinRoles: true }) |
| show log <logname> | db.adminCommand({ 'getLog' : '<logname>' }) |
| show logs | db.adminCommand({ 'getLog' : '*' }) |
| it (쿼리 결과 반복) | js cursor = db.collection.find(); if (cursor.hasNext()) { cursor.next(); } |
Table 5.1: 셸 도우미(helper)와 JavaScript 동등 표현
it는 mongosh 셸에서 너무 많은 결과가 한 번에 출력될 경우, 이를 반복적으로 조회할 수 있도록 제공되는 커서(Cursor) 객체입니다.
mongosh 스크립팅의 장점
mongosh를 사용하면 클라이언트에서 거의 모든 작업을 스크립트로 자동화할 수 있습니다. 또한, 빠르게 데이터를 프로토타이핑하고 분석할 수 있는 강력한 도구입니다.
mongosh를 이용한 배치 삽입 (Batch Inserts)
기본적인 반복문을 이용한 삽입
mongosh에서 여러 개의 문서를 프로그래밍 방식으로 삽입하려면, JavaScript 셸의 특성을 활용하여 반복문을 통해 하나씩 삽입할 수 있습니다. 아래 예제는 1000개의 문서를 반복문을 통해 개별적으로 삽입하는 코드입니다.
authorMongoDBFactory = function() {
for (let loop = 0; loop < 1000; loop++) {
db.books.insertOne({ name: "MongoDB factory book" + loop });
}
}
함수 실행:
> authorMongoDBFactory()
"MongoDB factory book0"부터 "MongoDB factory book999"까지 총 1000개의 책 문서를 개별적으로 삽입합니다.
하지만 1000번의 개별적인 데이터베이스 호출이 발생하므로, 데이터베이스에 부담을 줄 수 있습니다.
대량 삽입 (Bulk Insert) 활용
위 방법의 비효율성을 개선하기 위해, 한 번의 DB 호출로 대량 삽입을 수행할 수 있습니다.
아래 예제는 initializeUnorderedBulkOp()를 사용하여 1000개의 문서를 한 번에 삽입하는 코드입니다.
fastAuthorMongoDBFactory = function() {
let bulk = db.books.initializeUnorderedBulkOp();
for (let loop = 0; loop < 1000; loop++) {
bulk.insert({ name: "MongoDB factory book" + loop });
}
bulk.execute();
}
함수 실행:
> fastAuthorMongoDBFactory();
1000개의 문서를 한 번의 명령어로 삽입하여 데이터베이스 부하를 줄일 수 있습니다.
개별 삽입보다 훨씬 빠르고 효율적인 방법입니다.
최종 결과는 이전과 동일하게 books 컬렉션에 1000개의 문서가 삽입됩니다.
각 문서는 아래와 같은 구조를 따릅니다.
db.books.find({ name: /MongoDB factory/ });
[
{
_id: ObjectId("652059aff7f90f88bce39486"),
name: 'MongoDB factory book0'
},
{
_id: ObjectId("652059aff7f90f88bce39487"),
name: 'MongoDB factory book1'
},
{
_id: ObjectId("652059aff7f90f88bce39488"),
name: 'MongoDB factory book2'
},
{
_id: ObjectId("652059aff7f90f88bce39489"),
name: 'MongoDB factory book3'
},
...
{
_id: ObjectId("652059aff7f90f88bce3948a"),
name: 'MongoDB factory book999'
}
]
차이점
- 사용자 입장에서 달라지는 점은 실행 속도와 데이터베이스 부하 감소입니다.
- 이전 예제에서는 initializeUnorderedBulkOp()를 사용하여 삽입 순서를 신경 쓰지 않고 빠르게 문서를 추가했습니다.
- bulk.insert()로 추가한 순서와 데이터베이스에 삽입된 순서가 다를 수 있습니다.
- 이러한 방식은 각 삽입 작업이 서로 독립적이거나, 결과가 같아도 문제가 없을 때 적합합니다.
삽입 순서를 유지하려면?
- 삽입 순서를 유지하려면 initializeOrderedBulkOp()를 사용해야 합니다.
- 아래 코드처럼 두 번째 줄을 변경하면 삽입 순서를 보장할 수 있습니다.
let bulk = db.books.initializeOrderedBulkOp();
다음 섹션에서는 mongosh에서 배치 작업을 활용하여 운영 성능을 향상시키는 방법을 살펴보겠습니다.
mongosh를 사용한 배치 작업 (Batch Operations)
- 삽입 작업의 경우 순서는 크게 중요하지 않지만, 배치 명령어(Bulk Command)는 삽입 외에도 다양한 작업에 사용될 수 있습니다.
- 아래 예제에서는 bookOrders 컬렉션(재고가 부족한 책을 주문하는 데이터를 저장하는 컬렉션)에 한 권의 책이 있습니다.
- isbn: 101
- name: 'Mastering MongoDB 1'
- available: 99 (현재 주문 가능 도서 개수)
db.bookOrders.find()
[
{
_id: ObjectId("652065fcf7f90f88bce39c57"),
isbn: 101,
name: 'Mastering MongoDB 1',
available: 99
}
]
배치 작업 예제
- 한 번의 배치 작업에서 다음과 같은 두 가지 작업을 수행합니다.
- 주문 가능 도서 개수(available)에 책 1권 추가
- 100권 주문 처리 → 구매 가능 도서 개수는 0권이 됨
let bulk = db.bookOrders.initializeOrderedBulkOp();
bulk.find({ isbn: 101 }).updateOne({ $inc: { available: 1 } });
bulk.find({ isbn: 101 }).updateOne({ $inc: { available: -100 } });
bulk.execute();
결과:
위 코드를 실행하면 Figure 5.1의 출력 결과와 동일한 값이 반환됩니다.

Ordered vs. Unordered Bulk Operations
initializeOrderedBulkOp()을 사용하면,
- 100권 주문을 넣기 전에 1권을 먼저 추가하도록 보장할 수 있습니다.
- 따라서 재고 부족 상태(Out of Stock)가 발생하지 않습니다.
반대로 initializeUnorderedBulkOp()을 사용하면,
- 작업 순서가 보장되지 않기 때문에
- 100권 주문이 먼저 실행될 수도 있음 → 재고 부족 오류 발생 가능
MongoDB의 배치 처리 방식
- MongoDB는 순차적(Ordered) 배치 작업을 실행할 때,
- 한 번에 100,000개의 작업 단위(Batch)로 나눕니다.
- 유사한 유형의 작업을 그룹화하여 실행합니다.
예를 들어, 다음과 같은 작업들이 있다면:
- 100,004개의 insert
- 99,998개의 update
- 100,004개의 delete
- 추가적으로 5개의 insert
MongoDB는 이를 다음과 같이 처리합니다:
[100,000개의 insert]
[4개의 insert]
[99,998개의 update]
[100,000개의 delete]
[4개의 delete]
[5개의 insert]
(* 역자주 - 원문에 있는 Figure 5.2는 중복이고, 내용에 혼돈을 줄 수 있어 생략함)
이 방식은 작업의 순서에는 영향을 미치지 않지만, 데이터베이스에 100,000개 단위의 배치로 전송된다는 것을 의미합니다.
다만, 이 동작 방식이 향후 MongoDB 버전에서도 유지된다는 보장은 없습니다.
참고: MongoDB 3.2 버전부터는 bulkWrite()라는 대체 명령어를 사용할 수 있습니다.
다음 코드 스니펫에서 bulkWrite의 인수는 실행할 작업의 순서, writeConcern(기본값은 1), 그리고 배열에 나타난 순서대로 작업을 적용할지 여부(ordered, 기본적으로 true)를 포함합니다:
db.collection.bulkWrite(
[ <operation 1>, <operation 2>, ... ],
{
writeConcern: <document>,
ordered: <boolean>
}
)
bulkWrite에서 지원하는 작업은 다음과 같습니다:
- insertOne()
- updateOne()
- updateMany()
- deleteOne()
- deleteMany()
- replaceOne()
updateOne(), deleteOne(), replaceOne()는 하나의 문서만 수정, 삭제 또는 교체합니다.
여러 개의 문서가 일치하는 경우 첫 번째 문서만 적용되므로, 쿼리를 설계할 때 여러 문서가 일치하지 않도록 주의해야 합니다.
그렇지 않으면 예상치 못한 동작이 발생할 수 있습니다.
Ruby 드라이버를 이용한 CRUD
MongoDB의 Ruby 드라이버는 처음에는 외부 커뮤니티 멤버가 유지 관리했으나, 이후 MongoDB가 해당 개발자를 고용하면서 Ruby는 공식 지원 언어 중 하나가 되었습니다.
MongoDB Ruby 드라이버는 통합 쿼리 캐시(query cache) 기능을 포함하고 있습니다. 이 기능을 활성화하면, 이전에 실행된 find 및 aggregation 결과를 캐시합니다. 같은 쿼리가 다시 실행될 경우, 캐시된 결과를 반환하여 불필요한 데이터베이스 접근을 방지합니다.
이 장의 Mongoid ODM 섹션에서 쿼리 캐시 사용 예제를 확인할 수 있습니다.
MongoDB에 Ruby로 연결하는 권장 방법은 GitHub의 공식 MongoDB Ruby 드라이버를 이용하는 것입니다.
데이터베이스 연결
Gemfile에 MongoDB Ruby 드라이버를 추가합니다:
gem "mongo"
드라이버를 수동으로 설치하려면 다음 명령을 실행합니다:
gem install mongo
그런 다음, 다음 예제와 같이 클래스에서 데이터베이스에 연결할 수 있습니다:
connection_string = "mongodb://HOST:PORT/DATABASE_NAME"
client = Mongo::Client.new(connection_string)
db = client.database
- db.collection_names → 컬렉션 이름 목록 반환
- db.list_collections → 컬렉션 메타데이터 해시 목록 반환
문서 생성
mongodb_book 데이터베이스의 books 컬렉션을 가리키는 @collection 인스턴스 변수가 있다고 가정합니다.
단일 문서를 삽입하려면 다음과 같이 실행합니다:
@collection = client[:books]
document = {
isbn: '101',
name: 'Mastering MongoDB 7.0',
price: 30
}
result = @collection.insert_one(document)
puts result.n
두 개의 문서를 삽입하려면 insert_one 대신 insert_many를 사용합니다:
documents = [
{ isbn: '102', name: 'MongoDB in 7 years', price: 50 },
{ isbn: '103', name: 'MongoDB for experts', price: 40 }
]
result = @collection.insert_many(documents)
이때 반환된 객체는 Mongo::BulkWrite::Result 클래스로, BulkWrite 인터페이스를 사용하여 성능을 최적화했음을 의미합니다.
주요 차이점은 inserted_ids 속성을 통해 삽입된 문서의 ObjectId를 반환할 수 있다는 것입니다:
#<Mongo::BulkWrite::Result:0x000055e707ea5d28 @results={"n_inserted"=>2,
"n"=>2, "inserted_ids"=>[BSON::ObjectId('6521706eb2e05257537b2fea'),
BSON::ObjectId('6521706eb2e05257537b2feb')]}, @acknowledged=true>
데이터 읽기
문서를 찾는 방법은 컬렉션 레벨에서 문서를 생성하는 방식과 동일합니다:
@collection.find({ isbn: '101' })
여러 검색 조건을 연결할 수 있으며, 이는 SQL의 AND 연산자와 동일합니다:
@collection.find({ isbn: '101', name: 'Mastering MongoDB 7.0' })
MongoDB Ruby 드라이버의 CRUD 기능은 쿼리를 최적화할 수 있도록 여러 쿼리 옵션을 제공합니다.
다음 Table 5.2는 가장 많이 사용되는 쿼리 옵션을 정리한 것입니다.
| 옵션 | 설명 |
| allow_partial_results | 샤딩된 클러스터에서 일부 샤드가 다운된 경우, 응답 가능한 샤드에서만 결과를 반환합니다. |
| batch_size (Integer) | 한 번의 GETMORE 연산에서 가져올 문서 개수를 설정할 수 있습니다. |
| comment (String) | 쿼리에 설명을 추가하여 문서화할 수 있습니다. |
| hint (Hash) | 특정 인덱스를 강제로 사용하도록 지정할 수 있습니다. |
| limit (Integer) | 결과 집합을 지정한 개수만큼 제한합니다. |
| no_cursor_timeout | 기본적으로 MongoDB는 600초 동안 비활성 상태인 커서를 자동으로 닫습니다. 이 옵션을 사용하면 커서가 닫히지 않습니다. |
| projection (Hash) | 특정 필드만 가져오거나 제외할 수 있습니다. 예: client[:books].find.projection(:price => 1) (price 필드만 가져옴) |
| read (Hash) | 특정 읽기 우선순위를 지정할 수 있습니다. 예: client[:books].find.read(:mode => :secondary_preferred) |
| skip (Integer) | 지정한 개수만큼 문서를 건너뛰고 검색할 수 있습니다. 페이징 처리에 유용합니다. |
| sort (Hash) | 결과를 정렬할 수 있습니다. 예: client[:books].find.sort(:name => -1) (이름을 기준으로 내림차순 정렬) |
Table 5.2: MongoDB Ruby 드라이버의 주요 쿼리 옵션
MongoDB Ruby 드라이버는 쿼리 옵션 외에도 메서드 체이닝이 가능한 몇 가지 헬퍼 함수를 제공합니다:
- count_documents:
특정 필터 조건을 만족하는 문서의 개수를 조회하거나, 컬렉션 전체의 문서 개수를 가져옵니다. - estimated_document_count:
필터링 없이 컬렉션 내 문서 개수의 대략적인 추정치를 반환합니다. - distinct(:field_name):
이전 쿼리의 결과에서 중복되지 않는 특정 필드 값만 추출합니다. - find():
검색 결과를 포함하는 커서를 반환하며, Ruby에서 .each를 사용하여 반복할 수 있습니다.
예제 코드
result = @collection.find({ isbn: '101' })
result.each do |doc|
puts doc.inspect
end
출력 결과 (books 컬렉션의 예제)
{"_id"=>BSON::ObjectId('65216d4bb2e05254b3022d2f'),
"isbn"=>"101",
"name"=>"Mastering MongoDB 7.0",
"price"=>30}
find()에서 연산자 체이닝
- 기본적으로 find()는 AND 연산자를 사용하여 여러 필드를 매칭합니다.
- OR 연산자를 사용하려면 $or 키를 포함해야 합니다.
예제 코드 ($or 사용)
result = @collection.find('$or' => [{ isbn: '101' }, { isbn:'102' }]).to_a
puts result
출력 결과:
{"_id"=>BSON::ObjectId('65216d4bb2e05254b3022d2f'),
"isbn"=>"101",
"name"=>"Mastering MongoDB 7.0",
"price"=>30}
{"_id"=>BSON::ObjectId('6521706eb2e05257537b2fea'),
"isbn"=>"102",
"name"=>"MongoDB in 7 days",
"price"=>50}
예제 코드 ($and 사용)
result = @collection.find('$and' => [{ isbn: '101' }, { isbn: '102' }]).to_a
puts result
결과:
- ISBN이 101이면서 102인 문서는 존재할 수 없으므로 결과가 없음.
- 즉, 빈 배열 [] 반환.
중첩된(Nested) 연산
MongoDB Ruby 드라이버는 중첩된 구조를 가진 문서 삽입을 간단하게 처리할 수 있습니다.
아래 예제에서는, 최상위 필드(isbn, name, price)와 함께, 자체 하위 필드를 포함하는 meta 필드를 가진 문서를 생성합니다.
이 문서는 insert_one 메서드를 사용하여 MongoDB 컬렉션에 삽입할 수 있습니다.
document = {
'isbn': '104',
'name': 'Python and MongoDB',
'meta': {
'version': 'MongoDB 7.0'
},
'price': 60
}
@collection.insert_one(document)
MongoDB Ruby 드라이버는 간결한 데이터 조회 방법을 제공합니다.
Dot Notation(점 표기법)을 사용하여 중첩된 필드를 찾을 수 있습니다.
result = @collection.find({ 'meta.version': 'MongoDB 7.0' }).to_a
puts result
참고: 중첩된 필드를 조회할 때는 키 이름을 작은따옴표('')로 감싸야 하고, $set과 같은 $로 시작하는 연산자를 사용할 때도 따옴표로 감싸야 합니다.
데이터 업데이트
MongoDB Ruby 드라이버를 사용하여 문서를 업데이트하려면 먼저 해당 문서를 찾거나 식별해야 합니다.
예제 북 컬렉션에서, 다음 명령어를 실행하면 특정 문서를 찾아 업데이트할 수 있습니다.
@collection.update_one({ 'isbn': '101' }, { '$set' => { 'name': 'Mastering MongoDB 7.0' } })
위 코드는 isbn 값이 101인 문서를 찾아 이름(name)을 Mastering MongoDB 7.0으로 변경합니다.
update_one과 유사하게, update_many를 사용하면 첫 번째 매개변수로 찾은 여러 개의 문서를 한 번에 업데이트할 수 있습니다.
데이터 삭제
문서를 삭제하는 방법은 문서를 찾은 후 삭제 연산을 적용하는 방식으로 이루어집니다.
예를 들어, 북(책) 컬렉션에서 다음 코드를 실행하면 특정 문서를 삭제할 수 있습니다.
@collection.find({ isbn: '101' }).delete_one
위 코드는 isbn 값이 101인 단일 문서를 삭제합니다.
- isbn이 각 문서에서 고유한 값이라면, 해당 문서 하나만 삭제됩니다.
- 하지만 find()가 여러 문서를 찾을 경우, delete_one은 첫 번째로 반환된 문서만 삭제합니다.
여러 문서 삭제
만약 find()로 찾은 모든 문서를 삭제하려면 delete_many를 사용합니다.
@collection.find({ price: { '$gte': 30 } }).delete_many
위 코드는 가격(price)이 30 이상($gte: 30)인 모든 책을 삭제합니다.
배치(Batch) 작업
대량의 문서를 처리할 때는 bulkWrite 메서드를 사용할 수 있습니다.
이전 여러 문서 삽입(insert many) 예제와 유사하게, 다음과 같이 사용할 수 있습니다.
@collection.bulk_write([{ insertMany: documents }], { ordered: true })
bulkWrite 메서드는 다음과 같은 연산을 지원합니다.
- insertOne
- updateOne
- updateMany
- replaceOne
- deleteOne
- deleteMany
주의할 점
- *One으로 끝나는 메서드는 하나의 문서만 변경 또는 삭제합니다.
→ 필터를 정확히 지정하지 않으면 원치 않는 결과가 발생할 수 있습니다. - *Many로 끝나는 메서드는 필터에 맞는 모든 문서를 변경 또는 삭제합니다.
Mongoid ODM
Mongoid는 MongoDB용 Object-Document Mapper(ODM)로, Ruby on Rails 프레임워크에서 MongoDB를 보다 직관적으로 사용할 수 있도록 도와줍니다.
MongoDB의 저수준(Low-Level) 드라이버는 유연한 기능을 제공하지만, Mongoid는 이를 한층 더 발전시켜 Rails의 네이밍 규칙과 긴밀하게 통합됩니다.
Mongoid의 주요 장점
- 스키마 정의, 데이터 쿼리, 모델링을 쉽게 수행할 수 있음
- ORM(Object-Relational Mapping)과 유사하게 모델과 데이터베이스 간의 연결을 단순화
- Rails의 MVC 패턴과 자연스럽게 어우러짐
Mongoid는 Active Record와 유사한 방식으로 데이터를 다룰 수 있어, Ruby on Rails 환경에서 손쉽게 활용할 수 있습니다.
Mongoid 설치
Mongoid를 사용하려면 Gemfile에 다음과 같이 추가하면 됩니다.
gem 'mongoid'
또한, Rails 버전에 따라 application.rb 파일에 아래 설정을 추가해야 할 수도 있습니다.
config.generators do |g|
g.orm :mongoid
end
데이터베이스 연결
Mongoid는 mongoid.yml 설정 파일을 통해 데이터베이스에 연결합니다.
설정은 Key-Value 형식으로 정의되며, 들여쓰기를 사용하여 계층적으로 구성됩니다.
데이터 조회 (Reading Data)
Mongoid는 Active Record 스타일의 쿼리 DSL을 제공합니다.
ID로 문서 찾기
MongoDB의 _id 필드를 사용하여 특정 문서를 찾을 수 있습니다.
Book.find('592149c4aabac953a3a1e31e')
위 코드는 _id가 '592149c4aabac953a3a1e31e'인 문서를 찾습니다.
특정 속성(name)으로 검색
where 메서드를 사용하여 특정 속성 값을 가진 문서를 검색할 수 있습니다.
Book.where(name: 'Mastering MongoDB')
위 코드는 name이 'Mastering MongoDB'인 문서를 조회합니다.
find_by 메서드 활용
find_by 메서드를 사용하면 특정 속성을 기반으로 단일 문서를 찾을 수 있습니다.
Book.find_by(name: 'Mastering MongoDB')
위 코드는 'Mastering MongoDB'라는 이름(name)을 가진 첫 번째 문서를 반환합니다.
쿼리 캐싱 (Query Cache)
이 쿼리는 이전에 name 속성을 사용한 것과 유사합니다. 같은 쿼리를 반복해서 실행할 경우 데이터베이스 부하를 줄이기 위해 QueryCache를 활성화할 수 있습니다.
Mongoid::QueryCache.enabled = true
이 설정은 특정 코드 블록에서 적용할 수도 있고, Mongoid 초기화 파일에 추가할 수도 있습니다.
쿼리 범위 지정 (Scoping Queries)
Mongoid에서는 클래스 메서드(Class Method)를 사용하여 범위를 지정할 수 있습니다.
class Book
include Mongoid::Document
field :price, type: Float
scope :premium, -> { where(price: { '$gt' => 20 }) }
end
위 코드는 가격(price)이 20보다 큰 책을 찾는 premium 범위를 정의합니다.
이후 Book.premium을 실행하면 가격이 20보다 큰 책들만 반환됩니다.
Book.premium
문서 생성, 수정, 삭제 (Create, Update, Delete)
1. 문서 생성 (Create)
Mongoid의 문서 생성 방식은 Active Record API와 유사합니다.
Book.where(isbn: 202, name: 'Mastering MongoDB, 4th Edition').create
isbn이 202이고, 이름이 'Mastering MongoDB, 4th Edition'인 책을 생성합니다.
2. 문서 수정 (Update)
Mongoid에서는 update와 update_all을 사용할 수 있습니다.
Book.where(isbn: 202).update(name: 'Mastering MongoDB, 4th Edition')
위 코드는 isbn이 202인 첫 번째 문서의 name을 수정합니다.
Book.where(price: { '$gt': 20 }).update_all(price_range: 'premium')
위 코드는 price가 20보다 큰 모든 문서의 price_range 값을 'premium'으로 변경합니다.
3. 문서 삭제 (Delete)
문서를 삭제할 때는 delete와 destroy의 차이점을 알아야 합니다.
- delete → 콜백을 건너뛰고 즉시 삭제
- destroy → 콜백을 실행한 후 삭제
여러 개의 문서를 삭제할 경우 delete_all과 destroy_all을 사용할 수 있습니다.
- delete_all → 모든 문서를 즉시 삭제 (콜백 없음)
- destroy_all → 모든 문서를 삭제하면서 콜백 실행
Python 드라이버를 사용한 CRUD
Python은 강력한 프로그래밍 언어로, FastAPI와 같은 프레임워크와 함께 사용하면 강력한 웹 개발 기능을 제공합니다.
MongoDB를 Python과 통합하려면 MongoEngine과 공식 저수준 드라이버인 PyMongo를 사용할 수 있습니다.
PyMongo는 pip을 사용하여 간단하게 설치할 수 있습니다.
$ python -m pip install pymongo
MongoDB 연결 테스트
아래 코드 스니펫을 사용하여 MongoDB 배포 환경과의 연결을 테스트할 수 있습니다.
from pymongo import MongoClient
# 연결 문자열을 여기에 입력하세요
uri = "<connection string>"
# 클라이언트 생성 및 서버 연결
client = MongoClient(uri)
# Ping을 전송하여 연결 확인
try:
client.admin.command('ping')
print("MongoDB 배포 환경에 성공적으로 연결되었습니다!")
except Exception as e:
print(e)
위 코드는 MongoDB 서버에 ping을 보내 성공적으로 연결되었는지 확인합니다.
PyMongo와 Motor
PyMongo는 MongoDB 공식 지원 Python 드라이버로,
Python의 유연한 프로그래밍 환경과 MongoDB의 문서 지향 NoSQL 데이터베이스를 연결해 줍니다.
Motor는 MongoDB의 공식 비동기 드라이버입니다.
- 현대적인 실시간 웹 애플리케이션에서는 비동기 방식이 필수적입니다.
- Motor를 사용하면 비동기 프레임워크에서 MongoDB를 효율적으로 활용할 수 있습니다.
- 이는 특히 동시에 많은 사용자를 처리해야 하는 웹 애플리케이션에서 입출력(I/O) 성능을 최적화하는 데 유용합니다.
Motor는 다음 명령어로 간단히 설치할 수 있습니다.
$ python -m pip install motor
MongoDB Atlas에 비동기적으로 연결하기
아래 코드를 사용하면 asyncio 비동기 프레임워크를 활용하여 MongoDB Atlas 배포 환경과의 연결을 테스트할 수 있습니다.
import asyncio
from motor.motor_asyncio import AsyncIOMotorClient
from pymongo.server_api import ServerApi
async def ping_server():
# 여기에 Atlas 연결 문자열을 입력하세요
uri = "<connection string>"
# 새 클라이언트를 생성할 때 안정적인 API 버전 설정
client = AsyncIOMotorClient(uri, server_api=ServerApi('1'))
# Ping을 전송하여 연결 확인
try:
await client.admin.command('ping')
print("MongoDB 배포 환경에 성공적으로 연결되었습니다!")
except Exception as e:
print(e)
asyncio.run(ping_server())
위 코드 스니펫은 MongoDB의 안정적인 API(Stable API) 기능을 활용합니다.
이를 사용하려면 Motor 드라이버 버전 2.5 이상을 사용해야 하며, MongoDB Server 5.0 이상과 함께 사용할 수 있습니다.
MongoDB의 CRUD 작업
다음 섹션에서는 PyMongo를 사용하여 MongoDB에서 문서를 생성(Create), 읽기(Read), 업데이트(Update), 삭제(Delete)하는 방법을 다룹니다.
문서 삽입 예제
MongoDB의 Python 드라이버는 Ruby에서 제공되는 CRUD API와 유사한 기능을 제공하므로, Python을 사용하여 MongoDB 컬렉션과 쉽게 상호 작용할 수 있습니다. 아래는 MongoDB의 mongodb_books 데이터베이스 내 books 컬렉션에 새로운 문서를 삽입하는 예제입니다.
# 'mongodb_books' 데이터베이스와 'books' 컬렉션 사용
books = client.mongodb_books.books
# 새 책 문서 삽입
book = {
'isbn': '301',
'name': 'Python and MongoDB',
'meta': {'version': 'MongoDB 7.0'},
'price': 60
}
insert_result = books.insert_one(book)
pprint(insert_result)
스크립트를 실행하면 삽입 결과가 표시되며, 새로 삽입된 문서의 _id 값이 반환됩니다.
예시 출력:
[{'_id': ObjectId('6521ae0a0e542d46b87947a4')}]
insert_one() 메서드는 단일 문서 삽입을 수행하며, 문서는 Python의 딕셔너리(dictionary) 형식을 사용하여 작성됩니다.
이후 컬렉션을 조회하여 삽입된 문서를 확인할 수 있습니다.
문서 조회 (Finding Documents)
MongoDB에서 특정 속성을 기반으로 문서를 조회하는 과정은 직관적입니다.
최상위 속성(top-level attributes)의 경우, 조회하고 싶은 키-값 쌍을 포함하는 딕셔너리를 사용하면 됩니다.
아래는 mongodb_books 데이터베이스 내 books 컬렉션에서 특정 책 이름을 기준으로 문서를 조회하는 예제입니다.
# 'mongodb_books' 데이터베이스와 'books' 컬렉션 사용
books = client.mongodb_books.books
# 이름이 "Python and MongoDB"인 문서 찾기
result = books.find({"name": "Python and MongoDB"})
# 조회된 문서 출력
for document in result:
pprint(document)
앞서 실행한 스크립트는 books 컬렉션에서 name 필드 값이 "Python and MongoDB"인 모든 문서를 출력합니다.
만약 이전에 삽입한 문서가 컬렉션에 존재한다면, 콘솔에 다음과 같은 세부 정보가 표시됩니다:
{
'_id': ObjectId('6521ae0a0e542d46b87947a4'),
'isbn': '301',
'name': 'Python and MongoDB',
'meta': { 'version': 'MongoDB 7.0' },
'price': 60
}
중첩된 문서 내 특정 필드 조회
문서 내 임베디드(embedded) 필드에 특정 값을 포함하는 문서를 조회하려면 점(dot) 표기법을 사용할 수 있습니다.
아래 예제에서는 meta.version 필드를 대상으로 정규 표현식(Regex) 검색을 수행합니다.
query = {
'meta.version': {
"$regex": ".*?g.*?7\.0$", # "g" 문자가 포함되며 "7.0"으로 끝나는 문자열 검색
"$options": "i" # 대소문자 구분 없이 검색 (case-insensitive)
}
}
# 쿼리에 해당하는 문서 조회
result = books.find(query)
# 조회된 문서 출력
for document in result:
pprint(document)
설명:
- $regex: "meta.version" 필드에서 "g" 문자가 포함되고 "7.0"으로 끝나는 문자열을 검색합니다.
- $options: "i": 대소문자를 구분하지 않고 검색합니다.
결과:
이 스크립트를 실행하면, mongodb_books 데이터베이스 내 books 컬렉션에서 meta.version 필드 값이 위 정규식과 일치하는 모든 문서가 조회 및 출력됩니다.
비교 연산자(Comparison operators)도 유사한 방식으로 지원됩니다.
자주 사용되는 비교 연산자는 다음과 같습니다:
- $eq (equals) → 값이 같은지 비교
- $gt (greater than) → 값이 초과인지 비교
- $gte (greater than or equal to) → 값이 이상인지 비교
- $lt (less than) → 값이 미만인지 비교
- $lte (less than or equal to) → 값이 이하인지 비교
- $ne (not equal) → 값이 다른지 비교
아래는 $gt 연산자를 사용한 예제입니다:
# $gt 연산자를 사용하여 price 값이 50보다 큰 문서 검색
query = {
'price': {
"$gt": 50
}
}
# 쿼리에 해당하는 문서 조회
result = books.find(query)
# 조회된 문서 출력
for document in result:
pprint(document)
결과:
이 스크립트를 실행하면 price 값이 50보다 큰 문서가 검색됩니다.
출력된 문서는 총 세 개이며, 두 개는 "Python and MongoDB"라는 제목을 가지고 있고, 가격이 $60입니다.
그러나 두 문서는 서로 다른 ISBN 및 메타데이터를 가집니다.
{
'_id': ObjectId('6521ae0a0e542d46b87947a4'),
'isbn': '301',
'name': 'Python and MongoDB',
'meta': { 'version': 'MongoDB 7.0' },
'price': 60
}
{
'_id': ObjectId('6521c1a0379c18f8df180e57'),
'isbn': '302',
'meta': { 'version': 'MongoDB 7.0' },
'name': 'Python and MongoDB',
'price': 60
}
{
'_id': ObjectId('6521d7e309731cfd8fa0ca04'),
'isbn': '303',
'name': 'Advanced MongoDB Techniques',
'price': 70
}
이제 논리적 AND 조건을 사용하여 여러 개의 딕셔너리를 쿼리 결과에 추가해 보겠습니다:
# 논리적 AND 조건을 사용한 쿼리 정의
query = {
'name': 'Advanced MongoDB Techniques',
'price': 70
}
# 쿼리에 해당하는 문서 조회
result = books.find(query)
# 조회된 문서 출력
for document in result:
pprint(document)
결과:
{
'_id': ObjectId('6521d7e309731cfd8fa0ca04'),
'isbn': '303',
'name': 'Advanced MongoDB Techniques',
'price': 70
}
이제 $or 연산자를 사용하여, 책 이름이 Advanced MongoDB Techniques 이거나 ISBN이 301인 문서를 조회해봅시다:
# $or 조건으로 쿼리를 정의
query = {
"$or": [
{'name': 'Advanced MongoDB Techniques'},
{'isbn': '301'}
]
}
# 쿼리에 일치하는 문서 가져오기
result = books.find(query)
# 가져온 문서 출력
for document in result:
pprint(document)
결과:
The result will appear as follows:
{'_id': ObjectId('6521ae0a0e542d46b87947a4'),
'isbn': '301',
'name': 'Python and MongoDB',
'meta': {'version': 'MongoDB 7.0'},
'price': 60}
{'_id': ObjectId('6521d7e309731cfd8fa0ca04'),
'isbn': '303',
'name': 'Advanced MongoDB Techniques',
'price': 70}
문서 업데이트 (Updating Documents)
아래 코드 블록에서는 update_one 헬퍼 메서드를 사용하여 단일 문서를 업데이트하는 예제를 보여줍니다.
이 연산은 검색 단계에서 하나의 문서를 찾고, 해당 문서에 변경 사항을 적용합니다.
# "Advanced MongoDB Techniques" 책의 가격을 업데이트
update_result = books.update_one(
{"name": "Advanced MongoDB Techniques"},
{"$set": {"price": 75}} # 가격을 75로 변경
)
# 업데이트 결과 출력
pprint(update_result.raw_result)
# 변경된 문서를 가져와서 확인
updated_document = books.find_one({"name": "Advanced MongoDB Techniques"})
pprint(updated_document)
설명:
- "name": "Advanced MongoDB Techniques"인 문서를 찾아 "price": 75로 업데이트합니다.
- update_one() 메서드는 첫 번째로 찾은 문서 하나만 변경합니다.
- 변경 후, find_one()을 사용해 업데이트된 문서를 가져와 출력합니다.
결과:
{
'n': 1,
'nModified': 1,
'ok': 1.0,
'updatedExisting': True
}
{
'_id': ObjectId('6521d7e309731cfd8fa0ca04'),
'isbn': '303',
'name': 'Advanced MongoDB Techniques',
'price': 75
}
설명:
- n: 1 → 하나의 문서가 검색됨
- nModified: 1 → 하나의 문서가 수정됨
- ok: 1.0 → 작업이 정상적으로 수행됨
- updatedExisting: True → 기존 문서가 업데이트됨
문서 삭제 (Deleting Documents)
PyMongo에서 delete_one 메서드는 특정 필터 조건을 기반으로 단일 문서를 삭제하는 기능을 제공합니다.
만약 여러 개의 문서를 삭제해야 한다면, delete_many 메서드를 사용할 수 있습니다.
이는 특정 레코드나 그룹을 제거할 때 특히 유용합니다.
단일 문서 삭제 예제 (delete_one)
# 삭제할 책의 ISBN 번호
isbn_to_delete = '303'
# 해당 ISBN을 가진 책 삭제
delete_result = books.delete_one({"isbn": isbn_to_delete})
# 삭제 결과 출력
pprint(delete_result.raw_result)
실행 결과:
{
'n': 1,
'ok': 1.0
}
설명:
- 'n': 1 → 1개의 문서가 삭제됨
- 'ok': 1.0 → 작업이 정상적으로 수행됨
정규 표현식 (Regular Expressions) 사용하기
MongoDB에서 데이터를 조회할 때 정규 표현식(Regular Expressions, regex)을 활용할 수 있습니다.
이를 통해 특정 패턴을 포함하는 데이터를 손쉽게 검색할 수 있습니다.
db.books.find({"name": /mongo/})
위 코드는 "mongo"라는 단어가 포함된 책을 검색하는 예제입니다.
SQL의 LIKE 연산자와 유사한 역할을 합니다.
참고: MongoDB는 Perl Compatible Regular Expressions (PCRE) 8.42 버전을 사용하며, UTF-8을 지원합니다.
| 옵션 | 설명 |
| i | 대소문자 구분 없이 검색 (case-insensitive) |
| m | 여러 줄 검색 (multiline) - 시작(^)/끝($) 패턴 일치 |
Table 5.3: Query options for regular expressions
이전 예제에서 mongo, Mongo, MONGO 또는 그 외 대소문자 구분 없이 검색하고 싶다면, 다음과 같이 i 옵션을 사용해야 합니다:
db.books.find({"name": /mongo/i})
또는 더 유연하게 사용할 수 있는 $regex 연산자를 사용할 수도 있습니다. 같은 쿼리를 $regex를 이용하면 다음과 같이 작성할 수 있습니다:
db.books.find({'name': { '$regex': /mongo/ }})
db.books.find({'name': { '$regex': /mongo/i }})
$regex 연산자를 사용할 때는 다음 표와 같은 옵션도 사용할 수 있습니다:
| 옵션 | 설명 |
| x | 이 옵션은 $regex 패턴에서 모든 공백 문자를 무시합니다. 또한, 이스케이프되지 않은 해시(또는 파운드) 문자(#)와 다음 줄바꿈 사이의 모든 내용을 무시합니다. 주석을 포함할 때 사용할 수 있습니다. 문자 클래스 안이나 이스케이프된 경우에는 효과가 없습니다. VT 문자를 처리하는 데는 영향을 주지 않습니다. |
| s | 이 옵션은 점 문자(.)가 줄바꿈 문자를 포함한 모든 문자와 일치하도록 합니다. |
Table 5.4: $regex 연산자용 쿼리 옵션
$regex를 사용하여 일치하는 문서를 확장하면 쿼리 실행 속도가 느려질 수 있습니다.
정규식을 사용하는 인덱스는 인덱스된 문자열의 시작 부분을 쿼리할 때만 사용할 수 있습니다. 즉, ^ 또는 \A로 시작하는 정규식만 인덱스를 사용할 수 있습니다.
정규식을 이용한 문자열 쿼리만 원할 경우, 같은 문자열과 일치하더라도 너무 긴 정규식은 피하는 것이 좋습니다.
다음 코드 블록을 예로 들 수 있습니다:
db.books.find({ 'name': { '$regex': /mongo/ } });
db.books.find({ 'name': { '$regex': /^mongo.*/ } });
두 쿼리 모두 mongo로 시작하는 name 값을 (대소문자 구분하여) 일치시키지만, 첫 번째 쿼리가 더 빠릅니다. 이는 각 name 값에서 여섯 번째 문자에 도달하면 일치 여부 판단을 멈추기 때문입니다.
관리 (Administration)
MongoDB는 비관계형(non-relational) 및 스키마 없는(schema-free) 설계 덕분에 손쉽게 통합할 수 있습니다.
스키마 마이그레이션(schema migration)이 필요하지 않아 데이터베이스 운영이 간소화되며, 데이터베이스 관리 부담을 줄일 수 있습니다. 그러나 MongoDB의 성능을 최적화하려면 경험 많은 개발자 또는 아키텍트가 여러 작업을 수행해야 합니다.
MongoDB 관리 작업은 4가지 주요 수준으로 나눌 수 있습니다:
- 프로세스(Process) 수준
- 데이터베이스(Database) 수준
- 컬렉션(Collection) 수준
- 인덱스(Index) 수준
프로세스(Process) 수준
- shutDown → MongoDB 서버 종료
데이터베이스(Database) 수준
- dropDatabase → 데이터베이스 삭제
- listCollections → 현재 데이터베이스의 컬렉션 목록 조회
컬렉션(Collection) 수준
- drop → 컬렉션 삭제
- create → 새로운 컬렉션 생성
- renameCollection → 컬렉션 이름 변경
- cloneCollectionAsCapped → 컬렉션을 새로운 Capped Collection으로 복제
- convertToCapped → 일반 컬렉션을 Capped Collection으로 변환
단, 샤딩된(Sharded) 클러스터에서는 convertToCapped 명령어를 지원하지 않음 - compact → WiredTiger 데이터베이스에서 불필요한 디스크 공간을 운영체제에 반환
인덱스(Index) 수준
- createIndexes → 현재 컬렉션에 새로운 인덱스 생성
- listIndexes → 현재 컬렉션의 기존 인덱스 목록 조회
- dropIndexes → 현재 컬렉션에서 모든 인덱스 삭제
- reIndex → 현재 컬렉션의 인덱스를 삭제 후 재생성
- createSearchIndexes → 특정 컬렉션에서 하나 이상의 Atlas Search 인덱스 생성
- dropSearchIndex → 기존 Atlas Search 인덱스 삭제
- updateSearchIndex → 기존 Atlas Search 인덱스 업데이트
다음 섹션에서는 관리 측면에서 더 중요한 몇 가지 명령어에 대해 다룹니다.
currentOp() 및 killOp()
db.currentOp()는 현재 실행 중인 작업들에 대한 정보를 담고 있는 문서를 반환합니다. 이 문서는 해당 mongod 인스턴스에서 진행 중인 작업들과 그 작업의 ID를 포함합니다.
killOp()는 지정된 작업 ID에 따라 특정 작업을 강제로 종료합니다.
이 명령은 매우 신중하게 사용해야 하며, 클라이언트가 시작한 작업에만 사용해야 합니다. 내부 데이터베이스 작업은 종료하면 안 됩니다.
killOp() 명령어 사용 예시는 다음과 같습니다:
db.adminCommand({
killOp: 1,
op: <opid>,
comment: <임의의 코멘트>
})
collMod
collMod는 컬렉션에 옵션을 추가하거나 뷰(view) 정의를 수정할 수 있게 해줍니다. 예를 들어, 기존 인덱스의 속성을 변경하는 인덱스 옵션을 추가하거나, 컬렉션에 대해 유효성 검사를 수행할 수 있는 검증 규칙(validator) 을 설정할 수 있습니다. 또한 Capped 컬렉션의 크기를 조절하거나 시계열 컬렉션(time series collection) 을 수정하는 것도 가능합니다.
예를 들어, 문서 유효성 검사(document validation) 옵션을 사용하면, 컬렉션에 삽입되거나 업데이트되는 문서에 적용될 규칙을 지정할 수 있습니다. 기존 문서가 수정될 때 이 규칙에 따라 유효성 검사가 수행됩니다.
- 기존 문서에 대해 유효성 검사를 적용하려면 validationLevel을 "moderate"로 설정해야 합니다.
- validationAction을 설정하면 유효하지 않은 문서에 대해 어떤 조치를 취할지를 정의할 수 있습니다:
- "warn": 유효하지 않은 문서를 로그로 남김
- "error": 유효하지 않은 문서의 삽입 또는 수정 자체를 차단
예를 들어, bookOrders 컬렉션에서 isbn과 name 필드가 반드시 존재해야 한다는 유효성 검사(Validator) 를 설정할 수 있습니다.
유효성 검사 설정 예제
db.runCommand({
collMod: "bookOrders",
validator: {
$and: [
{ "isbn": { $exists: true } },
{ "name": { $exists: true } }
]
}
});
위 설정을 적용하면 isbn과 name 필드가 반드시 존재해야 함
유효성 검사 실패 예제
db.bookOrders.insertOne({ isbn: 102 })
name 필드가 없으므로 유효성 검사 오류 발생
오류 메시지 예시
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId("6521162942094e44892aa6f6"),
details: {
operatorName: '$and',
clausesNotSatisfied: [
{
index: 1,
details: {
operatorName: '$exists',
specifiedAs: { name: { '$exists': true } },
reason: 'path does not exist'
}
}
]
}
}
name 필드가 존재하지 않기 때문에 "path does not exist" 오류 발생
이 오류를 해결하려면 name 필드를 포함한 문서를 삽입해야 함
이것은 유효성 검사(Validation)에 실패했기 때문입니다.
쉘에서 유효성 검사를 관리하면 스크립트를 작성하여 자동화할 수 있고, 데이터의 무결성을 유지하는 데 유용합니다.
MongoDB 보안 설정
데이터베이스에 저장되는 데이터의 양과 민감도가 증가함에 따라 보안의 중요성이 커지고 있습니다.
MongoDB는 무단 액세스 및 보안 침해를 방지하기 위해 다양한 보안 기능을 제공합니다.
MongoDB의 보안 기능에는 다음과 같은 요소가 포함됩니다.
- 인증(Authentication) – 클라이언트의 신원을 확인
- 액세스 제어(Authorization) – 사용자가 수행할 수 있는 작업 결정
- 데이터 암호화(Encryption) – 데이터 보호
MongoDB의 보안 원칙을 이해하는 것은 안전한 데이터베이스 운영에 필수적입니다.
인증(Authentication)과 권한 부여(Authorization)
인증(Authentication): 사용자의 신원을 확인하는 과정
권한 부여(Authorization): 사용자가 데이터베이스에서 수행할 수 있는 작업을 결정하는 과정
MongoDB에서 액세스 제어(Authorization) 가 활성화되면, 모든 클라이언트는 인증을 거쳐야만 데이터베이스에 접근할 수 있습니다.
이제 MongoDB에서 인증 및 권한 부여 설정 방법과 보안 강화 방법을 살펴보겠습니다.
MongoDB 권한 부여(Authorization) 설정
MongoDB에서 가장 기본적인 권한 부여 방식은 사용자 이름/비밀번호 방식입니다.
이를 활성화하려면 MongoDB 서버를 -auth 옵션과 함께 실행해야 합니다.
mongod --auth
이 명령어를 실행하면 모든 클라이언트는 인증을 거쳐야만 데이터베이스에 접근 가능합니다.
로컬호스트 예외(Localhost Exception)
MongoDB는 최초 사용자 또는 역할(Role) 을 설정할 수 있도록 로컬호스트 예외(Localhost Exception) 기능을 제공합니다.
로컬호스트 예외란?
- 액세스 제어가 활성화된 상태에서도, 초기 사용자 또는 역할을 설정할 수 있도록 허용하는 기능
- MongoDB 환경에서 사용자나 역할이 아직 정의되지 않은 경우에만 적용
- mongod 인스턴스가 실행 중일 때, localhost(127.0.0.1)에서 접속하면 최초 관리자 계정을 생성할 수 있음
MongoDB의 보안을 강화하려면 인증(Authentication) 및 액세스 제어(Authorization)를 적절히 설정하는 것이 필수적입니다.
최초의 관리자(admin) 사용자를 생성하는 방법은 다음과 같습니다.
use admin
db.createUser(
{
user: <adminUser>,
pwd: passwordPrompt(), // 또는 "<평문 비밀번호>"
roles: [ { role: <adminRole>, db: "admin" } ]
}
)
여기서
- <adminUser>: 생성할 관리자 사용자 이름
- <cleartext password>: 비밀번호
- <adminRole>: 사용자의 역할(role)
관리자 역할(Role) 목록
MongoDB의 자체 호스팅(Self-hosted) 배포 환경에서 사용할 수 있는 역할(role)은 다음과 같습니다.
(가장 강력한 권한부터 낮은 권한 순으로 정렬됨)
- root
- dbAdminAnyDatabase
- userAdminAnyDatabase
- readWriteAnyDatabase
- readAnyDatabase
- dbOwner
- dbAdmin
- clusterAdmin
- clusterManager
- clusterMonitor
- hostManager
- backup
- restore
- userAdmin
- readWrite
- read
MongoDB Atlas에는 자체적으로 제공하는 추가적인 역할이 있으며, 대표적인 예로 atlasAdmin 역할이 있습니다.
주의: root 역할은 모든 권한을 가진 슈퍼유저(superuser) 이며, 특수한 경우를 제외하고 사용을 권장하지 않습니다.
AnyDatabase 역할의 의미
AnyDatabase 역할이 포함된 모든 권한은 모든 데이터베이스에 대한 접근 권한을 제공합니다.
특히, dbAdminAnyDatabase 역할은
- userAdminAnyDatabase (사용자 관리)
- readWriteAnyDatabase (읽기/쓰기)
이 두 가지 역할을 결합한 전체 데이터베이스 관리자 역할을 수행합니다.
특정 데이터베이스에 역할 부여
나머지 역할(role)은 특정 데이터베이스에서만 적용됩니다.
예를 들어, mongodb_book 데이터베이스에서 dbAdmin 역할을 가진 사용자를 생성하려면 다음과 같이 실행할 수 있습니다.
use admin
db.createUser(
{
user: "adminUser", // 사용자 이름
pwd: "passwordPrompt()", // 또는 "<평문 비밀번호>"를 입력할 수 있음
roles: [
{
role: "dbAdmin", // 부여할 역할
db: "mongodb_book" // 역할이 적용될 데이터베이스
}
]
}
)
인증(Authentication)과 권한(Authorization)
MongoDB의 운영 환경(Production)에서는 인증 및 권한 설정이 필수적입니다.
더 깊이 있는 보안 관련 내용을 알고 싶다면 15장 "보안(Security)"을 참고하세요.
이 장에서는 MongoDB 배포 환경을 안전하게 보호하는 방법을 설명합니다.
MongoDB Stable API
MongoDB 5.0부터 Stable API가 도입되었습니다.
Stable API는 클라이언트-서버 간의 통신이 MongoDB 버전 업데이트에 영향을 받지 않도록 보장하는 기능입니다.
Stable API를 사용할 때는 드라이버(driver) 또는 mongosh(쉘) 에서 API 버전을 선언해야 합니다.
다음은 mongosh에서 API 버전을 선언하는 예제입니다.
mongosh --apiVersion 1
참고: MongoDB 7.0 기준으로 현재 사용 가능한 API 버전은 1뿐입니다.
Stable API 보장 조건
Stable API를 사용하면 MongoDB 서버를 업그레이드해도 애플리케이션이 정상적으로 동작할 수 있습니다.
하지만 아래 3가지 조건이 충족되어야 합니다.
- 클라이언트에서 apiVersion을 선언해야 합니다.
- 공식 MongoDB 클라이언트의 지원되는 버전을 사용해야 합니다.
- 해당 API 버전에서 지원되는 명령어 및 기능만 사용해야 합니다.
이 조건을 충족하면, MongoDB 서버를 업그레이드하더라도 애플리케이션 동작에 문제가 발생하지 않습니다.
세 번째 제약 조건에 따라, apiVersion='1'에서는 다음 명령어를 사용할 수 있습니다.
| 명령어 | Stable API 버전 | Stable API에 추가된 MongoDB 버전 |
| count | V1 | MongoDB 6.0, 5.0.9 |
| abortTransaction | V1 | MongoDB 5.0 |
| aggregate (제한사항 포함) | V1 | MongoDB 5.0 |
| authenticate | V1 | MongoDB 5.0 |
| collMod | V1 | MongoDB 5.0 |
| commitTransaction | V1 | MongoDB 5.0 |
| create (제한사항 포함) | V1 | MongoDB 5.0 |
| createIndex (제한사항 포함) | V1 | MongoDB 5.0 |
| delete | V1 | MongoDB 5.0 |
| drop | V1 | MongoDB 5.0 |
| dropDatabase | V1 | MongoDB 5.0 |
| dropIndex | V1 | MongoDB 5.0 |
| endSession | V1 | MongoDB 5.0 |
| explain | V1 | MongoDB 5.0 |
| find (제한사항 포함) | V1 | MongoDB 5.0 |
| findAndModify | V1 | MongoDB 5.0 |
| getMore | V1 | MongoDB 5.0 |
| insert | V1 | MongoDB 5.0 |
| hello | V1 | MongoDB 5.0 |
| killCursors | V1 | MongoDB 5.0 |
| listCollections | V1 | MongoDB 5.0 |
| listDatabases | V1 | MongoDB 5.0 |
| listIndexes | V1 | MongoDB 5.0 |
| ping | V1 | MongoDB 5.0 |
| refreshSessions | V1 | MongoDB 5.0 |
| update | V1 | MongoDB 5.0 |
Table 5.5: Stable API 명령어 및 해당 MongoDB 버전
위 Table 5.5에 표시된 명령어 중 (제한사항 포함)이라는 주석이 있는 경우, Stable API 보장 범위에서 부분적으로만 지원됩니다.
또한 apiStrict 부울 플래그를 True로 설정하면 허용된 명령어 목록(whitelist)에 없는 명령어를 사용할 수 없도록 제한할 수 있습니다.
이 경우 MongoDB는 apiStrictError를 반환합니다.
참고: apiStrict의 기본값은 False입니다.
요약(Summary)
이 장에서는 CRUD 작업의 기본 개념을 다루었습니다. mongosh를 사용하여 문서를 삽입, 삭제, 조회 및 수정하는 방법을 배우고,
단일 삽입과 배치 삽입의 성능 차이를 이해했습니다. 또한 관리 작업과 mongosh에서 이를 수행하는 방법을 살펴보았습니다.
마지막으로, MongoDB의 보안 및 인증, 새로운 버전 관리 방식, 그리고 새로운 셸인 mongosh에 대해 간략히 다루었습니다.
다음 장에서는 MongoDB의 스키마 설계 원칙과 효과적인 데이터 표현 기술을 탐구하여 성능 및 확장성을 최적화하는 방법을 배웁니다.
'T. > Mastering MongoDB 7.0' 카테고리의 다른 글
| 6. Schema Design and Data Modeling (0) | 2025.03.10 |
|---|---|
| 4. Connecting to MongoDB (0) | 2025.03.10 |
| 3. Developer Tools (0) | 2025.03.10 |
| 2. The MongoDB Architecture (0) | 2025.03.10 |
| 1. Introduction to MongoDB (2) | 2025.03.10 |