샤드와 샤딩 (Shard and Sharding)
샤드 (Shard: Database Architecture)
데이터베이스 샤드는 데이터베이스 또는 검색엔진의 수평적 파티션을 말합니다. 각각의 파티션을 샤드 또는 데이터베이스 샤드라고 합니다. 각 샤드는 별도의 데이터베이스 서버 인스턴스에 분리 저장되어 부하를 분산시킵니다.
어떤 데이터는 데이터베이스의 모든 샤드에 저장되어 있지만, 어떤 데이터는 하나의 샤드 안에만 존재하기도 합니다. 각 샤드는 데이터 집합의 단일 소스처럼 동작합니다.
샤딩 (Sharding)
수평 샤딩 (Horizontal Sharding)
샤딩을 말할 때 일반적으로 수평 파티셔닝 방식을 이야기합니다. 수평 파티셔닝 또는 샤딩은 스키마를 복제한 뒤 샤드키를 기준으로 데이터를 나누는 것을 말합니다. 데이터베이스를 샤딩하면 기존에 하나로 구성된 스키마를 다수의 복제본으로 구성하고, 각각의 샤드에 어떤 데이터가 저장될지를 샤드키를 기준으로 분리합니다. DBA는 데이터 접근 패턴과 저장 공간 이슈(부하의 적절한 분산, 데이터의 균등한 저장)를 고려하여 적절한 샤드키를 결정합니다.
수직 파티셔닝 (Vertical partitioning)
수직 파티셔닝은 스키마를 나누고 데이터가 따라 옮겨가는 것을 말합니다. 엔티티의 컬럼을 분할하는 방식입니다. 도메인에 따라 쉽게 분리할 수 있으며, 도메인의 영향을 많이 받기 때문에 애플리케이션 단에서 CRUD를 구현합니다. 사용자 ID별, 텍스트, 사운드, 비디오 등 데이터 타입별로 분류할 때 자주 사용합니다. 샤딩 구현이 간결하지만 데이터가 늘어나면 추가 샤딩이 필요합니다.
샤딩 적용 시 고려사항
데이터베이스를 운영하거나 애플리케이션을 개발할 때 샤딩을 적용하면 일반적인 운영보다 복잡한 구조를 갖게 됩니다. 가능하면 샤딩 없이 설계하거나 구조적으로 다른 방법을 찾는 편이 좋습니다.
샤딩보다 먼저 고려할 수 있는 방법은 다음과 같습니다. 스케일 업(수직 확장)으로 서버 사양을 높이거나, 스케일 아웃(수평 확장)으로 읽기 복제본을 추가할 수 있습니다. 캐시나 데이터베이스가 제공하는 자체 다중화 구성을 이용하는 편이 좋습니다. 테이블의 특정 컬럼만 자주 사용한다면, 수직 파티셔닝을 사용하는 편이 좋습니다. 데이터베이스의 데이터를 접근 빈도에 따라 Hot, Warm, Cold로 나누어 저장하는 방법도 있습니다.
데이터베이스에 데이터를 분산 저장할 때, 어느 한 노드에만 데이터가 몰리면 성능 저하가 발생합니다. 균일하게 데이터를 분산하는 설계가 필요합니다.
샤딩의 방법
범위 기반 샤딩 (Range based Sharding)
샤드키의 범위를 기준으로 데이터를 분할합니다. 예를 들어 사용자 ID가 1~1000이면 샤드1, 1001~2000이면 샤드2에 저장하는 방식입니다. 구현이 간단하고 데이터 분포를 예측하기 쉽지만, 특정 범위에 데이터가 집중되면 특정 샤드에 부하가 몰릴 수 있습니다.
해시 기반 샤딩 (Hash based Sharding)
엔티티를 해시 함수에 넣어 나오는 출력을 기준으로 분할합니다. 특정 영역으로 데이터 값이 몰린 경우 성능 저하가 발생합니다. 해시 결과가 균등하게 분포되도록 신중한 해시 함수 선정이 필요합니다. 클러스터가 포함하는 노드의 수가 증가하거나 감소하면 해시의 크기가 변하게 되고, 해시 키 또한 변하게 됩니다. 기존에 있던 해시 키에 따라 분배된 데이터 분산 규칙이 모두 어긋나게 되고, 리샤딩(Re-Sharding) 작업이 필요합니다.
동적 샤딩 (Dynamic Sharding)
이름 그대로 동적으로 바꿀 수 있습니다. Locator Service를 통해 데이터가 어느 샤드에 있는지 조회합니다. 클러스터에 노드가 증가하거나 감소해도 Locator Service의 매핑 테이블만 갱신하면 됩니다. 기존 데이터의 샤드 키 변경은 없기 때문에 확장에 유연한 구조입니다. 데이터를 재배치하게 되면 Locator Service의 샤드 키 테이블도 일치시켜줘야 합니다. Locator가 성능을 위해 캐시하거나 복제하면 잘못된 라우팅을 통해 데이터를 찾지 못하고 에러가 발생합니다. Locator에 의존할 수밖에 없는 단점이 있습니다.
Key-Value가 아닌 다양한 객체들로 구성되는 경우도 있습니다. RDBMS의 join, index, transaction을 사용하면 애플리케이션의 복잡도를 줄일 수 있습니다. 이와 유사한 방법으로 샤딩하는 방법이 Entity Group입니다. 하나의 물리적인 샤드에 쿼리를 진행한다면 효율적입니다. 하나의 샤드에서 강한 응집도를 가질 수 있습니다. 데이터는 자연스럽게 사용자별로 분리되어 저장됩니다. 사용자가 늘어남에 따라 확장성이 좋은 파티셔닝입니다.
반대로 cross-partition 쿼리는 single partition 쿼리보다 일관성(consistency) 보장과 성능을 보장하지 않습니다. 그렇기 때문에 이런 쿼리들이 자주 실행되지 않도록 만들어야 합니다.
데이터베이스별 샤딩 지원
MySQL은 서버 자체에 내장 샤딩 기능이 없습니다. MySQL Router와 InnoDB Cluster는 고가용성과 읽기 분산을 위한 복제 솔루션이며 샤딩이 아닙니다. MySQL Fabric은 5.7 이전에 제공되던 샤딩 프레임워크였으나 현재는 제거되었습니다. 샤딩이 필요하면 애플리케이션 레벨에서 구현하거나 Vitess 같은 외부 솔루션을 사용해야 합니다.
MariaDB는 Spider 스토리지 엔진을 통해 샤딩을 구현할 수 있습니다. Spider는 원격 MariaDB 서버의 테이블을 링크하는 방식으로 동작하며, 파티셔닝과 XA 트랜잭션을 지원합니다.
PostgreSQL은 코어에 샤딩 기능이 없지만 postgres_fdw와 선언적 파티셔닝을 조합하거나 Citus 같은 확장을 통해 분산 구성을 만들 수 있습니다.
Redis, MongoDB 같은 NoSQL 데이터베이스는 샤딩을 기본 기능으로 제공합니다.
LINE 엔지니어링 블로그에서는 LINE MANGA 서비스의 MySQL 샤딩 구축 사례를 소개하고 있습니다. 사용자 ID 기반 8분할 구성, crc32() 해시 함수와 RANGE 파티셔닝 조합, EXCHANGE PARTITION을 활용한 마이그레이션 전략을 다룹니다.