1. 관리자 계정 생성 및 인증 활성화
--먼저 mongos1 컨테이너 내부로 접속
--클러스터 관리자 (Cluster Admin) 계정 생성
# docker exec -it mongos1 bash
$ mongosh "mongodb://admin@localhost:27017/?tls=true&tlsCAFile=/etc/ssl/mongodb/ca_sharded.pem&tlsCertificateKeyFile=/etc/ssl/mongodb/mongodb_sharded.pem"
% use admin
% db.createUser(
{
user: "clusterAdmin",
pwd: passwordPrompt(), // 보안을 위해 프롬프트 사용
roles: [ { role: "clusterAdmin", db: "admin" }, { role: "readAnyDatabase", db: "admin" } ]
}
)
/* passwordPrompt() : 패스워드를 입력받아서 생성
Enter password
****
*/
/*
--잘못 만들어진 사용자 삭제
--현재 DB에 있는 사용자ID 조회
% db.getUsers().users.forEach(function(u) { print(u.user); })
--clusterAdmin ID 삭제
% db.runCommand({ dropUser: "clusterAdmin" })
or % db.dropUser("clusterAdmin")
*/
--애플리케이션 사용자 (Application User) 계정 생성
% use myappdb // 애플리케이션에서 사용할 데이터베이스 (아직 없으면 생성됨)
% db.createUser(
{
user: "appUser",
pwd: passwordPrompt(),
roles: [ { role: "readWrite", db: "myappdb" } ]
}
)
2. 샤딩할 데이터베이스 및 컬렉션 준비
--샤딩할 데이터베이스 활성화
% sh.enableSharding("myappdb")
/*
. 아무 database에서 수행해도 상관 없음
*/
--샤딩 활성화 확인#1
% sh.status()
...
{
database: {
_id: 'myappdb',
primary: 'shard2RS',
version: {
uuid: UUID('c4cf4483-6a2c-4315-bc59-0fdaeee267b3'),
timestamp: Timestamp({ t: 1748311149, i: 3 }),
lastMod: 1
}
},
collections: {} //-- myappdb 데이터베이스에 아직 샤딩된 컬렉션이 없다
}
...
--샤드 키 선택 및 컬렉션 샤딩 (매우 중요!)
% use myappdb
% sh.shardCollection("myappdb.mycollection", { userId: "hashed" })
/*
. 샤딩 키를 데이터 삽입 전에 설정해야 하는 이유
. 성능 최적화 :
. 샤딩 키를 먼저 설정하면 데이터가 삽입되는 동시에 자동으로 샤드 간 분산됩니다.
. 사후에 설정하면 기존 데이터를 재분배하는 balancer 작업이 발생하며, 이는 시간과 리소스를 많이 소모합니다.
. 청크(chunk) 관리 효율성:
. 초기부터 청크가 고르게 분할되어 샤드 간 부하가 균형 있게 분산됩니다.
. % rs.status()
...
{
database: {
_id: 'myappdb',
primary: 'shard2RS',
version: {
uuid: UUID('c4cf4483-6a2c-4315-bc59-0fdaeee267b3'),
timestamp: Timestamp({ t: 1748311149, i: 3 }),
lastMod: 1
}
},
collections: { // 샤딩키 설정 후 추가된 부분
'myappdb.mycollection': {
shardKey: { userId: 'hashed' },
unique: false,
balancing: true,
chunkMetadata: [
{ shard: 'shard1RS', nChunks: 1 },
{ shard: 'shard2RS', nChunks: 1 }
],
chunks: [
{ min: { userId: MinKey() }, max: { userId: Long('0') }, 'on shard': 'shard2RS', 'last modified': Timestamp({ t: 1, i: 0 }) },
{ min: { userId: Long('0') }, max: { userId: MaxKey() }, 'on shard': 'shard1RS', 'last modified': Timestamp({ t: 1, i: 1 }) }
],
tags: []
}
}
}
...
*/
3. 데이터 삽입 및 확인
% const batchSize = 10000 ; // 한 번에 넣을 건수, 명령문의 끝을 명확하게 구분하기 위해 ";"를 붙임
const totalCount = 1000000 ; // 총 데이터 건수
const t0 = new Date(); // 시작 시간 기록
for (let i = 0; i < totalCount; i += batchSize) {
let bulk = [];
for (let j = i; j < i + batchSize && j < totalCount; j++) {
bulk.push({
userId: j,
name: "user" + j,
value: Math.random() * 1000,
create_dt: new Date(),
update_dt: null
});
}
// db.mycollection.insertMany(bulk);
db.mycollection.insertMany(bulk, { ordered: false });
// 진행상황 출력
print(`Inserted ${i + bulk.length} / ${totalCount} (${Math.round(((i + bulk.length) / totalCount) * 100)}%)`);
}
const t1 = new Date(); // 종료 시간 기록
const elapsedSec = (t1 - t0) / 1000;
print(`총 소요시간: ${elapsedSec}초 (${(elapsedSec/60).toFixed(2)}분)`);
/*
. truncate하는 명령은 없음
. ordered: false
. 여러 샤드에 동시 쓰기가 가능
. 네트워크 왕복 시간을 줄이고 샤드 간 부하 분산이 효율적
*/
--건수 확인
% db.mycollection.countDocuments()
--샤딩 활성화 확인#2
% sh.status()
...
{
database: {
_id: 'myappdb',
primary: 'shard2RS',
version: {
uuid: UUID('c4cf4483-6a2c-4315-bc59-0fdaeee267b3'),
timestamp: Timestamp({ t: 1748311149, i: 3 }),
lastMod: 1
}
},
collections: {
'myappdb.mycollection': {
shardKey: { userId: 'hashed' },
unique: false,
balancing: true,
chunkMetadata: [
{ shard: 'shard1RS', nChunks: 1 },
{ shard: 'shard2RS', nChunks: 1 }
],
chunks: [
{ min: { userId: MinKey() }, max: { userId: Long('0') }, 'on shard': 'shard2RS', 'last modified': Timestamp({ t: 1, i: 0 }) },
{ min: { userId: Long('0') }, max: { userId: MaxKey() }, 'on shard': 'shard1RS', 'last modified': Timestamp({ t: 1, i: 1 }) }
],
tags: []
}
}
}
...
/*
. 컬렉션 DROP
% use myappdb
% db.mycollection.drop()
% db.runCommand({ compact: "mycollection", force: true }) // 데이터 삭제 후 디스크 용량 회수
*/
--추가 인덱스 생성
% db.mycollection.createIndex(
{ create_dt: 1 },
{ name: "ix_create_dt_01" }
)
--인덱스 목록 확인
% use myappdb
% db.mycollection.getIndexes()
% db.runCommand({ listIndexes: "mycollection" }) // 자세한 정보가 필요할 때
--데이터가 각 샤드에 어떻게 분산되었는지 확인
% db.mycollection.getShardDistribution()
--컬렉션 별 사이즈
% db.getCollectionNames().forEach(function(coll) {
var sizeMB = db.getCollection(coll).stats(1024*1024).size;
// print(coll + " : " + Math.round(sizeMB) + " MB"); // mycollection : 95 MB
print(coll + " : " + sizeMB.toFixed(2) + " MB"); // mycollection : 95.26 MB
});
4. 밸런서(Balancer) 작동 확인 및 관리
--방법1 : sh.status()에서 Balancer 상태 확인
% sh.status()
...
balancer
{
'Currently enabled': 'yes', // 밸런서가 활성화(ON) 상태
'Failed balancer rounds in last 5 attempts': 0,
'Currently running': 'no', // 현재 밸런서가 실제로 동작 중인지(청크 이동 등) 여부
'Migration Results for the last 24 hours': 'No recent migrations'
}
...
--방법2 : balancer 상태만 따로 확인
% sh.getBalancerState() // true : 활성화, false : 비활성화
--관리
/*
. 청크 이동 시 네트워크 및 I/O 리소스를 사용하므로, 부하가 적은 시간대에만 작동하도록 설정
*/
--밸런서 즉시 중지/시작
% sh.setBalancerState(false)
% sh.status()
...
balancer
{
'Currently enabled': 'no',
'Currently running': 'no',
'Balancer active window is set between': '01:00 and 04:00 server local time',
'Failed balancer rounds in last 5 attempts': 0,
'Migration Results for the last 24 hours': 'No recent migrations'
}
...
% sh.setBalancerState(true)
% sh.status()
...
balancer
{
'Currently enabled': 'yes',
'Balancer active window is set between': '01:00 and 04:00 server local time',
'Currently running': 'no',
'Failed balancer rounds in last 5 attempts': 0,
'Migration Results for the last 24 hours': 'No recent migrations'
}
...
--트래픽이 적은 시간대(예: 새벽 1시~4시)에만 밸런서가 동작하도록 설정
% use config
% db.settings.find({ _id: "balancer" }) // 설정 확인
% db.settings.updateOne(
{ _id: "balancer" },
{ $set: { activeWindow: { start: "01:00", stop: "04:00" } } },
{ upsert: true }
)
% db.settings.find({ _id: "balancer" }) // 설정 확인
[
{ _id: 'balancer', activeWindow: { start: '01:00', stop: '04:00' } }
]
5. 모니터링 및 로깅 설정
(ing)
6. 백업 및 복구 전략 수립
(ing)