단일 데이터베이스 인스턴스의 쓰기 처리량(Write Throughput)이 하드웨어의 물리적 한계인 수직적 확장(Vertical Scaling) 임계점에 도달했을 때, 시스템 엔지니어는 필연적으로 수평적 확장(Horizontal Scaling)을 고려하게 됩니다. 이때 발생하는 가장 흔한 병목 현상은 단순히 CPU나 메모리 부족이 아닙니다. 실제 프로덕션 환경에서는 Lock Contention(잠금 경합)이나 I/O Wait 증가로 인한 레이턴시 스파이크가 먼저 관측됩니다.
특히 초당 수만 건 이상의 트랜잭션이 발생하는 커머스 주문 시스템이나 실시간 로그 수집기에서, 잘못 설계된 파티셔닝 전략은 특정 노드에 부하가 집중되는 Hot Partition 문제를 야기하며, 이는 전체 클러스터의 가용성을 떨어뜨리는 치명적인 장애로 이어집니다. 본고에서는 분산 데이터베이스 환경에서의 샤딩 아키텍처와 이를 뒷받침하는 NoSQL 모델링 기법을 커널 레벨의 동작 원리와 함께 분석합니다.
샤딩(Sharding) 아키텍처와 라우팅 알고리즘의 진화
샤딩은 논리적으로 단일한 데이터셋을 물리적으로 분리된 여러 데이터베이스 노드(Shard)에 분산 저장하는 기술입니다. 여기서 핵심은 '어떤 데이터를 어느 노드에 저장할 것인가'를 결정하는 라우팅 메커니즘입니다. 가장 기초적인 Modulo Sharding (Key % N) 방식은 노드(N)의 개수가 변경될 때마다 전체 데이터의 재배치(Reshuffling)가 필요하다는 치명적인 단점이 있습니다.
Consistent Hashing (일관된 해싱)
이를 해결하기 위해 현대적인 분산 시스템(Dynamo, Cassandra, Riak)은 Consistent Hashing 링(Ring) 구조를 사용합니다. 데이터와 노드를 동일한 해시 공간(Hash Ring)에 배치하고, 시계 방향으로 가장 가까운 노드에 데이터를 할당합니다. 이 방식은 노드 추가/삭제 시 인접한 노드의 데이터만 이동하면 되므로 재배치 비용을 1/N 수준으로 최소화합니다.
샤드 키(Shard Key) 선정 베스트 프랙티스
샤딩 전략의 성공 여부는 전적으로 샤드 키(Shard Key)의 카디널리티(Cardinality)와 분포도에 달려 있습니다. 단조 증가(Monotonically Increasing)하는 값(예: 타임스탬프, 자동 증가 ID)을 샤드 키로 사용할 경우, 쓰기 작업이 항상 마지막 샤드에만 집중되는 핫스팟이 발생합니다.
// [Bad Pattern] 타임스탬프 기반 샤딩
// 특정 시간대(현재)에 쓰기 부하가 단일 파티션에 집중됨
{
"pk": "2023-10-27T10:00:00", // Shard Key
"data": "log_entry_..."
}
// [Good Pattern] 고유 ID + 해시 접미사 활용 (Synthetic Key)
// 데이터가 전체 파티션에 균등하게 분산됨
String userId = "user_12345";
String timestamp = "2023-10-27";
// 샤드 키 생성: userId의 해시를 활용하거나, 비즈니스 로직에 따른 복합 키 사용
String shardKey = Hash(userId) + "#" + timestamp;
// 실제 NoSQL 저장 구조 예시
{
"pk": "a7f3#2023-10-27",
"sk": "user_12345",
"payload": "..."
}
NoSQL 모델링과 파티셔닝 전략 비교
RDBMS의 샤딩은 애플리케이션 레벨이나 미들웨어(예: Vitess, ProxySQL)에서 처리되는 반면, NoSQL은 데이터베이스 엔진 레벨에서 자동 샤딩(Auto-Sharding)을 지원하는 경우가 많습니다. 하지만 엔진이 자동으로 처리한다고 해서 설계의 중요성이 낮아지는 것은 아닙니다.
| 전략 | 설명 | 장점 | 단점 |
|---|---|---|---|
| Range Based Sharding | 키의 범위를 기준으로 분할 (예: A-M, N-Z) | 범위 쿼리(Range Query)에 효율적. 인접 데이터가 같은 샤드에 위치. | 특정 범위에 데이터가 몰릴 경우 불균형 발생 (Data Skew). |
| Hash Based Sharding | 키의 해시값을 기준으로 분할 | 데이터가 매우 균등하게 분산됨. 핫스팟 방지에 탁월. | 범위 쿼리 시 모든 샤드를 조회해야 함(Scatter-Gather). |
| Directory Based Sharding | 별도의 룩업 테이블(Look-up Table)로 매핑 관리 | 샤드 이동 및 동적 할당이 매우 유연함. | 룩업 테이블이 단일 실패 지점(SPOF)이 될 수 있으며 조회 성능 저하 우려. |
DynamoDB와 MongoDB의 스키마 설계 차이
NoSQL 데이터 모델링은 쿼리 패턴(Access Pattern)을 먼저 정의하고 스키마를 설계하는 "Query-First" 접근 방식을 취해야 합니다.
DynamoDB: Single Table Design
DynamoDB는 파티션 키(PK)와 정렬 키(SK)의 조합으로 데이터를 관리합니다. 여러 엔티티(User, Order, Item)를 하나의 테이블에 몰아넣고, PK와 SK를 제네릭하게(PK, SK) 명명하여 오버로딩(Overloading)하는 기법이 주로 사용됩니다. 이는 RDBMS의 JOIN을 피하고, 단일 요청(GetItem)으로 연관된 데이터를 한 번에 가져오기 위함입니다(Item Collection).
주의: GSI(Global Secondary Index)의 복제 지연
Single Table Design에서 쿼리 유연성을 확보하기 위해 GSI를 과도하게 사용하면, 쓰기 비용이 증가하고 데이터 일관성(Eventual Consistency) 관리 복잡도가 올라갑니다. GSI 업데이트는 비동기로 이루어지므로, 즉각적인 읽기 일관성이 필요한 로직에는 주의가 필요합니다.
MongoDB: 임베딩(Embedding) vs 참조(Referencing)
MongoDB는 BSON 문서 기반이므로, 관련된 데이터를 하나의 문서에 내장(Embedding)하는 것이 기본 원칙입니다. 이는 디스크 I/O를 줄이고 읽기 성능을 극대화합니다. 그러나 문서 크기 제한(16MB)과 잦은 업데이트로 인한 문서 이동(Fragmentation)을 고려해야 합니다. 데이터가 무한히 증가하는 로그나 댓글 같은 경우 참조(Referencing) 패턴을 사용하거나, 버킷 패턴(Bucket Pattern)을 적용하여 배열 크기를 관리해야 합니다.
// [Optimization] MongoDB Bucket Pattern 예시
// 시계열 데이터나 많은 수의 로그를 저장할 때 문서를 일정 크기로 그룹화
{
"_id": "sensor_1_2023_10_27",
"sensor_id": 1,
"date": "2023-10-27",
"count": 500, // 버킷 내 데이터 수
"measurements": [
{ "ts": 1698370000, "val": 23.5 },
{ "ts": 1698370060, "val": 23.6 },
... // 배열 크기가 일정 수준에 도달하면 새로운 문서를 생성
]
}
데이터 재분배 및 리밸런싱 (Rebalancing)
샤딩된 환경에서 노드 장애나 확장은 일상적인 이벤트입니다. MongoDB의 Balancer나 Cassandra의 VNode와 같은 메커니즘은 백그라운드에서 데이터를 이동시킵니다. 이때 Throttling 설정이 중요합니다. 과도한 데이터 마이그레이션은 네트워크 대역폭을 포화시켜 정상적인 서비스 트래픽에 영향을 줄 수 있습니다. 따라서 리밸런싱은 트래픽이 적은 시간대에 수행하도록 스케줄링하거나, 대역폭 제한을 엄격하게 적용해야 합니다.
Split Brain 시나리오
네트워크 파티션이 발생했을 때, 두 개의 노드 그룹이 서로 자신이 마스터라고 주장하는 Split Brain 현상이 발생할 수 있습니다. 이를 방지하기 위해 쿼럼(Quorum) 기반의 합의 알고리즘(Raft, Paxos) 설정과 홀수 개의 노드 구성이 필수적입니다.
결론적으로, 분산 데이터베이스의 샤딩과 모델링은 단순히 데이터를 쪼개는 기술적인 행위를 넘어, 비즈니스의 성장 속도와 데이터 액세스 패턴을 정확히 예측하여 설계해야 하는 아키텍처의 핵심입니다. 초기 설계 단계에서 올바른 샤드 키 선정과 쿼리 패턴 분석이 선행되지 않는다면, 서비스가 확장될수록 기술 부채는 기하급수적으로 증가하게 됩니다. 데이터의 지역성(Locality)과 분산 처리의 이점 사이에서 최적의 균형점을 찾는 것이 엔지니어의 핵심 역량입니다.
Post a Comment