cron 기반 이메일 체크가 꼬이는 이유
몇 시간마다 작업을 실행하는 것은 이론적으로는 간단해 보이지만, 실제 운영 환경은 복잡합니다. 이전 실행이 메시지를 남겨둘 수 있고, 재시도가 쌓일 수 있으며, 느린 워커가 15분 전에 도착한 메시지를 가져갈 수도 있습니다. 이러한 잔여 메시지들은 대부분의 스크립트가 의존하는 단순한 "최신 이메일 우선" 규칙을 깨뜨립니다.
로컬 테스트는 깨끗한 편지함과 예측 가능한 타이밍에서 시작하기 때문에 통과합니다. 하지만 운영 환경에서는 동일한 코드가 잘못된 메시지를 가져오거나, 경고를 조용히 누락시키거나, 한꺼번에 여러 개의 알림을 보낼 수 있습니다. 팀들은 종근 임의의 지연 시간(delay)을 추가하여 문제를 임시방편으로 해결하려 하지만, 지연 시간은 레이스 컨디션(race condition)을 가릴 뿐이며, 부하가 높아지거나 이메일 지연 시간이 변하면 곧 한계에 부딪힙니다.
리스(lease) 개념: 편지함을 일회용 자산으로 전환하기
편지함 리스는 각 cron 실행이 준수해야 하는 작은 계약입니다:
- 독점적 소유권 – 하나의 실행이 하나의 편지함(또는 그 안의 고유한 네임스페이스)을 점유합니다.
- 시간 제한적 – 리스는 시작 시간과 만료 시간을 기록합니다.
- 라벨 검증 – 예상되는 모든 이메일은 작업이 확인하는 라벨을 포함합니다.
- 오래된 메시지 방지 – 작업은 제목이 일치하더라도 리스 기간을 벗어난 이메일은 무시합니다.
이제 작업은 "이메일이 도착했는가?"라고 묻는 대신 "내 리스 기간 중에 내 이메일이 도착했는가?"라고 묻습니다. 이러한 변화를 통해 코드는 메시지가 현재 실행 건에 속하는지 확인하도록 강제되며, 실행 간의 간섭(contamination)을 제거합니다.
일반적인 4시간 주기 cron에 이 패턴을 적용하는 방법
- 리스 ID 생성: 실행 시작 시 리스 ID를 생성하고 선택한 편지함 ID와 함께 저장합니다.
- 엄격한 필터 적용: 폴링 시 리스 라벨, 수신자 고유성, 특정 제목, 그리고 가장 중요한 수신 타임스탬프를 기준으로 일치 여부를 확인합니다.
- 리스 메타데이터 기록: 리스 ID, 편지함 ID, 그리고 일치하는 메시지의 정확한 수신 시간을 로그에 남깁니다.
로그에 이 세 가지 정보가 있으면, 실패 시 모호한 "이메일을 찾을 수 없음" 메시지 대신 리스 누락, 잘못된 경로의 편지함, 또는 기간 외 이메일 중 무엇이 문제인지 명확히 알 수 있습니다.
자동화를 방해하는 흔한 함정들
- 깔끔한 대시보드를 위해 편지함 이름을 재사용하는 경우 – 사람이 읽기 좋은 이름은 보기에는 좋지만, 공유 상태(shared state)를 다시 도입하게 됩니다.
- 파일 곳곳에 폴링 규칙을 흩어놓는 경우 – 일관성 없는 "신선도(freshness)" 정의는 오래된 메시지가 새어 들어오게 만듭니다.
- 리스 ID 로깅을 건너뛰는 경우 – 해당 식별자가 없으면 디버깅은 추측에 의존하게 되며, 이는 불안정한 체크가 지속되는 바로 그 원인이 됩니다.
이러한 실수를 피하면 시스템의 신뢰성을 유지하고 로그를 유용하게 활용할 수 있습니다.
격리가 불가능할 때는 필터를 강화하십시오
매 실행마다 전용 편지함을 만드는 것이 비현실적이라면, 더 엄격한 기준으로 보완하십시오:
- 수신 시간 범위 – 리스 시작 시간보다 오래된 이메일은 거부합니다.
- 수신자 고유성 – 제공업체가 허용한다면 실행별 주소나 고유 별칭(alias)을 사용합니다.
- 제목 지문(Subject fingerprint) – 제목 줄에 실행별 토큰을 포함합니다.
부분적인 리스 구현만으로도 디버깅 비용이 커지기 전에 상태 드리프트(state drift)를 획기적으로 줄일 수 있습니다.
반론: 왜 여전히 "그냥 지연 시간을 추가하라"는 말이 나오는가
일부 팀은 실행 사이에 몇 초간 sleep을 주는 것으로 충분하다고 주장합니다. 지연 시간은 이메일 지연이 버퍼 내에 머무는 동안에는 작동하지만, 제공업체의 지연이 증가하거나, 일시적인 백로그가 발생하거나, 스케일링 이벤트가 발생하면 즉시 가정이 깨집니다.
요약
각 실행을 고유한 편지함(또는 네임스페이스)에 결합하고, 예상되는 메시지에 라벨을 붙이며, 리스 식별자를 로깅함으로써 실행 간의 간섭을 제거하고, 실패를 관찰 가능하게 만들며, 마침내 예약된 알림에 필요한 신뢰성을 확보할 수 있습니다.
