포스트

[DB] MongoDB와 Valkey의 Sharding 아키텍처 정리

[DB] MongoDB와 Valkey의 Sharding 아키텍처 정리

MongoDB와 Valkey의 샤딩 아키텍처를 정리할 일이 있었다. 둘 다 데이터를 여러 노드에 나눠 담는다는 점은 같은데, 접근 방식이 꽤 달라서 비교하면서 보면 재미있다. 정리한 김에 블로그에도 남겨본다.

MongoDB Sharding 아키텍처

MongoDB의 샤딩은 세 가지 컴포넌트로 굴러간다.

flowchart TD
    App[Application] --> mongos["mongos (Query Router)"]
    mongos -.->|메타데이터 캐시| Config["Config Server Replica Set"]
    mongos --> S1["Shard 1 (Replica Set)"]
    mongos --> S2["Shard 2 (Replica Set)"]
    mongos --> S3["Shard 3 (Replica Set)"]
  • mongos (Query Router) — 애플리케이션의 진입점. Stateless라서 여러 대 띄울 수 있고, 라우팅 테이블을 캐시해뒀다가 쿼리를 맞는 Shard로 전달한다.
  • Config Server Replica Set — 클러스터 메타데이터(어떤 데이터가 어느 Shard에 있는지)를 관리한다. 3-member Replica Set으로 구성한다.
  • Shard — 실제 데이터를 담는 곳. 각 Shard가 자체적으로 Primary + Secondary의 Replica Set이라서 고가용성도 확보된다.

쿼리 라우팅: Shard Key가 갈림길

  • 쿼리에 Shard Key가 포함되면targeted query. 필요한 Shard만 콕 집어 조회한다.
  • Shard Key가 없으면scatter-gather. 모든 Shard에 쿼리를 뿌리고 결과를 모은다. 당연히 비싸다.

그래서 Shard Key 설계가 샤딩의 절반이다. 자주 쓰는 쿼리 패턴이 targeted로 떨어지게 잡아야 한다.

Chunk Migration

데이터가 한쪽 Shard에 몰리면 Balancer(Config Server Primary에서 실행)가 chunk 재배치를 지시한다. 실제 데이터는 donor shard에서 recipient shard로 직접 전송된다.

핵심 원리: mongos는 Config Server 메타데이터를 기반으로 대상 Shard에 라우팅하고, 각 Shard는 Replica Set으로 고가용성을 확보한다.

Valkey Sharding 아키텍처

Valkey(Redis 포크)의 클러스터는 MongoDB와 철학이 다르다. 라우터가 없다.

16,384개의 Hash Slot

전체 키 공간을 16,384개의 Hash Slot으로 나누고, 이걸 Primary 노드들이 나눠 가진다.

노드 담당 Slot
Primary 1 0 – 5460
Primary 2 5461 – 10922
Primary 3 10923 – 16383

클라이언트(Cluster-aware Client)가 Slot Map을 직접 캐시하고 있다가, 키를 해싱해서 담당 노드에 바로 접근한다. 프록시를 거치지 않으니 홉이 하나 줄어든다.

노드 간 통신: Cluster Bus

  • Primary와 Replica 모든 노드가 참여하는 full mesh 구조
  • 데이터 포트 + 10000번 포트에서 Gossip Protocol로 서로의 상태를 주고받는다
  • Replica는 비동기 복제로 데이터를 받다가, Primary 장애 시 Failover를 지원한다

Resharding 중에는? MOVED와 ASK

슬롯을 다른 노드로 옮기는 중에 요청이 오면 두 가지 응답으로 클라이언트를 안내한다.

  • MOVED — 슬롯 이전이 완료됨. 클라이언트는 Slot Map을 갱신하고 재시도.
  • ASK — 슬롯 이전이 진행 중. 이번 요청만 새 노드로 우회하고, 캐시된 Slot Map은 유지.

비동기 복제의 대가

Primary와 Replica 사이 복제가 비동기라서, Failover 시 마지막 쓰기가 유실될 수 있다. 중요한 쓰기라면 업무 멱등성, WAIT 명령, AOF/RDB 정책을 RPO 요구사항과 함께 설계해야 한다.

핵심 원리: 16,384개 Hash Slot을 Primary에 분산하고, 클라이언트는 Slot Map으로 담당 노드에 직접 접근한다.

두 아키텍처 비교

  MongoDB Valkey
라우팅 주체 mongos (중앙 라우터) 클라이언트 (Slot Map 직접 캐시)
메타데이터 관리 Config Server Replica Set 노드 간 Gossip Protocol
데이터 분할 단위 Chunk (Shard Key 기반) 16,384 Hash Slot
재배치 Balancer가 chunk migration 지시 Resharding + MOVED/ASK
고가용성 Shard별 Replica Set Primary별 Replica (비동기 복제)

같이 쓰면: Cache-Aside 통합 아키텍처

실무에서는 둘을 조합해서 쓰는 그림이 나온다. MongoDB는 영구 저장 원본, Valkey는 캐시·세션·Rate Limit 같은 휘발성 데이터 담당이다.

flowchart TD
    App[Application] --> SL[Service Layer]
    SL -->|"① GET / ③ SET+TTL / DEL"| VK["Valkey Cluster (캐시)"]
    SL -->|"② miss 시 읽기 / commit"| MG["MongoDB Sharded Cluster (원본)"]
    VK x--x MG

읽기: ① Valkey를 먼저 GET → ② 캐시 미스면 MongoDB에서 원본 읽기 → ③ TTL과 함께 Valkey에 SET.

쓰기: ① MongoDB에 commit을 먼저 확정 → ② Valkey에 DEL로 캐시 무효화. 다음 읽기 때 캐시가 재생성된다.

정합성을 위해 챙길 것들:

  • 순서는 반드시 commit 먼저, DEL 나중 (반대로 하면 DEL 직후 다른 요청이 옛 데이터를 다시 캐시할 수 있다)
  • TTL로 오래된 캐시의 생존 시간을 제한
  • 재생성 락으로 Cache Stampede 방지 (캐시가 사라진 순간 수백 개 요청이 동시에 DB로 몰리는 것)

중요한 설계 포인트는 Valkey와 MongoDB가 직접 통신하지 않는다는 것. 두 시스템은 독립적으로 샤딩되고, 모든 캐시 조작은 Service Layer가 수행한다. 덕분에 한쪽 구조를 바꿔도 다른 쪽에 영향이 없다.

마무리

정리하고 보니 두 시스템의 차이가 명확하다. MongoDB는 중앙에서 관리하는 라우터를 두고 유연한 쿼리를 지원하는 방향, Valkey는 클라이언트가 직접 찾아가게 해서 지연을 최소화하는 방향이다. 어느 쪽이 낫다기보다, 문서 DB와 인메모리 캐시라는 각자의 역할에 맞는 선택이라는 생각이 든다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.