직접 해 본 결과만 적습니다 · 확인하지 못한 것은 그렇게 표시합니다
써 AI 마케팅 적용기 시중에 나온 AI 방법을 실무에 넣어 보고, 쓸만한지 남깁니다
반복업무 줄이기

예약 작업 실패 알림, 원인이 두 가지입니다 — 돌았는데 실패 vs 아예 안 돎

계속 쓴다2026-08-07·읽기 5분·글 써봄

예약 작업이 실패했다는 알림을 여러 번 받았습니다. 문구가 다 같아서 같은 문제인 줄 알았습니다.

로그를 종류별로 세어 보니 원인이 둘로 갈렸습니다.

  • A. 작업이 돌았고, 그 안에서 실패했다
  • B. 작업이 아예 안 돌았다

고치는 곳이 완전히 다릅니다. 이걸 안 나누면 A를 고치는 동안 B가 계속 쌓입니다. 저는 한동안 그러고 있었습니다.

크론·예약 작업 실패를 가르는 순서

1. 알림을 받으면 실행 기록부터 봅니다 2. 돌았으면 → 작업 로그에서 응답 코드를 찾습니다 3. 안 돌았으면 → 스케줄러 설정을 봅니다 4. 알림 문구에 어느 쪽인지 넣어 다음부터 안 헤매게 합니다

왜 구분이 안 되나

증상 — 알림 문구가 같습니다. "예약 작업 실패" 한 줄로는 어느 쪽인지 모릅니다.

같은 알림 문구인데 원인이 두 갈래로 나뉘는 것을 정리한 터미널 화면

원인 — 알림을 결과만 보고 만들었기 때문입니다. "결과물이 없다 → 실패"로 판정하면, 돌다 실패한 것과 시작도 안 한 것이 같은 문구가 됩니다.

구분하는 법 — 실행 기록부터

해결 — 코드나 로그를 뒤지기 전에 "돌긴 돌았나" 를 먼저 봅니다.

타이머의 마지막 실행 시각을 확인하는 터미널 화면

systemctl --user list-timers "작업이름*" --no-pager
보이는 것 판정
마지막 실행이 예정 주기 안에 있다 A — 돌았고 안에서 실패
마지막 실행이 한참 전이다 B — 안 돌았다
아예 목록에 없다 B — 등록이 풀렸다

이 한 줄이 조사 방향을 정합니다. A와 B는 볼 곳이 겹치지 않습니다.

경우 A — 예약 작업이 돌았는데 안에서 실패

작업 로그를 봅니다. 대개 원인이 로그에 있습니다.

흔한 원인 단서
인증 만료 401
권한 없음 403
대상이 없음 조회 결과 0건
외부 서비스 장애 타임아웃·5xx
코드 오류 예외 추적 기록

여기서 중요한 게 하나 있습니다. 응답 코드를 로그에 남겨야 합니다. "실패했습니다" 만 적혀 있으면 위 표를 못 씁니다. 저는 이걸 나중에 추가했는데, 그 뒤로 원인 찾는 시간이 확 줄었습니다.

처음엔 로그를 짧게 두는 게 깔끔하다고 생각했습니다. 그런데 짧은 로그는 나중에 아무 도움이 안 됩니다. 저는 지금 실패할 때만 자세히 남기는 쪽으로 두고 있습니다. 성공은 한 줄, 실패는 응답 코드·요청 대상·예외 종류까지 남깁니다.

경우 B — 크론이 아예 안 돌았다

이쪽이 더 안 보입니다. 작업 로그 자체가 없기 때문입니다. 없는 로그를 아무리 뒤져도 나올 게 없습니다.

예약 작업이 아예 안 돌았을 때 확인할 곳들을 정리한 터미널 화면

원인 확인·조치
타이머가 꺼져 있음 systemctl --user is-enabled 로 확인 후 다시 켬
그 시각에 서버가 꺼져 있었음 Persistent=true 로 켜질 때 밀린 것을 실행
이전 실행이 안 끝나 겹침 잠금 파일로 중복 실행 방지
실행 경로·사용자가 달라 즉시 죽음 WorkingDirectory·환경변수 확인
재부팅 뒤 등록이 풀림 enable 이 돼 있는지 확인

마지막 줄을 겪었습니다. 임시로 띄워 둔 것은 재부팅하면 사라집니다. 그날 이후로 조용히 아무것도 안 하고 있었는데, 실행 기록을 볼 생각을 안 해서 한참 몰랐습니다.

저는 그때 작업 로그만 계속 뒤졌습니다. 로그가 비어 있으니 "아직 실행 전인가" 하고 넘겼습니다. 로그가 없다는 것 자체가 신호였는데 그렇게 안 읽었습니다.

대안으로 아예 enable 여부를 정기 점검에 넣어 두는 방법도 있습니다. 등록된 목록과 실제로 켜져 있는 목록을 대조해서, 빠진 게 있으면 알립니다.

알림을 갈라야 합니다

원인이 둘이면 알림도 둘이어야 합니다. 안 그러면 받을 때마다 처음부터 조사해야 합니다.

if not ran_recently(job):
    notify(f"[미실행] {job} — 마지막 실행 {last_run(job)}")
else:
    notify(f"[실행됐으나 실패] {job} — {last_error(job)}")

문구에 어느 쪽인지와 마지막 실행 시각만 넣어도 조사 시간이 크게 줄어듭니다. 저는 이걸 바꾸고 나서 알림만 보고 어디를 볼지 바로 정할 수 있게 됐습니다.

미실행은 결과로만은 못 잡습니다

여기가 핵심입니다. "결과물이 없다"로는 미실행을 못 잡습니다.

판정 방식 A 감지 B 감지
결과물이 있나 됩니다 됩니다(늦게)
실행 기록이 있나 — 됩니다(바로)
둘 다 본다 됩니다 됩니다

작업이 시작조차 안 하면 실패 알림을 보낼 코드도 안 돕니다. 그래서 미실행은 작업 바깥에서 감시해야 합니다. 안에서 스스로를 감시할 수는 없습니다.

저는 하루 한 번 도는 별도 점검에서 각 작업의 마지막 실행 시각을 훑고, 주기보다 오래됐으면 알리게 했습니다.

확인 못 한 것

겹침 때문에 실행이 건너뛰어졌을 때, 시스템이 그걸 어디에 남기는지는 확인하지 못했습니다. 잠금 파일로 막는 쪽으로 처리해서 더 파보지 않았습니다. 기록이 남는다면 그걸 바로 보는 편이 더 나을 것 같은데, 확인은 못 했습니다.

판정 — 계속 쓴다

원인을 둘로 나누는 것만으로 조사 시간이 줄었습니다. 코드 몇 줄이고, 알림을 볼 때마다 어디를 볼지 바로 정해집니다.

미실행 감시는 따로 둬야 합니다. 작업 안에 넣으면 그 작업이 안 돌 때 같이 안 돕니다. 바깥에서 봐야 합니다.

다만 감시하는 쪽도 안 돌 수 있습니다. 그래서 그쪽은 "확인했다"는 기록을 남기게 해두고, 그 기록이 끊기면 의심합니다.

직접 하실 분께

하나. 실패 알림에 "돌았나 안 돌았나" 를 반드시 넣으세요. 이 한 줄이 조사 방향을 정합니다.

둘. 마지막 실행 시각을 알림에 같이 넣습니다. 언제부터 안 돌았는지가 바로 보입니다.

셋. 미실행 감시는 작업 바깥에 둡니다. 안에서 자기를 감시할 수 없습니다.

넷. 임시로 띄운 것은 재부팅하면 사라집니다. 정식으로 등록했는지 확인하는 항목을 점검에 넣어 두세요.

다음에는 실행이 겹쳐서 건너뛰어진 경우를 기록으로 바로 확인할 방법이 있는지 찾아보려고 합니다. 되면 이어서 적겠습니다.