본문 바로가기

🧑🏻‍💻 Dev/오류해결

[Spring | AWS EC2] AWS EC2에 @Schedule 기능 적용 시 시간 이슈

1. 문제


매일 00시 00분마다 작동되는 스케쥴러를 구현하기 위해서 다음과 같이 코드를 작성했습니다.

@Scheduled(cron = "0 0 0 * * *", zone = "Asia/Seoul")
@Transactional
public void surveyScheduling() {
    log.info("[{}] 스케쥴러 작동 설문조사 데이터 정리", LocalDateTime.now());

    LocalDate now = LocalDate.now();
    List<Survey> surveys = surveyRepository.findAllByStatusIsNot(SurveyStatus.REVERT);

    for (Survey survey : surveys) {
        if (isEnd(survey.getEndDate().toLocalDateTime(), now)) {
            survey.setStatus(SurveyStatus.REVERT);
        }
        if (isStart(survey.getStartDate().toLocalDateTime(), now)) {
            survey.setStatus(SurveyStatus.IN_PROGRESS);
        }
    }
}

private boolean isEnd(LocalDateTime endDate, LocalDate now) {
    return endDate.toLocalDate().isBefore(now);
}

private boolean isStart(LocalDateTime startDate, LocalDate now) {
    return startDate.toLocalDate().isEqual(now);
}

로컬에서 매일 00시 00분 정각에 작동되는 것을 테스트했습니다. (완전 무식하게 정각까지 안 자고 기다리고 있다가 확인했다. 😆)

 

그래서 정상적으로 작동되는 것을 확인하고, AWS EC2 서버에 변경된 코드를 적용해서 배포했습니다.

그리고 하루를 기다렸다가 스케쥴러가 작동 했는지 확인하려고 데이터베이스를 확인해 봤습니다.

음..? 저 빨간색 네모 안에 있는 status 값이 분명 WAIT에서 IN_PROGRESS로 변경되었어야 하는데... 왜 변하지 않았을까...?

 

스케쥴러가 작동 안 됐나 하면서 AWS 서버에 접근해서 로그 기록을 확인해 봤습니다. 분명 스케쥴러가 작동했다면 INFO 레벨의 로그가 찍혔을 것이기 때문에...

분명 INFO 레벨의 지정해 둔 로그가 찍혔다. 그럼 정상적으로 스케쥴러가 작동했다는 것을 의미한다. 근데 제가 의도한 시간이었던 2023년 8월 4일 00:00:00이 아닌 2023년 8월 3일 15:00:00에 동작을 하는 문제가 발견되었습니다.

 

 

2. 문제 분석


처음에 AWS EC2 인스턴스를 만들 때 Asia/Seoul로 선택해서 만들어서 당연히 동작될 줄 알았습니다. 그래서 일단 AWS 서버에 접근해서 date 명령어로 현재 서버의 시간을 한번 확인해 봤습니다.

현재 제 컴퓨터에서는 시간이 2023년 8월 4일 19:05인데, 서버에서는 2023년 8월 4일 10:04로 찍혔습니다. AWS 서버와 현재 제 로컬의 시간이 다르다는 것을 확인했습니다. 

 

UTC라는 세계 표준 시간을 사용해서 현재 한국의 시간에서 9시간을 빼면 AWS EC2의 서버 시간이 된다고 합니다. 허허허....

 

그렇게 생각을 하니깐 위에서 8월 3일 15:00에 작동한 게 이해가 됐습니다. 한국 시간으로는 8월 4일 00:00에서 9시간을 빼면 8월 3일 15:00가 나오기 때문이었습니다.

 

 

 

3. 해결 과정


(1) 서버 시간에서 그럼 9시간을 더해서 재설정해두면 되지 않을까?

되게 단순한 발상인데, 나름 그럴싸한 해결방법 같아 보입니다. 바로 실행에 옮기기 전에 항상 먼저 구글링을 시작합니다.

(터미널에서 뭐 잘못 건드렸다가 노트북 싹 다 날려버렸던 경험이 있어서.... 항상 제대로 알고 나서 실행하는 편...)

 

>> sudo date -s "2023-08-04 19:20:00"

위 명령어로 서버의 시간을 재설정할 수 있다고 한다. 근데 역시 위험한 느낌의 직감은 틀리지 않습니다.

위에 있는 명령어로 시간을 재설정하게 되면 EC2 타임존이 기본적으로 UTC로 되어 있어서 나머지 근본적인 지역에 대한 설정은 하나도 안 바뀌고 날짜만 한국 날짜로 바뀐 거여서 다른 서비스와 시간 동기를 맞출 때 시간이 다르다고 판단되어 의도하지 않은 오류가 발생할 수 있다고 합니다.

 

 

(2) AWS 서버의 타임존을 한국으로 바꿔보자.

첫 번째 방법처럼 날짜만 한국에 맞추는 것이 아니고, 근본적인 타임존을 한국으로 한번 설정해 봅시다.

>> sudo cat /etc/localtime

위 명령어로 한번 확인을 해보면 현재는 UTC0로 쓰여있습니다. 즉, 현재는 UTC 기준으로 작동된다는 것을 의미합니다.

이 부분을 이제 한국의 타임존을 쓸 수 있도록 수정해 봅시다.

 

>> sudo rm /etc/localtime
>> sudo ln -s /usr/share/zoneinfo/Asia/Seoul /etc/localtime

기존에 있는 UTC 타임존 파일을 제거하고, 서울 기준의 타임존 파일을 새롭게 적용하는 명령어입니다.

 

위 명령어로 재설정을 하고, 다시 date 명령어로 서버의 시간을 확인해 봅시다.

이제 시간이 의도한 대로 한국 시간으로 설정이 되었습니다.

이제 데이터베이스의 startDate를 2023년 8월 5일로 바꾸고, 하루를 기다려보겠습니다.

 

이제 2023년 8월 5일 00:00이 되면 저 id 1번의 데이터에서 status 값이 WAIT에서 IN_PROGRESS가 되어야 합니다. 제발 돼라...

 

데이터베이스를 확인해보니 정상적으로 스케쥴러가 2023년 8월 5일 00:00에 동작을 한 거 같네요! 😆

이제 AWS 서버에도 INFO 로그가 찍혔는지도 한번 확인해보겠습니다.

 

음...동작은 해서 update 쿼리가 실행되어 WAIT 상태를 IN_PROGRESS로 변경시키기는 했지만 여전히 로그에 찍힌 시간을 보면 UTC 기준으로 15:00:00이라고 찍히는 것을 확인할 수 있습니다.

 

 

(3) 프로젝트 기본 TimeZone 변경하기 + 로그에 찍히는 시간도 변경

@SpringBootApplication
public class ServerApplication {

    public static void main(String[] args) {

        SpringApplication.run(ServerApplication.class, args);
    }

    @PostConstruct
    public void init() {
        TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
        System.out.println(">>> 현재 시간은 " + LocalDateTime.now());
    }

}

@PostConstruct를 사용해서 Spring 프로젝트의 기본 TimeZone 세팅을 "Asia/Seoul"로 지정하도록 했습니다.

그리고 제대로 동작하는지 확인하기 위해서 LocalDateTime.now()를 사용해서 콘솔에 흔적을 남기도록 하고 한번 동작해 봤습니다.

빨간색을 보면 정상적으로 "Asia/Seoul"의 시간이 찍힌 것을 확인할 수 있었습니다. (현재 노트북 시간과 동일)

하지만 파란색 부분을 보면 로그로 찍히는 시간은 여전히 UTC 기준으로 찍히는 것을 볼 수 있습니다.

이 부분도 한번 바꿔봅시다! 간단하게 application.yml 파일을 설정해 주면 됩니다.

logging:
  pattern:
    dateformat: yyyy-MM-dd HH:mm:ss.SSSz,Asia/Seoul

Log에 찍히는 시간의 dateformat을 설정해 줄 수 있습니다.

이때 Timezone은 당연히 Asia/Seoul로 해주면 됩니다.

이제 다시 서버에 jar 파일을 올려서 실행시켜서 확인해 봅시다.

아주 완벽하게 LocalDateTime.now()도 현재 시간으로 뜨고, 로그에 찍히는 시간도 현재 시간으로 찍히는 것을 확인할 수 있습니다!

 

이제 아래와 같이 스케쥴러 코드를 살짝만 수정해서 10초가 될 때마다 작동되는 스케쥴러를 작동시켜 보겠습니다.

@Scheduled(cron = "10 * * * * *", zone = "Asia/Seoul")
@Transactional
public void surveyScheduling() {
    log.info("[{}] 스케쥴러 작동 설문조사 데이터 정리", LocalDateTime.now());

    LocalDate now = LocalDate.now();
    List<Survey> surveys = surveyRepository.findAllByStatusIsNot(SurveyStatus.REVERT);

    for (Survey survey : surveys) {
        if (isEnd(survey.getEndDate().toLocalDateTime(), now)) {
            survey.setStatus(SurveyStatus.REVERT);
        }
        if (isStart(survey.getStartDate().toLocalDateTime(), now)) {
            survey.setStatus(SurveyStatus.IN_PROGRESS);
        }
    }
}

실행 결과를 확인해 보면 로그에 찍히는 시간과 작동할 때 LocalDateTime.now()로 찍은 시간이 동일한 것을 확인했습니다. 🙆🏻‍♂️

다시 코드를 원복해서 서버에 적용해둡시다!

 

 

🔗 참고한 링크

[AWS] EC2 TimeZone 변경하기

Springboot log를 한국시간으로 출력