MongoDB 샤딩: 단계별 실용 가이드

샤딩은 대량의 데이터 세트를 분산된 환경에서 여러 MongoDB 인스턴스로 나누어 관리하는 방식입니다. 이렇게 분할된 데이터는 더 작은 단위로 취급됩니다.

샤딩이란 무엇인가?

MongoDB 샤딩은 단순히 데이터를 하나의 서버에 저장하는 것을 넘어, 여러 서버에 데이터를 분산시켜 저장함으로써 확장성을 높이는 해결책을 제시합니다.

데이터 양이 기하급수적으로 증가하는 현실에서, 모든 데이터를 단일 시스템에 저장하는 것은 비효율적입니다. 엄청난 양의 데이터를 단일 서버에서 처리하려고 하면 리소스 사용량이 급증하고, 읽기 및 쓰기 처리 속도가 저하될 수 있습니다.

데이터 증가에 대응하기 위한 확장 방법에는 크게 두 가지 유형이 있습니다.

수직적 확장은 단일 서버의 성능을 높이는 방법입니다. 예를 들어 더 강력한 프로세서를 사용하거나, RAM을 늘리거나, 더 많은 디스크 공간을 추가하는 것이 이에 해당합니다. 하지만 기존 기술 및 하드웨어 구성으로는 수직적 확장에 한계가 있을 수 있습니다.

수평적 확장은 여러 서버를 추가하고, 데이터를 분산 처리하는 방식입니다. 각 서버는 전체 데이터 세트의 일부분만 처리하므로, 고가의 하드웨어를 사용하는 것보다 더 효율적이고 비용 효율적인 해결책이 될 수 있습니다. 하지만 수많은 서버를 관리해야 하는 복잡성이 따릅니다.

MongoDB 샤딩은 바로 이 수평적 확장 기법을 활용합니다.

샤딩 구성 요소

MongoDB에서 샤딩을 구현하려면 다음과 같은 요소들이 필요합니다.

샤드(Shard)는 원본 데이터의 일부분을 처리하는 MongoDB 인스턴스입니다. 샤드는 복제 세트에 배포해야 합니다.

몽고스(Mongos)는 클라이언트 애플리케이션과 샤딩된 클러스터 사이의 인터페이스 역할을 하는 MongoDB 인스턴스입니다. 샤드로 향하는 쿼리를 라우팅하는 역할을 합니다.

구성 서버(Config Server)는 클러스터의 메타데이터 및 구성 정보를 저장하는 MongoDB 인스턴스입니다. 구성 서버 역시 복제 세트로 배포해야 합니다.

샤딩 아키텍처

MongoDB 클러스터는 여러 복제 세트로 구성됩니다.

각 복제 세트는 최소 3개 이상의 몽고 인스턴스로 이루어집니다. 샤드 클러스터는 여러 개의 몽고 샤드 인스턴스로 구성될 수 있으며, 각 샤드 인스턴스는 샤드 복제 세트 내에서 작동합니다. 애플리케이션은 몽고스와 통신하여 샤드와 상호작용합니다. 즉, 샤딩 환경에서 애플리케이션은 샤드 노드와 직접 통신하지 않습니다. 쿼리 라우터는 샤드 키를 기반으로 데이터를 샤드 노드들에 분산시킵니다.

샤딩 구현

샤딩을 설정하려면 다음 단계를 따르십시오.

1단계

  • 복제 세트에서 구성 서버를 시작하고 복제를 활성화합니다.

mongod –configsvr –port 27019 –replSet rs0 –dbpath C:datadata1 –bind_ip 로컬호스트

mongod –configsvr –port 27018 –replSet rs0 –dbpath C:datadata2 –bind_ip 로컬호스트

mongod –configsvr –port 27017 –replSet rs0 –dbpath C:datadata3 –bind_ip 로컬호스트

2단계

  • 구성 서버 중 하나에서 복제 세트를 초기화합니다.

rs.initiate( { _id : “rs0”, configsvr: true, members: [ { _id: 0, host: “IP:27017” }, { _id: 1, host: “IP:27018” }, { _id: 2, host: “IP:27019” } ] })

rs.initiate( { _id : "rs0",  configsvr: true,  members: [  { _id: 0, host: "IP:27017" },  { _id: 1, host: "IP:27018" },  { _id: 2, host: "IP:27019" }   ] })
{
        "ok" : 1,
        "$gleStats" : {
                "lastOpTime" : Timestamp(1593569257, 1),
                "electionId" : ObjectId("000000000000000000000000")
        },
        "lastCommittedOpTime" : Timestamp(0, 0),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593569257, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        },
        "operationTime" : Timestamp(1593569257, 1)
}

3단계

  • 복제 세트에서 샤딩 서버를 시작하고 서버 간 복제를 활성화합니다.

mongod –shardsvr –port 27020 –replSet rs1 –dbpath C:datadata4 –bind_ip 로컬호스트

mongod –shardsvr –port 27021 –replSet rs1 –dbpath C:datadata5 –bind_ip 로컬호스트

mongod –shardsvr –port 27022 –replSet rs1 –dbpath C:datadata6 –bind_ip 로컬호스트

MongoDB는 첫 번째 샤딩 서버를 기본 서버로 초기화합니다. 필요한 경우 movePrimary 방법을 사용하여 기본 샤딩 서버를 이동할 수 있습니다.

4단계

  • 샤딩된 서버 중 하나에서 복제 세트를 초기화합니다.

rs.initiate( { _id : “rs0”, members: [ { _id: 0, host: “IP:27020” }, { _id: 1, host: “IP:27021” }, { _id: 2, host: “IP:27022” } ] })

rs.initiate( { _id : "rs0",  members: [  { _id: 0, host: "IP:27020" },  { _id: 1, host: "IP:27021" },  { _id: 2, host: "IP:27022" }   ] })
{
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593569748, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        },
        "operationTime" : Timestamp(1593569748, 1)
}

5단계

  • 샤딩된 클러스터에 대한 몽고스를 시작합니다.

mongos –port 40000 –configdb rs0/localhost:27019,localhost:27018,localhost:27017

6단계

  • 몽고스 라우트 서버에 연결합니다.

mongo –port 40000

  • 이제 샤딩 서버를 추가합니다.

sh.addShard( “rs1/localhost:27020,localhost:27021,localhost:27022”)

sh.addShard( "rs1/localhost:27020,localhost:27021,localhost:27022")
{
        "shardAdded" : "rs1",
        "ok" : 1,
        "operationTime" : Timestamp(1593570212, 2),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593570212, 2),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

7단계

  • 몽고 셸에서 데이터베이스 및 컬렉션에 샤딩을 활성화합니다.
  • 데이터베이스에서 샤딩을 활성화합니다.

sh.enableSharding(“geekFlareDB”)

sh.enableSharding("geekFlareDB")
{
        "ok" : 1,
        "operationTime" : Timestamp(1591630612, 1),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1591630612, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

8단계

  • 컬렉션 샤드 키(이 문서 뒷부분에서 설명)를 샤딩하려면 필요합니다.

구문: sh.shardCollection(“dbName.collectionName”, { “키” : 1 } )

sh.shardCollection("geekFlareDB.geekFlareCollection", { "key" : 1 } )
{
        "collectionsharded" : "geekFlareDB.geekFlareCollection",
        "collectionUUID" : UUID("0d024925-e46c-472a-bf1a-13a8967e97c1"),
        "ok" : 1,
        "operationTime" : Timestamp(1593570389, 3),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593570389, 3),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

컬렉션이 존재하지 않으면 다음과 같이 생성합니다.

db.createCollection("geekFlareCollection")
{
        "ok" : 1,
        "operationTime" : Timestamp(1593570344, 4),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593570344, 5),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

9단계

컬렉션에 데이터를 삽입합니다. 몽고 로그가 증가하기 시작하고 밸런서가 작동하면서 샤드 간에 데이터 균형을 맞추려고 시도하는 것을 볼 수 있습니다.

10단계

마지막 단계는 샤딩 상태를 확인하는 것입니다. 상태는 몽고스 경로 노드에서 아래 명령을 실행하여 확인할 수 있습니다.

샤딩 상태

몽고 경로 노드에서 아래 명령을 실행하여 샤딩 상태를 확인합니다.

sh.status()

mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "minCompatibleVersion" : 5,
        "currentVersion" : 6,
        "clusterId" : ObjectId("5ede66c22c3262378c706d21")
  }
  shards:
        {  "_id" : "rs1",  "host" : "rs1/localhost:27020,localhost:27021,localhost:27022",  "state" : 1 }
  active mongoses:
        "4.2.7" : 1
  autosplit:
        Currently enabled: yes
  balancer:
        Currently enabled:  yes
        Currently running:  no
        Failed balancer rounds in last 5 attempts:  5
        Last reported error:  Could not find host matching read preference { mode: "primary" } for set rs1
        Time of Reported error:  Tue Jun 09 2020 15:25:03 GMT+0530 (India Standard Time)
        Migration Results for the last 24 hours:
                No recent migrations
  databases:
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }
                config.system.sessions
                        shard key: { "_id" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs1     1024
                        too many chunks to print, use verbose if you want to force print
        {  "_id" : "geekFlareDB",  "primary" : "rs1",  "partitioned" : true,  "version" : {  "uuid" : UUID("a770da01-1900-401e-9f34-35ce595a5d54"),  "lastMod" : 1 } }
                geekFlareDB.geekFlareCol
                        shard key: { "key" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs1     1
                        { "key" : { "$minKey" : 1 } } -->> { "key" : { "$maxKey" : 1 } } on : rs1 Timestamp(1, 0)
                geekFlareDB.geekFlareCollection
                        shard key: { "product" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs1     1
                        { "product" : { "$minKey" : 1 } } -->> { "product" : { "$maxKey" : 1 } } on : rs1 Timestamp(1, 0)
        {  "_id" : "test",  "primary" : "rs1",  "partitioned" : false,  "version" : {  "uuid" : UUID("fbc00f03-b5b5-4d13-9d09-259d7fdb7289"),  "lastMod" : 1 } }

mongos>

데이터 배포

몽고스 라우터는 샤드 키를 기반으로 샤드 간에 부하를 분산시키고 데이터를 고르게 배포합니다. 이 과정에서 밸런서가 중요한 역할을 합니다.

샤드 간 데이터 배포의 핵심 구성 요소는 다음과 같습니다.

  • 밸런서는 샤딩된 노드 간에 데이터 하위 집합의 균형을 맞추는 역할을 합니다. 밸런서는 몽고스 서버가 샤드 간 부하 분산을 시작할 때 작동하기 시작하며, 데이터가 더 균등하게 분배되도록 합니다. 밸런서의 상태를 확인하려면 sh.status() 또는 sh.getBalancerState() 또는 sh.isBalancerRunning()을 실행합니다.
mongos> sh.isBalancerRunning()
true
mongos>

또는

mongos> sh.getBalancerState()
true
mongos>

데이터가 삽입되면 몽고스 데몬에서 특정 샤드로 청크를 이동하는 등 여러 활동이 발생하는 것을 볼 수 있습니다. 밸런서가 작동하면 성능 문제가 발생할 수 있으므로, 특정 시간대에만 밸런서를 실행하는 것이 좋습니다. 밸런서 창을 참조하세요.

mongos> sh.status()
--- Sharding Status ---
  sharding version: {
        "_id" : 1,
        "minCompatibleVersion" : 5,
        "currentVersion" : 6,
        "clusterId" : ObjectId("5efbeff98a8bbb2d27231674")
  }
  shards:
        {  "_id" : "rs1",  "host" : "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022",  "state" : 1 }
        {  "_id" : "rs2",  "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025",  "state" : 1 }
  active mongoses:
        "4.2.7" : 1
  autosplit:
        Currently enabled: yes
  balancer:
        Currently enabled:  yes
        Currently running:  yes
        Failed balancer rounds in last 5 attempts:  5
        Last reported error:  Could not find host matching read preference { mode: "primary" } for set rs2
        Time of Reported error:  Wed Jul 01 2020 14:39:59 GMT+0530 (India Standard Time)
        Migration Results for the last 24 hours:
                1024 : Success
  databases:
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }
                config.system.sessions
                        shard key: { "_id" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs2     1024
                        too many chunks to print, use verbose if you want to force print
        {  "_id" : "geekFlareDB",  "primary" : "rs2",  "partitioned" : true,  "version" : {  "uuid" : UUID("a8b8dc5c-85b0-4481-bda1-00e53f6f35cd"),  "lastMod" : 1 } }
                geekFlareDB.geekFlareCollection
                        shard key: { "key" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs2     1
                        { "key" : { "$minKey" : 1 } } -->> { "key" : { "$maxKey" : 1 } } on : rs2 Timestamp(1, 0)
        {  "_id" : "test",  "primary" : "rs2",  "partitioned" : false,  "version" : {  "uuid" : UUID("a28d7504-1596-460e-9e09-0bdc6450028f"),  "lastMod" : 1 } }

mongos>
  • 샤드 키는 샤드 컬렉션의 문서를 샤드 간에 분산시키는 데 사용되는 논리를 결정합니다. 샤드 키는 삽입할 컬렉션의 모든 문서에 있어야 하는 인덱스 필드이거나 인덱스 복합 필드일 수 있습니다. 데이터는 청크 단위로 분할되며, 각 청크는 범위 기반 샤드 키와 연결됩니다. 쿼리 라우터는 이 샤드 키를 사용하여 청크를 저장할 샤드를 결정합니다.

샤드 키를 선택할 때는 다음 다섯 가지 속성을 고려해야 합니다.

  • 카디널리티
  • 쓰기 배포
  • 읽기 배포
  • 타겟팅 읽기
  • 지역 읽기

이상적인 샤드 키는 MongoDB가 모든 샤드에 부하를 균등하게 분산하도록 도와줍니다. 따라서 적절한 샤드 키를 선택하는 것은 매우 중요합니다.

이미지: MongoDB 샤딩 구조

샤드 노드 제거

클러스터에서 샤드를 제거하기 전에, 모든 데이터가 나머지 샤드로 안전하게 마이그레이션되어야 합니다. MongoDB는 필요한 샤드 노드를 제거하기 전에 데이터를 다른 샤드 노드로 안전하게 이동시킵니다.

다음 명령을 실행하여 필요한 샤드를 제거하세요.

1단계

먼저 제거할 샤드의 호스트 이름을 알아야 합니다. 아래 명령은 클러스터에 있는 모든 샤드와 해당 상태를 보여줍니다.

db.adminCommand( { listShards: 1 } )

mongos> db.adminCommand( { listShards: 1 } )
{
        "shards" : [
                {
                        "_id" : "rs1",
                        "host" : "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022",
                        "state" : 1
                },
                {
                        "_id" : "rs2",
                        "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025",
                        "state" : 1
                }
        ],
        "ok" : 1,
        "operationTime" : Timestamp(1593572866, 15),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593572866, 15),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

2단계

아래 명령을 실행하여 클러스터에서 필요한 샤드를 제거합니다. 이 명령을 실행하면 밸런서는 제거될 샤드 노드에서 청크를 제거하고, 나머지 샤드 노드 간에 남아있는 청크를 분산합니다.

db.adminCommand( { removeShard: “shardedReplicaNodes” } )

mongos> db.adminCommand( { removeShard: "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022" } )
{
        "msg" : "draining started successfully",
        "state" : "started",
        "shard" : "rs1",
        "note" : "you need to drop or movePrimary these databases",
        "dbsToMove" : [ ],
        "ok" : 1,
        "operationTime" : Timestamp(1593572385, 2),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593572385, 2),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

3단계

제거 중인 샤드의 상태를 확인하려면 같은 명령을 다시 실행합니다.

db.adminCommand( { removeShard: “rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022” } )

데이터 드레이닝이 완료될 때까지 기다려야 합니다. msg 및 state 필드를 통해 데이터 드레이닝이 완료되었는지 확인할 수 있습니다.

 "msg" : "draining ongoing",
"state" : "ongoing",

sh.status() 명령으로도 상태를 확인할 수 있습니다. 제거된 샤드 노드는 출력에 표시되지 않지만, 드레이닝이 진행 중인 경우 샤드 노드의 드레이닝 상태는 true로 표시됩니다.

4단계

필요한 샤드가 완전히 제거될 때까지 위의 명령을 반복하여 드레이닝 상태를 확인합니다.
완료되면 명령의 출력에 완료 메시지와 상태가 표시됩니다.

 "msg" : "removeshard completed successfully",
"state" : "completed",
"shard" : "rs1",
"ok" : 1,

5단계

마지막으로 클러스터에 남아 있는 샤드를 확인해야 합니다. 상태를 확인하려면 sh.status() 또는 db.adminCommand( { listShards: 1 } )를 입력합니다.

mongos> db.adminCommand( { listShards: 1 } )
{
        "shards" : [
                {
                        "_id" : "rs2",
                        "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025",
                        "state" : 1
                }
        ],
        "ok" : 1,
        "operationTime" : Timestamp(1593575215, 3),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1593575215, 3),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

출력 결과에서 제거된 샤드가 샤드 목록에서 더 이상 존재하지 않는 것을 확인할 수 있습니다.

복제보다 샤딩의 장점

  • 복제에서는 기본 노드가 모든 쓰기 작업을 처리하는 반면, 보조 노드는 백업 복사본을 유지하거나 읽기 전용 작업을 제공하는 데 사용됩니다. 샤딩을 복제 세트와 함께 사용하면 부하가 여러 서버에 분산됩니다.
  • 단일 복제 세트는 12개의 노드로 제한되지만, 샤드 수에는 제한이 없습니다.
  • 복제에서는 대규모 데이터 세트를 처리하기 위해 고급 하드웨어나 수직적 확장이 필요하지만, 샤딩은 서버를 추가하는 것으로 더 비용 효율적인 확장이 가능합니다.
  • 복제에서는 읽기 성능을 향상시키기 위해 슬레이브/보조 서버를 추가할 수 있지만, 샤딩에서는 더 많은 샤드 노드를 추가하여 읽기 및 쓰기 성능을 모두 향상시킬 수 있습니다.

샤딩의 제약 조건

  • 샤드 클러스터는 고유 인덱스가 전체 샤드 키 접두사를 포함할 때만 샤드에서 고유 인덱싱을 지원합니다.
  • 하나 이상의 문서로 분할된 컬렉션에 대한 모든 업데이트 작업은 쿼리에 분할된 키 또는 _id 필드를 포함해야 합니다.
  • 컬렉션 크기가 지정된 임계값을 초과하지 않으면 컬렉션을 샤딩할 수 없습니다. 이 임계값은 샤드 키의 평균 크기와 구성된 청크 크기를 기반으로 추정할 수 있습니다.
  • 샤딩에는 최대 컬렉션 크기 또는 분할 수에 대한 운영 제한이 있습니다.
  • 잘못된 샤드 키를 선택하면 성능에 부정적인 영향을 줄 수 있습니다.

결론

MongoDB는 성능 저하 없이 대규모 데이터베이스를 운영하기 위한 내장된 샤딩 기능을 제공합니다. 이 글이 MongoDB 샤딩을 설정하는 데 도움이 되었기를 바랍니다. 이제 자주 사용되는 MongoDB 명령어들을 학습하여 더욱 효과적으로 데이터를 관리할 수 있습니다.