How GitHub Sharded Their Databases Without Downtime and Broke Their Monolith
- GitHub은 단일 MySQL 클러스터의 확장성 문제(초당 100만 쿼리, 무작위 테이블 쿼리, 노이지 이웃 문제)에 직면했습니다. 🐘
- 초기 샤딩의 어려움은 복잡한 코드베이스와 교차 도메인 트랜잭션으로 인해 명확한 경계를 설정하기 어려웠기 때문입니다. 🚧
- 문제를 해결하기 위해 GitHub은 가상 파티셔닝과 물리적 파티셔닝의 2단계 전략을 채택했습니다. ✌️
- 가상 파티셔닝 (스키마 도메인): 물리적 분리 전에 논리적 경계를 강제하고 교차 도메인 통신을 제거하는 것이 목표였습니다. 🏷️
schema_domains.yml 파일을 통해 gist, repositories와 같은 논리적 스키마 도메인을 정의하고, 각 도메인 내에서만 쿼리가 이루어지도록 했습니다. 📁
- 개발/테스트 환경에서는 "쿼리 린터"를 사용하여 교차 도메인 조인을 감지하고 예외를 발생시켰으며, 예외는 코드에 주석으로 기록하여 격리 상태를 측정했습니다. 🛠️
- 운영 환경에서는 트랜잭션이 여러 도메인에 걸쳐 발생하는 경우를 감지하여 경고를 발생시켰지만, 차단하지는 않았습니다. 🚨
reactions와 같은 공유 테이블은 도메인별로 분할하거나 예외 처리하여 물리적 마이그레이션 준비를 마쳤습니다. 🧩
- 물리적 파티셔닝 (마이그레이션): 가상으로 격리된 도메인(예:
gist)을 기존 클러스터 A에서 새 클러스터 B로 이동하는 과정입니다. 🚀
- 주요 도구: 모든 클라이언트 연결을 처리하는 데이터베이스 프록시인 ProxySQL 🌐과 MySQL의 트랜잭션 복제 추적에 사용되는 GTID 🆔를 활용했습니다.
- 마이그레이션 설정:
- 클러스터 A에서
gist 테이블 스냅샷을 생성합니다. 📸
- 스냅샷을 클러스터 B의 프라이머리 및 레플리카에 로드하여 초기 데이터를 시드합니다. 🌱
- 클러스터 B 프라이머리에서 레플리카로의 복제를 설정합니다. 🔄
- 클러스터 A 프라이머리에서 클러스터 B 프라이머리로의 복제를 설정하여 클러스터 B가 클러스터 A의 레플리카 역할을 하도록 합니다. 🔗
- 클러스터 A와 B 모두에 ProxySQL을 설정합니다. 🚦
- 초기 트래픽 리디렉션:
gist 요청은 ProxySQL B로 보내지지만, ProxySQL B는 이 요청을 다시 ProxySQL A로 리디렉션하여 기존 클러스터 A에서 처리하도록 합니다. 이는 새로운 라우팅 경로를 테스트하는 단계입니다. ➡️
- 컷오버 (실제 전환):
- 클러스터 A와 B 간의 GTID 차이를 모니터링하여 복제 지연이 1초 미만으로 줄어들 때까지 기다립니다. ⏱️
- 클러스터 A 프라이머리에 읽기 전용 모드를 활성화하여 모든 새로운 쓰기 작업을 중단합니다. 이 짧은 기간 동안 모든 요청은 실패합니다 (5xx). 🛑
- 클러스터 B가 클러스터 A의 모든 변경 사항을 완전히 따라잡을 때까지 기다립니다. 🏃♀️
- 클러스터 A에서 클러스터 B로의 복제를 중단하여 클러스터 B를 독립적인 마스터로 만듭니다. ✂️
데브허브 | DEVHUB | How GitHub Sharded Their Databases Without Downtime and Broke Their Monolith