Load Balancing and Scaling

로드 밸런싱은 네트워크 트래픽을 여러 서버에 효율적으로 분산시켜 시스템의 안정성, 가용성, 응답 시간을 개선하는 기술이다. 웹 애플리케이션의 규모가 커질수록 단일 서버로는 모든 요청을 처리하기 어려워진다. 이때 로드 밸런서가 ‘교통 경찰’처럼 작동하여 들어오는 요청을 여러 서버로 분산시켜 과부하를 방지한다.

로드 밸런싱 알고리즘

로드 밸런싱의 효율성은 사용하는 알고리즘에 크게 좌우된다:

  1. 라운드 로빈 (Round Robin): 가장 단순한 방식으로, 순차적으로 각 서버에 요청을 배분한다. 모든 서버가 동일한 사양일 때 효과적이다.
  2. 최소 연결 (Least Connection): 현재 가장 적은 연결을 처리 중인 서버에 새 요청을 할당한다. 요청 처리 시간이 다양할 때 유용하다.
  3. IP 해시 (IP Hash): 사용자의 IP 주소를 기반으로 특정 서버에 요청을 라우팅한다. 세션 지속성이 필요할 때 적합하다.
  4. 가중치 기반 (Weighted): 서버의 처리 능력에 따라 가중치를 부여하여 더 강력한 서버가 더 많은 요청을 처리하도록 한다.
  5. 응답 시간 기반 (Response Time): 응답 시간이 가장 빠른 서버에 요청을 보낸다. 실시간 성능 모니터링이 필요하다.

스케일링 전략: 수직적 vs. 수평적

스케일링은 시스템의 용량을 증가시키는 방법으로, 두 가지 주요 접근 방식이 있다:

수직적 스케일링 (Vertical Scaling)

수직적 스케일링은 기존 서버의 자원(CPU, RAM, 스토리지)을 증가시키는 방식이다.

장점:

단점:

적합한 상황:

수평적 스케일링 (Horizontal Scaling)

수평적 스케일링은 동일한 사양의 서버를 여러 대 추가하여 전체 시스템 용량을 증가시키는 방식이다.

장점:

단점:

적합한 상황:

실제 구현 사례와 최적화 전략

AWS에서의 로드 밸런싱과 스케일링

AWS는 다양한 로드 밸런싱 옵션을 제공한다:

  1. Application Load Balancer (ALB): HTTP/HTTPS 트래픽에 최적화되어 있으며, 경로 기반 라우팅, 호스트 기반 라우팅 등 애플리케이션 레이어의 고급 기능을 제공한다.
  2. Network Load Balancer (NLB): TCP/UDP 트래픽에 적합하며, 초당 수백만 개의 요청을 처리할 수 있는 고성능 로드 밸런서이다.
  3. Elastic Load Balancer (ELB): 기본적인 로드 밸런싱 기능을 제공하는 클래식 로드 밸런서이다.

AWS의 Auto Scaling 그룹과 함께 사용하면 트래픽에 따라 자동으로 인스턴스 수를 조정할 수 있다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# AWS Auto Scaling 설정 예시 (Python, boto3 사용)
import boto3

client = boto3.client('autoscaling')

# Auto Scaling 그룹 생성
response = client.create_auto_scaling_group(
    AutoScalingGroupName='my-asg',
    LaunchConfigurationName='my-launch-config',
    MinSize=2,  # 최소 인스턴스 수
    MaxSize=10,  # 최대 인스턴스 수
    DesiredCapacity=4,  # 원하는 인스턴스 수
    VPCZoneIdentifier='subnet-12345,subnet-67890',  # 서브넷 ID
    TargetGroupARNs=['arn:aws:elasticloadbalancing:…']  # 로드 밸런서 타겟 그룹
)

# CPU 사용률 기반 스케일링 정책 설정
response = client.put_scaling_policy(
    AutoScalingGroupName='my-asg',
    PolicyName='cpu-scaling-policy',
    PolicyType='TargetTrackingScaling',
    TargetTrackingConfiguration={
        'PredefinedMetricSpecification': {
            'PredefinedMetricType': 'ASGAverageCPUUtilization'
        },
        'TargetValue': 70.0  # CPU 사용률 70% 이상일 때 스케일 아웃
    }
)

Kubernetes에서의 로드 밸런싱과 스케일링

Kubernetes는 컨테이너화된 애플리케이션의 자동화된 배포, 스케일링, 관리를 제공한다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Kubernetes 수평적 자동 스케일링 설정 예시
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 2  # 최소 Pod 수
  maxReplicas: 10  # 최대 Pod 수
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70  # CPU 사용률 70% 이상일 때 스케일 아웃
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Kubernetes 서비스 (로드 밸런싱) 설정 예시
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app
  ports:
  - port: 80
    targetPort: 8080
  type: LoadBalancer  # 클라우드 제공업체의 로드 밸런서 사용

고급 로드 밸런싱 기법

  1. 글로벌 로드 밸런싱
    지리적으로 분산된 사용자에게 최적의 성능을 제공하기 위해 전 세계 여러 데이터 센터에 걸쳐 트래픽을 분산한다. DNS 기반 로드 밸런싱(예: AWS Route 53)이나 애니캐스트(Anycast) 네트워킹을 활용한다.

  2. 애플리케이션 레이어 로드 밸런싱
    HTTP 헤더, 쿠키, URL 경로 등의 애플리케이션 레이어 정보를 기반으로 라우팅 결정을 내린다. 마이크로서비스 아키텍처에서 특히 유용하다.

  3. 서비스 메시 (Service Mesh)
    Istio나 Linkerd와 같은 서비스 메시는 마이크로서비스 간 통신을 관리하고 고급 로드 밸런싱, 트래픽 관리, 보안 기능을 제공한다.

데이터베이스 스케일링 전략

백엔드 성능에서 데이터베이스는 종종 병목 지점이 된다.
효과적인 데이터베이스 스케일링 전략은 다음과 같다:

  1. 읽기 전용 복제본 (Read Replicas)
    주 데이터베이스의 복제본을 만들어 읽기 작업을 분산시킨다. 읽기가 많은 애플리케이션에 적합하다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    // MongoDB 읽기 전용 복제본 연결 예시 (Node.js)
    const mongoose = require('mongoose');
    
    // 주 데이터베이스에는 쓰기 작업만 수행
    const primaryConnection = mongoose.createConnection('mongodb://primary:27017/mydb', {
      useNewUrlParser: true
    });
    
    // 읽기 전용 복제본에는 읽기 작업만 수행
    const replicaConnection = mongoose.createConnection('mongodb://replica:27017/mydb', {
      useNewUrlParser: true,
      readPreference: 'secondaryPreferred'  // 복제본에서 읽기 우선
    });
    
    // 모델 정의
    const UserSchema = new mongoose.Schema({ name: String, email: String });
    
    // 쓰기 작업용 모델
    const UserWrite = primaryConnection.model('User', UserSchema);
    
    // 읽기 작업용 모델
    const UserRead = replicaConnection.model('User', UserSchema);
    
    // 사용 예시
    async function createUser(userData) {
      // 쓰기 작업은 주 데이터베이스로
      return await UserWrite.create(userData);
    }
    
    async function findUsers(query) {
      // 읽기 작업은 복제본으로
      return await UserRead.find(query);
    }
    
  2. 샤딩 (Sharding)
    데이터를 여러 데이터베이스에 분산 저장하는 방식이다. 대규모 데이터셋과 높은 쓰기 부하에 적합하다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    // MongoDB 샤딩 설정 예시 (쉘 명령어)
    /*
    // 샤드 서버 설정
    mongod --shardsvr --replSet shard1 --dbpath /data/shard1 --port 27018
    
    // 구성 서버 설정
    mongod --configsvr --replSet configRS --dbpath /data/configdb --port 27019
    
    // mongos 라우터 설정
    mongos --configdb configRS/localhost:27019 --port 27017
    
    // 데이터베이스 샤딩 활성화
    sh.enableSharding("mydb")
    
    // 컬렉션 샤딩 - userId를 기준으로 데이터 분산
    sh.shardCollection("mydb.users", { userId: 1 })
    */
    
    // Node.js에서 샤딩된 MongoDB 연결 (코드는 일반 연결과 동일)
    const mongoose = require('mongoose');
    mongoose.connect('mongodb://mongos:27017/mydb', { useNewUrlParser: true });
    
  3. CQRS (Command Query Responsibility Segregation)
    명령(쓰기)과 쿼리(읽기) 작업을 별도의 모델로 분리하여 각각 최적화하는 패턴이다.

캐싱 전략과 로드 밸런싱의 결합

캐싱은 로드 밸런싱 및 스케일링과 함께 사용할 때 백엔드 성능을 크게 향상시킬 수 있다:

  1. CDN (Content Delivery Network)
    정적 자산을 전 세계 엣지 서버에 캐싱하여 로드 시간을 단축하고 원본 서버의 부하를 줄인다.

  2. 애플리케이션 레벨 캐싱
    Redis나 Memcached와 같은 인메모리 데이터 저장소를 사용하여 자주 접근하는 데이터를 캐싱한다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    # Redis를 사용한 캐싱 예시 (Python)
    import redis
    import json
    
    # Redis 연결
    r = redis.Redis(host='redis-server', port=6379, db=0)
    
    def get_user(user_id):
        # 캐시에서 먼저 확인
        cached_user = r.get(f"user:{user_id}")
    
        if cached_user:
            # 캐시 히트: 캐시된 데이터 반환
            return json.loads(cached_user)
        else:
            # 캐시 미스: 데이터베이스에서 조회
            user = database.find_user(user_id)  # 가상의 데이터베이스 함수
    
            if user:
                # 결과를 캐시에 저장 (60초 만료)
                r.setex(f"user:{user_id}", 60, json.dumps(user))
    
            return user
    

성능 모니터링 및 최적화

효과적인 로드 밸런싱과 스케일링을 위해서는 지속적인 모니터링이 필수적이다:

  1. 주요 모니터링 지표

    • CPU 사용률: 70-80% 이상이면 스케일링 고려
    • 메모리 사용률: 스왑이 발생하면 메모리 부족 신호
    • 응답 시간: 사용자 경험에 직접적인 영향
    • 처리량: 초당 요청 수
    • 오류율: 5xx, 4xx 오류
    • 큐 길이: 처리 대기 중인 요청 수
  2. 성능 최적화 도구

    • Prometheus + Grafana: 메트릭 수집 및 시각화
    • ELK Stack (Elasticsearch, Logstash, Kibana): 로그 분석
    • New Relic, Datadog: 종합적인 APM(애플리케이션 성능 모니터링)
    • Jaeger, Zipkin: 분산 추적

현대적인 백엔드 아키텍처 설계 원칙

최적의 로드 밸런싱과 스케일링을 위한 설계 원칙:

  1. 스테이트리스(Stateless) 설계: 서버가 클라이언트 상태를 저장하지 않아 수평적 확장이 용이
  2. 마이크로서비스 아키텍처: 독립적으로 스케일링 가능한 작은 서비스로 분리
  3. 비동기 처리: 큐를 사용한 장시간 실행 작업의 분리
  4. 컨테이너화: Docker와 Kubernetes를 활용한 일관된 환경과 쉬운 스케일링
  5. 서버리스 아키텍처: AWS Lambda와 같은 서버리스 함수로 자동 스케일링

용어 정리

용어설명

참고 및 출처