Sharding Cluster 만들기(6) : 구성 후 초기 세팅

arky uhm·2025년 5월 28일

MongoDB

목록 보기
13/14

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)


0개의 댓글