12. 분산 스케줄러의 조정 장치 부재로 인한 배치 중복 실행

난이도 상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// [시나리오] 일일 보상 지급 배치 스케줄러 (서버-서버)
// 게임서버 클러스터는 여러 노드(인스턴스)로 이중화되어 있고, 각 노드는 독립적으로
// 기동되며 "매일 00:00 KST 에 접속 보상을 모든 온라인 플레이어에게 지급"하는
// 배치 작업을 자체 스케줄러(타이머)로 실행한다. 노드들은 서로 다른 프로세스이며
// 클러스터 내 어떤 노드가 "이번 배치를 담당할지"를 정하는 조정 장치는 없다 —
// 각 노드가 각자의 로컬 시계로 자정을 감지하면 곧바로 배치를 실행한다.
//
// [요구사항]
//   - 일일 보상 지급 배치는 클러스터 전체에서 하루에 정확히 한 번만 실행되어야 한다.
//   - 노드가 여러 대 떠 있어도(스케일 아웃/이중화) 위 요구사항이 지켜져야 한다.
//
// [과제] DailyRewardScheduler.RunIfDue 를 검토하고, 클러스터에 노드가 2대 이상
//        떠 있을 때 위 요구사항이 어떻게 어긋날 수 있는지 원인과 수정안을 제시하라.
//        표시된 (A)(B) 지점에 주목하라.

using System;
using System.Threading;
using System.Threading.Tasks;

class DailyRewardScheduler
{
    private DateTime _lastRunDate = DateTime.MinValue; // 이 노드가 마지막으로 실행한 날짜
    private readonly Timer _timer;

    public DailyRewardScheduler()
    {
        // 1분마다 자정 여부를 확인
        _timer = new Timer(_ => RunIfDue(), null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
    }

    private void RunIfDue()
    {
        var today = DateTime.UtcNow.Date;

        // (A)
        if (_lastRunDate >= today)
            return;

        // (B)
        RunDailyRewardBatch();
        _lastRunDate = today;
    }

    private void RunDailyRewardBatch()
    {
        // 온라인 플레이어 전원에게 접속 보상 지급
        foreach (var player in GetOnlinePlayers())
        {
            GrantLoginReward(player);
        }
    }

    private System.Collections.Generic.IEnumerable<int> GetOnlinePlayers() => Array.Empty<int>();
    private void GrantLoginReward(int playerId) { /* 보상 지급 */ }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.