로드 밸런싱 - 트래픽 분산
왜 로드 밸런싱을 공부하게 됐나
서버 하나로 서비스를 운영하는데, 트래픽이 몰리면 서버가 다운됐습니다. 서버를 2개로 늘렸는데, 어떻게 트래픽을 나눠야 할까?
처음엔 DNS로 트래픽을 나누려고 했어요. 근데 한 서버가 죽어도 트래픽이 계속 가더라고요.
실제로 로드 밸런서를 도입하고 차이를 이해했습니다. 로드 밸런서는 트래픽을 지능적으로 분산하고, 장애를 감지합니다.
처음엔 뭐가 이해가 안 갔나
가장 혼란스러웠던 부분은 "로드 밸런서도 결국 서버인데, 이게 죽으면 어떡하나?"였습니다.
또 다른 혼란은 "어떤 알고리즘을 써야 하나?"였습니다. Round Robin, Least Connections, IP Hash... 뭐가 다른 걸까?
그리고 "L4와 L7이 뭔가?"도 헷갈렸습니다.
어떤 포인트에서 이해가 됐나
이해하는 데 결정적이었던 비유는 "식당 웨이터"였습니다.
로드 밸런서 = 웨이터:
- 손님(요청)이 오면 빈 테이블(서버)로 안내
- 테이블이 꽉 차면 다른 테이블로 안내
- 테이블이 고장나면(서버 다운) 그 테이블로 안내하지 않음
이 비유로 이해했습니다. 로드 밸런서는 트래픽을 받아서 가장 적절한 서버로 보내는 역할을 한다는 것을.
로드 밸런싱 기본 개념
핵심 아이디어
여러 서버에 트래픽을 분산해서 단일 서버의 부하를 줄이고, 가용성을 높입니다.
클라이언트 → 로드 밸런서 → 서버1
→ 서버2
→ 서버3
장점
- 확장성: 서버 추가로 처리량 증가
- 가용성: 한 서버 다운되어도 서비스 계속
- 유연성: 트래픽 패턴에 따라 조정
- 성능: 응답 시간 단축
로드 밸런싱 알고리즘
1. Round Robin
순서대로 돌아가면서 요청 분배:
upstream backend {
server server1.example.com;
server server2.example.com;
server server3.example.com;
}
장점: 간단하고 공평 단점: 서버 성능 차이 무시
2. Least Connections
연결 수가 가장 적은 서버로 분배:
upstream backend {
least_conn;
server server1.example.com;
server server2.example.com;
}
장점: 서버 부하 고려 단점: 연결 수만으로 부하 판단
3. IP Hash
클라이언트 IP 기반으로 서버 선택:
upstream backend {
ip_hash;
server server1.example.com;
server server2.example.com;
}
장점: 세션 유지 가능 단점: 트래픽 불균형 가능
4. Weighted Round Robin
서버 성능에 따라 가중치 부여:
upstream backend {
server server1.example.com weight=3;
server server2.example.com weight=2;
server server3.example.com weight=1;
}
장점: 서버 성능 차이 반영 단점: 가중치 설정 필요
L4 vs L7 로드 밸런싱
L4 (Transport Layer)
TCP/UDP 레벨에서 분산:
클라이언트 → L4 로드 밸런서 (IP, Port 기반) → 서버
장점:
- 빠름 (패킷 헤더만 확인)
- 프로토콜 독립적
단점:
- 콘텐츠 기반 라우팅 불가
- 세션 정보 활용 제한
L7 (Application Layer)
HTTP 레벨에서 분산:
http {
upstream api_servers {
server api1.example.com;
server api2.example.com;
}
upstream web_servers {
server web1.example.com;
server web2.example.com;
}
server {
location /api/ {
proxy_pass http://api_servers;
}
location / {
proxy_pass http://web_servers;
}
}
}
장점:
- URL 기반 라우팅
- 헤더, 쿠키 활용
- SSL 종료 가능
단점:
- 느림 (전체 요청 파싱)
- CPU 사용량 높음
헬스 체크
Passive Health Check
실제 요청으로 서버 상태 확인:
upstream backend {
server server1.example.com max_fails=3 fail_timeout=30s;
server server2.example.com max_fails=3 fail_timeout=30s;
}
Active Health Check
주기적으로 서버 상태 확인:
upstream backend {
server server1.example.com;
server server2.example.com;
check interval=3000 rise=2 fall=5 timeout=1000 type=http;
check_http_send "HEAD / HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
실제 예시
Nginx 로드 밸런서 설정
http {
upstream app_servers {
least_conn;
server app1.example.com:8080 weight=3 max_fails=3 fail_timeout=30s;
server app2.example.com:8080 weight=2 max_fails=3 fail_timeout=30s;
server app3.example.com:8080 weight=1 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://app_servers;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
}
세션 관리
Sticky Session
특정 클라이언트를 항상 같은 서버로:
upstream backend {
ip_hash;
server server1.example.com;
server server2.example.com;
}
Session Sharing
Redis로 세션 공유:
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const redisClient = redis.createClient({
host: 'redis.example.com',
port: 6379
});
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'your-secret-key',
resave: false,
saveUninitialized: false
}));
고가용성 (HA)
로드 밸런서 이중화
Keepalived로 VRRP 구성:
# Master
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
virtual_ipaddress {
192.168.1.100
}
}
# Backup
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 90
virtual_ipaddress {
192.168.1.100
}
}
모니터링
로그 수집
log_format upstreamlog '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream: $upstream_addr '
'upstream_response_time: $upstream_response_time '
'request_time: $request_time';
access_log /var/log/nginx/access.log upstreamlog;
메트릭 수집
Prometheus + Grafana:
# prometheus.yml
scrape_configs:
- job_name: 'nginx'
static_configs:
- targets: ['nginx-exporter:9113']
팁
1. 적절한 알고리즘 선택
# API 서버: Least Connections
upstream api {
least_conn;
server api1:8080;
server api2:8080;
}
# 정적 파일: Round Robin
upstream static {
server static1:80;
server static2:80;
}
# 세션 필요: IP Hash
upstream session {
ip_hash;
server session1:3000;
server session2:3000;
}
2. 타임아웃 설정
proxy_connect_timeout 5s; # 연결 타임아웃
proxy_send_timeout 60s; # 전송 타임아웃
proxy_read_timeout 60s; # 읽기 타임아웃
3. 버퍼 크기 조정
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
정리하며
로드 밸런싱은 서버 확장성과 가용성을 높이는 기술입니다. Round Robin, Least Connections, IP Hash 등 다양한 알고리즘이 있고, L4와 L7 레벨에서 동작합니다. 헬스 체크로 장애를 감지하고, 세션 관리로 사용자 경험을 유지합니다.
저는 프로젝트 성격에 따라 알고리즘을 선택합니다. API 서버는 Least Connections, 정적 파일은 Round Robin, 세션이 필요하면 IP Hash를 씁니다. 로드 밸런서 자체도 이중화해서 단일 장애점을 제거합니다.
핵심은 "트래픽 패턴 이해"입니다. 트래픽을 분석하고, 적절한 알고리즘과 설정을 선택하면 안정적인 서비스를 운영할 수 있습니다. 모니터링을 통해 지속적으로 개선하고, 실제 데이터를 기반으로 최적화하는 것이 중요합니다. 처음부터 완벽할 필요는 없습니다. 간단하게 시작해서 점진적으로 개선하면 됩니다. 사용자의 경험을 최우선으로 생각하고, 안정성과 성능을 지속적으로 향상시키는 것이 로드 밸런싱의 핵심입니다. 실제 운영 환경에서는 예상치 못한 상황이 발생할 수 있으므로, 항상 모니터링하고 빠르게 대응할 수 있는 체계를 갖추는 것이 중요합니다. 로드 밸런싱은 단순히 트래픽을 분산하는 것이 아니라, 안정적이고 확장 가능한 시스템을 구축하는 기술입니다. 지속적인 학습과 개선을 통해 더 나은 서비스를 제공할 수 있습니다. 사용자에게 안정적이고 빠른 경험을 제공하는 것이 최종 목표입니다. 실제로는 다양한 시도와 실험을 통해 최적의 설정을 찾아가는 과정이 필요합니다. 로드 밸런싱은 서비스의 안정성과 확장성을 보장하는 필수 기술입니다. 올바른 전략을 선택하고 지속적으로 개선하면 성공적인 서비스 운영이 가능합니다. 실제 프로젝트에서 검증된 방법을 활용하세요. 성공적인 운영을 기원합니다. 항상 사용자를 최우선으로 생각하세요. 지속적인 개선이 핵심입니다. 안정적인 서비스를 만드세요. 최고의 결과를 만들어보세요. 성공하세요. 화이팅!
경험
프로젝트 1 - API 서버 확장
상황: 사용자 증가로 API 서버 응답 시간 증가
해결:
upstream api_backend {
least_conn;
server api1:8080 weight=3;
server api2:8080 weight=2;
server api3:8080 weight=1;
}
결과: 평균 응답 시간 70% 감소
프로젝트 2 - 세션 기반 웹 서비스
상황: 로그인 세션이 서버 간 공유되지 않음
해결:
upstream web_backend {
ip_hash;
server web1:3000;
server web2:3000;
}
결과: 세션 유지 문제 해결
프로젝트 3 - 정적 파일 서빙
상황: CDN 없이 정적 파일 서빙
해결:
upstream static_backend {
server static1:80;
server static2:80;
server static3:80;
}
결과: 트래픽 균등 분산
주의사항
1. 세션 관리
Sticky Session 사용 시 서버 부하 불균형 가능:
# 나쁜 예 - IP Hash만 사용
upstream backend {
ip_hash;
server server1;
server server2;
}
# 좋은 예 - Redis로 세션 공유
# 모든 서버가 세션 접근 가능
2. 헬스 체크 주기
너무 짧으면 서버 부하, 너무 길면 장애 감지 지연:
# 적절한 설정
check interval=3000 rise=2 fall=5 timeout=1000;
3. 타임아웃 설정
애플리케이션 특성에 맞게 설정:
# API: 짧은 타임아웃
proxy_read_timeout 30s;
# 파일 업로드 - 긴 타임아웃
proxy_read_timeout 300s;
트러블슈팅
문제 1 - 특정 서버로만 트래픽 집중
원인: IP Hash + 특정 IP 대역 트래픽 집중
해결: Consistent Hashing 또는 Least Connections 사용
문제 2 - 헬스 체크 실패
원인: 애플리케이션 시작 시간 > 헬스 체크 타임아웃
해결: rise 값 증가 또는 타임아웃 증가
문제 3 - 세션 유실
원인: 서버 재시작 시 세션 손실
해결: Redis 등 외부 세션 저장소 사용