데이터베이스 확장

데이터베이스 확장

개요

이 문서에서는 데이터베이스 확장 전략을 다룹니다. 파티셔닝과 샤딩의 차이, 다양한 샤딩 전략(Range, Hash, Directory), 샤딩 키 선택, 핫스팟 방지, 그리고 리밸런싱을 학습합니다.

난이도: ⭐⭐⭐ 예상 학습 시간: 2-3시간 선수 지식: 07_Distributed_Cache_Systems.md, PostgreSQL 폴더


목차

  1. 데이터베이스 확장의 필요성
  2. 파티셔닝 vs 샤딩
  3. 샤딩 전략
  4. 샤딩 키 선택
  5. 핫스팟 방지
  6. 리밸런싱
  7. 연습 문제
  8. 다음 단계
  9. 참고 자료

1. 데이터베이스 확장의 필요성

1.1 단일 데이터베이스의 한계

┌─────────────────────────────────────────────────────────────────┐
│                단일 데이터베이스 한계                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  트래픽/데이터 증가 시 문제:                                    │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  1. 저장 용량 한계                                         │ │
│  │     • 단일 디스크/서버의 물리적 한계                       │ │
│  │     • 수십 TB 이상 데이터 관리 어려움                      │ │
│  │                                                            │ │
│  │  2. 처리량 한계                                            │ │
│  │     • 단일 서버 CPU/메모리 한계                            │ │
│  │     • 동시 연결 수 제한                                    │ │
│  │                                                            │ │
│  │  3. 응답 시간 증가                                         │ │
│  │     • 큰 테이블 → 느린 쿼리                                │ │
│  │     • 인덱스 크기 증가                                     │ │
│  │                                                            │ │
│  │  4. 단일 장애점 (SPOF)                                     │ │
│  │     • 서버 장애 = 전체 서비스 장애                         │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  해결 방법:                                                     │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  수직 확장 (Scale Up)                                      │ │
│  │    → 더 강력한 하드웨어 (한계 있음)                        │ │
│  │                                                            │ │
│  │  수평 확장 (Scale Out)                                     │ │
│  │    → 여러 서버로 분산 (파티셔닝/샤딩)                      │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

1.2 확장 전략 개요

┌─────────────────────────────────────────────────────────────────┐
│                   데이터베이스 확장 전략                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  1. 읽기 복제 (Read Replica)                               │ │
│  │     • 읽기 부하 분산                                       │ │
│  │     • 다음 레슨에서 자세히 다룸                            │ │
│  │                                                            │ │
│  │  2. 파티셔닝 (Partitioning)                                │ │
│  │     • 단일 DB 내에서 테이블 분할                           │ │
│  │     • PostgreSQL 네이티브 파티셔닝                         │ │
│  │                                                            │ │
│  │  3. 샤딩 (Sharding)                                        │ │
│  │     • 여러 DB 서버로 데이터 분산                           │ │
│  │     • 수평 확장의 핵심                                     │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│                    [단일 DB]                                    │
│                        │                                        │
│         ┌──────────────┼──────────────┐                         │
│         ▼              ▼              ▼                         │
│    [복제]         [파티셔닝]       [샤딩]                       │
│    읽기 확장      단일DB 분할     다중DB 분산                   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

2. 파티셔닝 vs 샤딩

2.1 파티셔닝 (Partitioning)

┌─────────────────────────────────────────────────────────────────┐
                      파티셔닝                                    
├─────────────────────────────────────────────────────────────────┤
                                                                  
  "하나의 DB 서버 내에서 테이블을 여러 파티션으로 분할"          
                                                                  
  ┌────────────────────────────────────────────────────────────┐ 
                                                               
    ┌───────────────────────────────────────────────────────┐  
                  Database Server                            
                                                             
      ┌─────────────────────────────────────────────────┐    
                    orders (논리 테이블)                   
      └─────────────────────────────────────────────────┘    
                                                            
             ┌──────────────┼──────────────┐                 
                                                          
      ┌───────────┐  ┌───────────┐  ┌───────────┐           
      orders_2022  orders_2023  orders_2024           
       (파티션1)    (파티션2)    (파티션3)            
      └───────────┘  └───────────┘  └───────────┘           
                                                             
    └───────────────────────────────────────────────────────┘  
                                                               
  └────────────────────────────────────────────────────────────┘ 
                                                                  
  PostgreSQL 예시:                                               
  ┌────────────────────────────────────────────────────────────┐ 
    -- 파티션 테이블 생성                                      
    CREATE TABLE orders (                                      
        id SERIAL,                                             
        order_date DATE,                                       
        amount DECIMAL                                         
    ) PARTITION BY RANGE (order_date);                         
                                                               
    -- 파티션 생성                                             
    CREATE TABLE orders_2024 PARTITION OF orders               
        FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');      
  └────────────────────────────────────────────────────────────┘ 
                                                                  
  장점:                                                          
   쿼리 성능 향상 (파티션 프루닝)                               
   데이터 관리 용이 (파티션별 삭제/백업)                        
   인덱스 크기 감소                                             
                                                                  
  한계:                                                          
   여전히 단일 서버 (용량, 처리량 한계)                         
                                                                  
└─────────────────────────────────────────────────────────────────┘

2.2 샤딩 (Sharding)

┌─────────────────────────────────────────────────────────────────┐
│                        샤딩                                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  "여러 DB 서버로 데이터를 분산 저장"                            │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │         ┌─────────────────────────────────────────┐        │ │
│  │         │           Application                   │        │ │
│  │         └──────────────────┬──────────────────────┘        │ │
│  │                            │                               │ │
│  │                   ┌────────┴────────┐                      │ │
│  │                   │  Shard Router   │                      │ │
│  │                   │  (샤드 결정)    │                      │ │
│  │                   └────────┬────────┘                      │ │
│  │                            │                               │ │
│  │         ┌──────────────────┼──────────────────┐            │ │
│  │         │                  │                  │            │ │
│  │         ▼                  ▼                  ▼            │ │
│  │  ┌────────────┐     ┌────────────┐     ┌────────────┐     │ │
│  │  │  Shard 1   │     │  Shard 2   │     │  Shard 3   │     │ │
│  │  │ (Server 1) │     │ (Server 2) │     │ (Server 3) │     │ │
│  │  │            │     │            │     │            │     │ │
│  │  │ user_id    │     │ user_id    │     │ user_id    │     │ │
│  │  │ 1-1000000  │     │ 1000001-   │     │ 2000001-   │     │ │
│  │  │            │     │ 2000000    │     │ 3000000    │     │ │
│  │  └────────────┘     └────────────┘     └────────────┘     │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  장점:                                                          │
│  • 이론적으로 무한 확장 가능                                    │
│  • 각 샤드의 부하 감소                                          │
│  • 장애 격리                                                    │
│                                                                  │
│  단점:                                                          │
│  • 복잡도 증가                                                  │
│  • 크로스 샤드 쿼리 어려움                                      │
│  • 트랜잭션 제약                                                │
│  • 리밸런싱 어려움                                              │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

2.3 비교

항목 파티셔닝 샤딩
위치 단일 서버 내 여러 서버
확장성 제한적 높음
복잡도 낮음 높음
트랜잭션 전체 가능 샤드 내만
JOIN 가능 크로스 샤드 어려움
관리 DB가 자동 애플리케이션이 관리

3. 샤딩 전략

3.1 Range 기반 샤딩

┌─────────────────────────────────────────────────────────────────┐
│                   Range 기반 샤딩                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  "값의 범위에 따라 샤드 결정"                                   │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  user_id 기반 Range 샤딩:                                  │ │
│  │                                                            │ │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │ │
│  │  │  Shard 1    │  │  Shard 2    │  │  Shard 3    │        │ │
│  │  │             │  │             │  │             │        │ │
│  │  │ user_id     │  │ user_id     │  │ user_id     │        │ │
│  │  │ 1 ~ 1M      │  │ 1M+1 ~ 2M   │  │ 2M+1 ~ 3M   │        │ │
│  │  │             │  │             │  │             │        │ │
│  │  └─────────────┘  └─────────────┘  └─────────────┘        │ │
│  │                                                            │ │
│  │  날짜 기반 Range 샤딩:                                     │ │
│  │                                                            │ │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │ │
│  │  │  Shard 1    │  │  Shard 2    │  │  Shard 3    │        │ │
│  │  │             │  │             │  │             │        │ │
│  │  │ 2022년      │  │ 2023년      │  │ 2024년      │        │ │
│  │  │ 데이터      │  │ 데이터      │  │ 데이터      │        │ │
│  │  │             │  │             │  │             │        │ │
│  │  └─────────────┘  └─────────────┘  └─────────────┘        │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  라우팅 로직:                                                   │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  def get_shard(user_id):                                   │ │
│  │      if user_id <= 1_000_000:                              │ │
│  │          return "shard_1"                                  │ │
│  │      elif user_id <= 2_000_000:                            │ │
│  │          return "shard_2"                                  │ │
│  │      else:                                                 │ │
│  │          return "shard_3"                                  │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  장점:                                                          │
│  • 범위 쿼리 효율적                                             │
│  • 구현 단순                                                    │
│                                                                  │
│  단점:                                                          │
│  • 핫스팟 발생 가능 (신규 사용자가 마지막 샤드에 몰림)          │
│  • 불균등 분배 가능                                             │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

3.2 Hash 기반 샤딩

┌─────────────────────────────────────────────────────────────────┐
│                    Hash 기반 샤딩                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  "키의 해시값으로 샤드 결정"                                    │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  shard = hash(user_id) % N                                 │ │
│  │                                                            │ │
│  │  user_id: 12345                                            │ │
│  │  hash(12345) = 67890                                       │ │
│  │  67890 % 3 = 0 → Shard 0                                   │ │
│  │                                                            │ │
│  │  user_id: 12346                                            │ │
│  │  hash(12346) = 12347                                       │ │
│  │  12347 % 3 = 1 → Shard 1                                   │ │
│  │                                                            │ │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │ │
│  │  │  Shard 0    │  │  Shard 1    │  │  Shard 2    │        │ │
│  │  │             │  │             │  │             │        │ │
│  │  │ user: 12345 │  │ user: 12346 │  │ user: 12347 │        │ │
│  │  │ user: 12348 │  │ user: 12349 │  │ user: 12350 │        │ │
│  │  │     ...     │  │     ...     │  │     ...     │        │ │
│  │  │             │  │             │  │             │        │ │
│  │  └─────────────┘  └─────────────┘  └─────────────┘        │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  라우팅 로직:                                                   │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  def get_shard(user_id, num_shards=3):                     │ │
│  │      hash_value = hash(str(user_id))                       │ │
│  │      shard_id = hash_value % num_shards                    │ │
│  │      return f"shard_{shard_id}"                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  장점:                                                          │
│  • 균등 분배                                                    │
│  • 핫스팟 방지                                                  │
│                                                                  │
│  단점:                                                          │
│  • 범위 쿼리 어려움 (모든 샤드 조회 필요)                       │
│  • 샤드 추가/제거 시 대규모 재분배                              │
│  • → 일관성 해싱으로 해결                                       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

3.3 Directory 기반 샤딩

┌─────────────────────────────────────────────────────────────────┐
│                 Directory 기반 샤딩                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  "조회 테이블(디렉토리)로 샤드 결정"                            │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │                   Application                              │ │
│  │                        │                                   │ │
│  │                        │ user_id: 12345                    │ │
│  │                        ▼                                   │ │
│  │              ┌──────────────────┐                          │ │
│  │              │ Directory Service│                          │ │
│  │              │ (Lookup Table)   │                          │ │
│  │              │                  │                          │ │
│  │              │ user_id │ shard  │                          │ │
│  │              │ ────────┼─────── │                          │ │
│  │              │ 12345   │ shard_2│                          │ │
│  │              │ 12346   │ shard_1│                          │ │
│  │              │ 12347   │ shard_3│                          │ │
│  │              │   ...   │  ...   │                          │ │
│  │              └─────────┬────────┘                          │ │
│  │                        │                                   │ │
│  │                        │ shard_2                           │ │
│  │                        ▼                                   │ │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │ │
│  │  │  Shard 1    │  │  Shard 2    │  │  Shard 3    │        │ │
│  │  └─────────────┘  └──────┬──────┘  └─────────────┘        │ │
│  │                          │                                 │ │
│  │                          ▼                                 │ │
│  │                    user: 12345                             │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  장점:                                                          │
│  • 유연한 데이터 이동                                           │
│  • 불균형 해소 용이                                             │
│  • 특정 사용자를 특정 샤드로 이동 가능                          │
│                                                                  │
│  단점:                                                          │
│  • 디렉토리 서비스가 SPOF                                       │
│  • 추가 조회 오버헤드                                           │
│  • 디렉토리 크기 증가                                           │
│                                                                  │
│  해결:                                                          │
│  • 디렉토리를 캐싱 (Redis)                                      │
│  • 디렉토리 복제                                                │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

3.4 전략 비교

전략 균등 분배 범위 쿼리 리밸런싱 복잡도
Range 낮음 좋음 어려움 낮음
Hash 좋음 어려움 어려움 낮음
Directory 좋음 가능 쉬움 높음
Consistent Hash 좋음 어려움 쉬움 중간

4. 샤딩 키 선택

4.1 좋은 샤딩 키의 조건

┌─────────────────────────────────────────────────────────────────┐
│                  좋은 샤딩 키 조건                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 높은 카디널리티 (High Cardinality)                          │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  좋음: user_id (수백만 개의 고유 값)                       │ │
│  │  나쁨: country (수십 개)                                   │ │
│  │  나쁨: status (몇 개)                                      │ │
│  │                                                            │ │
│  │  → 고유 값이 많아야 균등 분배 가능                         │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  2. 균등 분포 (Even Distribution)                               │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  좋음: 해시된 user_id                                      │ │
│  │  나쁨: 생성일 (신규 데이터가 한 샤드에 집중)               │ │
│  │  나쁨: 인기 상품 ID (특정 상품에 쿼리 집중)                │ │
│  │                                                            │ │
│  │  → 데이터와 쿼리가 균등하게 분배되어야 함                  │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  3. 쿼리 패턴과 일치                                            │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  대부분 쿼리: "WHERE user_id = ?"                          │ │
│  │  → user_id를 샤딩 키로!                                    │ │
│  │                                                            │ │
│  │  대부분 쿼리: "WHERE order_date BETWEEN ... AND ..."       │ │
│  │  → order_date를 샤딩 키로!                                 │ │
│  │                                                            │ │
│  │  → 자주 사용하는 쿼리가 단일 샤드에서 처리되도록           │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  4. 불변성 (Immutability)                                       │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  좋음: user_id (변하지 않음)                               │ │
│  │  나쁨: email (사용자가 변경할 수 있음)                     │ │
│  │  나쁨: status (변경됨)                                     │ │
│  │                                                            │ │
│  │  → 샤딩 키 변경 = 데이터 이동 (비용 큼)                    │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

4.2 샤딩 키 예시

┌─────────────────────────────────────────────────────────────────┐
│                   도메인별 샤딩 키 예시                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  소셜 미디어 (사용자 중심):                                     │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  • users: user_id                                          │ │
│  │  • posts: user_id (작성자)                                 │ │
│  │  • followers: user_id (팔로우 당하는 사람)                 │ │
│  │  • messages: conversation_id                               │ │
│  │                                                            │ │
│  │  → 사용자별 데이터가 같은 샤드에!                          │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  이커머스 (테넌트 중심):                                        │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  • products: merchant_id                                   │ │
│  │  • orders: user_id 또는 order_id                           │ │
│  │  • reviews: product_id                                     │ │
│  │                                                            │ │
│  │  주의: orders를 user_id로 하면                             │ │
│  │        판매자 주문 조회 시 모든 샤드 조회 필요             │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  SaaS (테넌트 중심):                                            │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  • 모든 테이블: tenant_id                                  │ │
│  │                                                            │ │
│  │  → 테넌트별로 완전 격리                                    │ │
│  │  → 크로스 테넌트 쿼리 불필요                               │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  로그/분석 (시계열):                                            │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  • logs: timestamp (Range 샤딩)                            │ │
│  │  • metrics: (device_id, timestamp)                         │ │
│  │                                                            │ │
│  │  → 범위 쿼리에 효율적                                      │ │
│  │  → 오래된 샤드 삭제 용이                                   │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

5. 핫스팟 방지

5.1 핫스팟 문제

┌─────────────────────────────────────────────────────────────────┐
│                    핫스팟 문제                                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  "특정 샤드에 트래픽/데이터가 집중되는 현상"                    │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  정상:                                                     │ │
│  │  ┌───────┐ ┌───────┐ ┌───────┐                            │ │
│  │  │Shard 1│ │Shard 2│ │Shard 3│                            │ │
│  │  │  33%  │ │  33%  │ │  33%  │                            │ │
│  │  └───────┘ └───────┘ └───────┘                            │ │
│  │                                                            │ │
│  │  핫스팟:                                                   │ │
│  │  ┌───────┐ ┌───────┐ ┌───────┐                            │ │
│  │  │Shard 1│ │Shard 2│ │Shard 3│                            │ │
│  │  │  80%  │ │  10%  │ │  10%  │ 💥 과부하!                  │ │
│  │  └───────┘ └───────┘ └───────┘                            │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  발생 원인:                                                     │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  1. Range 샤딩 + 순차 키                                   │ │
│  │     → 신규 데이터가 마지막 샤드에 집중                     │ │
│  │                                                            │ │
│  │  2. 인기 있는 엔티티                                       │ │
│  │     → 유명인 계정, 인기 상품                               │ │
│  │                                                            │ │
│  │  3. 시간 기반 패턴                                         │ │
│  │     → 특정 시간대에 특정 샤드 집중                         │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

5.2 핫스팟 방지 전략

┌─────────────────────────────────────────────────────────────────┐
│                  핫스팟 방지 전략                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. Hash 기반 샤딩 사용                                         │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  Range 대신 Hash로 균등 분배                               │ │
│  │  user_id: 1, 2, 3 → 같은 샤드가 아니라 분산               │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  2. 복합 샤딩 키                                                │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  단일 키: user_id                                          │ │
│  │  → 인기 사용자가 핫스팟                                    │ │
│  │                                                            │ │
│  │  복합 키: (user_id, post_id)                               │ │
│  │  → 같은 사용자의 포스트도 분산                             │ │
│  │                                                            │ │
│  │  shard = hash(user_id + "_" + post_id) % N                 │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  3. 솔팅 (Salting)                                              │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  원래 키: celebrity_123                                    │ │
│  │  솔팅:   celebrity_123_0                                   │ │
│  │          celebrity_123_1                                   │ │
│  │          celebrity_123_2                                   │ │
│  │                                                            │ │
│  │  # 쓰기: 랜덤 솔트 추가                                    │ │
│  │  salt = random(0, 10)                                      │ │
│  │  key = f"{user_id}_{salt}"                                 │ │
│  │                                                            │ │
│  │  # 읽기: 모든 솔트 조회 후 합치기                          │ │
│  │  for salt in range(10):                                    │ │
│  │      results += query(f"{user_id}_{salt}")                 │ │
│  │                                                            │ │
│  │  → 쓰기 분산, 읽기 복잡도 증가                             │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  4. 핫 엔티티 전용 처리                                         │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  • 인기 엔티티를 캐시 (Redis)                              │ │
│  │  • 별도의 전용 샤드/서버                                   │ │
│  │  • 비동기 처리 (카운터 등)                                 │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

6. 리밸런싱

6.1 리밸런싱이 필요한 경우

┌─────────────────────────────────────────────────────────────────┐
│                  리밸런싱 필요 상황                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 샤드 추가 (확장)                                            │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  Before: [Shard 1] [Shard 2] [Shard 3]                     │ │
│  │  After:  [Shard 1] [Shard 2] [Shard 3] [Shard 4]           │ │
│  │                                                            │ │
│  │  일부 데이터를 새 샤드로 이동                              │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  2. 샤드 제거 (축소)                                            │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  Before: [Shard 1] [Shard 2] [Shard 3]                     │ │
│  │  After:  [Shard 1] [Shard 2]                               │ │
│  │                                                            │ │
│  │  Shard 3 데이터를 다른 샤드로 이동                         │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  3. 불균형 해소                                                 │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  Before: [Shard 1: 80%] [Shard 2: 10%] [Shard 3: 10%]      │ │
│  │  After:  [Shard 1: 33%] [Shard 2: 33%] [Shard 3: 33%]      │ │
│  │                                                            │ │
│  │  Shard 1에서 다른 샤드로 데이터 이동                       │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

6.2 리밸런싱 전략

┌─────────────────────────────────────────────────────────────────┐
│                   리밸런싱 전략                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 일관성 해싱 (Consistent Hashing)                            │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  샤드 추가/제거 시 최소한의 데이터만 이동                  │ │
│  │  이전 레슨에서 학습                                        │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  2. 이중 쓰기 (Dual Write)                                      │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  1단계: 새 샤드 추가, 쓰기는 양쪽에                        │ │
│  │  ┌──────┐     ┌──────┐     ┌──────┐                        │ │
│  │  │Old S1│     │Old S2│     │New S3│                        │ │
│  │  │ R/W  │     │ R/W  │     │  W   │                        │ │
│  │  └──────┘     └──────┘     └──────┘                        │ │
│  │                                                            │ │
│  │  2단계: 기존 데이터 마이그레이션 (백그라운드)              │ │
│  │                                                            │ │
│  │  3단계: 읽기도 새 샤드로                                   │ │
│  │  ┌──────┐     ┌──────┐     ┌──────┐                        │ │
│  │  │Shard1│     │Shard2│     │Shard3│                        │ │
│  │  │ R/W  │     │ R/W  │     │ R/W  │                        │ │
│  │  └──────┘     └──────┘     └──────┘                        │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
│  3. 버전 기반 라우팅                                            │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  shard_config_v1:                                          │ │
│  │    range 1-1M → shard_1                                    │ │
│  │    range 1M-2M → shard_2                                   │ │
│  │                                                            │ │
│  │  shard_config_v2: (마이그레이션 후)                        │ │
│  │    range 1-700K → shard_1                                  │ │
│  │    range 700K-1.4M → shard_2                               │ │
│  │    range 1.4M-2M → shard_3                                 │ │
│  │                                                            │ │
│  │  # 새 쓰기는 v2, 기존 읽기는 v1                            │ │
│  │  # 마이그레이션 완료 후 v2로 전환                          │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

7. 연습 문제

문제 1: 샤딩 전략 선택

다음 서비스에 적합한 샤딩 전략을 선택하세요.

a) 채팅 앱 (대화방별 메시지) b) 로그 분석 시스템 c) 글로벌 사용자 서비스 d) SaaS 멀티 테넌트

문제 2: 샤딩 키 선택

이커머스 서비스에서 다음 테이블의 샤딩 키를 선택하세요.

  • users (id, email, name)
  • orders (id, user_id, status, created_at)
  • order_items (id, order_id, product_id, quantity)
  • products (id, merchant_id, name, price)

요구사항: - 사용자별 주문 조회 빈번 - 판매자별 상품 조회 필요 - 주문과 주문 항목은 함께 조회

문제 3: 핫스팟 해결

인기 게시글에 댓글이 집중되어 해당 샤드가 과부하입니다. 해결책을 제시하세요.

문제 4: 리밸런싱 계획

3개 샤드에서 5개 샤드로 확장하려고 합니다. 무중단 마이그레이션 계획을 세우세요.


정답

문제 1 정답

a) 채팅 앱: Hash(conversation_id)
   - 같은 대화방 메시지가 같은 샤드
   - 대화방 단위로 조회하므로 효율적

b) 로그 분석: Range(timestamp)
   - 시간 범위 쿼리가 효율적
   - 오래된 로그 샤드 삭제 용이

c) 글로벌 사용자: Hash(user_id)
   - 균등 분배
   - 사용자별 데이터 조회 효율적

d) SaaS 멀티 테넌트: tenant_id
   - 테넌트별 완전 격리
   - 크로스 테넌트 쿼리 불필요

문제 2 정답

users: Hash(user_id)
  - 기본 키로 사용
  - 균등 분배

orders: Hash(user_id)
  - user_id가  조회 조건
  - 사용자별 주문 조회 효율적

order_items: Hash(order_id)
  - 또는 Hash(user_id) (orders와 같은 샤드)
  - order_id를 사용하면 orders와 다른 샤드 가능
  -  user_id 권장 (orders와 같은 샤드)

products: Hash(merchant_id)
  - 판매자별 상품 조회
  - 판매자 단위 격리

크로스 샤드 주의:
- 주문의 상품 정보  캐시 활용
- 판매자 주문 조회  별도 인덱스 테이블

문제 3 정답

해결책:

1.  게시글 캐싱
   - 댓글을 Redis에 캐싱
   - 주기적으로 DB에 배치 저장

2. 솔팅 적용
   post_123_0, post_123_1, ...
   - 쓰기  랜덤 솔트
   - 읽기  모든 솔트 조회  합치기

3. 댓글 카운터 분리
   - 카운터만 Redis에서 관리
   - 비동기로 DB 업데이트

4. 샤딩  변경
   - (post_id, comment_id) 복합 키로
   - 같은 게시글 댓글도 분산

5. 전용 처리
   - 인기 게시글 감지
   - 전용 캐시 레이어 추가

문제 4 정답

무중단 마이그레이션 계획:

1단계: 새 샤드 준비
   - Shard 4, 5 서버 준비
   - 스키마 생성

2단계: 이중 쓰기 시작
   - 라우터 업데이트
   - 새 쓰기 → 기존 샤드 + 새 샤드 (일관성 해싱 기준)

3단계: 기존 데이터 마이그레이션
   - 백그라운드 복사
   - 일관성 해싱으로 대상 결정 (약 40% 이동)

4단계: 검증
   - 데이터 카운트 비교
   - 샘플 데이터 검증

5단계: 읽기 전환
   - 새 라우팅 규칙 활성화
   - 점진적 전환 (10% → 50% → 100%)

6단계: 이중 쓰기 중단
   - 기존 라우팅 제거

7단계: 정리
   - 기존 샤드에서 이동된 데이터 삭제

8. 다음 단계

데이터베이스 확장을 이해했다면, 데이터베이스 복제를 학습하세요.

다음 레슨

관련 레슨

추천 실습

  1. PostgreSQL 파티셔닝 실습
  2. 샤딩 라우터 구현해보기
  3. 일관성 해싱 구현

9. 참고 자료

도서

  • Designing Data-Intensive Applications - Ch. 6

데이터베이스

기업 사례


문서 정보 - 최종 수정: 2024년 - 난이도: ⭐⭐⭐ - 예상 학습 시간: 2-3시간

to navigate between lessons