Engineering
서머타임과 배치작업
2020년 4월 13일
원문에서 보기 ↗흔히 서머타임으로 불리지만 공식 명칭은 일광절약시간제(Daylight saving time)로 하절기에 표준시를 원래 시간보다 한 시간 앞당긴 시간을 쓰는 것을 말합니다. '여름엔 해가 기니까, 겨울보다 1시간씩 일찍 당겨서 생활하면 해가 떠 있는 동안 더 많은 일을 할(시킬) 수 있겠다’는 발상에서 비롯된 악랄한(...) 제도로¹⁾, 다행스럽게도 한국에서는 1988년을 마지막으로 더 이상 시행하고 있지 않습니다. 하지만, 제가 파견 나와 있는 미국은 애석하게도 여전히 이 제도를 시행하고 있습니다.
한편, 소프트웨어 엔지니어로서 우리는 많은 수의 배치작업을 돌리고²⁾ 있습니다. 시시 때때로 동작하는 마이크로 배치부터, 하루에 한 번 아무때나 돌리면 충분한 작업이 있는가 하면, 반드시 그 시간에 돌리지 않으면 안되는 작업 또한 존재합니다. 매일 똑같은 작업 돌리는 게 뭐 그리 어렵냐고 생각하기 쉽겠지만, 의외로 신경 써야 할 부분이 많은 것이 배치작업이라는 것을 해 본 사람은 다 알 겁니다. 그리고, 서머타임은 여기에 고민을 하나 더 얹어줍니다.
그림출처: https://www.flickr.com/photos/notionscapital/25000493314/in/photostream/
미국 서부 지방의 경우 서머타임을 3월 두 번째 일요일에 시작해서 11월 첫 번째 일요일에 마칩니다. 2020년의 경우 3월 8일 오전 2시에 시각을 오전 3시로 변경하며, 11월 1일에 오전 2시에 시각을 오전 1시로 변경하게 됩니다. 로컬타임 기준으로 2020년 3월 8일에는 2:00 ~ 2:59 가 존재하지 않는 것이며, 11월 1일에는 01:00 ~ 01:59 가 두 번 존재하는 셈이지요. 소프트웨어 엔지니어로서 우리의 고민은 과연, 해당 시각에 실행되는 배치작업은 정상적으로 수행될 수 있을까 하는 것입니다.
한 시간보다 짧은 주기로 돌아가는, 정확하게는 60분의 약수 단위로 실행되는 배치작업들은 별 문제 되지 않을 겁니다. 예를 들어, 10분에 한 번 돌아가는 배치작업들은 별 문제 없이 10분에 한 번 돌아갈 겁니다. 6번 덜 실행되거나, 6번 더 실행된 날이 있을 뿐입니다. 7분에 한 번 돌아가는 배치작업이 있다고요? 왜죠? -_-;
한 시간보다 긴 주기로 돌아가는 배치작업은 고민이 필요합니다. 예를 들어, 2시간마다 실행되는 작업이 있다면, 실행 간격이 줄어들거나 늘어나는 날이 있을 수 있습니다. 하루에 한 번 돌아가는 작업이 이 시간에 걸쳐있다면, 1년 중에 하루는 두 번 실행되거나 아예 실행되지 않는 날이 있을 수 있습니다.
여러 가지 해법을 생각해 볼 수 있습니다.
- UTC 기준으로 돌린다. (또는 서머타임이 적용되지 않는 시간대를 기준으로 실행한다.)
- 위 시간대를 피해서 돌린다.
- 서머타임 적용/해제에 따른 시각 변경을 겪어도 문제없이 돌아가는 배치작업 환경을 구축한다.
1은 얼핏 타당해보입니다만, 사실 그렇지 않습니다. 우선, 1년에 두 번 정도 윗사람에게 불려가게 될 것이라는 문제가 있습니다. 봄에는 7시에 전달되어야 할 리포트 메일이 왜 8시가 되어서 날아왔냐고 한 번 불려갈 것이고, 가을에는 왜 6시에 메일을 보내서 자는 사람을 깨우냐고 또 한 번 불려갈 것입니다. 각 배치작업이 정확한 일정에 따라서 한 치의 오차 없이 동작하게 만드는 데에는 성공했지만, 결과물을 받아보는 사람의 입장에서는 그렇지 못했기 때문입니다. 결국 배치 작업의 끝에 존재하는 것은 사람이니까요.
만약, 당신이 부지런해서 서머타임으로 시간이 변경되기 바로 전날에 이 작업들이 돌아가는 시각을 미리 변경한다고 해도, 1년에 두 번씩 이 일을 꼬박꼬박 챙겨야 한다는 사실은 변하지 않습니다. 서머타임 적용 또는 해제에 맞추어 다른 배치작업의 실행 시각 설정을 변경하는 배치작업을 만들어서 돌리는 방법도 생각해봤지만, 모든 종류의 배치작업 관리/실행 도구(또는 잡 스케줄러)에 대해서 적용 가능한 방법은 아닌 것 같습니다.
2의 방법은 1과 같은 부지런함을 갖출 필요가 없습니다. 새벽 1시부터 3시까지는 배치작업을 돌리지 않는 것이지요. 대신 다른 문제가 생깁니다. 배치작업을 실행할 수 있는 시간을 2시간이나 포기하게 되는 셈이니까요. 시간이 오래 걸리는 배치작업일수록 밤 시간대에 실행되기를 원하는 것이 보통인데, 그 소중한 시간대를 2시간이나 포기해야 하는 건 너무 아깝습니다. 그러니, 우리는 3의 방법을 고민하도록 합시다.
앞서 겁을 드리기는 했습니다만, 사실 실행 간격이 늘거나 주는 것은 대부분의 배치작업에서 큰 문제가 되지 않습니다. 특정 작업 회차가 빨리 끝나거나 늦게 끝나는 정도의 차이만이 있겠지요. 문제가 되는 것은 다음과 같은 경우들입니다.
같은 작업이 중복해서 실행되는 경우
서머타임이 끝나는 경우에 발생할 수 있습니다. 미국 서부 지역이라면 1:30 이 두 번 존재하는 날이 있으니까요. 이를 위해서는 배치작업이 중복해서 실행될 수 없도록 하거나, 중복해서 실행되어도 같은 결과가 나타나도록 만들 필요가 있습니다. 어느 쪽을 선택해야 할지는 각자의 상황에 따라 다를 것 같습니다. 똑똑한 배치작업 관리/실행 도구 중에는 중복 실행을 막아주는 경우도 있는 것 같으니 스스로가 사용하는 도구가 어떻게 동작하는지 알아두는 것이 필요하겠습니다.
작업을 건너뛰게 되는 경우
서머타임이 시작하는 시기에 발생할 수 있습니다. 미국 서부 지역에서 2020년 3월 8일 오전 2시 30분은 존재하지 않는 시각이니까요. 보통은 다음번 주기가 되어서야 작업이 누락되었다는 것을 발견하게 되겠습니다만, 작업 구성이나 모니터링 설정에 따라서는 좀 더 이른 시각에 발견하는 것도 가능하겠지요.
그런데, 혹시 배치작업 관리/실행 도구에서 이미 같은 고민을 해서 대응해 두지는 않았을까요? 대표적으로 많이 사용하는 crontab 의 매뉴얼 페이지를 찾아보니 역시나 고민의 흔적을 찾아볼 수 있었습니다.
Daylight Saving Time and other time changes
Local time changes of less than three hours,
such as those caused by the Daylight Saving Time changes, are handled in a special way.
This only applies to jobs that run at a specific time and jobs that run with a granularity greater
than one hour. Jobs that run more frequently are scheduled normally.
If time was adjusted one hour forward, those jobs that would have run in the interval
that has been skipped will be run immediately. Conversely, if time was adjusted backward,
running the same job twice is avoided.
Time changes of more than 3 hours are considered to be corrections to the clock or the timezone,
and the new time is used immediately.
일광 절약 시간 및 기타 시간 변화
일광 절약 시간 변경으로 인한 것과 같이 3시간 미만의 현지 시간 변화는 특별한 방법으로 처리한다.
이는 특정 시간에 실행되는 작업과 1시간 이상 세분화하여 실행되는 작업에만 적용된다. 더 자주
실행되는 작업은 정상적으로 스케줄링된다.
만약 한 시간 전에(으로) 시간을 조정한다면, 건너뛴 간격으로 실행되었을 그 작업들은 즉시 실행될
것이다. 반대로 시간을 뒤로 조정하면 동일한 작업을 두 번 실행하는 것은 피한다.
3시간 이상의 시간 변화는 시계나 시간대를 보정하는 것으로 간주되며, 새로운 시간을 즉시 사용한다.
(번역은 파파고가 해 주었습니다. "(으로)"만 제가 추가했어요.)
이 설명을 그대로 믿어도 되는 것일까요? 주변에 물어봐도 crontab이 이렇게 동작한다는 것을 아는 사람이 없었어요. 하긴, 저도 나름 오랫동안 개발을 해 왔습니다만, 이런 문제를 고민해 본 것이 처음이었으니 그럴만하다 싶기는 하더군요.
그래서, 실험해봤습니다. 실험을 위해서 TOAST Cloud의 US 리전³⁾에서 제일 저렴한 t2 인스턴스 하나를 생성했어요. 실험 대상으로는 crontab 과 spring scheduled task를 선택했습니다. 생각나는 게 이 두 개뿐이더라고요. 그리고, spring scheduled task는 crontab과는 달리 서머타임 예외 동작에 대한 문서가 존재하지 않았어요. 비교 실험 대상으로도 적당해 보였습니다.
두 개의 배치작업 관리/실행 도구에서 여러 가지 설정으로 배치작업을 돌려두었습니다. 각 배치작업은 다음과 같은 형식으로 로그를 남기도록 했어요.
{local-time} | {UTC} | {job-name}
실행 시각 설정은 다음과 같습니다. 여기 적어둔 것은 crontab 이지만, spring scheduled task의 설정도 거의 비슷합니다. 차이점이 있다면 spring scheduled task는 초 단위로 설정이 가능해서 앞에 '0'이 하나 더 붙습니다.
*/10 * * * * /home/centos/work/PDT_test/test_crontab.sh "Every 10 minutes #1" 001_10min_1
5-55/10 * * * * /home/centos/work/PDT_test/test_crontab.sh "Every 10 minutes #2" 002_10min_2
0 * * * * /home/centos/work/PDT_test/test_crontab.sh "Every 1 hour #1" 011_1hour_1
30 * * * * /home/centos/work/PDT_test/test_crontab.sh "Every 1 hour #2" 012_1hour_2
0 */2 * * * /home/centos/work/PDT_test/test_crontab.sh "Every 2 hours #1" 021_2hours_1
30 */2 * * * /home/centos/work/PDT_test/test_crontab.sh "Every 2 hours #2" 022_2hours_2
0 1-23/2 * * * /home/centos/work/PDT_test/test_crontab.sh "Every 2 hours #3" 023_2hours_3
30 1-23/2 * * * /home/centos/work/PDT_test/test_crontab.sh "Every 2 hours #4" 024_2hours_4
0 2 * * * /home/centos/work/PDT_test/test_crontab.sh "Every 1 day #1" 031_1day_1
30 2 * * * /home/centos/work/PDT_test/test_crontab.sh "Every 1 day #2" 032_1day_2
0 3 * * * /home/centos/work/PDT_test/test_crontab.sh "Every 1 day #3" 033_1day_3
30 3 * * * /home/centos/work/PDT_test/test_crontab.sh "Every 1 day #4" 034_1day_4
0 2,3,5 * * * /home/centos/work/PDT_test/test_crontab.sh "Designated #1" 041_designated_1
30 2,3,5 * * * /home/centos/work/PDT_test/test_crontab.sh "Designated #2" 042_designated_2
0 2,4,5 * * * /home/centos/work/PDT_test/test_crontab.sh "Designated #3" 043_designated_3
30 2,4,5 * * * /home/centos/work/PDT_test/test_crontab.sh "Designated #4" 044_designated_4
대조군이라 부를 수 있는 spring scheduled task는 상식적으로(?) 동작했습니다. PDT 3월 8일 2:00 ~ 2:59은 spring scheduled task에게는 존재하지 않는 시간이었어요. 위에 나열된 모든 배치작업 설정 중에서 해당 시간에 동작했어야 했던 모든 배치작업들이 실행되지 않았습니다.
반면, 실험군이라 할 수 있는 crontab에서는 재미있는 결과가 나왔습니다. 10분 단위 작업과, 1시간 단위 작업은 아무 일도 없었던 것처럼 동작했어요. 1일 주기 또는 실행 시각을 정해둔 경우에는 PDT 3월 8일 2:00 ~ 2:59에 실행되었어야 하는 작업들이 모두 PDT 3월 8일 3:00 에 실행되었습니다. 이 중에서도 특이한 것은 41번 실험입니다. 매일 새벽 2, 3, 5시에 동작하게 시켰는데요, 3월 8일 3:00에 두 개의 배치 작업이 실행되었습니다. 하나는 2시에 돌았어야 하는 작업이고, 다른 하나는 원래 3시에 돌았어야 하는 작업이지요.
Sat Mar 7 02:00:02 PST 2020 | Sat Mar 7 10:00:02 UTC 2020 | Designated #1
Sat Mar 7 03:00:01 PST 2020 | Sat Mar 7 11:00:01 UTC 2020 | Designated #1
Sat Mar 7 05:00:01 PST 2020 | Sat Mar 7 13:00:01 UTC 2020 | Designated #1
Sun Mar 8 03:00:01 PDT 2020 | Sun Mar 8 10:00:01 UTC 2020 | Designated #1
Sun Mar 8 03:00:01 PDT 2020 | Sun Mar 8 10:00:01 UTC 2020 | Designated #1
Sun Mar 8 05:00:01 PDT 2020 | Sun Mar 8 12:00:01 UTC 2020 | Designated #1
Mon Mar 9 02:00:02 PDT 2020 | Mon Mar 9 09:00:02 UTC 2020 | Designated #1
Mon Mar 9 03:00:01 PDT 2020 | Mon Mar 9 10:00:01 UTC 2020 | Designated #1
Mon Mar 9 05:00:01 PDT 2020 | Mon Mar 9 12:00:01 UTC 2020 | Designated #1
이상한 것은 2시간 주기로 실행시킨 배치작업이었습니다. 문서에 따르면 실행 주기가 1시간보다 길기 때문에 PDT 3월 8일 2:00 ~ 2:59에 실행되었어야 하는 작업들이 모두 PDT 3월 8일 3:00 에 실행되었어야 할 것 같은데 그렇지 않았습니다. 모두 무시되었어요.
Sat Mar 7 20:00:01 PST 2020 | Sun Mar 8 04:00:01 UTC 2020 | Every 2 hours #1
Sat Mar 7 22:00:01 PST 2020 | Sun Mar 8 06:00:01 UTC 2020 | Every 2 hours #1
Sun Mar 8 00:00:01 PST 2020 | Sun Mar 8 08:00:01 UTC 2020 | Every 2 hours #1
Sun Mar 8 04:00:01 PDT 2020 | Sun Mar 8 11:00:01 UTC 2020 | Every 2 hours #1
Sun Mar 8 06:00:01 PDT 2020 | Sun Mar 8 13:00:01 UTC 2020 | Every 2 hours #1
이럴 줄 알았으면 3시간 단위 작업도 만들어 볼 걸 그랬어요.⁴⁾
crontab의 이와 같은 과도한(?) 친절은 다른 고민거리를 던져줍니다. 배치 작업의 실행 간격에 대한 고민입니다. crontab의 이와 같은 동작은 각 배치 작업이 서로 독립적일 것이라는 가정에 기반하고 있을 것으로 생각합니다. 하지만, 현실은 그렇지 않을 수 있습니다. 1번 배치 작업의 실행 결과를 2번 배치 작업의 입력으로 사용하는 식으로 일련의 배치작업을 구성하는 경우가 실제로는 적지 않게 존재하니까요.
이런 경우 가장 쉽게 선택하는 방법은 두 배치 작업의 실행 시각을 충분히 벌려두는 것입니다. "1번이 보통 30분 정도면 실행이 완료되니까, 2번은 1번보다 1시간 늦게 시작하도록 하면 넉넉하겠지? 1번은 2시에, 2번은 3시에 실행하면 되겠다." 라고 생각하셨다면, 이 배치 작업은 3월 8일 새벽에는 실패했을 겁니다. 둘 다 3시에 실행되었을 테니까요. 그러니, 서로 다른 배치 작업이 서로 의존 관계에 있다면, 실행 시각을 기준으로 실행하는 것은 조심하시는 편이 좋겠습니다. 또는, Event 기반으로 동작하는 배치작업 관리/실행 도구를 선택하는 방법도 생각해 볼 수 있겠습니다.
TOAST Cloud US 리전 사용 비용 총 3,393원으로 개인적인 호기심은 충족되었습니다. 재미있는 결과라서 공유해봅니다. 부끄러운 수준의 날 코딩입니다만, 본 실험에 사용한 소스 코드 및 실행 로그도 github에 올려두었습니다.
https://github.com/iizs/daylight-saving
각주)
- 정확하게는 벤자민 프랭클린(다들 잘 알고 계시는 그 벤자민 프랭클린 맞습니다.)이 "early to bed and early to rise makes a man healthy, wealthy, and wise"라는 표현을 사용했다고 합니다. 출처는 https://en.wikipedia.org/wiki/Daylight_saving_time 입니다.
- 엄밀하게는 배치작업을 실행하다, 수행하다가 현대 표준어에 맞는 표현이겠지만, “돌리다”만큼 배치작업에 잘 어울리는 동사는 없다고 생각합니다.
- TOAST Cloud에서 작년 8월부터 미국 리전을 사용할 수 있게 되었습니다!
- 사실 지금도 타임서버 동기화를 끊어두고 실험 인스턴스의 기준 시각을 변경하면 (즉, 서버의 시각을 과거로 돌리면) 추가 실험은 가능할 텐데, 벌써 인스턴스를 반납했어요. 새로 인스턴스 올리고 셋업하기가 귀찮았어요.