ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • MongoDB Shard 개수에 따른 퍼포먼스 확인하기
    MongoDB 2021. 4. 26. 20:49

    MongoDB shard 구조

     

    오늘은 MongoDB 샤드란 무엇인지 그리고 샤드 수에 따른 퍼포먼스 변화를 확인해보겠습니다.

     

    개념

    Sharding 이란?

    Sharding 데이터를 여러 몽고디비 서버에서 분산해서 저장하고 처리할 수 있도록 만들어주는 기술입니다.

     

    하나의 컬렉션의 데이터를 여러 샤드 서버가 나누어 저장

     

    가끔 replica와 sharding의 목적을 혼동하시는분이 계신데 replica는 고가용성을 위한 기술이며 샤딩은 분산처리를 위한 기술입니다.

    그래서 몽고디비의 고가용성과 대용량 분산 처리를 하려면 replica sharding이 모두 필요하게 됩니다.

    단순히 장비의 하드웨어 부품을 스펙업하는 스케일업(scale-up)으로도 성능을 향상 시킬 수 있지만, 투자 대비 효율이 떨어진다고 생각을합니다.

    하지만 스케일아웃(scale-out)을 위해 수평적 샤딩을 한다면, 더 저렴한 금액으로 좀더 선형에 가까운 퍼포먼스 향상을 가져올 수 있습니다.

    뿐만아니라 서버가 늘어나는것이다보니 자연스레 고가용성도 챙겨갈 수 있게됩니다.

     

    하지만 스케일 아웃(샤딩)도 단점이 존재합니다. 응용프로그램의 코드를 많이 변경해야하는 상황이 생길 수 있습니다.

    ($lookup, deleteMany, updateMany 등 쿼리에대해 변경사항 등)

    때문에 서비스가 한번 시작되면 서비스를 중단하지 않고 샤딩을 적용하기에는 매우 제한적입니다.

    그래서 처음부터 샤딩을 고려해서 설계 및 개발을 진행하는것이 도움이 될 수 있습니다.

     

    Sharding algorithm

    앞서 말했듯이, 샤드는 데이터를 분산처리하는 기술입니다.

    데이터를 분산하는 기준을 샤딩 알고리즘이라고 하는데 고리즘은 3가지가 있습니다.

     

    레인지 샤딩, 해시 샤딩, 지역 기반 샤딩

     

    레인지 샤딩과 해시 샤딩은 기본적인 샤딩 알고리즘 이며, 지역 기반 샤딩은 해시 샤딩과 레인지 샤딩을 특정 샤드서버와 연결하는 복합적인 샤딩 알고리즘입니다.

     

    Chunk

    샤딩은 데이터를 특정 기준으로 그룹핑해서 관리하는것이며, 그 그룹을 청크라고하는 단위로 관리를 합니다.

    모든 청크는 해당되는 샤드키의 최솟값과 최댓값을 두고 그 영역(최소 이상 최대 미만)을 담당하여 여러 샤드에 분산되어 관리됩니다.

     

    데이터의 샤드키가 한글인 필드라고 가정했을때 ㄱ-ㅎ까지 범위를 나누어 청크로 관리

     

    각 청크를 어느 샤드 서버에서 관리할 것인지는 사용자가 결정할 수 없습니다. 이말은 해시 샤딩과 레인지 샤딩에서 특정 청크를 특정 샤드 서버에서 처리하도록 설정할 수는 없다. 하지만 지역 기반 샤딩은 조건을 만족하는 청크를 특정 샤드서버에만 저장하고 처리하게 할 수 있습니다.

    다시 말해 지역 기반 샤딩은 기존 샤딩 알고리즘에 청크의 분산 방식까지 포함하는 샤딩입니다.

     

    청크는 물리적인 개념이 아니라 완전히 논리적으로만 존재하는 개념입니다. 때문에 청크 단위로 데이터 파일이 생성되거나 모여있지 않습니다.

    컨피그 서버의 메타 데이터로만 존재하고 실제 샤드 서버는 전혀 관계가 없습니다.

    이 구조가 가능한 이유는 몽고디비의 클러스터 동작 방식을 이해하시면 알 수 있습니다.

     

    아래의 그림은 서버에 쿼리가 전달되면 Config Server에서 먼저 접근하여 청크 정보를 조회 후 해당 데이터 서버에 명령을 내립니다.

    또한 각 서버에서 값이 반환되면 Router에서 정렬등 데이터를 재조합하여 반환하게 됩니다.

    (ConfigServer는 청크 뿐만 아니라 인증 등 클러스터 운영에 관한 모든 정보를 관리하고 있습니다.)

     

    청크는 기본적으로 64MB 설정되어 있으며, 그 이상으로 커지면 밸런서에 의해서 자동으로 분할됩니다.

    청크의 균형이 맞지 않으면 밸런스가 청크를 이동시키면서 계속 균형된 상태를 유지합니다.

    하지만 이 비용은 매우 고비용 입니다. 때문에 청크 이동이 발생했을때 서버의 부하를 장시간 유발하게 됩니다. (청크밸런서가 청크이동을 하는 시간 대역을 정할 수 있음)

    청크가 크면 클수록 샤드간 부하를 조절하기가 어려우며, 청크가 작을수록 샤드간 부하를 미세하게 분산할 수 있지만 청크 이동이 빈번하게 발생할수도 있으니 샤드 키를 설정하는데 신중해야합니다.

     

    Sharding Key

    앞서 언급한 샤딩 알고리즘의 기준이 되는것이 샤딩키(Sharding-Key)입니다. 즉 어떤 필드를 바탕으로 범위를 나눌지, 해싱을 할지 정하게됩니다.

    이 샤딩키는 전략을 굉장히 잘 짜야합니다.

    첫번째 이유는, 한번 설정하면 컬렉션을 재 생성하지 않는이상 변경이 불가능하기 때문입니다.

    두번째 이유는, 레인지 샤딩의 경우 하나의 키값으로 몰리는 현상이 일어날 수 있습니다. 예를들어 name 필드를 샤딩키로 지정했는데 아래그림처럼 불균형이 올 수 있습니다.

    더 심각한 경우 하나의 값을 가지는 다큐먼트가 많아질 경우 청크가 점점 커지고 쪼갤 수 없는 상태에 이르게됩니다.

    이를 점보청크라고 부르며 몽고디비는 점보청크에 대해서 모든 관리 작업(스플릿, 마이그레이션)을 포기하게 됩니다.

     

    테스트

    구성환경

    3S - MongoDB Atlas M30 model (8GB RAM / 40GB Disk), 3 Shard

    2S - MongoDB Atlas M30 model (8GB RAM / 40GB Disk), 2 Shard

    1S - MongoDB Atlas M30 model (8GB RAM / 40GB Disk), 1 Shard

     

    데이터

    글, 카테고리, 판매처 로 구성된 상품 리뷰 데이터 100만개

     

    클러스터 공통 설정

    db.Data.createIndex({ "mallId": 1 })

     

    3S, 2S 설정

    use admin
    
    // 샤딩 DB 등록
    db.runCommand({
        enablesharding: "Test"
    })
    
    // 샤딩 키 등록
    db.runCommand({
        shardcollection: "Test.Data",
        key: {mallId: 1}
    })
    
    // 컬렉션 샤딩 확인
    db.runCommand({
        listshards: 1
    })

     

    Chunk 분포 현황을 체크하는 명령어 및 로그 예시 (클릭)

    더보기
    // 데이터가 어떻게 나뉘어졌는지 확인 3-Shard 예시
    db.Data.getShardDistribution()
    
    Shard atlas-80djc9-shard-1 at atlas-80djc9-shard-1/atlas-80djc9-shard-01-00.z8juq.mongodb.net:27017,atlas-80djc9-shard-01-01.z8juq.mongodb.net:27017,atlas-80djc9-shard-01-02.z8juq.mongodb.net:27017
     data : 104.94MiB docs : 300000 chunks : 3
     estimated data per chunk : 34.98MiB
     estimated docs per chunk : 100000
    
    Shard atlas-80djc9-shard-2 at atlas-80djc9-shard-2/atlas-80djc9-shard-02-00.z8juq.mongodb.net:27017,atlas-80djc9-shard-02-01.z8juq.mongodb.net:27017,atlas-80djc9-shard-02-02.z8juq.mongodb.net:27017
     data : 125.77MiB docs : 400000 chunks : 4
     estimated data per chunk : 31.44MiB
     estimated docs per chunk : 100000
    
    Shard atlas-80djc9-shard-0 at atlas-80djc9-shard-0/atlas-80djc9-shard-00-00.z8juq.mongodb.net:27017,atlas-80djc9-shard-00-01.z8juq.mongodb.net:27017,atlas-80djc9-shard-00-02.z8juq.mongodb.net:27017
     data : 94.09MiB docs : 300000 chunks : 3
     estimated data per chunk : 31.36MiB
     estimated docs per chunk : 100000
    
    Totals
     data : 324.82MiB docs : 1000000 chunks : 10
     Shard atlas-80djc9-shard-1 contains 32.3% data, 30% docs in cluster, avg obj size on shard : 366B
     Shard atlas-80djc9-shard-2 contains 38.72% data, 40% docs in cluster, avg obj size on shard : 329B
     Shard atlas-80djc9-shard-0 contains 28.96% data, 30% docs in cluster, avg obj size on shard : 328B
    // 3-Shard cluster 예시
    MongoDB Enterprise mongos> sh.status()
    --- Sharding Status --- 
      sharding version: {
      	"_id" : 1,
      	"minCompatibleVersion" : 5,
      	"currentVersion" : 6,
      	"clusterId" : ObjectId("5ffe4b70032951d09a7ac0a9")
      }
      shards:
            {  "_id" : "atlas-80djc9-shard-0",  "host" : "atlas-80djc9-shard-0/atlas-80djc9-shard-00-00.z8juq.mongodb.net:27017,atlas-80djc9-shard-00-01.z8juq.mongodb.net:27017,atlas-80djc9-shard-00-02.z8juq.mongodb.net:27017",  "state" : 1 }
            {  "_id" : "atlas-80djc9-shard-1",  "host" : "atlas-80djc9-shard-1/atlas-80djc9-shard-01-00.z8juq.mongodb.net:27017,atlas-80djc9-shard-01-01.z8juq.mongodb.net:27017,atlas-80djc9-shard-01-02.z8juq.mongodb.net:27017",  "state" : 1 }
            {  "_id" : "atlas-80djc9-shard-2",  "host" : "atlas-80djc9-shard-2/atlas-80djc9-shard-02-00.z8juq.mongodb.net:27017,atlas-80djc9-shard-02-01.z8juq.mongodb.net:27017,atlas-80djc9-shard-02-02.z8juq.mongodb.net:27017",  "state" : 1 }
      active mongoses:
            "4.4.3" : 9
      autosplit:
            Currently enabled: yes
      balancer:
            Currently enabled:  yes
            Currently running:  no
            Failed balancer rounds in last 5 attempts:  0
            Migration Results for the last 24 hours: 
                    687 : Success
      databases:
            {  "_id" : "Test",  "primary" : "atlas-80djc9-shard-1",  "partitioned" : true,  "version" : {  "uuid" : UUID("e1b86d68-58a2-4ee5-a8f5-1866d76df594"),  "lastMod" : 1 } }
                    Test.Data
                            shard key: { "mallId" : 1 }
                            unique: false
                            balancing: true
                            chunks:
                                    atlas-80djc9-shard-0	3
                                    atlas-80djc9-shard-1	3
                                    atlas-80djc9-shard-2	4
                            { "mallId" : { "$minKey" : 1 } } -->> { "mallId" : "daXXX" } on : atlas-80djc9-shard-0 Timestamp(2, 0) 
                            { "mallId" : "daXXX" } -->> { "mallId" : "foXXX" } on : atlas-80djc9-shard-0 Timestamp(6, 0) 
                            { "mallId" : "foXXX" } -->> { "mallId" : "frXXX" } on : atlas-80djc9-shard-2 Timestamp(6, 2) 
                            { "mallId" : "frXXX" } -->> { "mallId" : "freXXX" } on : atlas-80djc9-shard-2 Timestamp(6, 3) 
                            { "mallId" : "freXXX" } -->> { "mallId" : "naXXX" } on : atlas-80djc9-shard-2 Timestamp(5, 3) 
                            { "mallId" : "naXXX" } -->> { "mallId" : "naXXX_XXX" } on : atlas-80djc9-shard-2 Timestamp(6, 1) 
                            { "mallId" : "naXXX_XXX" } -->> { "mallId" : "naXXX_XXX_XXX" } on : atlas-80djc9-shard-0 Timestamp(5, 0) 
                            { "mallId" : "naXXX_XXX_XXX" } -->> { "mallId" : "skXXX" } on : atlas-80djc9-shard-1 Timestamp(5, 4) 
                            { "mallId" : "skXXX" } -->> { "mallId" : "woXXX" } on : atlas-80djc9-shard-1 Timestamp(5, 5) 
                            { "mallId" : "woXXX" } -->> { "mallId" : { "$maxKey" : 1 } } on : atlas-80djc9-shard-1 Timestamp(5, 1) 
            {  "_id" : "config",  "primary" : "config",  "partitioned" : true }
                    config.system.sessions
                            shard key: { "_id" : 1 }
                            unique: false
                            balancing: true
                            chunks:
                                    atlas-80djc9-shard-0	342
                                    atlas-80djc9-shard-1	341
                                    atlas-80djc9-shard-2	341
                            too many chunks to print, use verbose if you want to force print
    
    // sh.splitFind("Test.Data", {"mallId": "naver"})를 사용해서 강제적으로 청크를 나눌 수 있다. 즉, 재조정이 가능한것
    // 혹은 db.settings.save({"_id": "chunksize", value: 64}) 청크 사이즈를 조정하여도 가능하다.

     

     

    테스트 결과 정리

    (검색 쿼리 및 실행계획은 아래에 첨부)

    Cluster 정규식 고르게 분포된 샤드키로 검색 고르게 분포되지 않은 샤드키로 검색
    3S 497ms 242ms 369ms
    2S 664ms 369ms 564ms
    1S 1195ms 597ms 567ms

     

    결론

    샤드에 따른 퍼포먼스 향상은 분명합니다.

    하지만 샤드를 구성하여도 검색되는 문서가 여러 서버에 분포되어있는것이 아닌 단일 서버에 저장된 문서라면

    1-Shard나 N-Shard가 퍼포먼스 차이는 거의 존재하지 않습니다.

    때문에 청크를 고르게 분포시킬 수 있고 검색에 자주쓰이는 필드를 샤드 키로 지정하는 것이 합당하다고 생각합니다.

     

     

    테스트 코드 및 실행계획 

    // 단순 정규식 실험
    
    // 해당 컬렉션을 캐시에 올려놓기 위한 워밍업 작업
    db.Data.find({ch: "1"})
    
    db.Data.find({
        text: {
            $regex: /추천/
        }
    })
    .explain("executionStats")

    실행계획 (클릭)

    더보기
    3S
    2S
    1S
    // 샤딩이 잘 분배된 샤드키를 포함한채로 (선행 매칭)
    
    
    db.Data.find({ch: "1"})
    
    db.Data.find({
        mallId: {$in: ["daXXX", "foXXX", "woXXX"]},
        text: {
            $regex: /추천/
        }
    })
    .explain("executionStats")

    실행계획 (클릭)

    더보기
    3S
    2S
    1S
    // 샤딩이 잘 분배안된 샤드키를 포함한채로 (선행 매칭)
    
    db.Data.find({ch: "1"})
    
    db.Data.find({
        mallId: {$in: ["daXXX", "foXXX", "11XXX"]},
        text: {
            $regex: /추천/
        }
    })
    .explain("executionStats")

    실행계획 (클릭)

    더보기
    3S
    2S
    1S

     

     

    'MongoDB' 카테고리의 다른 글

    MongoDB Oplog 와 Journaling 차이  (0) 2021.04.28
    MongoDB 초간단 log 관리하기  (0) 2021.04.26
    MongoDB 스키마 디자인 패턴  (0) 2020.08.20
Designed by Tistory.