n8n 워크플로우를 정각에 스케줄링하는 것을 멈추다 — 제 429 오류가 사라진 이유
요약
n8n 워크플로우를 정각에 스케줄링할 경우 발생하는 API 속도 제한(rate limit) 문제를 경험하고 해결 과정을 공유합니다. 여러 워크플로우가 같은 시간에 폭발적으로 실행되면서 '섬기는 무리' 현상을 일으켜 429 오류가 발생했습니다. 이 문제는 시간 단위의 사고 모델과 분 단위의 API 강제 적용 창 간의 불일치에서 기인하며, 스케줄링 시간을 정각을 피하고 의도적으로 간격을 두는 것으로 해결할 수 있습니다.
핵심 포인트
- 정각(09:00)에 여러 워크플로우를 실행하면 속도 제한 오류가 발생하기 쉽습니다.
- API의 속도 제한은 시간 단위가 아닌 분 단위로 적용됨을 이해해야 합니다.
- 스케줄링 시간을 정각이 아닌, 의도적으로 간격을 둔 특정 분(예: 09:07)으로 설정하는 것이 중요합니다.
n8n 워크플로우를 정각에 스케줄링하는 것을 멈추다 — 제 429 오류가 사라진 이유
n8n에서 LLM 워크플로우를 프로덕션 환경에서 운영하는 과정의 '빌드-인-퍼블릭(build-in-public)' 시리즈 중 일부. 이번 주 사건은 돌이켜보면 창피했습니다: 세 개의 워크플로우, 하나의 API 키가 모두 정각 09:00에 실행되었고 — API의 속도 제한기(rate limiter)는 자신이 해야 할 일을 정확히 수행했습니다.
아침의 세 가지 워크플로우가 저를 DDoS하게 만들었습니다
지난 화요일, 저는 동일한 분 내에 타임스탬프가 찍힌 세 개의 항목이 있는 데드 레터 큐(dead-letter queue)로 눈을 떴습니다. 제 프로덕션 워크플로우 중 세 가지 — 콘텐츠 리서치 스윕, SEO 초안 생성기, 인박스 분류 실행 — 가 모두 대략 09:00에 실패했습니다. 모든 오류는 동일했습니다:
429 Too Many Requests
세 개의 워크플로우, 하나의 공유 API 키가 정각에 같은 LLM 엔드포인트를 호출한 것입니다. 가장 먼저 통과한 것이 토큰을 가져갔고. 두 번째 것이 겨우 통과했습니다. 세 번째 것이 속도 제한(rate limit)을 소진시켰습니다. API가 다운된 것이 아니었습니다. 제 스케줄링이 그저 무례했던 것뿐입니다.
조사에는 10분과 하나의 실행 로그가 필요했습니다
n8n의 실행 목록은 한눈에 모든 이야기를 들려주었습니다: 세 번의 실행, 시작 시간은 09:00:03, 09:00:11, 09:00:19. 세 가지 모두 병렬 LLM 호출의 폭발과 함께 시작되었습니다 — 각 5개에서 8개의 요청이 노드가 가능한 한 빠르게 발사된 것입니다. 이는 분당 하나의 속도 제한 창(per-minute rate window) 내에 약 20개의 요청을 의미하며, 이 키는 그보다 훨씬 낮은 수준으로 제한되어 있었습니다.
API 대시보드는 이를 확인시켜 주었습니다: 09:00에 수직적인 급락의 요청이 있었고, 그다음은 침묵이 찾아왔습니다. 전형적인 '섬기는 무리(thundering herd)' 현상이었습니다. 저는 작고 스스로 유발한 DDoS를 만들었고 그것을 제 자신의 API 키에 겨냥했습니다.
여기 불편한 부분이 있습니다: 저는 이 모든 스케줄을 다른 이유로, 다른 날짜에 :00으로 설정했습니다. '아침'이라서 리서치 스윕은 09:00. '역시 아침'이라서 SEO 초안은 09:00. 인박스 분류는 — 추측하셨겠지만 — '아침'이라서 09:00. 모든 스케줄은 개별적으로 합리적이었습니다. 하지만 함께 모이자, 저는 하나의 워크플로우씩 신중하게 조립한 쌓임 더미가 되었습니다.
왜 :00이 함정인가요
시간 단위로 스케줄링하는 것은 마치 모두가 같은 출구로 경기장을 나가는 크론(cron) 방식과 같습니다. 이는 기본 사고 모델입니다. 반올림된 숫자가 깔끔하게 느껴지기 때문에, n8n의 Schedule Trigger는 0 9 * * *을 가장 쉬운 경로로 만듭니다. 하지만 API는 시간 단위가 아닌 분 단위로 속도 제한(rate-limit)을 부과합니다 (때로는 초 단위). 당신의 사고 모델은 시간 단위이지만, 그들의 강제 적용 창(enforcement window)은 분 단위입니다. 이 불일치가 바로 전체 버그였습니다.
그리고 이것은 단순히 당신의 워크플로우끼리 충돌하는 문제만이 아닙니다. 해당 API를 사용하는 다른 모든 고객들도 :00에 스케줄링했습니다. 제공업체의 매 시간 가장 바쁜 분은 정각입니다. 09:00에 스케줄링한다는 것은 전 세계의 09:00과 경쟁한다는 의미입니다. 09:07에 스케줄링한다는 것은 거의 아무와도 경쟁하지 않는다는 의미입니다.
세 단계로 해결하기
레벨 1: 수동으로 간격을 두기 (5분 작업). 저는 n8n UI에서 세 개의 스케줄을 09:07, 09:23, 09:41로 옮겼습니다. 코드도 없고, 노드도 없고, 재배포도 필요 없습니다. 소수점 근처의 숫자들(prime-ish numbers)로, 의도적으로 반올림되지 않은 값들을 각기 다른 15분 간격에 배치했습니다. 이것만으로 병목 현상이 사라졌습니다: 세 워크플로우의 요청 급증이 더 이상 같은 속도 제한 창을 공유하지 않게 된 것입니다.
레벨 2: 버스트가 발생하는 경우 지터(jitter) 적용하기. 연구 스윕(research sweep)의 경우—시작하는 순간 8개의 병렬 LLM 호출을 실행하는 워크플로우—저는 시작 부분에 무작위 지연 시간(0–90초)을 가진 작은 Wait 노드를 추가했습니다 ({{ Math.floor(Math.random() * 90) }}s). 설령 두 개의 워크플로우가 다시 같은 분에 도착하더라도, 그들의 급증은 초 단위로 정렬되지 않을 것입니다. 무작위성은 미래의 제가 또 다른 :00을 스케줄링하는 것에 대한 저렴한 보험입니다.
레벨 3: 429를 예외가 아닌 예상되는 것으로 취급하기. 간격을 두는 것은 충돌을 줄여주지만, 완전히 없애지는 못합니다. API는 나쁜 날이 있고, 공유 키에는 다른 사용자들이 있기 때문입니다. 따라서 HTTP 노드는 이제 429를 모든 일시적 실패와 마찬가지로 처리합니다: 지수 백오프(exponential backoff) (2초 → 8초 → 32초), 최대 세 번의 재시도 후 데드 레터 큐(dead-letter queue)로 보내고, 빨간색 실행 상태가 아닙니다. 저는 재시도 예산 규율에 대해 별도로 작성했습니다. 여기서 요점은 429 처리가 인시던트 대응이 아니라 워크플로우의 '설계' 영역에 속한다는 것입니다.
솔직한 수치
이전: 3개의 워크플로우에서 매주 약 6~10건의 429 거부 응답(rejections)이 발생했으며, 각각은 제가 수동으로 재실행해야 하는 데드레터 항목이었습니다. 스케줄을 분산시킨 2주 후: 제로. '적게'가 아니라 제로입니다. 수동 재실행에 걸리는 시간은 주당 약 20분에서 아무것도 아닌 것이 되었습니다.
수정하는 비용은 워크플로우당 설정 변경 한 번과 Wait 노드 하나였습니다. 고치지 않았을 때의 비용은 실패한 실행들을 매주 반복해서 재실행하고, 완벽하게 합리적으로 작동하는 API에 조용히 불만을 품는 의식적인 시간이었습니다.
원라인 규칙
시간 정각에는 절대 스케줄링하지 마세요. 정각이 아닌 분을 선택하세요. 15분 단위로 분산시키세요. 급증할 수 있는 모든 것에 지터(jitter)를 추가하세요. 이것은 제가 구축한 n8n 설정 전체에서 가장 저렴한 신뢰성 개선 방법이며, 세 개의 워크플로우를 같은 시간에 맞춰 작동시키고 속도 제한기(rate limiter)가 제 역할을 하는 것을 보면서 배웠습니다.
만약 여러 개의 n8n 워크플로우를 하나의 API 키로 실행하고 있다면, 지금 바로 스케줄 트리거(Schedule Triggers)를 확인해 보세요. 저는 적어도 두 개 이상이 :00으로 설정되어 있을 것이라고 확신합니다.
저는 AI Automation Lab을 구축하고 있습니다 — 프로덕션 레디 n8n 워크플로우 템플릿입니다. 무료인 AI Hook Generator가 대부분의 사람들이 시작하는 곳이며, AI Content Repurposing Engine($29)은 제가 지난주에 해체한 유료 파이프라인입니다. 저의 오픈 소스 패턴은 github.com/zhp910318/n8n-ai-automation-lab에서 확인할 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기