로드 밸런서 정리: 부하 분산 알고리즘부터 운영 포인트까지
처음 웹 서버를 돌릴 때는 한 대만 잘 띄워 두면 충분합니다. 그런데 사용자가 늘고 트래픽이 점점 무거워지면, 어느 순간 한 대로는 감당이 안 되는 시점이 옵니다. 사양을 키우는 방법(스케일 업)도 있지만, 한 대를 더 띄워서 일을 나누어 시키는 방법(스케일 아웃)이 더 자연스러운 선택일 때가 많은데요.
이 분산을 책임지는 도구가 로드 밸런서입니다. 이름 그대로 트래픽이라는 부하를 여러 서버에 나눠 주는 장치인데, 막상 들여다보면 “어떤 기준으로 나누느냐”, “어느 계층에서 동작하느냐”, “한 대가 죽으면 어떻게 알아채느냐”처럼 결정거리가 줄지어 있습니다. 이번 글에서는 로드 밸런서가 어떤 일을 하는지, 그리고 운영 환경에서 자주 마주치는 굵직한 선택지들을 차근차근 풀어보겠습니다.
로드 밸런서가 하는 일
로드 밸런서의 가장 큰 일은 사실 단순합니다. “여러 대의 서버 중에서 어느 쪽으로 이번 요청을 보낼까”를 결정하는 것이죠. 클라이언트는 로드 밸런서의 주소만 알면 되고, 그 뒤에 서버가 한 대인지 백 대인지는 굳이 알 필요가 없습니다.
이 단순한 한 일이 가져오는 효과가 꽤 큽니다. 서버 한 대가 죽어도 로드 밸런서가 다른 서버로 요청을 돌려주니 서비스 자체는 계속 동작하고, 트래픽이 늘어나면 서버를 한 대 더 추가해 등록만 하면 자연스럽게 부하가 분산되는데요. 배포할 때도 한 대씩 빼고 배포한 뒤 다시 넣는 식으로 무중단 운영이 가능해집니다. 즉 가용성, 확장성, 배포 편의성을 한 도구가 함께 끌어올리는 셈이죠.
순서대로 돌리기: 라운드 로빈 계열
가장 직관적인 분산 방식이 라운드 로빈입니다. 순서대로 1번 서버, 2번 서버, 3번 서버에 차례로 보내고 다시 1번부터 도는 식인데요. 구현이 단순하고 어떤 서버에 트래픽이 갈지 미리 짐작할 수 있어 디버깅하기도 편합니다. 다만 모든 요청이 비슷한 비용을 가진 환경을 가정하기 때문에, 어떤 요청은 짧고 어떤 요청은 길면 부하가 한쪽으로 쏠릴 수 있습니다.
서버 사양이 서로 다를 때는 가중 라운드 로빈(weighted round robin)이 자주 쓰입니다. 사양이 좋은 서버에 더 큰 가중치를 주어 같은 라운드에서 더 많은 요청을 받도록 만드는 방식인데요. 가중치가 3:1로 잡혀 있다면 네 요청 중 셋은 큰 서버에, 하나는 작은 서버에 보내는 식이라, 서버를 한꺼번에 균일하게 갖추지 못하는 환경에서 자주 보이는 패턴입니다.
조금 더 단순한 변형으로 무작위(random) 방식도 있습니다. 매 요청마다 서버를 임의로 골라서 보내는 방식이라 표면적으로는 라운드 로빈과 비슷한 결과를 만들어 주는데요. 구현이 간결하고 상태를 기억할 필요가 없어, 분산 환경에서 여러 로드 밸런서가 동시에 동작할 때 특히 잘 어울립니다. 한 인스턴스가 자기가 어느 서버에 마지막으로 보냈는지를 다른 인스턴스와 동기화할 필요가 없으니까요.
서버 상태를 보고 고르기
순서나 무작위에 맡기는 대신 서버의 현재 상태를 보고 결정하는 방식도 있습니다. 가장 흔한 것이 최소 연결(least connections)인데요. 지금 연결이 가장 적게 붙어 있는 서버에 새 요청을 보내는 식이라, 처리에 오래 걸리는 요청이 한쪽에 몰려도 자동으로 균형이 잡힙니다.
여기에도 가중치 변형이 있습니다. 가중 최소 연결(weighted least connections)은 서버 사양 차이를 반영해 “연결 수 / 가중치”가 가장 작은 서버를 고르는 식인데요. 사양이 두 배 좋은 서버라면 같은 비율의 연결을 받아도 부하 비중을 절반으로 보는 셈이라, 실제 부하에 더 가까운 분산이 가능합니다.
응답 시간을 직접 측정해 의사결정에 쓰는 방식도 있습니다. 최소 응답 시간(least response time)은 최근 응답이 빠른 서버를 우선해서 보내는 알고리즘인데, 어느 한 서버가 슬슬 느려지기 시작하면 자동으로 트래픽 비중이 낮아지고 다시 회복되면 비중이 올라가는 식으로 자연스럽게 균형이 맞춰집니다. 대신 응답 시간을 추적하는 비용이 들고, 짧은 통계 노이즈에 흔들릴 수 있어 평균을 잡는 창의 길이를 잘 정해 주어야 합니다.
흥미로운 변형이 “두 후보 중 적은 쪽 고르기”(power of two choices)입니다.
서버를 두 개 무작위로 뽑은 다음, 그 중 연결이 적은 쪽에 보내는 방식인데요.
모든 서버를 매번 비교하는 비용 없이도 최소 연결과 비슷한 분산 효과를 얻을 수 있어, nginx의 random two least_conn 옵션이나 일부 메시지 큐 시스템이 채택해 쓰고 있습니다.
서버 수가 많을수록 단순 무작위와의 격차가 커져 효과가 두드러집니다.
같은 키는 늘 같은 서버로: 해시 기반
서버를 골고루 나누는 게 아니라, 같은 사용자나 같은 키가 늘 같은 서버에 가도록 묶고 싶은 경우도 있습니다. 이때 자주 쓰이는 게 해시 기반 분산이죠. 요청의 어떤 속성(예: 클라이언트 IP, 세션 ID, URL)을 해시해서 그 결과로 서버를 정하는 방식인데, 메모리에 캐시된 세션을 그대로 활용하거나 같은 URL이 같은 캐시 노드에 모이도록 만들고 싶을 때 잘 어울립니다.
가장 흔한 변형이 IP 해시입니다. 클라이언트의 IP 주소를 해시해서 서버를 고르는 식이라, 같은 클라이언트는 늘 같은 서버로 가게 되는데요. 구현이 간단하지만 NAT 뒤에 있는 사용자가 많을 때(예: 회사 네트워크에서 여러 명이 같은 외부 IP로 나가는 경우) 한 서버에 부하가 쏠릴 수 있다는 약점이 있습니다. URL 해시는 이름 그대로 요청 경로를 해시 키로 쓰는 방식이라, 캐시 서버가 뒤에 줄지어 있을 때 같은 자원을 같은 캐시 노드로 모아 캐시 적중률을 끌어올립니다.
해시 분산을 쓰다가 서버를 한 대 추가하거나 빼면, 단순 해시 방식에서는 거의 모든 키가 다른 서버로 재배치됩니다. 캐시 같은 자료가 한꺼번에 무효화되어 백엔드에 부하가 몰리는 현상이 생기는데요. 이를 막기 위해 일관된 해싱(consistent hashing)이라는 기법이 자주 함께 쓰입니다. 서버를 추가/제거할 때 영향받는 키가 일부에 그치도록 만들어 주는 도구로, 분산 캐시나 분산 데이터베이스에서도 같은 원리가 활용됩니다.
L4와 L7, 어디에서 트래픽을 가르는가
분산 알고리즘과 별개로, 로드 밸런서는 “어느 계층에서 트래픽을 들여다보느냐”에 따라 두 부류로 나뉩니다. 하나는 전송 계층에서 동작하는 L4 로드 밸런서이고, 다른 하나는 응용 계층에서 동작하는 L7 로드 밸런서입니다.
L4 로드 밸런서는 TCP/UDP 헤더의 IP 주소와 포트만 보고 분산 결정을 내립니다. 패킷 안에 무엇이 들었는지(예: HTTP 요청 경로)는 보지 않으니, 처리량이 굉장히 빠르고 단순한데요. 요청 한 번을 처리하는 비용이 작아 초당 수십만 건 이상의 연결을 무리 없이 받아낼 수 있어, 게임 서버나 데이터베이스 앞단처럼 처리량이 절대적으로 중요한 자리에 잘 어울립니다.
L7 로드 밸런서는 한 단계 위까지 들여다봅니다. HTTP 메서드, 경로, 호스트 헤더, 쿠키 같은 응용 정보를 확인할 수 있어서 “이 경로는 A 서버 그룹으로”, “이 도메인은 B 서버 그룹으로” 같은 세밀한 라우팅이 가능한데요. HTTPS 종료(TLS termination)나 헤더 조작, 가벼운 캐싱까지 함께 해 주는 경우가 많아, 우리가 흔히 보는 웹 서비스 앞단에는 대부분 L7 쪽이 자리 잡고 있습니다.
대신 응용 데이터를 들여다보는 만큼 L4보다는 처리 비용이 큽니다. 한쪽이 절대적으로 좋다기보다는, 풀려는 문제가 어느 계층의 정보를 필요로 하느냐에 따라 골라 쓰는 도구로 생각하면 됩니다.
어디에 두느냐의 문제
로드 밸런서는 둘 수 있는 위치도 한 가지가 아닙니다. 가장 흔한 모습은 한 데이터센터 안에 한 대(또는 이중화로 두 대) 두는 형태입니다. 이 위에 한 단계 더 얹어서, 여러 지역에 흩어진 데이터센터 중 사용자와 가까운 곳으로 트래픽을 보내는 글로벌 로드 밸런싱(GSLB)을 두는 경우도 많은데요.
글로벌 로드 밸런싱은 보통 DNS 단계에서 일어납니다. 같은 도메인 이름에 대해 사용자의 위치에 따라 다른 IP를 응답하는 식이라, 미국 사용자는 미국 데이터센터로, 유럽 사용자는 유럽 데이터센터로 자연스럽게 흘러갑니다. DNS는 캐시되는 시간이 있어 즉각적인 페일오버에는 약하지만, 평소 지연 시간을 줄이는 데에는 효과가 큽니다.
또 하나 자주 쓰이는 방식이 Anycast입니다. 같은 IP 주소를 여러 데이터센터가 동시에 광고해서, 사용자의 패킷이 라우팅 경로상 가장 가까운 데이터센터로 자동으로 흘러가게 만드는 방식이죠. CDN이나 DDoS 방어 서비스가 자주 쓰는 기법이고, 사용자는 어디로 연결되는지 의식할 필요 없이 가장 가까운 출구를 잡습니다.
구현 형태도 다양합니다. 예전에는 F5 같은 전용 하드웨어가 자리를 차지했지만, 요즘은 nginx, HAProxy, Envoy 같은 소프트웨어가 일반 서버 위에서 동작하는 경우가 많습니다. 클라우드에서는 AWS의 ALB/NLB, GCP의 Cloud Load Balancing, Cloudflare Load Balancing 같은 관리형 서비스가 같은 자리를 채워 주고 있어, 작은 팀일수록 이런 관리형 서비스를 그대로 쓰는 쪽이 운영 부담이 적습니다.
세션을 어떻게 다룰까
로드 밸런서를 도입하면 처음에는 잘 보이지 않다가 나중에 발목을 잡는 문제가 하나 있습니다. 사용자가 로그인을 했는데, 다음 요청이 다른 서버로 가면서 로그인 상태가 풀리는 현상인데요. 서버 메모리에 세션을 저장하던 구조에서 자주 발생합니다.
가장 단순한 해결은 sticky session(또는 세션 고정)이라는 기능을 켜는 것입니다. 앞에서 본 해시 기반 분산이나 쿠키 기반 라우팅을 통해, 같은 사용자의 요청이 늘 같은 서버로 가도록 묶어 두는 방식이죠. 구현이 간단하지만 한쪽 서버가 죽으면 그 서버에 묶여 있던 사용자만 로그아웃되는 약점이 있고, 트래픽이 한쪽으로 쏠리는 문제도 잠재적으로 안고 있습니다.
좀 더 견고한 방법은 세션을 서버 메모리가 아니라 외부에 두는 것입니다. 쿠키와 세션 글에서 다룬 것처럼, 세션 정보를 Redis 같은 공유 저장소에 두면 어느 서버가 요청을 받아도 같은 세션을 읽을 수 있는데요. 또 다른 길은 JWT처럼 클라이언트가 인증 정보를 직접 들고 다니게 만들어, 서버 쪽이 아예 세션을 보관하지 않는 무상태 구조로 가는 방식입니다.
건강 검사와 페일오버
마지막으로 빼놓을 수 없는 게 건강 검사입니다.
서버 한 대가 죽었는데 로드 밸런서가 그걸 모르고 계속 트래픽을 보내면, 사용자는 절반쯤 확률로 에러를 맞게 되거든요.
그래서 로드 밸런서는 주기적으로 백엔드 서버에 작은 요청(예: /health 경로)을 보내고, 응답이 늦거나 잘못되면 그 서버를 풀에서 잠시 빼 두는 일을 합니다.
이 검사 주기와 실패 임계값을 어떻게 잡느냐가 운영 안정성을 크게 좌우합니다. 너무 짧으면 잠시 느려진 서버까지 자꾸 빼버려 풀이 흔들리고, 너무 길면 죽은 서버가 한참 동안 트래픽을 받아 사용자에게 에러를 흘립니다. 보통은 몇 초 단위 검사에 두세 번 연속 실패를 빼는 기준으로 시작해서, 운영 데이터를 보면서 조정해 나갑니다.
건강 검사는 능동적인 방식과 수동적인 방식으로 나뉩니다. 앞에서 설명한 주기적인 핑이 능동(active) 헬스 체크이고, 실제 사용자 요청의 결과를 관찰해 실패율이 높아진 서버를 자동으로 빼는 방식이 수동(passive) 헬스 체크입니다. HAProxy나 Envoy 같은 도구는 두 방식을 함께 사용해, 외형적으로는 살아 있는데 실제 트래픽에서는 자꾸 에러를 내는 “좀비 서버”를 빠르게 잡아냅니다.
페일오버 자체도 한 단계만 있는 것이 아닙니다. 한 풀 안에서 일부 서버가 죽는 경우는 자동 분산으로 처리되지만, 풀 전체가 망가지는 사고도 생기죠. 이런 상황을 대비해 보조 풀(다른 데이터센터, 다른 지역)을 미리 등록해 두고 주 풀이 모두 죽으면 자동으로 그쪽으로 트래픽을 돌리는 구성을 운영하는 곳이 많습니다.
리버스 프록시와는 어떻게 다를까
종종 같이 등장하는 개념이 리버스 프록시입니다. 둘은 영역이 많이 겹쳐서 nginx나 HAProxy 같은 한 도구가 두 역할을 함께 해내는 일이 흔한데요. 다만 강조점이 조금 다릅니다. “로드 밸런서”는 트래픽 분산이라는 일에 초점을 두고, “리버스 프록시”는 백엔드를 대신해 요청을 받는 중계자라는 역할에 초점을 두는 이름이죠. 백엔드가 한 대뿐이어도 그 앞에 nginx를 두면 그건 리버스 프록시이지만 분산할 대상이 없으니 로드 밸런서로 부르기는 어색하고, 반대로 패킷만 흘려보내는 L4 로드 밸런서는 응용 데이터를 보지 않아 엄밀히는 리버스 프록시가 아닌 경우도 있습니다. 실무에서는 둘이 한 박스 안에서 함께 동작하는 일이 많아, 굳이 어느 한쪽이라고 못 박지 않고 “앞단(front-end)“이라고 부르는 경우도 흔합니다.
마치며
로드 밸런서는 “트래픽을 골고루 나눠 주는 장치”라는 한 줄로 시작하지만, 안을 들여다보면 분산 알고리즘 선택, 계층 결정, 헬스 체크 정책, 세션 처리처럼 결정거리가 줄지어 있습니다. 정답이 하나 있는 것이 아니라, 트래픽 패턴과 운영 사이즈에 맞춰 그때그때 고르는 도구라고 생각하면 마음이 편한데요. 처음에는 라운드 로빈과 능동 헬스 체크 정도로 단순하게 시작해, 운영 데이터를 보며 알고리즘과 설정을 다듬어 나가는 흐름이 가장 자연스럽습니다.
분산 알고리즘이나 헬스 체크 같은 디테일을 더 보고 싶다면 HAProxy 공식 문서가 좋은 출발점이 됩니다.
This work is licensed under
CC BY 4.0