본문 바로가기
Q&A해결2026년 7월 23일1분 읽기

setInterval로 도는 배치가 이전 게 안 끝났는데 다음 게 또 시작됩니다

j
job_taeho
조회 551댓글 4

외부 API에서 데이터 긁어와서 저장하는 배치를 setInterval로 1분마다 돌리고 있어요. 평소엔 몇 초면 끝나는데, 외부 API가 느려지거나 데이터가 많은 날엔 한 번 도는 데 2~3분씩 걸립니다.

그러면 이전 실행이 아직 안 끝났는데 다음 1분 tick이 와서 또 실행되고, 그게 쌓이면서 같은 데이터를 중복 저장하거나 외부 API rate limit에 걸려버려요.

setInterval(async () => {
  await syncFromExternal() // 오래 걸리면 겹침
}, 60_000)

겹치지 않게, 이전 게 완전히 끝난 다음에 다음 걸 돌리려면 어떻게 해야 하나요?

댓글 4

김김영삼3개월 전
setInterval의 함정이에요. setInterval은 콜백이 끝났는지 신경 안 쓰고 무조건 정해진 간격마다 큐에 넣습니다. 콜백이 async면 interval은 그 Promise를 안 기다리니까, 실행이 간격보다 오래 걸리면 그대로 겹쳐요.

제일 깔끔한 건 setInterval을 버리고 재귀 setTimeout으로 바꾸는 겁니다. 한 번 실행이 완전히 끝난 다음에 다음 타이머를 거는 방식이에요.

async function loop() {
try { await syncFromExternal() }
catch (e) { console.error(e) }
finally { setTimeout(loop, 60_000) }
}
loop()

이러면 실행이 2분 걸리면 그게 끝나고 1분 뒤에 다음 게 도니 절대 안 겹칩니다. 간격의 의미가 '시작 간격 1분'에서 '끝난 뒤 1분 쉼'으로 바뀌는 건 알아두세요.

setInterval을 꼭 유지하고 싶으면 실행 중 플래그로 겹침만 막을 수도 있어요. let running = false 두고 콜백 진입 시 if (running) return; running = true; 하고 finally에서 running = false. 이건 tick 자체는 정시에 오되 이미 돌고 있으면 그 tick을 건너뜁니다.

어느 쪽이든 catch/finally를 꼭 넣으세요. 안에서 에러가 나면 재귀 방식은 다음 스케줄이 안 걸리고, 플래그 방식은 running이 true로 박혀서 영영 멈추는 사고가 납니다. 재스케줄과 플래그 해제를 finally에서 하는 게 그래서 중요해요.
jjob_taeho(guest)3개월 전
재귀 setTimeout으로 바꿨더니 겹침은 사라졌어요. 근데 이제 서버 재배포할 때 도중이던 작업이 반쯤 끊기던데, 종료 신호 받으면 지금 돌던 건 끝내고 다음 스케줄만 안 걸게 하려면 어떻게 잡으면 될까요?
김김영삼3개월 전
graceful shutdown이랑 엮는 거죠. 플래그 하나 더 두면 됩니다. let stopping = false 두고 SIGTERM 핸들러에서 stopping = true. 그리고 loop의 finally를 if (!stopping) setTimeout(loop, 60_000) 로 조건부 재스케줄로 바꾸세요. 이러면 지금 돌던 syncFromExternal은 await로 끝까지 완료되고, 그 다음 스케줄만 안 걸립니다. 추가로 setTimeout이 리턴하는 핸들을 변수에 담아뒀다가 shutdown 시 clearTimeout 해주면 대기 중이던 타이머도 즉시 취소돼요. 마지막으로 server.close()로 새 요청을 막고, 돌던 배치 Promise를 await 한 다음 process.exit(0) 하는 순서로 짜면 배포 중 중간에 끊기는 일 없이 깔끔하게 빠집니다.
jjob_taeho(guest)3개월 전
말씀대로 stopping 플래그로 재스케줄만 막으니 배포할 때 딱 떨어지네요. 덕분에 해결됐습니다.
Ctrl+Enter로 등록