Load Shifting

Load Shifting은 컴퓨팅 또는 시스템 워크로드를 효율적으로 관리하고 분배하기 위한 전략으로, 피크 시간대의 부하를 비피크 시간대로 이동시키는 것을 목표로 한다. 이를 통해 리소스 사용을 최적화하고 비용을 절감하며, 시스템의 안정성과 성능을 개선할 수 있다. 이 전략은 클라우드 컴퓨팅, 데이터 센터 운영, 그리고 대규모 분산 시스템에서 널리 사용된다.

로드 시프팅(Load Shifting)의 개념

로드 시프팅은 시스템 부하를 효율적으로 관리하는 전략으로, 피크 시간대의 작업 부하를 비피크 시간대로 이동시켜 자원 활용을 최적화하는 방법이다. 이는 마치 전기 그리드에서 전력 수요가 낮은 시간대로 소비를 옮기는 것과 유사한 개념으로, IT 인프라에 적용된다.

핵심 원리

로드 시프팅의 기본 원리는 다음과 같다:

  1. 부하 분산: 시스템 자원에 대한 수요를 시간적으로 분산
  2. 자원 최적화: 유휴 자원을 활용하여 전체 시스템 효율성 향상
  3. 비용 절감: 피크 시간대 자원 확장 필요성 감소
  4. 안정성 향상: 시스템 과부하 위험 감소

Load Shifting의 유형

  1. 시간 기반 Load Shifting
    • 개념: 작업을 피크 시간대에서 비피크 시간대로 이동하여 리소스 사용을 균형 있게 분배.
    • 예시:
      • 데이터 분석 작업을 야간에 실행하여 낮 동안의 사용자 요청 처리에 집중.
  2. 지리적 Load Shifting
    • 개념: 워크로드를 서로 다른 지역 또는 데이터 센터로 이동하여 지역별 부하를 분산.
    • 예시:
      • 북미 데이터 센터가 피크 상태일 때 유럽 데이터 센터로 작업을 전환.
  3. 리소스 기반 Load Shifting
    • 개념: CPU, 메모리, 네트워크 대역폭 등 특정 리소스의 사용량에 따라 워크로드를 조정.
    • 예시:
      • 네트워크 트래픽이 높은 경우 로컬 캐싱을 활용해 서버 부하 감소.

로드 시프팅의 장점과 단점

장점

  1. 비용 효율성: 피크 시간대 확장 비용 감소
  2. 자원 활용 최적화: 유휴 자원의 효율적 활용
  3. 시스템 안정성 향상: 트래픽 폭주로 인한 장애 위험 감소
  4. 에너지 효율성: 전체 에너지 소비 최적화 가능
  5. 사용자 경험 개선: 피크 시간대 응답 시간 단축

단점

  1. 복잡성 증가: 시스템 설계 및 관리 복잡도 상승
  2. 지연 시간 증가: 일부 작업의 처리 지연 가능성
  3. 추가 인프라 필요: 큐, 스케줄러 등 추가 구성 요소 필요
  4. 모니터링 비용: 효과적인 모니터링 시스템 구축 필요

백엔드 시스템에서의 로드 시프팅 구현 방법

백엔드 시스템에서 로드 시프팅을 구현하는 다양한 방법이 있다:

작업 스케줄링(Task Scheduling)

비동기 작업 처리 시스템을 구축하여 즉시 처리가 필요하지 않은 작업을 대기열에 넣고 시스템 부하가 낮은 시간에 처리한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Python으로 구현한 작업 스케줄링 예시 (Celery 사용)
from celery import Celery
from celery.schedules import crontab

app = Celery('tasks', broker='redis://localhost:6379/0')

# 시스템 부하가 낮은 시간대(새벽 3시)에 작업 실행 설정
@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    # 데이터 집계 작업을 새벽 3시에 실행
    sender.add_periodic_task(
        crontab(hour=3, minute=0),
        aggregate_data.s(),
    )

@app.task
def aggregate_data():
    # 리소스 집약적 데이터 집계 작업 수행
    pass

지리적 로드 밸런싱(Geographic Load Balancing)

다양한 지역의 데이터 센터나 클라우드 리전을 활용하여 다른 시간대의 트래픽을 분산한다.

 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
34
35
36
// AWS Route 53을 사용한 지리적 로드 밸런싱 설정 예시 (AWS CDK)
const usEastAlb = new ApplicationLoadBalancer(this, 'USEastALB', {
  vpc: usEastVpc,
  internetFacing: true
});

const euWestAlb = new ApplicationLoadBalancer(this, 'EUWestALB', {
  vpc: euWestVpc,
  internetFacing: true
});

// 지리적 기반 라우팅 정책 설정
const hostedZone = HostedZone.fromLookup(this, 'HostedZone', {
  domainName: 'example.com'
});

new Route53.ARecord(this, 'GeoDNS', {
  zone: hostedZone,
  target: Route53.RecordTarget.fromAlias(
    new targets.LoadBalancerTarget(usEastAlb)
  ),
  // 지리적 위치에 따라 라우팅 규칙 설정
  geoLocation: {
    continentCode: 'NA' // 북미 트래픽
  }
});

new Route53.ARecord(this, 'GeoDNSEurope', {
  zone: hostedZone,
  target: Route53.RecordTarget.fromAlias(
    new targets.LoadBalancerTarget(euWestAlb)
  ),
  geoLocation: {
    continentCode: 'EU' // 유럽 트래픽
  }
});

자동 스케일링(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
29
30
31
32
33
34
35
36
37
38
39
40
41
# AWS CDK를 사용한 시간 기반 자동 스케일링 설정
from aws_cdk import (
    core,
    aws_autoscaling as autoscaling,
    aws_ec2 as ec2,
)

class TimeBasedScalingStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)
        
        vpc = ec2.Vpc(self, "VPC")
        
        asg = autoscaling.AutoScalingGroup(self, "ASG",
            vpc=vpc,
            instance_type=ec2.InstanceType.of(
                ec2.InstanceClass.BURSTABLE3, 
                ec2.InstanceSize.MICRO
            ),
            machine_image=ec2.AmazonLinuxImage(),
            min_capacity=2,
            max_capacity=10
        )
        
        # 피크 시간대 용량 증가 (업무 시간 동안)
        asg.scale_on_schedule("ScaleUpDuringBusinessHours",
            schedule=autoscaling.Schedule.cron(
                hour="8", 
                minute="0"
            ),
            desired_capacity=8  # 업무 시간에는 8대로 확장
        )
        
        # 비피크 시간대 용량 감소 (야간)
        asg.scale_on_schedule("ScaleDownAtNight",
            schedule=autoscaling.Schedule.cron(
                hour="20", 
                minute="0"
            ),
            desired_capacity=2  # 야간에는 2대로 축소
        )

메시지 큐(Message Queue) 시스템

메시지 큐를 사용하여 작업을 비동기적으로 처리하고 부하를 분산한다.

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# RabbitMQ를 사용한 메시지 큐 구현 예시
import pika
import json
import time

# 메시지 생산자: 피크 시간에 작업을 큐에 저장
def publish_task(task_data):
    connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = connection.channel()
    
    # 작업 우선순위에 따른 큐 선언
    channel.queue_declare(queue='task_queue', durable=True)
    
    # 메시지 영속성 설정
    message = json.dumps(task_data)
    channel.basic_publish(
        exchange='',
        routing_key='task_queue',
        body=message,
        properties=pika.BasicProperties(
            delivery_mode=2,  # 메시지 지속성 확보
            priority=task_data.get('priority', 5)  # 우선순위 설정
        )
    )
    
    connection.close()

# 메시지 소비자: 비피크 시간에 작업 처리
def consume_tasks():
    connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = connection.channel()
    
    channel.queue_declare(queue='task_queue', durable=True)
    
    # 작업자가 한 번에 하나의 메시지만 처리하도록 설정
    channel.basic_qos(prefetch_count=1)
    
    def callback(ch, method, properties, body):
        task = json.loads(body)
        print(f"처리 중인 작업: {task}")
        
        # 작업 처리 시뮬레이션
        time.sleep(task.get('estimated_time', 1))
        
        # 작업 완료 확인
        ch.basic_ack(delivery_tag=method.delivery_tag)
    
    channel.basic_consume(queue='task_queue', on_message_callback=callback)
    
    print('작업 처리 대기 중...')
    channel.start_consuming()

로드 시프팅 구현 시 고려사항

효과적인 로드 시프팅 전략을 수립할 때 고려해야 할 사항들:

  1. 데이터 의존성 분석
    작업 간의 의존성을 파악하여 시프팅 가능한 작업과 실시간 처리가 필요한 작업을 구분한다. 데이터 흐름 다이어그램(DFD)을 사용하여 의존성을 시각화하는 것이 도움이 된다.

  2. 워크로드 패턴 모니터링
    시스템 부하 패턴을 분석하여 피크 시간과 비피크 시간을 정확히 파악한다. 이를 위해 다음과 같은 도구를 활용할 수 있다:

    • Prometheus + Grafana
    • AWS CloudWatch
    • New Relic
    • Datadog
  3. SLA(Service Level Agreement) 준수 확인
    로드 시프팅으로 인해 서비스 수준 계약을 위반하지 않도록 주의한다. 특히 시간에 민감한 작업의 경우 주의가 필요하다.

  4. 점진적 구현
    로드 시프팅은 한 번에 모든 시스템에 적용하기보다 점진적으로 구현하는 것이 안전하다. 먼저 영향이 적은 시스템부터 시작하여 검증 후 확장하는 접근법을 권장한다.

로드 시프팅의 실제 적용 사례

데이터베이스 백업 및 유지보수

데이터베이스 백업, 인덱스 재구성, 통계 업데이트 등의 유지보수 작업을 트래픽이 낮은 시간대로 이동한다.

 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
-- MS SQL Server에서 유지보수 작업 일정 설정 예시
USE msdb;
GO

-- 백업 작업 생성
EXEC dbo.sp_add_job
    @job_name = N'Weekly Database Backup',
    @enabled = 1;

-- 작업 단계 추가
EXEC dbo.sp_add_jobstep
    @job_name = N'Weekly Database Backup',
    @step_name = N'Full Backup',
    @subsystem = N'TSQL',
    @command = N'BACKUP DATABASE [CustomerDB] TO DISK = N''D:\Backups\CustomerDB.bak'' WITH COMPRESSION';

-- 비피크 시간대(토요일 새벽 2시)로 일정 설정
EXEC dbo.sp_add_schedule
    @schedule_name = N'WeekendMaintenanceSchedule',
    @freq_type = 8, -- 주간 
    @freq_interval = 64, -- 토요일
    @active_start_time = 20000; -- 오전 2시

-- 작업에 일정 연결
EXEC dbo.sp_attach_schedule
    @job_name = N'Weekly Database Backup',
    @schedule_name = N'WeekendMaintenanceSchedule';

대규모 배치 처리

주문 처리, 청구서 생성, 보고서 작성 등의 배치 작업을 시스템 부하가 낮은 시간에 실행한다.

 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
34
35
36
37
38
// Spring Batch를 사용한 배치 작업 설정 예시
@Configuration
@EnableBatchProcessing
public class BatchConfig {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job invoiceGenerationJob() {
        return jobBuilderFactory.get("invoiceGenerationJob")
                .flow(step1())
                .end()
                .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .<Customer, Invoice>chunk(100)
                .reader(customerReader())
                .processor(invoiceProcessor())
                .writer(invoiceWriter())
                .build();
    }

    // 작업 스케줄러 설정 - 매일 밤 12시에 실행
    @Bean
    public Trigger jobTrigger() {
        return TriggerBuilder.newTrigger()
                .forJob(invoiceGenerationJob())
                .withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 * * ?"))
                .build();
    }
}

CDN(Content Delivery Network) 콘텐츠 사전 로딩

피크 시간 전에 CDN에 콘텐츠를 미리 로드하여 원본 서버의 부하를 분산한다.

 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
34
35
36
37
38
39
40
41
42
43
// AWS CloudFront 사전 로딩 Lambda 함수 예시
const AWS = require('aws-sdk');
const cloudfront = new AWS.CloudFront();

exports.handler = async (event) => {
    // 사전 로딩할 콘텐츠 목록
    const contentPaths = [
        '/images/banner.jpg',
        '/css/main.css',
        '/js/app.js',
        // 인기 콘텐츠 경로
    ];
    
    const cloudfrontDistributionId = process.env.DISTRIBUTION_ID;
    
    try {
        // 각 경로에 대해 CloudFront 무효화 요청 생성
        // (무효화 요청은 콘텐츠를 원본에서 다시 가져오도록 함)
        const invalidationParams = {
            DistributionId: cloudfrontDistributionId,
            InvalidationBatch: {
                CallerReference: `preload-${Date.now()}`,
                Paths: {
                    Quantity: contentPaths.length,
                    Items: contentPaths
                }
            }
        };
        
        await cloudfront.createInvalidation(invalidationParams).promise();
        
        return {
            statusCode: 200,
            body: JSON.stringify({ message: '콘텐츠 사전 로딩 완료' })
        };
    } catch (error) {
        console.error('사전 로딩 실패:', error);
        return {
            statusCode: 500,
            body: JSON.stringify({ message: '사전 로딩 실패', error })
        };
    }
};

용어 정리

용어설명

참고 및 출처