Search

1장 사용자 수에 따른 규모 확장성

개요

시스템이 수백만명의 사용자 트래픽을 감당할 수 있도록 설계하는 것은 언제나 도전적인 과제이며 지속적인 계량과 개선이 요구되는 끊임없는 여정이다.
한 명의 사용자를 지원하는 시스템에서 수백만 명의 사용자를 지원하는 시스템이 되도록 설계하는 과정을 배워보자

단일서버

우측은 모든 컴포넌트가 단 한대의 서버에서 실행되는 간단한 시스템이다.
이 시스템에서 사용자의 요청이 처리되는 과정은 아래와 같다.
1.
사용자는 도메인 이름을 이용하여 웹 사이트에 접속한다.
1-1. 이 접속을 위해서는 DNS에 질의하여 도메인 이름을 IP주소로 변환하는 과정이 필요하다.
2.
DNS의 조회 결과로 IP주소가 반환된다. ( IP Address : 15.125.23.214)
3.
해당 IP 주소로 HTTP 요청이 전될된다.
4.
요청을 받은 웹 서버는 HTML 형식이나 JSON 형식으로 응답값을 반환한다.
웹 애플리케이션 : 비즈니스 로직, 데이터 저장 등을 처리하기 위해 서버는 Java, Python 등 다양한 언어를 사용하고 프레젠테이션용 클라이언트는 HTML이나 자바스크립트 등을 사용한다.
모바일 앱 : 모바일 앱과 웹 서버 간의 통신을 위해서는 HTTP 프로토콜을 이용한다. HTTP 프로토콜을 통해서 반환될 응답 데이터의 포맷으로는 대표적으로 JSON이 있다.

데이터베이스

사용자가 늘어나게 되면 하나의 서버에서 모든 요청을 처리하기 어렵다.
단일서버에서 더 많은 트래픽을 감당하기 위해 생각해 볼 수 있는 것은 웹/모바일 트래픽 처리 용도 서버 1개, 데이터베이스 서버 1개를 독립적으로 운용하는 것이다.
이 구조가 유리한 것은 웹 계층 서버와 데이터 계층의 서버를 독립적으로 확장해 나갈 수 있다는 것이다.

어떤 데이터베이스를 사용할 것인가?

그러면 데이터베이스는 어떤 데이터베이스를 사용해야 할지 고민해볼 필요가 있다.
크게는 전통적인 관계형 데이터베이스 (RDB)와 비-관계형 데이터베이스 (NoSQL)으로 분리하여 고민해 볼 수 있다.

RDBMS

관계형 데이터베이스는 대표적으로 MySQL, 오라클, PostgreSQL 등이 있다.
데이터를 테이블과 열, 칼럼으로 표현한다.
SQL을 사용하면 여러 테이블에 있는 데이터를 그 관계에 따라 조인하여 한 번에 가져오기도 한다.

NoSQL

대표적인 예로는 CouchDB, Neo4j, HBase 등이 있다.
NoSQL은 여기서 다시 크게 네 부류로 나눌 수 있다.
1.
Key-Value 저장소
2.
그래프 저장소
3.
칼럼 저장소
4.
문서 저장소
NoSQL에서는 일반적으로 조인 연산은 지원하지 않는다. 대부분의 개발자들은 RDBMS가 익숙하고 선호하는 경향이 있지만
구축하려는 시스템의 성질을 파악하고 성질에 맞는 데이터베이스를 선택해야 한다.
NoSQL을 고려해야 하는 경우
아주 낮은 응답 지연시간이 요구됨
다루는 데이터가 비정형이라서 관계형 데이터베이스에 담기에는 적합하지 않음.
데이터를 직렬화하거나 역직렬화 할 수 있기만 하면 됨
아주 많은 양의 데이터를 저장할 필요가 있는Sas 경우

Scale up(수직적 확장) VS Vertical scaling(수평적 확장) : 웹 계층화

그 다음에 고려해 볼 것은 수직적 확장 or 수평적 확장이다.

수직적 확장 (Scale up)

수직적 확장은 서버에 고사양 자원 (더 좋은 CPU, 더 많은 RAM 등)을 추가하여 서버의 트래픽 한계 용량을 늘리는 것을 말한다.
서버로 유입되는 트래픽의 양이 적을 때는 수직적 확장이 좋은 선택이 될 수 있다.
수직적 확장의 가장 큰 장점은 단순함이지만 심각한 단점이 여러 개 존재한다.
수직적 확장에는 한계가 존재한다. 한 대의 서버에 무한대로 CPU나 RAM을 증설할 수 없다.
장애에 대한 자동복구(failover)이나 다중화 방안을 제시하지 않는다. 서버에 장애가 발생하면 곧 서비스의 중단으로 이어진다.
이러한 단점들로 인해 보통 수직적 확장보다는 수평적 확장이 적절한 선택이다.

수평적 확장 (Vertical scaling)

수평적 확장은 한 서버와 동일한 인스턴스를 여러 개 띄워 성능을 개선하는 행위를 말한다.
수평적 확장은 여러 인스턴스에 고르게 트래픽을 분산시키기 위해 로드밸런서라는 것을 사용한다.
사용자는 로드밸런서의 Public IP 88.88.88.1로 접속한다.
웹 서버는 클라이언트의 접속을 직접 처리하지 않고 더 나은 보안을 위해 사설 IP 주소를 이용한다.
사설 IP 주소는 같은 네트워크에 속한 서버 사이의 통신에서만 사용할 수 있는 IP주소로 인터넷을 통해서 접속이 불가하다.
로드 밸런서는 웹 서버와 통신하기 위해 이 사설 주소를 이용한다.
옆의 그림처럼 서버1 이 다운되더라도 장애를 자동으로 복구하는 Auto Failover를 사용할 수 있고 웹 계층의 가용성은 보다 향상된다.

데이터베이스 다중화

위에서 수평적 확장과 수직적 확장을 통해 웹 계층화를 이루고 웹 계층의 가용성을 향상시켰다.
데이터 계층(데이터베이스)도 웹 계층처럼 다중화를 지원하여 가용성을 향상시키는 방법에는 무엇이 있을까?
보통은 서버 사이에 Master-Slave 관계를 설정하고 데이터 원본은 Master 서버Slave 서버는 데이터의 사본을 저장한다.
쓰기 연산은 Master서버에서만 지원하고 Slave서버는 사본을 전달받고 읽기 연산만을 지원한다.
대부분의 애플리케이션은 읽기 연산의 비중이 쓰기 연산보다 훨씬 높다. (읽기 연산 > 쓰기 연산)
따라서 통상적으로 Slave 데이터베이스의 수가 Master 데이터베이스의 수보다 많다.
옆의 그림처럼 데이터베이스를 다중화하면 얻을 수 있는 이점은 다음과 같다.
1.
성능 향상 : 데이터 쓰기 연산은 Master 서버로만 전달되고 읽기 연산은 Slave 서버로 분산되기 때문에 병렬로 처리되는 query의 수가 늘어 성능이 올라간다.
2.
안정성 : 데이터 베이스 서버 가운데 일부가 파괴되어도 남은 서버를 통해 데이터를 복구할 수 있다. 데이터를 물리적으로 떨어진 여러 장소에 다중화시킬 수 있다.
3.
가용성 : 데이터를 여러 지역에 복제해 둠으로써 하나의 데이터베이스 서버에 장애가 발생하더라도 다른 서버의 데이터를 가져와 서비스를 제공할 수 있다.

데이터베이스 다중화에 관련된 면접질문

1.
Slave 서버가 한 대 뿐인데 다운된 상태라면 어떻게 되는가?
Slave서버가 다운되면 한시적으로 읽기 연산도 모두 Master서버로 전달될 것이다. 그러는 사이 Slave 서버가 다시 가동되게 되고 자연스럽게 읽기 연산은 다시 Slave 서버로 전달될 것이다.
만약 Slave서버가 여러 대이고 한 대가 다운된다 하면 남은 Slave서버에 읽기 연산이 고르게 분산되어 전달될 것이다.
2.
만약 Master 서버가 다운된다면?
여러 Slave서버 중 한 서버가 Master 서버의 역할을 할 것이고 모든 데이터베이스 연산은 일시적으로 새로운 Master 서버에서 실행될 것이다. 그리고 새로운 Slave서버가 추가되게 된다.
프로덕션 상황에서는 이 보다 더 복잡한 프로세스를 거친다. 왜나하면 Master서버로 승격시키기로 한 Slave 서버의 데이터가 모두 최신상태가 아닐 수 있기 때문이다.
없거나 구 버전 데이터들은 데이터 복구 스크립트를 돌려서 추가하거나 갱신해야 한다.
오른쪽 그림대로 설계된 서버는 다음과 같이 동작한다.
a.
사용자는 DNS를 통해 도메인 주소를 로드밸런서의 Public IP로 변환하여 받는다.
b.
사용자는 해당 Public IP를 이용하여 로드밸런서에 접속한다.
c.
HTTP 요청은 로드밸런서에 의해 알맞게 분배되어 서버1이나 서버2로 전달된다.
d.
웹 서버는 사용자의 데이터를 Slave 데이터베이스 서버에서 읽어온다.
e.
만약 데이터를 갱신, 삭제, 생성을 하는 요청이라면 Matser 서버를 통해 요청을 수행한다.
여태까지 고려해본 것들을 적용한 서버의 모습은 아래와 같다.

캐시 (Cache)

캐시는 주로 응답시간을 개선하는데 사용한다. 응답 시간은 캐시를 사용하거나 정적 콘텐츠를 콘텐츠 전송 네트워크(Content Delivery Network, CDM)으로 옮기면 개선할 수 있다.
캐시는 값비싼 연산 결과 또는 자주 참조되는 데이터를 메모리 안에 두고 뒤이은 요청이 보다 빨리 처리될 수 있도록 하는 저장소이다.
웹 페이즈를 새로고침할 때마다 데이터를 가져오기 위해 한 번 이상의 데이터베이스 호출이 발생한다.
애플리케이션의 성능 중 가장 큰 부분을 차지하는 것은 데이터베이스에 얼마냐 접근하냐인데 캐시는 데이터베이스를 거치지 않고 메모리에서 데이터를 읽어와 응답하기 때문에 애플리케이션의 성능을 크게 향상시킬 수 있다.

캐시 계층

캐시 계층은 데이터가 잠시 보관되는 곳으로 데이터베이스보다 훨씬 빠르다.
별도의 캐시 계층을 두면 성능이 개선될 뿐만 아니라 데이터베이스에 가해지는 부하를 줄일 수 있다.
캐시 계층을 앞서 웹 계층이나 데이터 계층을 독립적으로 확장하듯이 마찬가지로 독립적으로 확장시킬 수 있다.
캐시 계층은 다음과 같이 동작한다.
1.
요청을 받은 웹 서버는 해당 데이터가 캐시에 저장되어 있는지 확인한다.
2.
만일 저장되어 있다면 데이터베이스를 거치지 않고 메모리에서 데이터를 읽어 클라이언트에 반환한다.
3.
없는 경우에는 데이터베이스에 쿼리를 통해 데이터를 찾은 후 캐시에 저장, 그리고 클라이언트에 반환한다.
이러한 캐시 전략을 읽기 주도형 캐시 전략이라 부른다.
이 외에도 다양한 캐시 전략이 존재하는데 캐시할 데이터의 종류, 크기, 액세스 패턴에 맞는 캐시 전략을 선택하면 된다.
캐시 서버를 이용하는 방법은 간단한데 대부분의 프로그래밍 언어는 API를 통해 이 기능을 제공하기 때문이다.

캐시 사용 시 유의해야 할 점

캐시는 어떤 상황에 바람직한가?
데이터의 갱신이 자주 일어나지 않고 참조가 빈번하게 일어난다면 캐시 사용에 적합하다.
캐시에 보관된 데이터는 어떻게 만료될까?
이에 대한 정책을 충분히 고려해보아야 한다. 만료된 데이터는 캐시에서 삭제되어야 하고 만료정책이 없다면 계속해서 남아있게 된다. 데이터가 만료되지 않고 계속 남아있는 상태에서 새로운 데이터가 계속해서 캐시에 들어오게 된다면 StackOverFlow 오류가 발생한다.
만료 주기에 대해 충분히 고려해야 한다.
주기를 너무 짧게 한다면? 캐시 사용의 이점은 희미해지고 데이터베이스에 대한 접근횟수는 증가할 것이다.
주기를 너무 길게 한다면? 원본 데이터와 캐시의 데이터 간의 불일치 문제가 발생할 수 있다.
즉, 캐시 데이터의 일관성StackOverFlow를 주의하도록 하는것이 가장 크게 고려할 점이다!
캐시 메모리의 크기는 어떻게 적절하게 선택할 것인가?
캐시 메모리가 너무 작다면 엑세스 패턴에 따라서는 데이터가 너무 자주 캐시에 밀려나버려 캐시의 성능이 떨어지게 된다.
캐시 메모리 과할당을 통해 캐시에 보관될 데이터가 갑자기 늘어날 경우에 생길 문제를 방지할 수 있다.
캐시 데이터 방출 정책은 어떻게 수립할것인가?
캐시가 꽉 차게 된다면 기존 데이터를 내보내고 새로운 데이터를 캐시에 적재해야 한다. 이를 캐시 데이터 방출 정책이라 하는데 가장 많이 쓰이는 방식은 아래와 같다.
LRU(Least Recently-Used)방식
사용된 시점이 가장 오래된 데이터를 내보내는 정책이다.
LFU(Least Frequently-Used)방식
사용된 빈도가 가장 낮은 데이터를 내보내는 정책이다.
FIFO(First In First Out) 방식
어떤 데이터를 캐시에 두어야 하는가?
캐시는 기본적으로 메모리이기 때문에 휘발성이다. 따라서 영구적으로 저장되어야 할 데이터는 캐시에 보관하는 것이 바람직하지 않다. 캐시 서버가 종료된 후 다시 시작되면 그 전의 데이터는 모두 삭제된다는 말이다! 중요한 데이터는 영속성을 보장해야 한다.
캐시 데이터와 DB 데이터의 일관성을 어떻게 유지할 것인가?
데이터베이스의 데이터를 갱신하는 연산과 캐시를 갱신하는 연산이 한 트랜잭션에서 이뤄지지 않으면 일관성이 깨질 수 있다.
두 연산을 한 트랜잭션에서 처리 : 가장 간단한 방법이지만 쓰기 연산의 성능이 저하될 수 있다.
캐시 무효화 : DB의 데이터가 갱신된 후 캐시의 데이터를 무효화하는 방법이다. 하지만 DB에 조회 횟수가 증가하는 단점이 존재한다.
분산 락 : redis와 같은 캐시 시스템에서 락을 걸어 DB와 캐시간의 갱신 순서를 보장할 수 있다.
최종적 일관성 : 캐시와 데이터베이스가 완벽하게 동기화되지 않더라도 결국 어느순간에는 일관성을 이루는 방식이다. 트랜잭션 성능에 영향을 미치지 않으면서도 궁극적으로는 일관성을 보장할 수 있지만 일정 시간동안은 일고나성을 보장하지 못한다는 단점이 있다.
TTL 설정 : 캐시된 데이터에 TTL을 설정하여 일정 시간이 지나면 자동적으로 캐시를 무효화되도록 설정할 수 있다. 오래된 캐시 데이터로 일관성이 깨지는 경우를 방지할 수는 있지만 캐시가 자주 무효화된다면 성능저하 문제가 발생한다.
메시지 큐를 사용한 비동기 갱신 : DB를 갱신한 후 메시지 큐(Kafka, RabbitMQ 등)을 사용해 캐시를 갱신하는 작업을 비동기적으로 처리하는 방식이다. 캐시의 일관성을 보장하면서도 비동기 작업으로 성능에 영향을 미치지 않는다는 장점이 있지만 메세지가 유실될 경우 메시지 재처리 로직이 필요하다.

콘텐츠 전송 네트워크 (CDN)

CDN은 정적 콘텐츠를 전송하는데 쓰이는 지리적으로 분산된 서버의 네트워크이다. 주로 이미지, 비디오, CSS, JavaScript파일 등을 캐시한다.
정적 콘텐츠를 더 이상 웹 서버를 통해 서비스하지 않으며 CDN을 통해 제공하여 더 나은 성능을 보장한다.
크게 중요한지는 모르겠어서 일단 이정도까지만 정리…

무상태 웹 계층 (Stateless)

웹 계층을 수평적으로 확장히 위해서는 상태 정보 (사용자 세션 데이터 등)을 웹 계층에서 제거해야 한다.
고려해볼 만한 것으로는 상태 정보를 DB에 저장하고 필요할 때 가져오도록 하는 것이다. 이렇게 구성된 웹 계층을 무상태 웹 계층이라 부른다.
무상태 웹 계층과 상태정보에 의존적인 웹 계층을 비교해보자.

상태정보에 의존적인 아키텍처

상태 정보를 보관하는 서버와 그렇지 않은 서버 사이에는 몇 가지 차이점이 있다.
상태 정보를 보관하는 서버는 클라이언트 정보, 즉 상태를 유지하여 요청들 사이에 공유되도록 한다.
만약 사용자 A의 상태 정보는 서버 1에 저장되어 있다고 가정해보자
사용자 A의 요청은 인증을 받기 위해서는 반드시 서버 1로 전송되어야 한다.
만약 서버2로 요청이 전송되게 되면 인증에 실패하게 될 것이다.
마찬가지로 사용자 B, 사용자 C도 자신의 상태정보가 저장되어있는 서버로만 요청이 전송되어야 인증에 성공할 것이다.

무상태 아키텍처

그림 A는 무상태 아키텍처의 구조를 표현한 그림이다.
이 구조에서는 사용자의 요청은 어떤 웹 서버로 전송되든 상관이 없다.
웹 서버는 사용자의 상태정보가 필요할 경우 공유 저장소로부터 해당 정보를 가져온다.
따라서 상태 정보는 웹 서버(웹 계층)으로부터 물리적으로 분리되어 있다.
이 구조는 단순하며 안정적이고 확장에도 용이하다.
그림 B를 보면 세션 데이터(상태 정보)를 웹 계층에서 분리하여 지속성 데이터 보관소에 저장도록 만들었다.
상태 정보를 웹 계층으로부터 분리시켰기 때문에 트래픽 양에 따라 웹 서버를 추가하거나 삭제할 수 있다.
이 서비스는 크게 성장하여 전 세계적으로 이목을 받고 글로벌 사용자도 크게 증가하게 되었다.
우리는 이 상황에서 전 세계 유저들에게 쾌적하고 안정적인 서비스를 제공할 수 있어야 한다.
이를 위해서는 여러 데이터 센터를 지원하는 것이 필수이다.
그림 A : 무상태 아키텍처
그림 B : 무상태 아키텍처까지 적용한 서버의 모습

데이터 센터 (Data Center)

데이터 센터는 기업이나 조직이 데이터를 저장하고 관리하며 IT 시스템을 운영하기 위해 사용하는 물리적 시설이다.
장애가 없는 상황이라고 가정한다면 사용자는 가장 가까운 데이터 센터로 안내되는데 이를 지리적 라우팅이라 한다.
지리적 라우팅에서 geoDNS는 사용자의 위치에 따라 도메인 이름을 어떤 IP 주소로 변환할지 결정해 주는 DNS 서비스이다.
오른쪽의 그림을 예로 들면 사용자는 위치에 따라 US-East 센터나 US-West 센터로 안내된다.
만약 데이터 센터 중 하나에 장애가 발생하면 모든 트래픽은 장애가 없는 데이터 센터로 전송되게 된다.
이처럼 다중 데이터센터 아키텍처를 만들기 위해서 몇 가지 고려해야할 사항이 있다.
1.
트래픽 우회 : 올바른 데이터 센터로 트래픽을 보내는 효과적인 방법을 찾아야 한다. GeoDNS는 사용자에게서 가장 가까운 데이터센터로 트래픽을 보낼 수 있도록 한다.
2.
데이터 동기화 : 데이터 센터마다 별도의 데이터베이스 서버를 가지고 있는 상황이라면 장애가 자동으로 복구되어 트래픽이 다른 데이터베이스로 우회된다고 하더라도 해당 데이터센터에는 찾는 데이터가 없을 수도 있다. 데이터를 여러 데이터센터에 걸쳐 다중화하는 방안이 있다.
3.
테스트와 배포 : 여러 데이터 센터를 사용하도록 시스템이 구성된 상황이라면 웹 사이트 또는 애플리케이션을 여러 위치에서 테스트하는 것이 중요하다.
시스템을 더 큰 규모로 확장하기 위해서는 시스템의 컴포넌트를 분리 (ex. 결제 서버, 주문 서버 등) 각기 독립적으로 확장될 수 있도록 해야 한다.
메시지 큐는 많은 실제 분산 시스템이 이 문제를 해결할 수 있는 핵심적인 전략 중 하나이다.
두 개의 데이터 센터를 이용하는 서버의 구조

메시지 큐

메시지 큐는 메시지의 무손실을 보장하는 비동기 통신 컴포넌트이다.
메시지의 버퍼 역할을 하며 비동기적으로 전송한다. 메시지 큐의 기본 아키텍처는 간단하다.
메시지 발행자 (Producer/Publisher)라고 불리는 입력 서비스가 메시지를 만들어 메시지 큐에 발행(Publish)한다. 큐에는 메시지 소비자(Consumber/Subscriber)라 불리는 서비스/서버가 연결되어 있는데 메시지를 받아 그에 맞는 동작을 수행한다.
메시지 큐를 사용하면 서비스 또는 서버 간 결합도가 감소하여 규모 확장성이 보장되어야 하는 애플리케이션을 구성하기 좋다.
생산자는 소비자 서버가 다운되어 있어도 메시지를 발행할 수 있고 소비자는 생산자 서버가 다운되더라도 메시지를 수신할 수 있다.

로그, 메트릭 그리고 자동화

몇 개 서버에서 실행되는 소규모 프로그램을 만들 때는 로그나 메트릭, 자동화 같은 것은 필수요소는 아니다.
하지만 규모가 큰 프로그램에서는 그런 도구가 필수적으로 요구된다.
로그 : 에러 로그를 모니터링하여 시스템의 오류와 문제가 발생하는 위치와 원인을 쉽게 찾아낼 수 있도록 해야 한다. 에러 로그는 서버 단위로 모니터링 할 수도 있지만 로그를 단일 서비스로 모아주는 도구를 활용하면 더 편리하게 검색하고 조회할 수 있다.
메트릭 : 메트릭을 잘 수집하면 사업 현황에 관한 유용한 정보를 얻을 수 있고 현재 상태를 손쉽게 파악할 수 있다.
호스트 단위 메트릭 : CPU, 메모리, 디스크 I/O에 관한 메트릭이 해당된다.
종합 메트릭 : 데이터베이스의 성능, 캐시의 성능 같은 것이 해당된다.
핵심 비즈니스 메트릭 : 일별 사용자, 수익, 재방문율 같은 것이 해당된다.
자동화 : 자동화를 통해 거대하고 복잡한 시스템의 생산성을 높일 수 있다. 예를 들면 CI/CD 같은 지속적 통합을 도와주는 도구를 활용하면 개발자가 작성한 코드가 자동적으로 검증 절차를 거쳐 빌드, 테스트, 배포까지 이어지도록 지원한다.

데이터베이스의 규모 확장

저장할 데이터가 많아지면 데이터베이스에 대한 부하도 증가하게 된다. 데이터베이스가 한계에 다달았을 때 데이터베이스를 증설할 방법을 고려해보아야 한다.

수직적 확장

웹 계층의 수직적 확장과 마찬가지로 데이터베이스 서버의 스펙을 올리는 것이다.
하지만 마찬가지로 단점이 존재한다.
1.
데이터베이스 서버 하드웨어도 마찬가지로 스펙을 올리는데 한계가 있다.
2.
SPOF (단일 실패지점)으로 인한 위험성이 매우 크다.
3.
고성능 서버에 대한 비용이 크게 증가한다.

수평적 확장

데이터베이스의 수평적 확장은 샤딩(Sharding)이라고 부른다.
더 많은 서버를 추가함으로 성능을 향상시킬 수 있다.
샤딩은 대규모 데이터베으스를 샤드라고 부르는 작은 단위로 분할하는 것을 말한다.
모든 샤드는 같은 스키마를 사용하지만 샤드에 보관되는 데이터에는 중복이 없다.
오른쪽 그림은 user_id를 4로 나눈값을 기준으로 각 번호에 맞는 샤드에 해당 데이터를 저장하는 것이다.

샤딩 전략을 구현할 때 고려해야 할 점

샤딩 전략을 구현할 때 가장 중요하게 고려해야 할 점은 샤딩 키를 어떻게 정할 것인가 이다.
샤딩 키는 파티션 키라고도 부르는데, 데이터가 어떻게 분산될지 정하는 하나 이상의 칼럼으로 구성된다.
샤딩을 예로 들었던 그림에서 샤딩키(파티션 키)는 user_id이다.
샤딩 키를 정할 때는 데이터를 고르게 분산시킬 수 있는지를 따져보는 것이 가장 중요하다.
샤딩은 이처럼 장점이 있지만 시스템이 복잡해지고 샤딩으로 파생되는 문제점도 존재한다.
데이터의 재 샤딩
데이터가 너무 많아져서 하나의 샤드로는 더 이상 감당하기 어려울 때 재 샤딩을 고려해야 한다.
샤드 간 데이터 분포가 균등하지 못할 경우 샤드 소진이라는 현상이 생기는데 이 경우 반드시 샤드 키를 계산하는 함수를 변경하고 데이터를 재배치 해야 한다.
유명인사 문제(핫스팟 키)
특정 샤드에 query가 집중되어 서버에 과부하가 걸리는 문제이다.
만약 유저들이 자주 검색하는 데이터들이 특정 샤드에 집중되어 있는 경우를 가정해 본다면 이 샤드에는 과부하가 걸릴 것이다.
따라서 user_id처럼 단순하게 데이터를 샤딩하는 것은 위 같은 문제를 야기시킨다.
조인과 비정규화
하나의 데이터베이스를 여러 샤드로 서버로 나누게 된다면 여러 샤드에 걸친 데이터를 조인하는것이 매우 힘들어진다.
데이터베이스를 비정규화하여 하나의 테이블에서 query가 수행될 수 있도록 하는 해결방법이 존재한다.

정리