Node.js AbortController 실전 가이드: 타임아웃·취소 표준화로 장애 반경 줄이기
Node.js 운영에서 자주 겪는 문제는 “요청이 느리다”보다 느린 작업을 끝까지 붙잡고 놓지 않는 것입니다. 외부 API, DB, 파일 I/O 중 하나만 지연돼도 대기열이 길어지고, 결국 타임아웃 폭발로 이어집니다. 이 글에서는 AbortController를 기준으로 타임아웃·취소 정책을 통일해 장애 반경을 줄이는 방법을 정리합니다.
왜 AbortController 표준화가 필요한가
H3. 타임아웃은 있는데 취소는 없는 상태가 더 위험하다
많은 서비스가 5초 타임아웃은 걸어두지만, 실제 작업은 백그라운드에서 계속 도는 경우가 있습니다. 그 결과는 다음과 같습니다.
- 이미 실패 처리된 요청이 리소스를 계속 점유
- 소켓/커넥션/워커가 늦게 반환되어 순간 트래픽에 취약
- 재시도와 중복 작업이 겹치며 장애 반경 확대
핵심은 “시간 제한”과 “실제 취소”를 한 세트로 묶는 것입니다.
H3. 표준화가 안 되면 팀마다 에러 의미가 달라진다
A 모듈은 TimeoutError, B 모듈은 ECONNRESET, C 모듈은 일반 Error를 던지면
온콜 대응 시 원인 분류가 느려지고 자동 복구 룰도 복잡해집니다.
AbortController를 공통 규약으로 쓰면, “사용자 취소”, “서버 타임아웃”, “상위 요청 취소 전파”를 일관되게 다룰 수 있습니다.
Node.js에서 타임아웃·취소를 구현하는 기본 패턴
H3. fetch + AbortSignal.timeout으로 1차 표준화
Node.js 내장 fetch(undici 기반)를 사용하면 아래처럼 간단히 시작할 수 있습니다.
async function getProfile(userId: string) {
const signal = AbortSignal.timeout(3000);
const res = await fetch(`https://api.example.com/users/${userId}`, {
method: 'GET',
signal,
headers: { 'content-type': 'application/json' },
});
if (!res.ok) {
throw new Error(`Upstream failed: ${res.status}`);
}
return res.json();
}
포인트는 “호출부마다 제각각 setTimeout”이 아니라, AbortSignal 기반 API로 인터페이스를 통일하는 것입니다.
H3. 상위 요청 취소를 하위 작업으로 전파하기
API 핸들러에서 클라이언트 연결이 끊기면 하위 호출도 즉시 중단해야 합니다.
export async function handler(req, res) {
const controller = new AbortController();
req.on('close', () => controller.abort(new Error('client disconnected')));
try {
const data = await serviceFlow({ signal: controller.signal });
res.statusCode = 200;
res.end(JSON.stringify(data));
} catch (err) {
if (controller.signal.aborted) {
res.statusCode = 499; // 내부 규약 예시
return res.end('request canceled');
}
res.statusCode = 504;
res.end('upstream timeout');
}
}
실무에서는 HTTP 프레임워크(Express/Fastify/Nest)별 훅으로 연결 종료 이벤트를 동일하게 매핑해두면 운영이 훨씬 단순해집니다.
실무에서 자주 놓치는 포인트
H3. DB/큐 라이브러리도 “취소 가능” 여부를 확인해야 한다
모든 라이브러리가 AbortSignal을 지원하지는 않습니다. 지원이 없다면 아래 우회 전략이 필요합니다.
- 작업 단위를 더 작게 쪼개 중단 지점을 만든다
- 드라이버 레벨 타임아웃(쿼리 타임아웃/락 타임아웃)을 별도 설정한다
- 오래 걸리는 작업은 워커 큐로 이동하고, TTL/재시도 상한을 둔다
“AbortController 썼다 = 취소 완성”이 아니라, 라이브러리 실제 동작까지 검증해야 안전합니다.
H3. 취소 에러를 장애 에러와 분리해 관측하기
취소는 정상 제어 흐름일 때가 많습니다. 로그/메트릭에서 일반 에러와 섞으면 알람 피로도가 올라갑니다.
권장 분리:
canceled_by_clienttimeout_budget_exceededupstream_abortedunexpected_failure
이렇게 분류하면 “진짜 장애”와 “의도된 종료”를 빠르게 구분할 수 있습니다.
배포 전 체크리스트
H3. 코드/설계 점검
- 모든 외부 I/O 경로에
signal인자가 전달되는가 - 상위 요청 취소가 하위 API/DB/큐 작업까지 전파되는가
- 타임아웃 값이 서비스 SLO와 맞는가(너무 짧거나 길지 않은가)
H3. 안전/윤리 점검
- 예시 코드/로그에 API 키·토큰·개인정보가 없는가
- 특정 벤더/서비스를 근거 없이 비방하지 않았는가
- 검증 없는 성능 수치나 과장 표현이 없는가
요약
AbortController는 “코드 스타일”이 아니라 장애 제어 장치입니다. 타임아웃·취소·전파를 한 규약으로 맞추면 대기열 폭증과 리소스 고갈을 크게 줄일 수 있습니다. 특히 트래픽이 급증하는 시간대일수록 “빨리 실패하고, 즉시 정리하는” 시스템이 더 오래 버팁니다.
내부 링크
이 글은 AI가 작성/정리하고, 오너가 방향을 결정한 프로젝트 운영 로그입니다.