테마
프록시와 로드 밸런싱 2
프록시와 로드 밸런싱에서 본 로드 밸런서(load balancer)가 실제로 어느 서버를 고르고, 죽은 서버를 어떻게 빼는지 이어서 다룬다. 서버를 여러 대로 늘릴 때 로그인 상태가 끊기는 문제와 그 대안, 서버 용량을 키우는 두 가지 방식도 정리한다. 마지막으로 사용자 가까이에 사본을 두는 CDN(Content Delivery Network, 콘텐츠 전송 네트워크)으로 지연과 원본 서버의 부하를 줄이는 방법을 본다.
분산 알고리즘
어느 서버에 보낼지 고르는 방법은 차례, 지금의 연결 수, 클라이언트 IP(Internet Protocol, 인터넷 프로토콜) 주소처럼 무엇을 기준으로 삼느냐에 따라 여러 가지다. 웹 서버이자 로드 밸런서로 널리 쓰는 nginx는 따로 지정하지 않으면, 서버마다 몫을 정한 숫자인 가중치를 반영한 라운드 로빈(round robin)으로 나눈다.
| 방식 | 동작 | 알맞은 경우 |
|---|---|---|
| 라운드 로빈 | 서버를 차례로 돌아가며 하나씩 | 서버 성능과 요청 부담이 비슷할 때 |
| 가중치 라운드 로빈 | 성능 좋은 서버에 더 많이 | 서버 사양이 다를 때 |
| 최소 연결 | 지금 연결이 가장 적은 서버에 | 요청마다 처리 시간이 크게 다를 때 |
| IP 해시 | 클라이언트 IP로 서버를 고정 | 같은 사용자를 같은 서버로 보내야 할 때 |
라운드 로빈은 서버를 순서대로 한 번씩 돌아가며 요청을 주는 방식이다. 단순하지만, 오래 걸리는 요청이 한 서버에 몰려도 그 사실을 반영하지 못한다. 최소 연결 방식은 지금 처리 중인 연결 수를 보고 가장 한가한 서버를 고르므로 이 문제를 줄인다. IP 해시 방식은 클라이언트 IP 주소를 계산식에 넣어 늘 같은 서버 번호가 나오게 한다.
아래는 nginx에서 뒤쪽 서버 묶음(upstream)과 분산 방식을 정하는 설정 예시다. weight는 가중치다.
nginx
upstream app {
least_conn; # 최소 연결 방식
server 10.0.0.11 weight=2; # 두 배로 받는다
server 10.0.0.12 max_fails=3 fail_timeout=30s;
}헬스 체크와 장애 서버 제외
로드 밸런서가 죽은 서버에 계속 요청을 보내면 사용자 일부가 오류를 본다. 헬스 체크(health check)는 로드 밸런서가 뒤쪽 서버가 살아 있는지 주기적으로 확인하는 요청이다. 보통 /health 같은 경로에 요청을 보내 정상 응답이 오는지 본다. 정해진 횟수만큼 실패한 서버는 분산 대상에서 빼고, 다시 정상 응답이 오면 되돌린다.
실제 요청의 실패를 보고 판단하는 방식도 있다. 위 설정의 max_fails=3 fail_timeout=30s는 30초 안에 요청이 세 번 실패하면 그 서버를 30초 동안 쓰지 않는다는 뜻이다. 오픈 소스 nginx는 이렇게 실제 요청의 실패만 보고, 주기적으로 따로 확인 요청을 보내는 기능은 상용판인 NGINX Plus에서 제공한다.
스티키 세션과 그 대안
로그인 정보를 서버 메모리의 세션(session)에 두면 문제가 생긴다. 첫 요청은 서버 A에서 로그인했는데 다음 요청이 서버 B로 가면, B는 그 세션을 모른다. 스티키 세션(sticky session)은 같은 사용자의 요청을 늘 같은 서버로 보내 이 문제를 피하는 방법이다. 로드 밸런서가 첫 응답에 어느 서버인지 적은 쿠키(cookie)를 심어 두고, 다음 요청부터 그 쿠키를 보고 같은 서버로 보낸다. IP 해시로 묶는 방법도 있다.
하지만 스티키 세션에는 약점이 있다. 특정 서버에 사용자가 몰려도 옮길 수 없어 부하가 고르지 않다. 그 서버가 죽으면 거기 묶인 사용자의 로그인이 모두 풀린다. 그래서 보통은 세션을 서버 밖의 공유 저장소(예: 메모리에 데이터를 두어 빠르게 읽고 쓰는 Redis)로 뺀다. 아니면 사용자 정보와 서명을 담은 JWT(JSON Web Token, JSON 웹 토큰) 같은 토큰(token)을 클라이언트가 요청마다 보내게 한다. 서버는 서명만 검증하면 되므로 세션을 저장하지 않아도 된다. 그러면 어느 서버가 요청을 받아도 같은 결과를 낼 수 있어 서버를 자유롭게 늘리고 뺄 수 있다.
스케일 업과 스케일 아웃
서버 용량을 늘리는 방법은 두 가지다. 스케일 업(scale-up)은 서버 한 대의 CPU·메모리를 더 좋은 것으로 바꾸는 방식이다. 구조를 바꿀 필요가 없어 간단하지만, 한 대의 성능에는 한계가 있고, 그 한 대가 죽으면 서비스 전체가 멈추는 단일 장애점(single point of failure)이 된다. 스케일 아웃(scale-out)은 비슷한 서버를 여러 대로 늘리는 방식이다. 로드 밸런서가 필요하고 서버를 무상태(stateless)로 설계해야 하지만, 거의 끝없이 늘릴 수 있고 한 대가 죽어도 버틴다.
CDN
CDN은 세계 곳곳에 둔 캐시(cache) 서버에 콘텐츠 사본을 두고, 사용자와 가까운 서버가 대신 응답하게 하는 네트워크다. 이 캐시 서버들을 엣지 서버(edge server)라 하고, 원본을 가진 서비스 서버를 오리진(origin)이라 한다.
사용자가 이미지를 요청하면 가까운 엣지 서버가 먼저 받는다. 엣지에 사본이 있으면 바로 돌려주고, 없으면 오리진에서 가져와 돌려준 뒤 저장해 둔다. 얼마나 오래 저장할지는 오리진 응답의 Cache-Control 헤더(header) 등으로 정한다. 멀리 있는 오리진까지 왕복하지 않으니 지연 시간(latency)이 줄고, 오리진으로 가는 요청 수도 크게 준다. 사용자를 가까운 엣지로 보내는 데는 보통 두 방법을 쓴다. 하나는 DNS(Domain Name System, 도메인 이름 시스템) 응답이다. CDN의 DNS 서버가 질의한 쪽의 위치를 보고 가까운 엣지 서버의 IP 주소를 돌려주므로, 같은 도메인이라도 사용자마다 다른 서버로 접속한다. 다른 하나는 애니캐스트(anycast) 라우팅(routing)이다. 여러 지역의 엣지 서버가 같은 IP 주소를 알리면, 라우터(router)가 그중 네트워크상 가장 가까운 곳으로 패킷(packet)을 보낸다.
면접 포인트
- "로드 밸런서의 분산 방식은?" → 라운드 로빈(차례대로), 가중치 라운드 로빈(사양 차이 반영), 최소 연결(처리 시간 차이 반영), IP 해시(같은 사용자 고정).
- "죽은 서버는 어떻게 빼나요?" → 헬스 체크로 주기적으로 확인하거나 실제 요청 실패 횟수를 보고 분산 대상에서 뺀다.
- "스티키 세션의 문제와 대안은?" → 부하 쏠림·서버 장애 시 세션 유실 → 세션 저장소 분리나 토큰 방식.
- "스케일 업과 스케일 아웃?" → 한 대를 키움(단순·한계·단일 장애점) vs 여러 대로 늘림(무상태 설계·로드 밸런서 필요).
- "CDN은 어떻게 빠르게 하나요?" → 가까운 엣지 서버가 사본으로 응답해 지연을 줄이고, 없을 때만 오리진에 가서 오리진 부하도 준다.
