본문으로 바로가기

22

샤딩

한 권으로 안 되면 여러 권으로 나눈다 — 다만 나누는 기준이 전부다

먼저 보면 좋은 강의: 지연 시간과 처리량, 데이터베이스 인덱스, 데이터베이스 복제

단계별로 보기

스페이스바로 재생/정지, 좌우 화살표로 단계 이동, R로 처음으로 돌아갑니다.

대기 중1 / 4 단계 — 복제로는 넘을 수 없는 벽

쓰기가 초당 300건 들어옵니다. 원본은 하나뿐이고 한계에 부딪혔습니다. 여기서 사본을 아무리 늘려도 소용이 없습니다. 기록은 원본에만 할 수 있고, 사본이 늘수록 원본은 알려줄 곳이 많아져 오히려 더 바빠집니다.

사용률100.0%
오류율60.0%

이 강의를 마치면

  • 복제로는 해결되지 않는 쓰기 부하를 샤딩이 어떻게 나누는지 설명한다.
  • 키를 기준으로 데이터를 여러 데이터베이스에 나누는 구조를 직접 구성한다.
  • 샤드 키 선택이 잘못되면 특정 샤드만 과부하가 되는 이유를 설명한다.
  • 샤딩이 가져오는 대가(여러 샤드에 걸친 질의의 어려움)를 판단한다.

복제로는 못 고치는 병

앞 강의에서 사본을 여러 권 만들어 읽기 부하를 나눴습니다. 그런데 이번엔 기록이 초당 5,000건씩 들어옵니다. 원본 장부는 초당 1,000건이 한계입니다.

사본을 100권 만들면 될까요? 안 됩니다. 기록은 원본에만 할 수 있으니까요. 사본을 늘리면 원본은 오히려 100권에 변경 사항을 알려주느라 더 바빠집니다.

약이 병에 안 맞는 겁니다. 복제는 읽기 병의 약이고, 지금 앓는 건 쓰기 병입니다.

그러면 장부를 쪼개자

유일한 방법은 원본 자체를 여러 개로 나누는 것입니다.

고객 성씨 ㄱ~ㅁ  →  1번 장부
고객 성씨 ㅂ~ㅅ  →  2번 장부
고객 성씨 ㅇ~ㅈ  →  3번 장부
고객 성씨 ㅊ~ㅎ  →  4번 장부

각 장부는 자기 몫에 대해서는 완전한 원본입니다. 사본이 아닙니다. 1번 장부에 기록하는 동안 2번 장부에도 동시에 기록할 수 있습니다. 장부가 4개면 기록 능력도 4배입니다. 이걸 샤딩이라고 하고, 각 조각을 샤드라고 부릅니다.

이제 장부를 늘리는 만큼 쓰기가 늘어납니다. 복제로는 절대 얻을 수 없던 것입니다.

그런데 누가 어느 장부인지 어떻게 아나

요청이 들어오면 어느 샤드로 갈지 정해주는 존재가 필요합니다. "김철수의 정보를 줘"라는 요청이 오면, 이름을 보고 "1번 장부"라고 판단합니다.

중요한 것은 이 판단이 매번 같아야 한다는 점입니다. 김철수를 저장할 때 1번에 넣었는데 꺼낼 때 3번을 뒤지면 영영 못 찾습니다. 그래서 이 판단은 키를 계산해서 나온 항상 같은 답이어야 합니다. 이 계산 방식이 다음 강의의 주제인 일관된 해시입니다.

이 강의의 캔버스에서는 샤드를 여러 개의 데이터베이스로 두고, 그 앞의 로드밸런서에 '일관된 해시' 방식을 지정해서 샤드 라우터 역할을 시킵니다. 실제 시스템에서는 이 역할을 전용 프록시나 애플리케이션 코드가 맡습니다.

무엇을 기준으로 나눌 것인가 — 여기가 전부다

샤딩에서 유일하게 중요한 결정은 "무엇을 기준으로 나눌 것인가"입니다. 이걸 샤드 키라고 합니다. 그리고 한 번 정하면 바꾸기가 매우 어렵습니다.

나쁜 예를 봅시다. 가입 연도로 나눴다고 합시다.

샤드담당실제 트래픽
1번2021년 가입자거의 없음
2번2022년 가입자거의 없음
3번2023년 가입자조금
4번2024년 가입자폭주

신규 가입자는 전부 4번으로 갑니다. 샤드를 4개로 늘렸는데 일하는 건 하나뿐입니다. 이걸 핫스팟이라고 합니다. 나머지 3대는 돈만 쓰고 놀고 있습니다.

좋은 샤드 키의 조건은 두 가지입니다.

  1. 고르게 퍼진다 — 어느 샤드에도 몰리지 않는다 (고객 ID처럼 값이 다양한 것)
  2. 대부분의 질의에 그 키가 들어 있다 — 안 그러면 모든 샤드를 다 뒤져야 한다

샤딩의 대가

공짜가 아닙니다. 데이터가 흩어진 순간 여러 샤드에 걸친 질문이 어려워집니다.

  • "김철수의 주문 내역" → 쉽습니다. 김철수의 샤드 하나만 봅니다.
  • "지난달 전체 매출" → 4개 샤드를 전부 조회해서 합산해야 합니다.
  • "A의 잔액을 B에게 이체" → 두 사람이 다른 샤드에 있으면 매우 어렵습니다.

한쪽만 성공하고 다른 쪽이 실패하면 돈이 사라집니다.

마지막 항목이 특히 중요합니다. 하나의 데이터베이스 안이라면 이 문제는 트랜잭션이 해결해 줍니다. 그게 다음다음 강의의 주제입니다. 그런데 샤드가 갈리는 순간 그 안전장치가 사라집니다.

그래서 순서가 중요합니다. 샤딩은 마지막 카드입니다. 인덱스로 안 되고, 캐시로 안 되고, 복제로도 안 될 때 꺼내는 것입니다. 처음부터 샤딩하는 설계는 대부분 필요 없는 고통을 자초하는 일입니다.

이 강의가 단순화한 것

교육용 모형은 언제나 무언가를 생략합니다. 무엇을 생략했는지 아는 것도 학습의 일부입니다.

전제한 것

  • 이 강의의 캔버스에는 전용 샤드 라우터 컴포넌트가 없어서, '일관된 해시'로 설정한 로드밸런서를 샤드 라우터 대신 쓴다. 실제 샤드 라우팅은 로드밸런싱과 목적이 다르다 — 로드밸런서는 아무 서버나 골라도 되지만 샤드 라우터는 반드시 정해진 한 곳을 찾아가야 한다.
  • 각 샤드를 독립된 주 데이터베이스로 표현하며, 샤드마다 복제본을 두는 실제 구성(샤딩 + 복제 조합)은 생략한다.
  • 샤드 키는 요청에 딸린 키 값으로 이미 정해져 있다고 본다. 현실에서는 어떤 필드를 샤드 키로 삼을지 정하는 일이 설계의 대부분이다.
  • 샤드를 추가할 때 기존 데이터를 옮기는 재배치 작업은 이 강의에서 시간이 들지 않는 것으로 본다. 실제로는 이 이사가 몇 시간에서 며칠까지 걸린다.

다루지 않은 것

  • 범위 기반 샤딩과 해시 기반 샤딩의 비교
  • 샤드 간 분산 트랜잭션과 2단계 커밋
  • 샤드 재배치(resharding)와 무중단 데이터 이전
  • 글로벌 보조 인덱스와 스캐터-개더 질의

더 읽을거리: Vitess — Sharding Documentation

이제 직접 만들어 보세요

읽어서 아는 것과 만들어서 아는 것은 다릅니다. 컴포넌트를 배치하고 트래픽을 흘려서 실제로 동작하는지 확인해 보세요.

실습 과제 열기

이 강의에 물어보기

하루 5번

이 강의 본문만 근거로 답합니다. 강의에 없는 내용은 지어내지 않습니다.