Loading
2022. 7. 24. 18:03 - lazykuna

대용량 로드밸런서 설계

오늘도 블라인드를 흠칫흠칫 보다가 재미있는 주제를 발견했다. 초 거대 로드밸런서는 어떻게 설계할 수 있는가? 여기서 초-거대라 함은 대용량, 즉 로드밸런서 하나만으로는 버거운 수준의 접속 요청이 들어올 경우를 의미한다.

잠깐! 그 전에 로드밸런서의 정의와 요구사항에 대해서 짚고 넘어갈 필요가 있을 것 같다.

  • 로드밸런싱은 여러 컴퓨터들에게 자원을 적절히 나누는(= 라우팅) 기술을 의미
  • 항상 그렇지만 “적절히"가 문제다.
    • 시스템 자원을 우선으로 고려하여, 자원의 여분이 남는 곳에 더 많은 할당을 하도록 할 수도 있고,
    • Stateless하지 않은 경우(예: 세션이 존재) 같은 IP가 같은 시스템에 접속되어야 한다. 이 경우 해시를 통해서 동일 유저가 같은 시스템에 연결될 수 있도록 로드 밸런싱을 할 수도 있고.
    • 이 Stateless 하지 않은 경우가 다소 골치가 아플 수 있다. session도 있고, connection이 keep-alive 일 수도 있고, 생각해야 할 점이 꽤 많음.
  • 어디에서 로드를 분배하느냐도 논쟁 포인트. L4 레이어(TCP/UDP)가 될수도 있고, L7 레이어(어플리케이션 단)이 될수도 있다. L4와 같은 경우 거의 라우팅 수준에서의 로드밸런싱이 됨.

역시나 댓글에서 여러 논쟁이 펼쳐졌고, 다양한 의견들이 있었다.

  • 일단 LB를 중첩으로 구성하는 것은 잘못된 설계
    • (위에서 봤듯이) LB는 실제 인스턴스들에게 요청을 넘겨줄 것을 기대하고 설계되었기 때문
  • 라우터를 제일 앞에 두고 뒤에 ACTIVE 로드밸런서를 여러개 배치
    • Router —> (LB1, LB2, LB3) —> Server 로 가는 구조
    • 라우터는 Round-Robin으로 요청을 아무 LB에게 뿌리고, 각 LB는 일관성 있게 로드 분산. 즉 어떤 LB한테 요청을 던지든지 목적지는 일관성있게 동일한 곳으로 감.
    • 즉 라우터가 1차 로드 분산을 하고, LB가 실질적인 load balacing을 수행
    • 꽤 괜찮은 방식 같음. Scale-out에서도 자유로울 것 같다. ACTIVE-PASSIVE 구조로도 만들 수 있을 것 같은데, 이 경우 가용성까지 확보 가능
  • DNS에 여러 LB를 등록해두기
    • 사실상 DNS 서버에게 라우팅 역할을 위임하는 것
    • 라우터와 비슷하지만 한 가지 차이점은 DNS 네임서버는 캐시까지 걸리는 딜레이가 다소 있다. 즉 변화가 생겨도 바로 반영이 안 되는 점을 감안해야 함

… 말은 다양하다고 썼는데 결국은 “앞에 라우터 끼고 해라"가 결론 😅

실제 기업에서의 로드 밸런싱

네이버

그럼 실제 기업에서는 어떤 식으로 로드밸런싱을 하고 있을까? 이번에도 역시 빛과 소금과도 같은 NAVER D2에 좋은 글들이 많이 올라와 있었다. 엄밀히 따지면 완전한 통상적인 로드밸런서 설계는 아니지만 기본 밑바탕은 로드밸런서

대충 고려된 내용들을 정리해 보면…

  • DNS는 안 된다. 세션 서버 배포 시간을 줄여서 라우팅이 변화에 빠르게 반응할 수 있도록 해야함.
  • 클라이언트 측에서도 백오프 알고리즘을 언제나 고려해야 함 (서버가 완전히 knockout되거나, 망 문제를 감안하여)
  • 자유롭게 분배 알고리즘 변경 가능: 이거 좀 큰거 같은데, 고부하상태에서는 Weight Least Connection 같은 알고리즘을 사용하여 최대한 견디게 할 수도 있을 것 같다. (비록 고급 프로토콜은 못 쓰겠지만)
  • 모니터링은 필수
  • 필요한 경우 서킷 브레이커 사용을 고민해봐야 함 : 과부하 상태에서 시스템 전체 down이 되는걸 원하지 않을 것임.

AWS

AWS의 경우는 ELB라는 이름으로 로드밸런서 서비스를 제공하고 있다. 몇 가지 특이할만한 점이라면,

  • 가용성 측면에 대한 고려: Availability Zone 간 로드 밸런싱 수행
  • 균등한 로드 밸런스: AZ와 상관없이 컴퓨팅 인스턴스들이 최대한의 자원을 사용하도록 함

그림으로 보면 아래와 같다.

이처럼 각 availability zone마다 인스턴스의 개수가 다르지만 로드 분포는 균등하게 수행되도록 의도하고 있다.

그런데 이를 위해서 제공하고 있는 라우팅 알고리즘이 여러개다!

  • Application Level: 기본적으로 균등하게 로드밸런싱. 부하가 제일 클 듯 (= 가장 낮은 가용성?)
    • 사실 Max TPS와 같은 정보에 대해서 언급이 되어있지는 않아 확실치가 않음.
  • Network Level: TCP/UDP 레벨에서의 로드밸런싱으로, 프로토콜 / 대상 / 소스 IP 주소를 기반으로 라우팅 수행 (아마 높은 확률로 해싱)
  • Classic: 그냥 가장 고전적인… Round Robin 방식이라고 함. 가장 한계 처리량이 많겠지만, 연결에 대해 state를 유지할 수는 없을 것.

위의 특이사항 때문에 지원되는 HTTP 프로토콜의 종류도 달라진다. multiplexing이 요구되는 HTTP/2와 같은 경우에는 Application Level에서의 라우팅만 가능하다고 한다. 상황에 따라 어떤 것이 나을지는 잘 판단할 필요가 있을 것. 근데 왜 Application Level 라우팅에서만 멀티플렉싱이 되지?

이외에도 또 고려되어야 하는 사항이 의외로 MTU 였습니다. 전달되는 최대 패킷의 크기를 의미하는데, 너무 크지 않도록 설계되어야(1500 MTU) 한다고 이야기합니다. 이것도 왜 그런지는 잘 모르겠으나, Application Level단에서의 라우팅을 고려하여 부하를 줄이기 위함일지도

  • 찾아보니 IBM 도큐먼트에서 재미있는 정보를 보았는데, “더 큰 패킷은 전송하는 데 시간이 더 소요되고, 인터럽트할 기회가 많다". 확실히 MTU가 크면 LB의 부하가 커질 가능성이 높아지는 게 자명하네요.

이외 로드밸런서 토폴로지 유형

https://mr-y.tistory.com/3

세부 설계에 대한 부분인데, 꽤 재미있는 내용들이 많았다.

  • 클라이언트(여기서는 프론트엔드 및 서비스 검색 등을 의미) 자체에서 프록시(LB) 기능을 하는 설계도 꽤 보편적인 편
  • L4 단계의 로드밸런서는 TCP 연결 상태를 추적하지 않음
    • Client-LB, LB-Target 각각 TCP연결을 생성하지 않고, 패킷을 그대로 넘겨준다. (ACK/FIN 등도). 그렇게 하는 쪽이 부담이 훨씬 적으니까. 패킷 그대로를 target에게 전달하는 것이 보편적인 설계이다.
  • 들어오는 요청만 LB가 잡아주고 나가는 건 Client에게 그대로 전달하는 설계 (= DSR)
    • 얼핏 보면 괜찮아 보이지만, LB 측에서 TCP 커넥션 상태를 추적할 수 없는 점이 있음.
  • L7 로드밸런서의 도래: timeout, 서킷브레이커, 모니터링 등의 기능적 수요 증가. 가격도 SW적으로 처리 가능한 특성상 더 저렴함

이리저리 살펴보니 로드밸런서는 똑똑한 라우터 정도가 되지만, 단순히 그렇게 생각하기에는 꽤 고려할만한 요소가 의외로 많았다. 오늘도 여러모로 배워간다.