node-cron을 사용하여 특정 시간대에 작업을 예약할 때, 특히 timezone: 'Asia/Seoul' 옵션을 사용했음에도 불구하고 예약된 작업이 예상보다 하루 늦게 실행되는 문제가 발생할 수 있습니다. 이 문제는 주로 서버의 시스템 시간대 설정과 node-crontimezone 옵션 해석 간의 불일치에서 비롯됩니다. 이 가이드에서는 이 문제의 원인을 파악하고, 명확한 해결 방법을 제시합니다.

문제의 원인

node-croncron.schedule 함수에 timezone 옵션을 지정하면, 해당 크론 표현식(0 0 * * * 등)이 지정된 시간대(예: Asia/Seoul)를 기준으로 해석됩니다. 예를 들어, 0 0 * * *timezone: 'Asia/Seoul'을 함께 사용하면, node-cron은 매일 자정(0시 0분)을 Asia/Seoul 시간 기준으로 계산하여 작업을 실행해야 합니다.

하지만 다음과 같은 경우에 하루가 밀려 실행되는 문제가 발생할 수 있습니다:

  1. timezone 옵션의 오적용 또는 누락: cron.schedule 함수 호출 시 timezone: 'Asia/Seoul' 옵션이 올바르게 전달되지 않았거나, 다른 설정에 의해 무시되는 경우 node-cron은 서버의 기본 시스템 시간대를 따르게 됩니다. 많은 서버가 기본적으로 UTC(협정 세계시)를 사용하므로, 만약 서버가 UTC이고 timezone 옵션이 제대로 적용되지 않았다면, 0 0 * * *는 UTC 자정을 의미하게 됩니다. UTC 자정은 Asia/Seoul 기준으로는 오전 9시이므로, Asia/Seoul 자정에 실행되기를 기대했던 작업이 오전 9시에 실행되어 날짜상으로 하루가 밀린 것처럼 보일 수 있습니다.
  2. 크론 표현식에 대한 오해: Asia/Seoul 시간대를 기준으로 0 0 * * *가 의미하는 바를 정확히 이해하지 못하고, 다른 시간대를 기준으로 스케줄을 작성했을 때 발생하는 혼동입니다.

해결 방법

이 문제를 해결하는 가장 확실한 방법은 node-crontimezone 옵션을 명확히 지정하고, 실제 작업 실행 시각을 로그로 확인하여 의도대로 동작하는지 검증하는 것입니다.

1. timezone 옵션 명확히 지정

cron.schedule 함수를 호출할 때 options 객체에 timezone: 'Asia/Seoul'을 정확하게 포함해야 합니다. 이는 node-cron에게 크론 표현식을 Asia/Seoul 시간대 기준으로 해석하도록 지시합니다.

import { schedule } from 'node-cron';

console.log('node-cron 스케줄러를 시작합니다.');
console.log(`현재 시스템 시간 (KST): ${new Date().toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' })}`);
console.log(`현재 시스템 시간 (UTC): ${new Date().toISOString()}`);

// 매일 자정(0시 0분) Asia/Seoul 기준으로 실행되도록 스케줄링
// 이 스케줄은 Asia/Seoul의 0시 0분에 정확히 실행됩니다.
schedule('0 0 * * *', () => {
    const now = new Date();
    console.log(`[${now.toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' })}] Asia/Seoul 자정 작업이 실행되었습니다.`);
    // 여기에 실행할 로직 추가
}, {
    timezone: 'Asia/Seoul' // 핵심: Asia/Seoul 시간대로 크론 표현식 해석
});

// 비교용: timezone 옵션이 없는 경우 (서버 기본 시간대 사용)
// 만약 서버의 기본 시간대가 UTC라면, 이 작업은 UTC 자정에 실행되므로
// Asia/Seoul 기준으로는 오전 9시에 실행되어 '하루 밀림'의 원인이 될 수 있습니다.
schedule('0 0 * * *', () => {
    const now = new Date();
    console.log(`[${now.toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' })}] (시스템 기본 시간대) 자정 작업이 실행되었습니다. 서버가 UTC면 KST 오전 9시입니다.`);
}, {
    // timezone 옵션을 생략하면 시스템 기본 시간대를 따릅니다.
});

// 스케줄러가 백그라운드에서 계속 실행되도록 유지 (예시)
// 실제 애플리케이션에서는 프로세스가 종료되지 않도록 다른 방식으로 관리해야 합니다.
setInterval(() => {}, 1000 * 60 * 60); // 1시간마다 더미 작업으로 프로세스 유지

2. 서버 시간대 설정 확인 (선택 사항)

node-crontimezone 옵션이 최우선적으로 적용되지만, 서버의 시스템 시간대가 Asia/Seoul과 일치하는지 확인하는 것도 좋습니다. TZ 환경 변수를 설정하여 Node.js 프로세스의 기본 시간대를 지정할 수 있습니다.

# Linux/macOS 환경에서
export TZ='Asia/Seoul'
node your-cron-script.js

# 또는 Dockerfile 내에서
ENV TZ Asia/Seoul

이 방법은 node-crontimezone 옵션이 제대로 작동하지 않을 때 보조적으로 사용할 수 있지만, node-cron의 옵션이 더 명시적이고 권장되는 방식입니다.

흔히 하는 실수 및 주의점

  • timezone 옵션의 오타: Asia/Seoul과 같은 시간대 문자열은 대소문자를 포함하여 정확히 일치해야 합니다. 오타가 있는 경우 node-cron이 해당 시간대를 인식하지 못하고 시스템 기본 시간대로 폴백할 수 있습니다.
  • new Date() 객체의 시간대: node-cron 작업 내부에서 new Date()를 사용하여 현재 시각을 얻을 때, 이 객체는 기본적으로 Node.js 프로세스의 시스템 시간대를 따릅니다. 따라서 node-cron 스케줄이 Asia/Seoul에 맞춰 실행되더라도, new Date()로 얻은 시간을 별도로 Asia/Seoul로 포매팅하지 않으면 예상과 다른 시간으로 보일 수 있습니다. 로그를 출력할 때는 toLocaleString 등의 메서드를 사용하여 명시적으로 Asia/Seoul 시간대로 변환하여 확인하는 것이 좋습니다.
  • node-cron 버전: 아주 오래된 node-cron 버전에서는 timezone 옵션의 동작 방식에 차이가 있을 수 있습니다. 최신 버전을 사용하는 것이 가장 안전하며, 문제가 지속될 경우 node-cron 공식 문서를 통해 해당 버전의 timezone 옵션 동작 방식을 확인해야 합니다.
  • 서버 재시작: 환경 변수를 변경하거나 코드를 수정한 후에는 Node.js 프로세스를 반드시 재시작하여 변경 사항이 적용되도록 해야 합니다.