
로드 밸런싱: 트래픽 분산
로드 밸런싱의 동작 원리와 활용 방법을 프로젝트 경험을 통해 이해한 과정

로드 밸런싱의 동작 원리와 활용 방법을 프로젝트 경험을 통해 이해한 과정
포스트모템의 목적과 작성 방법

Terraform의 동작 원리와 활용 방법을 프로젝트 경험을 통해 이해한 과정

DRAM의 누설 전류부터 ECC, HBM, 그리고 실제 성능 벤치마크(fio, sysbench)까지. 단순한 비유를 넘어 메모리의 모든 것을 파헤치는 개발자 필독 가이드.

공개 API를 운영하다 보면 예상치 못한 대량 요청에 시달릴 수 있다. Rate Limiting과 API Key 관리로 API를 보호하는 방법을 정리했다.

서버 하나로 서비스를 운영하는데, 트래픽이 몰리면 서버가 다운됐습니다. 서버를 2개로 늘렸는데, 어떻게 트래픽을 나눠야 할까?
처음엔 DNS로 트래픽을 나누려고 했어요. 근데 한 서버가 죽어도 트래픽이 계속 가더라고요.
실제로 로드 밸런서를 도입하고 차이를 이해했습니다. 로드 밸런서는 트래픽을 지능적으로 분산하고, 장애를 감지합니다.
가장 혼란스러웠던 부분은 "로드 밸런서도 결국 서버인데, 이게 죽으면 어떡하나?"였습니다.
또 다른 혼란은 "어떤 알고리즘을 써야 하나?"였습니다. Round Robin, Least Connections, IP Hash... 뭐가 다른 걸까?
그리고 "L4와 L7이 뭔가?"도 헷갈렸습니다.
이해하는 데 결정적이었던 비유는 "식당 웨이터"였습니다.
로드 밸런서 = 웨이터:이 비유로 이해했습니다. 로드 밸런서는 트래픽을 받아서 가장 적절한 서버로 보내는 역할을 한다는 것을.
여러 서버에 트래픽을 분산해서 단일 서버의 부하를 줄이고, 가용성을 높입니다.
클라이언트 → 로드 밸런서 → 서버1
→ 서버2
→ 서버3
순서대로 돌아가면서 요청 분배:
upstream backend {
server server1.example.com;
server server2.example.com;
server server3.example.com;
}
장점: 간단하고 공평 단점: 서버 성능 차이 무시
연결 수가 가장 적은 서버로 분배:
upstream backend {
least_conn;
server server1.example.com;
server server2.example.com;
}
장점: 서버 부하 고려 단점: 연결 수만으로 부하 판단
클라이언트 IP 기반으로 서버 선택:
upstream backend {
ip_hash;
server server1.example.com;
server server2.example.com;
}
장점: 세션 유지 가능 단점: 트래픽 불균형 가능
서버 성능에 따라 가중치 부여:
upstream backend {
server server1.example.com weight=3;
server server2.example.com weight=2;
server server3.example.com weight=1;
}
장점: 서버 성능 차이 반영 단점: 가중치 설정 필요
TCP/UDP 레벨에서 분산:
클라이언트 → L4 로드 밸런서 (IP, Port 기반) → 서버
장점:
단점:
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;
}
}
}
장점:
단점:
실제 요청으로 서버 상태 확인:
upstream backend {
server server1.example.com max_fails=3 fail_timeout=30s;
server server2.example.com max_fails=3 fail_timeout=30s;
}
주기적으로 서버 상태 확인:
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;
}
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;
}
}
}
특정 클라이언트를 항상 같은 서버로:
upstream backend {
ip_hash;
server server1.example.com;
server server2.example.com;
}
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
}));
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']
# 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;
}
proxy_connect_timeout 5s; # 연결 타임아웃
proxy_send_timeout 60s; # 전송 타임아웃
proxy_read_timeout 60s; # 읽기 타임아웃
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를 씁니다. 로드 밸런서 자체도 이중화해서 단일 장애점을 제거합니다.
핵심은 "트래픽 패턴 이해"입니다. 트래픽을 분석하고, 적절한 알고리즘과 설정을 선택하면 안정적인 서비스를 운영할 수 있습니다. 모니터링을 통해 지속적으로 개선하고, 실제 데이터를 기반으로 최적화하는 것이 중요합니다. 처음부터 완벽할 필요는 없습니다. 간단하게 시작해서 점진적으로 개선하면 됩니다. 사용자의 경험을 최우선으로 생각하고, 안정성과 성능을 지속적으로 향상시키는 것이 로드 밸런싱의 핵심입니다. 실제 운영 환경에서는 예상치 못한 상황이 발생할 수 있으므로, 항상 모니터링하고 빠르게 대응할 수 있는 체계를 갖추는 것이 중요합니다. 로드 밸런싱은 단순히 트래픽을 분산하는 것이 아니라, 안정적이고 확장 가능한 시스템을 구축하는 기술입니다. 지속적인 학습과 개선을 통해 더 나은 서비스를 제공할 수 있습니다. 사용자에게 안정적이고 빠른 경험을 제공하는 것이 최종 목표입니다. 실제로는 다양한 시도와 실험을 통해 최적의 설정을 찾아가는 과정이 필요합니다. 로드 밸런싱은 서비스의 안정성과 확장성을 보장하는 필수 기술입니다. 올바른 전략을 선택하고 지속적으로 개선하면 성공적인 서비스 운영이 가능합니다. 실제 프로젝트에서 검증된 방법을 활용하세요. 성공적인 운영을 기원합니다. 항상 사용자를 최우선으로 생각하세요. 지속적인 개선이 핵심입니다. 안정적인 서비스를 만드세요. 최고의 결과를 만들어보세요. 성공하세요. 화이팅!
상황: 사용자 증가로 API 서버 응답 시간 증가
해결:
upstream api_backend {
least_conn;
server api1:8080 weight=3;
server api2:8080 weight=2;
server api3:8080 weight=1;
}
결과: 평균 응답 시간 70% 감소
상황: 로그인 세션이 서버 간 공유되지 않음
해결:
upstream web_backend {
ip_hash;
server web1:3000;
server web2:3000;
}
결과: 세션 유지 문제 해결
상황: CDN 없이 정적 파일 서빙
해결:
upstream static_backend {
server static1:80;
server static2:80;
server static3:80;
}
결과: 트래픽 균등 분산
Sticky Session 사용 시 서버 부하 불균형 가능:
# 나쁜 예 - IP Hash만 사용
upstream backend {
ip_hash;
server server1;
server server2;
}
# 좋은 예 - Redis로 세션 공유
# 모든 서버가 세션 접근 가능
너무 짧으면 서버 부하, 너무 길면 장애 감지 지연:
# 적절한 설정
check interval=3000 rise=2 fall=5 timeout=1000;
애플리케이션 특성에 맞게 설정:
# API: 짧은 타임아웃
proxy_read_timeout 30s;
# 파일 업로드 - 긴 타임아웃
proxy_read_timeout 300s;
원인: IP Hash + 특정 IP 대역 트래픽 집중
해결: Consistent Hashing 또는 Least Connections 사용
원인: 애플리케이션 시작 시간 > 헬스 체크 타임아웃
해결: rise 값 증가 또는 타임아웃 증가
원인: 서버 재시작 시 세션 손실
해결: Redis 등 외부 세션 저장소 사용