샤딩 (Sharding)
샤딩 (Sharding)
샤딩(Sharding)은 여러 대의 머신에 데이터를 분산해서 저장하는 방법이에요. MongoDB는 매우 큰 데이터셋과 높은 처리량(high throughput)을 요구하는 배포를 지원하기 위해 샤딩을 사용해요.
데이터셋이 크거나 처리량이 높은 애플리케이션은 단일 서버의 용량을 넘어서기 쉬워요. 예를 들어 쿼리 요청이 매우 많아지면 서버의 CPU 용량이 고갈될 수 있고, 시스템 메모리(RAM)보다 큰 작업 집합(working set)은 디스크 드라이브의 I/O 용량을 압박하게 돼요.
이렇게 시스템이 커지는 문제를 해결하는 방법은 두 가지가 있어요: **수직 확장(vertical scaling)**과 **수평 확장(horizontal scaling)**이죠.
**수직 확장(Vertical Scaling)**은 더 강력한 CPU를 쓰거나 RAM을 추가하고 스토리지를 늘리는 방식으로 단일 서버의 용량을 키우는 거예요. 하지만 사용 가능한 기술과 클라우드 제공자의 하드웨어 구성 때문에 수직 확장에는 현실적인 최대치가 존재해요.
**수평 확장(Horizontal Scaling)**은 시스템의 데이터셋과 부하를 여러 서버에 나누고, 필요에 따라 서버를 추가해 용량을 늘리는 방식이에요. 각 머신은 전체 작업량의 일부만 처리하므로, 고성능 하드웨어 한 대를 사는 것보다 비용이 덜 들 수 있어요. 대신 인프라와 유지보수의 복잡성이 커진다는 트레이드오프가 있죠.
샤디드 클러스터 (Sharded Cluster)
MongoDB의 샤디드 클러스터(sharded cluster)는 다음 구성 요소로 이루어져요:
- 샤드(shard): 각 샤드는 샤딩된 데이터의 일부분을 담아요. 각 샤드는 반드시 복제 셋(replica set)으로 배포해야 해요.
- mongos를 통한 라우팅(Routing with mongos):
mongos는 쿼리 라우터 역할을 하면서 클라이언트 애플리케이션과 샤디드 클러스터 사이의 인터페이스를 제공해요. - 컨피그 서버(config servers): 컨피그 서버는 클러스터의 메타데이터와 설정 정보를 저장해요. 컨피그 서버 역시 반드시 복제 셋(CSRS)으로 배포해야 해요.
샤디드 클러스터 안에서 이 구성 요소들이 어떻게 상호작용하는지는 아래 그림에서 확인할 수 있어요:
MongoDB는 컬렉션(collection) 단위로 데이터를 샤딩해요. 컬렉션의 데이터를 클러스터 안의 여러 샤드에 나눠 배치하는 방식이에요.
샤드 키 (Shard Keys)
MongoDB는 컬렉션의 문서들을 샤드에 분배하기 위해 **샤드 키(shard key)**를 사용해요. 샤드 키는 문서 안의 하나 또는 여러 개의 필드로 구성돼요.
샤딩된 컬렉션의 문서에는 샤드 키 필드가 없을 수도 있어요. 샤드 키 필드가 없는 경우, 문서를 샤드에 분배할 때는 그 필드를 null 값으로 취급하지만 쿼리를 라우팅할 때는 그렇게 취급하지 않아요. 자세한 내용은 "Set Missing Shard Key Fields" 문서를 참고하세요.
컬렉션을 샤딩할 때 샤드 키를 선택하게 돼요.
문서의 샤드 키 값이 샤드 간 분배를 결정해요. 샤드 키 필드가 항상 바뀌지 않는 _id 필드가 아니라면, 문서의 샤드 키 값을 업데이트할 수 있어요. 자세한 내용은 "Change a Document's Shard Key Value" 문서를 참고하세요.
샤드 키 인덱스 (Shard Key Index)
이미 데이터가 있는 컬렉션을 샤딩하려면, 해당 컬렉션에 샤드 키로 시작하는 인덱스가 있어야 해요. 빈 컬렉션을 샤딩할 때는, 지정된 샤드 키에 맞는 인덱스가 없다면 MongoDB가 지원 인덱스를 자동으로 만들어 줘요. "Shard Key Indexes" 문서를 참고하세요.
샤드 키 전략 (Shard Key Strategy)
샤드 키와 그 뒤를 받치는 인덱스의 선택은 클러스터가 사용할 수 있는 샤딩 전략에도 영향을 줄 수 있어요.
청크 (Chunks)
MongoDB는 샤딩된 데이터를 청크(chunk) 단위로 분할해요. 각 청크는 샤드 키를 기준으로 하한을 포함하고 상한을 제외한 범위(inclusive lower, exclusive upper range)를 가져요.
밸런서와 균등한 데이터 분배 (Balancer and Even Data Distribution)
클러스터의 모든 샤드에 데이터를 고르게 분배하기 위해 **밸런서(balancer)**가 백그라운드에서 실행돼요. 밸런서는 샤드 간에 **범위(range)**를 옮겨가며 마이그레이션해요.
샤딩의 장점 (Advantages of Sharding)
읽기 / 쓰기 (Reads / Writes)
MongoDB는 읽기·쓰기 작업 부하를 샤디드 클러스터 안의 샤드들에 나눠서, 각 샤드가 클러스터 전체 작업의 일부만 처리하게 해요. 샤드를 추가하면 읽기와 쓰기 작업 부하를 모두 클러스터 전체에 걸쳐 수평 확장할 수 있어요.
쿼리에 샤드 키 또는 복합(compound) 샤드 키의 접두사가 포함되면, mongos는 그 쿼리를 특정 샤드나 일부 샤드로 겨냥(target)할 수 있어요. 이렇게 **타깃이 정해진 연산(targeted operations)**은 일반적으로 클러스터의 모든 샤드에 브로드캐스트하는 방식보다 효율적이에요.
스토리지 용량 (Storage Capacity)
샤딩은 데이터를 클러스터 안의 샤드에 분산해서, 각 샤드가 클러스터 전체 데이터의 일부만 담게 해요. 데이터셋이 성장함에 따라 샤드를 추가하면 클러스터의 스토리지 용량도 커져요.
고가용성 (High Availability)
컨피그 서버와 샤드를 복제 셋으로 배포하면 가용성이 높아져요.
하나 이상의 샤드 복제 셋이 완전히 사용 불가능해져도, 샤디드 클러스터는 부분적인 읽기와 쓰기를 계속 수행할 수 있어요. 즉 사용 불가능한 샤드의 데이터에는 접근할 수 없지만, 사용 가능한 샤드를 대상으로 한 읽기나 쓰기는 여전히 성공할 수 있다는 뜻이에요.
샤딩 전에 고려할 점 (Considerations Before Sharding)
샤디드 클러스터의 인프라 요구 사항과 복잡성 때문에 신중한 계획, 실행, 그리고 유지보수가 필요해요.
나중에 컬렉션을 다시 샤딩(reshard)할 수는 있지만, 확장성과 성능 문제를 피하려면 샤드 키 선택을 신중히 해야 해요.
컬렉션을 샤딩할 때의 운영 요구 사항과 제약을 이해하려면 "Operational Restrictions in Sharded Clusters" 문서를 참고하세요.
쿼리가 샤드 키 또는 복합 샤드 키의 접두사를 포함하지 않으면, mongos는 **브로드캐스트 연산(broadcast operation)**을 수행해서 샤디드 클러스터의 모든 샤드를 쿼리해요. 이런 scatter/gather 쿼리는 오래 걸리는 연산이 될 수 있어요.
MongoDB 5.1부터는 sh.addShard()로 샤드 서버를 시작, 재시작, 또는 추가할 때 **Cluster Wide Write Concern(CWWC)**이 설정되어 있어야 해요.
CWWC가 설정되어 있지 않고, 샤드의 기본 쓰기 고려(write concern)가 { w : 1 }로 구성되어 있다면, 샤드 서버는 시작되거나 추가되지 못하고 오류를 반환해요.
기본 쓰기 고려가 어떻게 계산되는지에 대한 자세한 내용은 "default write concern calculations" 문서를 참고하세요.
MongoDB와 활성 지원 계약(Active support contract)이 있다면, 샤디드 클러스터의 계획과 배포에 대한 도움을 받기 위해 계정 담당자에게 연락하는 것을 고려해 보세요.
밸런싱을 위한 리샤딩 (Reshard to Balance)
sh.shardCollection() 메서드를 실행하면 밸런서가 컬렉션 데이터를 클러스터의 다른 샤드에 분배하기 시작해요. 하나의 샤드는 한 번에 하나의 청크 마이그레이션에만 참여할 수 있어요. MongoDB가 한 샤드에서 다른 샤드로 데이터 범위를 복사하는 데 성공하면, 기증자(donor) 샤드의 해당 범위는 range deleter가 제거할 대상으로 표시돼요. 이 과정은 느리고 리소스를 많이 소모해요.
MongoDB 8.0부터는, 배포 환경이 필요한 리소스 조건을 충족한다면 sh.shardAndDistributeCollection() 메서드를 사용해 컬렉션을 샤딩하는 것을 권장해요. 이 메서드는 shardCollection과 reshardCollection 명령을 감싸서 컬렉션을 샤딩하고 즉시 같은 키로 다시 샤딩해요. 이렇게 하면 밸런서를 기다리지 않고 샤드 간 데이터를 리밸런싱할 수 있어요.
자세한 내용은 "Reshard to the Same Shard Key" 문서를 참고하세요.
샤딩된 컬렉션과 샤딩되지 않은 컬렉션 (Sharded and Non-Sharded Collections)
하나의 데이터베이스에는 샤딩된 컬렉션과 샤딩되지 않은 컬렉션이 섞여 있을 수 있어요. 샤딩된 컬렉션은 **파티셔닝(partitioned)**되어 클러스터의 샤드들에 분산돼요. 샤딩되지 않은 컬렉션은 어떤 샤드에든 위치할 수 있지만 여러 샤드에 걸쳐 있을 수는 없어요.
샤디드 클러스터에 연결하기 (Connecting to a Sharded Cluster)
샤디드 클러스터 안의 어떤 컬렉션이든 상호작용하려면 mongos 라우터에 연결해야 해요. 여기에는 샤딩된 컬렉션과 샤딩되지 않은 컬렉션이 모두 포함돼요. 클라이언트는 읽기나 쓰기 연산을 수행하기 위해 단일 샤드에 절대 직접 연결하면 안 돼요.
MongoDB 8.3부터는 모든 샤디드 클러스터의 DDL 연산과 **applyOps**를 mongos에서만 실행할 수 있어요. 이 연산들은 복제 셋에서 샤디드 클러스터로 전환하는 동안에는 일시적으로 사용할 수 없을 수도 있어요.
mongos에는 mongod에 연결하듯이, mongosh나 MongoDB 드라이버를 사용해 연결할 수 있어요.
MongoDB 8.0부터는 샤디드 클러스터의 노드에서 특정 명령만 실행할 수 있어요. 노드에 직접 연결해 지원되지 않는 명령을 실행하려 하면 MongoDB는 다음과 같은 오류를 반환해요:
"You are connecting to a sharded cluster improperly by connecting directlyto a shard. Please connect to the cluster via a router (mongos)."
샤디드 클러스터의 노드에 직접 연결해서 지원되지 않는 데이터베이스 명령을 실행하려면, mongos에 연결하거나 유지보수 전용 directShardOperations 역할을 가져야 해요.
샤딩 전략 (Sharding Strategy)
MongoDB는 샤디드 클러스터에 데이터를 분산하는 두 가지 샤딩 전략을 지원해요.
해시 기반 샤딩 (Hashed Sharding)
해시 기반 샤딩(Hashed Sharding)은 샤드 키 필드 값의 해시(hash)를 계산해요. 그런 다음 각 청크에 해시된 샤드 키 값을 기준으로 한 범위를 할당해요.
MongoDB는 해시 인덱스를 사용해 쿼리를 처리할 때 해시를 자동으로 계산해요. 애플리케이션이 해시를 직접 계산할 필요는 없어요.
해시된 값은 같은 청크를 공유할 가능성이 낮아서, 특히 단조롭게(monotonically) 변하는 샤드 키의 경우 데이터 분포가 더 고르게 돼요.
하지만 해시 분포는 샤드 키에 대한 범위 기반 쿼리가 단일 샤드를 겨냥할 가능성을 낮춰서, 클러스터 전체에 걸친 브로드캐스트 연산이 더 많아질 수 있어요.
자세한 내용은 "Hashed Sharding" 문서를 참고하세요.
범위 기반 샤딩 (Ranged Sharding)
범위 기반 샤딩(Ranged Sharding)은 샤드 키 값을 기준으로 데이터를 범위로 나누고, 각 청크에 그중 하나의 범위를 할당해요.
서로 "가까운" 값을 가진 샤드 키들은 같은 청크에 위치할 가능성이 높아요. 이 덕분에 mongos가 필요한 데이터가 있는 샤드에만 연산을 라우팅할 수 있어서 **타깃 연산(targeted operations)**이 가능해져요.
범위 기반 샤딩의 효율성은 선택한 샤드 키에 따라 달라져요. 잘못 고른 샤드 키는 데이터가 고르지 않게 분포하면서 샤딩의 장점을 상쇄하거나 성능 병목을 일으킬 수 있어요. 범위 기반 샤딩의 샤드 키 선택에 대한 내용을 참고하세요.
자세한 내용은 "Ranged Sharding" 문서를 참고하세요.
샤디드 클러스터의 존 (Zones in Sharded Clusters)
존(Zones)은 여러 데이터 센터에 걸쳐 있는 샤디드 클러스터에서 데이터의 지역성(locality)을 개선해 줘요.
샤디드 클러스터에서는 샤드 키를 기준으로 샤딩된 데이터의 존을 만들 수 있어요. 각 존을 클러스터의 하나 이상의 샤드와 연결할 수 있고, 하나의 샤드는 원하는 만큼 많은 존과 연결될 수 있어요. 밸런싱이 된 클러스터에서 MongoDB는 존이 커버하는 청크를 그 존과 연결된 샤드로만 마이그레이션해요.
각 존은 하나 이상의 샤드 키 값 범위를 커버해요. 존이 커버하는 각 범위는 항상 하한을 포함하고 상한을 제외해요.
존이 커버할 새 범위를 정의할 때는 반드시 샤드 키에 포함된 필드를 사용해야 해요. 복합 샤드 키를 사용한다면, 범위에 반드시 샤드 키의 접두사가 포함되어야 해요. 존의 샤드 키에 대한 자세한 내용은 "shard keys in zones" 문서를 참고하세요.
샤드 키를 선택할 때는 미래에 존을 사용할 가능성도 고려하는 게 좋아요.
빈 컬렉션이나 아직 존재하지 않는 컬렉션을 샤딩하기 전에 존과 존 범위를 설정하면, 존 기반 샤딩을 더 빠르게 구성할 수 있어요.
자세한 내용은 "zones" 문서를 참고하세요.
샤딩에서의 콜레이션 (Collations in Sharding)
기본 콜레이션(default collation)이 있는 컬렉션을 샤딩하려면 collation : { locale : "simple" } 옵션과 함께 shardCollection 명령을 사용해요. 샤딩이 성공하려면 다음 조건이 필요해요:
- 컬렉션에 샤드 키를 접두사로 가지는 인덱스가 있어야 함
- 그 인덱스의 콜레이션이
{ locale: "simple" }이어야 함
콜레이션이 있는 새 컬렉션을 만들 때는, 샤딩 전에 이 조건들이 충족되는지 확인해야 해요.
샤딩된 컬렉션에 대한 쿼리는 계속해서 컬렉션에 구성된 기본 콜레이션을 사용해요. 샤드 키 인덱스의 simple 콜레이션을 사용하려면, 쿼리의 콜레이션 문서에 {locale : "simple"}을 지정하세요.
샤딩과 콜레이션에 대한 자세한 내용은 shardCollection 문서를 참고하세요.
변경 스트림 (Change Streams)
변경 스트림(change streams)은 복제 셋과 샤디드 클러스터 모두에서 사용할 수 있어요. 변경 스트림을 사용하면 애플리케이션이 oplog를 추적(tailing)하는 복잡성과 위험 없이 실시간 데이터 변경에 접근할 수 있어요.
트랜잭션 (Transactions)
분산 트랜잭션(Distributed transactions)은 샤디드 클러스터에서 다중 문서 트랜잭션을 지원해요.
트랜잭션이 커밋되기 전까지, 트랜잭션에서 만든 데이터 변경은 트랜잭션 외부에서 보이지 않아요.
하지만 트랜잭션이 여러 샤드에 쓸 때는, 모든 외부 읽기 연산이 커밋된 트랜잭션의 결과가 샤드 전체에 보이게 될 때까지 기다릴 필요는 없어요. 예를 들어 트랜잭션이 커밋되었는데 샤드 A에서는 쓰기 1이 보이고 샤드 B에서는 아직 쓰기 2가 보이지 않는 상태라면, 읽기 고려(read concern)가 "local"인 외부 읽기는 쓰기 2를 보지 못하면서도 쓰기 1의 결과를 읽을 수 있어요.
더 알아보기 (Learn More)
Practical MongoDB Aggregations 이북
샤딩이 애그리게이션과 함께 어떻게 동작하는지 더 자세히 알고 싶다면, Practical MongoDB Aggregations 이북의 샤딩 챕터를 읽어 보세요.