Buildkite의 데이터베이스 샤딩 여정은 여러 단계로 진행되었습니다.
1. 문제 인식 및 초기 대응
-
2019년: 창립 엔지니어 Sam Cochran이 데이터베이스 스토리지 위험을 경고하며 샤딩의 필요성을 예고했습니다.
-
2022년 말: Matt의 분석에 따르면 주 데이터베이스의 여유 공간이 12TB밖에 남지 않았고, 플랫폼 사용량이 50% 증가하며 부하 문제가 심각해졌습니다. 최악의 경우 2023년 8월에 스토리지 고갈이 예상되었습니다.
-
초기 노력:
artifacts테이블을 별도 데이터베이스로 분리하여 10TB를 확보하고, 테이블 파티셔닝 등의 노력을 기울였으나 근본적인 해결책은 아니었습니다.
2. 샤딩 전략 수립 및 데이터 분리
-
2022년 말 AWS 워크샵: 콜드 스토리지, 셀룰러 아키텍처 등 다양한 논의 끝에 샤딩이 가장 현실적인 해결책으로 결정되었습니다.
-
Foundation 데이터베이스 분리: 기존 메인 데이터베이스인
Greyhound에 파이프라인 데이터와 조직 관련 데이터가 혼재되어 있었습니다. 샤딩을 위해 조직 데이터를foundation데이터베이스로 분리했습니다. 이 과정에서 RDS 디스크 공간은 줄일 수 없어 비용 절감 효과는 없었지만, 샤딩을 위한 명확한 데이터 경계를 설정했습니다.
3. Rails 기반 수평 샤딩 구현
-
Active Record Sharding: Rails의
connects_to shards기능을 활용하여 여러 샤드를 정의하고,connected_to(shard: :shard_name)을 통해 특정 샤드에 연결하도록 구현했습니다. -
기본 샤드 강제: 복잡한 시스템에서 의도치 않은 샤드에 쓰기(split brain)를 방지하기 위해
ActiveSupport::Notifications를 사용하여 기본 샤드에 대한 쓰기 작업을 감지하고 오류를 발생시키는 메커니즘을 도입했습니다. -
헬퍼 메서드:
Pipelines::Record.for(organization)와 같은 헬퍼를 만들어 특정 조직에 대한 요청을 해당 샤드로 라우팅하도록 했습니다.
4. 데이터 마이그레이션 기술 및 무결성 확보
-
데이터 이동: WAL(Write Ahead Log) 기반 논리적 복제 대신 PostgreSQL의
COPY명령어를 바이너리 포맷으로 사용하여 데이터를 이동했습니다. 이를 10개의 병렬 프로세스로 분할하여 빠르게 처리했습니다. -
IN32 -> IN64 변환: 기존 IN32 ID를 IN64로 변환하기 위해 바이너리 데이터 스트림 내에서 필드 길이와 값을 변환하는 인플라이트(in-flight) 변환을 수행했습니다.
-
정수 ID 시퀀스 동기화: 샤드 간 ID 충돌을 방지하기 위해 각 샤드 시퀀스의
INCREMENT BY와START WITH값을 조절하는 인터리브(interleaved) 시퀀스 방식을 도입했습니다. -
데이터 무결성: 마이그레이션 중에는 조직 상태를
draining(새 빌드 생성 방지) 및locked(서비스 중단 페이지 노출)로 설정하여 쓰기 작업을 방지했습니다.
5. 배경 작업자(Psychic) 샤딩 및 Greyhound 은퇴
-
배경 작업자 문제 해결: 데이터베이스 샤딩 후에도 공유 리소스인 배경 작업자(Psychic) 큐가 병목이 되어 ‘시끄러운 이웃(noisy neighbor)’ 문제를 유발했습니다. 이를 해결하기 위해 Psychic 클라이언트 미들웨어를 통해 작업 페이로드에 따라 큐를 다시 작성하여 샤드별 전용 큐를 사용하도록 했습니다.
-
Greyhound 은퇴 자동화: 수동 마이그레이션 방식은 대규모 조직에 적합하지 않아, 모든 조직을 이동시키기 위해 마이그레이션, 검증, 예약 시스템 등을 자동화하는 상태 머신을 구축했습니다.
-
최종 은퇴: 모든 조직이 이동된 후, Buildkite 파이프라인을 사용하여 Greyhound 데이터베이스를 최종적으로 종료하는 이벤트를 진행했습니다. 이 과정에서 수동으로 각 단계가 승인되어야 하는 블록 스텝(block step)을 활용하여 모든 팀원이 참여하도록 했습니다.