왜 `exit code 0`가 당신에게 거짓말을 하는가

발행: (2026년 4월 3일 오후 06:01 GMT+9)
8 분 소요
원문: Dev.to

I’m happy to translate the article for you, but I’ll need the text you’d like translated. Could you please paste the content (or the portion you want translated) here? I’ll keep the source line exactly as you provided and preserve all formatting, markdown, and code blocks. Thank you!

“did it run?”의 문제

당신의 크론 작업이 새벽 2시에 실행되었습니다. 종료 코드는 0이었고, 모든 것이 정상인가요?

지난 달, 제 SIRENE 데이터 가져오기 스크립트가 매주 일요일마다 3 주 연속 성공적으로 실행되었습니다. 종료 코드 0, 로그에 오류 없음. 모든 지표가 초록색이었습니다.

하지만 실제로는 0 행을 가져왔고, 가져와야 할 1,680만 행을 놓쳤습니다. 3 주 동안 아무도 눈치채지 못했습니다.

대부분의 크론‑모니터링 도구는 한 가지 질문에만 답합니다: 작업이 실행되었는가?
이들은 데드맨 스위치를 사용합니다 — 스크립트가 끝나면 URL에 핑을 보내고, 핑이 도착하지 않으면 알림을 받습니다.

이는 유용하지만 가장 위험한 실패 모드를 놓칩니다: 작업은 성공적으로 실행됐지만 결과가 엉망인 경우.

  • 백업 스크립트는 실행되지만 데이터베이스 연결이 조용히 실패 → 0 바이트 백업
  • ETL 작업이 14 000 행 대신 12 행만 처리 → 종료 코드 0
  • 가져오기 작업이 2 분이 아니라 45 분이 걸림 → 오류는 없고, 단지 성능 저하만 발생

종료 코드 0은 “모든 것이 정상”이라는 뜻이 아닙니다. “나는 충돌하지 않았다”는 뜻일 뿐입니다.

Source:

당신의 모니터링이 출력 내용을 이해한다면?

저는 Crontiq 를 만들어 작업이 실행됐는지 그리고 출력이 정상인지에 대한 다른 질문에 답하고자 했습니다.

아이디어는 간단합니다. URL을 단순히 ping 하는 대신, 작업의 출력을 JSON 형태로 전송합니다:

# 스크립트 마지막에:
curl -X POST https://ping.crontiq.io/p/$API_KEY/import-sirene \
  -H "Content-Type: application/json" \
  -d "{\"rows\": $ROW_COUNT, \"duration_ms\": $DURATION, \"errors\": $ERROR_COUNT}"

Crontiq는 자동으로 세 가지 일을 수행합니다:

  1. JSON에서 모든 숫자 값을 추출합니다(중첩 객체 포함, 예: {"db": {"rows": 100}}db.rows = 100).
  2. 각 메트릭을 시간에 따라 스파크라인 그래프로 추적합니다.
  3. 이동 평균 + 2 표준 편차를 이용해 이상치를 감지합니다.

설정도, 임계값도, 스키마 정의도 필요 없습니다. JSON만 보내면 자동으로 알아서 처리합니다.

Source:

수학은 놀라울 정도로 간단합니다

이상 탐지는 머신러닝이나 복잡한 것이 아닙니다. 최근 10개의 값에 대한 이동 평균에 표준 편차 2배를 더한 것입니다:

average = avg(last_10_values)
stddev  = stddev(last_10_values)
is_anomaly = abs(current_value - average) > 2 * stddev

rows 메트릭이 보통 16,800,000 정도이고 갑자기 0으로 떨어지면, 이는 약 7,200 표준 편차에 해당하는 편차이며 → 알림이 발생합니다.

몇 주에 걸쳐 16.8 M에서 16.7 M으로 서서히 감소한다면, 아무 일도 일어나지 않습니다 — 정상 범위 안에 있기 때문입니다.

Healthchecks.io는 POST 본문을 원시 텍스트로 저장합니다. Cronitor는 메트릭을 특정 쿼리‑파라미터 구문(&metric=count:3329)으로 포맷하도록 요구합니다. Crontiq를 사용하면 스크립트가 이미 생성하는 JSON을 그대로 전송하면 됩니다.

스스로를 홍보하는 배지

모니터를 공개하면 Crontiq가 실시간 SVG 배지를 생성합니다:

[![SIRENE Import](https://crontiq.io/badge/cq_chk_xxx.svg)](https://crontiq.io/public/cq_chk_xxx)

배지는 “통과” 또는 “실패”만이 아니라 실제 데이터를 표시합니다:

import-sirene | 16.8M rows | ✓ healthy

GitHub README에 삽입하면 모든 방문자가 데이터 파이프라인이 실행 중이며 정상임을 확인할 수 있습니다. 배지를 클릭하면 스파크라인과 히스토리가 포함된 공개 상태 페이지가 열립니다.

예시

어떻게 구축되었는가

스택은 간단합니다:

  • Java 21 / Spring Boot 3.3 – 핑 수집을 처리합니다
  • PostgreSQL 파티션 테이블 사용 – 월별로 핑과 메트릭을 저장합니다
  • Redis – 빠른 배지 생성을 위해 모니터 상태를 캐시합니다
  • Hetzner – 단일 서버, Docker 컨테이너

핑 엔드포인트는 50 ms 미만에 응답합니다. JSON 파싱, 메트릭 추출, 이상 탐지는 비동기적으로 수행되므로 스크립트가 Crontiq가 끝날 때까지 기다릴 필요가 없습니다.

JSON 평탄화는 재귀적으로 이루어지며, 모든 중첩 구조는 점 표기법 키로 변환됩니다:

{
  "db": {
    "connections": 5,
    "pool_size": 20
  },
  "rows": 14209
}

다음과 같이 변환됩니다:

  • db.connections = 5
  • db.pool_size = 20
  • rows = 14209

각 키마다 대시보드에 개별 스파크라인이 표시됩니다.

무료. 정말 무료.

Crontiq는 AZMORIS 개발자 생태계에 대한 무료 첫 관문입니다: 20개의 모니터, 무제한 핑, 자동 메트릭, 이메일 알림 — 신용카드 필요 없음, 체험 기간도 없습니다.

왜 무료인가요? 처음에 내 자신의 문제(내 API 모니터링)를 해결하기 위해 만들었기 때문이며, 좋은 손실 리더는 유료 장벽보다 더 큰 가치를 제공합니다.

사용해 보세요: crontiq.io

Crontiq는 GEOREFER (프랑스 비즈니스 데이터 API), Doxnex (문서 인텔리전스), GreenCalc (탄소 발자국 API), 그리고 IDonex (신원 관리)를 포함하는 AZMORIS 생태계의 일부입니다.

0 조회
Back to Blog

관련 글

더 보기 »

JavaScript에서 오류 처리: Try, Catch, Finally

코드가 아무리 좋아도 오류는 피할 수 없습니다. 중요한 것은 오류를 어떻게 처리하느냐입니다. JavaScript는 try, catch, finally와 같은 강력한 도구를 제공합니다...