GN⁺: Dropbox의 자체 로드 밸런싱 서비스 Robinhood
(dropbox.tech)- Robinhood는 Dropbox의 내부 로드 밸런싱 서비스로 2020년에 배포됨
- 서버 간 내부 트래픽을 라우팅하여 서비스 로드를 균형있게 분산함
- Robinhood 이전에는 대부분의 Dropbox 서비스가 백엔드 간 불균형한 로드 분포로 어려움을 겪음
- 하드웨어 차이와 이전 로드 밸런싱 알고리듬의 한계로 인해 과부하 인스턴스로 인한 신뢰성 문제가 발생함
- 이를 해결하기 위해 서비스 플릿을 과도하게 프로비저닝해야 했고, 이는 하드웨어 비용 증가로 이어짐
Robinhood의 새로운 기능
- PID(Proportional-Integral-Derivative) 컨트롤러를 활용하여 로드 불균형을 더 빠르고 효과적으로 관리할 수 있게 됨
- 이는 인프라의 신뢰성 개선과 상당한 하드웨어 비용 절감으로 이어짐
- 최신 지능형 기능을 구동하는 AI 워크로드 증가로 GPU 리소스에 대한 효과적인 수요 관리가 그 어느 때보다 중요해짐
Dropbox에서의 로드 밸런싱 과제
- Dropbox의 서비스 디스커버리 시스템은 전 세계 여러 데이터 센터에 걸쳐 수십만 대의 호스트로 확장 가능함
- 일부 Dropbox 서비스는 수백만 개의 클라이언트를 보유하고 있지만, 각 클라이언트가 모든 서버 인스턴스에 연결하도록 허용할 수는 없음
- 이는 서버에 너무 많은 메모리 압력을 가하고, 서버 재시작 시 TLS 핸드셰이크로 인해 서버가 과부하될 수 있기 때문
- 대신 서비스 디스커버리 시스템을 사용하여 각 클라이언트에 연결할 서버 하위 집합을 제공함
- 클라이언트가 사용할 수 있는 최선의 로드 밸런싱 전략은 서비스 디스커버리 시스템에서 제공하는 주소 목록을 라운드 로빈하는 것임
- 그러나 이 방법으로는 각 서버 인스턴스의 로드가 상당히 불균형할 수 있음
- 하위 집합 크기를 늘리는 것은 쉬운 완화책이지만 불균형을 완전히 제거하지는 못하며 서비스 소유자에게 또 다른 매개변수를 제공할 뿐임
- 더 깊은 문제는 각 서버로 동일한 수의 요청을 보내더라도 기본 하드웨어가 서버마다 다를 수 있다는 것
- 즉, 요청이 서로 다른 하드웨어 클래스에서 다른 양의 리소스를 소비함
- 핵심은 클라이언트가 서버 로드에 대한 가시성이 없다는 것
- 과거에는 서버가 응답 헤더에 로드를 첨부하도록 하여 이 문제를 해결하려고 시도함
- 클라이언트는 주소 하위 집합에서 가장 적게 로드된 엔드포인트를 선택하여 직접 로드 밸런싱을 수행할 수 있음
- 결과는 유망했지만 여전히 몇 가지 단점이 있었음
- 특수 로드 헤더에 대해 서버와 클라이언트 모두 코드 변경이 필요했기 때문에 전 세계적으로 채택하기 어려웠음
- 결과는 좋았지만 충분히 좋지는 않았음
Robinhood 구축 결정
- 2019년 Robinhood 구축을 공식적으로 결정함
- 이 새로운 서비스는 기존 내부 서비스 디스커버리 시스템 위에 구축되어 서버에서 로드 정보를 수집하고 라우팅 정보에 첨부함
- Robinhood는 Envoy의 Endpoint Discovery Service를 활용하여 로드 정보를 엔드포인트 가중치에 통합하므로 클라이언트가 가중치 기반 라운드 로빈을 수행할 수 있음
- gRPC 커뮤니티가 Envoy xDS 프로토콜을 채택하고 있어 Robinhood는 Envoy 및 gRPC 클라이언트와 모두 호환됨
- 당시 Dropbox의 요구사항을 충족하는 기존 로드 밸런싱 솔루션이 없었기 때문에 새로운 서비스를 구축하기로 결정함
Robinhood의 성과
- 프로덕션 환경에서 몇 년 사용한 결과 유망한 결과를 보임
- 일부 대규모 서비스의 플릿 크기를 25% 줄이는 데 성공하여 매년 상당한 하드웨어 비용을 절감함
- 과도하게 활용되는 프로세스가 줄어들어 신뢰성도 향상됨
Robinhood의 아키텍처
- 각 데이터 센터에 Robinhood 인스턴스가 배포되며 로드 밸런싱 서비스, 프록시, 라우팅 데이터베이스의 세 부분으로 구성됨
로드 밸런싱 서비스(LBS)
- Robinhood의 핵심
- 로드 정보를 수집하고 엔드포인트 가중치가 포함된 라우팅 정보를 생성하는 역할
- 여러 인스턴스가 서비스에 대한 라우팅 정보를 동시에 업데이트하므로 내부 shard manager를 사용하여 각 서비스에 대한 기본 작업자를 할당함
- 각 서비스가 독립적이므로 서비스별로 LBS를 분할하고 수평으로 확장할 수 있음
프록시
- 서비스의 로드 정보를 데이터 센터 내 해당 LBS 파티션으로 라우팅하는 역할
- 프록시를 사용하면 LBS 프로세스에 직접 연결되는 수도 줄일 수 있음
- 프록시가 없으면 모든 LBS 프로세스가 인프라 내 모든 노드에 연결되어야 함
- 대신 LBS 프로세스는 프록시에만 연결되므로 LBS의 메모리 압력이 크게 줄어듬
- 프록시는 동일한 데이터 센터 내 노드에만 연결되므로 수평으로 확장 가능
- 이 패턴은 너무 많은 TLS 연결을 수신하지 않도록 서비스를 보호하기 위해 인프라의 많은 부분에서 사용됨
라우팅 데이터베이스
- 호스트 이름, IP 주소, LBS에서 생성한 가중치 등 서비스에 대한 라우팅 정보를 저장하는 ZooKeeper/etcd 기반 데이터베이스
- ZooKeeper와 etcd는 노드/키 변경 사항을 실시간으로 모든 감시자에게 알릴 수 있으며 Dropbox의 읽기 중심 서비스 검색 사용 사례에 매우 적합함
- ZooKeeper/etcd에서 보장하는 최종 일관성은 서비스 검색에도 충분함
로드 밸런싱 서비스 자세히 살펴보기
- 로드 밸런싱의 목표는 모든 노드의 활용률이 평균 활용률과 같도록 하는 것
- PID 컨트롤러를 사용하여 각 노드의 활용률을 평균 활용률과 거의 동일하게 유지
- LBS는 각 노드에 대해 PID 컨트롤러를 생성하고 평균 활용률을 설정점으로 사용
- LBS는 PID 컨트롤러의 출력을 엔드포인트 가중치에 대한 델타로 사용하고 서비스의 모든 엔드포인트 간에 가중치를 정규화함
- 새 노드가 평균 활용률에 수렴하는 데 몇 번의 조정이 필요하지만 PID 컨트롤러는 로드 밸런싱에 매우 효과적임
LBS 동작 시나리오
- LBS는 노드 재시작부터 로드 보고서 누락에 이르기까지 로드 밸런싱에 영향을 줄 수 있는 다양한 시나리오를 처리하도록 설계됨
- 최적의 성능을 유지하기 위해 LBS는 이러한 예외 사례를 처리하기 위한 몇 가지 전략을 구현함
LBS 시작
- LBS는 로드 정보와 PID 컨트롤러 상태를 메모리에 보관함
- LBS 재시작 중에는 즉시 가중치 업데이트를 시작하지 않고 로드 보고서가 들어올 때까지 잠시 기다림
- PID 컨트롤러 가중치의 경우 LBS는 라우팅 데이터베이스에서 엔드포인트 가중치를 읽어 복원함
Cold Start 노드
- 서비스 플릿에 새 노드가 자주 참여하므로 Thundering Herd 문제를 방지하는 것이 중요
- 새 노드의 초기 활용률이 일반적으로 0이므로 LBS는 새 노드의 가중치를 낮은 엔드포인트 가중치로 설정하고 PID 컨트롤러가 노드를 평균 활용률까지 끌어올리도록 함
누락된 로드 보고서
- 분산 시스템 환경에서는 장애가 일반적임
- 네트워크 정체 또는 하드웨어 장애로 인해 일부 노드의 로드 보고서가 지연되거나 도착하지 않을 수 있음
- LBS는 가중치 업데이트 중에 이러한 노드를 건너뛰므로 해당 노드의 엔드포인트 가중치는 변경되지 않음
- 그러나 대량의 로드 보고서가 누락되면 평균 활용률 계산이 부정확해질 수 있음
- 안전을 위해 LBS는 이 경우 가중치 업데이트 단계를 완전히 건너뜀
활용률 메트릭
- CPU 활용률은 Dropbox에서 가장 널리 사용되는 로드 밸런싱 메트릭
- CPU로 병목 현상이 발생하지 않는 서비스의 경우 진행 중인 요청 수가 좋은 대체 측정값
- LBS는 CPU 및/또는 진행 중인 요청을 기반으로 로드 밸런싱을 지원하도록 구현됨
제한사항
- PID 컨트롤러는 노드의 활용률을 목표값(평균 활용률)에 가깝게 유지하기 위해 피드백 루프를 구성함
- 트래픽이 매우 적은 서비스 또는 분 단위로 측정되는 매우 높은 지연 시간 요청과 같이 피드백이 거의 없는 경우 로드 밸런싱이 효과적이지 않음
- 높은 지연 시간 요청이 있는 서비스는 비동기식이어야 함
데이터 센터 간 라우팅
- LBS 인스턴스는 데이터 센터 내에서 로드 밸런싱을 처리함
- 데이터 센터 간 라우팅에는 다른 고려 사항이 있음
- 예를 들어 요청의 왕복 시간을 줄이기 위해 요청을 가장 가까운 데이터 센터로 라우팅하려고 함
- 이를 위해 대상 데이터 센터 간 트래픽 분할을 정의하는 지역성 구성을 도입함
로드 밸런서 성능 평가
- 로드 밸런싱 성능은 max/avg비율로 측정함
- 서비스 소유자가 CPU를 기반으로 로드 밸런싱을 선택하면 maxCPU/avgCPU를 성능 지표로 사용
- 서비스 소유자는 일반적으로 노드 간 최대 활용률을 기준으로 서비스를 프로비저닝하며, 로드 밸런싱의 주요 목적은 플릿 크기를 줄이는 것이기 때문
- PID 컨트롤러 로드 밸런싱 전략은 max/avg 비율을 1에 가깝게 유지할 수 있음
로드 밸런싱 성능 평가 그래프
- Envoy 프록시 클러스터 중 가장 큰 클러스터의 max/avg CPU 및 p95/avg CPU를 보여주는 그래프
- PID 컨트롤러 기반 로드 밸런싱을 활성화한 후 두 메트릭이 1에 가깝게 떨어짐
- max/avg 비율이 1.26에서 1.01로 떨어져 20% 개선을 보여줌
- 노드별 CPU 활용률의 분위수 분석을 보여주는 그래프
- PID 컨트롤러 기반 로드 밸런싱을 활성화한 후 max, p95, avg, p5가 거의 하나의 선으로 통합됨
- 데이터베이스 프론트엔드 클러스터 중 가장 큰 클러스터의 max/avg CPU 및 p95/avg CPU를 보여주는 또 다른 그래프
- PID 컨트롤러 기반 로드 밸런싱을 활성화한 후 두 메트릭이 1에 가깝게 떨어짐
- max/avg 비율이 1.4에서 1.05로 떨어져 25% 개선을 보여줌
- 노드별 CPU 활용률의 분위수 분석을 보여주는 또 다른 그래프
- PID 컨트롤러 기반 로드 밸런싱을 활성화한 후 max, p95, avg, p5가 다시 한 번 거의 하나의 선으로 통합됨
Config Aggregator 구축 이유
- Robinhood는 서비스 소유자가 선택할 수 있는 여러 옵션을 제공하며 동적으로 변경 사항을 적용할 수도 있음
- 서비스 소유자는 코드베이스 내 서비스 디렉토리에서 서비스에 대한 Robinhood 구성을 생성하고 업데이트함
- 이러한 설정은 구성 관리 서비스에 저장되며, 이는 Robinhood 구성의 변경 사항을 실시간으로 수신하는 편리한 라이브러리임
- 그러나 몇 가지 문제로 인해 코드베이스에서 Robinhood의 메가 구성을 정기적으로 빌드하고 푸시할 수는 없음
- 구성 푸시에 의해 변경 사항이 도입되면 롤백 버튼을 누르는 것이 위험함
- 마지막 푸시 이후 얼마나 많은 다른 서비스가 변경되었는지 알 수 없기 때문
- Robinhood를 소유한 팀은 각 메가 구성 푸시에 대해서도 책임이 있음
- 이는 Robinhood 팀이 모든 변경 구성 푸시에 참여해야 한다는 것을 의미하며, 이는 엔지니어링 시간 낭비임
- 대부분의 인시던트는 서비스 소유자가 해결할 수 있기 때문
- 잠재적 위험을 최소화하기 위해 각 푸시는 여러 데이터 센터에 배포하는 데 몇 시간이 걸림
- 구성 푸시에 의해 변경 사항이 도입되면 롤백 버튼을 누르는 것이 위험함
- 이러한 문제를 해결하기 위해 또 다른 작은 서비스인 Config Aggregator를 구축함
Config Aggregator
- Config Aggregator는 모든 서비스별 구성을 수집하고 LBS가 사용할 메가 구성을 구성함
- Config Aggregator는 서비스별 구성을 감시하고 변경 사항을 실시간으로 메가 구성에 전파함
- Config Aggregator는 서비스의 Robinhood 구성이 실수로 삭제되는 것을 방지하기 위한 tombstone 기능도 제공함
- 서비스 소유자가 Robinhood 구성에서 서비스를 삭제하는 변경 사항을 푸시하면 Config Aggregator는 서비스 항목을 즉시 제거하는 대신 tombstone 표시를 함
- 실제 제거는 며칠 후에 발생함
- 이 기능은 Robinhood 구성과 기타 라우팅 구성(예: Envoy 구성) 간의 서로 다른 푸시 주기로 인해 발생할 수 있는 경쟁 조건도 해결함
- 구성 관리 서비스의 단점은 현재 버전 관리가 되지 않는다는 것
- LBS 구성을 알려진 양호한 상태로 되돌려야 하는 경우에 대비하여 메가 구성을 주기적으로 백업함
Migration 전략
- 한 번에 로드 밸런싱 전략을 전환하는 것은 위험할 수 있음
- 이것이 Robinhood에서 서비스에 대해 여러 로드 밸런싱 전략을 구성할 수 있도록 하는 이유임
- Dropbox에는 퍼센트 기반 기능 게이트가 있으므로 클라이언트가 두 로드 밸런싱 전략에서 생성된 가중치의 가중 합계를 엔드포인트 가중치로 사용하는 혼합 전략을 구현함
- 이렇게 하면 모든 클라이언트가 엔드포인트에 대해 동일한 가중치 할당을 보면서 새로운 로드 밸런싱 전략으로 점진적으로 마이그레이션할 수 있음
배운 점
- Robinhood를 설계하고 구현하는 동안 효과적인 것과 그렇지 않은 것에 대한 몇 가지 핵심 교훈을 얻음
- 단순성을 우선시하고 클라이언트 변경을 최소화하며 처음부터 마이그레이션을 계획함으로써 LBS의 개발 및 배포를 간소화하고 비용이 많이 드는 함정을 피할 수 있었음
구성은 가능한 한 단순해야 함
- Robinhood는 서비스 소유자가 구성할 수 있는 많은 옵션을 도입
- 그러나 대부분의 경우 그들이 필요한 것은 제공된 기본 설정임
- 좋고 간단한 기본 구성(또는 더 좋은 경우 구성 없음)은 엄청난 엔지니어링 시간을 절약할 수 있음
클라이언트 변경 사항도 단순하게 유지할 것
- 내부 클라이언트에 변경 사항을 롤아웃하는 데 몇 달이 걸릴 수 있음
- 대부분의 배포는 매주 푸시되지만 많은 배포는 한 달에 한 번 또는 몇 년 동안 전혀 배포되지 않음
- LBS로 이동할 수 있는 변경 사항이 많을수록 더 좋음
- 예를 들어 초기에 클라이언트 설계에 가중 라운드 로빈을 사용하기로 결정했으며 그 이후로 변경하지 않았음
- 이는 진행 속도를 크게 높임
- 대부분의 변경 사항을 LBS로 제한하면 안정성 위험도 줄어듬
- 필요한 경우 LBS의 변경 사항을 몇 분 내에 롤백할 수 있기 때문
마이그레이션은 프로젝트 설계 단계에서 계획되어야 함
- 마이그레이션에는 엄청난 양의 엔지니어링 시간이 소요됨
- 고려해야 할 안정성 위험도 있음
- 재미있지는 않지만 중요한 작업임
- 새 서비스를 설계할 때 기존 사용 사례를 새 서비스로 원활하게 마이그레이션하는 방법을 가능한 한 빨리 고려해야 함
- 서비스 소유자에게 요구하는 사항이 많을수록 마이그레이션이 악몽이 됨
- 특히 기본 인프라 구성 요소의 경우 더욱 그러함
- Robinhood의 마이그레이션 프로세스는 처음부터 잘 설계되지 않았으므로 예상보다 훨씬 더 많은 시간을 프로세스 재구현 및 구성 재설계에 소비함
- 마이그레이션에 필요한 엔지니어링 시간은 성공의 핵심 지표여야 함
Robinhood의 효과
- 프로덕션 환경에서 약 1년 후 Robinhood의 최신 반복이 Dropbox의 오랜 로드 밸런싱 과제를 효과적으로 해결했다고 말할 수 있음
- 핵심인 PID 컨트롤러 알고리듬은 유망한 결과를 보여주었으며 가장 큰 서비스에서 상당한 성능 향상을 보여줌
- Dropbox 규모의 로드 밸런싱 서비스 설계 및 운영에 대한 귀중한 통찰력을 얻음
각주
-
N, M, s를 각각 서버 수, 클라이언트 수, 주소의 부분 집합 크기라고 하자. 서버가 연결하는 클라이언트 수는 이항 분포 B(M, s/n)의 표본을 따른다. 앞서 언급했듯이 클라이언트는 서비스 검색에서 제공하는 주소 집합에 대해 간단한 라운드 로빈을 수행한다. 따라서 각 클라이언트가 대략 동일한 양의 요청을 보내면 서버 측의 부하 분포는 이항 분포와 유사하다.
-
기존 서비스 검색 시스템을 확장하여 gRPC xDS 프로토콜(A27)을 지원한다. 이 블로그를 작성한 날짜 기준으로 gRPC 클라이언트는 제어 플레인의 엔드포인트 가중치에 대한 가중 라운드 로빈을 지원하지 않으므로 최단 기한 우선 스케줄링을 기반으로 사용자 지정 가중 라운드 로빈 선택기를 구현했다.
-
서비스가 때때로 성능이 저하된 I/O로 정체되는 흥미로운 사례가 발생했다. 이러한 상황에서는 해당 노드의 CPU가 낮게 유지되고 LBS는 노드의 가중치를 늘려 CPU를 평균으로 올리기 시작하여 데드 스파이럴로 이어진다. 해결책으로 CPU와 진행 중인 요청의 최대값을 로드 측정값으로 사용하여 서비스의 균형을 맞추게 되었다.
GN⁺의 의견
- Robinhood는 Dropbox의 로드 밸런싱 과제를 효과적으로 해결한 훌륭한 서비스로 보임. PID 컨트롤러를 활용한 점이 인상적임
- 매우 큰 규모의 글로벌 인프라에서 로드 밸런싱이 얼마나 어려운 과제인지를 잘 보여주는 사례임. 하드웨어 차이, 불균형한 로드 분포, 네트워크 정체 등 고려해야 할 사항이 많음
- 모든 컴포넌트들이 유기적으로 잘 연결되어 동작하도록 설계하는 것이 중요해 보임. LBS, 프록시, 라우팅 DB가 분리되어 있지만 실시간으로 긴밀하게 상호작용함
- 로드 밸런싱 성능을 정량적으로 평가하고 개선 사항을 시각화한 그래프들이 인상적임. 특히 max/avg 비율을 1에 가깝게 유지하는 것이 플릿 사이즈 최적화에 중요함을 잘 보여줌
- Config Aggregator를 도입하여 서비스별 구성을 분리한 것도 좋은 아이디어로 보임. 서비스 소유자들이 자신의 변경사항을 독립적으로 관리할 수 있게 해줌
- tombstone과 같은 안전장치를 마련한 것도 세심한 부분. 실수로 인한 구성 삭제를 방지하는 것이 중요함
- 마이그레이션 전략에 대한 교훈도 유용해 보임. 처음부터 마이그레이션을 고려하지 않으면 나중에 많은 시간을 소모하게 됨
- 전반적으로 Robinhood는 Dropbox 규모의 로드 밸런싱을 위한 체계적이고 세련된 솔루션으로 보임. 다른 대규모 인프라를 가진 기업들도 참고할 만한 사례임
유사한 솔루션들:
- AWS의 Elastic Load Balancing(ELB)이나 Google Cloud의 Cloud Load Balancing도 대규모 로드 밸런싱을 위한 관리형 서비스를 제공함
- 쿠버네티스의 경우 자체 로드 밸런서(kube-proxy)를 가지고 있지만, Istio나 Linkerd와 같은 서비스 메시 솔루션을 활용하면 더 강력한 로드 밸런싱과 트래픽 관리 기능을 사용할 수 있음
- Netflix의 Zuul이나 Lyft의 Envoy도 프록시 기반의 로드 밸런싱 기능을 제공함
도입 시 고려사항:
- 기존 인프라 및 서비스와의 호환성 확인이 필요함. 마이그레이션이 필요한 경우 전략을 세워야 함
- 성능과 안정성에 미치는 영향을 충분히 테스트하고 모니터링 해야 함. 로드 밸런싱 로직의 버그는 치명적일 수 있음
- 팀 역량을 고려하여 도입 범위와 속도를 결정해야 함. 성급하게 전체에 적용하기보다는 단계적 도입이 낫겠음
- 장기적으로는 지속적으로 최적화하고 개선해 나가는 노력이 필요함. 로드 밸런싱 알고리듬을 상황에 맞게 튜닝하고, 병목 지점을 제거하는 등의 활동이 도움될 것임